pytrainer: unpin python 3.10
[NixPkgs.git] / pkgs / build-support / rust / import-cargo-lock.nix
blobe88931d0f383d30c43b15e751f506ef44f786b63
1 { fetchgit, fetchurl, lib, writers, python3Packages, runCommand, cargo, jq }:
4   # Cargo lock file
5   lockFile ? null
7   # Cargo lock file contents as string
8 , lockFileContents ? null
10   # Allow `builtins.fetchGit` to be used to not require hashes for git dependencies
11 , allowBuiltinFetchGit ? false
13   # Additional registries to pull sources from
14   #   { "https://<registry index URL>" = "https://<registry download URL>"; }
15   #   or if the registry is using the new sparse protocol
16   #   { "sparse+https://<registry download URL>" = "https://<registry download URL>"; }
17   # where:
18   # - "index URL" is the "index" value of the configuration entry for that registry
19   #   https://doc.rust-lang.org/cargo/reference/registries.html#using-an-alternate-registry
20   # - "download URL" is the "dl" value of its associated index configuration
21   #   https://doc.rust-lang.org/cargo/reference/registry-index.html#index-configuration
22 , extraRegistries ? {}
24   # Hashes for git dependencies.
25 , outputHashes ? {}
26 } @ args:
28 assert (lockFile == null) != (lockFileContents == null);
30 let
31   # Parse a git source into different components.
32   parseGit = src:
33     let
34       parts = builtins.match ''git\+([^?]+)(\?(rev|tag|branch)=(.*))?#(.*)'' src;
35       type = builtins.elemAt parts 2; # rev, tag or branch
36       value = builtins.elemAt parts 3;
37     in
38       if parts == null then null
39       else {
40         url = builtins.elemAt parts 0;
41         sha = builtins.elemAt parts 4;
42       } // lib.optionalAttrs (type != null) { inherit type value; };
44   # shadows args.lockFileContents
45   lockFileContents =
46     if lockFile != null
47     then builtins.readFile lockFile
48     else args.lockFileContents;
50   parsedLockFile = builtins.fromTOML lockFileContents;
52   packages = parsedLockFile.package;
54   # There is no source attribute for the source package itself. But
55   # since we do not want to vendor the source package anyway, we can
56   # safely skip it.
57   depPackages = builtins.filter (p: p ? "source") packages;
59   # Create dependent crates from packages.
60   #
61   # Force evaluation of the git SHA -> hash mapping, so that an error is
62   # thrown if there are stale hashes. We cannot rely on gitShaOutputHash
63   # being evaluated otherwise, since there could be no git dependencies.
64   depCrates = builtins.deepSeq gitShaOutputHash (builtins.map mkCrate depPackages);
66   # Map package name + version to git commit SHA for packages with a git source.
67   namesGitShas = builtins.listToAttrs (
68     builtins.map nameGitSha (builtins.filter (pkg: lib.hasPrefix "git+" pkg.source) depPackages)
69   );
71   nameGitSha = pkg: let gitParts = parseGit pkg.source; in {
72     name = "${pkg.name}-${pkg.version}";
73     value = gitParts.sha;
74   };
76   # Convert the attrset provided through the `outputHashes` argument to a
77   # a mapping from git commit SHA -> output hash.
78   #
79   # There may be multiple different packages with different names
80   # originating from the same git repository (typically a Cargo
81   # workspace). By using the git commit SHA as a universal identifier,
82   # the user does not have to specify the output hash for every package
83   # individually.
84   gitShaOutputHash = lib.mapAttrs' (nameVer: hash:
85     let
86       unusedHash = throw "A hash was specified for ${nameVer}, but there is no corresponding git dependency.";
87       rev = namesGitShas.${nameVer} or unusedHash; in {
88       name = rev;
89       value = hash;
90     }) outputHashes;
92   # We can't use the existing fetchCrate function, since it uses a
93   # recursive hash of the unpacked crate.
94   fetchCrate = pkg: downloadUrl:
95     let
96       checksum = pkg.checksum or parsedLockFile.metadata."checksum ${pkg.name} ${pkg.version} (${pkg.source})";
97     in
98     assert lib.assertMsg (checksum != null) ''
99       Package ${pkg.name} does not have a checksum.
100     '';
101     fetchurl {
102       name = "crate-${pkg.name}-${pkg.version}.tar.gz";
103       url = "${downloadUrl}/${pkg.name}/${pkg.version}/download";
104       sha256 = checksum;
105     };
107   registries = {
108     "https://github.com/rust-lang/crates.io-index" = "https://crates.io/api/v1/crates";
109   } // extraRegistries;
111   # Replaces values inherited by workspace members.
112   replaceWorkspaceValues = writers.writePython3 "replace-workspace-values"
113     { libraries = with python3Packages; [ tomli tomli-w ]; flakeIgnore = [ "E501" "W503" ]; }
114     (builtins.readFile ./replace-workspace-values.py);
116   # Fetch and unpack a crate.
117   mkCrate = pkg:
118     let
119       gitParts = parseGit pkg.source;
120       registryIndexUrl = lib.removePrefix "registry+" pkg.source;
121     in
122       if (lib.hasPrefix "registry+" pkg.source || lib.hasPrefix "sparse+" pkg.source)
123         && builtins.hasAttr registryIndexUrl registries then
124       let
125         crateTarball = fetchCrate pkg registries.${registryIndexUrl};
126       in runCommand "${pkg.name}-${pkg.version}" {} ''
127         mkdir $out
128         tar xf "${crateTarball}" -C $out --strip-components=1
130         # Cargo is happy with largely empty metadata.
131         printf '{"files":{},"package":"${crateTarball.outputHash}"}' > "$out/.cargo-checksum.json"
132       ''
133       else if gitParts != null then
134       let
135         missingHash = throw ''
136           No hash was found while vendoring the git dependency ${pkg.name}-${pkg.version}. You can add
137           a hash through the `outputHashes` argument of `importCargoLock`:
139           outputHashes = {
140             "${pkg.name}-${pkg.version}" = "<hash>";
141           };
143           If you use `buildRustPackage`, you can add this attribute to the `cargoLock`
144           attribute set.
145         '';
146         tree =
147           if gitShaOutputHash ? ${gitParts.sha} then
148             fetchgit {
149               inherit (gitParts) url;
150               rev = gitParts.sha; # The commit SHA is always available.
151               sha256 = gitShaOutputHash.${gitParts.sha};
152             }
153           else if allowBuiltinFetchGit then
154             builtins.fetchGit {
155               inherit (gitParts) url;
156               rev = gitParts.sha;
157               allRefs = true;
158               submodules = true;
159             }
160           else
161             missingHash;
162       in runCommand "${pkg.name}-${pkg.version}" {} ''
163         tree=${tree}
165         # If the target package is in a workspace, or if it's the top-level
166         # crate, we should find the crate path using `cargo metadata`.
167         # Some packages do not have a Cargo.toml at the top-level,
168         # but only in nested directories.
169         # Only check the top-level Cargo.toml, if it actually exists
170         if [[ -f $tree/Cargo.toml ]]; then
171           crateCargoTOML=$(${cargo}/bin/cargo metadata --format-version 1 --no-deps --manifest-path $tree/Cargo.toml | \
172           ${jq}/bin/jq -r '.packages[] | select(.name == "${pkg.name}") | .manifest_path')
173         fi
175         # If the repository is not a workspace the package might be in a subdirectory.
176         if [[ -z $crateCargoTOML ]]; then
177           for manifest in $(find $tree -name "Cargo.toml"); do
178             echo Looking at $manifest
179             crateCargoTOML=$(${cargo}/bin/cargo metadata --format-version 1 --no-deps --manifest-path "$manifest" | ${jq}/bin/jq -r '.packages[] | select(.name == "${pkg.name}") | .manifest_path' || :)
180             if [[ ! -z $crateCargoTOML ]]; then
181               break
182             fi
183           done
185           if [[ -z $crateCargoTOML ]]; then
186             >&2 echo "Cannot find path for crate '${pkg.name}-${pkg.version}' in the tree in: $tree"
187             exit 1
188           fi
189         fi
191         echo Found crate ${pkg.name} at $crateCargoTOML
192         tree=$(dirname $crateCargoTOML)
194         cp -prvL "$tree/" $out
195         chmod u+w $out
197         if grep -q workspace "$out/Cargo.toml"; then
198           chmod u+w "$out/Cargo.toml"
199           ${replaceWorkspaceValues} "$out/Cargo.toml" "$(${cargo}/bin/cargo metadata --format-version 1 --no-deps --manifest-path $crateCargoTOML | ${jq}/bin/jq -r .workspace_root)/Cargo.toml"
200         fi
202         # Cargo is happy with empty metadata.
203         printf '{"files":{},"package":null}' > "$out/.cargo-checksum.json"
205         # Set up configuration for the vendor directory.
206         cat > $out/.cargo-config <<EOF
207         [source."${gitParts.url}${lib.optionalString (gitParts ? type) "?${gitParts.type}=${gitParts.value}"}"]
208         git = "${gitParts.url}"
209         ${lib.optionalString (gitParts ? type) "${gitParts.type} = \"${gitParts.value}\""}
210         replace-with = "vendored-sources"
211         EOF
212       ''
213       else throw "Cannot handle crate source: ${pkg.source}";
215   vendorDir = runCommand "cargo-vendor-dir"
216     (if lockFile == null then {
217       inherit lockFileContents;
218       passAsFile = [ "lockFileContents" ];
219     } else {
220       passthru = {
221         inherit lockFile;
222       };
223     }) ''
224     mkdir -p $out/.cargo
226     ${
227       if lockFile != null
228       then "ln -s ${lockFile} $out/Cargo.lock"
229       else "cp $lockFileContentsPath $out/Cargo.lock"
230     }
232     cat > $out/.cargo/config.toml <<EOF
233 [source.crates-io]
234 replace-with = "vendored-sources"
236 [source.vendored-sources]
237 directory = "cargo-vendor-dir"
240     declare -A keysSeen
242     for registry in ${toString (builtins.attrNames extraRegistries)}; do
243       cat >> $out/.cargo/config.toml <<EOF
245 [source."$registry"]
246 registry = "$registry"
247 replace-with = "vendored-sources"
249     done
251     for crate in ${toString depCrates}; do
252       # Link the crate directory, removing the output path hash from the destination.
253       ln -s "$crate" $out/$(basename "$crate" | cut -c 34-)
255       if [ -e "$crate/.cargo-config" ]; then
256         key=$(sed 's/\[source\."\(.*\)"\]/\1/; t; d' < "$crate/.cargo-config")
257         if [[ -z ''${keysSeen[$key]} ]]; then
258           keysSeen[$key]=1
259           cat "$crate/.cargo-config" >> $out/.cargo/config.toml
260         fi
261       fi
262     done
263   '';
265   vendorDir