public inbox for buildroot@busybox.net
 help / color / mirror / Atom feed
* [Buildroot] [PATCH v4 0/6] Extend CycloneDX metadata
@ 2026-04-09  8:13 Martin Willi
  2026-04-09  8:13 ` [Buildroot] [PATCH v4 1/6] support/testing/utils: add basic tests for utils/generate-cyclonedx Martin Willi
                   ` (5 more replies)
  0 siblings, 6 replies; 10+ messages in thread
From: Martin Willi @ 2026-04-09  8:13 UTC (permalink / raw)
  To: buildroot; +Cc: Thomas Perale

Adds SBOM component externalReferences with source-distribution URLs and
hashes, basic unit-tests and a minor fix root component dependencies.

Changes v3 -> v4:
  - Fix test data to properly model virtual package provides/dependencies
  - Add docstring for root component filtering function in patch 2/6
  - Refactor download URL extraction in patch 3/6, preparing vcs support
  - New patch 6/6: add vcs externalReferences for packages sourced via git

Martin Willi (6):
  support/testing/utils: add basic tests for utils/generate-cyclonedx
  utils/generate-cyclonedx: remove indirect dependencies from root
    component
  utils/generate-cyclonedx: generate externalReferences with
    source-distribution
  package/pkg-utils: add 'hashes' to show-info
  utils/generate-cyclonedx: add hashes from .hash files to
    externalReferences
  utils/generate-cyclonedx: generate vcs externalReferences for source
    repos

 package/pkg-utils.mk                          |   7 +
 .../tests/utils/test_generate_cyclonedx.py    | 240 ++++++++++++++++++
 .../cve_upstream.patch                        |  11 +
 utils/generate-cyclonedx                      | 110 +++++++-
 4 files changed, 367 insertions(+), 1 deletion(-)
 create mode 100644 support/testing/tests/utils/test_generate_cyclonedx.py
 create mode 100644 support/testing/tests/utils/test_generate_cyclonedx/cve_upstream.patch

-- 
2.43.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [Buildroot] [PATCH v4 1/6] support/testing/utils: add basic tests for utils/generate-cyclonedx
  2026-04-09  8:13 [Buildroot] [PATCH v4 0/6] Extend CycloneDX metadata Martin Willi
@ 2026-04-09  8:13 ` Martin Willi
  2026-04-09  8:34   ` Thomas Perale via buildroot
  2026-04-09  8:13 ` [Buildroot] [PATCH v4 2/6] utils/generate-cyclonedx: remove indirect dependencies from root component Martin Willi
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 10+ messages in thread
From: Martin Willi @ 2026-04-09  8:13 UTC (permalink / raw)
  To: buildroot; +Cc: Thomas Perale

Introduce unit-tests for the generate-cyclonedx script, covering basic
script invocation, patch CVE extraction and virtual packages.

Signed-off-by: Martin Willi <martin@strongswan.org>
---
 .../tests/utils/test_generate_cyclonedx.py    | 139 ++++++++++++++++++
 .../cve_upstream.patch                        |  11 ++
 2 files changed, 150 insertions(+)
 create mode 100644 support/testing/tests/utils/test_generate_cyclonedx.py
 create mode 100644 support/testing/tests/utils/test_generate_cyclonedx/cve_upstream.patch

diff --git a/support/testing/tests/utils/test_generate_cyclonedx.py b/support/testing/tests/utils/test_generate_cyclonedx.py
new file mode 100644
index 000000000000..bfe5eaf054cf
--- /dev/null
+++ b/support/testing/tests/utils/test_generate_cyclonedx.py
@@ -0,0 +1,139 @@
+"""Unit tests for utils/generate-cyclonedx."""
+
+import json
+import os
+import subprocess
+import tempfile
+import unittest
+from pathlib import Path
+
+import infra
+
+PATCH = "support/testing/tests/utils/test_generate_cyclonedx/cve_upstream.patch"
+SCHEMA_LICENSES = ["MIT", "Apache-2.0", "GPL-3.0-only"]
+
+
+class TestGenerateCycloneDX(unittest.TestCase):
+    def setUp(self):
+        # Provide a fake SPDX schema so the script never hits the network.
+        self.schema_dir = tempfile.TemporaryDirectory()
+        self.addCleanup(self.schema_dir.cleanup)
+
+        cyclonedx_dir = Path(self.schema_dir.name) / "cyclonedx"
+        cyclonedx_dir.mkdir(parents=True)
+        schema_path = cyclonedx_dir / "spdx-1.6.schema.json"
+        schema_path.write_text(json.dumps({"enum": SCHEMA_LICENSES}))
+
+        self.env = os.environ.copy()
+        self.env["BR2_DL_DIR"] = self.schema_dir.name
+        self.script = infra.basepath("utils/generate-cyclonedx")
+        self.cwd = infra.basepath()
+
+    def _make_show_info(self) -> dict:
+        return {
+            "package-foo": {
+                "name": "foo",
+                "version": "1.2",
+                "type": "target",
+                "virtual": False,
+                "licenses": "MIT",
+                "cpe-id": "cpe:2.3:a:example:foo:1.2:*:*:*:*:*:*:*",
+                "patches": [PATCH],
+                "provides": ["package-virtual"],
+                "dependencies": ["skeleton-baz", "package-bar"],
+                "ignore_cves": ["CVE-2025-0001"],
+                "package_dir": "package/package-foo",
+            },
+            "skeleton-baz": {
+                "name": "skeleton-baz",
+                "version": "0.1",
+                "type": "target",
+                "virtual": False,
+                "licenses": "Apache-2.0",
+                "dependencies": [],
+                "package_dir": "package/skeleton-baz",
+            },
+            "package-bar": {
+                "name": "bar",
+                "version": "0.2",
+                "type": "target",
+                "virtual": False,
+                "licenses": "MIT",
+                "ignore_cves": ["CVE-2025-0002"],
+                "dependencies": ["package-virtual"],
+                "package_dir": "package/package-bar",
+            },
+            "host-tool": {
+                "name": "host-tool",
+                "version": "0.3",
+                "type": "host",
+                "virtual": False,
+                "licenses": "GPL-3.0-only",
+                "dependencies": [],
+                "package_dir": "package/host-tool",
+            },
+            "package-virtual": {
+                "name": "virtual-provider",
+                "virtual": True,
+                "type": "target",
+                "dependencies": ["package-foo"],
+                "package_dir": "package/package-virtual",
+            },
+        }
+
+    def _run_script(self, extra_args=(), show_info=None):
+        data = show_info if show_info is not None else self._make_show_info()
+        completed = subprocess.run(
+            [self.script, *extra_args],
+            cwd=self.cwd,
+            env=self.env,
+            input=json.dumps(data),
+            text=True,
+            capture_output=True,
+            check=True,
+        )
+        return json.loads(completed.stdout)
+
+    def _find_component(self, result: dict, name: str) -> dict:
+        for component in result["components"]:
+            if component["bom-ref"] == name:
+                return component
+        self.fail(f"component {name} missing")
+
+    def test_default(self):
+        result = self._run_script()
+
+        self.assertEqual(len(result["components"]), 4)
+        self.assertIn("vulnerabilities", result)
+        vulnerabilities = {v["id"]: v for v in result["vulnerabilities"]}
+        self.assertEqual(len(vulnerabilities), 2)
+        self.assertEqual(vulnerabilities["CVE-2025-0001"]["analysis"]["state"], "resolved_with_pedigree")
+        self.assertEqual(vulnerabilities["CVE-2025-0002"]["analysis"]["state"], "in_triage")
+
+        foo = self._find_component(result, "package-foo")
+        patch = foo["pedigree"]["patches"][0]
+        self.assertIn("text", patch["diff"])
+        self.assertIn("content", patch["diff"]["text"])
+
+        host = self._find_component(result, "host-tool")
+        self.assertEqual(host["properties"][0]["value"], "host")
+
+        names = {c["bom-ref"] for c in result["components"]}
+        self.assertIn("skeleton-baz", names)
+        self.assertIn("package-bar", names)
+        self.assertNotIn("package-virtual", names)
+
+        foo_deps = next(d for d in result["dependencies"] if d["ref"] == "package-foo")
+        self.assertEqual(foo_deps["dependsOn"], ["package-bar", "skeleton-baz"])
+
+        bar_deps = next(d for d in result["dependencies"] if d["ref"] == "package-bar")
+        self.assertEqual(bar_deps["dependsOn"], ["package-foo"])
+
+    def test_virtual(self):
+        result = self._run_script(["--virtual"])
+
+        names = {c["bom-ref"] for c in result["components"]}
+        self.assertEqual(names, {"package-foo", "skeleton-baz", "host-tool", "package-virtual", "package-bar"})
+
+        foo_deps = next(d for d in result["dependencies"] if d["ref"] == "package-foo")
+        self.assertEqual(foo_deps["dependsOn"], ["package-bar", "skeleton-baz"])
diff --git a/support/testing/tests/utils/test_generate_cyclonedx/cve_upstream.patch b/support/testing/tests/utils/test_generate_cyclonedx/cve_upstream.patch
new file mode 100644
index 000000000000..f18e51ebb9ec
--- /dev/null
+++ b/support/testing/tests/utils/test_generate_cyclonedx/cve_upstream.patch
@@ -0,0 +1,11 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+CVE: CVE-2025-0001
+Upstream: https://patches.example/foo.patch
+
+diff --git a/foo.txt b/foo.txt
+index 0000001..0000002 100644
+--- a/foo.txt
++++ b/foo.txt
+@@
+-foo
++bar
-- 
2.43.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [Buildroot] [PATCH v4 2/6] utils/generate-cyclonedx: remove indirect dependencies from root component
  2026-04-09  8:13 [Buildroot] [PATCH v4 0/6] Extend CycloneDX metadata Martin Willi
  2026-04-09  8:13 ` [Buildroot] [PATCH v4 1/6] support/testing/utils: add basic tests for utils/generate-cyclonedx Martin Willi
@ 2026-04-09  8:13 ` Martin Willi
  2026-04-09  8:13 ` [Buildroot] [PATCH v4 3/6] utils/generate-cyclonedx: generate externalReferences with source-distribution Martin Willi
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 10+ messages in thread
From: Martin Willi @ 2026-04-09  8:13 UTC (permalink / raw)
  To: buildroot; +Cc: Thomas Perale

Commit dc4af8bfa979 ("utils/generate-cyclonedx: use direct dependencies")
removes indirect dependencies from any listed component, as required by
CycloneDX. The root component, however, still includes indirect dependencies,
as it just takes the components from the show-info output.

Fix this by collecting all component dependencies, and then filter the root
component dependencies to include direct dependencies only.

Signed-off-by: Martin Willi <martin@strongswan.org>
Acked-By: Thomas Perale <thomas.perale@mind.be>
---
 .../tests/utils/test_generate_cyclonedx.py    |  3 +++
 utils/generate-cyclonedx                      | 21 ++++++++++++++++++-
 2 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/support/testing/tests/utils/test_generate_cyclonedx.py b/support/testing/tests/utils/test_generate_cyclonedx.py
index bfe5eaf054cf..bf1b8e099bf9 100644
--- a/support/testing/tests/utils/test_generate_cyclonedx.py
+++ b/support/testing/tests/utils/test_generate_cyclonedx.py
@@ -129,6 +129,9 @@ class TestGenerateCycloneDX(unittest.TestCase):
         bar_deps = next(d for d in result["dependencies"] if d["ref"] == "package-bar")
         self.assertEqual(bar_deps["dependsOn"], ["package-foo"])
 
+        project_deps = next(d for d in result["dependencies"] if d["ref"] == "buildroot")
+        self.assertEqual(project_deps["dependsOn"], ["host-tool", "package-foo"])
+
     def test_virtual(self):
         result = self._run_script(["--virtual"])
 
diff --git a/utils/generate-cyclonedx b/utils/generate-cyclonedx
index 35198a47cfdd..f4d5afd847e5 100755
--- a/utils/generate-cyclonedx
+++ b/utils/generate-cyclonedx
@@ -385,6 +385,25 @@ def br2_parse_deps(ref, show_info_dict, virtual=False) -> list:
     return list(deps)
 
 
+def br2_root_deps(show_info_dict, virtual=False) -> list:
+    """Retrieve the list of direct dependencies of the root component.
+
+    This function returns all components that are not a dependency of any
+    other component.
+
+    Args:
+        show_info_dict (dict): The JSON output of the show-info command.
+        virtual (bool): Whether to resolve virtual dependencies to their providers.
+
+    Returns:
+        list: List of direct dependencies of the root component.
+    """
+    indirect = set()
+    for ref in show_info_dict:
+        indirect.update(br2_parse_deps(ref, show_info_dict, virtual))
+    return [ref for ref in show_info_dict if ref not in indirect]
+
+
 def main():
     parser = argparse.ArgumentParser(
             description='''Create a CycloneDX SBoM for the Buildroot configuration.
@@ -447,7 +466,7 @@ def main():
             cyclonedx_component(name, comp) for name, comp in filtered_show_info_dict.items()
         ],
         "dependencies": [
-            cyclonedx_dependency(args.project_name, list(filtered_show_info_dict)),
+            cyclonedx_dependency(args.project_name, br2_root_deps(filtered_show_info_dict, args.virtual)),
             *[cyclonedx_dependency(ref, br2_parse_deps(ref, show_info_dict, args.virtual))
               for ref in filtered_show_info_dict],
         ],
-- 
2.43.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [Buildroot] [PATCH v4 3/6] utils/generate-cyclonedx: generate externalReferences with source-distribution
  2026-04-09  8:13 [Buildroot] [PATCH v4 0/6] Extend CycloneDX metadata Martin Willi
  2026-04-09  8:13 ` [Buildroot] [PATCH v4 1/6] support/testing/utils: add basic tests for utils/generate-cyclonedx Martin Willi
  2026-04-09  8:13 ` [Buildroot] [PATCH v4 2/6] utils/generate-cyclonedx: remove indirect dependencies from root component Martin Willi
@ 2026-04-09  8:13 ` Martin Willi
  2026-04-09  8:43   ` Thomas Perale via buildroot
  2026-04-09  8:13 ` [Buildroot] [PATCH v4 4/6] package/pkg-utils: add 'hashes' to show-info Martin Willi
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 10+ messages in thread
From: Martin Willi @ 2026-04-09  8:13 UTC (permalink / raw)
  To: buildroot; +Cc: Thomas Perale

BSI TR-03183-2 5.4.2 [1] lists source code URIs under "Additional data fields
for each component", and as such "MUST additionally be provided, if it exists".

If a http or https source download URI is available from show-info, extract
it and include it as an externalReference of type "source-distribution" in the
CycloneDX output.

[1] https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/TechGuidelines/TR03183/BSI-TR-03183-2_v2_1_0.pdf?__blob=publicationFile&v=5

Signed-off-by: Martin Willi <martin@strongswan.org>
---
 .../tests/utils/test_generate_cyclonedx.py    | 26 ++++++++++
 utils/generate-cyclonedx                      | 47 +++++++++++++++++++
 2 files changed, 73 insertions(+)

diff --git a/support/testing/tests/utils/test_generate_cyclonedx.py b/support/testing/tests/utils/test_generate_cyclonedx.py
index bf1b8e099bf9..a071ff867923 100644
--- a/support/testing/tests/utils/test_generate_cyclonedx.py
+++ b/support/testing/tests/utils/test_generate_cyclonedx.py
@@ -140,3 +140,29 @@ class TestGenerateCycloneDX(unittest.TestCase):
 
         foo_deps = next(d for d in result["dependencies"] if d["ref"] == "package-foo")
         self.assertEqual(foo_deps["dependsOn"], ["package-bar", "skeleton-baz"])
+
+    def test_external_references(self):
+        info = self._make_show_info()
+        info["package-foo"]["downloads"] = [
+            {
+                "source": "foo-1.2.tar.gz",
+                "uris": [
+                    "https+https://sources.buildroot.net/foo",
+                    "http|https+https://mirror.example.org/foo",
+                ],
+            },
+        ]
+
+        result = self._run_script(show_info=info)
+        foo = self._find_component(result, "package-foo")
+
+        self.assertIn("externalReferences", foo)
+        self.assertEqual(
+            foo["externalReferences"],
+            [
+                {
+                    "type": "source-distribution",
+                    "url": "https://mirror.example.org/foo/foo-1.2.tar.gz",
+                },
+            ],
+        )
diff --git a/utils/generate-cyclonedx b/utils/generate-cyclonedx
index f4d5afd847e5..a3b7293f9a5e 100755
--- a/utils/generate-cyclonedx
+++ b/utils/generate-cyclonedx
@@ -14,6 +14,8 @@ import gzip
 import json
 import os
 from pathlib import Path
+from typing import Iterator
+import urllib.parse
 import urllib.request
 import subprocess
 import sys
@@ -261,6 +263,50 @@ def cyclonedx_patches(patch_list: list[str]):
     }
 
 
+def parse_uris(uris: list[str]) -> Iterator[tuple[list[str], str]]:
+    """Parse download URIs into (schemes, url) tuples.
+
+    Splits the Buildroot URI format "scheme[|scheme]+url" and yields all
+    Buildroot schemes with the stripped URL, excluding
+    sources.buildroot.net mirrors.
+
+    Args:
+        uris (list): Array of URI strings from the show-info output.
+    Yields:
+        tuple[list[str], str]: (schemes, url) for each usable URI.
+    """
+    for uri in uris:
+        scheme, _, stripped_uri = uri.partition("+")
+        if stripped_uri:
+            parsed = urllib.parse.urlparse(stripped_uri)
+            if parsed.hostname != "sources.buildroot.net":
+                yield scheme.split("|"), stripped_uri
+
+
+def cyclonedx_external_refs(comp):
+    """Create CycloneDX external references for a component.
+
+    Args:
+        comp (dict): The component information from the show-info output.
+    Returns:
+        dict: External reference information in CycloneDX format, or empty dict
+    """
+    SOURCE_DIST_SCHEMES = {"http", "https"}
+
+    refs = []
+    for download in comp.get("downloads", []):
+        source = download.get("source")
+        for schemes, uri in parse_uris(download.get("uris", [])):
+            if set(schemes) & SOURCE_DIST_SCHEMES and source:
+                refs.append({
+                    "type": "source-distribution",
+                    "url": f"{uri}/{source}",
+                })
+    if refs:
+        return {"externalReferences": refs}
+    return {}
+
+
 def cyclonedx_component(name, comp):
     """Translate a component from the show-info output, to a component entry in CycloneDX format.
 
@@ -284,6 +330,7 @@ def cyclonedx_component(name, comp):
         **({
             "cpe": comp["cpe-id"],
         } if "cpe-id" in comp else {}),
+        **cyclonedx_external_refs(comp),
         **(cyclonedx_patches(comp["patches"]) if comp.get("patches") else {}),
         "properties": [{
             "name": "BR_TYPE",
-- 
2.43.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [Buildroot] [PATCH v4 4/6] package/pkg-utils: add 'hashes' to show-info
  2026-04-09  8:13 [Buildroot] [PATCH v4 0/6] Extend CycloneDX metadata Martin Willi
                   ` (2 preceding siblings ...)
  2026-04-09  8:13 ` [Buildroot] [PATCH v4 3/6] utils/generate-cyclonedx: generate externalReferences with source-distribution Martin Willi
@ 2026-04-09  8:13 ` Martin Willi
  2026-04-09  8:14 ` [Buildroot] [PATCH v4 5/6] utils/generate-cyclonedx: add hashes from .hash files to externalReferences Martin Willi
  2026-04-09  8:14 ` [Buildroot] [PATCH v4 6/6] utils/generate-cyclonedx: generate vcs externalReferences for source repos Martin Willi
  5 siblings, 0 replies; 10+ messages in thread
From: Martin Willi @ 2026-04-09  8:13 UTC (permalink / raw)
  To: buildroot; +Cc: Thomas Perale

Finding the hash file for a package is non-trivial, as they can be in a
<version> sub-directory or under GLOBAL_PATCH_DIR. To allow other tools
such as utils/generate-cyclonedx to find hash files, expose this information
from show-info. If a package does not provide a hash file, create an
empty hashes array.

Suggested-by: Thomas Perale <thomas.perale@mind.be>
Signed-off-by: Martin Willi <martin@strongswan.org>
---
 package/pkg-utils.mk | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/package/pkg-utils.mk b/package/pkg-utils.mk
index 211e681e8f5c..17b0aa176045 100644
--- a/package/pkg-utils.mk
+++ b/package/pkg-utils.mk
@@ -197,6 +197,13 @@ define _json-info-pkg-details
 		},
 	)
 	],
+	"hashes": [
+		$(call make-comma-list, \
+			$(foreach hash,$(wildcard $($(1)_HASH_FILES)),
+				$(call mk-json-str,$(hash)) \
+			) \
+		)
+	],
 	"patches": [
 		$(foreach patch, \
 			$(call pkg-patches-list,$(1)), \
-- 
2.43.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [Buildroot] [PATCH v4 5/6] utils/generate-cyclonedx: add hashes from .hash files to externalReferences
  2026-04-09  8:13 [Buildroot] [PATCH v4 0/6] Extend CycloneDX metadata Martin Willi
                   ` (3 preceding siblings ...)
  2026-04-09  8:13 ` [Buildroot] [PATCH v4 4/6] package/pkg-utils: add 'hashes' to show-info Martin Willi
@ 2026-04-09  8:14 ` Martin Willi
  2026-04-09  8:14 ` [Buildroot] [PATCH v4 6/6] utils/generate-cyclonedx: generate vcs externalReferences for source repos Martin Willi
  5 siblings, 0 replies; 10+ messages in thread
From: Martin Willi @ 2026-04-09  8:14 UTC (permalink / raw)
  To: buildroot; +Cc: Thomas Perale

BSI TR-03183-2 5.2.5 [1] lists the "Hash value of the source code of the
component" under "Optional data fields for each component", and as such
CycloneDX "MAY additionally include the [...] information, if it exists".

As hash values are available in Buildroot, iterate over .hash file paths
from show-info input and read hash values for the source distribution. Add
all found hashes to externalReferences source-distribution entries.

[1] https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/TechGuidelines/TR03183/BSI-TR-03183-2_v2_1_0.pdf?__blob=publicationFile&v=5

Signed-off-by: Martin Willi <martin@strongswan.org>
Acked-By: Thomas Perale <thomas.perale@mind.be>
---
 .../tests/utils/test_generate_cyclonedx.py    | 44 +++++++++++++++++++
 utils/generate-cyclonedx                      | 34 ++++++++++++++
 2 files changed, 78 insertions(+)

diff --git a/support/testing/tests/utils/test_generate_cyclonedx.py b/support/testing/tests/utils/test_generate_cyclonedx.py
index a071ff867923..84f94f050760 100644
--- a/support/testing/tests/utils/test_generate_cyclonedx.py
+++ b/support/testing/tests/utils/test_generate_cyclonedx.py
@@ -166,3 +166,47 @@ class TestGenerateCycloneDX(unittest.TestCase):
                 },
             ],
         )
+
+    def test_external_references_hashes(self):
+        with tempfile.TemporaryDirectory() as tmpdir:
+            hash_file = Path(tmpdir) / "foo.hash"
+            hash_file.write_text(
+                "# source archive checksums\n"
+                "sha256 1111111111111111111111111111111111111111111111111111111111111111 foo-1.2.tar.gz\n"
+                "sha1 2222222222222222222222222222222222222222 foo-1.2.tar.gz\n"
+                "sha256 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa LICENSE\n"
+            )
+
+            info = self._make_show_info()
+            info["package-foo"]["hashes"] = [str(hash_file)]
+            info["package-foo"]["downloads"] = [
+                {
+                    "source": "foo-1.2.tar.gz",
+                    "uris": [
+                        "http|https+https://mirror.example.org/foo",
+                    ],
+                },
+            ]
+
+            result = self._run_script(show_info=info)
+
+        foo = self._find_component(result, "package-foo")
+        self.assertEqual(
+            foo["externalReferences"],
+            [
+                {
+                    "type": "source-distribution",
+                    "url": "https://mirror.example.org/foo/foo-1.2.tar.gz",
+                    "hashes": [
+                        {
+                            "alg": "SHA-256",
+                            "content": "1111111111111111111111111111111111111111111111111111111111111111",
+                        },
+                        {
+                            "alg": "SHA-1",
+                            "content": "2222222222222222222222222222222222222222",
+                        },
+                    ],
+                }
+            ],
+        )
diff --git a/utils/generate-cyclonedx b/utils/generate-cyclonedx
index a3b7293f9a5e..382d91ce55af 100755
--- a/utils/generate-cyclonedx
+++ b/utils/generate-cyclonedx
@@ -283,6 +283,39 @@ def parse_uris(uris: list[str]) -> Iterator[tuple[list[str], str]]:
                 yield scheme.split("|"), stripped_uri
 
 
+def cyclonedx_source_hashes(comp, source):
+    """Create CycloneDX hashes for a source distribution.
+
+    Args:
+        comp (dict): The component information from the show-info output.
+        source (str): The source distribution filename to look for in the hash file.
+    Returns:
+        dict: Hash information in CycloneDX format, or empty dict
+    """
+    MAPPING = {
+        "sha1": "SHA-1",
+        "sha256": "SHA-256",
+        "sha512": "SHA-512",
+        "md5": "MD5",
+    }
+
+    hashes = []
+    for hash_file in comp.get("hashes", []):
+        with Path(hash_file).open() as f:
+            for line in f:
+                line = line.strip()
+                if not line.startswith("#") and line.endswith(f" {source}"):
+                    parts = line.split()
+                    if len(parts) >= 3 and parts[0] in MAPPING:
+                        hashes.append({
+                            "alg": MAPPING[parts[0]],
+                            "content": parts[1],
+                        })
+    if hashes:
+        return {"hashes": hashes}
+    return {}
+
+
 def cyclonedx_external_refs(comp):
     """Create CycloneDX external references for a component.
 
@@ -301,6 +334,7 @@ def cyclonedx_external_refs(comp):
                 refs.append({
                     "type": "source-distribution",
                     "url": f"{uri}/{source}",
+                    **cyclonedx_source_hashes(comp, source),
                 })
     if refs:
         return {"externalReferences": refs}
-- 
2.43.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [Buildroot] [PATCH v4 6/6] utils/generate-cyclonedx: generate vcs externalReferences for source repos
  2026-04-09  8:13 [Buildroot] [PATCH v4 0/6] Extend CycloneDX metadata Martin Willi
                   ` (4 preceding siblings ...)
  2026-04-09  8:14 ` [Buildroot] [PATCH v4 5/6] utils/generate-cyclonedx: add hashes from .hash files to externalReferences Martin Willi
@ 2026-04-09  8:14 ` Martin Willi
  2026-04-09  8:49   ` Thomas Perale via buildroot
  5 siblings, 1 reply; 10+ messages in thread
From: Martin Willi @ 2026-04-09  8:14 UTC (permalink / raw)
  To: buildroot; +Cc: Thomas Perale

Some packages do not have a http/https download URL for a source tarball,
but are acquired over a version control system like git. If so, add
externalReferences of type "vcs" for such URLs.

As most git repositories use a https:// transport that may not indicated the
repository type, add a "comment" due to the lack of a better mechanism in
CycloneDX.

While the hashes are calculated over a tarball created locally, it still may
be useful, so add them for "vcs" externalReferences as well.

Signed-off-by: Martin Willi <martin@strongswan.org>
---
 .../tests/utils/test_generate_cyclonedx.py    | 30 ++++++++++++++++++-
 utils/generate-cyclonedx                      |  8 +++++
 2 files changed, 37 insertions(+), 1 deletion(-)

diff --git a/support/testing/tests/utils/test_generate_cyclonedx.py b/support/testing/tests/utils/test_generate_cyclonedx.py
index 84f94f050760..77690b1b98bc 100644
--- a/support/testing/tests/utils/test_generate_cyclonedx.py
+++ b/support/testing/tests/utils/test_generate_cyclonedx.py
@@ -147,6 +147,8 @@ class TestGenerateCycloneDX(unittest.TestCase):
             {
                 "source": "foo-1.2.tar.gz",
                 "uris": [
+                    "git+git://git.example.org/foo",
+                    "svn+https://svn.example.org/foo",
                     "https+https://sources.buildroot.net/foo",
                     "http|https+https://mirror.example.org/foo",
                 ],
@@ -160,10 +162,20 @@ class TestGenerateCycloneDX(unittest.TestCase):
         self.assertEqual(
             foo["externalReferences"],
             [
+                {
+                    "type": "vcs",
+                    "url": "git://git.example.org/foo",
+                    "comment": "git repository",
+                },
+                {
+                    "type": "vcs",
+                    "url": "https://svn.example.org/foo",
+                    "comment": "svn repository",
+                },
                 {
                     "type": "source-distribution",
                     "url": "https://mirror.example.org/foo/foo-1.2.tar.gz",
-                },
+                }
             ],
         )
 
@@ -183,6 +195,7 @@ class TestGenerateCycloneDX(unittest.TestCase):
                 {
                     "source": "foo-1.2.tar.gz",
                     "uris": [
+                        "git+git://git.example.org/foo",
                         "http|https+https://mirror.example.org/foo",
                     ],
                 },
@@ -194,6 +207,21 @@ class TestGenerateCycloneDX(unittest.TestCase):
         self.assertEqual(
             foo["externalReferences"],
             [
+                {
+                    "type": "vcs",
+                    "url": "git://git.example.org/foo",
+                    "comment": "git repository",
+                    "hashes": [
+                        {
+                            "alg": "SHA-256",
+                            "content": "1111111111111111111111111111111111111111111111111111111111111111",
+                        },
+                        {
+                            "alg": "SHA-1",
+                            "content": "2222222222222222222222222222222222222222",
+                        },
+                    ]
+                },
                 {
                     "type": "source-distribution",
                     "url": "https://mirror.example.org/foo/foo-1.2.tar.gz",
diff --git a/utils/generate-cyclonedx b/utils/generate-cyclonedx
index 382d91ce55af..4166abd9ff04 100755
--- a/utils/generate-cyclonedx
+++ b/utils/generate-cyclonedx
@@ -325,6 +325,7 @@ def cyclonedx_external_refs(comp):
         dict: External reference information in CycloneDX format, or empty dict
     """
     SOURCE_DIST_SCHEMES = {"http", "https"}
+    VCS_SCHEMES = {"git", "svn", "cvs", "hg", "bzr"}
 
     refs = []
     for download in comp.get("downloads", []):
@@ -336,6 +337,13 @@ def cyclonedx_external_refs(comp):
                     "url": f"{uri}/{source}",
                     **cyclonedx_source_hashes(comp, source),
                 })
+            elif set(schemes) & VCS_SCHEMES:
+                refs.append({
+                    "type": "vcs",
+                    "url": uri,
+                    "comment": f"{schemes[0]} repository",
+                    **cyclonedx_source_hashes(comp, source),
+                })
     if refs:
         return {"externalReferences": refs}
     return {}
-- 
2.43.0

_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [Buildroot] [PATCH v4 1/6] support/testing/utils: add basic tests for utils/generate-cyclonedx
  2026-04-09  8:13 ` [Buildroot] [PATCH v4 1/6] support/testing/utils: add basic tests for utils/generate-cyclonedx Martin Willi
@ 2026-04-09  8:34   ` Thomas Perale via buildroot
  0 siblings, 0 replies; 10+ messages in thread
From: Thomas Perale via buildroot @ 2026-04-09  8:34 UTC (permalink / raw)
  To: Martin Willi; +Cc: Thomas Perale, buildroot

Thanks Martin !

Acked-By: Thomas Perale <thomas.perale@mind.be>

In reply of:
> Introduce unit-tests for the generate-cyclonedx script, covering basic
> script invocation, patch CVE extraction and virtual packages.
> 
> Signed-off-by: Martin Willi <martin@strongswan.org>

> ---
>  .../tests/utils/test_generate_cyclonedx.py    | 139 ++++++++++++++++++
>  .../cve_upstream.patch                        |  11 ++
>  2 files changed, 150 insertions(+)
>  create mode 100644 support/testing/tests/utils/test_generate_cyclonedx.py
>  create mode 100644 support/testing/tests/utils/test_generate_cyclonedx/cve_upstream.patch
> 
> diff --git a/support/testing/tests/utils/test_generate_cyclonedx.py b/support/testing/tests/utils/test_generate_cyclonedx.py
> new file mode 100644
> index 000000000000..bfe5eaf054cf
> --- /dev/null
> +++ b/support/testing/tests/utils/test_generate_cyclonedx.py
> @@ -0,0 +1,139 @@
> +"""Unit tests for utils/generate-cyclonedx."""
> +
> +import json
> +import os
> +import subprocess
> +import tempfile
> +import unittest
> +from pathlib import Path
> +
> +import infra
> +
> +PATCH = "support/testing/tests/utils/test_generate_cyclonedx/cve_upstream.patch"
> +SCHEMA_LICENSES = ["MIT", "Apache-2.0", "GPL-3.0-only"]
> +
> +
> +class TestGenerateCycloneDX(unittest.TestCase):
> +    def setUp(self):
> +        # Provide a fake SPDX schema so the script never hits the network.
> +        self.schema_dir = tempfile.TemporaryDirectory()
> +        self.addCleanup(self.schema_dir.cleanup)
> +
> +        cyclonedx_dir = Path(self.schema_dir.name) / "cyclonedx"
> +        cyclonedx_dir.mkdir(parents=True)
> +        schema_path = cyclonedx_dir / "spdx-1.6.schema.json"
> +        schema_path.write_text(json.dumps({"enum": SCHEMA_LICENSES}))
> +
> +        self.env = os.environ.copy()
> +        self.env["BR2_DL_DIR"] = self.schema_dir.name
> +        self.script = infra.basepath("utils/generate-cyclonedx")
> +        self.cwd = infra.basepath()
> +
> +    def _make_show_info(self) -> dict:
> +        return {
> +            "package-foo": {
> +                "name": "foo",
> +                "version": "1.2",
> +                "type": "target",
> +                "virtual": False,
> +                "licenses": "MIT",
> +                "cpe-id": "cpe:2.3:a:example:foo:1.2:*:*:*:*:*:*:*",
> +                "patches": [PATCH],
> +                "provides": ["package-virtual"],
> +                "dependencies": ["skeleton-baz", "package-bar"],
> +                "ignore_cves": ["CVE-2025-0001"],
> +                "package_dir": "package/package-foo",
> +            },
> +            "skeleton-baz": {
> +                "name": "skeleton-baz",
> +                "version": "0.1",
> +                "type": "target",
> +                "virtual": False,
> +                "licenses": "Apache-2.0",
> +                "dependencies": [],
> +                "package_dir": "package/skeleton-baz",
> +            },
> +            "package-bar": {
> +                "name": "bar",
> +                "version": "0.2",
> +                "type": "target",
> +                "virtual": False,
> +                "licenses": "MIT",
> +                "ignore_cves": ["CVE-2025-0002"],
> +                "dependencies": ["package-virtual"],
> +                "package_dir": "package/package-bar",
> +            },
> +            "host-tool": {
> +                "name": "host-tool",
> +                "version": "0.3",
> +                "type": "host",
> +                "virtual": False,
> +                "licenses": "GPL-3.0-only",
> +                "dependencies": [],
> +                "package_dir": "package/host-tool",
> +            },
> +            "package-virtual": {
> +                "name": "virtual-provider",
> +                "virtual": True,
> +                "type": "target",
> +                "dependencies": ["package-foo"],
> +                "package_dir": "package/package-virtual",
> +            },
> +        }
> +
> +    def _run_script(self, extra_args=(), show_info=None):
> +        data = show_info if show_info is not None else self._make_show_info()
> +        completed = subprocess.run(
> +            [self.script, *extra_args],
> +            cwd=self.cwd,
> +            env=self.env,
> +            input=json.dumps(data),
> +            text=True,
> +            capture_output=True,
> +            check=True,
> +        )
> +        return json.loads(completed.stdout)
> +
> +    def _find_component(self, result: dict, name: str) -> dict:
> +        for component in result["components"]:
> +            if component["bom-ref"] == name:
> +                return component
> +        self.fail(f"component {name} missing")
> +
> +    def test_default(self):
> +        result = self._run_script()
> +
> +        self.assertEqual(len(result["components"]), 4)
> +        self.assertIn("vulnerabilities", result)
> +        vulnerabilities = {v["id"]: v for v in result["vulnerabilities"]}
> +        self.assertEqual(len(vulnerabilities), 2)
> +        self.assertEqual(vulnerabilities["CVE-2025-0001"]["analysis"]["state"], "resolved_with_pedigree")
> +        self.assertEqual(vulnerabilities["CVE-2025-0002"]["analysis"]["state"], "in_triage")
> +
> +        foo = self._find_component(result, "package-foo")
> +        patch = foo["pedigree"]["patches"][0]
> +        self.assertIn("text", patch["diff"])
> +        self.assertIn("content", patch["diff"]["text"])
> +
> +        host = self._find_component(result, "host-tool")
> +        self.assertEqual(host["properties"][0]["value"], "host")
> +
> +        names = {c["bom-ref"] for c in result["components"]}
> +        self.assertIn("skeleton-baz", names)
> +        self.assertIn("package-bar", names)
> +        self.assertNotIn("package-virtual", names)
> +
> +        foo_deps = next(d for d in result["dependencies"] if d["ref"] == "package-foo")
> +        self.assertEqual(foo_deps["dependsOn"], ["package-bar", "skeleton-baz"])
> +
> +        bar_deps = next(d for d in result["dependencies"] if d["ref"] == "package-bar")
> +        self.assertEqual(bar_deps["dependsOn"], ["package-foo"])
> +
> +    def test_virtual(self):
> +        result = self._run_script(["--virtual"])
> +
> +        names = {c["bom-ref"] for c in result["components"]}
> +        self.assertEqual(names, {"package-foo", "skeleton-baz", "host-tool", "package-virtual", "package-bar"})
> +
> +        foo_deps = next(d for d in result["dependencies"] if d["ref"] == "package-foo")
> +        self.assertEqual(foo_deps["dependsOn"], ["package-bar", "skeleton-baz"])
> diff --git a/support/testing/tests/utils/test_generate_cyclonedx/cve_upstream.patch b/support/testing/tests/utils/test_generate_cyclonedx/cve_upstream.patch
> new file mode 100644
> index 000000000000..f18e51ebb9ec
> --- /dev/null
> +++ b/support/testing/tests/utils/test_generate_cyclonedx/cve_upstream.patch
> @@ -0,0 +1,11 @@
> +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
> +CVE: CVE-2025-0001
> +Upstream: https://patches.example/foo.patch
> +
> +diff --git a/foo.txt b/foo.txt
> +index 0000001..0000002 100644
> +--- a/foo.txt
> ++++ b/foo.txt
> +@@
> +-foo
> ++bar
> -- 
> 2.43.0
> 
> _______________________________________________
> buildroot mailing list
> buildroot@buildroot.org
> https://lists.buildroot.org/mailman/listinfo/buildroot
_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [Buildroot] [PATCH v4 3/6] utils/generate-cyclonedx: generate externalReferences with source-distribution
  2026-04-09  8:13 ` [Buildroot] [PATCH v4 3/6] utils/generate-cyclonedx: generate externalReferences with source-distribution Martin Willi
@ 2026-04-09  8:43   ` Thomas Perale via buildroot
  0 siblings, 0 replies; 10+ messages in thread
From: Thomas Perale via buildroot @ 2026-04-09  8:43 UTC (permalink / raw)
  To: Martin Willi; +Cc: Thomas Perale, buildroot

Thanks !

Acked-by: Thomas Perale <thomas.perale@mind.be>

In reply of:
> BSI TR-03183-2 5.4.2 [1] lists source code URIs under "Additional data fields
> for each component", and as such "MUST additionally be provided, if it exists".
> 
> If a http or https source download URI is available from show-info, extract
> it and include it as an externalReference of type "source-distribution" in the
> CycloneDX output.
> 
> [1] https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/TechGuidelines/TR03183/BSI-TR-03183-2_v2_1_0.pdf?__blob=publicationFile&v=5
> 
> Signed-off-by: Martin Willi <martin@strongswan.org>

> ---
>  .../tests/utils/test_generate_cyclonedx.py    | 26 ++++++++++
>  utils/generate-cyclonedx                      | 47 +++++++++++++++++++
>  2 files changed, 73 insertions(+)
> 
> diff --git a/support/testing/tests/utils/test_generate_cyclonedx.py b/support/testing/tests/utils/test_generate_cyclonedx.py
> index bf1b8e099bf9..a071ff867923 100644
> --- a/support/testing/tests/utils/test_generate_cyclonedx.py
> +++ b/support/testing/tests/utils/test_generate_cyclonedx.py
> @@ -140,3 +140,29 @@ class TestGenerateCycloneDX(unittest.TestCase):
>  
>          foo_deps = next(d for d in result["dependencies"] if d["ref"] == "package-foo")
>          self.assertEqual(foo_deps["dependsOn"], ["package-bar", "skeleton-baz"])
> +
> +    def test_external_references(self):
> +        info = self._make_show_info()
> +        info["package-foo"]["downloads"] = [
> +            {
> +                "source": "foo-1.2.tar.gz",
> +                "uris": [
> +                    "https+https://sources.buildroot.net/foo",
> +                    "http|https+https://mirror.example.org/foo",
> +                ],
> +            },
> +        ]
> +
> +        result = self._run_script(show_info=info)
> +        foo = self._find_component(result, "package-foo")
> +
> +        self.assertIn("externalReferences", foo)
> +        self.assertEqual(
> +            foo["externalReferences"],
> +            [
> +                {
> +                    "type": "source-distribution",
> +                    "url": "https://mirror.example.org/foo/foo-1.2.tar.gz",
> +                },
> +            ],
> +        )
> diff --git a/utils/generate-cyclonedx b/utils/generate-cyclonedx
> index f4d5afd847e5..a3b7293f9a5e 100755
> --- a/utils/generate-cyclonedx
> +++ b/utils/generate-cyclonedx
> @@ -14,6 +14,8 @@ import gzip
>  import json
>  import os
>  from pathlib import Path
> +from typing import Iterator
> +import urllib.parse
>  import urllib.request
>  import subprocess
>  import sys
> @@ -261,6 +263,50 @@ def cyclonedx_patches(patch_list: list[str]):
>      }
>  
>  
> +def parse_uris(uris: list[str]) -> Iterator[tuple[list[str], str]]:
> +    """Parse download URIs into (schemes, url) tuples.
> +
> +    Splits the Buildroot URI format "scheme[|scheme]+url" and yields all
> +    Buildroot schemes with the stripped URL, excluding
> +    sources.buildroot.net mirrors.
> +
> +    Args:
> +        uris (list): Array of URI strings from the show-info output.
> +    Yields:
> +        tuple[list[str], str]: (schemes, url) for each usable URI.
> +    """
> +    for uri in uris:
> +        scheme, _, stripped_uri = uri.partition("+")
> +        if stripped_uri:
> +            parsed = urllib.parse.urlparse(stripped_uri)
> +            if parsed.hostname != "sources.buildroot.net":
> +                yield scheme.split("|"), stripped_uri
> +
> +
> +def cyclonedx_external_refs(comp):
> +    """Create CycloneDX external references for a component.
> +
> +    Args:
> +        comp (dict): The component information from the show-info output.
> +    Returns:
> +        dict: External reference information in CycloneDX format, or empty dict
> +    """
> +    SOURCE_DIST_SCHEMES = {"http", "https"}
> +
> +    refs = []
> +    for download in comp.get("downloads", []):
> +        source = download.get("source")
> +        for schemes, uri in parse_uris(download.get("uris", [])):
> +            if set(schemes) & SOURCE_DIST_SCHEMES and source:
> +                refs.append({
> +                    "type": "source-distribution",
> +                    "url": f"{uri}/{source}",
> +                })
> +    if refs:
> +        return {"externalReferences": refs}
> +    return {}
> +
> +
>  def cyclonedx_component(name, comp):
>      """Translate a component from the show-info output, to a component entry in CycloneDX format.
>  
> @@ -284,6 +330,7 @@ def cyclonedx_component(name, comp):
>          **({
>              "cpe": comp["cpe-id"],
>          } if "cpe-id" in comp else {}),
> +        **cyclonedx_external_refs(comp),
>          **(cyclonedx_patches(comp["patches"]) if comp.get("patches") else {}),
>          "properties": [{
>              "name": "BR_TYPE",
> -- 
> 2.43.0
> 
> _______________________________________________
> buildroot mailing list
> buildroot@buildroot.org
> https://lists.buildroot.org/mailman/listinfo/buildroot
_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [Buildroot] [PATCH v4 6/6] utils/generate-cyclonedx: generate vcs externalReferences for source repos
  2026-04-09  8:14 ` [Buildroot] [PATCH v4 6/6] utils/generate-cyclonedx: generate vcs externalReferences for source repos Martin Willi
@ 2026-04-09  8:49   ` Thomas Perale via buildroot
  0 siblings, 0 replies; 10+ messages in thread
From: Thomas Perale via buildroot @ 2026-04-09  8:49 UTC (permalink / raw)
  To: Martin Willi; +Cc: Thomas Perale, buildroot

Acked-By: Thomas Perale <thomas.perale@mind.be>

In reply of:
> Some packages do not have a http/https download URL for a source tarball,
> but are acquired over a version control system like git. If so, add
> externalReferences of type "vcs" for such URLs.
> 
> As most git repositories use a https:// transport that may not indicated the
> repository type, add a "comment" due to the lack of a better mechanism in
> CycloneDX.
> 
> While the hashes are calculated over a tarball created locally, it still may
> be useful, so add them for "vcs" externalReferences as well.
> 
> Signed-off-by: Martin Willi <martin@strongswan.org>

> ---
>  .../tests/utils/test_generate_cyclonedx.py    | 30 ++++++++++++++++++-
>  utils/generate-cyclonedx                      |  8 +++++
>  2 files changed, 37 insertions(+), 1 deletion(-)
> 
> diff --git a/support/testing/tests/utils/test_generate_cyclonedx.py b/support/testing/tests/utils/test_generate_cyclonedx.py
> index 84f94f050760..77690b1b98bc 100644
> --- a/support/testing/tests/utils/test_generate_cyclonedx.py
> +++ b/support/testing/tests/utils/test_generate_cyclonedx.py
> @@ -147,6 +147,8 @@ class TestGenerateCycloneDX(unittest.TestCase):
>              {
>                  "source": "foo-1.2.tar.gz",
>                  "uris": [
> +                    "git+git://git.example.org/foo",
> +                    "svn+https://svn.example.org/foo",
>                      "https+https://sources.buildroot.net/foo",
>                      "http|https+https://mirror.example.org/foo",
>                  ],
> @@ -160,10 +162,20 @@ class TestGenerateCycloneDX(unittest.TestCase):
>          self.assertEqual(
>              foo["externalReferences"],
>              [
> +                {
> +                    "type": "vcs",
> +                    "url": "git://git.example.org/foo",
> +                    "comment": "git repository",
> +                },
> +                {
> +                    "type": "vcs",
> +                    "url": "https://svn.example.org/foo",
> +                    "comment": "svn repository",
> +                },
>                  {
>                      "type": "source-distribution",
>                      "url": "https://mirror.example.org/foo/foo-1.2.tar.gz",
> -                },
> +                }
>              ],
>          )
>  
> @@ -183,6 +195,7 @@ class TestGenerateCycloneDX(unittest.TestCase):
>                  {
>                      "source": "foo-1.2.tar.gz",
>                      "uris": [
> +                        "git+git://git.example.org/foo",
>                          "http|https+https://mirror.example.org/foo",
>                      ],
>                  },
> @@ -194,6 +207,21 @@ class TestGenerateCycloneDX(unittest.TestCase):
>          self.assertEqual(
>              foo["externalReferences"],
>              [
> +                {
> +                    "type": "vcs",
> +                    "url": "git://git.example.org/foo",
> +                    "comment": "git repository",
> +                    "hashes": [
> +                        {
> +                            "alg": "SHA-256",
> +                            "content": "1111111111111111111111111111111111111111111111111111111111111111",
> +                        },
> +                        {
> +                            "alg": "SHA-1",
> +                            "content": "2222222222222222222222222222222222222222",
> +                        },
> +                    ]
> +                },
>                  {
>                      "type": "source-distribution",
>                      "url": "https://mirror.example.org/foo/foo-1.2.tar.gz",
> diff --git a/utils/generate-cyclonedx b/utils/generate-cyclonedx
> index 382d91ce55af..4166abd9ff04 100755
> --- a/utils/generate-cyclonedx
> +++ b/utils/generate-cyclonedx
> @@ -325,6 +325,7 @@ def cyclonedx_external_refs(comp):
>          dict: External reference information in CycloneDX format, or empty dict
>      """
>      SOURCE_DIST_SCHEMES = {"http", "https"}
> +    VCS_SCHEMES = {"git", "svn", "cvs", "hg", "bzr"}
>  
>      refs = []
>      for download in comp.get("downloads", []):
> @@ -336,6 +337,13 @@ def cyclonedx_external_refs(comp):
>                      "url": f"{uri}/{source}",
>                      **cyclonedx_source_hashes(comp, source),
>                  })
> +            elif set(schemes) & VCS_SCHEMES:
> +                refs.append({
> +                    "type": "vcs",
> +                    "url": uri,
> +                    "comment": f"{schemes[0]} repository",
> +                    **cyclonedx_source_hashes(comp, source),
> +                })
>      if refs:
>          return {"externalReferences": refs}
>      return {}
> -- 
> 2.43.0
> 
> _______________________________________________
> buildroot mailing list
> buildroot@buildroot.org
> https://lists.buildroot.org/mailman/listinfo/buildroot
_______________________________________________
buildroot mailing list
buildroot@buildroot.org
https://lists.buildroot.org/mailman/listinfo/buildroot

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2026-04-09  8:49 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-09  8:13 [Buildroot] [PATCH v4 0/6] Extend CycloneDX metadata Martin Willi
2026-04-09  8:13 ` [Buildroot] [PATCH v4 1/6] support/testing/utils: add basic tests for utils/generate-cyclonedx Martin Willi
2026-04-09  8:34   ` Thomas Perale via buildroot
2026-04-09  8:13 ` [Buildroot] [PATCH v4 2/6] utils/generate-cyclonedx: remove indirect dependencies from root component Martin Willi
2026-04-09  8:13 ` [Buildroot] [PATCH v4 3/6] utils/generate-cyclonedx: generate externalReferences with source-distribution Martin Willi
2026-04-09  8:43   ` Thomas Perale via buildroot
2026-04-09  8:13 ` [Buildroot] [PATCH v4 4/6] package/pkg-utils: add 'hashes' to show-info Martin Willi
2026-04-09  8:14 ` [Buildroot] [PATCH v4 5/6] utils/generate-cyclonedx: add hashes from .hash files to externalReferences Martin Willi
2026-04-09  8:14 ` [Buildroot] [PATCH v4 6/6] utils/generate-cyclonedx: generate vcs externalReferences for source repos Martin Willi
2026-04-09  8:49   ` Thomas Perale via buildroot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox