···99import tomllib
1010from pathlib import Path
1111from typing import Any, TypedDict, cast
1212+from urllib.parse import unquote
12131314import requests
1415from requests.adapters import HTTPAdapter, Retry
···1920def load_toml(path: Path) -> dict[str, Any]:
2021 with open(path, "rb") as f:
2122 return tomllib.load(f)
2323+2424+2525+def get_lockfile_version(cargo_lock_toml: dict[str, Any]) -> int:
2626+ # lockfile v1 and v2 don't have the `version` key, so assume v2
2727+ version = cargo_lock_toml.get("version", 2)
2828+2929+ # TODO: add logic for differentiating between v1 and v2
3030+3131+ return version
223223332434def download_file_with_checksum(url: str, destination_path: Path) -> str:
···93103 git_sha_rev: str
94104951059696-def parse_git_source(source: str) -> GitSourceInfo:
106106+def parse_git_source(source: str, lockfile_version: int) -> GitSourceInfo:
97107 match = GIT_SOURCE_REGEX.match(source)
98108 if match is None:
99109 raise Exception(f"Unable to process git source: {source}.")
100100- return cast(GitSourceInfo, match.groupdict(default=None))
110110+111111+ source_info = cast(GitSourceInfo, match.groupdict(default=None))
112112+113113+ # the source URL is URL-encoded in lockfile_version >=4
114114+ # since we just used regex to parse it we have to manually decode the escaped branch/tag name
115115+ if lockfile_version >= 4 and source_info["value"] is not None:
116116+ source_info["value"] = unquote(source_info["value"])
117117+118118+ return source_info
101119102120103121def create_vendor_staging(lockfile_path: Path, out_dir: Path) -> None:
104104- cargo_toml = load_toml(lockfile_path)
122122+ cargo_lock_toml = load_toml(lockfile_path)
123123+ lockfile_version = get_lockfile_version(cargo_lock_toml)
105124106125 git_packages: list[dict[str, Any]] = []
107126 registry_packages: list[dict[str, Any]] = []
108127109109- for pkg in cargo_toml["package"]:
128128+ for pkg in cargo_lock_toml["package"]:
110129 # ignore local dependenices
111130 if "source" not in pkg.keys():
112131 eprint(f"Skipping local dependency: {pkg["name"]}")
···122141123142 git_sha_rev_to_url: dict[str, str] = {}
124143 for pkg in git_packages:
125125- source_info = parse_git_source(pkg["source"])
144144+ source_info = parse_git_source(pkg["source"], lockfile_version)
126145 git_sha_rev_to_url[source_info["git_sha_rev"]] = source_info["url"]
127146128147 out_dir.mkdir(exist_ok=True)
···207226 out_dir.mkdir(exist_ok=True)
208227 shutil.copy(lockfile_path, out_dir / "Cargo.lock")
209228210210- cargo_toml = load_toml(lockfile_path)
229229+ cargo_lock_toml = load_toml(lockfile_path)
230230+ lockfile_version = get_lockfile_version(cargo_lock_toml)
211231212232 config_lines = [
213233 '[source.vendored-sources]',
···217237 ]
218238219239 seen_source_keys = set()
220220- for pkg in cargo_toml["package"]:
240240+ for pkg in cargo_lock_toml["package"]:
221241222242 # ignore local dependenices
223243 if "source" not in pkg.keys():
···230250231251 if source.startswith("git+"):
232252233233- source_info = parse_git_source(pkg["source"])
253253+ source_info = parse_git_source(pkg["source"], lockfile_version)
254254+234255 git_sha_rev = source_info["git_sha_rev"]
235256 git_tree = vendor_staging_dir / "git" / git_sha_rev
236257
+15-2
pkgs/build-support/rust/import-cargo-lock.nix
···49495050 parsedLockFile = builtins.fromTOML lockFileContents;
51515252+ # lockfile v1 and v2 don't have the `version` key, so assume v2
5353+ # we can implement more fine-grained detection later, if needed
5454+ lockFileVersion = parsedLockFile.version or 2;
5555+5256 packages = parsedLockFile.package;
53575458 # There is no source attribute for the source package itself. But
···202206 # Cargo is happy with empty metadata.
203207 printf '{"files":{},"package":null}' > "$out/.cargo-checksum.json"
204208209209+ ${lib.optionalString (gitParts ? type) ''
210210+ gitPartsValue=${lib.escapeShellArg gitParts.value}
211211+ # starting with lockfile version v4 the git source url contains encoded query parameters
212212+ # our regex parser does not know how to unescape them to get the actual value, so we do it here
213213+ ${lib.optionalString (lockFileVersion >= 4) ''
214214+ gitPartsValue=$(${lib.getExe python3Packages.python} -c "import sys, urllib.parse; print(urllib.parse.unquote(sys.argv[1]))" "$gitPartsValue")
215215+ ''}
216216+ ''}
217217+205218 # Set up configuration for the vendor directory.
206219 cat > $out/.cargo-config <<EOF
207207- [source."${gitParts.url}${lib.optionalString (gitParts ? type) "?${gitParts.type}=${gitParts.value}"}"]
220220+ [source."${pkg.source}"]
208221 git = "${gitParts.url}"
209209- ${lib.optionalString (gitParts ? type) "${gitParts.type} = \"${gitParts.value}\""}
222222+ ${lib.optionalString (gitParts ? type) "${gitParts.type} = \"$gitPartsValue\""}
210223 replace-with = "vendored-sources"
211224 EOF
212225 ''