ipetkov / crane

A Nix library for building cargo projects. Never build twice thanks to incremental artifact caching.
https://crane.dev
MIT License
914 stars 86 forks source link

Automatic `pname` selection for cargo workspace results in `nix run` failing with an obtuse error #236

Closed benwis closed 1 year ago

benwis commented 1 year ago

At the risk of looking silly again, I was able to get this to build fine, but I cannot get it to run with nix-run .

{
  description = "Build a cargo project with a custom toolchain";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

    crane = {
      url = "github:ipetkov/crane";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    flake-utils.url = "github:numtide/flake-utils";

    rust-overlay = {
      url = "github:oxalica/rust-overlay";
      inputs = {
        nixpkgs.follows = "nixpkgs";
        flake-utils.follows = "flake-utils";
      };
    };
    advisory-db = {
      url = "github:rustsec/advisory-db";
      flake = false;
    };
  };

  outputs = { self, nixpkgs, crane, flake-utils, advisory-db, rust-overlay, ... }:
    flake-utils.lib.eachDefaultSystem
      (system:
        let
          pkgs = import nixpkgs {
            inherit system;
            overlays = [ (import rust-overlay) ];
          };

          rustTarget = pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default.override {
            extensions = [ "rust-src" "rust-analyzer" ];
            targets = [ "wasm32-unknown-unknown" ];
          });

          # NB: we don't need to overlay our custom toolchain for the *entire*
          # pkgs (which would require rebuidling anything else which uses rust).
          # Instead, we just want to update the scope that crane will use by appendings
          inherit (pkgs) lib;
          # our specific toolchain there.
          craneLib = (crane.mkLib pkgs).overrideToolchain rustTarget;
          #craneLib = crane.lib.${system};
          # Only keeps markdown files
          protoFilter = path: _type: builtins.match ".*proto$" path != null;
          sqlxFilter = path: _type: builtins.match ".*json$" path != null;
          protoOrCargo = path: type:
            (protoFilter path type) || (craneLib.filterCargoSources path type) || (sqlxFilter path type);
          # other attributes omitted
          src = lib.cleanSourceWith {
            src = ./.; # The original, unfiltered source
            filter = protoOrCargo;
          };
          #    src = craneLib.cleanCargoSource ./.;

          buildInputs = [
            # Add additional build inputs here
            pkgs.pkg-config
            pkgs.openssl
            pkgs.protobuf
          ] ++ lib.optionals pkgs.stdenv.isDarwin [
            # Additional darwin specific inputs can be set here
            pkgs.libiconv
          ];

          # Build *just* the cargo dependencies, so we can reuse
          # all of that work (e.g. via cachix) when running in CI
          cargoArtifacts = craneLib.buildDepsOnly {
            inherit src buildInputs;
          };

          # Build the actual crate itself, reusing the dependency
          # artifacts from above.
          vidette = craneLib.buildPackage {
            inherit cargoArtifacts src buildInputs;

            # Prevent cargo test and nextest from duplicating tests
            doCheck = false;
            # ALL CAPITAL derivations will get forwarded to mkDerivation and will set the env var during build
            SQLX_OFFLINE = "true";
          };
        in
        {
          checks = {
            # Build the crate as part of `nix flake check` for convenience
            inherit vidette;

            # Run clippy (and deny all warnings) on the crate source,
            # again, resuing the dependency artifacts from above.
            #
            # Note that this is done as a separate derivation so that
            # we can block the CI if there are issues here, but not
            # prevent downstream consumers from building our crate by itself.
            vidette-clippy = craneLib.cargoClippy {
              inherit cargoArtifacts src buildInputs;
              cargoClippyExtraArgs = "--all-targets -- --deny warnings";
            };

            vidette-doc = craneLib.cargoDoc {
              inherit cargoArtifacts src buildInputs;
            };

            # Check formatting
            vidette-fmt = craneLib.cargoFmt {
              inherit src;
            };

            # Audit dependencies
            vidette-audit = craneLib.cargoAudit {
              inherit src advisory-db;
            };

            # Run tests with cargo-nextest
            # Consider setting `doCheck = false` on `vidette` if you do not want
            # the tests to run twice
            # vidette-nextest = craneLib.cargoNextest {
            #  inherit cargoArtifacts src buildInputs;
            #  partitions = 1;
            #  partitionType = "count";
            #};
          } // lib.optionalAttrs (system == "x86_64-linux") {
            # NB: cargo-tarpaulin only supports x86_64 systems
            # Check code coverage (note: this will not upload coverage anywhere)
            #vidette-coverage = craneLib.cargoTarpaulin {
            #  inherit cargoArtifacts src;
            #};
          };

          packages.default = vidette;

          apps.default = flake-utils.lib.mkApp {
            drv = vidette;
          };

          devShells.default = pkgs.mkShell {
            inputsFrom = builtins.attrValues self.checks;

            # Extra inputs can be added here
            nativeBuildInputs = with pkgs; [
              rustTarget
              openssl
              sqlx-cli
              pkg-config
              protobuf
              cargo-watch
            ];
          };
        });
}

It keeps giving this error:

error: unable to execute '/nix/store/lffzr03gd6kafrc5vamnf2v7mr3y7q5z-cargo-package-0.0.1/bin/cargo-package': No such file or directory

If I look in that directory, the vidette package is in there, and the Nix docs suggest apps.default or packages.default should set it.

ipetkov commented 1 year ago

Ah this error is from an unfortunate interaction of multiple things...

Basically apps/nix run assumes that the name of the binary to run happens to be the same as the pname of the derivation itself.

Crane will try to set pname by peeking at the root Cargo.toml, but if it doesn't contain a package definition it will default to "cargo-package" as you can see in the error.

The easiest way to fix this is to set pname = "..."; explicitly in buildPackage and substitute the name of the actual binary that will run.

Hope this helps!

benwis commented 1 year ago

Ah, that makes sense! And it works, thanks for the insight.

ipetkov commented 1 year ago

Glad to help! Thanks for reporting this too, btw, I think I should revisit that default pname selection behavior (or at least have it be more noisy) so stuff like this isn't so opaque to anyone working with it for the first time...

I'll leave the issue open to track making some improvements there!