diff mbox series

[2/2] classes/cargo-update-recipe-crates: support multiple Cargo.lock search directories

Message ID 20261021-mfe-multi-cargo-lock-files-v1-2-d3510d4ad963@pengutronix.de
State New
Headers show
Series Add support for multi Cargo.lock files | expand

Commit Message

Marco Felsch Oct. 21, 2026, 6:46 p.m. UTC
Recipes using cargo's -Z build-std must also vendor the standard
library's dependencies. The std sources staged by rust-native
(.../rustlib/src/rust/library) ship a Cargo.lock but no vendor
directory, so those crates need crate:// entries in SRC_URI as well.
Until now only a single directory could be scanned (CARGO_LOCK_SRC_DIR),
which forced the std crate list to be maintained by hand and go stale on
toolchain upgrades, breaking do_compile with:

  error: failed to select a version for the requirement
  `cfg-if = "^1.0"` (locked to 1.0.4)

Add CARGO_LOCK_SRC_DIRS, a comma-separated list of directories defaulting
to CARGO_LOCK_SRC_DIR, allowing e.g.:

  CARGO_LOCK_SRC_DIRS = "${S},${STAGING_LIBDIR_NATIVE}/rustlib/src/rust/library"

Crates found in multiple lockfiles are listed only once, in the section
of the first Cargo.lock containing them; conflicting checksums for the
same crate version now fail the task. The crates.io warning now names the
lockfile it came from. Output for recipes with a single lockfile is
unchanged.

Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
 .../cargo-update-recipe-crates.bbclass             | 54 ++++++++++++++--------
 1 file changed, 35 insertions(+), 19 deletions(-)
diff mbox series

Patch

diff --git a/meta/classes-recipe/cargo-update-recipe-crates.bbclass b/meta/classes-recipe/cargo-update-recipe-crates.bbclass
index 0767b566ee287d11dc2e1e6d0aeea0a0d3b225b8..859a3d087a90c669c0b640f0ec929ff44c7e5882 100644
--- a/meta/classes-recipe/cargo-update-recipe-crates.bbclass
+++ b/meta/classes-recipe/cargo-update-recipe-crates.bbclass
@@ -21,17 +21,19 @@  RECIPE_UPGRADE_EXTRA_TASKS += "do_update_crates"
 
 # The directory where to search for Cargo.lock files
 CARGO_LOCK_SRC_DIR ??= "${S}"
+# Comma-separated list of directories where to search for Cargo.lock files.
+# Crates found in multiple Cargo.lock files are listed only once, in the
+# section of the first Cargo.lock file that contains them.
+CARGO_LOCK_SRC_DIRS ??= "${CARGO_LOCK_SRC_DIR}"
 
 python do_update_crates() {
-    cargo_lock_src_dir = d.getVar("CARGO_LOCK_SRC_DIR")
-
-    def get_crates(filename):
+    def get_crates(filename, src_dir, seen):
         try:
             import tomllib
         except ImportError:
             import bb._vendor.tomli as tomllib
 
-        c_list = '# from %s' % os.path.relpath(filename, cargo_lock_src_dir)
+        c_list = '# from %s' % os.path.relpath(filename, src_dir)
         c_list += '\nSRC_URI += " \\'
         with open(filename, 'rb') as fp:
             crates = tomllib.load(fp)
@@ -40,17 +42,28 @@  python do_update_crates() {
         crates_candidates = list(filter(lambda c: 'crates.io' in c.get('source', ''), crates['package']))
 
         if not crates_candidates:
-            print("WARNING: Unable to find any candidate crates that use crates.io")
+            print("WARNING: Unable to find any candidate crates that use crates.io in %s" % filename)
             return None
 
         # Update crates uri and their checksum, to avoid name clashing on the checksum
         # we need to rename crates with name and version to have a unique key
+        # Crates already emitted for a previous Cargo.lock are skipped.
         cksum_list = ''
+        ncrates = 0
         for c in crates_candidates:
             rename = "%s-%s" % (c['name'], c['version'])
+            if rename in seen:
+                if seen[rename] != c.get('checksum'):
+                    raise ValueError(f"Conflicting checksums for crate {rename}")
+                continue
+            seen[rename] = c.get('checksum')
             c_list += '\n    crate://crates.io/%s/%s \\' % (c['name'], c['version'])
             if 'checksum' in c:
                 cksum_list += '\nSRC_URI[%s.sha256sum] = "%s"' % (rename, c['checksum'])
+            ncrates += 1
+
+        if not ncrates:
+            return None
 
         c_list += '\n"\n'
         c_list += cksum_list
@@ -62,23 +75,26 @@  python do_update_crates() {
     pn = d.getVar("PN")
     crates = f"# Autogenerated with 'bitbake -c update_crates {pn}'\n\n"
 
+    seen = {}
     found = False
-    for root, dirs, files in os.walk(cargo_lock_src_dir):
-        # ignore git and patches directories
-        if root.startswith(os.path.join(cargo_lock_src_dir, '.pc')):
-            continue
-        if root.startswith(os.path.join(cargo_lock_src_dir, '.git')):
-            continue
-        for file in files:
-            if file == 'Cargo.lock':
-                cargo_lock_path = os.path.join(root, file)
-                c = get_crates(cargo_lock_path)
-                if c is not None:
-                    crates += c
-                    found = True
+    cargo_lock_src_dirs = [x.strip() for x in d.getVar("CARGO_LOCK_SRC_DIRS").split(',') if x.strip()]
+    for cargo_lock_src_dir in cargo_lock_src_dirs:
+        for root, dirs, files in os.walk(cargo_lock_src_dir):
+            # ignore git and patches directories
+            if root.startswith(os.path.join(cargo_lock_src_dir, '.pc')):
+                continue
+            if root.startswith(os.path.join(cargo_lock_src_dir, '.git')):
+                continue
+            for file in files:
+                if file == 'Cargo.lock':
+                    cargo_lock_path = os.path.join(root, file)
+                    c = get_crates(cargo_lock_path, cargo_lock_src_dir, seen)
+                    if c is not None:
+                        crates += c
+                        found = True
 
     if not found:
-        raise ValueError(f"Unable to find any Cargo.lock in {cargo_lock_src_dir}")
+        raise ValueError(f"Unable to find any Cargo.lock in {d.getVar('CARGO_LOCK_SRC_DIRS')}")
 
     target_file = d.expand("${THISDIR}/${BPN}-crates.inc")
     with open(target_file, 'w') as f: