summaryrefslogtreecommitdiffstats
path: root/meta/lib/oe
diff options
context:
space:
mode:
authorJoshua Watt <JPEWhacker@gmail.com>2024-12-10 10:33:07 -0700
committerRichard Purdie <richard.purdie@linuxfoundation.org>2024-12-12 12:55:56 +0000
commit102743c4dfe2ceedd4c0663ee6f6b14bb1f9f745 (patch)
treea21be43869aafb1f243ce3d8434705df43c16aa3 /meta/lib/oe
parent9b80c039add2bdd7ed70774a4c379030df6538a5 (diff)
downloadpoky-102743c4dfe2ceedd4c0663ee6f6b14bb1f9f745.tar.gz
spdx 3.0: Rework how SPDX aliases are linked
The SPDX code needs to be able to look up an Element by its SPDX ID, locating the file that (should) contain the SPDX ID and opening it for parsing. Previously, the code would do this be hashing each Element SPDX ID and Alias, and the creating a symbolic link to the file that contains the element with a name of the hash. This worked well as it was possible to look up any arbitrary SPDX ID or alias by simply hashing it and following the symbolic link to get the file. However, the down side of this approach is that it creates a lot of symbolic links, since it will make one or two per Element in the document. This can be a problem when using SPDX_INCLUDE_SOURCES, for example. This change reworks this strategy so that the only Element that gets a symbolic link based on the hash is the singular SpdxDocument that is create for each file. All other Elements are assigned an alias with a special prefix that encodes the hash of SpdxDocument alias. Thus, when attempting to look up an arbitrary alias, the code sees the special prefix, extract the hash, opens the file based on the symlink with that hash name, then finds the matching Element in the file. This drastically reduces the number of symbolic links by making only one per file. This also means that the custom link extension can be removed since it is now superfluous. (From OE-Core rev: 838d64c09657ac53175737fc4e7fd6f01f3dcf47) Signed-off-by: Joshua Watt <JPEWhacker@gmail.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'meta/lib/oe')
-rw-r--r--meta/lib/oe/sbom30.py159
-rw-r--r--meta/lib/oe/spdx30_tasks.py60
2 files changed, 97 insertions, 122 deletions
diff --git a/meta/lib/oe/sbom30.py b/meta/lib/oe/sbom30.py
index 9a3b188dbb..29cb9e45ad 100644
--- a/meta/lib/oe/sbom30.py
+++ b/meta/lib/oe/sbom30.py
@@ -21,45 +21,8 @@ VEX_VERSION = "1.0.0"
21 21
22SPDX_BUILD_TYPE = "http://openembedded.org/bitbake" 22SPDX_BUILD_TYPE = "http://openembedded.org/bitbake"
23 23
24 24OE_ALIAS_PREFIX = "http://spdxdocs.org/openembedded-alias/by-doc-hash/"
25@oe.spdx30.register(OE_SPDX_BASE + "link-extension") 25OE_DOC_ALIAS_PREFIX = "http://spdxdocs.org/openembedded-alias/doc/"
26class OELinkExtension(oe.spdx30.extension_Extension):
27 """
28 This custom extension controls if an Element creates a symlink based on
29 its SPDX ID in the deploy directory. Some elements may not be able to be
30 linked because they are duplicated in multiple documents (e.g. the bitbake
31 Build Element). Those elements can add this extension and set link_spdx_id
32 to False
33
34 It is in internal extension that should be removed when writing out a final
35 SBoM
36 """
37
38 CLOSED = True
39 INTERNAL = True
40
41 @classmethod
42 def _register_props(cls):
43 super()._register_props()
44 cls._add_property(
45 "link_spdx_id",
46 oe.spdx30.BooleanProp(),
47 OE_SPDX_BASE + "link-spdx-id",
48 min_count=1,
49 max_count=1,
50 )
51
52 # The symlinks written to the deploy directory are based on the hash of
53 # the SPDX ID. While this makes it easy to look them up, it can be
54 # difficult to trace a Element to the hashed symlink name. As a
55 # debugging aid, this property is set to the basename of the symlink
56 # when the symlink is created to make it easier to trace
57 cls._add_property(
58 "link_name",
59 oe.spdx30.StringProp(),
60 OE_SPDX_BASE + "link-name",
61 max_count=1,
62 )
63 26
64 27
65@oe.spdx30.register(OE_SPDX_BASE + "id-alias") 28@oe.spdx30.register(OE_SPDX_BASE + "id-alias")
@@ -185,18 +148,6 @@ def get_element_link_id(e):
185 return e._id 148 return e._id
186 149
187 150
188def set_alias(obj, alias):
189 for ext in obj.extension:
190 if not isinstance(ext, OEIdAliasExtension):
191 continue
192 ext.alias = alias
193 return ext
194
195 ext = OEIdAliasExtension(alias=alias)
196 obj.extension.append(ext)
197 return ext
198
199
200def get_alias(obj): 151def get_alias(obj):
201 for ext in obj.extension: 152 for ext in obj.extension:
202 if not isinstance(ext, OEIdAliasExtension): 153 if not isinstance(ext, OEIdAliasExtension):
@@ -206,6 +157,10 @@ def get_alias(obj):
206 return None 157 return None
207 158
208 159
160def hash_id(_id):
161 return hashlib.sha256(_id.encode("utf-8")).hexdigest()
162
163
209def to_list(l): 164def to_list(l):
210 if isinstance(l, set): 165 if isinstance(l, set):
211 l = sorted(list(l)) 166 l = sorted(list(l))
@@ -220,6 +175,7 @@ class ObjectSet(oe.spdx30.SHACLObjectSet):
220 def __init__(self, d): 175 def __init__(self, d):
221 super().__init__() 176 super().__init__()
222 self.d = d 177 self.d = d
178 self.alias_prefix = None
223 179
224 def create_index(self): 180 def create_index(self):
225 self.by_sha256_hash = {} 181 self.by_sha256_hash = {}
@@ -230,11 +186,10 @@ class ObjectSet(oe.spdx30.SHACLObjectSet):
230 if isinstance(obj, oe.spdx30.Element): 186 if isinstance(obj, oe.spdx30.Element):
231 if not obj._id: 187 if not obj._id:
232 raise ValueError("Element missing ID") 188 raise ValueError("Element missing ID")
233 for ext in obj.extension: 189
234 if not isinstance(ext, OEIdAliasExtension): 190 alias_ext = get_alias(obj)
235 continue 191 if alias_ext is not None and alias_ext.alias:
236 if ext.alias: 192 self.obj_by_id[alias_ext.alias] = obj
237 self.obj_by_id[ext.alias] = obj
238 193
239 for v in obj.verifiedUsing: 194 for v in obj.verifiedUsing:
240 if not isinstance(v, oe.spdx30.Hash): 195 if not isinstance(v, oe.spdx30.Hash):
@@ -248,6 +203,9 @@ class ObjectSet(oe.spdx30.SHACLObjectSet):
248 super().add_index(obj) 203 super().add_index(obj)
249 if isinstance(obj, oe.spdx30.SpdxDocument): 204 if isinstance(obj, oe.spdx30.SpdxDocument):
250 self.doc = obj 205 self.doc = obj
206 alias_ext = get_alias(obj)
207 if alias_ext is not None and alias_ext.alias:
208 self.alias_prefix = OE_ALIAS_PREFIX + hash_id(alias_ext.alias) + "/"
251 209
252 def __filter_obj(self, obj, attr_filter): 210 def __filter_obj(self, obj, attr_filter):
253 return all(getattr(obj, k) == v for k, v in attr_filter.items()) 211 return all(getattr(obj, k) == v for k, v in attr_filter.items())
@@ -307,6 +265,21 @@ class ObjectSet(oe.spdx30.SHACLObjectSet):
307 for o in self.foreach_type(oe.spdx30.Element): 265 for o in self.foreach_type(oe.spdx30.Element):
308 self.set_element_alias(o) 266 self.set_element_alias(o)
309 267
268 def new_alias_id(self, obj, replace):
269 unihash = self.d.getVar("BB_UNIHASH")
270 namespace = self.get_namespace() + "/"
271 if unihash not in obj._id:
272 bb.warn(f"Unihash {unihash} not found in {obj._id}")
273 return None
274
275 if namespace not in obj._id:
276 bb.warn(f"Namespace {namespace} not found in {obj._id}")
277 return None
278
279 return obj._id.replace(unihash, "UNIHASH").replace(
280 namespace, replace + self.d.getVar("PN")
281 )
282
310 def remove_internal_extensions(self): 283 def remove_internal_extensions(self):
311 def remove(o): 284 def remove(o):
312 o.extension = [e for e in o.extension if not getattr(e, "INTERNAL", False)] 285 o.extension = [e for e in o.extension if not getattr(e, "INTERNAL", False)]
@@ -334,21 +307,17 @@ class ObjectSet(oe.spdx30.SHACLObjectSet):
334 307
335 alias_ext = get_alias(e) 308 alias_ext = get_alias(e)
336 if alias_ext is None: 309 if alias_ext is None:
337 unihash = self.d.getVar("BB_UNIHASH") 310 alias_id = self.new_alias_id(e, self.alias_prefix)
338 namespace = self.get_namespace() 311 if alias_id is not None:
339 if unihash not in e._id: 312 e.extension.append(OEIdAliasExtension(alias=alias_id))
340 bb.warn(f"Unihash {unihash} not found in {e._id}") 313 elif (
341 elif namespace not in e._id: 314 alias_ext.alias
342 bb.warn(f"Namespace {namespace} not found in {e._id}") 315 and not isinstance(e, oe.spdx30.SpdxDocument)
343 else: 316 and not alias_ext.alias.startswith(self.alias_prefix)
344 alias_ext = set_alias( 317 ):
345 e, 318 bb.warn(
346 e._id.replace(unihash, "UNIHASH").replace( 319 f"Element {e._id} has alias {alias_ext.alias}, but it should have prefix {self.alias_prefix}"
347 namespace, 320 )
348 "http://spdx.org/spdxdocs/openembedded-alias/"
349 + self.d.getVar("PN"),
350 ),
351 )
352 321
353 def new_spdxid(self, *suffix, include_unihash=True): 322 def new_spdxid(self, *suffix, include_unihash=True):
354 items = [self.get_namespace()] 323 items = [self.get_namespace()]
@@ -812,9 +781,17 @@ class ObjectSet(oe.spdx30.SHACLObjectSet):
812 _id=objset.new_spdxid("document", name), 781 _id=objset.new_spdxid("document", name),
813 name=name, 782 name=name,
814 ) 783 )
815 document.extension.append(OEIdAliasExtension()) 784
816 document.extension.append(OELinkExtension(link_spdx_id=False)) 785 document.extension.append(
786 OEIdAliasExtension(
787 alias=objset.new_alias_id(
788 document,
789 OE_DOC_ALIAS_PREFIX + d.getVar("PN") + "/" + name + "/",
790 ),
791 )
792 )
817 objset.doc = document 793 objset.doc = document
794 objset.add_index(document)
818 795
819 if copy_from_bitbake_doc: 796 if copy_from_bitbake_doc:
820 bb_objset = objset.import_bitbake_build_objset() 797 bb_objset = objset.import_bitbake_build_objset()
@@ -907,9 +884,7 @@ def jsonld_arch_path(d, arch, subdir, name, deploydir=None):
907 return deploydir / arch / subdir / (name + ".spdx.json") 884 return deploydir / arch / subdir / (name + ".spdx.json")
908 885
909 886
910def jsonld_hash_path(_id): 887def jsonld_hash_path(h):
911 h = hashlib.sha256(_id.encode("utf-8")).hexdigest()
912
913 return Path("by-spdxid-hash") / h[:2], h 888 return Path("by-spdxid-hash") / h[:2], h
914 889
915 890
@@ -981,7 +956,7 @@ def write_recipe_jsonld_doc(
981 dest = jsonld_arch_path(d, pkg_arch, subdir, objset.doc.name, deploydir=deploydir) 956 dest = jsonld_arch_path(d, pkg_arch, subdir, objset.doc.name, deploydir=deploydir)
982 957
983 def link_id(_id): 958 def link_id(_id):
984 hash_path = jsonld_hash_path(_id) 959 hash_path = jsonld_hash_path(hash_id(_id))
985 960
986 link_name = jsonld_arch_path( 961 link_name = jsonld_arch_path(
987 d, 962 d,
@@ -1005,28 +980,9 @@ def write_recipe_jsonld_doc(
1005 980
1006 try: 981 try:
1007 if create_spdx_id_links: 982 if create_spdx_id_links:
1008 for o in objset.foreach_type(oe.spdx30.Element): 983 alias_ext = get_alias(objset.doc)
1009 if not o._id or o._id.startswith("_:"): 984 if alias_ext is not None and alias_ext.alias:
1010 continue 985 alias_ext.link_name = link_id(alias_ext.alias)
1011
1012 ext = None
1013 for e in o.extension:
1014 if not isinstance(e, OELinkExtension):
1015 continue
1016
1017 ext = e
1018 break
1019
1020 if ext is None:
1021 ext = OELinkExtension(link_spdx_id=True)
1022 o.extension.append(ext)
1023
1024 if ext.link_spdx_id:
1025 ext.link_name = link_id(o._id)
1026
1027 alias_ext = get_alias(o)
1028 if alias_ext is not None and alias_ext.alias:
1029 alias_ext.link_name = link_id(alias_ext.alias)
1030 986
1031 finally: 987 finally:
1032 # It is really helpful for debugging if the JSON document is written 988 # It is really helpful for debugging if the JSON document is written
@@ -1055,7 +1011,10 @@ def load_obj_in_jsonld(d, arch, subdir, fn_name, obj_type, **attr_filter):
1055 1011
1056 1012
1057def find_by_spdxid(d, spdxid, *, required=False): 1013def find_by_spdxid(d, spdxid, *, required=False):
1058 return find_jsonld(d, *jsonld_hash_path(spdxid), required=required) 1014 if spdxid.startswith(OE_ALIAS_PREFIX):
1015 h = spdxid[len(OE_ALIAS_PREFIX) :].split("/", 1)[0]
1016 return find_jsonld(d, *jsonld_hash_path(h), required=required)
1017 return find_jsonld(d, *jsonld_hash_path(hash_id(spdxid)), required=required)
1059 1018
1060 1019
1061def create_sbom(d, name, root_elements, add_objectsets=[]): 1020def create_sbom(d, name, root_elements, add_objectsets=[]):
diff --git a/meta/lib/oe/spdx30_tasks.py b/meta/lib/oe/spdx30_tasks.py
index 3d7035909f..036c58bf4b 100644
--- a/meta/lib/oe/spdx30_tasks.py
+++ b/meta/lib/oe/spdx30_tasks.py
@@ -56,6 +56,7 @@ def add_license_expression(d, objset, license_expression, license_data):
56 name=name, 56 name=name,
57 ) 57 )
58 ) 58 )
59 objset.set_element_alias(lic)
59 simple_license_text[name] = lic 60 simple_license_text[name] = lic
60 61
61 if name == "PD": 62 if name == "PD":
@@ -106,7 +107,9 @@ def add_license_expression(d, objset, license_expression, license_data):
106 107
107 spdx_license = "LicenseRef-" + l 108 spdx_license = "LicenseRef-" + l
108 if spdx_license not in license_text_map: 109 if spdx_license not in license_text_map:
109 license_text_map[spdx_license] = add_license_text(l)._id 110 license_text_map[spdx_license] = oe.sbom30.get_element_link_id(
111 add_license_text(l)
112 )
110 113
111 return spdx_license 114 return spdx_license
112 115
@@ -277,7 +280,7 @@ def collect_dep_objsets(d, build):
277 for dep in deps: 280 for dep in deps:
278 bb.debug(1, "Fetching SPDX for dependency %s" % (dep.pn)) 281 bb.debug(1, "Fetching SPDX for dependency %s" % (dep.pn))
279 dep_build, dep_objset = oe.sbom30.find_root_obj_in_jsonld( 282 dep_build, dep_objset = oe.sbom30.find_root_obj_in_jsonld(
280 d, "recipes", dep.pn, oe.spdx30.build_Build 283 d, "recipes", "recipe-" + dep.pn, oe.spdx30.build_Build
281 ) 284 )
282 # If the dependency is part of the taskhash, return it to be linked 285 # If the dependency is part of the taskhash, return it to be linked
283 # against. Otherwise, it cannot be linked against because this recipe 286 # against. Otherwise, it cannot be linked against because this recipe
@@ -461,7 +464,7 @@ def create_spdx(d):
461 if not include_vex in ("none", "current", "all"): 464 if not include_vex in ("none", "current", "all"):
462 bb.fatal("SPDX_INCLUDE_VEX must be one of 'none', 'current', 'all'") 465 bb.fatal("SPDX_INCLUDE_VEX must be one of 'none', 'current', 'all'")
463 466
464 build_objset = oe.sbom30.ObjectSet.new_objset(d, d.getVar("PN")) 467 build_objset = oe.sbom30.ObjectSet.new_objset(d, "recipe-" + d.getVar("PN"))
465 468
466 build = build_objset.new_task_build("recipe", "recipe") 469 build = build_objset.new_task_build("recipe", "recipe")
467 build_objset.set_element_alias(build) 470 build_objset.set_element_alias(build)
@@ -501,8 +504,11 @@ def create_spdx(d):
501 bb.debug(1, "Skipping %s since it is already fixed upstream" % cve) 504 bb.debug(1, "Skipping %s since it is already fixed upstream" % cve)
502 continue 505 continue
503 506
507 spdx_cve = build_objset.new_cve_vuln(cve)
508 build_objset.set_element_alias(spdx_cve)
509
504 cve_by_status.setdefault(decoded_status["mapping"], {})[cve] = ( 510 cve_by_status.setdefault(decoded_status["mapping"], {})[cve] = (
505 build_objset.new_cve_vuln(cve), 511 spdx_cve,
506 decoded_status["detail"], 512 decoded_status["detail"],
507 decoded_status["description"], 513 decoded_status["description"],
508 ) 514 )
@@ -574,7 +580,7 @@ def create_spdx(d):
574 580
575 bb.debug(1, "Creating SPDX for package %s" % pkg_name) 581 bb.debug(1, "Creating SPDX for package %s" % pkg_name)
576 582
577 pkg_objset = oe.sbom30.ObjectSet.new_objset(d, pkg_name) 583 pkg_objset = oe.sbom30.ObjectSet.new_objset(d, "package-" + pkg_name)
578 584
579 spdx_package = pkg_objset.add_root( 585 spdx_package = pkg_objset.add_root(
580 oe.spdx30.software_Package( 586 oe.spdx30.software_Package(
@@ -662,20 +668,21 @@ def create_spdx(d):
662 for status, cves in cve_by_status.items(): 668 for status, cves in cve_by_status.items():
663 for cve, items in cves.items(): 669 for cve, items in cves.items():
664 spdx_cve, detail, description = items 670 spdx_cve, detail, description = items
671 spdx_cve_id = oe.sbom30.get_element_link_id(spdx_cve)
665 672
666 all_cves.add(spdx_cve._id) 673 all_cves.add(spdx_cve_id)
667 674
668 if status == "Patched": 675 if status == "Patched":
669 pkg_objset.new_vex_patched_relationship( 676 pkg_objset.new_vex_patched_relationship(
670 [spdx_cve._id], [spdx_package] 677 [spdx_cve_id], [spdx_package]
671 ) 678 )
672 elif status == "Unpatched": 679 elif status == "Unpatched":
673 pkg_objset.new_vex_unpatched_relationship( 680 pkg_objset.new_vex_unpatched_relationship(
674 [spdx_cve._id], [spdx_package] 681 [spdx_cve_id], [spdx_package]
675 ) 682 )
676 elif status == "Ignored": 683 elif status == "Ignored":
677 spdx_vex = pkg_objset.new_vex_ignored_relationship( 684 spdx_vex = pkg_objset.new_vex_ignored_relationship(
678 [spdx_cve._id], 685 [spdx_cve_id],
679 [spdx_package], 686 [spdx_package],
680 impact_statement=description, 687 impact_statement=description,
681 ) 688 )
@@ -810,7 +817,7 @@ def create_package_spdx(d):
810 d, 817 d,
811 pkg_arch, 818 pkg_arch,
812 "packages-staging", 819 "packages-staging",
813 pkg_name, 820 "package-" + pkg_name,
814 oe.spdx30.software_Package, 821 oe.spdx30.software_Package,
815 software_primaryPurpose=oe.spdx30.software_SoftwarePurpose.install, 822 software_primaryPurpose=oe.spdx30.software_SoftwarePurpose.install,
816 ) 823 )
@@ -849,7 +856,7 @@ def create_package_spdx(d):
849 dep_spdx_package, _ = oe.sbom30.find_root_obj_in_jsonld( 856 dep_spdx_package, _ = oe.sbom30.find_root_obj_in_jsonld(
850 d, 857 d,
851 "packages-staging", 858 "packages-staging",
852 dep_pkg, 859 "package-" + dep_pkg,
853 oe.spdx30.software_Package, 860 oe.spdx30.software_Package,
854 software_primaryPurpose=oe.spdx30.software_SoftwarePurpose.install, 861 software_primaryPurpose=oe.spdx30.software_SoftwarePurpose.install,
855 ) 862 )
@@ -949,13 +956,14 @@ def write_bitbake_spdx(d):
949 ) 956 )
950 957
951 for obj in objset.foreach_type(oe.spdx30.Element): 958 for obj in objset.foreach_type(oe.spdx30.Element):
952 obj.extension.append(oe.sbom30.OELinkExtension(link_spdx_id=False))
953 obj.extension.append(oe.sbom30.OEIdAliasExtension()) 959 obj.extension.append(oe.sbom30.OEIdAliasExtension())
954 960
955 oe.sbom30.write_jsonld_doc(d, objset, deploy_dir_spdx / "bitbake.spdx.json") 961 oe.sbom30.write_jsonld_doc(d, objset, deploy_dir_spdx / "bitbake.spdx.json")
956 962
957 963
958def collect_build_package_inputs(d, objset, build, packages): 964def collect_build_package_inputs(d, objset, build, packages):
965 import oe.sbom30
966
959 providers = oe.spdx_common.collect_package_providers(d) 967 providers = oe.spdx_common.collect_package_providers(d)
960 968
961 build_deps = set() 969 build_deps = set()
@@ -972,11 +980,11 @@ def collect_build_package_inputs(d, objset, build, packages):
972 pkg_spdx, _ = oe.sbom30.find_root_obj_in_jsonld( 980 pkg_spdx, _ = oe.sbom30.find_root_obj_in_jsonld(
973 d, 981 d,
974 "packages", 982 "packages",
975 pkg_name, 983 "package-" + pkg_name,
976 oe.spdx30.software_Package, 984 oe.spdx30.software_Package,
977 software_primaryPurpose=oe.spdx30.software_SoftwarePurpose.install, 985 software_primaryPurpose=oe.spdx30.software_SoftwarePurpose.install,
978 ) 986 )
979 build_deps.add(pkg_spdx._id) 987 build_deps.add(oe.sbom30.get_element_link_id(pkg_spdx))
980 988
981 if missing_providers: 989 if missing_providers:
982 bb.fatal( 990 bb.fatal(
@@ -1002,7 +1010,9 @@ def create_rootfs_spdx(d):
1002 with root_packages_file.open("r") as f: 1010 with root_packages_file.open("r") as f:
1003 packages = json.load(f) 1011 packages = json.load(f)
1004 1012
1005 objset = oe.sbom30.ObjectSet.new_objset(d, "%s-%s" % (image_basename, machine)) 1013 objset = oe.sbom30.ObjectSet.new_objset(
1014 d, "%s-%s-rootfs" % (image_basename, machine)
1015 )
1006 1016
1007 rootfs = objset.add_root( 1017 rootfs = objset.add_root(
1008 oe.spdx30.software_Package( 1018 oe.spdx30.software_Package(
@@ -1030,6 +1040,8 @@ def create_rootfs_spdx(d):
1030 1040
1031 1041
1032def create_image_spdx(d): 1042def create_image_spdx(d):
1043 import oe.sbom30
1044
1033 image_deploy_dir = Path(d.getVar("IMGDEPLOYDIR")) 1045 image_deploy_dir = Path(d.getVar("IMGDEPLOYDIR"))
1034 manifest_path = Path(d.getVar("IMAGE_OUTPUT_MANIFEST")) 1046 manifest_path = Path(d.getVar("IMAGE_OUTPUT_MANIFEST"))
1035 spdx_work_dir = Path(d.getVar("SPDXIMAGEWORK")) 1047 spdx_work_dir = Path(d.getVar("SPDXIMAGEWORK"))
@@ -1037,7 +1049,9 @@ def create_image_spdx(d):
1037 image_basename = d.getVar("IMAGE_BASENAME") 1049 image_basename = d.getVar("IMAGE_BASENAME")
1038 machine = d.getVar("MACHINE") 1050 machine = d.getVar("MACHINE")
1039 1051
1040 objset = oe.sbom30.ObjectSet.new_objset(d, "%s-%s" % (image_basename, machine)) 1052 objset = oe.sbom30.ObjectSet.new_objset(
1053 d, "%s-%s-image" % (image_basename, machine)
1054 )
1041 1055
1042 with manifest_path.open("r") as f: 1056 with manifest_path.open("r") as f:
1043 manifest = json.load(f) 1057 manifest = json.load(f)
@@ -1090,7 +1104,7 @@ def create_image_spdx(d):
1090 rootfs_image, _ = oe.sbom30.find_root_obj_in_jsonld( 1104 rootfs_image, _ = oe.sbom30.find_root_obj_in_jsonld(
1091 d, 1105 d,
1092 "rootfs", 1106 "rootfs",
1093 "%s-%s" % (image_basename, machine), 1107 "%s-%s-rootfs" % (image_basename, machine),
1094 oe.spdx30.software_Package, 1108 oe.spdx30.software_Package,
1095 # TODO: Should use a purpose to filter here? 1109 # TODO: Should use a purpose to filter here?
1096 ) 1110 )
@@ -1098,7 +1112,7 @@ def create_image_spdx(d):
1098 builds, 1112 builds,
1099 oe.spdx30.RelationshipType.hasInput, 1113 oe.spdx30.RelationshipType.hasInput,
1100 oe.spdx30.LifecycleScopeType.build, 1114 oe.spdx30.LifecycleScopeType.build,
1101 [rootfs_image._id], 1115 [oe.sbom30.get_element_link_id(rootfs_image)],
1102 ) 1116 )
1103 1117
1104 objset.add_aliases() 1118 objset.add_aliases()
@@ -1107,6 +1121,8 @@ def create_image_spdx(d):
1107 1121
1108 1122
1109def create_image_sbom_spdx(d): 1123def create_image_sbom_spdx(d):
1124 import oe.sbom30
1125
1110 image_name = d.getVar("IMAGE_NAME") 1126 image_name = d.getVar("IMAGE_NAME")
1111 image_basename = d.getVar("IMAGE_BASENAME") 1127 image_basename = d.getVar("IMAGE_BASENAME")
1112 image_link_name = d.getVar("IMAGE_LINK_NAME") 1128 image_link_name = d.getVar("IMAGE_LINK_NAME")
@@ -1121,17 +1137,17 @@ def create_image_sbom_spdx(d):
1121 rootfs_image, _ = oe.sbom30.find_root_obj_in_jsonld( 1137 rootfs_image, _ = oe.sbom30.find_root_obj_in_jsonld(
1122 d, 1138 d,
1123 "rootfs", 1139 "rootfs",
1124 "%s-%s" % (image_basename, machine), 1140 "%s-%s-rootfs" % (image_basename, machine),
1125 oe.spdx30.software_Package, 1141 oe.spdx30.software_Package,
1126 # TODO: Should use a purpose here? 1142 # TODO: Should use a purpose here?
1127 ) 1143 )
1128 root_elements.append(rootfs_image._id) 1144 root_elements.append(oe.sbom30.get_element_link_id(rootfs_image))
1129 1145
1130 image_objset, _ = oe.sbom30.find_jsonld( 1146 image_objset, _ = oe.sbom30.find_jsonld(
1131 d, "image", "%s-%s" % (image_basename, machine), required=True 1147 d, "image", "%s-%s-image" % (image_basename, machine), required=True
1132 ) 1148 )
1133 for o in image_objset.foreach_root(oe.spdx30.software_File): 1149 for o in image_objset.foreach_root(oe.spdx30.software_File):
1134 root_elements.append(o._id) 1150 root_elements.append(oe.sbom30.get_element_link_id(o))
1135 1151
1136 objset, sbom = oe.sbom30.create_sbom(d, image_name, root_elements) 1152 objset, sbom = oe.sbom30.create_sbom(d, image_name, root_elements)
1137 1153