qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Paolo Bonzini <pbonzini@redhat.com>
To: qemu-devel@nongnu.org
Cc: Neal Gompa <ngompa@fedoraproject.org>
Subject: [PULL 3/4] scripts: add script to help distros use global Rust packages
Date: Fri, 25 Jul 2025 14:54:02 +0200	[thread overview]
Message-ID: <20250725125412.1128617-4-pbonzini@redhat.com> (raw)
In-Reply-To: <20250725125412.1128617-1-pbonzini@redhat.com>

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>
---
 MAINTAINERS                              |   1 +
 docs/about/build-platforms.rst           |   8 +
 scripts/get-wraps-from-cargo-registry.py | 190 +++++++++++++++++++++++
 3 files changed, 199 insertions(+)
 create mode 100755 scripts/get-wraps-from-cargo-registry.py

diff --git a/MAINTAINERS b/MAINTAINERS
index a4623456183..1604f3dfc1f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3511,6 +3511,7 @@ S: Maintained
 F: rust/qemu-api
 F: rust/qemu-api-macros
 F: rust/rustfmt.toml
+F: scripts/get-wraps-from-cargo-registry.py
 
 Rust-related patches CC here
 L: qemu-rust@nongnu.org
diff --git a/docs/about/build-platforms.rst b/docs/about/build-platforms.rst
index 8ecbd6b26f7..8671c3be9cd 100644
--- a/docs/about/build-platforms.rst
+++ b/docs/about/build-platforms.rst
@@ -127,6 +127,14 @@ Rust build dependencies
   (or newer) package.  The path to ``rustc`` and ``rustdoc`` must be
   provided manually to the configure script.
 
+  Some distros prefer to avoid vendored crate sources, and instead use
+  local sources from e.g. ``/usr/share/cargo/registry``.  QEMU includes a
+  script, ``scripts/get-wraps-from-cargo-registry.py``, that automatically
+  performs this task.  The script is meant to be invoked after unpacking
+  the QEMU tarball.  QEMU also includes ``rust/Cargo.toml`` and
+  ``rust/Cargo.lock`` files that can be used to compute QEMU's build
+  dependencies, e.g. using ``cargo2rpm -p rust/Cargo.toml buildrequires``.
+
 Optional build dependencies
   Build components whose absence does not affect the ability to build QEMU
   may not be available in distros, or may be too old for our requirements.
diff --git a/scripts/get-wraps-from-cargo-registry.py b/scripts/get-wraps-from-cargo-registry.py
new file mode 100755
index 00000000000..31eed5c2dd4
--- /dev/null
+++ b/scripts/get-wraps-from-cargo-registry.py
@@ -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()
-- 
2.50.1



  parent reply	other threads:[~2025-07-25 12:58 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-07-25 12:53 [PULL 0/4] Rust, target/i386 patches for 10.1-rc1 Paolo Bonzini
2025-07-25 12:54 ` [PULL 1/4] rust: devices are not staticlibs Paolo Bonzini
2025-07-25 12:54 ` [PULL 2/4] rust/pl011: merge device_class.rs into device.rs Paolo Bonzini
2025-07-25 12:54 ` Paolo Bonzini [this message]
2025-07-25 12:54 ` [PULL 4/4] target/i386: fix width of third operand of VINSERTx128 Paolo Bonzini
2025-07-28 13:30 ` [PULL 0/4] Rust, target/i386 patches for 10.1-rc1 Stefan Hajnoczi

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250725125412.1128617-4-pbonzini@redhat.com \
    --to=pbonzini@redhat.com \
    --cc=ngompa@fedoraproject.org \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).