src:
{ #| What command to run during the build phase
cargoBuild
, #| What command to run during the test phase
cargoTest ? "cargo test --$CARGO_BUILD_PROFILE"
, #| What command to run during the optional doc phase
cargoDoc ? "cargo doc --offline"
#| Whether or not to forward build artifacts to $out
, copyBuildArtifacts ? false
, doCheck ? true
, doDoc ? true
#| Whether to remove references to source code from the generated cargo docs
# to reduce Nix closure size. By default cargo doc includes snippets like the
# following in the generated highlighted source code in files like: src/rand/lib.rs.html:
#
#
#
# The reference to /nix/store/...-crates-io/... causes a run-time dependency
# to the complete source code blowing up the Nix closure size for no good
# reason. If this argument is set to true (which is the default) the latter
# will be replaced by:
#
#
#
# Which drops the run-time dependency on the crates-io source thereby
# significantly reducing the Nix closure size.
, removeReferencesToSrcFromDocs ? true
, name
, version
, rustc
, cargo
, override ? null
, buildInputs ? []
, nativeBuildInputs ? []
, builtDependencies ? []
, cargolock ? null
, cargotoml ? null
, release ? true
, stdenv
, lib
, llvmPackages
, rsync
, jq
, darwin
, writeText
, symlinkJoin
, runCommand
, remarshal
, crateDependencies
, cratePaths
}:
with
{ builtinz =
builtins //
import ./builtins
{ inherit lib writeText remarshal runCommand ; };
};
with rec
{
drv = stdenv.mkDerivation (
{ inherit
src
doCheck
nativeBuildInputs
cratePaths
name
version;
cargoconfig = builtinz.toTOML
{ source =
{ crates-io = { replace-with = "nix-sources"; } ;
nix-sources =
{ directory = symlinkJoin
{ name = "crates-io";
paths = map (v: unpackCrate v.name v.version v.sha256)
crateDependencies;
};
};
};
};
outputs = [ "out" ] ++ lib.optional doDoc "doc";
preInstallPhases = lib.optional doDoc [ "docPhase" ];
CARGO_BUILD_PROFILE = if release then "release" else "debug";
# Otherwise specifying CMake as a dep breaks the build
dontUseCmakeConfigure = true;
buildInputs =
[ cargo
# needed for "dsymutil"
llvmPackages.stdenv.cc.bintools
# needed for "cc"
llvmPackages.stdenv.cc
# needed at various steps in the build
jq
rsync
] ++ (stdenv.lib.optionals stdenv.isDarwin
[ darwin.Security
darwin.apple_sdk.frameworks.CoreServices
darwin.cf-private
]) ++ buildInputs;
LIBCLANG_PATH="${llvmPackages.libclang.lib}/lib";
CXX="clang++";
RUSTC="${rustc}/bin/rustc";
configurePhase =
''
runHook preConfigure
if [ -n "$cargolock" ]
then
echo "Setting Cargo.lock"
if [ -f "Cargo.lock" ]
then
echo "WARNING: replacing existing Cargo.lock"
fi
echo "$cargolock" > Cargo.lock
fi
if [ -n "$cargotoml" ]
then
echo "Setting Cargo.toml"
if [ -f "Cargo.toml" ]
then
echo "WARNING: replacing existing Cargo.toml"
fi
echo "$cargotoml" > Cargo.toml
fi
mkdir -p target
cat ${builtinz.writeJSON "dependencies-json" builtDependencies} |\
jq -r '.[]' |\
while IFS= read -r dep
do
echo pre-installing dep $dep
rsync -rl \
--no-perms \
--no-owner \
--no-group \
--chmod=+w \
--executability $dep/target/ target
chmod +w -R target
done
export CARGO_HOME=''${CARGO_HOME:-$PWD/.cargo-home}
mkdir -p $CARGO_HOME
echo "$cargoconfig" > $CARGO_HOME/config
# TODO: figure out why "1" works whereas "0" doesn't
find . -type f -exec touch --date=@1 {} +
runHook postConfigure
'';
buildPhase =
''
runHook preBuild
echo "Running build command:"
echo " ${cargoBuild}"
${cargoBuild}
runHook postBuild
'';
checkPhase =
''
runHook preCheck
echo "Running test command:"
echo " ${cargoTest}"
${cargoTest}
runHook postCheck
'';
docPhase = lib.optionalString doDoc ''
runHook preDoc
echo "Running doc command:"
echo " ${cargoDoc}"
${cargoDoc}
${lib.optionalString removeReferencesToSrcFromDocs ''
# Remove references to the source derivation to reduce closure size
match=''
replacement=''
find target/doc -name "*\.rs\.html" -exec sed -i "s|$match|$replacement|" {} +
''}
runHook postDoc
'';
installPhase =
''
runHook preInstall
# cargo install defaults to "release", but it doesn't have a
# "--release" flag, only "--debug", so we can't just pass
# "--$CARGO_BUILD_PROFILE" like we do with "cargo build" and "cargo
# test"
install_arg=""
if [ "$CARGO_BUILD_PROFILE" == "debug" ]
then
install_arg="--debug"
fi
mkdir -p $out/bin
for p in $cratePaths; do
# XXX: we don't quote install_arg to avoid passing an empty arg
# to cargo
cargo install \
--path $p \
$install_arg \
--bins \
--root $out ||\
echo "WARNING: Member wasn't installed: $p"
done
mkdir -p $out
mkdir -p $out/lib
${lib.optionalString copyBuildArtifacts ''
cp -vr target/$CARGO_BUILD_PROFILE/deps/* $out/lib ||\
echo "WARNING: couldn't copy libs"
cp -r target $out
''}
${lib.optionalString doDoc ''
cp -r target/doc $doc
''}
runHook postInstall
'';
} //
lib.optionalAttrs (! isNull cargolock )
{ cargolock = builtinz.toTOML cargolock; } //
lib.optionalAttrs (! isNull cargotoml )
{ cargotoml = builtinz.toTOML cargotoml; }
)
;
# XXX: the actual crate format is not documented but in practice is a
# gzipped tar; we simply unpack it and introduce a ".cargo-checksum.json"
# file that cargo itself uses to double check the sha256
unpackCrate = name: version: sha256:
with
{ crate = builtins.fetchurl
{ url = "https://crates.io/api/v1/crates/${name}/${version}/download";
inherit sha256;
};
};
runCommand "unpack-${name}-${version}" {}
''
mkdir -p $out
tar -xvzf ${crate} -C $out
echo '{"package":"${sha256}","files":{}}' > $out/${name}-${version}/.cargo-checksum.json
'';
};
if isNull override then drv else drv.overrideAttrs override