diff --git a/pkgs/build-support/rust/build-rust-crate/configure-crate.nix b/pkgs/build-support/rust/build-rust-crate/configure-crate.nix index bbe7090aed3d7..013b99a77b4b2 100644 --- a/pkgs/build-support/rust/build-rust-crate/configure-crate.nix +++ b/pkgs/build-support/rust/build-rust-crate/configure-crate.nix @@ -31,12 +31,25 @@ let version_ = lib.splitString "-" crateVersion; completeDepsDir = lib.concatStringsSep " " completeDeps; completeBuildDepsDir = lib.concatStringsSep " " completeBuildDeps; in '' - cd ${workspace_member} ${echo_colored colors} ${noisily colors verbose} source ${./lib.sh} + ${lib.optionalString (workspace_member != null) '' + noisily cd "${workspace_member}" +''} + ${lib.optionalString (workspace_member == null) '' + echo_colored "Searching for matching Cargo.toml (${crateName})" + local cargo_toml_dir=$(matching_cargo_toml_dir "${crateName}") + if [ -z "$cargo_toml_dir" ]; then + echo_error "ERROR configuring ${crateName}: No matching Cargo.toml in $(pwd) found." >&2 + exit 23 + fi + noisily cd "$cargo_toml_dir" +''} + runHook preConfigure + symlink_dependency() { # $1 is the nix-store path of a dependency # $2 is the target path diff --git a/pkgs/build-support/rust/build-rust-crate/default.nix b/pkgs/build-support/rust/build-rust-crate/default.nix index 35cb49293e84a..70d48bef8c9b0 100644 --- a/pkgs/build-support/rust/build-rust-crate/default.nix +++ b/pkgs/build-support/rust/build-rust-crate/default.nix @@ -88,7 +88,7 @@ stdenv.mkDerivation (rec { src = crate.src or (fetchCrate { inherit (crate) crateName version sha256; }); name = "rust_${crate.crateName}-${crate.version}${lib.optionalString buildTests_ "-test"}"; - depsBuildBuild = [ rust stdenv.cc ]; + depsBuildBuild = [ rust stdenv.cc cargo jq ]; buildInputs = (crate.buildInputs or []) ++ buildInputs_; dependencies = map lib.getLib dependencies_; buildDependencies = map lib.getLib buildDependencies_; @@ -114,6 +114,8 @@ stdenv.mkDerivation (rec { in lib.substring 0 10 hashedMetadata; build = crate.build or ""; + # Either set to a concrete sub path to the crate root + # or use `null` for auto-detect. workspace_member = crate.workspace_member or "."; crateVersion = crate.version; crateDescription = crate.description or ""; diff --git a/pkgs/build-support/rust/build-rust-crate/lib.sh b/pkgs/build-support/rust/build-rust-crate/lib.sh index 35e804aa10445..0f08c133e5572 100644 --- a/pkgs/build-support/rust/build-rust-crate/lib.sh +++ b/pkgs/build-support/rust/build-rust-crate/lib.sh @@ -144,3 +144,37 @@ search_for_bin_path() { exit 1 fi } + +# Extracts cargo_toml_path of the matching crate. +matching_cargo_toml_path() { + local manifest_path="$1" + local expected_crate_name="$2" + + # If the Cargo.toml is not a workspace root, + # it will only contain one package in ".packages" + # because "--no-deps" suppressed dependency resolution. + # + # But to make it more general, we search for a matching + # crate in all packages and use the manifest path that + # is referenced there. + cargo metadata --no-deps --format-version 1 \ + --manifest-path "$manifest_path" \ + | jq -r '.packages[] + | select( .name == "'$expected_crate_name'") + | .manifest_path' +} + +# Find a Cargo.toml in the current or any sub directory +# with a matching crate name. +matching_cargo_toml_dir() { + local expected_crate_name="$1" + + find -L -name Cargo.toml | sort | while read manifest_path; do + echo "...checking manifest_path $manifest_path" >&2 + local matching_path="$(matching_cargo_toml_path "$manifest_path" "$expected_crate_name")" + if [ -n "${matching_path}" ]; then + echo "$(dirname $matching_path)" + break + fi + done +} \ No newline at end of file diff --git a/pkgs/build-support/rust/build-rust-crate/test/default.nix b/pkgs/build-support/rust/build-rust-crate/test/default.nix index 6aad02992c1b9..710045664686b 100644 --- a/pkgs/build-support/rust/build-rust-crate/test/default.nix +++ b/pkgs/build-support/rust/build-rust-crate/test/default.nix @@ -8,6 +8,14 @@ let } // args; in buildRustCrate p; + mkCargoToml = + { name, crateVersion ? "0.1.0", path ? "Cargo.toml" }: + mkFile path '' + [package] + name = ${builtins.toJSON name} + version = ${builtins.toJSON crateVersion} + ''; + mkFile = destination: text: writeTextFile { name = "src"; destination = "/${destination}"; @@ -89,7 +97,7 @@ let in rec { tests = let - cases = { + cases = rec { libPath = { libPath = "src/my_lib.rs"; src = mkLib "src/my_lib.rs"; }; srcLib = { src = mkLib "src/lib.rs"; }; @@ -220,6 +228,40 @@ let ]; }; }; + rustCargoTomlInSubDir = { + # The "workspace_member" can be set to the sub directory with the crate to build. + # By default ".", meaning the top level directory is assumed. + # Using null will trigger a search. + workspace_member = null; + src = symlinkJoin rec { + name = "find-cargo-toml"; + paths = [ + (mkCargoToml { name = "ignoreMe"; }) + (mkTestFileWithMain "src/main.rs" "ignore_main") + + (mkCargoToml { name = "rustCargoTomlInSubDir"; path = "subdir/Cargo.toml"; }) + (mkTestFileWithMain "subdir/src/main.rs" "src_main") + (mkTestFile "subdir/tests/foo/main.rs" "tests_foo") + (mkTestFile "subdir/tests/bar/main.rs" "tests_bar") + ]; + }; + buildTests = true; + expectedTestOutputs = [ + "test src_main ... ok" + "test tests_foo ... ok" + "test tests_bar ... ok" + ]; + }; + + rustCargoTomlInTopDir = + let + withoutCargoTomlSearch = builtins.removeAttrs rustCargoTomlInSubDir [ "workspace_member" ]; + in + withoutCargoTomlSearch // { + expectedTestOutputs = [ + "test ignore_main ... ok" + ]; + }; }; brotliCrates = (callPackage ./brotli-crates.nix {}); in lib.mapAttrs (key: value: mkTest (value // lib.optionalAttrs (!value?crateName) { crateName = key; })) cases // {