about summary refs log tree commit diff
path: root/nixpkgs/pkgs/build-support/rust/build-rust-crate
diff options
context:
space:
mode:
authorAlyssa Ross <hi@alyssa.is>2020-01-11 23:37:02 +0000
committerAlyssa Ross <hi@alyssa.is>2020-01-11 23:41:30 +0000
commit6c557e3f1c28cf87e9fba232811d6875dd1399c1 (patch)
tree035a071d5d8980df6de0fa42e2ef8fc0cce7055e /nixpkgs/pkgs/build-support/rust/build-rust-crate
parentda7500bc026e937ac7fce7b50f67a0e1765737a7 (diff)
parente4134747f5666bcab8680aff67fa3b63384f9a0f (diff)
downloadnixlib-6c557e3f1c28cf87e9fba232811d6875dd1399c1.tar
nixlib-6c557e3f1c28cf87e9fba232811d6875dd1399c1.tar.gz
nixlib-6c557e3f1c28cf87e9fba232811d6875dd1399c1.tar.bz2
nixlib-6c557e3f1c28cf87e9fba232811d6875dd1399c1.tar.lz
nixlib-6c557e3f1c28cf87e9fba232811d6875dd1399c1.tar.xz
nixlib-6c557e3f1c28cf87e9fba232811d6875dd1399c1.tar.zst
nixlib-6c557e3f1c28cf87e9fba232811d6875dd1399c1.zip
Merge commit 'e4134747f5666bcab8680aff67fa3b63384f9a0f'
Diffstat (limited to 'nixpkgs/pkgs/build-support/rust/build-rust-crate')
-rw-r--r--nixpkgs/pkgs/build-support/rust/build-rust-crate/build-crate.nix214
-rw-r--r--nixpkgs/pkgs/build-support/rust/build-rust-crate/configure-crate.nix17
-rw-r--r--nixpkgs/pkgs/build-support/rust/build-rust-crate/default.nix173
-rw-r--r--nixpkgs/pkgs/build-support/rust/build-rust-crate/helpers.nix10
-rw-r--r--nixpkgs/pkgs/build-support/rust/build-rust-crate/install-crate.nix45
-rw-r--r--nixpkgs/pkgs/build-support/rust/build-rust-crate/lib.sh138
-rw-r--r--nixpkgs/pkgs/build-support/rust/build-rust-crate/log.nix33
-rw-r--r--nixpkgs/pkgs/build-support/rust/build-rust-crate/test/default.nix119
8 files changed, 486 insertions, 263 deletions
diff --git a/nixpkgs/pkgs/build-support/rust/build-rust-crate/build-crate.nix b/nixpkgs/pkgs/build-support/rust/build-rust-crate/build-crate.nix
index e0a52e62561b..4e2e2af1aa77 100644
--- a/nixpkgs/pkgs/build-support/rust/build-rust-crate/build-crate.nix
+++ b/nixpkgs/pkgs/build-support/rust/build-rust-crate/build-crate.nix
@@ -1,169 +1,109 @@
-{ lib, stdenv, echo_build_heading, noisily, makeDeps }:
+{ lib, stdenv, echo_build_heading, noisily, mkRustcDepArgs, rust }:
 { crateName,
   dependencies,
   crateFeatures, crateRenames, libName, release, libPath,
   crateType, metadata, crateBin, hasCrateBin,
-  extraRustcOpts, verbose, colors }:
+  extraRustcOpts, verbose, colors,
+  buildTests
+}:
 
   let
-
-    deps = makeDeps dependencies crateRenames;
-    rustcOpts =
-      lib.lists.foldl' (opts: opt: opts + " " + opt)
-        (if release then "-C opt-level=3" else "-C debuginfo=2")
-        (["-C codegen-units=$NIX_BUILD_CORES"] ++ extraRustcOpts);
+    baseRustcOpts =
+      [(if release then "-C opt-level=3" else "-C debuginfo=2")]
+      ++ ["-C codegen-units=$NIX_BUILD_CORES"]
+      ++ [(mkRustcDepArgs dependencies crateRenames)]
+      ++ [crateFeatures]
+      ++ extraRustcOpts
+      ++ lib.optional (stdenv.hostPlatform != stdenv.buildPlatform) "--target ${rust.toRustTarget stdenv.hostPlatform} -C linker=${stdenv.hostPlatform.config}-gcc"
+    ;
     rustcMeta = "-C metadata=${metadata} -C extra-filename=-${metadata}";
 
-    # Some platforms have different names for rustc.
-    rustPlatform =
-      with stdenv.hostPlatform.parsed;
-      let cpu_ = if cpu.name == "armv7a" then "armv7"
-                 else cpu.name;
-          vendor_ = vendor.name;
-          kernel_ = kernel.name;
-          abi_ = abi.name;
-      in
-      "${cpu_}-${vendor_}-${kernel_}-${abi_}";
+
+    # build the final rustc arguments that can be different between different
+    # crates
+    libRustcOpts = lib.concatStringsSep " " (
+      baseRustcOpts
+      ++ [rustcMeta]
+      ++ (map (x: "--crate-type ${x}") crateType)
+    );
+
+    binRustcOpts = lib.concatStringsSep " " (
+      baseRustcOpts
+    );
+
+    build_bin = if buildTests then "build_bin_test" else "build_bin";
   in ''
     runHook preBuild
-    norm=""
-    bold=""
-    green=""
-    boldgreen=""
-    if [[ "${colors}" == "always" ]]; then
-      norm="$(printf '\033[0m')" #returns to "normal"
-      bold="$(printf '\033[0;1m')" #set bold
-      green="$(printf '\033[0;32m')" #set green
-      boldgreen="$(printf '\033[0;1;32m')" #set bold, and set green.
-    fi
     ${echo_build_heading colors}
     ${noisily colors verbose}
 
-    build_lib() {
-       lib_src=$1
-       echo_build_heading $lib_src ${libName}
-
-       noisily rustc --crate-name $CRATE_NAME $lib_src \
-         ${lib.strings.concatStrings (map (x: " --crate-type ${x}") crateType)}  \
-         ${rustcOpts} ${rustcMeta} ${crateFeatures} --out-dir target/lib \
-         --emit=dep-info,link -L dependency=target/deps ${deps} --cap-lints allow \
-         $BUILD_OUT_DIR $EXTRA_BUILD $EXTRA_FEATURES --color ${colors}
-
-       EXTRA_LIB=" --extern $CRATE_NAME=target/lib/lib$CRATE_NAME-${metadata}.rlib"
-       if [ -e target/deps/lib$CRATE_NAME-${metadata}${stdenv.hostPlatform.extensions.sharedLibrary} ]; then
-          EXTRA_LIB="$EXTRA_LIB --extern $CRATE_NAME=target/lib/lib$CRATE_NAME-${metadata}${stdenv.hostPlatform.extensions.sharedLibrary}"
-       fi
-    }
-
-    build_bin() {
-      crate_name=$1
-      crate_name_=$(echo $crate_name | sed -e "s/-/_/g")
-      main_file=""
-      if [[ ! -z $2 ]]; then
-        main_file=$2
-      fi
-      echo_build_heading $@
-      noisily rustc --crate-name $crate_name_ $main_file --crate-type bin ${rustcOpts}\
-        ${crateFeatures} --out-dir target/bin --emit=dep-info,link -L dependency=target/deps \
-        $LINK ${deps}$EXTRA_LIB --cap-lints allow \
-        $BUILD_OUT_DIR $EXTRA_BUILD $EXTRA_FEATURES --color ${colors} \
-        ${if stdenv.hostPlatform != stdenv.buildPlatform then "--target ${rustPlatform} -C linker=${stdenv.hostPlatform.config}-gcc" else ""}
-      if [ "$crate_name_" != "$crate_name" ]; then
-        mv target/bin/$crate_name_ target/bin/$crate_name
-      fi
-    }
+    # configure & source common build functions
+    LIB_RUSTC_OPTS="${libRustcOpts}"
+    BIN_RUSTC_OPTS="${binRustcOpts}"
+    LIB_EXT="${stdenv.hostPlatform.extensions.sharedLibrary}"
+    LIB_PATH="${libPath}"
+    LIB_NAME="${libName}"
+    source ${./lib.sh}
 
+    CRATE_NAME='${lib.replaceStrings ["-"] ["_"] libName}'
 
-    EXTRA_LIB=""
-    CRATE_NAME=$(echo ${libName} | sed -e "s/-/_/g")
+    setup_link_paths
 
-    if [[ -e target/link_ ]]; then
-      EXTRA_BUILD="$(cat target/link_) $EXTRA_BUILD"
-    fi
-
-    if [[ -e "${libPath}" ]]; then
-       build_lib ${libPath}
+    if [[ -e "$LIB_PATH" ]]; then
+       build_lib "$LIB_PATH"
+       ${lib.optionalString buildTests ''build_lib_test "$LIB_PATH"''}
     elif [[ -e src/lib.rs ]]; then
        build_lib src/lib.rs
-    elif [[ -e src/${libName}.rs ]]; then
-       build_lib src/${libName}.rs
+       ${lib.optionalString buildTests "build_lib_test src/lib.rs"}
+    elif [[ -e "src/$LIB_NAME.rs" ]]; then
+       build_lib src/$LIB_NAME.rs
+       ${lib.optionalString buildTests ''build_lib_test "src/$LIB_NAME.rs"''}
     fi
 
-    echo "$EXTRA_LINK_SEARCH" | while read i; do
-       if [[ ! -z "$i" ]]; then
-         for lib in $i; do
-           echo "-L $lib" >> target/link
-           L=$(echo $lib | sed -e "s#$(pwd)/target/build#$out/lib#")
-           echo "-L $L" >> target/link.final
-         done
-       fi
-    done
-    echo "$EXTRA_LINK" | while read i; do
-       if [[ ! -z "$i" ]]; then
-         for lib in $i; do
-           echo "-l $lib" >> target/link
-           echo "-l $lib" >> target/link.final
-         done
-       fi
-    done
-
-    if [[ -e target/link ]]; then
-       sort -u target/link.final > target/link.final.sorted
-       mv target/link.final.sorted target/link.final
-       sort -u target/link > target/link.sorted
-       mv target/link.sorted target/link
-
-       tr '\n' ' ' < target/link > target/link_
-       LINK=$(cat target/link_)
-    fi
-    ${lib.optionalString (crateBin != "") ''
-    printf "%s\n" "${crateBin}" | head -n1 | tr -s ',' '\n' | while read -r BIN_NAME BIN_PATH; do
+
+
+    ${lib.optionalString (lib.length crateBin > 0) (lib.concatMapStringsSep "\n" (bin: ''
       mkdir -p target/bin
-      # filter empty entries / empty "lines"
-      if [[ -z "$BIN_NAME" ]]; then
-           continue
-      fi
+      BIN_NAME='${bin.name or crateName}'
+      ${if !bin ? path then ''
+        BIN_PATH=""
+        search_for_bin_path "$BIN_NAME"
+      '' else ''
+        BIN_PATH='${bin.path}'
+      ''}
+        ${build_bin} "$BIN_NAME" "$BIN_PATH"
+    '') crateBin)}
+
+    ${lib.optionalString buildTests ''
+    # When tests are enabled build all the files in the `tests` directory as
+    # test binaries.
+    if [ -d tests ]; then
+      # find all the .rs files (or symlinks to those) in the tests directory, no subdirectories
+      find tests -maxdepth 1 \( -type f -o -type l \) -a -name '*.rs' -print0 | while IFS= read -r -d ''' file; do
+        mkdir -p target/bin
+        build_bin_test_file "$file"
+      done
 
-      if [[ -z "$BIN_PATH" ]]; then
-        # heuristic to "guess" the correct source file as found in cargo:
-        # https://github.com/rust-lang/cargo/blob/90fc9f620190d5fa3c80b0c8c65a1e1361e6b8ae/src/cargo/util/toml/targets.rs#L308-L325
-
-        # the first two cases are the "new" default IIRC
-        BIN_NAME_=$(echo $BIN_NAME | sed -e 's/-/_/g')
-        FILES=( "src/bin/$BIN_NAME.rs" "src/bin/$BIN_NAME/main.rs" "src/bin/$BIN_NAME_.rs" "src/bin/$BIN_NAME_/main.rs" "src/bin/main.rs" "src/main.rs" )
-
-        if ! [ -e "${libPath}" -o -e src/lib.rs -o -e "src/${libName}.rs" ]; then
-          # if this is not a library the following path is also valid
-          FILES=( "src/$BIN_NAME.rs" "src/$BIN_NAME_.rs" "''${FILES[@]}" )
-        fi
-
-        for file in "''${FILES[@]}";
-        do
-          echo "checking file $file"
-          # first file that exists wins
-          if [[ -e "$file" ]]; then
-                  BIN_PATH="$file"
-                  break
-          fi
-        done
-
-        if [[ -z "$BIN_PATH" ]]; then
-          echo "failed to find file for binary target: $BIN_NAME" >&2
-          exit 1
-        fi
-      fi
-      build_bin "$BIN_NAME" "$BIN_PATH"
-    done
+      # find all the subdirectories of tests/ that contain a main.rs file as
+      # that is also a test according to cargo
+      find tests/ -mindepth 1 -maxdepth 2 \( -type f -o -type l \) -a -name 'main.rs' -print0 | while IFS= read -r -d ''' file; do
+        mkdir -p target/bin
+        build_bin_test_file "$file"
+      done
+
+    fi
     ''}
 
-    ${lib.optionalString (crateBin == "" && !hasCrateBin) ''
+    # If crateBin is empty and hasCrateBin is not set then we must try to
+    # detect some kind of bin target based on some files that might exist.
+    ${lib.optionalString (lib.length crateBin == 0 && !hasCrateBin) ''
       if [[ -e src/main.rs ]]; then
         mkdir -p target/bin
-        build_bin ${crateName} src/main.rs
+        ${build_bin} ${crateName} src/main.rs
       fi
       for i in src/bin/*.rs; do #*/
         mkdir -p target/bin
-        build_bin "$(basename $i .rs)" "$i"
+        ${build_bin} "$(basename $i .rs)" "$i"
       done
     ''}
     # Remove object files to avoid "wrong ELF type"
diff --git a/nixpkgs/pkgs/build-support/rust/build-rust-crate/configure-crate.nix b/nixpkgs/pkgs/build-support/rust/build-rust-crate/configure-crate.nix
index 2a40240671cb..efc538f0fd61 100644
--- a/nixpkgs/pkgs/build-support/rust/build-rust-crate/configure-crate.nix
+++ b/nixpkgs/pkgs/build-support/rust/build-rust-crate/configure-crate.nix
@@ -1,4 +1,4 @@
-{ lib, stdenv, echo_build_heading, noisily, makeDeps }:
+{ lib, stdenv, echo_build_heading, noisily, mkRustcDepArgs }:
 { build
 , buildDependencies
 , colors
@@ -20,12 +20,12 @@
 , verbose
 , workspace_member }:
 let version_ = lib.splitString "-" crateVersion;
-    versionPre = if lib.tail version_ == [] then "" else builtins.elemAt version_ 1;
-    version = lib.splitString "." (lib.head version_);
-    rustcOpts = lib.lists.foldl' (opts: opt: opts + " " + opt)
+    versionPre = if lib.tail version_ == [] then "" else lib.elemAt version_ 1;
+    version = lib.splitVersion (lib.head version_);
+    rustcOpts = lib.foldl' (opts: opt: opts + " " + opt)
         (if release then "-C opt-level=3" else "-C debuginfo=2")
         (["-C codegen-units=$NIX_BUILD_CORES"] ++ extraRustcOpts);
-    buildDeps = makeDeps buildDependencies crateRenames;
+    buildDeps = mkRustcDepArgs buildDependencies crateRenames;
     authors = lib.concatStringsSep ":" crateAuthors;
     optLevel = if release then 3 else 0;
     completeDepsDir = lib.concatStringsSep " " completeDeps;
@@ -90,9 +90,9 @@ in ''
   export HOST="${stdenv.hostPlatform.config}"
   export PROFILE=${if release then "release" else "debug"}
   export OUT_DIR=$(pwd)/target/build/${crateName}.out
-  export CARGO_PKG_VERSION_MAJOR=${builtins.elemAt version 0}
-  export CARGO_PKG_VERSION_MINOR=${builtins.elemAt version 1}
-  export CARGO_PKG_VERSION_PATCH=${builtins.elemAt version 2}
+  export CARGO_PKG_VERSION_MAJOR=${lib.elemAt version 0}
+  export CARGO_PKG_VERSION_MINOR=${lib.elemAt version 1}
+  export CARGO_PKG_VERSION_PATCH=${lib.elemAt version 2}
   export CARGO_PKG_VERSION_PRE="${versionPre}"
   export CARGO_PKG_HOMEPAGE="${crateHomepage}"
   export NUM_JOBS=1
@@ -150,4 +150,3 @@ in ''
   fi
   runHook postConfigure
 ''
-
diff --git a/nixpkgs/pkgs/build-support/rust/build-rust-crate/default.nix b/nixpkgs/pkgs/build-support/rust/build-rust-crate/default.nix
index 6534e21c0f0c..2885b2aef510 100644
--- a/nixpkgs/pkgs/build-support/rust/build-rust-crate/default.nix
+++ b/nixpkgs/pkgs/build-support/rust/build-rust-crate/default.nix
@@ -4,7 +4,7 @@
 # This can be useful for deploying packages with NixOps, and to share
 # binary dependencies between projects.
 
-{ lib, stdenv, defaultCrateOverrides, fetchCrate, rustc }:
+{ lib, stdenv, defaultCrateOverrides, fetchCrate, rustc, rust }:
 
 let
     # This doesn't appear to be officially documented anywhere yet.
@@ -13,60 +13,38 @@ let
       then "macos"
       else stdenv.hostPlatform.parsed.kernel.name;
 
-    makeDeps = dependencies: crateRenames:
-      (lib.concatMapStringsSep " " (dep:
+    # Create rustc arguments to link against the given list of dependencies and
+    # renames
+    mkRustcDepArgs = dependencies: crateRenames:
+      lib.concatMapStringsSep " " (dep:
         let
-          extern = lib.strings.replaceStrings ["-"] ["_"] dep.libName;
-          name = if builtins.hasAttr dep.crateName crateRenames then
+          extern = lib.replaceStrings ["-"] ["_"] dep.libName;
+          name = if lib.hasAttr dep.crateName crateRenames then
             lib.strings.replaceStrings ["-"] ["_"] crateRenames.${dep.crateName}
           else
             extern;
-        in (if lib.lists.any (x: x == "lib") dep.crateType then
-           " --extern ${name}=${dep.out}/lib/lib${extern}-${dep.metadata}.rlib"
+        in (if lib.any (x: x == "lib") dep.crateType then
+           " --extern ${name}=${dep.lib}/lib/lib${extern}-${dep.metadata}.rlib"
          else
-           " --extern ${name}=${dep.out}/lib/lib${extern}-${dep.metadata}${stdenv.hostPlatform.extensions.sharedLibrary}")
-      ) dependencies);
-
-    echo_build_heading = colors: ''
-      echo_build_heading() {
-       start=""
-       end=""
-       if [[ "${colors}" == "always" ]]; then
-         start="$(printf '\033[0;1;32m')" #set bold, and set green.
-         end="$(printf '\033[0m')" #returns to "normal"
-       fi
-       if (( $# == 1 )); then
-         echo "$start""Building $1""$end"
-       else
-         echo "$start""Building $1 ($2)""$end"
-       fi
-      }
-    '';
-    noisily = colors: verbose: ''
-      noisily() {
-        start=""
-        end=""
-        if [[ "${colors}" == "always" ]]; then
-          start="$(printf '\033[0;1;32m')" #set bold, and set green.
-          end="$(printf '\033[0m')" #returns to "normal"
-        fi
-	${lib.optionalString verbose ''
-            echo -n "$start"Running "$end"
-            echo $@
-	''}
-	$@
-      }
-    '';
-
-    configureCrate = import ./configure-crate.nix { inherit lib stdenv echo_build_heading noisily makeDeps; };
-    buildCrate = import ./build-crate.nix { inherit lib stdenv echo_build_heading noisily makeDeps; };
-    installCrate = import ./install-crate.nix;
-
-    in
+           " --extern ${name}=${dep.lib}/lib/lib${extern}-${dep.metadata}${stdenv.hostPlatform.extensions.sharedLibrary}")
+      ) dependencies;
+
+   inherit (import ./log.nix { inherit lib; }) noisily echo_build_heading;
+
+   configureCrate = import ./configure-crate.nix {
+     inherit lib stdenv echo_build_heading noisily mkRustcDepArgs;
+   };
+
+   buildCrate = import ./build-crate.nix {
+     inherit lib stdenv echo_build_heading noisily mkRustcDepArgs rust;
+   };
+
+   installCrate = import ./install-crate.nix { inherit stdenv; };
+in
 
 crate_: lib.makeOverridable ({ rust, release, verbose, features, buildInputs, crateOverrides,
   dependencies, buildDependencies, crateRenames,
-  extraRustcOpts,
+  extraRustcOpts, buildTests,
   preUnpack, postUnpack, prePatch, patches, postPatch,
   preConfigure, postConfigure, preBuild, postBuild, preInstall, postInstall }:
 
@@ -77,59 +55,66 @@ let crate = crate_ // (lib.attrByPath [ crate_.crateName ] (attr: {}) crateOverr
       "src" "buildInputs" "crateBin" "crateLib" "libName" "libPath"
       "buildDependencies" "dependencies" "features" "crateRenames"
       "crateName" "version" "build" "authors" "colors" "edition"
+      "buildTests"
     ];
-    extraDerivationAttrs = lib.filterAttrs (n: v: ! lib.elem n processedAttrs) crate;
+    extraDerivationAttrs = builtins.removeAttrs crate processedAttrs;
     buildInputs_ = buildInputs;
     extraRustcOpts_ = extraRustcOpts;
+    buildTests_ = buildTests;
+
+    # take a list of crates that we depend on and override them to fit our overrides, rustc, release, …
+    makeDependencies = map (dep: lib.getLib (dep.override { inherit release verbose crateOverrides; }));
+
+    # crate2nix has a hack for the old bash based build script that did split
+    # entries at `,`. No we have to work around that hack.
+    # https://github.com/kolloch/crate2nix/blame/5b19c1b14e1b0e5522c3e44e300d0b332dc939e7/crate2nix/templates/build.nix.tera#L89
+    crateBin = lib.filter (bin: !(bin ? name && bin.name == ",")) (crate.crateBin or []);
+    hasCrateBin = crate ? crateBin;
 in
 stdenv.mkDerivation (rec {
 
     inherit (crate) crateName;
-    inherit preUnpack postUnpack prePatch patches postPatch preConfigure postConfigure preBuild postBuild preInstall postInstall;
-
-    src = if lib.hasAttr "src" crate then
-        crate.src
-      else
-        fetchCrate { inherit (crate) crateName version sha256; };
-    name = "rust_${crate.crateName}-${crate.version}";
+    inherit
+      preUnpack
+      postUnpack
+      prePatch
+      patches
+      postPatch
+      preConfigure
+      postConfigure
+      preBuild
+      postBuild
+      preInstall
+      postInstall
+      buildTests
+    ;
+
+    src = crate.src or (fetchCrate { inherit (crate) crateName version sha256; });
+    name = "rust_${crate.crateName}-${crate.version}${lib.optionalString buildTests_ "-test"}";
     depsBuildBuild = [ rust stdenv.cc ];
     buildInputs = (crate.buildInputs or []) ++ buildInputs_;
-    dependencies =
-      builtins.map
-        (dep: dep.override { rust = rust; release = release; verbose = verbose; crateOverrides = crateOverrides; })
-        dependencies_;
-
-    buildDependencies =
-      builtins.map
-        (dep: dep.override { rust = rust; release = release; verbose = verbose; crateOverrides = crateOverrides; })
-        buildDependencies_;
-
-    completeDeps = lib.lists.unique (dependencies ++ lib.lists.concatMap (dep: dep.completeDeps) dependencies);
-    completeBuildDeps = lib.lists.unique (
+    dependencies = makeDependencies dependencies_;
+    buildDependencies = makeDependencies buildDependencies_;
+
+    completeDeps = lib.unique (dependencies ++ lib.concatMap (dep: dep.completeDeps) dependencies);
+    completeBuildDeps = lib.unique (
       buildDependencies
-      ++ lib.lists.concatMap (dep: dep.completeBuildDeps ++ dep.completeDeps) buildDependencies
+      ++ lib.concatMap (dep: dep.completeBuildDeps ++ dep.completeDeps) buildDependencies
     );
 
-    crateFeatures = if crate ? features then
-        lib.concatMapStringsSep " " (f: "--cfg feature=\\\"${f}\\\"") (crate.features ++ features) #"
-      else "";
+    crateFeatures = lib.optionalString (crate ? features)
+      (lib.concatMapStringsSep " " (f: "--cfg feature=\\\"${f}\\\"") (crate.features ++ features));
 
     libName = if crate ? libName then crate.libName else crate.crateName;
     libPath = if crate ? libPath then crate.libPath else "";
 
-    depsMetadata = builtins.foldl' (str: dep: str + dep.metadata) "" (dependencies ++ buildDependencies);
-    metadata = builtins.substring 0 10 (builtins.hashString "sha256" (crateName + "-" + crateVersion + "___" + toString crateFeatures + "___" + depsMetadata ));
-
-    crateBin = if crate ? crateBin then
-       builtins.foldl' (bins: bin: let
-            name = (if bin ? name then bin.name else crateName);
-            path = if bin ? path then bin.path else "";
-          in
-          bins + (if bin == "" then "" else ",") + "${name} ${path}"
-
-       ) "" crate.crateBin
-    else "";
-    hasCrateBin = crate ? crateBin;
+    # Seed the symbol hashes with something unique every time.
+    # https://doc.rust-lang.org/1.0.0/rustc/metadata/loader/index.html#frobbing-symbols
+    metadata = let
+      depsMetadata = lib.foldl' (str: dep: str + dep.metadata) "" (dependencies ++ buildDependencies);
+      hashedMetadata = builtins.hashString "sha256"
+        (crateName + "-" + crateVersion + "___" + toString crateFeatures + "___" + depsMetadata);
+      in lib.substring 0 10 hashedMetadata;
 
     build = crate.build or "";
     workspace_member = crate.workspace_member or ".";
@@ -142,9 +127,13 @@ stdenv.mkDerivation (rec {
       if lib.attrByPath ["plugin"] false crate then ["dylib"] else
         (crate.type or ["lib"]);
     colors = lib.attrByPath [ "colors" ] "always" crate;
-    extraLinkFlags = builtins.concatStringsSep " " (crate.extraLinkFlags or []);
+    extraLinkFlags = lib.concatStringsSep " " (crate.extraLinkFlags or []);
     edition = crate.edition or null;
-    extraRustcOpts = (if crate ? extraRustcOpts then crate.extraRustcOpts else []) ++ extraRustcOpts_ ++ (lib.optional (edition != null) "--edition ${edition}");
+    extraRustcOpts =
+      lib.optionals (crate ? extraRustcOpts) crate.extraRustcOpts
+      ++ extraRustcOpts_
+      ++ (lib.optional (edition != null) "--edition ${edition}");
+
 
     configurePhase = configureCrate {
       inherit crateName buildDependencies completeDeps completeBuildDeps crateDescription
@@ -155,10 +144,15 @@ stdenv.mkDerivation (rec {
     buildPhase = buildCrate {
       inherit crateName dependencies
               crateFeatures crateRenames libName release libPath crateType
-              metadata crateBin hasCrateBin verbose colors
-              extraRustcOpts;
+              metadata hasCrateBin crateBin verbose colors
+              extraRustcOpts buildTests;
     };
-    installPhase = installCrate crateName metadata;
+    installPhase = installCrate crateName metadata buildTests;
+
+    # depending on the test setting we are either producing something with bins
+    # and libs or just test binaries
+    outputs = if buildTests then [ "out" ] else [ "out" "lib" ];
+    outputDev = if buildTests then [ "out" ] else  [ "lib" ];
 
 } // extraDerivationAttrs
 )) {
@@ -183,4 +177,5 @@ stdenv.mkDerivation (rec {
   dependencies = crate_.dependencies or [];
   buildDependencies = crate_.buildDependencies or [];
   crateRenames = crate_.crateRenames or {};
+  buildTests = crate_.buildTests or false;
 }
diff --git a/nixpkgs/pkgs/build-support/rust/build-rust-crate/helpers.nix b/nixpkgs/pkgs/build-support/rust/build-rust-crate/helpers.nix
index 14d997b2d5cf..386d0ce7084f 100644
--- a/nixpkgs/pkgs/build-support/rust/build-rust-crate/helpers.nix
+++ b/nixpkgs/pkgs/build-support/rust/build-rust-crate/helpers.nix
@@ -3,23 +3,23 @@
   kernel = stdenv.hostPlatform.parsed.kernel.name;
   abi = stdenv.hostPlatform.parsed.abi.name;
   cpu = stdenv.hostPlatform.parsed.cpu.name;
-   updateFeatures = f: up: functions: builtins.deepSeq f (lib.lists.foldl' (features: fun: fun features) (lib.attrsets.recursiveUpdate f up) functions);
+   updateFeatures = f: up: functions: lib.deepSeq f (lib.foldl' (features: fun: fun features) (lib.attrsets.recursiveUpdate f up) functions);
    mapFeatures = features: map (fun: fun { features = features; });
-   mkFeatures = feat: lib.lists.foldl (features: featureName:
+   mkFeatures = feat: lib.foldl (features: featureName:
      if feat.${featureName} or false then
        [ featureName ] ++ features
      else
        features
-   ) [] (builtins.attrNames feat);
+   ) [] (lib.attrNames feat);
   include = includedFiles: src: builtins.filterSource (path: type:
-     lib.lists.any (f:
+     lib.any (f:
        let p = toString (src + ("/" + f));
        in
        p == path || (lib.strings.hasPrefix (p + "/") path)
      ) includedFiles
   ) src;
   exclude = excludedFiles: src: builtins.filterSource (path: type:
-    lib.lists.all (f:
+    lib.all (f:
        !lib.strings.hasPrefix (toString (src + ("/" + f))) path
     ) excludedFiles
   ) src;
diff --git a/nixpkgs/pkgs/build-support/rust/build-rust-crate/install-crate.nix b/nixpkgs/pkgs/build-support/rust/build-rust-crate/install-crate.nix
index 3b0282621ea1..5ba7b69bedc5 100644
--- a/nixpkgs/pkgs/build-support/rust/build-rust-crate/install-crate.nix
+++ b/nixpkgs/pkgs/build-support/rust/build-rust-crate/install-crate.nix
@@ -1,24 +1,27 @@
-crateName: metadata:
-''
+{ stdenv }:
+crateName: metadata: buildTests:
+if !buildTests then ''
   runHook preInstall
-  mkdir -p $out
+  # always create $out even if we do not have binaries. We are detecting binary targets during compilation, if those are missing there is no way to only have $lib
+  mkdir $out
   if [[ -s target/env ]]; then
-    cp target/env $out/env
+    mkdir -p $lib
+    cp target/env $lib/env
   fi
   if [[ -s target/link.final ]]; then
-    mkdir -p $out/lib
-    cp target/link.final $out/lib/link
+    mkdir -p $lib/lib
+    cp target/link.final $lib/lib/link
   fi
   if [[ "$(ls -A target/lib)" ]]; then
-    mkdir -p $out/lib
-    cp target/lib/* $out/lib #*/
-    for lib in $out/lib/*.so $out/lib/*.dylib; do #*/
-      ln -s $lib $(echo $lib | sed -e "s/-${metadata}//")
+    mkdir -p $lib/lib
+    cp target/lib/* $lib/lib #*/
+    for library in $lib/lib/*.so $lib/lib/*.dylib; do #*/
+      ln -s $library $(echo $library | sed -e "s/-${metadata}//")
     done
   fi
   if [[ "$(ls -A target/build)" ]]; then # */
-    mkdir -p $out/lib
-    cp -r target/build/* $out/lib # */
+    mkdir -p $lib/lib
+    cp -r target/build/* $lib/lib # */
   fi
   if [[ -d target/bin ]]; then
     if [[ "$(ls -A target/bin)" ]]; then
@@ -27,4 +30,22 @@ crateName: metadata:
     fi
   fi
   runHook postInstall
+'' else
+# for tests we just put them all in the output. No execution.
+''
+  runHook preInstall
+
+  mkdir -p $out/tests
+  if [ -e target/bin ]; then
+    find target/bin/ -type f -executable -exec cp {} $out/tests \;
+  fi
+  if [ -e target/lib ]; then
+    find target/lib/ -type f \! -name '*.rlib' \
+      -a \! -name '*${stdenv.hostPlatform.extensions.sharedLibrary}' \
+      -a \! -name '*.d' \
+      -executable \
+      -print0 | xargs --no-run-if-empty --null install --target $out/tests;
+  fi
+
+  runHook postInstall
 ''
diff --git a/nixpkgs/pkgs/build-support/rust/build-rust-crate/lib.sh b/nixpkgs/pkgs/build-support/rust/build-rust-crate/lib.sh
new file mode 100644
index 000000000000..d4d9317496f5
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/rust/build-rust-crate/lib.sh
@@ -0,0 +1,138 @@
+build_lib() {
+  lib_src=$1
+  echo_build_heading $lib_src ${libName}
+
+  noisily rustc \
+    --crate-name $CRATE_NAME \
+    $lib_src \
+    --out-dir target/lib \
+    --emit=dep-info,link \
+    -L dependency=target/deps \
+    --cap-lints allow \
+    $LIB_RUSTC_OPTS \
+    $BUILD_OUT_DIR \
+    $EXTRA_BUILD \
+    $EXTRA_FEATURES \
+    $EXTRA_RUSTC_FLAGS \
+    --color $colors
+
+  EXTRA_LIB=" --extern $CRATE_NAME=target/lib/lib$CRATE_NAME-$metadata.rlib"
+  if [ -e target/deps/lib$CRATE_NAME-$metadata$LIB_EXT ]; then
+     EXTRA_LIB="$EXTRA_LIB --extern $CRATE_NAME=target/lib/lib$CRATE_NAME-$metadata$LIB_EXT"
+  fi
+}
+
+build_bin() {
+  local crate_name=$1
+  local crate_name_=$(echo $crate_name | tr '-' '_')
+  local main_file=""
+
+  if [[ ! -z $2 ]]; then
+    main_file=$2
+  fi
+  echo_build_heading $@
+  noisily rustc \
+    --crate-name $crate_name_ \
+    $main_file \
+    --crate-type bin \
+    $BIN_RUSTC_OPTS \
+    --out-dir target/bin \
+    --emit=dep-info,link \
+    -L dependency=target/deps \
+    $LINK \
+    $EXTRA_LIB \
+    --cap-lints allow \
+    $BUILD_OUT_DIR \
+    $EXTRA_BUILD \
+    $EXTRA_FEATURES \
+    $EXTRA_RUSTC_FLAGS \
+    --color ${colors} \
+
+  if [ "$crate_name_" != "$crate_name" ]; then
+    mv target/bin/$crate_name_ target/bin/$crate_name
+  fi
+}
+
+build_lib_test() {
+    local file="$1"
+    EXTRA_RUSTC_FLAGS="--test $EXTRA_RUSTC_FLAGS" build_lib "$1" "$2"
+}
+
+build_bin_test() {
+    local crate="$1"
+    local file="$2"
+    EXTRA_RUSTC_FLAGS="--test $EXTRA_RUSTC_FLAGS" build_bin "$1" "$2"
+}
+
+build_bin_test_file() {
+    local file="$1"
+    local derived_crate_name="${file//\//_}"
+    derived_crate_name="${derived_crate_name%.rs}"
+    build_bin_test "$derived_crate_name" "$file"
+}
+
+setup_link_paths() {
+  EXTRA_LIB=""
+  if [[ -e target/link_ ]]; then
+    EXTRA_BUILD="$(cat target/link_) $EXTRA_BUILD"
+  fi
+
+  echo "$EXTRA_LINK_SEARCH" | while read i; do
+     if [[ ! -z "$i" ]]; then
+       for library in $i; do
+         echo "-L $library" >> target/link
+         L=$(echo $library | sed -e "s#$(pwd)/target/build#$lib/lib#")
+         echo "-L $L" >> target/link.final
+       done
+     fi
+  done
+  echo "$EXTRA_LINK" | while read i; do
+     if [[ ! -z "$i" ]]; then
+       for library in $i; do
+         echo "-l $library" >> target/link
+         echo "-l $library" >> target/link.final
+       done
+     fi
+  done
+
+  if [[ -e target/link ]]; then
+     sort -u target/link.final > target/link.final.sorted
+     mv target/link.final.sorted target/link.final
+     sort -u target/link > target/link.sorted
+     mv target/link.sorted target/link
+
+     tr '\n' ' ' < target/link > target/link_
+     LINK=$(cat target/link_)
+  fi
+}
+
+search_for_bin_path() {
+  # heuristic to "guess" the correct source file as found in cargo:
+  # https://github.com/rust-lang/cargo/blob/90fc9f620190d5fa3c80b0c8c65a1e1361e6b8ae/src/cargo/util/toml/targets.rs#L308-L325
+
+  BIN_NAME=$1
+  BIN_NAME_=$(echo $BIN_NAME | tr '-' '_')
+
+  # the first two cases are the "new" default IIRC
+  FILES=( "src/bin/$BIN_NAME.rs" "src/bin/$BIN_NAME/main.rs" "src/bin/$BIN_NAME_.rs" "src/bin/$BIN_NAME_/main.rs" "src/bin/main.rs" "src/main.rs" )
+
+  if ! [ -e "$LIB_PATH" -o -e src/lib.rs -o -e "src/$LIB_NAME.rs" ]; then
+    # if this is not a library the following path is also valid
+    FILES=( "src/$BIN_NAME.rs" "src/$BIN_NAME_.rs" "${FILES[@]}" )
+  fi
+
+  for file in "${FILES[@]}";
+  do
+    echo "checking file $file"
+    # first file that exists wins
+    if [[ -e "$file" ]]; then
+            BIN_PATH="$file"
+            break
+    fi
+  done
+
+  if [[ -z "$BIN_PATH" ]]; then
+    echo "failed to find file for binary target: $BIN_NAME" >&2
+    exit 1
+  fi
+}
diff --git a/nixpkgs/pkgs/build-support/rust/build-rust-crate/log.nix b/nixpkgs/pkgs/build-support/rust/build-rust-crate/log.nix
new file mode 100644
index 000000000000..25181c787e2c
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/rust/build-rust-crate/log.nix
@@ -0,0 +1,33 @@
+{ lib }:
+{
+  echo_build_heading = colors: ''
+    echo_build_heading() {
+     start=""
+     end=""
+     ${lib.optionalString (colors == "always") ''
+       start="$(printf '\033[0;1;32m')" #set bold, and set green.
+       end="$(printf '\033[0m')" #returns to "normal"
+     ''}
+     if (( $# == 1 )); then
+       echo "$start""Building $1""$end"
+     else
+       echo "$start""Building $1 ($2)""$end"
+     fi
+    }
+  '';
+  noisily = colors: verbose: ''
+    noisily() {
+      start=""
+      end=""
+      ${lib.optionalString (colors == "always") ''
+        start="$(printf '\033[0;1;32m')" #set bold, and set green.
+        end="$(printf '\033[0m')" #returns to "normal"
+      ''}
+  	  ${lib.optionalString verbose ''
+        echo -n "$start"Running "$end"
+        echo $@
+  	  ''}
+  	  $@
+    }
+  '';
+}
diff --git a/nixpkgs/pkgs/build-support/rust/build-rust-crate/test/default.nix b/nixpkgs/pkgs/build-support/rust/build-rust-crate/test/default.nix
index 4a90cf442a4d..f0f1ed4d1ebf 100644
--- a/nixpkgs/pkgs/build-support/rust/build-rust-crate/test/default.nix
+++ b/nixpkgs/pkgs/build-support/rust/build-rust-crate/test/default.nix
@@ -29,10 +29,30 @@ let
     }
   '';
 
+  mkTestFile = name: functionName: mkFile name ''
+    #[cfg(test)]
+    #[test]
+    fn ${functionName}() {
+      assert!(true);
+    }
+  '';
+  mkTestFileWithMain = name: functionName: mkFile name ''
+    #[cfg(test)]
+    #[test]
+    fn ${functionName}() {
+      assert!(true);
+    }
+
+    fn main() {}
+  '';
+
+
   mkLib = name: mkFile name "pub fn test() -> i32 { return 23; }";
 
   mkTest = crateArgs: let
-    crate = mkCrate crateArgs;
+    crate = mkCrate (builtins.removeAttrs crateArgs ["expectedTestOutput"]);
+    hasTests = crateArgs.buildTests or false;
+    expectedTestOutputs = crateArgs.expectedTestOutputs or null;
     binaries = map (v: ''"${v.name}"'') (crateArgs.crateBin or []);
     isLib = crateArgs ? libName || crateArgs ? libPath;
     crateName = crateArgs.crateName or "nixtestcrate";
@@ -44,16 +64,28 @@ let
       src = mkBinExtern "src/main.rs" libName;
     };
 
-    in runCommand "run-buildRustCrate-${crateName}-test" {
-      nativeBuildInputs = [ crate ];
-    } ''
-      ${lib.concatStringsSep "\n" binaries}
-      ${lib.optionalString isLib ''
-          test -e ${crate}/lib/*.rlib || exit 1
-          ${libTestBinary}/bin/run-test-${crateName}
-      ''}
-      touch $out
-  '';
+    in
+      assert expectedTestOutputs != null -> hasTests;
+      assert hasTests -> expectedTestOutputs != null;
+
+      runCommand "run-buildRustCrate-${crateName}-test" {
+        nativeBuildInputs = [ crate ];
+      } (if !hasTests then ''
+          ${lib.concatStringsSep "\n" binaries}
+          ${lib.optionalString isLib ''
+              test -e ${crate}/lib/*.rlib || exit 1
+              ${libTestBinary}/bin/run-test-${crateName}
+          ''}
+          touch $out
+        '' else ''
+          for file in ${crate}/tests/*; do
+            $file 2>&1 >> $out
+          done
+          set -e
+          ${lib.concatMapStringsSep "\n" (o: "grep '${o}' $out || {  echo 'output \"${o}\" not found in:'; cat $out; exit 23; }") expectedTestOutputs}
+        ''
+      );
+
   in rec {
 
   tests = let
@@ -85,6 +117,71 @@ let
         dependencies = [ (mkCrate { crateName = "foo"; libName = "foolib"; src = mkLib "src/lib.rs"; }) ];
         crateRenames = { "foo" = "foo_renamed"; };
       };
+      rustLibTestsDefault = {
+        src = mkTestFile "src/lib.rs" "baz";
+        buildTests = true;
+        expectedTestOutputs = [ "test baz ... ok" ];
+      };
+      rustLibTestsCustomLibName = {
+        libName = "test_lib";
+        src = mkTestFile "src/test_lib.rs" "foo";
+        buildTests = true;
+        expectedTestOutputs = [ "test foo ... ok" ];
+      };
+      rustLibTestsCustomLibPath = {
+        libPath = "src/test_path.rs";
+        src = mkTestFile "src/test_path.rs" "bar";
+        buildTests = true;
+        expectedTestOutputs = [ "test bar ... ok" ];
+      };
+      rustLibTestsCustomLibPathWithTests = {
+        libPath = "src/test_path.rs";
+        src = symlinkJoin {
+          name = "rust-lib-tests-custom-lib-path-with-tests-dir";
+          paths = [
+            (mkTestFile "src/test_path.rs" "bar")
+            (mkTestFile "tests/something.rs" "something")
+          ];
+        };
+        buildTests = true;
+        expectedTestOutputs = [
+          "test bar ... ok"
+          "test something ... ok"
+        ];
+      };
+      rustBinTestsCombined = {
+        src = symlinkJoin {
+          name = "rust-bin-tests-combined";
+          paths = [
+            (mkTestFileWithMain "src/main.rs" "src_main")
+            (mkTestFile "tests/foo.rs" "tests_foo")
+            (mkTestFile "tests/bar.rs" "tests_bar")
+          ];
+        };
+        buildTests = true;
+        expectedTestOutputs = [
+          "test src_main ... ok"
+          "test tests_foo ... ok"
+          "test tests_bar ... ok"
+        ];
+      };
+      rustBinTestsSubdirCombined = {
+        src = symlinkJoin {
+          name = "rust-bin-tests-subdir-combined";
+          paths = [
+            (mkTestFileWithMain "src/main.rs" "src_main")
+            (mkTestFile "tests/foo/main.rs" "tests_foo")
+            (mkTestFile "tests/bar/main.rs" "tests_bar")
+          ];
+        };
+        buildTests = true;
+        expectedTestOutputs = [
+          "test src_main ... ok"
+          "test tests_foo ... ok"
+          "test tests_bar ... ok"
+        ];
+      };
+
     };
     brotliCrates = (callPackage ./brotli-crates.nix {});
   in lib.mapAttrs (key: value: mkTest (value // lib.optionalAttrs (!value?crateName) { crateName = key; })) cases // {