Browse Source
Some distros prefer to avoid vendored crate sources, and instead use
local sources from e.g. ``/usr/share/cargo/registry``. Add a
script, inspired by the Mesa spec file(*), that automatically
performs this task. The script is meant to be invoked after unpacking
the QEMU tarball.
(*) This is the hack that Mesa uses:
export MESON_PACKAGE_CACHE_DIR="%{cargo_registry}/"
%define inst_crate_nameversion() %(basename %{cargo_registry}/%{1}-*)
%define rewrite_wrap_file() sed -e "/source.*/d" -e "s/%{1}-.*/%{inst_crate_nameversion %{1}}/" -i subprojects/%{1}.wrap
%rewrite_wrap_file proc-macro2
... more %rewrite_wrap_file invocations follow ...
Reviewed-by: Neal Gompa <ngompa@fedoraproject.org>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
pull/304/head
3 changed files with 199 additions and 0 deletions
@ -0,0 +1,190 @@ |
|||
#!/usr/bin/env python3 |
|||
|
|||
# SPDX-License-Identifier: GPL-2.0-or-later |
|||
|
|||
""" |
|||
get-wraps-from-cargo-registry.py - Update Meson subprojects from a global registry |
|||
""" |
|||
|
|||
# Copyright (C) 2025 Red Hat, Inc. |
|||
# |
|||
# Author: Paolo Bonzini <pbonzini@redhat.com> |
|||
|
|||
import argparse |
|||
import configparser |
|||
import filecmp |
|||
import glob |
|||
import os |
|||
import subprocess |
|||
import sys |
|||
|
|||
|
|||
def get_name_and_semver(namever: str) -> tuple[str, str]: |
|||
"""Split a subproject name into its name and semantic version parts""" |
|||
parts = namever.rsplit("-", 1) |
|||
if len(parts) != 2: |
|||
return namever, "" |
|||
|
|||
return parts[0], parts[1] |
|||
|
|||
|
|||
class UpdateSubprojects: |
|||
cargo_registry: str |
|||
top_srcdir: str |
|||
dry_run: bool |
|||
changes: int = 0 |
|||
|
|||
def find_installed_crate(self, namever: str) -> str | None: |
|||
"""Find installed crate matching name and semver prefix""" |
|||
name, semver = get_name_and_semver(namever) |
|||
|
|||
# exact version match |
|||
path = os.path.join(self.cargo_registry, f"{name}-{semver}") |
|||
if os.path.exists(path): |
|||
return f"{name}-{semver}" |
|||
|
|||
# semver match |
|||
matches = sorted(glob.glob(f"{path}.*")) |
|||
return os.path.basename(matches[0]) if matches else None |
|||
|
|||
def compare_build_rs(self, orig_dir: str, registry_namever: str) -> None: |
|||
"""Warn if the build.rs in the original directory differs from the registry version.""" |
|||
orig_build_rs = os.path.join(orig_dir, "build.rs") |
|||
new_build_rs = os.path.join(self.cargo_registry, registry_namever, "build.rs") |
|||
|
|||
msg = None |
|||
if os.path.isfile(orig_build_rs) != os.path.isfile(new_build_rs): |
|||
if os.path.isfile(orig_build_rs): |
|||
msg = f"build.rs removed in {registry_namever}" |
|||
if os.path.isfile(new_build_rs): |
|||
msg = f"build.rs added in {registry_namever}" |
|||
|
|||
elif os.path.isfile(orig_build_rs) and not filecmp.cmp(orig_build_rs, new_build_rs): |
|||
msg = f"build.rs changed from {orig_dir} to {registry_namever}" |
|||
|
|||
if msg: |
|||
print(f"⚠️ Warning: {msg}") |
|||
print(" This may affect the build process - please review the differences.") |
|||
|
|||
def update_subproject(self, wrap_file: str, registry_namever: str) -> None: |
|||
"""Modify [wrap-file] section to point to self.cargo_registry.""" |
|||
assert wrap_file.endswith("-rs.wrap") |
|||
wrap_name = wrap_file[:-5] |
|||
|
|||
env = os.environ.copy() |
|||
env["MESON_PACKAGE_CACHE_DIR"] = self.cargo_registry |
|||
|
|||
config = configparser.ConfigParser() |
|||
config.read(wrap_file) |
|||
if "wrap-file" not in config: |
|||
return |
|||
|
|||
# do not download the wrap, always use the local copy |
|||
orig_dir = config["wrap-file"]["directory"] |
|||
if os.path.exists(orig_dir) and orig_dir != registry_namever: |
|||
self.compare_build_rs(orig_dir, registry_namever) |
|||
|
|||
if self.dry_run: |
|||
if orig_dir == registry_namever: |
|||
print(f"Will install {orig_dir} from registry.") |
|||
else: |
|||
print(f"Will replace {orig_dir} with {registry_namever}.") |
|||
self.changes += 1 |
|||
return |
|||
|
|||
config["wrap-file"]["directory"] = registry_namever |
|||
for key in list(config["wrap-file"].keys()): |
|||
if key.startswith("source"): |
|||
del config["wrap-file"][key] |
|||
|
|||
# replace existing directory with installed version |
|||
if os.path.exists(orig_dir): |
|||
subprocess.run( |
|||
["meson", "subprojects", "purge", "--confirm", wrap_name], |
|||
cwd=self.top_srcdir, |
|||
env=env, |
|||
check=True, |
|||
) |
|||
|
|||
with open(wrap_file, "w") as f: |
|||
config.write(f) |
|||
|
|||
if orig_dir == registry_namever: |
|||
print(f"Installing {orig_dir} from registry.") |
|||
else: |
|||
print(f"Replacing {orig_dir} with {registry_namever}.") |
|||
patch_dir = config["wrap-file"]["patch_directory"] |
|||
patch_dir = os.path.join("packagefiles", patch_dir) |
|||
_, ver = registry_namever.rsplit("-", 1) |
|||
subprocess.run( |
|||
["meson", "rewrite", "kwargs", "set", "project", "/", "version", ver], |
|||
cwd=patch_dir, |
|||
env=env, |
|||
check=True, |
|||
) |
|||
|
|||
subprocess.run( |
|||
["meson", "subprojects", "download", wrap_name], |
|||
cwd=self.top_srcdir, |
|||
env=env, |
|||
check=True, |
|||
) |
|||
self.changes += 1 |
|||
|
|||
@staticmethod |
|||
def parse_cmdline() -> argparse.Namespace: |
|||
parser = argparse.ArgumentParser( |
|||
description="Replace Meson subprojects with packages in a Cargo registry" |
|||
) |
|||
parser.add_argument( |
|||
"--cargo-registry", |
|||
default=os.environ.get("CARGO_REGISTRY"), |
|||
help="Path to Cargo registry (default: CARGO_REGISTRY env var)", |
|||
) |
|||
parser.add_argument( |
|||
"--dry-run", |
|||
action="store_true", |
|||
default=False, |
|||
help="Do not actually replace anything", |
|||
) |
|||
|
|||
args = parser.parse_args() |
|||
if not args.cargo_registry: |
|||
print("error: CARGO_REGISTRY environment variable not set and --cargo-registry not provided") |
|||
sys.exit(1) |
|||
|
|||
return args |
|||
|
|||
def __init__(self, args: argparse.Namespace): |
|||
self.cargo_registry = args.cargo_registry |
|||
self.dry_run = args.dry_run |
|||
self.top_srcdir = os.getcwd() |
|||
|
|||
def main(self) -> None: |
|||
if not os.path.exists("subprojects"): |
|||
print("'subprojects' directory not found, nothing to do.") |
|||
return |
|||
|
|||
os.chdir("subprojects") |
|||
for wrap_file in sorted(glob.glob("*-rs.wrap")): |
|||
namever = wrap_file[:-8] # Remove '-rs.wrap' |
|||
|
|||
registry_namever = self.find_installed_crate(namever) |
|||
if not registry_namever: |
|||
print(f"No installed crate found for {wrap_file}") |
|||
continue |
|||
|
|||
self.update_subproject(wrap_file, registry_namever) |
|||
|
|||
if self.changes: |
|||
if self.dry_run: |
|||
print("Rerun without --dry-run to apply changes.") |
|||
else: |
|||
print(f"✨ {self.changes} subproject(s) updated!") |
|||
else: |
|||
print("No changes.") |
|||
|
|||
|
|||
if __name__ == "__main__": |
|||
args = UpdateSubprojects.parse_cmdline() |
|||
UpdateSubprojects(args).main() |
|||
Loading…
Reference in new issue