push sheeet
Some checks failed
Periodic Merges (6h) / master → staging-nixos (push) Failing after 12m50s
Periodic Merges (6h) / master → staging-next (push) Failing after 12m54s
Periodic Merges (24h) / merge-base(master,staging) → haskell-updates (push) Failing after 11m54s
Periodic Merges (6h) / staging-next → staging (push) Failing after 12m13s
Periodic Merges (24h) / staging-next-25.05 → staging-25.05 (push) Failing after 13m24s
Periodic Merges (24h) / release-25.05 → staging-next-25.05 (push) Failing after 14m28s

This commit is contained in:
Dark Steveneq
2025-10-09 14:15:47 +02:00
commit 646b892680
49168 changed files with 5897842 additions and 0 deletions

View File

@@ -0,0 +1,161 @@
{
lib,
stdenv,
mkRustcDepArgs,
mkRustcFeatureArgs,
needUnstableCLI,
rustc,
}:
{
crateName,
dependencies,
crateFeatures,
crateRenames,
libName,
release,
libPath,
crateType,
metadata,
crateBin,
hasCrateBin,
extraRustcOpts,
verbose,
colors,
buildTests,
codegenUnits,
}:
let
baseRustcOpts = [
(if release then "-C opt-level=3" else "-C debuginfo=2")
"-C codegen-units=${toString codegenUnits}"
"--remap-path-prefix=$NIX_BUILD_TOP=/"
(mkRustcDepArgs dependencies crateRenames)
(mkRustcFeatureArgs crateFeatures)
]
++ lib.optionals (stdenv.hostPlatform != stdenv.buildPlatform) [
"--target"
stdenv.hostPlatform.rust.rustcTargetSpec
]
++ lib.optionals (needUnstableCLI dependencies) [
"-Z"
"unstable-options"
]
++ extraRustcOpts
# since rustc 1.42 the "proc_macro" crate is part of the default crate prelude
# https://github.com/rust-lang/cargo/commit/4d64eb99a4#diff-7f98585dbf9d30aa100c8318e2c77e79R1021-R1022
++ lib.optional (lib.elem "proc-macro" crateType) "--extern proc_macro"
++
lib.optional (stdenv.hostPlatform.linker == "lld" && rustc ? llvmPackages.lld) # Needed when building for targets that use lld. e.g. 'wasm32-unknown-unknown'
"-C linker=${rustc.llvmPackages.lld}/bin/lld"
++ lib.optional (
stdenv.hasCC && stdenv.hostPlatform.linker != "lld"
) "-C linker=${stdenv.cc}/bin/${stdenv.cc.targetPrefix}cc";
rustcMeta = "-C metadata=${metadata} -C extra-filename=-${metadata}";
# 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
# configure & source common build functions
LIB_RUSTC_OPTS="${libRustcOpts}"
BIN_RUSTC_OPTS="${binRustcOpts}"
LIB_EXT="${stdenv.hostPlatform.extensions.library}"
LIB_PATH="${libPath}"
LIB_NAME="${libName}"
CRATE_NAME='${lib.replaceStrings [ "-" ] [ "_" ] libName}'
setup_link_paths
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
${lib.optionalString buildTests "build_lib_test src/lib.rs"}
fi
${lib.optionalString (lib.length crateBin > 0) (
lib.concatMapStringsSep "\n" (
bin:
let
haveRequiredFeature =
if bin ? requiredFeatures then
# Check that all element in requiredFeatures are also present in crateFeatures
lib.intersectLists bin.requiredFeatures crateFeatures == bin.requiredFeatures
else
true;
in
if haveRequiredFeature then
''
mkdir -p target/bin
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"
''
else
''
echo Binary ${bin.name or crateName} not compiled due to not having all of the required features -- ${lib.escapeShellArg (builtins.toJSON bin.requiredFeatures)} -- enabled.
''
) 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
fi
for i in src/bin/*.rs; do #*/
mkdir -p target/bin
${build_bin} "$(basename $i .rs)" "$i"
done
''}
# Remove object files to avoid "wrong ELF type"
find target -type f -name "*.o" -print0 | xargs -0 rm -f
runHook postBuild
''

View File

@@ -0,0 +1,259 @@
{
lib,
stdenv,
echo_colored,
noisily,
mkRustcDepArgs,
mkRustcFeatureArgs,
}:
{
build,
buildDependencies,
codegenUnits,
colors,
completeBuildDeps,
completeDeps,
crateAuthors,
crateDescription,
crateFeatures,
crateHomepage,
crateLicense,
crateLicenseFile,
crateLinks,
crateName,
crateType,
crateReadme,
crateRenames,
crateRepository,
crateRustVersion,
crateVersion,
extraLinkFlags,
extraRustcOptsForBuildRs,
libName,
libPath,
release,
verbose,
workspace_member,
}:
let
version_ = lib.splitString "-" crateVersion;
versionPre = lib.optionalString (lib.tail version_ != [ ]) (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=${toString codegenUnits}" ] ++ extraRustcOptsForBuildRs);
buildDeps = mkRustcDepArgs buildDependencies crateRenames;
authors = lib.concatStringsSep ":" crateAuthors;
optLevel = if release then 3 else 0;
completeDepsDir = lib.concatStringsSep " " completeDeps;
completeBuildDepsDir = lib.concatStringsSep " " completeBuildDeps;
envFeatures = lib.concatStringsSep " " (
map (f: lib.replaceStrings [ "-" ] [ "_" ] (lib.toUpper f)) crateFeatures
);
in
''
${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
i=$1
ln -s -f $i/lib/*.rlib $2 #*/
ln -s -f $i/lib/*.so $i/lib/*.dylib $2 #*/
if [ -e $i/env ]; then
source $i/env
fi
}
# The following steps set up the dependencies of the crate. Two
# kinds of dependencies are distinguished: build dependencies
# (used by the build script) and crate dependencies. For each
# dependency we have to:
#
# - Make its Rust library available to rustc. This is done by
# symlinking all library dependencies into a directory that
# can be provided to rustc.
# - Accumulate linking flags. These flags are largely used for
# linking native libraries.
#
# The crate link flags are added to the `link` and `link.final`
# files. The `link` file is used for linkage in the current
# crate. The `link.final` file will be copied to the output and can
# be used by downstream crates to get the linker flags of this
# crate.
mkdir -p target/{deps,lib,build,buildDeps}
chmod uga+w target -R
echo ${extraLinkFlags} > target/link
echo ${extraLinkFlags} > target/link.final
# Prepare crate dependencies
for i in ${completeDepsDir}; do
symlink_dependency $i target/deps
if [ -e "$i/lib/link" ]; then
cat $i/lib/link >> target/link
cat $i/lib/link >> target/link.final
fi
done
# Prepare crate build dependencies that are used for the build script.
for i in ${completeBuildDepsDir}; do
symlink_dependency $i target/buildDeps
if [ -e "$i/lib/link" ]; then
cat $i/lib/link >> target/link.build
fi
done
# Remove duplicate linker flags from the build dependencies.
if [[ -e target/link.build ]]; then
sort -uo target/link.build target/link.build
fi
# Remove duplicate linker flags from the dependencies.
sort -uo target/link target/link
tr '\n' ' ' < target/link > target/link_
# Remove duplicate linker flags from the that are written
# to the derivation's output.
sort -uo target/link.final target/link.final
EXTRA_BUILD=""
BUILD_OUT_DIR=""
# Set up Cargo Environment variables: https://doc.rust-lang.org/cargo/reference/environment-variables.html
export CARGO_PKG_NAME=${crateName}
export CARGO_PKG_VERSION=${crateVersion}
export CARGO_PKG_AUTHORS="${authors}"
export CARGO_PKG_DESCRIPTION="${crateDescription}"
export CARGO_CFG_TARGET_ARCH=${stdenv.hostPlatform.rust.platform.arch}
export CARGO_CFG_TARGET_OS=${stdenv.hostPlatform.rust.platform.os}
export CARGO_CFG_TARGET_FAMILY="unix"
export CARGO_CFG_UNIX=1
export CARGO_CFG_TARGET_ENV="gnu"
export CARGO_CFG_TARGET_ENDIAN=${
if stdenv.hostPlatform.parsed.cpu.significantByte.name == "littleEndian" then "little" else "big"
}
export CARGO_CFG_TARGET_POINTER_WIDTH=${
with stdenv.hostPlatform; toString (if isILP32 then 32 else parsed.cpu.bits)
}
export CARGO_CFG_TARGET_VENDOR=${stdenv.hostPlatform.parsed.vendor.name}
export CARGO_MANIFEST_DIR=$(pwd)
export CARGO_MANIFEST_LINKS=${crateLinks}
export DEBUG="${toString (!release)}"
export OPT_LEVEL="${toString optLevel}"
export TARGET="${stdenv.hostPlatform.rust.rustcTargetSpec}"
export HOST="${stdenv.buildPlatform.rust.rustcTargetSpec}"
export PROFILE=${if release then "release" else "debug"}
export OUT_DIR=$(pwd)/target/build/${crateName}.out
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 CARGO_PKG_LICENSE="${crateLicense}"
export CARGO_PKG_LICENSE_FILE="${crateLicenseFile}"
export CARGO_PKG_README="${crateReadme}"
export CARGO_PKG_REPOSITORY="${crateRepository}"
export CARGO_PKG_RUST_VERSION="${crateRustVersion}"
export NUM_JOBS=$NIX_BUILD_CORES
export RUSTC="rustc"
export RUSTDOC="rustdoc"
BUILD=""
if [[ ! -z "${build}" ]] ; then
BUILD=${build}
elif [[ -e "build.rs" ]]; then
BUILD="build.rs"
fi
# Compile and run the build script, when available.
if [[ ! -z "$BUILD" ]] ; then
echo_build_heading "$BUILD" ${libName}
mkdir -p target/build/${crateName}
EXTRA_BUILD_FLAGS=""
if [ -e target/link.build ]; then
EXTRA_BUILD_FLAGS="$EXTRA_BUILD_FLAGS $(tr '\n' ' ' < target/link.build)"
fi
noisily rustc --crate-name build_script_build $BUILD --crate-type bin ${rustcOpts} \
${mkRustcFeatureArgs crateFeatures} --out-dir target/build/${crateName} --emit=dep-info,link \
-L dependency=target/buildDeps ${buildDeps} --cap-lints allow $EXTRA_BUILD_FLAGS --color ${colors}
mkdir -p target/build/${crateName}.out
export RUST_BACKTRACE=1
BUILD_OUT_DIR="-L $OUT_DIR"
mkdir -p $OUT_DIR
(
# Features should be set as environment variable for build scripts:
# https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts
for feature in ${envFeatures}; do
export CARGO_FEATURE_$feature=1
done
target/build/${crateName}/build_script_build | tee target/build/${crateName}.opt
)
set +e
# We want to support the new prefix invocation syntax which uses two colons
# See https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script
EXTRA_BUILD=$(sed -n "s/^cargo::\{0,1\}rustc-flags=\(.*\)/\1/p" target/build/${crateName}.opt | tr '\n' ' ' | sort -u)
EXTRA_FEATURES=$(sed -n "s/^cargo::\{0,1\}rustc-cfg=\(.*\)/--cfg \1/p" target/build/${crateName}.opt | tr '\n' ' ')
EXTRA_LINK_ARGS=$(sed -n "s/^cargo::\{0,1\}rustc-link-arg=\(.*\)/-C link-arg=\1/p" target/build/${crateName}.opt | tr '\n' ' ')
EXTRA_LINK_ARGS_BINS=$(sed -n "s/^cargo::\{0,1\}rustc-link-arg-bins=\(.*\)/-C link-arg=\1/p" target/build/${crateName}.opt | tr '\n' ' ')
EXTRA_LINK_ARGS_LIB=$(sed -n "s/^cargo::\{0,1\}rustc-link-arg-lib=\(.*\)/-C link-arg=\1/p" target/build/${crateName}.opt | tr '\n' ' ')
EXTRA_LINK_LIBS=$(sed -n "s/^cargo::\{0,1\}rustc-link-lib=\(.*\)/\1/p" target/build/${crateName}.opt | tr '\n' ' ')
EXTRA_LINK_SEARCH=$(sed -n "s/^cargo::\{0,1\}rustc-link-search=\(.*\)/\1/p" target/build/${crateName}.opt | tr '\n' ' ' | sort -u)
${lib.optionalString (lib.elem "cdylib" crateType) ''
CRATE_TYPE_IS_CDYLIB="true"
EXTRA_CDYLIB_LINK_ARGS=$(sed -n "s/^cargo::\{0,1\}rustc-cdylib-link-arg=\(.*\)/-C link-arg=\1/p" target/build/${crateName}.opt | tr '\n' ' ')
''}
# We want to read part of every line that has cargo:rustc-env= prefix and
# export it as environment variables. This turns out tricky if the lines
# have spaces: we can't wrap the command in double quotes as that captures
# all the lines in single output. We can't use while read loop because
# exporting from inside of it doesn't make it to the outside scope. We
# can't use xargs as export is a built-in and does not work from it. As a
# last resort then, we change the IFS so that the for loop does not split
# on spaces and reset it after we are done. See ticket #199298.
#
_OLDIFS="$IFS"
IFS=$'\n'
for env in $(sed -n "s/^cargo::\{0,1\}rustc-env=\(.*\)/\1/p" target/build/${crateName}.opt); do
export "$env"
done
IFS="$_OLDIFS"
CRATENAME=$(echo ${crateName} | sed -e "s/\(.*\)-sys$/\U\1/" -e "s/-/_/g")
# SemVer allows version numbers to contain alphanumeric characters and `.+-`
# which aren't legal bash identifiers.
# https://semver.org/#backusnaur-form-grammar-for-valid-semver-versions
CRATEVERSION=$(echo ${crateVersion} | sed -e "s/[\.\+-]/_/g")
grep -P "^cargo:(?!:?(rustc-|warning=|rerun-if-changed=|rerun-if-env-changed))" target/build/${crateName}.opt \
| awk -F= "/^cargo::metadata=/ { gsub(/-/, \"_\", \$2); print \"export \" toupper(\"DEP_$(echo $CRATENAME)_\" \$2) \"=\" \"\\\"\"\$3\"\\\"\"; next }
/^cargo:/ { sub(/^cargo::?/, \"\", \$1); gsub(/-/, \"_\", \$1); print \"export \" toupper(\"DEP_$(echo $CRATENAME)_\" \$1) \"=\" \"\\\"\"\$2\"\\\"\"; print \"export \" toupper(\"DEP_$(echo $CRATENAME)_$(echo $CRATEVERSION)_\" \$1) \"=\" \"\\\"\"\$2\"\\\"\"; next }" > target/env
set -e
fi
runHook postConfigure
''

View File

@@ -0,0 +1,494 @@
# Code for buildRustCrate, a Nix function that builds Rust code, just
# like Cargo, but using Nix instead.
#
# This can be useful for deploying packages with NixOps, and to share
# binary dependencies between projects.
{
lib,
stdenv,
defaultCrateOverrides,
fetchCrate,
pkgsBuildBuild,
rustc,
cargo,
jq,
libiconv,
# Controls codegen parallelization for all crates.
# May be overridden on a per-crate level.
# See <https://doc.rust-lang.org/rustc/codegen-options/index.html#codegen-units>
defaultCodegenUnits ? 1,
}:
let
# Create rustc arguments to link against the given list of dependencies
# and renames.
#
# See docs for crateRenames below.
mkRustcDepArgs =
dependencies: crateRenames:
lib.concatMapStringsSep " " (
dep:
let
normalizeName = lib.replaceStrings [ "-" ] [ "_" ];
extern = normalizeName dep.libName;
# Find a choice that matches in name and optionally version.
findMatchOrUseExtern =
choices:
lib.findFirst (choice: (!(choice ? version) || choice.version == dep.version or "")) {
rename = extern;
} choices;
name =
if lib.hasAttr dep.crateName crateRenames then
let
choices = crateRenames.${dep.crateName};
in
normalizeName (if builtins.isList choices then (findMatchOrUseExtern choices).rename else choices)
else
extern;
opts = lib.optionalString (dep.stdlib or false) "noprelude:";
filename =
if lib.any (x: x == "lib" || x == "rlib") dep.crateType then
"${dep.metadata}.rlib"
# Adjust lib filename for crates of type proc-macro. Proc macros are compiled/run on the build platform architecture.
else if (lib.attrByPath [ "procMacro" ] false dep) then
"${dep.metadata}${stdenv.buildPlatform.extensions.library}"
else
"${dep.metadata}${stdenv.hostPlatform.extensions.library}";
in
" --extern ${opts}${name}=${dep.lib}/lib/lib${extern}-${filename}"
) dependencies;
# Create feature arguments for rustc.
mkRustcFeatureArgs = lib.concatMapStringsSep " " (f: ''--cfg feature=\"${f}\"'');
# Whether we need to use unstable command line flags
#
# Currently just needed for standard library dependencies, which have a
# special "noprelude:" modifier. If in later versions of Rust this is
# stabilized we can account for that here, too, so we don't opt into
# instability unnecessarily.
needUnstableCLI = dependencies: lib.any (dep: dep.stdlib or false) dependencies;
inherit (import ./log.nix { inherit lib; }) noisily echo_colored;
configureCrate = import ./configure-crate.nix {
inherit
lib
stdenv
echo_colored
noisily
mkRustcDepArgs
mkRustcFeatureArgs
;
};
installCrate = import ./install-crate.nix { inherit stdenv; };
in
/*
The overridable pkgs.buildRustCrate function.
*
* Any unrecognized parameters will be passed as to
* the underlying stdenv.mkDerivation.
*/
crate_:
lib.makeOverridable
(
# The rust compiler to use.
#
# Default: pkgs.rustc
{
rust ? rustc,
# The cargo package to use for getting some metadata.
#
# Default: pkgs.cargo
cargo ? cargo,
# Whether to build a release version (`true`) or a debug
# version (`false`). Debug versions are faster to build
# but might be much slower at runtime.
release,
# Whether to print rustc invocations etc.
#
# Example: false
# Default: true
verbose,
# A list of rust/cargo features to enable while building the crate.
# Example: [ "std" "async" ]
features,
# Additional native build inputs for building this crate.
nativeBuildInputs,
# Additional build inputs for building this crate.
#
# Example: [ pkgs.openssl ]
buildInputs,
# Allows to override the parameters to buildRustCrate
# for any rust dependency in the transitive build tree.
#
# Default: pkgs.defaultCrateOverrides
#
# Example:
#
# pkgs.defaultCrateOverrides // {
# hello = attrs: { buildInputs = [ openssl ]; };
# }
crateOverrides,
# Rust library dependencies, i.e. other libraries that were built
# with buildRustCrate.
dependencies,
# Rust build dependencies, i.e. other libraries that were built
# with buildRustCrate and are used by a build script.
buildDependencies,
# Specify the "extern" name of a library if it differs from the library target.
# See above for an extended explanation.
#
# Default: no renames.
#
# Example:
#
# `crateRenames` supports two formats.
#
# The simple version is an attrset that maps the
# `crateName`s of the dependencies to their alternative
# names.
#
# ```nix
# {
# my_crate_name = "my_alternative_name";
# # ...
# }
# ```
#
# The extended version is also keyed by the `crateName`s but allows
# different names for different crate versions:
#
# ```nix
# {
# my_crate_name = [
# { version = "1.2.3"; rename = "my_alternative_name01"; }
# { version = "3.2.3"; rename = "my_alternative_name03"; }
# ]
# # ...
# }
# ```
#
# This roughly corresponds to the following snippet in Cargo.toml:
#
# ```toml
# [dependencies]
# my_alternative_name01 = { package = "my_crate_name", version = "0.1" }
# my_alternative_name03 = { package = "my_crate_name", version = "0.3" }
# ```
#
# Dependencies which use the lib target name as extern name, do not need
# to be specified in the crateRenames, even if their crate name differs.
#
# Including multiple versions of a crate is very popular during
# ecosystem transitions, e.g. from futures 0.1 to futures 0.3.
crateRenames,
# A list of extra options to pass to rustc.
#
# Example: [ "-Z debuginfo=2" ]
# Default: []
extraRustcOpts,
# A list of extra options to pass to rustc when building a build.rs.
#
# Example: [ "-Z debuginfo=2" ]
# Default: []
extraRustcOptsForBuildRs,
# Whether to enable building tests.
# Use true to enable.
# Default: false
buildTests,
# Passed to stdenv.mkDerivation.
preUnpack,
# Passed to stdenv.mkDerivation.
postUnpack,
# Passed to stdenv.mkDerivation.
prePatch,
# Passed to stdenv.mkDerivation.
patches,
# Passed to stdenv.mkDerivation.
postPatch,
# Passed to stdenv.mkDerivation.
preConfigure,
# Passed to stdenv.mkDerivation.
postConfigure,
# Passed to stdenv.mkDerivation.
preBuild,
# Passed to stdenv.mkDerivation.
postBuild,
# Passed to stdenv.mkDerivation.
preInstall,
# Passed to stdenv.mkDerivation.
postInstall,
}:
let
crate = crate_ // (lib.attrByPath [ crate_.crateName ] (attr: { }) crateOverrides crate_);
dependencies_ = dependencies;
buildDependencies_ = buildDependencies;
processedAttrs = [
"src"
"nativeBuildInputs"
"buildInputs"
"crateBin"
"crateLib"
"libName"
"libPath"
"buildDependencies"
"dependencies"
"features"
"crateRenames"
"crateName"
"version"
"build"
"authors"
"colors"
"edition"
"buildTests"
"codegenUnits"
"links"
];
extraDerivationAttrs = removeAttrs crate processedAttrs;
nativeBuildInputs_ = nativeBuildInputs;
buildInputs_ = buildInputs;
extraRustcOpts_ = extraRustcOpts;
extraRustcOptsForBuildRs_ = extraRustcOptsForBuildRs;
buildTests_ = buildTests;
# 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;
buildCrate = import ./build-crate.nix {
inherit
lib
stdenv
mkRustcDepArgs
mkRustcFeatureArgs
needUnstableCLI
;
rustc = rust;
};
in
stdenv.mkDerivation (
rec {
inherit (crate) crateName;
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"}";
version = crate.version;
depsBuildBuild = [ pkgsBuildBuild.stdenv.cc ];
nativeBuildInputs = [
rust
cargo
jq
]
++ lib.optionals stdenv.hasCC [ stdenv.cc ]
++ lib.optionals stdenv.buildPlatform.isDarwin [ libiconv ]
++ (crate.nativeBuildInputs or [ ])
++ nativeBuildInputs_;
buildInputs =
lib.optionals stdenv.hostPlatform.isDarwin [ libiconv ]
++ (crate.buildInputs or [ ])
++ buildInputs_;
dependencies = map lib.getLib dependencies_;
buildDependencies = map lib.getLib buildDependencies_;
completeDeps = lib.unique (dependencies ++ lib.concatMap (dep: dep.completeDeps) dependencies);
completeBuildDeps = lib.unique (
buildDependencies
++ lib.concatMap (dep: dep.completeBuildDeps ++ dep.completeDeps) buildDependencies
);
# Create a list of features that are enabled by the crate itself and
# through the features argument of buildRustCrate. Exclude features
# with a forward slash, since they are passed through to dependencies,
# and dep: features, since they're internal-only and do nothing except
# enable optional dependencies.
crateFeatures = lib.optionals (crate ? features) (
builtins.filter (f: !(lib.hasInfix "/" f || lib.hasPrefix "dep:" f)) (crate.features ++ features)
);
libName = if crate ? libName then crate.libName else crate.crateName;
libPath = lib.optionalString (crate ? libPath) crate.libPath;
# 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 (mkRustcFeatureArgs crateFeatures)
+ "___"
+ depsMetadata
+ "___"
+ stdenv.hostPlatform.rust.rustcTarget
);
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 ".";
crateAuthors = if crate ? authors && lib.isList crate.authors then crate.authors else [ ];
crateDescription = crate.description or "";
crateHomepage = crate.homepage or "";
crateLicense = crate.license or "";
crateLicenseFile = crate.license-file or "";
crateLinks = crate.links or "";
crateReadme = crate.readme or "";
crateRepository = crate.repository or "";
crateRustVersion = crate.rust-version or "";
crateVersion = crate.version;
crateType =
if lib.attrByPath [ "procMacro" ] false crate then
[ "proc-macro" ]
else if lib.attrByPath [ "plugin" ] false crate then
[ "dylib" ]
else
(crate.type or [ "lib" ]);
colors = lib.attrByPath [ "colors" ] "always" crate;
extraLinkFlags = lib.concatStringsSep " " (crate.extraLinkFlags or [ ]);
edition = crate.edition or null;
codegenUnits = if crate ? codegenUnits then crate.codegenUnits else defaultCodegenUnits;
extraRustcOpts =
lib.optionals (crate ? extraRustcOpts) crate.extraRustcOpts
++ extraRustcOpts_
++ (lib.optional (edition != null) "--edition ${edition}");
extraRustcOptsForBuildRs =
lib.optionals (crate ? extraRustcOptsForBuildRs) crate.extraRustcOptsForBuildRs
++ extraRustcOptsForBuildRs_
++ (lib.optional (edition != null) "--edition ${edition}");
configurePhase = configureCrate {
inherit
crateName
crateType
buildDependencies
completeDeps
completeBuildDeps
crateDescription
crateFeatures
crateRenames
libName
build
workspace_member
release
libPath
crateVersion
crateLinks
extraLinkFlags
extraRustcOptsForBuildRs
crateLicense
crateLicenseFile
crateReadme
crateRepository
crateRustVersion
crateAuthors
crateHomepage
verbose
colors
codegenUnits
;
};
buildPhase = buildCrate {
inherit
crateName
dependencies
crateFeatures
crateRenames
libName
release
libPath
crateType
metadata
hasCrateBin
crateBin
verbose
colors
extraRustcOpts
buildTests
codegenUnits
;
};
dontStrip = !release;
# We need to preserve metadata in .rlib, which might get stripped on macOS. See https://github.com/NixOS/nixpkgs/issues/218712
stripExclude = [ "*.rlib" ];
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" ];
meta = {
mainProgram = crateName;
badPlatforms = [
# Rust is currently unable to target the n32 ABI
lib.systems.inspect.patterns.isMips64n32
];
};
}
// extraDerivationAttrs
)
)
{
rust = crate_.rust or rustc;
cargo = crate_.cargo or cargo;
release = crate_.release or true;
verbose = crate_.verbose or true;
extraRustcOpts = [ ];
extraRustcOptsForBuildRs = [ ];
features = [ ];
nativeBuildInputs = [ ];
buildInputs = [ ];
crateOverrides = defaultCrateOverrides;
preUnpack = crate_.preUnpack or "";
postUnpack = crate_.postUnpack or "";
prePatch = crate_.prePatch or "";
patches = crate_.patches or [ ];
postPatch = crate_.postPatch or "";
preConfigure = crate_.preConfigure or "";
postConfigure = crate_.postConfigure or "";
preBuild = crate_.preBuild or "";
postBuild = crate_.postBuild or "";
preInstall = crate_.preInstall or "";
postInstall = crate_.postInstall or "";
dependencies = crate_.dependencies or [ ];
buildDependencies = crate_.buildDependencies or [ ];
crateRenames = crate_.crateRenames or { };
buildTests = crate_.buildTests or false;
}

View File

@@ -0,0 +1,35 @@
{ stdenv, lib }:
{
kernel = stdenv.hostPlatform.parsed.kernel.name;
abi = stdenv.hostPlatform.parsed.abi.name;
cpu = stdenv.hostPlatform.parsed.cpu.name;
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.foldl (
features: featureName:
if feat.${featureName} or false then [ featureName ] ++ features else features
) [ ] (lib.attrNames feat);
include =
includedFiles: src:
builtins.filterSource (
path: type:
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.all (f: !lib.strings.hasPrefix (toString (src + ("/" + f))) path) excludedFiles
) src;
}

View File

@@ -0,0 +1,53 @@
{ 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
if [[ -s target/env ]]; then
mkdir -p $lib
cp target/env $lib/env
fi
if [[ -s target/link.final ]]; then
mkdir -p $lib/lib
cp target/link.final $lib/lib/link
fi
if [[ "$(ls -A target/lib)" ]]; then
mkdir -p $lib/lib
cp -r 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 $lib/lib
cp -r target/build/* $lib/lib # */
fi
if [[ -d target/bin ]]; then
if [[ "$(ls -A target/bin)" ]]; then
mkdir -p $out/bin
cp -rP 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.library}' \
-a \! -name '*.d' \
-executable \
-print0 | xargs --no-run-if-empty --null install --target $out/tests;
fi
runHook postInstall
''

View File

@@ -0,0 +1,192 @@
echo_build_heading() {
if (( $# == 1 )); then
echo_colored "Building $1"
else
echo_colored "Building $1 ($2)"
fi
}
build_lib() {
lib_src=$1
echo_build_heading $lib_src ${libName}
noisily rustc \
--crate-name $CRATE_NAME \
$lib_src \
--out-dir target/lib \
-L dependency=target/deps \
--cap-lints allow \
$LINK \
$EXTRA_LINK_ARGS \
$EXTRA_LINK_ARGS_LIB \
$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 \
-L dependency=target/deps \
$LINK \
$EXTRA_LINK_ARGS \
$EXTRA_LINK_ARGS_BINS \
$EXTRA_LIB \
--cap-lints allow \
$BUILD_OUT_DIR \
$EXTRA_BUILD \
$EXTRA_FEATURES \
$EXTRA_RUSTC_FLAGS \
--color ${colors} \
if [ "$crate_name_" != "$crate_name" ]; then
if [ -f "target/bin/$crate_name_.wasm" ]; then
mv target/bin/$crate_name_.wasm target/bin/$crate_name.wasm
else
mv target/bin/$crate_name_ target/bin/$crate_name
fi
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//\//_}"
# Make sure to strip the top level `tests` directory: see #204051. Note that
# a forward slash has now become an underscore due to the substitution
# above.
derived_crate_name=${derived_crate_name#"tests_"}
derived_crate_name="${derived_crate_name%.rs}"
build_bin_test "$derived_crate_name" "$file"
}
# Add additional link options that were provided by the build script.
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_LIBS" | while read i; do
if [[ ! -z "$i" ]]; then
for library in $i; do
echo "-l $library" >> target/link
done
fi
done
if [[ -e target/link ]]; then
tr '\n' ' ' < target/link > target/link_
LINK=$(cat target/link_)
fi
# Add "rustc-cdylib-link-arg" as linker arguments
# https://doc.rust-lang.org/cargo/reference/build-scripts.html#rustc-cdylib-link-arg
if [[ -n "$CRATE_TYPE_IS_CDYLIB" ]]; then
EXTRA_BUILD+=" $EXTRA_CDYLIB_LINK_ARGS"
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_error "ERROR: failed to find file for binary target: $BIN_NAME" >&2
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
}

View File

@@ -0,0 +1,61 @@
{ lib }:
let
echo_colored_body =
start_escape:
# Body of a function that behaves like "echo" but
# has the output colored by the given start_escape
# sequence. E.g.
#
# * echo_x "Building ..."
# * echo_x -n "Running "
#
# This is more complicated than apparent at first sight
# because:
# * The color markers and the text must be print
# in the same echo statement. Otherwise, other
# intermingled text from concurrent builds will
# be colored as well.
# * We need to preserve the trailing newline of the
# echo if and only if it is present. Bash likes
# to strip those if we capture the output of echo
# in a variable.
# * Leading "-" will be interpreted by test as an
# option for itself. Therefore, we prefix it with
# an x in `[[ "x$1" =~ ^x- ]]`.
''
local echo_args="";
while [[ "x$1" =~ ^x- ]]; do
echo_args+=" $1"
shift
done
local start_escape="$(printf '${start_escape}')"
local reset="$(printf '\033[0m')"
echo $echo_args $start_escape"$@"$reset
'';
echo_conditional_colored_body =
colors: start_escape:
if colors == "always" then (echo_colored_body start_escape) else ''echo "$@"'';
in
{
echo_colored = colors: ''
echo_colored() {
${echo_conditional_colored_body colors ''\033[0;1;32m''}
}
echo_error() {
${echo_conditional_colored_body colors ''\033[0;1;31m''}
}
'';
noisily = colors: verbose: ''
noisily() {
${lib.optionalString verbose ''
echo_colored -n "Running "
echo $@
''}
$@
}
'';
}

View File

@@ -0,0 +1,161 @@
{
lib,
stdenv,
buildRustCrate,
fetchgit,
}:
let
kernel = stdenv.buildPlatform.parsed.kernel.name;
abi = stdenv.buildPlatform.parsed.abi.name;
include =
includedFiles: src:
builtins.filterSource (
path: type:
lib.lists.any (
f:
let
p = toString (src + ("/" + f));
in
(path == p) || (type == "directory" && lib.strings.hasPrefix path p)
) includedFiles
) src;
updateFeatures =
f: up: functions:
builtins.deepSeq f (
lib.lists.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:
if feat.${featureName} or false then [ featureName ] ++ features else features
) [ ] (builtins.attrNames feat);
in
rec {
alloc_no_stdlib_1_3_0_ =
{
dependencies ? [ ],
buildDependencies ? [ ],
features ? [ ],
}:
buildRustCrate {
crateName = "alloc-no-stdlib";
version = "1.3.0";
authors = [ "Daniel Reiter Horn <danielrh@dropbox.com>" ];
sha256 = "1jcp27pzmqdszgp80y484g4kwbjbg7x8a589drcwbxg0i8xwkir9";
crateBin = [ { name = "example"; } ];
inherit dependencies buildDependencies features;
};
brotli_2_5_0_ =
{
dependencies ? [ ],
buildDependencies ? [ ],
features ? [ ],
}:
buildRustCrate {
crateName = "brotli";
version = "2.5.0";
authors = [
"Daniel Reiter Horn <danielrh@dropbox.com>"
"The Brotli Authors"
];
sha256 = "1ynw4hkdwnp0kj30p86ls44ahv4s99258s019bqrq4mya8hlsb5b";
crateBin = [ { name = "brotli"; } ];
inherit dependencies buildDependencies features;
};
brotli_decompressor_1_3_1_ =
{
dependencies ? [ ],
buildDependencies ? [ ],
features ? [ ],
}:
buildRustCrate {
crateName = "brotli-decompressor";
version = "1.3.1";
authors = [
"Daniel Reiter Horn <danielrh@dropbox.com>"
"The Brotli Authors"
];
sha256 = "022g69q1xzwdj0130qm3fa4qwpn4q1jx3lc8yz0v0v201p7bm8fb";
crateBin = [ { name = "brotli-decompressor"; } ];
inherit dependencies buildDependencies features;
};
alloc_no_stdlib_1_3_0 =
{
features ? (alloc_no_stdlib_1_3_0_features { }),
}:
alloc_no_stdlib_1_3_0_ {
features = mkFeatures (features.alloc_no_stdlib_1_3_0 or { });
};
alloc_no_stdlib_1_3_0_features =
f:
updateFeatures f {
alloc_no_stdlib_1_3_0.default = (f.alloc_no_stdlib_1_3_0.default or true);
} [ ];
brotli_2_5_0 =
{
features ? (brotli_2_5_0_features { }),
}:
brotli_2_5_0_ {
dependencies = mapFeatures features [
alloc_no_stdlib_1_3_0
brotli_decompressor_1_3_1
];
features = mkFeatures (features.brotli_2_5_0 or { });
};
brotli_2_5_0_features =
f:
updateFeatures f
rec {
alloc_no_stdlib_1_3_0.no-stdlib =
(f.alloc_no_stdlib_1_3_0.no-stdlib or false)
|| (brotli_2_5_0.no-stdlib or false)
|| (f.brotli_2_5_0.no-stdlib or false);
alloc_no_stdlib_1_3_0.default = true;
brotli_2_5_0.default = (f.brotli_2_5_0.default or true);
brotli_decompressor_1_3_1.disable-timer =
(f.brotli_decompressor_1_3_1.disable-timer or false)
|| (brotli_2_5_0.disable-timer or false)
|| (f.brotli_2_5_0.disable-timer or false);
brotli_decompressor_1_3_1.no-stdlib =
(f.brotli_decompressor_1_3_1.no-stdlib or false)
|| (brotli_2_5_0.no-stdlib or false)
|| (f.brotli_2_5_0.no-stdlib or false);
brotli_decompressor_1_3_1.benchmark =
(f.brotli_decompressor_1_3_1.benchmark or false)
|| (brotli_2_5_0.benchmark or false)
|| (f.brotli_2_5_0.benchmark or false);
brotli_decompressor_1_3_1.default = true;
brotli_decompressor_1_3_1.seccomp =
(f.brotli_decompressor_1_3_1.seccomp or false)
|| (brotli_2_5_0.seccomp or false)
|| (f.brotli_2_5_0.seccomp or false);
}
[
alloc_no_stdlib_1_3_0_features
brotli_decompressor_1_3_1_features
];
brotli_decompressor_1_3_1 =
{
features ? (brotli_decompressor_1_3_1_features { }),
}:
brotli_decompressor_1_3_1_ {
dependencies = mapFeatures features [ alloc_no_stdlib_1_3_0 ];
features = mkFeatures (features.brotli_decompressor_1_3_1 or { });
};
brotli_decompressor_1_3_1_features =
f:
updateFeatures f rec {
alloc_no_stdlib_1_3_0.no-stdlib =
(f.alloc_no_stdlib_1_3_0.no-stdlib or false)
|| (brotli_decompressor_1_3_1.no-stdlib or false)
|| (f.brotli_decompressor_1_3_1.no-stdlib or false);
alloc_no_stdlib_1_3_0.default = true;
alloc_no_stdlib_1_3_0.unsafe =
(f.alloc_no_stdlib_1_3_0.unsafe or false)
|| (brotli_decompressor_1_3_1.unsafe or false)
|| (f.brotli_decompressor_1_3_1.unsafe or false);
brotli_decompressor_1_3_1.default = (f.brotli_decompressor_1_3_1.default or true);
} [ alloc_no_stdlib_1_3_0_features ];
}

View File

@@ -0,0 +1,922 @@
{
lib,
buildPackages,
buildRustCrate,
callPackage,
releaseTools,
runCommand,
runCommandCC,
stdenv,
symlinkJoin,
writeTextFile,
pkgsCross,
}:
let
mkCrate =
buildRustCrate: args:
let
p = {
crateName = "nixtestcrate";
version = "0.1.0";
authors = [ "Test <test@example.com>" ];
}
// args;
in
buildRustCrate p;
mkHostCrate = mkCrate buildRustCrate;
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}";
inherit text;
};
mkBin =
name:
mkFile name ''
use std::env;
fn main() {
let name: String = env::args().nth(0).unwrap();
println!("executed {}", name);
}
'';
mkBinExtern =
name: extern:
mkFile name ''
extern crate ${extern};
fn main() {
assert_eq!(${extern}::test(), 23);
}
'';
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 = mkHostCrate (removeAttrs crateArgs [ "expectedTestOutput" ]);
hasTests = crateArgs.buildTests or false;
expectedTestOutputs = crateArgs.expectedTestOutputs or null;
binaries = map (v: lib.escapeShellArg v.name) (crateArgs.crateBin or [ ]);
isLib = crateArgs ? libName || crateArgs ? libPath;
crateName = crateArgs.crateName or "nixtestcrate";
libName = crateArgs.libName or crateName;
libTestBinary =
if !isLib then
null
else
mkHostCrate {
crateName = "run-test-${crateName}";
dependencies = [ crate ];
src = mkBinExtern "src/main.rs" libName;
};
in
assert expectedTestOutputs != null -> hasTests;
assert hasTests -> expectedTestOutputs != null;
runCommand "run-buildRustCrate-${crateName}-test"
{
nativeBuildInputs = [ crate ];
}
(
if !hasTests then
''
${lib.concatMapStringsSep "\n" (
binary:
# Can't actually run the binary when cross-compiling
(lib.optionalString (stdenv.hostPlatform != stdenv.buildPlatform) "type ") + binary
) binaries}
${lib.optionalString isLib ''
test -e ${crate}/lib/*.rlib || exit 1
${lib.optionalString (stdenv.hostPlatform != stdenv.buildPlatform) "test -x "} \
${libTestBinary}/bin/run-test-${crateName}
''}
touch $out
''
else if stdenv.hostPlatform == stdenv.buildPlatform then
''
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}
''
else
''
for file in ${crate}/tests/*; do
test -x "$file"
done
touch "$out"
''
);
/*
Returns a derivation that asserts that the crate specified by `crateArgs`
has the specified files as output.
`name` is used as part of the derivation name that performs the checking.
`mkCrate` can be used to override the `mkCrate` call/implementation to use to
override the `buildRustCrate`, useful for cross compilation. Uses `mkHostCrate` by default.
`crateArgs` is passed to `mkCrate` to build the crate with `buildRustCrate`
`expectedFiles` contains a list of expected file paths in the output. E.g.
`[ "./bin/my_binary" ]`.
`output` specifies the name of the output to use. By default, the default
output is used but e.g. `output = "lib";` will cause the lib output
to be checked instead. You do not need to specify any directories.
*/
assertOutputs =
{
name,
mkCrate ? mkHostCrate,
crateArgs,
expectedFiles,
output ? null,
}:
assert (builtins.isString name);
assert (builtins.isAttrs crateArgs);
assert (builtins.isList expectedFiles);
let
crate = mkCrate (removeAttrs crateArgs [ "expectedTestOutput" ]);
crateOutput = if output == null then crate else crate."${output}";
expectedFilesFile = writeTextFile {
name = "expected-files-${name}";
text =
let
sorted = builtins.sort (a: b: a < b) expectedFiles;
concatenated = builtins.concatStringsSep "\n" sorted;
in
"${concatenated}\n";
};
in
runCommand "assert-outputs-${name}"
{
}
(
''
local actualFiles=$(mktemp)
cd "${crateOutput}"
find . -type f \
| sort \
''
# sed out the hash because it differs per platform
+ ''
| sed 's/-${crate.metadata}//g' \
> "$actualFiles"
diff -q ${expectedFilesFile} "$actualFiles" > /dev/null || {
echo -e "\033[0;1;31mERROR: Difference in expected output files in ${crateOutput} \033[0m" >&2
echo === Got:
sed -e 's/^/ /' $actualFiles
echo === Expected:
sed -e 's/^/ /' ${expectedFilesFile}
echo === Diff:
diff -u ${expectedFilesFile} $actualFiles |\
tail -n +3 |\
sed -e 's/^/ /'
exit 1
}
touch $out
''
);
in
rec {
tests =
let
cases = rec {
libPath = {
libPath = "src/my_lib.rs";
src = mkLib "src/my_lib.rs";
};
srcLib = {
src = mkLib "src/lib.rs";
};
# 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.
# This might be a regression or deprecated thing they finally removed…
# customLibName = { libName = "test_lib"; src = mkLib "src/test_lib.rs"; };
# rustLibTestsCustomLibName = {
# libName = "test_lib";
# src = mkTestFile "src/test_lib.rs" "foo";
# buildTests = true;
# expectedTestOutputs = [ "test foo ... ok" ];
# };
customLibNameAndLibPath = {
libName = "test_lib";
libPath = "src/best-lib.rs";
src = mkLib "src/best-lib.rs";
};
crateBinWithPath = {
crateBin = [
{
name = "test_binary1";
path = "src/foobar.rs";
}
];
src = mkBin "src/foobar.rs";
};
crateBinNoPath1 = {
crateBin = [ { name = "my-binary2"; } ];
src = mkBin "src/my_binary2.rs";
};
crateBinNoPath2 = {
crateBin = [
{ name = "my-binary3"; }
{ name = "my-binary4"; }
];
src = symlinkJoin {
name = "buildRustCrateMultipleBinariesCase";
paths = [
(mkBin "src/bin/my_binary3.rs")
(mkBin "src/bin/my_binary4.rs")
];
};
};
crateBinNoPath3 = {
crateBin = [ { name = "my-binary5"; } ];
src = mkBin "src/bin/main.rs";
};
crateBinNoPath4 = {
crateBin = [ { name = "my-binary6"; } ];
src = mkBin "src/main.rs";
};
crateBinRename1 = {
crateBin = [ { name = "my-binary-rename1"; } ];
src = mkBinExtern "src/main.rs" "foo_renamed";
dependencies = [
(mkHostCrate {
crateName = "foo";
src = mkLib "src/lib.rs";
})
];
crateRenames = {
"foo" = "foo_renamed";
};
};
crateBinRename2 = {
crateBin = [ { name = "my-binary-rename2"; } ];
src = mkBinExtern "src/main.rs" "foo_renamed";
dependencies = [
(mkHostCrate {
crateName = "foo";
libName = "foolib";
src = mkLib "src/lib.rs";
})
];
crateRenames = {
"foo" = "foo_renamed";
};
};
crateBinRenameMultiVersion =
let
crateWithVersion =
version:
mkHostCrate {
crateName = "my_lib";
inherit version;
src = mkFile "src/lib.rs" ''
pub const version: &str = "${version}";
'';
};
depCrate01 = crateWithVersion "0.1.2";
depCrate02 = crateWithVersion "0.2.1";
in
{
crateName = "my_bin";
src = symlinkJoin {
name = "my_bin_src";
paths = [
(mkFile "src/main.rs" ''
#[test]
fn my_lib_01() { assert_eq!(lib01::version, "0.1.2"); }
#[test]
fn my_lib_02() { assert_eq!(lib02::version, "0.2.1"); }
fn main() { }
'')
];
};
dependencies = [
depCrate01
depCrate02
];
crateRenames = {
"my_lib" = [
{
version = "0.1.2";
rename = "lib01";
}
{
version = "0.2.1";
rename = "lib02";
}
];
};
buildTests = true;
expectedTestOutputs = [
"test my_lib_01 ... ok"
"test my_lib_02 ... ok"
];
};
rustLibTestsDefault = {
src = mkTestFile "src/lib.rs" "baz";
buildTests = true;
expectedTestOutputs = [ "test baz ... 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"
];
};
linkAgainstRlibCrate = {
crateName = "foo";
src = mkFile "src/main.rs" ''
extern crate somerlib;
fn main() {}
'';
dependencies = [
(mkHostCrate {
crateName = "somerlib";
type = [ "rlib" ];
src = mkLib "src/lib.rs";
})
];
};
buildScriptDeps =
let
depCrate =
buildRustCrate: boolVal:
mkCrate buildRustCrate {
crateName = "bar";
src = mkFile "src/lib.rs" ''
pub const baz: bool = ${boolVal};
'';
};
in
{
crateName = "foo";
src = symlinkJoin {
name = "build-script-and-main";
paths = [
(mkFile "src/main.rs" ''
extern crate bar;
#[cfg(test)]
#[test]
fn baz_false() { assert!(!bar::baz); }
fn main() { }
'')
(mkFile "build.rs" ''
extern crate bar;
fn main() { assert!(bar::baz); }
'')
];
};
buildDependencies = [ (depCrate buildPackages.buildRustCrate "true") ];
dependencies = [ (depCrate buildRustCrate "false") ];
buildTests = true;
expectedTestOutputs = [ "test baz_false ... ok" ];
};
buildScriptFeatureEnv = {
crateName = "build-script-feature-env";
features = [
"some-feature"
"crate/another_feature"
];
src = symlinkJoin {
name = "build-script-feature-env";
paths = [
(mkFile "src/main.rs" ''
#[cfg(test)]
#[test]
fn feature_not_visible() {
assert!(std::env::var("CARGO_FEATURE_SOME_FEATURE").is_err());
assert!(option_env!("CARGO_FEATURE_SOME_FEATURE").is_none());
}
fn main() {}
'')
(mkFile "build.rs" ''
fn main() {
assert!(std::env::var("CARGO_FEATURE_SOME_FEATURE").is_ok());
assert!(option_env!("CARGO_FEATURE_SOME_FEATURE").is_none());
}
'')
];
};
buildTests = true;
expectedTestOutputs = [ "test feature_not_visible ... ok" ];
};
# Regression test for https://github.com/NixOS/nixpkgs/pull/88054
# Build script output should be rewritten as valid env vars.
buildScriptIncludeDirDeps =
let
depCrate = mkHostCrate {
crateName = "bar";
src = symlinkJoin {
name = "build-script-and-include-dir-bar";
paths = [
(mkFile "src/lib.rs" ''
fn main() { }
'')
(mkFile "build.rs" ''
use std::path::PathBuf;
fn main() { println!("cargo:include-dir={}/src", std::env::current_dir().unwrap_or(PathBuf::from(".")).to_str().unwrap()); }
'')
];
};
};
in
{
crateName = "foo";
src = symlinkJoin {
name = "build-script-and-include-dir-foo";
paths = [
(mkFile "src/main.rs" ''
fn main() { }
'')
(mkFile "build.rs" ''
fn main() { assert!(std::env::var_os("DEP_BAR_INCLUDE_DIR").is_some()); }
'')
];
};
buildDependencies = [ depCrate ];
dependencies = [ depCrate ];
};
# Support new invocation prefix for build scripts `cargo::`
# https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script
buildScriptInvocationPrefix =
let
depCrate =
buildRustCrate:
mkCrate buildRustCrate {
crateName = "bar";
src = mkFile "build.rs" ''
fn main() {
// Old invocation prefix
// We likely won't see be mixing these syntaxes in the same build script in the wild.
println!("cargo:key_old=value_old");
// New invocation prefix
println!("cargo::metadata=key=value");
println!("cargo::metadata=key_complex=complex(value)");
}
'';
};
in
{
crateName = "foo";
src = symlinkJoin {
name = "build-script-and-main-invocation-prefix";
paths = [
(mkFile "src/main.rs" ''
const BUILDFOO: &'static str = env!("BUILDFOO");
#[test]
fn build_foo_check() { assert!(BUILDFOO == "yes(check)"); }
fn main() { }
'')
(mkFile "build.rs" ''
use std::env;
fn main() {
assert!(env::var_os("DEP_BAR_KEY_OLD").expect("metadata key 'key_old' not set in dependency") == "value_old");
assert!(env::var_os("DEP_BAR_KEY").expect("metadata key 'key' not set in dependency") == "value");
assert!(env::var_os("DEP_BAR_KEY_COMPLEX").expect("metadata key 'key_complex' not set in dependency") == "complex(value)");
println!("cargo::rustc-env=BUILDFOO=yes(check)");
}
'')
];
};
buildDependencies = [ (depCrate buildPackages.buildRustCrate) ];
dependencies = [ (depCrate buildRustCrate) ];
buildTests = true;
expectedTestOutputs = [ "test build_foo_check ... ok" ];
};
# Regression test for https://github.com/NixOS/nixpkgs/issues/74071
# Whenevever a build.rs file is generating files those should not be overlaid onto the actual source dir
buildRsOutDirOverlay = {
src = symlinkJoin {
name = "buildrs-out-dir-overlay";
paths = [
(mkLib "src/lib.rs")
(mkFile "build.rs" ''
use std::env;
use std::ffi::OsString;
use std::fs;
use std::path::Path;
fn main() {
let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR not set");
let out_file = Path::new(&out_dir).join("lib.rs");
fs::write(out_file, "invalid rust code!").expect("failed to write lib.rs");
}
'')
];
};
};
# Regression test for https://github.com/NixOS/nixpkgs/pull/83379
# link flag order should be preserved
linkOrder = {
src = symlinkJoin {
name = "buildrs-out-dir-overlay";
paths = [
(mkFile "build.rs" ''
fn main() {
// in the other order, linkage will fail
println!("cargo:rustc-link-lib=b");
println!("cargo:rustc-link-lib=a");
}
'')
(mkFile "src/main.rs" ''
extern "C" {
fn hello_world();
}
fn main() {
unsafe {
hello_world();
}
}
'')
];
};
buildInputs =
let
compile =
name: text:
let
src = writeTextFile {
name = "${name}-src.c";
inherit text;
};
in
runCommandCC name { } ''
mkdir -p $out/lib
# Note: On darwin (which defaults to clang) we have to add
# `-undefined dynamic_lookup` as otherwise the compilation fails.
$CC -shared \
${lib.optionalString stdenv.hostPlatform.isDarwin "-undefined dynamic_lookup"} \
-o $out/lib/${name}${stdenv.hostPlatform.extensions.library} ${src}
'';
b = compile "libb" ''
#include <stdio.h>
void hello();
void hello_world() {
hello();
printf(" world!\n");
}
'';
a = compile "liba" ''
#include <stdio.h>
void hello() {
printf("hello");
}
'';
in
[
a
b
];
};
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 {
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 = removeAttrs rustCargoTomlInSubDir [ "workspace_member" ];
in
withoutCargoTomlSearch
// {
expectedTestOutputs = [
"test ignore_main ... ok"
];
};
procMacroInPrelude = {
procMacro = true;
edition = "2018";
src = symlinkJoin {
name = "proc-macro-in-prelude";
paths = [
(mkFile "src/lib.rs" ''
use proc_macro::TokenTree;
'')
];
};
};
};
brotliCrates = (callPackage ./brotli-crates.nix { });
rcgenCrates = callPackage ./rcgen-crates.nix {
# Suppress deprecation warning
buildRustCrate = null;
};
tests = lib.mapAttrs (
key: value: mkTest (value // lib.optionalAttrs (!value ? crateName) { crateName = key; })
) cases;
in
tests
// {
crateBinWithPathOutputs = assertOutputs {
name = "crateBinWithPath";
crateArgs = {
crateBin = [
{
name = "test_binary1";
path = "src/foobar.rs";
}
];
src = mkBin "src/foobar.rs";
};
expectedFiles = [
"./bin/test_binary1"
];
};
crateBinWithPathOutputsDebug = assertOutputs {
name = "crateBinWithPath";
crateArgs = {
release = false;
crateBin = [
{
name = "test_binary1";
path = "src/foobar.rs";
}
];
src = mkBin "src/foobar.rs";
};
expectedFiles = [
"./bin/test_binary1"
]
++ lib.optionals stdenv.hostPlatform.isDarwin [
# On Darwin, the debug symbols are in a separate directory.
"./bin/test_binary1.dSYM/Contents/Info.plist"
"./bin/test_binary1.dSYM/Contents/Resources/DWARF/test_binary1"
];
};
crateBinNoPath1Outputs = assertOutputs {
name = "crateBinNoPath1";
crateArgs = {
crateBin = [ { name = "my-binary2"; } ];
src = mkBin "src/my_binary2.rs";
};
expectedFiles = [
"./bin/my-binary2"
];
};
crateLibOutputs = assertOutputs {
name = "crateLib";
output = "lib";
crateArgs = {
libName = "test_lib";
type = [ "rlib" ];
libPath = "src/lib.rs";
src = mkLib "src/lib.rs";
};
expectedFiles = [
"./nix-support/propagated-build-inputs"
"./lib/libtest_lib.rlib"
"./lib/link"
];
};
crateLibOutputsDebug = assertOutputs {
name = "crateLib";
output = "lib";
crateArgs = {
release = false;
libName = "test_lib";
type = [ "rlib" ];
libPath = "src/lib.rs";
src = mkLib "src/lib.rs";
};
expectedFiles = [
"./nix-support/propagated-build-inputs"
"./lib/libtest_lib.rlib"
"./lib/link"
];
};
crateLibOutputsWasm32 = assertOutputs {
name = "wasm32-crate-lib";
output = "lib";
mkCrate = mkCrate pkgsCross.wasm32-unknown-none.buildRustCrate;
crateArgs = {
libName = "test_lib";
type = [ "cdylib" ];
libPath = "src/lib.rs";
src = mkLib "src/lib.rs";
};
expectedFiles = [
"./nix-support/propagated-build-inputs"
"./lib/test_lib.wasm"
"./lib/link"
];
};
crateWasm32BinHyphens = assertOutputs {
name = "wasm32-crate-bin-hyphens";
mkCrate = mkCrate pkgsCross.wasm32-unknown-none.buildRustCrate;
crateArgs = {
crateName = "wasm32-crate-bin-hyphens";
crateBin = [ { name = "wasm32-crate-bin-hyphens"; } ];
src = mkBin "src/main.rs";
};
expectedFiles = [
"./bin/wasm32-crate-bin-hyphens.wasm"
];
};
brotliTest =
let
pkg = brotliCrates.brotli_2_5_0 { };
in
runCommand "run-brotli-test-cmd"
{
nativeBuildInputs = [ pkg ];
}
(
if stdenv.hostPlatform == stdenv.buildPlatform then
''
${pkg}/bin/brotli -c ${pkg}/bin/brotli > /dev/null && touch $out
''
else
''
test -x '${pkg}/bin/brotli' && touch $out
''
);
allocNoStdLibTest =
let
pkg = brotliCrates.alloc_no_stdlib_1_3_0 { };
in
runCommand "run-alloc-no-stdlib-test-cmd"
{
nativeBuildInputs = [ pkg ];
}
''
test -e ${pkg}/bin/example && touch $out
'';
brotliDecompressorTest =
let
pkg = brotliCrates.brotli_decompressor_1_3_1 { };
in
runCommand "run-brotli-decompressor-test-cmd"
{
nativeBuildInputs = [ pkg ];
}
''
test -e ${pkg}/bin/brotli-decompressor && touch $out
'';
rcgenTest =
let
pkg = rcgenCrates.rootCrate.build;
in
runCommand "run-rcgen-test-cmd"
{
nativeBuildInputs = [ pkg ];
}
(
if stdenv.hostPlatform == stdenv.buildPlatform then
''
${pkg}/bin/rcgen && touch $out
''
else
''
test -x '${pkg}/bin/rcgen' && touch $out
''
);
};
test = releaseTools.aggregate {
name = "buildRustCrate-tests";
meta = {
description = "Test cases for buildRustCrate";
maintainers = [ ];
};
constituents = builtins.attrValues tests;
};
}

File diff suppressed because it is too large Load Diff