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 import tomllib 10 from pathlib import Path 11 from typing import Any, TypedDict, cast 12 13 import requests 14 from requests.adapters import HTTPAdapter, Retry ··· 19 def load_toml(path: Path) -> dict[str, Any]: 20 with open(path, "rb") as f: 21 return tomllib.load(f) 22 23 24 def download_file_with_checksum(url: str, destination_path: Path) -> str: ··· 93 git_sha_rev: str 94 95 96 - def parse_git_source(source: str) -> GitSourceInfo: 97 match = GIT_SOURCE_REGEX.match(source) 98 if match is None: 99 raise Exception(f"Unable to process git source: {source}.") 100 - return cast(GitSourceInfo, match.groupdict(default=None)) 101 102 103 def create_vendor_staging(lockfile_path: Path, out_dir: Path) -> None: 104 - cargo_toml = load_toml(lockfile_path) 105 106 git_packages: list[dict[str, Any]] = [] 107 registry_packages: list[dict[str, Any]] = [] 108 109 - for pkg in cargo_toml["package"]: 110 # ignore local dependenices 111 if "source" not in pkg.keys(): 112 eprint(f"Skipping local dependency: {pkg["name"]}") ··· 122 123 git_sha_rev_to_url: dict[str, str] = {} 124 for pkg in git_packages: 125 - source_info = parse_git_source(pkg["source"]) 126 git_sha_rev_to_url[source_info["git_sha_rev"]] = source_info["url"] 127 128 out_dir.mkdir(exist_ok=True) ··· 207 out_dir.mkdir(exist_ok=True) 208 shutil.copy(lockfile_path, out_dir / "Cargo.lock") 209 210 - cargo_toml = load_toml(lockfile_path) 211 212 config_lines = [ 213 '[source.vendored-sources]', ··· 217 ] 218 219 seen_source_keys = set() 220 - for pkg in cargo_toml["package"]: 221 222 # ignore local dependenices 223 if "source" not in pkg.keys(): ··· 230 231 if source.startswith("git+"): 232 233 - source_info = parse_git_source(pkg["source"]) 234 git_sha_rev = source_info["git_sha_rev"] 235 git_tree = vendor_staging_dir / "git" / git_sha_rev 236
··· 9 import tomllib 10 from pathlib import Path 11 from typing import Any, TypedDict, cast 12 + from urllib.parse import unquote 13 14 import requests 15 from requests.adapters import HTTPAdapter, Retry ··· 20 def load_toml(path: Path) -> dict[str, Any]: 21 with open(path, "rb") as f: 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 32 33 34 def download_file_with_checksum(url: str, destination_path: Path) -> str: ··· 103 git_sha_rev: str 104 105 106 + def parse_git_source(source: str, lockfile_version: int) -> GitSourceInfo: 107 match = GIT_SOURCE_REGEX.match(source) 108 if match is None: 109 raise Exception(f"Unable to process git source: {source}.") 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 119 120 121 def create_vendor_staging(lockfile_path: Path, out_dir: Path) -> None: 122 + cargo_lock_toml = load_toml(lockfile_path) 123 + lockfile_version = get_lockfile_version(cargo_lock_toml) 124 125 git_packages: list[dict[str, Any]] = [] 126 registry_packages: list[dict[str, Any]] = [] 127 128 + for pkg in cargo_lock_toml["package"]: 129 # ignore local dependenices 130 if "source" not in pkg.keys(): 131 eprint(f"Skipping local dependency: {pkg["name"]}") ··· 141 142 git_sha_rev_to_url: dict[str, str] = {} 143 for pkg in git_packages: 144 + source_info = parse_git_source(pkg["source"], lockfile_version) 145 git_sha_rev_to_url[source_info["git_sha_rev"]] = source_info["url"] 146 147 out_dir.mkdir(exist_ok=True) ··· 226 out_dir.mkdir(exist_ok=True) 227 shutil.copy(lockfile_path, out_dir / "Cargo.lock") 228 229 + cargo_lock_toml = load_toml(lockfile_path) 230 + lockfile_version = get_lockfile_version(cargo_lock_toml) 231 232 config_lines = [ 233 '[source.vendored-sources]', ··· 237 ] 238 239 seen_source_keys = set() 240 + for pkg in cargo_lock_toml["package"]: 241 242 # ignore local dependenices 243 if "source" not in pkg.keys(): ··· 250 251 if source.startswith("git+"): 252 253 + source_info = parse_git_source(pkg["source"], lockfile_version) 254 + 255 git_sha_rev = source_info["git_sha_rev"] 256 git_tree = vendor_staging_dir / "git" / git_sha_rev 257
+15 -2
pkgs/build-support/rust/import-cargo-lock.nix
··· 49 50 parsedLockFile = builtins.fromTOML lockFileContents; 51 52 packages = parsedLockFile.package; 53 54 # There is no source attribute for the source package itself. But ··· 202 # Cargo is happy with empty metadata. 203 printf '{"files":{},"package":null}' > "$out/.cargo-checksum.json" 204 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 ''
··· 49 50 parsedLockFile = builtins.fromTOML lockFileContents; 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 + 56 packages = parsedLockFile.package; 57 58 # There is no source attribute for the source package itself. But ··· 206 # Cargo is happy with empty metadata. 207 printf '{"files":{},"package":null}' > "$out/.cargo-checksum.json" 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 + 218 # Set up configuration for the vendor directory. 219 cat > $out/.cargo-config <<EOF 220 + [source."${pkg.source}"] 221 git = "${gitParts.url}" 222 + ${lib.optionalString (gitParts ? type) "${gitParts.type} = \"$gitPartsValue\""} 223 replace-with = "vendored-sources" 224 EOF 225 ''