20 crateName = "nixtestcrate";
22 authors = [ "Test <test@example.com>" ];
26 mkHostCrate = mkCrate buildRustCrate;
31 crateVersion ? "0.1.0",
36 name = ${builtins.toJSON name}
37 version = ${builtins.toJSON crateVersion}
44 destination = "/${destination}";
53 let name: String = env::args().nth(0).unwrap();
54 println!("executed {}", name);
61 extern crate ${extern};
63 assert_eq!(${extern}::test(), 23);
72 fn ${functionName}() {
81 fn ${functionName}() {
88 mkLib = name: mkFile name "pub fn test() -> i32 { return 23; }";
93 crate = mkHostCrate (builtins.removeAttrs crateArgs [ "expectedTestOutput" ]);
94 hasTests = crateArgs.buildTests or false;
95 expectedTestOutputs = crateArgs.expectedTestOutputs or null;
96 binaries = map (v: lib.escapeShellArg v.name) (crateArgs.crateBin or [ ]);
97 isLib = crateArgs ? libName || crateArgs ? libPath;
98 crateName = crateArgs.crateName or "nixtestcrate";
99 libName = crateArgs.libName or crateName;
106 crateName = "run-test-${crateName}";
107 dependencies = [ crate ];
108 src = mkBinExtern "src/main.rs" libName;
112 assert expectedTestOutputs != null -> hasTests;
113 assert hasTests -> expectedTestOutputs != null;
115 runCommand "run-buildRustCrate-${crateName}-test"
117 nativeBuildInputs = [ crate ];
122 ${lib.concatMapStringsSep "\n" (
124 # Can't actually run the binary when cross-compiling
125 (lib.optionalString (stdenv.hostPlatform != stdenv.buildPlatform) "type ") + binary
127 ${lib.optionalString isLib ''
128 test -e ${crate}/lib/*.rlib || exit 1
129 ${lib.optionalString (stdenv.hostPlatform != stdenv.buildPlatform) "test -x "} \
130 ${libTestBinary}/bin/run-test-${crateName}
134 else if stdenv.hostPlatform == stdenv.buildPlatform then
136 for file in ${crate}/tests/*; do
140 ${lib.concatMapStringsSep "\n" (
141 o: "grep '${o}' $out || { echo 'output \"${o}\" not found in:'; cat $out; exit 23; }"
142 ) expectedTestOutputs}
146 for file in ${crate}/tests/*; do
154 Returns a derivation that asserts that the crate specified by `crateArgs`
155 has the specified files as output.
157 `name` is used as part of the derivation name that performs the checking.
159 `mkCrate` can be used to override the `mkCrate` call/implementation to use to
160 override the `buildRustCrate`, useful for cross compilation. Uses `mkHostCrate` by default.
162 `crateArgs` is passed to `mkCrate` to build the crate with `buildRustCrate`
164 `expectedFiles` contains a list of expected file paths in the output. E.g.
165 `[ "./bin/my_binary" ]`.
167 `output` specifies the name of the output to use. By default, the default
168 output is used but e.g. `output = "lib";` will cause the lib output
169 to be checked instead. You do not need to specify any directories.
174 mkCrate ? mkHostCrate,
179 assert (builtins.isString name);
180 assert (builtins.isAttrs crateArgs);
181 assert (builtins.isList expectedFiles);
184 crate = mkCrate (builtins.removeAttrs crateArgs [ "expectedTestOutput" ]);
185 crateOutput = if output == null then crate else crate."${output}";
186 expectedFilesFile = writeTextFile {
187 name = "expected-files-${name}";
190 sorted = builtins.sort (a: b: a < b) expectedFiles;
191 concatenated = builtins.concatStringsSep "\n" sorted;
196 runCommand "assert-outputs-${name}"
201 local actualFiles=$(mktemp)
207 # sed out the hash because it differs per platform
209 | sed 's/-${crate.metadata}//g' \
211 diff -q ${expectedFilesFile} "$actualFiles" > /dev/null || {
212 echo -e "\033[0;1;31mERROR: Difference in expected output files in ${crateOutput} \033[0m" >&2
214 sed -e 's/^/ /' $actualFiles
216 sed -e 's/^/ /' ${expectedFilesFile}
218 diff -u ${expectedFilesFile} $actualFiles |\
234 libPath = "src/my_lib.rs";
235 src = mkLib "src/my_lib.rs";
238 src = mkLib "src/lib.rs";
241 # This used to be supported by cargo but as of 1.40.0 I can't make it work like that with just cargo anymore.
242 # This might be a regression or deprecated thing they finally removed…
243 # customLibName = { libName = "test_lib"; src = mkLib "src/test_lib.rs"; };
244 # rustLibTestsCustomLibName = {
245 # libName = "test_lib";
246 # src = mkTestFile "src/test_lib.rs" "foo";
248 # expectedTestOutputs = [ "test foo ... ok" ];
251 customLibNameAndLibPath = {
252 libName = "test_lib";
253 libPath = "src/best-lib.rs";
254 src = mkLib "src/best-lib.rs";
259 name = "test_binary1";
260 path = "src/foobar.rs";
263 src = mkBin "src/foobar.rs";
266 crateBin = [ { name = "my-binary2"; } ];
267 src = mkBin "src/my_binary2.rs";
271 { name = "my-binary3"; }
272 { name = "my-binary4"; }
275 name = "buildRustCrateMultipleBinariesCase";
277 (mkBin "src/bin/my_binary3.rs")
278 (mkBin "src/bin/my_binary4.rs")
283 crateBin = [ { name = "my-binary5"; } ];
284 src = mkBin "src/bin/main.rs";
287 crateBin = [ { name = "my-binary6"; } ];
288 src = mkBin "src/main.rs";
291 crateBin = [ { name = "my-binary-rename1"; } ];
292 src = mkBinExtern "src/main.rs" "foo_renamed";
296 src = mkLib "src/lib.rs";
300 "foo" = "foo_renamed";
304 crateBin = [ { name = "my-binary-rename2"; } ];
305 src = mkBinExtern "src/main.rs" "foo_renamed";
310 src = mkLib "src/lib.rs";
314 "foo" = "foo_renamed";
317 crateBinRenameMultiVersion =
322 crateName = "my_lib";
324 src = mkFile "src/lib.rs" ''
325 pub const version: &str = "${version}";
328 depCrate01 = crateWithVersion "0.1.2";
329 depCrate02 = crateWithVersion "0.2.1";
332 crateName = "my_bin";
336 (mkFile "src/main.rs" ''
338 fn my_lib_01() { assert_eq!(lib01::version, "0.1.2"); }
341 fn my_lib_02() { assert_eq!(lib02::version, "0.2.1"); }
364 expectedTestOutputs = [
365 "test my_lib_01 ... ok"
366 "test my_lib_02 ... ok"
369 rustLibTestsDefault = {
370 src = mkTestFile "src/lib.rs" "baz";
372 expectedTestOutputs = [ "test baz ... ok" ];
374 rustLibTestsCustomLibPath = {
375 libPath = "src/test_path.rs";
376 src = mkTestFile "src/test_path.rs" "bar";
378 expectedTestOutputs = [ "test bar ... ok" ];
380 rustLibTestsCustomLibPathWithTests = {
381 libPath = "src/test_path.rs";
383 name = "rust-lib-tests-custom-lib-path-with-tests-dir";
385 (mkTestFile "src/test_path.rs" "bar")
386 (mkTestFile "tests/something.rs" "something")
390 expectedTestOutputs = [
392 "test something ... ok"
395 rustBinTestsCombined = {
397 name = "rust-bin-tests-combined";
399 (mkTestFileWithMain "src/main.rs" "src_main")
400 (mkTestFile "tests/foo.rs" "tests_foo")
401 (mkTestFile "tests/bar.rs" "tests_bar")
405 expectedTestOutputs = [
406 "test src_main ... ok"
407 "test tests_foo ... ok"
408 "test tests_bar ... ok"
411 rustBinTestsSubdirCombined = {
413 name = "rust-bin-tests-subdir-combined";
415 (mkTestFileWithMain "src/main.rs" "src_main")
416 (mkTestFile "tests/foo/main.rs" "tests_foo")
417 (mkTestFile "tests/bar/main.rs" "tests_bar")
421 expectedTestOutputs = [
422 "test src_main ... ok"
423 "test tests_foo ... ok"
424 "test tests_bar ... ok"
427 linkAgainstRlibCrate = {
429 src = mkFile "src/main.rs" ''
430 extern crate somerlib;
435 crateName = "somerlib";
437 src = mkLib "src/lib.rs";
444 buildRustCrate: boolVal:
445 mkCrate buildRustCrate {
447 src = mkFile "src/lib.rs" ''
448 pub const baz: bool = ${boolVal};
455 name = "build-script-and-main";
457 (mkFile "src/main.rs" ''
461 fn baz_false() { assert!(!bar::baz); }
464 (mkFile "build.rs" ''
466 fn main() { assert!(bar::baz); }
470 buildDependencies = [ (depCrate buildPackages.buildRustCrate "true") ];
471 dependencies = [ (depCrate buildRustCrate "false") ];
473 expectedTestOutputs = [ "test baz_false ... ok" ];
475 buildScriptFeatureEnv = {
476 crateName = "build-script-feature-env";
479 "crate/another_feature"
482 name = "build-script-feature-env";
484 (mkFile "src/main.rs" ''
487 fn feature_not_visible() {
488 assert!(std::env::var("CARGO_FEATURE_SOME_FEATURE").is_err());
489 assert!(option_env!("CARGO_FEATURE_SOME_FEATURE").is_none());
493 (mkFile "build.rs" ''
495 assert!(std::env::var("CARGO_FEATURE_SOME_FEATURE").is_ok());
496 assert!(option_env!("CARGO_FEATURE_SOME_FEATURE").is_none());
502 expectedTestOutputs = [ "test feature_not_visible ... ok" ];
504 # Regression test for https://github.com/NixOS/nixpkgs/pull/88054
505 # Build script output should be rewritten as valid env vars.
506 buildScriptIncludeDirDeps =
508 depCrate = mkHostCrate {
511 name = "build-script-and-include-dir-bar";
513 (mkFile "src/lib.rs" ''
516 (mkFile "build.rs" ''
517 use std::path::PathBuf;
518 fn main() { println!("cargo:include-dir={}/src", std::env::current_dir().unwrap_or(PathBuf::from(".")).to_str().unwrap()); }
527 name = "build-script-and-include-dir-foo";
529 (mkFile "src/main.rs" ''
532 (mkFile "build.rs" ''
533 fn main() { assert!(std::env::var_os("DEP_BAR_INCLUDE_DIR").is_some()); }
537 buildDependencies = [ depCrate ];
538 dependencies = [ depCrate ];
540 # Support new invocation prefix for build scripts `cargo::`
541 # https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script
542 buildScriptInvocationPrefix =
546 mkCrate buildRustCrate {
548 src = mkFile "build.rs" ''
550 // Old invocation prefix
551 // We likely won't see be mixing these syntaxes in the same build script in the wild.
552 println!("cargo:key_old=value_old");
554 // New invocation prefix
555 println!("cargo::metadata=key=value");
556 println!("cargo::metadata=key_complex=complex(value)");
564 name = "build-script-and-main-invocation-prefix";
566 (mkFile "src/main.rs" ''
567 const BUILDFOO: &'static str = env!("BUILDFOO");
570 fn build_foo_check() { assert!(BUILDFOO == "yes(check)"); }
574 (mkFile "build.rs" ''
577 assert!(env::var_os("DEP_BAR_KEY_OLD").expect("metadata key 'key_old' not set in dependency") == "value_old");
578 assert!(env::var_os("DEP_BAR_KEY").expect("metadata key 'key' not set in dependency") == "value");
579 assert!(env::var_os("DEP_BAR_KEY_COMPLEX").expect("metadata key 'key_complex' not set in dependency") == "complex(value)");
581 println!("cargo::rustc-env=BUILDFOO=yes(check)");
586 buildDependencies = [ (depCrate buildPackages.buildRustCrate) ];
587 dependencies = [ (depCrate buildRustCrate) ];
589 expectedTestOutputs = [ "test build_foo_check ... ok" ];
591 # Regression test for https://github.com/NixOS/nixpkgs/issues/74071
592 # Whenevever a build.rs file is generating files those should not be overlayed onto the actual source dir
593 buildRsOutDirOverlay = {
595 name = "buildrs-out-dir-overlay";
598 (mkFile "build.rs" ''
600 use std::ffi::OsString;
604 let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR not set");
605 let out_file = Path::new(&out_dir).join("lib.rs");
606 fs::write(out_file, "invalid rust code!").expect("failed to write lib.rs");
612 # Regression test for https://github.com/NixOS/nixpkgs/pull/83379
613 # link flag order should be preserved
616 name = "buildrs-out-dir-overlay";
618 (mkFile "build.rs" ''
620 // in the other order, linkage will fail
621 println!("cargo:rustc-link-lib=b");
622 println!("cargo:rustc-link-lib=a");
625 (mkFile "src/main.rs" ''
642 src = writeTextFile {
643 name = "${name}-src.c";
647 runCommandCC name { } ''
649 # Note: On darwin (which defaults to clang) we have to add
650 # `-undefined dynamic_lookup` as otherwise the compilation fails.
652 ${lib.optionalString stdenv.hostPlatform.isDarwin "-undefined dynamic_lookup"} \
653 -o $out/lib/${name}${stdenv.hostPlatform.extensions.library} ${src}
655 b = compile "libb" ''
665 a = compile "liba" ''
678 rustCargoTomlInSubDir = {
679 # The "workspace_member" can be set to the sub directory with the crate to build.
680 # By default ".", meaning the top level directory is assumed.
681 # Using null will trigger a search.
682 workspace_member = null;
683 src = symlinkJoin rec {
684 name = "find-cargo-toml";
686 (mkCargoToml { name = "ignoreMe"; })
687 (mkTestFileWithMain "src/main.rs" "ignore_main")
690 name = "rustCargoTomlInSubDir";
691 path = "subdir/Cargo.toml";
693 (mkTestFileWithMain "subdir/src/main.rs" "src_main")
694 (mkTestFile "subdir/tests/foo/main.rs" "tests_foo")
695 (mkTestFile "subdir/tests/bar/main.rs" "tests_bar")
699 expectedTestOutputs = [
700 "test src_main ... ok"
701 "test tests_foo ... ok"
702 "test tests_bar ... ok"
706 rustCargoTomlInTopDir =
708 withoutCargoTomlSearch = builtins.removeAttrs rustCargoTomlInSubDir [ "workspace_member" ];
710 withoutCargoTomlSearch
712 expectedTestOutputs = [
713 "test ignore_main ... ok"
716 procMacroInPrelude = {
720 name = "proc-macro-in-prelude";
722 (mkFile "src/lib.rs" ''
723 use proc_macro::TokenTree;
729 brotliCrates = (callPackage ./brotli-crates.nix { });
730 rcgenCrates = callPackage ./rcgen-crates.nix {
731 # Suppress deprecation warning
732 buildRustCrate = null;
734 tests = lib.mapAttrs (
735 key: value: mkTest (value // lib.optionalAttrs (!value ? crateName) { crateName = key; })
741 crateBinWithPathOutputs = assertOutputs {
742 name = "crateBinWithPath";
746 name = "test_binary1";
747 path = "src/foobar.rs";
750 src = mkBin "src/foobar.rs";
757 crateBinWithPathOutputsDebug = assertOutputs {
758 name = "crateBinWithPath";
763 name = "test_binary1";
764 path = "src/foobar.rs";
767 src = mkBin "src/foobar.rs";
773 ++ lib.optionals stdenv.hostPlatform.isDarwin [
774 # On Darwin, the debug symbols are in a separate directory.
775 "./bin/test_binary1.dSYM/Contents/Info.plist"
776 "./bin/test_binary1.dSYM/Contents/Resources/DWARF/test_binary1"
780 crateBinNoPath1Outputs = assertOutputs {
781 name = "crateBinNoPath1";
783 crateBin = [ { name = "my-binary2"; } ];
784 src = mkBin "src/my_binary2.rs";
791 crateLibOutputs = assertOutputs {
795 libName = "test_lib";
797 libPath = "src/lib.rs";
798 src = mkLib "src/lib.rs";
801 "./nix-support/propagated-build-inputs"
802 "./lib/libtest_lib.rlib"
807 crateLibOutputsDebug = assertOutputs {
812 libName = "test_lib";
814 libPath = "src/lib.rs";
815 src = mkLib "src/lib.rs";
818 "./nix-support/propagated-build-inputs"
819 "./lib/libtest_lib.rlib"
824 crateLibOutputsWasm32 = assertOutputs {
825 name = "wasm32-crate-lib";
827 mkCrate = mkCrate pkgsCross.wasm32-unknown-none.buildRustCrate;
829 libName = "test_lib";
831 libPath = "src/lib.rs";
832 src = mkLib "src/lib.rs";
835 "./nix-support/propagated-build-inputs"
836 "./lib/test_lib.wasm"
843 pkg = brotliCrates.brotli_2_5_0 { };
845 runCommand "run-brotli-test-cmd"
847 nativeBuildInputs = [ pkg ];
850 if stdenv.hostPlatform == stdenv.buildPlatform then
852 ${pkg}/bin/brotli -c ${pkg}/bin/brotli > /dev/null && touch $out
856 test -x '${pkg}/bin/brotli' && touch $out
861 pkg = brotliCrates.alloc_no_stdlib_1_3_0 { };
863 runCommand "run-alloc-no-stdlib-test-cmd"
865 nativeBuildInputs = [ pkg ];
868 test -e ${pkg}/bin/example && touch $out
870 brotliDecompressorTest =
872 pkg = brotliCrates.brotli_decompressor_1_3_1 { };
874 runCommand "run-brotli-decompressor-test-cmd"
876 nativeBuildInputs = [ pkg ];
879 test -e ${pkg}/bin/brotli-decompressor && touch $out
884 pkg = rcgenCrates.rootCrate.build;
886 runCommand "run-rcgen-test-cmd"
888 nativeBuildInputs = [ pkg ];
891 if stdenv.hostPlatform == stdenv.buildPlatform then
893 ${pkg}/bin/rcgen && touch $out
897 test -x '${pkg}/bin/rcgen' && touch $out
901 test = releaseTools.aggregate {
902 name = "buildRustCrate-tests";
904 description = "Test cases for buildRustCrate";
907 constituents = builtins.attrValues tests;