rustPlatform.{fetchCargoVendor,importCargoLock}: support lockfile v4 escaping (#371795)

authored by Masum Reza and committed by GitHub 640ab88c ef46055a

+44 -10
+29 -8
pkgs/build-support/rust/fetch-cargo-vendor-util.py
··· 9 9 import tomllib 10 10 from pathlib import Path 11 11 from typing import Any, TypedDict, cast 12 + from urllib.parse import unquote 12 13 13 14 import requests 14 15 from requests.adapters import HTTPAdapter, Retry ··· 19 20 def load_toml(path: Path) -> dict[str, Any]: 20 21 with open(path, "rb") as f: 21 22 return tomllib.load(f) 23 + 24 + 25 + def get_lockfile_version(cargo_lock_toml: dict[str, Any]) -> int: 26 + # lockfile v1 and v2 don't have the `version` key, so assume v2 27 + version = cargo_lock_toml.get("version", 2) 28 + 29 + # TODO: add logic for differentiating between v1 and v2 30 + 31 + return version 22 32 23 33 24 34 def download_file_with_checksum(url: str, destination_path: Path) -> str: ··· 93 103 git_sha_rev: str 94 104 95 105 96 - def parse_git_source(source: str) -> GitSourceInfo: 106 + def parse_git_source(source: str, lockfile_version: int) -> GitSourceInfo: 97 107 match = GIT_SOURCE_REGEX.match(source) 98 108 if match is None: 99 109 raise Exception(f"Unable to process git source: {source}.") 100 - return cast(GitSourceInfo, match.groupdict(default=None)) 110 + 111 + source_info = cast(GitSourceInfo, match.groupdict(default=None)) 112 + 113 + # the source URL is URL-encoded in lockfile_version >=4 114 + # since we just used regex to parse it we have to manually decode the escaped branch/tag name 115 + if lockfile_version >= 4 and source_info["value"] is not None: 116 + source_info["value"] = unquote(source_info["value"]) 117 + 118 + return source_info 101 119 102 120 103 121 def create_vendor_staging(lockfile_path: Path, out_dir: Path) -> None: 104 - cargo_toml = load_toml(lockfile_path) 122 + cargo_lock_toml = load_toml(lockfile_path) 123 + lockfile_version = get_lockfile_version(cargo_lock_toml) 105 124 106 125 git_packages: list[dict[str, Any]] = [] 107 126 registry_packages: list[dict[str, Any]] = [] 108 127 109 - for pkg in cargo_toml["package"]: 128 + for pkg in cargo_lock_toml["package"]: 110 129 # ignore local dependenices 111 130 if "source" not in pkg.keys(): 112 131 eprint(f"Skipping local dependency: {pkg["name"]}") ··· 122 141 123 142 git_sha_rev_to_url: dict[str, str] = {} 124 143 for pkg in git_packages: 125 - source_info = parse_git_source(pkg["source"]) 144 + source_info = parse_git_source(pkg["source"], lockfile_version) 126 145 git_sha_rev_to_url[source_info["git_sha_rev"]] = source_info["url"] 127 146 128 147 out_dir.mkdir(exist_ok=True) ··· 207 226 out_dir.mkdir(exist_ok=True) 208 227 shutil.copy(lockfile_path, out_dir / "Cargo.lock") 209 228 210 - cargo_toml = load_toml(lockfile_path) 229 + cargo_lock_toml = load_toml(lockfile_path) 230 + lockfile_version = get_lockfile_version(cargo_lock_toml) 211 231 212 232 config_lines = [ 213 233 '[source.vendored-sources]', ··· 217 237 ] 218 238 219 239 seen_source_keys = set() 220 - for pkg in cargo_toml["package"]: 240 + for pkg in cargo_lock_toml["package"]: 221 241 222 242 # ignore local dependenices 223 243 if "source" not in pkg.keys(): ··· 230 250 231 251 if source.startswith("git+"): 232 252 233 - source_info = parse_git_source(pkg["source"]) 253 + source_info = parse_git_source(pkg["source"], lockfile_version) 254 + 234 255 git_sha_rev = source_info["git_sha_rev"] 235 256 git_tree = vendor_staging_dir / "git" / git_sha_rev 236 257
+15 -2
pkgs/build-support/rust/import-cargo-lock.nix
··· 49 49 50 50 parsedLockFile = builtins.fromTOML lockFileContents; 51 51 52 + # lockfile v1 and v2 don't have the `version` key, so assume v2 53 + # we can implement more fine-grained detection later, if needed 54 + lockFileVersion = parsedLockFile.version or 2; 55 + 52 56 packages = parsedLockFile.package; 53 57 54 58 # There is no source attribute for the source package itself. But ··· 202 206 # Cargo is happy with empty metadata. 203 207 printf '{"files":{},"package":null}' > "$out/.cargo-checksum.json" 204 208 209 + ${lib.optionalString (gitParts ? type) '' 210 + gitPartsValue=${lib.escapeShellArg gitParts.value} 211 + # starting with lockfile version v4 the git source url contains encoded query parameters 212 + # our regex parser does not know how to unescape them to get the actual value, so we do it here 213 + ${lib.optionalString (lockFileVersion >= 4) '' 214 + gitPartsValue=$(${lib.getExe python3Packages.python} -c "import sys, urllib.parse; print(urllib.parse.unquote(sys.argv[1]))" "$gitPartsValue") 215 + ''} 216 + ''} 217 + 205 218 # Set up configuration for the vendor directory. 206 219 cat > $out/.cargo-config <<EOF 207 - [source."${gitParts.url}${lib.optionalString (gitParts ? type) "?${gitParts.type}=${gitParts.value}"}"] 220 + [source."${pkg.source}"] 208 221 git = "${gitParts.url}" 209 - ${lib.optionalString (gitParts ? type) "${gitParts.type} = \"${gitParts.value}\""} 222 + ${lib.optionalString (gitParts ? type) "${gitParts.type} = \"$gitPartsValue\""} 210 223 replace-with = "vendored-sources" 211 224 EOF 212 225 ''