Merge pull request #76820 from andir/buildRustCrate-tests

buildRustCrate: add `buildTests` flag to tell rustc to build tests instead of binaries
This commit is contained in:
Andreas Rammhold 2020-01-08 15:39:54 +01:00 committed by GitHub
commit 366dc671a9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 213 additions and 33 deletions

1
.github/CODEOWNERS vendored
View file

@ -82,6 +82,7 @@
# Rust
/pkgs/development/compilers/rust @Mic92 @LnL7
/pkgs/build-support/rust @andir
# Darwin-related
/pkgs/stdenv/darwin @NixOS/darwin-maintainers

View file

@ -4,6 +4,7 @@
crateFeatures, crateRenames, libName, release, libPath,
crateType, metadata, crateBin, hasCrateBin,
extraRustcOpts, verbose, colors,
buildTests
}:
let
@ -30,6 +31,7 @@
baseRustcOpts
);
build_bin = if buildTests then "build_bin_test" else "build_bin";
in ''
runHook preBuild
${echo_build_heading colors}
@ -48,14 +50,18 @@
setup_link_paths
if [[ -e "$LIB_PATH" ]]; then
build_lib $LIB_PATH
build_lib "$LIB_PATH"
${lib.optionalString buildTests ''build_lib_test "$LIB_PATH"''}
elif [[ -e src/lib.rs ]]; then
build_lib src/lib.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
${lib.optionalString (lib.length crateBin > 0) (lib.concatMapStringsSep "\n" (bin: ''
mkdir -p target/bin
BIN_NAME='${bin.name or crateName}'
@ -65,19 +71,39 @@
'' else ''
BIN_PATH='${bin.path}'
''}
build_bin "$BIN_NAME" "$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
# 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
''}
# 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"

View file

@ -39,12 +39,12 @@ let
inherit lib stdenv echo_build_heading noisily mkRustcDepArgs rust;
};
installCrate = import ./install-crate.nix;
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 }:
@ -55,10 +55,12 @@ 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; }));
@ -72,13 +74,23 @@ in
stdenv.mkDerivation (rec {
inherit (crate) crateName;
inherit preUnpack postUnpack prePatch patches postPatch preConfigure postConfigure preBuild postBuild preInstall postInstall;
inherit
preUnpack
postUnpack
prePatch
patches
postPatch
preConfigure
postConfigure
preBuild
postBuild
preInstall
postInstall
buildTests
;
src = if lib.hasAttr "src" crate then
crate.src
else
fetchCrate { inherit (crate) crateName version sha256; };
name = "rust_${crate.crateName}-${crate.version}";
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 = makeDependencies dependencies_;
@ -122,6 +134,7 @@ stdenv.mkDerivation (rec {
++ extraRustcOpts_
++ (lib.optional (edition != null) "--edition ${edition}");
configurePhase = configureCrate {
inherit crateName buildDependencies completeDeps completeBuildDeps crateDescription
crateFeatures crateRenames libName build workspace_member release libPath crateVersion
@ -132,12 +145,14 @@ stdenv.mkDerivation (rec {
inherit crateName dependencies
crateFeatures crateRenames libName release libPath crateType
metadata hasCrateBin crateBin verbose colors
extraRustcOpts;
extraRustcOpts buildTests;
};
installPhase = installCrate crateName metadata;
installPhase = installCrate crateName metadata buildTests;
outputs = [ "out" "lib" ];
outputDev = [ "lib" ];
# 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
)) {
@ -162,4 +177,5 @@ stdenv.mkDerivation (rec {
dependencies = crate_.dependencies or [];
buildDependencies = crate_.buildDependencies or [];
crateRenames = crate_.crateRenames or {};
buildTests = crate_.buildTests or false;
}

View file

@ -1,5 +1,6 @@
crateName: metadata:
''
{ stdenv }:
crateName: metadata: buildTests:
if !buildTests then ''
runHook preInstall
# 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
@ -28,5 +29,23 @@ crateName: metadata:
cp -P target/bin/* $out/bin # */
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
''

View file

@ -13,6 +13,7 @@ build_lib() {
$BUILD_OUT_DIR \
$EXTRA_BUILD \
$EXTRA_FEATURES \
$EXTRA_RUSTC_FLAGS \
--color $colors
EXTRA_LIB=" --extern $CRATE_NAME=target/lib/lib$CRATE_NAME-$metadata.rlib"
@ -22,9 +23,10 @@ build_lib() {
}
build_bin() {
crate_name=$1
crate_name_=$(echo $crate_name | tr '-' '_')
main_file=""
local crate_name=$1
local crate_name_=$(echo $crate_name | tr '-' '_')
local main_file=""
if [[ ! -z $2 ]]; then
main_file=$2
fi
@ -43,6 +45,7 @@ build_bin() {
$BUILD_OUT_DIR \
$EXTRA_BUILD \
$EXTRA_FEATURES \
$EXTRA_RUSTC_FLAGS \
--color ${colors} \
if [ "$crate_name_" != "$crate_name" ]; then
@ -50,6 +53,24 @@ build_bin() {
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

View file

@ -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 // {