nixpkgs mirror (for testing) github.com/NixOS/nixpkgs
nix
at devShellTools-shell 321 lines 11 kB view raw
1{ 2 fetchgit, 3 fetchurl, 4 lib, 5 writers, 6 python3Packages, 7 runCommand, 8 cargo, 9 jq, 10}: 11 12{ 13 # Cargo lock file 14 lockFile ? null, 15 16 # Cargo lock file contents as string 17 lockFileContents ? null, 18 19 # Allow `builtins.fetchGit` to be used to not require hashes for git dependencies 20 allowBuiltinFetchGit ? false, 21 22 # Additional registries to pull sources from 23 # { "https://<registry index URL>" = "https://<registry download URL>"; } 24 # or if the registry is using the new sparse protocol 25 # { "sparse+https://<registry download URL>" = "https://<registry download URL>"; } 26 # where: 27 # - "index URL" is the "index" value of the configuration entry for that registry 28 # https://doc.rust-lang.org/cargo/reference/registries.html#using-an-alternate-registry 29 # - "download URL" is the "dl" value of its associated index configuration 30 # https://doc.rust-lang.org/cargo/reference/registry-index.html#index-configuration 31 extraRegistries ? { }, 32 33 # Hashes for git dependencies. 34 outputHashes ? { }, 35}@args: 36 37assert (lockFile == null) != (lockFileContents == null); 38 39let 40 # Parse a git source into different components. 41 parseGit = 42 src: 43 let 44 parts = builtins.match ''git\+([^?]+)(\?(rev|tag|branch)=(.*))?#(.*)'' src; 45 type = builtins.elemAt parts 2; # rev, tag or branch 46 value = builtins.elemAt parts 3; 47 in 48 if parts == null then 49 null 50 else 51 { 52 url = builtins.elemAt parts 0; 53 sha = builtins.elemAt parts 4; 54 } 55 // lib.optionalAttrs (type != null) { inherit type value; }; 56 57 # shadows args.lockFileContents 58 lockFileContents = if lockFile != null then builtins.readFile lockFile else args.lockFileContents; 59 60 parsedLockFile = builtins.fromTOML lockFileContents; 61 62 # lockfile v1 and v2 don't have the `version` key, so assume v2 63 # we can implement more fine-grained detection later, if needed 64 lockFileVersion = parsedLockFile.version or 2; 65 66 packages = parsedLockFile.package; 67 68 # There is no source attribute for the source package itself. But 69 # since we do not want to vendor the source package anyway, we can 70 # safely skip it. 71 depPackages = builtins.filter (p: p ? "source") packages; 72 73 # Create dependent crates from packages. 74 # 75 # Force evaluation of the git SHA -> hash mapping, so that an error is 76 # thrown if there are stale hashes. We cannot rely on gitShaOutputHash 77 # being evaluated otherwise, since there could be no git dependencies. 78 depCrates = builtins.deepSeq gitShaOutputHash (builtins.map mkCrate depPackages); 79 80 # Map package name + version to git commit SHA for packages with a git source. 81 namesGitShas = builtins.listToAttrs ( 82 builtins.map nameGitSha (builtins.filter (pkg: lib.hasPrefix "git+" pkg.source) depPackages) 83 ); 84 85 nameGitSha = 86 pkg: 87 let 88 gitParts = parseGit pkg.source; 89 in 90 { 91 name = "${pkg.name}-${pkg.version}"; 92 value = gitParts.sha; 93 }; 94 95 # Convert the attrset provided through the `outputHashes` argument to a 96 # a mapping from git commit SHA -> output hash. 97 # 98 # There may be multiple different packages with different names 99 # originating from the same git repository (typically a Cargo 100 # workspace). By using the git commit SHA as a universal identifier, 101 # the user does not have to specify the output hash for every package 102 # individually. 103 gitShaOutputHash = lib.mapAttrs' ( 104 nameVer: hash: 105 let 106 unusedHash = throw "A hash was specified for ${nameVer}, but there is no corresponding git dependency."; 107 rev = namesGitShas.${nameVer} or unusedHash; 108 in 109 { 110 name = rev; 111 value = hash; 112 } 113 ) outputHashes; 114 115 # We can't use the existing fetchCrate function, since it uses a 116 # recursive hash of the unpacked crate. 117 fetchCrate = 118 pkg: downloadUrl: 119 let 120 checksum = 121 pkg.checksum or parsedLockFile.metadata."checksum ${pkg.name} ${pkg.version} (${pkg.source})"; 122 in 123 assert lib.assertMsg (checksum != null) '' 124 Package ${pkg.name} does not have a checksum. 125 ''; 126 fetchurl { 127 name = "crate-${pkg.name}-${pkg.version}.tar.gz"; 128 url = "${downloadUrl}/${pkg.name}/${pkg.version}/download"; 129 sha256 = checksum; 130 }; 131 132 registries = { 133 "https://github.com/rust-lang/crates.io-index" = "https://crates.io/api/v1/crates"; 134 } 135 // extraRegistries; 136 137 # Replaces values inherited by workspace members. 138 replaceWorkspaceValues = writers.writePython3 "replace-workspace-values" { 139 libraries = with python3Packages; [ 140 tomli 141 tomli-w 142 ]; 143 flakeIgnore = [ 144 "E501" 145 "W503" 146 ]; 147 } (builtins.readFile ./replace-workspace-values.py); 148 149 # Fetch and unpack a crate. 150 mkCrate = 151 pkg: 152 let 153 gitParts = parseGit pkg.source; 154 registryIndexUrl = lib.removePrefix "registry+" pkg.source; 155 in 156 if 157 (lib.hasPrefix "registry+" pkg.source || lib.hasPrefix "sparse+" pkg.source) 158 && builtins.hasAttr registryIndexUrl registries 159 then 160 let 161 crateTarball = fetchCrate pkg registries.${registryIndexUrl}; 162 in 163 runCommand "${pkg.name}-${pkg.version}" { } '' 164 mkdir $out 165 tar xf "${crateTarball}" -C $out --strip-components=1 166 167 # Cargo is happy with largely empty metadata. 168 printf '{"files":{},"package":"${crateTarball.outputHash}"}' > "$out/.cargo-checksum.json" 169 '' 170 else if gitParts != null then 171 let 172 missingHash = throw '' 173 No hash was found while vendoring the git dependency ${pkg.name}-${pkg.version}. You can add 174 a hash through the `outputHashes` argument of `importCargoLock`: 175 176 outputHashes = { 177 "${pkg.name}-${pkg.version}" = "<hash>"; 178 }; 179 180 If you use `buildRustPackage`, you can add this attribute to the `cargoLock` 181 attribute set. 182 ''; 183 tree = 184 if gitShaOutputHash ? ${gitParts.sha} then 185 fetchgit { 186 inherit (gitParts) url; 187 rev = gitParts.sha; # The commit SHA is always available. 188 sha256 = gitShaOutputHash.${gitParts.sha}; 189 } 190 else if allowBuiltinFetchGit then 191 builtins.fetchGit { 192 inherit (gitParts) url; 193 rev = gitParts.sha; 194 allRefs = true; 195 submodules = true; 196 } 197 else 198 missingHash; 199 in 200 runCommand "${pkg.name}-${pkg.version}" { } '' 201 tree=${tree} 202 203 # If the target package is in a workspace, or if it's the top-level 204 # crate, we should find the crate path using `cargo metadata`. 205 # Some packages do not have a Cargo.toml at the top-level, 206 # but only in nested directories. 207 # Only check the top-level Cargo.toml, if it actually exists 208 if [[ -f $tree/Cargo.toml ]]; then 209 crateCargoTOML=$(${cargo}/bin/cargo metadata --format-version 1 --no-deps --manifest-path $tree/Cargo.toml | \ 210 ${jq}/bin/jq -r '.packages[] | select(.name == "${pkg.name}") | .manifest_path') 211 fi 212 213 # If the repository is not a workspace the package might be in a subdirectory. 214 if [[ -z $crateCargoTOML ]]; then 215 for manifest in $(find $tree -name "Cargo.toml"); do 216 echo Looking at $manifest 217 crateCargoTOML=$(${cargo}/bin/cargo metadata --format-version 1 --no-deps --manifest-path "$manifest" | ${jq}/bin/jq -r '.packages[] | select(.name == "${pkg.name}") | .manifest_path' || :) 218 if [[ ! -z $crateCargoTOML ]]; then 219 break 220 fi 221 done 222 223 if [[ -z $crateCargoTOML ]]; then 224 >&2 echo "Cannot find path for crate '${pkg.name}-${pkg.version}' in the tree in: $tree" 225 exit 1 226 fi 227 fi 228 229 echo Found crate ${pkg.name} at $crateCargoTOML 230 tree=$(dirname $crateCargoTOML) 231 232 cp -prvL "$tree" "$out" || echo "Warning: certain files couldn't be copied!" >&2 233 chmod u+w $out 234 235 if grep -q workspace "$out/Cargo.toml"; then 236 chmod u+w "$out/Cargo.toml" 237 ${replaceWorkspaceValues} "$out/Cargo.toml" "$(${cargo}/bin/cargo metadata --format-version 1 --no-deps --manifest-path $crateCargoTOML | ${jq}/bin/jq -r .workspace_root)/Cargo.toml" 238 fi 239 240 # Cargo is happy with empty metadata. 241 printf '{"files":{},"package":null}' > "$out/.cargo-checksum.json" 242 243 ${lib.optionalString (gitParts ? type) '' 244 gitPartsValue=${lib.escapeShellArg gitParts.value} 245 # starting with lockfile version v4 the git source url contains encoded query parameters 246 # our regex parser does not know how to unescape them to get the actual value, so we do it here 247 ${lib.optionalString (lockFileVersion >= 4) '' 248 gitPartsValue=$(${lib.getExe python3Packages.python} -c "import sys, urllib.parse; print(urllib.parse.unquote(sys.argv[1]))" "$gitPartsValue") 249 ''} 250 ''} 251 252 # Set up configuration for the vendor directory. 253 cat > $out/.cargo-config <<EOF 254 [source."${pkg.source}"] 255 git = "${gitParts.url}" 256 ${lib.optionalString (gitParts ? type) "${gitParts.type} = \"$gitPartsValue\""} 257 replace-with = "vendored-sources" 258 EOF 259 '' 260 else 261 throw "Cannot handle crate source: ${pkg.source}"; 262 263 vendorDir = 264 runCommand "cargo-vendor-dir" 265 ( 266 if lockFile == null then 267 { 268 inherit lockFileContents; 269 passAsFile = [ "lockFileContents" ]; 270 } 271 else 272 { 273 passthru = { 274 inherit lockFile; 275 }; 276 } 277 ) 278 '' 279 mkdir -p $out/.cargo 280 281 ${ 282 if lockFile != null then 283 "ln -s ${lockFile} $out/Cargo.lock" 284 else 285 "cp $lockFileContentsPath $out/Cargo.lock" 286 } 287 288 cat > $out/.cargo/config.toml <<EOF 289 [source.crates-io] 290 replace-with = "vendored-sources" 291 292 [source.vendored-sources] 293 directory = "cargo-vendor-dir" 294 EOF 295 296 declare -A keysSeen 297 298 for registry in ${toString (builtins.attrNames extraRegistries)}; do 299 cat >> $out/.cargo/config.toml <<EOF 300 301 [source."$registry"] 302 registry = "$registry" 303 replace-with = "vendored-sources" 304 EOF 305 done 306 307 for crate in ${toString depCrates}; do 308 # Link the crate directory, removing the output path hash from the destination. 309 ln -s "$crate" $out/$(basename "$crate" | cut -c 34-) 310 311 if [ -e "$crate/.cargo-config" ]; then 312 key=$(sed 's/\[source\."\(.*\)"\]/\1/; t; d' < "$crate/.cargo-config") 313 if [[ -z ''${keysSeen[$key]} ]]; then 314 keysSeen[$key]=1 315 cat "$crate/.cargo-config" >> $out/.cargo/config.toml 316 fi 317 fi 318 done 319 ''; 320in 321vendorDir