diff mbox series

[RFC,06/30] lib: oe: vendor: add cargo support

Message ID 20250211150034.18696-7-stefan.herbrechtsmeier-oss@weidmueller.com
State New
Headers show
Series Add vendor support for go, npm and rust | expand

Commit Message

Stefan Herbrechtsmeier Feb. 11, 2025, 3 p.m. UTC
From: Stefan Herbrechtsmeier <stefan.herbrechtsmeier@weidmueller.com>

Add a vendor module for cargo to resolve dependencies and populate
vendor directories from a Cargo.lock file.

Signed-off-by: Stefan Herbrechtsmeier <stefan.herbrechtsmeier@weidmueller.com>
---

 meta/lib/oe/vendor/cargo.py | 121 ++++++++++++++++++++++++++++++++++++
 1 file changed, 121 insertions(+)
 create mode 100644 meta/lib/oe/vendor/cargo.py

Comments

Alexander Kanavin Feb. 12, 2025, 10:32 a.m. UTC | #1
On Tue, 11 Feb 2025 at 16:01, Stefan Herbrechtsmeier via
lists.openembedded.org
<stefan.herbrechtsmeier-oss=weidmueller.com@lists.openembedded.org>
wrote:
>
> From: Stefan Herbrechtsmeier <stefan.herbrechtsmeier@weidmueller.com>
>
> Add a vendor module for cargo to resolve dependencies and populate
> vendor directories from a Cargo.lock file.

It isn't clear what is a public API here and what is internal to the
module. Perhaps you could nest the internal functions and constants
into the public functions that use them? Or use the _ prefix in their
names.

Alex
Frédéric Martinsons Feb. 12, 2025, 12:45 p.m. UTC | #2
On Tue, 11 Feb 2025 at 16:01, Stefan Herbrechtsmeier via
lists.openembedded.org <stefan.herbrechtsmeier-oss=
weidmueller.com@lists.openembedded.org> wrote:

> From: Stefan Herbrechtsmeier <stefan.herbrechtsmeier@weidmueller.com>
>
> Add a vendor module for cargo to resolve dependencies and populate
> vendor directories from a Cargo.lock file.
>
>
>
Hello,

Have you looked at vendor subcommand of cargo :
https://doc.rust-lang.org/cargo/commands/cargo-vendor.html ?
I didn't read the series but the commit message makes me think that "cargo
vendor" already exists for resolving dependencies
and populating a vendor directory.
Stefan Herbrechtsmeier Feb. 12, 2025, 4:29 p.m. UTC | #3
Am 12.02.2025 um 13:45 schrieb Frédéric Martinsons:
>
> On Tue, 11 Feb 2025 at 16:01, Stefan Herbrechtsmeier via 
> lists.openembedded.org <http://lists.openembedded.org> 
> <stefan.herbrechtsmeier-oss=weidmueller.com@lists.openembedded.org> wrote:
>
>     From: Stefan Herbrechtsmeier <stefan.herbrechtsmeier@weidmueller.com>
>
>     Add a vendor module for cargo to resolve dependencies and populate
>     vendor directories from a Cargo.lock file.
>
>
>
> Hello,
>
> Have you looked at vendor subcommand of cargo : 
> https://doc.rust-lang.org/cargo/commands/cargo-vendor.html ?
> I didn't read the series but the commit message makes me think that 
> "cargo vendor" already exists for resolving dependencies
> and populating a vendor directory.
>
The class uses the bitbake fetcher (wget / git) to create a vendor 
directory and emulate the cargo vendor command.
Frédéric Martinsons Feb. 12, 2025, 5:48 p.m. UTC | #4
Le mer. 12 févr. 2025, 17:29, Stefan Herbrechtsmeier <
stefan.herbrechtsmeier-oss@weidmueller.com> a écrit :

> Am 12.02.2025 um 13:45 schrieb Frédéric Martinsons:
>
>
> On Tue, 11 Feb 2025 at 16:01, Stefan Herbrechtsmeier via
> lists.openembedded.org <stefan.herbrechtsmeier-oss=
> weidmueller.com@lists.openembedded.org> wrote:
>
>> From: Stefan Herbrechtsmeier <stefan.herbrechtsmeier@weidmueller.com>
>>
>> Add a vendor module for cargo to resolve dependencies and populate
>> vendor directories from a Cargo.lock file.
>>
>>
>>
> Hello,
>
> Have you looked at vendor subcommand of cargo :
> https://doc.rust-lang.org/cargo/commands/cargo-vendor.html ?
> I didn't read the series but the commit message makes me think that "cargo
> vendor" already exists for resolving dependencies
> and populating a vendor directory.
>
> The class uses the bitbake fetcher (wget / git) to create a vendor
> directory and emulate the cargo vendor command.
>

Yes, I understood that. Let me rephrase my question.
Is it desirable/needed to mimick an existing (upstream and maintained)
command, considering that we already use native cargo to build rust
binaries?

Is the new flow you suggest can use the "cargo vendor" call?

Maybe there is room for less custom code here. Sorry if I missed something
obvious, I just react to some keywords in area that I contributed 2 years
ago.
Stefan Herbrechtsmeier Feb. 13, 2025, 8:53 a.m. UTC | #5
Am 12.02.2025 um 18:48 schrieb Frédéric Martinsons:
>
>
> Le mer. 12 févr. 2025, 17:29, Stefan Herbrechtsmeier 
> <stefan.herbrechtsmeier-oss@weidmueller.com> a écrit :
>
>     Am 12.02.2025 um 13:45 schrieb Frédéric Martinsons:
>>
>>     On Tue, 11 Feb 2025 at 16:01, Stefan Herbrechtsmeier via
>>     lists.openembedded.org <http://lists.openembedded.org>
>>     <stefan.herbrechtsmeier-oss=weidmueller.com@lists.openembedded.org>
>>     wrote:
>>
>>         From: Stefan Herbrechtsmeier
>>         <stefan.herbrechtsmeier@weidmueller.com>
>>
>>         Add a vendor module for cargo to resolve dependencies and
>>         populate
>>         vendor directories from a Cargo.lock file.
>>
>>
>>
>>     Hello,
>>
>>     Have you looked at vendor subcommand of cargo :
>>     https://doc.rust-lang.org/cargo/commands/cargo-vendor.html ?
>>     I didn't read the series but the commit message makes me think
>>     that "cargo vendor" already exists for resolving dependencies
>>     and populating a vendor directory.
>>
>     The class uses the bitbake fetcher (wget / git) to create a vendor
>     directory and emulate the cargo vendor command.
>
>
> Yes, I understood that. Let me rephrase my question.
> Is it desirable/needed to mimick an existing (upstream and maintained) 
> command, considering that we already use native cargo to build rust 
> binaries?
We already mimick the cargo vendor command. But at the moment the code 
is hidden inside the crate fetcher.

We need to bypass the download of crates via cargo to support download 
mirrors and offline build. Therefore we download the files via wget and 
populate a local crate repository.

> Is the new flow you suggest can use the "cargo vendor" call?

It is useless because we already create a vendor folder manual 
(CARGO_VENDORING_DIRECTORY).

> Maybe there is room for less custom code here. Sorry if I missed 
> something obvious, I just react to some keywords in area that I 
> contributed 2 years ago.

I mainly extend existing code from crate fetcher and 
cargo-update-recipe-crate class. We need to extract download information 
from the lock file to create SRC_URIs.
diff mbox series

Patch

diff --git a/meta/lib/oe/vendor/cargo.py b/meta/lib/oe/vendor/cargo.py
new file mode 100644
index 0000000000..4d0a0034f3
--- /dev/null
+++ b/meta/lib/oe/vendor/cargo.py
@@ -0,0 +1,121 @@ 
+# Copyright (C) 2024-2025 Weidmueller Interface GmbH & Co. KG
+# Stefan Herbrechtsmeier <stefan.herbrechtsmeier@weidmueller.com>
+#
+# SPDX-License-Identifier: MIT
+#
+import json
+import os
+import tomllib
+import bb
+import oe.vendor
+from bb.fetch2 import URI
+from . import ResolveError
+
+VENDOR_TYPE = "cargo"
+
+VENDOR_DIR = "vendor/cargo"
+
+def escape(path):
+    return re.sub(r'([A-Z])', lambda m: '!' + m.group(1).lower(), path)
+
+def determine_subdir(name, version):
+    return f"{name}-{version}"
+
+def determine_uri_path(path, name, version):
+    path = path.rstrip("/")
+    return f"{path}/api/v1/crates/{name}/{version}/download"
+
+def determine_downloadfilename(name, version):
+    filename = f"{name}-{version}.crate"
+    return oe.vendor.determine_downloadfilename(VENDOR_TYPE, filename)
+
+def extend_uri(uri, name, version, subdir, checksum_name=None,
+               checksum_value=None):
+    uri.path = determine_uri_path(uri.path, name, version)
+    params = uri.params
+    params["subdir"] = subdir
+    params["downloadfilename"] = determine_downloadfilename(name, version)
+    if checksum_name and checksum_value:
+        params[checksum_name] = checksum_value
+
+def determine_src_uri(registry, name, version, subdir):
+    uri = URI(registry)
+    extend_uri(uri, name, version, subdir)
+    return str(uri)
+
+def parse_lock_file(lock_file, function):
+    try:
+        with open(lock_file, "rb") as f:
+            crates = tomllib.load(f)
+    except Exception as e:
+        raise ResolveError(f"Invalid file: {str(e)}", lock_file)
+
+    for data in crates.get("package", []):
+        if "source" not in data:
+            continue
+
+        function(data)
+
+def resolve_src_uris(lock_file, registry, base_subdir, vendor_subdir):
+    src_uris = []
+
+    def resolve_src_uri(data):
+        name =  data.get('name')
+        version = data.get('version')
+        source = data.get("source")
+
+        if source.startswith("registry"):
+            checksum_name = "sha256sum"
+            checksum_value = data.get('checksum')
+            uri = URI(source[9:])
+            if (source[9:] == "https://github.com/rust-lang/crates.io-index"):
+                uri = URI(registry)
+                params = uri.params
+                params["name"] = name
+                params["version"] = version
+                params["type"] = VENDOR_TYPE
+                subdir = os.path.join(base_subdir, vendor_subdir)
+                extend_uri(uri, name, version, subdir, checksum_name,
+                           checksum_value)
+            else:
+                raise ResolveError(f"Unsupported cargo registry: {source}",
+                                   lock_file)
+
+        elif source.startswith("git"):
+            repository, _, revision = source.partition("#")
+            uri = URI(repository)
+            params = uri.params
+            scheme, _, protocol = uri.scheme.partition("+")
+            if protocol:
+                params["protocol"] = protocol
+                uri.scheme = scheme
+            params["nobranch"] = "1"
+            subdir = determine_subdir(name, version)
+            params["subdir"] = os.path.join(base_subdir, vendor_subdir, subdir)
+            params["rev"] = revision
+        else:
+            raise ResolveError(f"Unsupported dependency: {name}", lock_file)
+
+        src_uris.append(str(uri))
+
+    parse_lock_file(lock_file, resolve_src_uri)
+
+    return src_uris
+
+def populate_vendor(lock_file, rootdir):
+    def populate_checksum(data):
+        name =  data.get('name')
+        version = data.get('version')
+        source = data.get("source")
+        chechsum = data.get('checksum')
+
+        if source.startswith("registry"):
+            subdir = determine_subdir(name, version)
+            filepath = os.path.join(rootdir, subdir, ".cargo-checksum.json")
+            with open(filepath, "w") as f:
+                json.dump({
+                    "files": {},
+                    "package": chechsum
+                }, f)
+
+    parse_lock_file(lock_file, populate_checksum)