3
# SPDX-License-Identifier: GPL-2.0-or-later
4
5
"""
6
-get-wraps-from-cargo-registry.py - Update Meson subprojects from a global registry
6
+get-wraps-from-cargo-registry.py - Update Meson subprojects from a Cargo
7
+registry or from the versions pinned in Cargo.lock.
8
"""
9
10
# Copyright (C) 2025 Red Hat, Inc.
19
import shutil
20
import subprocess
21
import sys
22
+import tomllib
23
24
25
def get_name_and_semver(namever: str) -> tuple[str, str]:
77
return True
78
79
80
+class CargoLock(CrateSource):
81
+ """Locate crates by the versions pinned in a Cargo.lock file."""
82
+
83
+ origin = "crates.io"
84
+
85
+ def __init__(self, path: str):
86
+ with open(path, "rb") as f:
87
+ data = tomllib.load(f)
88
+
89
+ self.versions: dict[str, list[str]] = {}
90
+ self.checksums: dict[str, str] = {}
91
+ for pkg in data.get("package", []):
92
+ # workspace members have neither a checksum nor a crates.io tarball
93
+ if "checksum" not in pkg:
94
+ continue
95
+ name, version = pkg["name"], pkg["version"]
96
+ self.versions.setdefault(name, []).append(version)
97
+ self.checksums[f"{name}-{version}"] = pkg["checksum"]
98
+
99
+ def find(self, namever: str) -> str | None:
100
+ """Find pinned crate matching name and semver prefix"""
101
+ name, semver = get_name_and_semver(namever)
102
+ versions = sorted(self.versions.get(name, []))
103
+
104
+ # exact version match
105
+ if semver in versions:
106
+ return f"{name}-{semver}"
107
+
108
+ # semver match
109
+ matches = [v for v in versions if v.startswith(f"{semver}.")]
110
+ return f"{name}-{matches[0]}" if matches else None
111
+
112
+ def rewrite_source(self, section: configparser.SectionProxy, orig_namever: str, new_namever: str) -> bool:
113
+ # rewrite the download keys to fetch the pinned version from crates.io.
114
+ if orig_namever == new_namever:
115
+ return False
116
+ name, version = get_name_and_semver(new_namever)
117
+ section["source_url"] = f"https://crates.io/api/v1/crates/{name}/{version}/download"
118
+ section["source_filename"] = f"{new_namever}.tar.gz"
119
+ section["source_hash"] = self.checksums[new_namever]
120
+ return True
121
+
122
+
123
class UpdateSubprojects:
124
cargo_registry: str
125
source: CrateSource
149
print(" This may affect the build process - please review the differences.")
150
151
def update_subproject(self, wrap_file: str, source_namever: str) -> None:
107
- """Modify [wrap-file] section to point to self.cargo_registry."""
152
+ """Modify [wrap-file] section to use the crate resolved as `source_namever`."""
153
assert wrap_file.endswith("-rs.wrap")
154
wrap_name = wrap_file[:-5]
155
156
env = os.environ.copy()
112
- env["MESON_PACKAGE_CACHE_DIR"] = self.cargo_registry
157
+ if self.cargo_registry:
158
+ env["MESON_PACKAGE_CACHE_DIR"] = self.cargo_registry
159
160
config = configparser.ConfigParser()
161
config.read(wrap_file)
203
parser = argparse.ArgumentParser(
204
description="Replace Meson subprojects with packages in a Cargo registry"
205
)
206
+ parser.add_argument(
207
+ "--cargo-lock",
208
+ action='store_true',
209
+ default=False,
210
+ help="Update wraps from Cargo.lock",
211
+ )
212
parser.add_argument(
213
"--cargo-registry",
162
- default=os.environ.get("CARGO_REGISTRY"),
214
+ default=None,
215
help="Path to Cargo registry (default: CARGO_REGISTRY env var)",
216
)
217
parser.add_argument(
222
)
223
224
args = parser.parse_args()
173
- if not args.cargo_registry:
174
- print("error: CARGO_REGISTRY environment variable not set and --cargo-registry not provided")
225
+ if args.cargo_registry and args.cargo_lock:
226
+ print("error: --cargo-registry and --cargo-lock are incompatible")
227
sys.exit(1)
228
+ if not args.cargo_registry and not args.cargo_lock:
229
+ args.cargo_registry = os.environ.get("CARGO_REGISTRY")
230
+ if not args.cargo_registry:
231
+ print("error: CARGO_REGISTRY environment variable not set and " +
232
+ "--cargo-registry or --cargo-lock not provided")
233
+ sys.exit(1)
234
235
return args
236
238
self.cargo_registry = args.cargo_registry
239
self.dry_run = args.dry_run
240
self.top_srcdir = os.getcwd()
183
- self.source = CargoRegistry(args.cargo_registry)
241
+ if args.cargo_lock:
242
+ self.source = CargoLock(os.path.join(self.top_srcdir, "Cargo.lock"))
243
+ else:
244
+ self.source = CargoRegistry(args.cargo_registry)
245
246
def main(self) -> None:
247
if not os.path.exists("subprojects"):
254
255
source_namever = self.source.find(namever)
256
if not source_namever:
196
- print(f"No installed crate found for {wrap_file}")
257
+ print(f"No crate found for {wrap_file}")
258
continue
259
260
self.update_subproject(wrap_file, source_namever)