importCargoLock
pkgs.rustPackages.rustPlatform.importCargoLock
Functor
Docs pulled from | This Revision | about 1 hour ago
Contribute
Enhance the ecosystem with your expertise! Contribute to fill the gaps in documentation. Your input can make a difference.
Noogle detected
This is a Functor
Learn about functors
Implementation
The following is the current implementation of this function.
{
# Cargo lock file
lockFile ? null,
# Cargo lock file contents as string
lockFileContents ? null,
# Allow `fetchGit` to be used to not require hashes for git dependencies
allowBuiltinFetchGit ? false,
# Additional registries to pull sources from
# { "https://<registry index URL>" = "https://<registry download URL>"; }
# or if the registry is using the new sparse protocol
# { "sparse+https://<registry download URL>" = "https://<registry download URL>"; }
# where:
# - "index URL" is the "index" value of the configuration entry for that registry
# https://doc.rust-lang.org/cargo/reference/registries.html#using-an-alternate-registry
# - "download URL" is the "dl" value of its associated index configuration
# https://doc.rust-lang.org/cargo/reference/registry-index.html#index-configuration
extraRegistries ? { },
# Hashes for git dependencies.
outputHashes ? { },
}@args:
assert (lockFile == null) != (lockFileContents == null);
let
# Parse a git source into different components.
parseGit =
src:
let
parts = builtins.match ''git\+([^?]+)(\?(rev|tag|branch)=(.*))?#(.*)'' src;
type = builtins.elemAt parts 2; # rev, tag or branch
value = builtins.elemAt parts 3;
in
if parts == null then
null
else
{
url = builtins.elemAt parts 0;
sha = builtins.elemAt parts 4;
}
// lib.optionalAttrs (type != null) { inherit type value; };
# shadows args.lockFileContents
lockFileContents = if lockFile != null then builtins.readFile lockFile else args.lockFileContents;
parsedLockFile = fromTOML lockFileContents;
# lockfile v1 and v2 don't have the `version` key, so assume v2
# we can implement more fine-grained detection later, if needed
lockFileVersion = parsedLockFile.version or 2;
packages = parsedLockFile.package;
# There is no source attribute for the source package itself. But
# since we do not want to vendor the source package anyway, we can
# safely skip it.
depPackages = builtins.filter (p: p ? "source") packages;
# Create dependent crates from packages.
#
# Force evaluation of the git SHA -> hash mapping, so that an error is
# thrown if there are stale hashes. We cannot rely on gitShaOutputHash
# being evaluated otherwise, since there could be no git dependencies.
depCrates = builtins.deepSeq gitShaOutputHash (map mkCrate depPackages);
# Map package name + version to git commit SHA for packages with a git source.
namesGitShas = builtins.listToAttrs (
map nameGitSha (builtins.filter (pkg: lib.hasPrefix "git+" pkg.source) depPackages)
);
nameGitSha =
pkg:
let
gitParts = parseGit pkg.source;
in
{
name = "${pkg.name}-${pkg.version}";
value = gitParts.sha;
};
# Convert the attrset provided through the `outputHashes` argument to a
# a mapping from git commit SHA -> output hash.
#
# There may be multiple different packages with different names
# originating from the same git repository (typically a Cargo
# workspace). By using the git commit SHA as a universal identifier,
# the user does not have to specify the output hash for every package
# individually.
gitShaOutputHash = lib.mapAttrs' (
nameVer: hash:
let
unusedHash = throw "A hash was specified for ${nameVer}, but there is no corresponding git dependency.";
rev = namesGitShas.${nameVer} or unusedHash;
in
{
name = rev;
value = hash;
}
) outputHashes;
# We can't use the existing fetchCrate function, since it uses a
# recursive hash of the unpacked crate.
fetchCrate =
pkg: downloadUrl:
let
checksum =
pkg.checksum or parsedLockFile.metadata."checksum ${pkg.name} ${pkg.version} (${pkg.source})";
in
assert lib.assertMsg (checksum != null) ''
Package ${pkg.name} does not have a checksum.
'';
fetchurl {
name = "crate-${pkg.name}-${pkg.version}.tar.gz";
url = "${downloadUrl}/${pkg.name}/${pkg.version}/download";
sha256 = checksum;
};
registries = {
"https://github.com/rust-lang/crates.io-index" = "https://crates.io/api/v1/crates";
}
// extraRegistries;
# Replaces values inherited by workspace members.
replaceWorkspaceValues = writers.writePython3 "replace-workspace-values" {
libraries = with python3Packages; [
tomli
tomli-w
];
flakeIgnore = [
"E501"
"W503"
];
} (builtins.readFile ./replace-workspace-values.py);
# Fetch and unpack a crate.
mkCrate =
pkg:
let
gitParts = parseGit pkg.source;
registryIndexUrl = lib.removePrefix "registry+" pkg.source;
in
if
(lib.hasPrefix "registry+" pkg.source || lib.hasPrefix "sparse+" pkg.source)
&& builtins.hasAttr registryIndexUrl registries
then
let
crateTarball = fetchCrate pkg registries.${registryIndexUrl};
in
runCommand "${pkg.name}-${pkg.version}" { } ''
mkdir $out
tar xf "${crateTarball}" -C $out --strip-components=1
# Cargo is happy with largely empty metadata.
printf '{"files":{},"package":"${crateTarball.outputHash}"}' > "$out/.cargo-checksum.json"
''
else if gitParts != null then
let
missingHash = throw ''
No hash was found while vendoring the git dependency ${pkg.name}-${pkg.version}. You can add
a hash through the `outputHashes` argument of `importCargoLock`:
outputHashes = {
"${pkg.name}-${pkg.version}" = "<hash>";
};
If you use `buildRustPackage`, you can add this attribute to the `cargoLock`
attribute set.
'';
tree =
if gitShaOutputHash ? ${gitParts.sha} then
fetchgit {
inherit (gitParts) url;
rev = gitParts.sha; # The commit SHA is always available.
sha256 = gitShaOutputHash.${gitParts.sha};
}
else if allowBuiltinFetchGit then
fetchGit {
inherit (gitParts) url;
rev = gitParts.sha;
allRefs = true;
submodules = true;
}
else
missingHash;
in
runCommand "${pkg.name}-${pkg.version}" { } ''
tree=${tree}
# If the target package is in a workspace, or if it's the top-level
# crate, we should find the crate path using `cargo metadata`.
# Some packages do not have a Cargo.toml at the top-level,
# but only in nested directories.
# Only check the top-level Cargo.toml, if it actually exists
if [[ -f $tree/Cargo.toml ]]; then
crateCargoTOML=$(${cargo}/bin/cargo metadata --format-version 1 --no-deps --manifest-path $tree/Cargo.toml | \
${jq}/bin/jq -r '.packages[] | select(.name == "${pkg.name}") | .manifest_path')
fi
# If the repository is not a workspace the package might be in a subdirectory.
if [[ -z $crateCargoTOML ]]; then
for manifest in $(find $tree -name "Cargo.toml"); do
echo Looking at $manifest
crateCargoTOML=$(${cargo}/bin/cargo metadata --format-version 1 --no-deps --manifest-path "$manifest" | ${jq}/bin/jq -r '.packages[] | select(.name == "${pkg.name}") | .manifest_path' || :)
if [[ ! -z $crateCargoTOML ]]; then
break
fi
done
if [[ -z $crateCargoTOML ]]; then
>&2 echo "Cannot find path for crate '${pkg.name}-${pkg.version}' in the tree in: $tree"
exit 1
fi
fi
echo Found crate ${pkg.name} at $crateCargoTOML
tree=$(dirname $crateCargoTOML)
cp -prvL "$tree" "$out" || echo "Warning: certain files couldn't be copied!" >&2
chmod u+w $out
if grep -q workspace "$out/Cargo.toml"; then
chmod u+w "$out/Cargo.toml"
${replaceWorkspaceValues} "$out/Cargo.toml" "$(${cargo}/bin/cargo metadata --format-version 1 --no-deps --manifest-path $crateCargoTOML | ${jq}/bin/jq -r .workspace_root)/Cargo.toml"
fi
# Cargo is happy with empty metadata.
printf '{"files":{},"package":null}' > "$out/.cargo-checksum.json"
${lib.optionalString (gitParts ? type) ''
gitPartsValue=${lib.escapeShellArg gitParts.value}
# starting with lockfile version v4 the git source url contains encoded query parameters
# our regex parser does not know how to unescape them to get the actual value, so we do it here
${lib.optionalString (lockFileVersion >= 4) ''
gitPartsValue=$(${lib.getExe python3Packages.python} -c "import sys, urllib.parse; print(urllib.parse.unquote(sys.argv[1]))" "$gitPartsValue")
''}
''}
# Set up configuration for the vendor directory.
cat > $out/.cargo-config <<EOF
[source."${pkg.source}"]
git = "${gitParts.url}"
${lib.optionalString (gitParts ? type) "${gitParts.type} = \"$gitPartsValue\""}
replace-with = "vendored-sources"
EOF
''
else
throw "Cannot handle crate source: ${pkg.source}";
vendorDir =
runCommand "cargo-vendor-dir"
(
{
__structuredAttrs = true;
}
// (
if lockFile == null then
{
inherit lockFileContents;
}
else
{
passthru = {
inherit lockFile;
};
}
)
)
''
mkdir -p $out/.cargo
${
if lockFile == null then
''
printf "%s" "$lockFileContents" > "$out/Cargo.lock"
''
else
"ln -s ${lockFile} $out/Cargo.lock"
}
cat > $out/.cargo/config.toml <<EOF
[source.crates-io]
replace-with = "vendored-sources"
[source.vendored-sources]
directory = "cargo-vendor-dir"
EOF
declare -A keysSeen
for registry in ${toString (builtins.attrNames extraRegistries)}; do
cat >> $out/.cargo/config.toml <<EOF
[source."$registry"]
registry = "$registry"
replace-with = "vendored-sources"
EOF
done
for crate in ${toString depCrates}; do
# Link the crate directory, removing the output path hash from the destination.
ln -s "$crate" $out/$(basename "$crate" | cut -c 34-)
if [ -e "$crate/.cargo-config" ]; then
key=$(sed 's/\[source\."\(.*\)"\]/\1/; t; d' < "$crate/.cargo-config")
if [[ -z ''${keysSeen[$key]} ]]; then
keysSeen[$key]=1
cat "$crate/.cargo-config" >> $out/.cargo/config.toml
fi
fi
done
'';
in
vendorDir