blob: 844b674f6f142a8fe0e831eab13dc960d165c28e [file] [log] [blame]
Patrick Williams7784c422022-11-17 07:29:11 -06001#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6
Patrick Williamsb542dec2023-06-09 01:26:37 -05007DEPLOY_DIR_SPDX ??= "${DEPLOY_DIR}/spdx"
Patrick Williams7784c422022-11-17 07:29:11 -06008
9# The product name that the CVE database uses. Defaults to BPN, but may need to
10# be overriden per recipe (for example tiff.bb sets CVE_PRODUCT=libtiff).
11CVE_PRODUCT ??= "${BPN}"
12CVE_VERSION ??= "${PV}"
13
14SPDXDIR ??= "${WORKDIR}/spdx"
15SPDXDEPLOY = "${SPDXDIR}/deploy"
16SPDXWORK = "${SPDXDIR}/work"
Andrew Geissler6aa7eec2023-03-03 12:41:14 -060017SPDXIMAGEWORK = "${SPDXDIR}/image-work"
18SPDXSDKWORK = "${SPDXDIR}/sdk-work"
Patrick Williamsb542dec2023-06-09 01:26:37 -050019SPDXDEPS = "${SPDXDIR}/deps.json"
Patrick Williams7784c422022-11-17 07:29:11 -060020
21SPDX_TOOL_NAME ??= "oe-spdx-creator"
22SPDX_TOOL_VERSION ??= "1.0"
23
24SPDXRUNTIMEDEPLOY = "${SPDXDIR}/runtime-deploy"
25
26SPDX_INCLUDE_SOURCES ??= "0"
27SPDX_ARCHIVE_SOURCES ??= "0"
28SPDX_ARCHIVE_PACKAGED ??= "0"
29
30SPDX_UUID_NAMESPACE ??= "sbom.openembedded.org"
31SPDX_NAMESPACE_PREFIX ??= "http://spdx.org/spdxdoc"
32SPDX_PRETTY ??= "0"
33
34SPDX_LICENSES ??= "${COREBASE}/meta/files/spdx-licenses.json"
35
Andrew Geissler6aa7eec2023-03-03 12:41:14 -060036SPDX_CUSTOM_ANNOTATION_VARS ??= ""
37
Patrick Williams7784c422022-11-17 07:29:11 -060038SPDX_ORG ??= "OpenEmbedded ()"
39SPDX_SUPPLIER ??= "Organization: ${SPDX_ORG}"
40SPDX_SUPPLIER[doc] = "The SPDX PackageSupplier field for SPDX packages created from \
41 this recipe. For SPDX documents create using this class during the build, this \
42 is the contact information for the person or organization who is doing the \
43 build."
44
45def extract_licenses(filename):
46 import re
47
48 lic_regex = re.compile(rb'^\W*SPDX-License-Identifier:\s*([ \w\d.()+-]+?)(?:\s+\W*)?$', re.MULTILINE)
49
50 try:
51 with open(filename, 'rb') as f:
52 size = min(15000, os.stat(filename).st_size)
53 txt = f.read(size)
54 licenses = re.findall(lic_regex, txt)
55 if licenses:
56 ascii_licenses = [lic.decode('ascii') for lic in licenses]
57 return ascii_licenses
58 except Exception as e:
59 bb.warn(f"Exception reading {filename}: {e}")
60 return None
61
62def get_doc_namespace(d, doc):
63 import uuid
64 namespace_uuid = uuid.uuid5(uuid.NAMESPACE_DNS, d.getVar("SPDX_UUID_NAMESPACE"))
65 return "%s/%s-%s" % (d.getVar("SPDX_NAMESPACE_PREFIX"), doc.name, str(uuid.uuid5(namespace_uuid, doc.name)))
66
67def create_annotation(d, comment):
68 from datetime import datetime, timezone
69
70 creation_time = datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
71 annotation = oe.spdx.SPDXAnnotation()
72 annotation.annotationDate = creation_time
73 annotation.annotationType = "OTHER"
74 annotation.annotator = "Tool: %s - %s" % (d.getVar("SPDX_TOOL_NAME"), d.getVar("SPDX_TOOL_VERSION"))
75 annotation.comment = comment
76 return annotation
77
78def recipe_spdx_is_native(d, recipe):
79 return any(a.annotationType == "OTHER" and
80 a.annotator == "Tool: %s - %s" % (d.getVar("SPDX_TOOL_NAME"), d.getVar("SPDX_TOOL_VERSION")) and
81 a.comment == "isNative" for a in recipe.annotations)
82
83def is_work_shared_spdx(d):
84 return bb.data.inherits_class('kernel', d) or ('work-shared' in d.getVar('WORKDIR'))
85
86def get_json_indent(d):
87 if d.getVar("SPDX_PRETTY") == "1":
88 return 2
89 return None
90
91python() {
92 import json
93 if d.getVar("SPDX_LICENSE_DATA"):
94 return
95
96 with open(d.getVar("SPDX_LICENSES"), "r") as f:
97 data = json.load(f)
98 # Transform the license array to a dictionary
99 data["licenses"] = {l["licenseId"]: l for l in data["licenses"]}
100 d.setVar("SPDX_LICENSE_DATA", data)
101}
102
103def convert_license_to_spdx(lic, document, d, existing={}):
104 from pathlib import Path
105 import oe.spdx
106
107 license_data = d.getVar("SPDX_LICENSE_DATA")
108 extracted = {}
109
110 def add_extracted_license(ident, name):
111 nonlocal document
112
113 if name in extracted:
114 return
115
116 extracted_info = oe.spdx.SPDXExtractedLicensingInfo()
117 extracted_info.name = name
118 extracted_info.licenseId = ident
119 extracted_info.extractedText = None
120
121 if name == "PD":
122 # Special-case this.
123 extracted_info.extractedText = "Software released to the public domain"
124 else:
125 # Seach for the license in COMMON_LICENSE_DIR and LICENSE_PATH
126 for directory in [d.getVar('COMMON_LICENSE_DIR')] + (d.getVar('LICENSE_PATH') or '').split():
127 try:
128 with (Path(directory) / name).open(errors="replace") as f:
129 extracted_info.extractedText = f.read()
130 break
131 except FileNotFoundError:
132 pass
133 if extracted_info.extractedText is None:
134 # If it's not SPDX or PD, then NO_GENERIC_LICENSE must be set
135 filename = d.getVarFlag('NO_GENERIC_LICENSE', name)
136 if filename:
137 filename = d.expand("${S}/" + filename)
138 with open(filename, errors="replace") as f:
139 extracted_info.extractedText = f.read()
140 else:
Patrick Williamsb542dec2023-06-09 01:26:37 -0500141 bb.fatal("Cannot find any text for license %s" % name)
Patrick Williams7784c422022-11-17 07:29:11 -0600142
143 extracted[name] = extracted_info
144 document.hasExtractedLicensingInfos.append(extracted_info)
145
146 def convert(l):
147 if l == "(" or l == ")":
148 return l
149
150 if l == "&":
151 return "AND"
152
153 if l == "|":
154 return "OR"
155
156 if l == "CLOSED":
157 return "NONE"
158
159 spdx_license = d.getVarFlag("SPDXLICENSEMAP", l) or l
160 if spdx_license in license_data["licenses"]:
161 return spdx_license
162
163 try:
164 spdx_license = existing[l]
165 except KeyError:
166 spdx_license = "LicenseRef-" + l
167 add_extracted_license(spdx_license, l)
168
169 return spdx_license
170
Patrick Williamsb542dec2023-06-09 01:26:37 -0500171 lic_split = lic.replace("(", " ( ").replace(")", " ) ").replace("|", " | ").replace("&", " & ").split()
Patrick Williams7784c422022-11-17 07:29:11 -0600172
173 return ' '.join(convert(l) for l in lic_split)
174
175def process_sources(d):
176 pn = d.getVar('PN')
177 assume_provided = (d.getVar("ASSUME_PROVIDED") or "").split()
178 if pn in assume_provided:
179 for p in d.getVar("PROVIDES").split():
180 if p != pn:
181 pn = p
182 break
183
184 # glibc-locale: do_fetch, do_unpack and do_patch tasks have been deleted,
185 # so avoid archiving source here.
186 if pn.startswith('glibc-locale'):
187 return False
188 if d.getVar('PN') == "libtool-cross":
189 return False
190 if d.getVar('PN') == "libgcc-initial":
191 return False
192 if d.getVar('PN') == "shadow-sysroot":
193 return False
194
195 # We just archive gcc-source for all the gcc related recipes
196 if d.getVar('BPN') in ['gcc', 'libgcc']:
197 bb.debug(1, 'spdx: There is bug in scan of %s is, do nothing' % pn)
198 return False
199
200 return True
201
202
203def add_package_files(d, doc, spdx_pkg, topdir, get_spdxid, get_types, *, archive=None, ignore_dirs=[], ignore_top_level_dirs=[]):
204 from pathlib import Path
205 import oe.spdx
206 import hashlib
207
208 source_date_epoch = d.getVar("SOURCE_DATE_EPOCH")
209 if source_date_epoch:
210 source_date_epoch = int(source_date_epoch)
211
212 sha1s = []
213 spdx_files = []
214
215 file_counter = 1
216 for subdir, dirs, files in os.walk(topdir):
217 dirs[:] = [d for d in dirs if d not in ignore_dirs]
218 if subdir == str(topdir):
219 dirs[:] = [d for d in dirs if d not in ignore_top_level_dirs]
220
221 for file in files:
222 filepath = Path(subdir) / file
223 filename = str(filepath.relative_to(topdir))
224
225 if not filepath.is_symlink() and filepath.is_file():
226 spdx_file = oe.spdx.SPDXFile()
227 spdx_file.SPDXID = get_spdxid(file_counter)
228 for t in get_types(filepath):
229 spdx_file.fileTypes.append(t)
230 spdx_file.fileName = filename
231
232 if archive is not None:
233 with filepath.open("rb") as f:
234 info = archive.gettarinfo(fileobj=f)
235 info.name = filename
236 info.uid = 0
237 info.gid = 0
238 info.uname = "root"
239 info.gname = "root"
240
241 if source_date_epoch is not None and info.mtime > source_date_epoch:
242 info.mtime = source_date_epoch
243
244 archive.addfile(info, f)
245
246 sha1 = bb.utils.sha1_file(filepath)
247 sha1s.append(sha1)
248 spdx_file.checksums.append(oe.spdx.SPDXChecksum(
249 algorithm="SHA1",
250 checksumValue=sha1,
251 ))
252 spdx_file.checksums.append(oe.spdx.SPDXChecksum(
253 algorithm="SHA256",
254 checksumValue=bb.utils.sha256_file(filepath),
255 ))
256
257 if "SOURCE" in spdx_file.fileTypes:
258 extracted_lics = extract_licenses(filepath)
259 if extracted_lics:
260 spdx_file.licenseInfoInFiles = extracted_lics
261
262 doc.files.append(spdx_file)
263 doc.add_relationship(spdx_pkg, "CONTAINS", spdx_file)
264 spdx_pkg.hasFiles.append(spdx_file.SPDXID)
265
266 spdx_files.append(spdx_file)
267
268 file_counter += 1
269
270 sha1s.sort()
271 verifier = hashlib.sha1()
272 for v in sha1s:
273 verifier.update(v.encode("utf-8"))
274 spdx_pkg.packageVerificationCode.packageVerificationCodeValue = verifier.hexdigest()
275
276 return spdx_files
277
278
279def add_package_sources_from_debug(d, package_doc, spdx_package, package, package_files, sources):
280 from pathlib import Path
281 import hashlib
282 import oe.packagedata
283 import oe.spdx
284
285 debug_search_paths = [
286 Path(d.getVar('PKGD')),
287 Path(d.getVar('STAGING_DIR_TARGET')),
288 Path(d.getVar('STAGING_DIR_NATIVE')),
289 Path(d.getVar('STAGING_KERNEL_DIR')),
290 ]
291
292 pkg_data = oe.packagedata.read_subpkgdata_extended(package, d)
293
294 if pkg_data is None:
295 return
296
297 for file_path, file_data in pkg_data["files_info"].items():
298 if not "debugsrc" in file_data:
299 continue
300
301 for pkg_file in package_files:
302 if file_path.lstrip("/") == pkg_file.fileName.lstrip("/"):
303 break
304 else:
305 bb.fatal("No package file found for %s" % str(file_path))
306 continue
307
308 for debugsrc in file_data["debugsrc"]:
309 ref_id = "NOASSERTION"
310 for search in debug_search_paths:
311 if debugsrc.startswith("/usr/src/kernel"):
312 debugsrc_path = search / debugsrc.replace('/usr/src/kernel/', '')
313 else:
314 debugsrc_path = search / debugsrc.lstrip("/")
315 if not debugsrc_path.exists():
316 continue
317
318 file_sha256 = bb.utils.sha256_file(debugsrc_path)
319
320 if file_sha256 in sources:
321 source_file = sources[file_sha256]
322
323 doc_ref = package_doc.find_external_document_ref(source_file.doc.documentNamespace)
324 if doc_ref is None:
325 doc_ref = oe.spdx.SPDXExternalDocumentRef()
326 doc_ref.externalDocumentId = "DocumentRef-dependency-" + source_file.doc.name
327 doc_ref.spdxDocument = source_file.doc.documentNamespace
328 doc_ref.checksum.algorithm = "SHA1"
329 doc_ref.checksum.checksumValue = source_file.doc_sha1
330 package_doc.externalDocumentRefs.append(doc_ref)
331
332 ref_id = "%s:%s" % (doc_ref.externalDocumentId, source_file.file.SPDXID)
333 else:
334 bb.debug(1, "Debug source %s with SHA256 %s not found in any dependency" % (str(debugsrc_path), file_sha256))
335 break
336 else:
337 bb.debug(1, "Debug source %s not found" % debugsrc)
338
339 package_doc.add_relationship(pkg_file, "GENERATED_FROM", ref_id, comment=debugsrc)
340
Patrick Williamsb542dec2023-06-09 01:26:37 -0500341add_package_sources_from_debug[vardepsexclude] += "STAGING_KERNEL_DIR"
342
Patrick Williams7784c422022-11-17 07:29:11 -0600343def collect_dep_recipes(d, doc, spdx_recipe):
Patrick Williamsb542dec2023-06-09 01:26:37 -0500344 import json
Patrick Williams7784c422022-11-17 07:29:11 -0600345 from pathlib import Path
346 import oe.sbom
347 import oe.spdx
348
349 deploy_dir_spdx = Path(d.getVar("DEPLOY_DIR_SPDX"))
Patrick Williamsb542dec2023-06-09 01:26:37 -0500350 spdx_deps_file = Path(d.getVar("SPDXDEPS"))
Patrick Williams7784c422022-11-17 07:29:11 -0600351
352 dep_recipes = []
Patrick Williamsb542dec2023-06-09 01:26:37 -0500353
354 with spdx_deps_file.open("r") as f:
355 deps = json.load(f)
356
357 for dep_pn, dep_hashfn in deps:
358 dep_recipe_path = oe.sbom.doc_path_by_hashfn(deploy_dir_spdx, "recipe-" + dep_pn, dep_hashfn)
Patrick Williams7784c422022-11-17 07:29:11 -0600359
360 spdx_dep_doc, spdx_dep_sha1 = oe.sbom.read_doc(dep_recipe_path)
361
362 for pkg in spdx_dep_doc.packages:
363 if pkg.name == dep_pn:
364 spdx_dep_recipe = pkg
365 break
366 else:
367 continue
368
369 dep_recipes.append(oe.sbom.DepRecipe(spdx_dep_doc, spdx_dep_sha1, spdx_dep_recipe))
370
371 dep_recipe_ref = oe.spdx.SPDXExternalDocumentRef()
372 dep_recipe_ref.externalDocumentId = "DocumentRef-dependency-" + spdx_dep_doc.name
373 dep_recipe_ref.spdxDocument = spdx_dep_doc.documentNamespace
374 dep_recipe_ref.checksum.algorithm = "SHA1"
375 dep_recipe_ref.checksum.checksumValue = spdx_dep_sha1
376
377 doc.externalDocumentRefs.append(dep_recipe_ref)
378
379 doc.add_relationship(
380 "%s:%s" % (dep_recipe_ref.externalDocumentId, spdx_dep_recipe.SPDXID),
381 "BUILD_DEPENDENCY_OF",
382 spdx_recipe
383 )
384
385 return dep_recipes
386
Patrick Williams7784c422022-11-17 07:29:11 -0600387
388def collect_dep_sources(d, dep_recipes):
389 import oe.sbom
390
391 sources = {}
392 for dep in dep_recipes:
393 # Don't collect sources from native recipes as they
394 # match non-native sources also.
395 if recipe_spdx_is_native(d, dep.recipe):
396 continue
397 recipe_files = set(dep.recipe.hasFiles)
398
399 for spdx_file in dep.doc.files:
400 if spdx_file.SPDXID not in recipe_files:
401 continue
402
403 if "SOURCE" in spdx_file.fileTypes:
404 for checksum in spdx_file.checksums:
405 if checksum.algorithm == "SHA256":
406 sources[checksum.checksumValue] = oe.sbom.DepSource(dep.doc, dep.doc_sha1, dep.recipe, spdx_file)
407 break
408
409 return sources
410
Andrew Geissler6aa7eec2023-03-03 12:41:14 -0600411def add_download_packages(d, doc, recipe):
412 import os.path
413 from bb.fetch2 import decodeurl, CHECKSUM_LIST
414 import bb.process
415 import oe.spdx
416 import oe.sbom
417
418 for download_idx, src_uri in enumerate(d.getVar('SRC_URI').split()):
419 f = bb.fetch2.FetchData(src_uri, d)
420
421 for name in f.names:
422 package = oe.spdx.SPDXPackage()
423 package.name = "%s-source-%d" % (d.getVar("PN"), download_idx + 1)
424 package.SPDXID = oe.sbom.get_download_spdxid(d, download_idx + 1)
425
426 if f.type == "file":
427 continue
428
429 uri = f.type
430 proto = getattr(f, "proto", None)
431 if proto is not None:
432 uri = uri + "+" + proto
433 uri = uri + "://" + f.host + f.path
434
435 if f.method.supports_srcrev():
436 uri = uri + "@" + f.revisions[name]
437
438 if f.method.supports_checksum(f):
439 for checksum_id in CHECKSUM_LIST:
440 if checksum_id.upper() not in oe.spdx.SPDXPackage.ALLOWED_CHECKSUMS:
441 continue
442
443 expected_checksum = getattr(f, "%s_expected" % checksum_id)
444 if expected_checksum is None:
445 continue
446
447 c = oe.spdx.SPDXChecksum()
448 c.algorithm = checksum_id.upper()
449 c.checksumValue = expected_checksum
450 package.checksums.append(c)
451
452 package.downloadLocation = uri
453 doc.packages.append(package)
454 doc.add_relationship(doc, "DESCRIBES", package)
455 # In the future, we might be able to do more fancy dependencies,
456 # but this should be sufficient for now
457 doc.add_relationship(package, "BUILD_DEPENDENCY_OF", recipe)
Patrick Williams7784c422022-11-17 07:29:11 -0600458
Patrick Williamsb542dec2023-06-09 01:26:37 -0500459def collect_direct_deps(d, dep_task):
460 current_task = "do_" + d.getVar("BB_CURRENTTASK")
461 pn = d.getVar("PN")
462
463 taskdepdata = d.getVar("BB_TASKDEPDATA", False)
464
465 for this_dep in taskdepdata.values():
466 if this_dep[0] == pn and this_dep[1] == current_task:
467 break
468 else:
469 bb.fatal(f"Unable to find this {pn}:{current_task} in taskdepdata")
470
471 deps = set()
472 for dep_name in this_dep[3]:
473 dep_data = taskdepdata[dep_name]
474 if dep_data[1] == dep_task and dep_data[0] != pn:
475 deps.add((dep_data[0], dep_data[7]))
476
477 return sorted(deps)
478
479collect_direct_deps[vardepsexclude] += "BB_TASKDEPDATA"
480collect_direct_deps[vardeps] += "DEPENDS"
481
482python do_collect_spdx_deps() {
483 # This task calculates the build time dependencies of the recipe, and is
484 # required because while a task can deptask on itself, those dependencies
485 # do not show up in BB_TASKDEPDATA. To work around that, this task does the
486 # deptask on do_create_spdx and writes out the dependencies it finds, then
487 # do_create_spdx reads in the found dependencies when writing the actual
488 # SPDX document
489 import json
490 from pathlib import Path
491
492 spdx_deps_file = Path(d.getVar("SPDXDEPS"))
493
494 deps = collect_direct_deps(d, "do_create_spdx")
495
496 with spdx_deps_file.open("w") as f:
497 json.dump(deps, f)
498}
499# NOTE: depending on do_unpack is a hack that is necessary to get it's dependencies for archive the source
500addtask do_collect_spdx_deps after do_unpack
501do_collect_spdx_deps[depends] += "${PATCHDEPENDENCY}"
502do_collect_spdx_deps[deptask] = "do_create_spdx"
503do_collect_spdx_deps[dirs] = "${SPDXDIR}"
504
Patrick Williams7784c422022-11-17 07:29:11 -0600505python do_create_spdx() {
506 from datetime import datetime, timezone
507 import oe.sbom
508 import oe.spdx
509 import uuid
510 from pathlib import Path
511 from contextlib import contextmanager
512 import oe.cve_check
513
514 @contextmanager
515 def optional_tarfile(name, guard, mode="w"):
516 import tarfile
517 import bb.compress.zstd
518
519 num_threads = int(d.getVar("BB_NUMBER_THREADS"))
520
521 if guard:
522 name.parent.mkdir(parents=True, exist_ok=True)
523 with bb.compress.zstd.open(name, mode=mode + "b", num_threads=num_threads) as f:
524 with tarfile.open(fileobj=f, mode=mode + "|") as tf:
525 yield tf
526 else:
527 yield None
528
529
530 deploy_dir_spdx = Path(d.getVar("DEPLOY_DIR_SPDX"))
531 spdx_workdir = Path(d.getVar("SPDXWORK"))
532 include_sources = d.getVar("SPDX_INCLUDE_SOURCES") == "1"
533 archive_sources = d.getVar("SPDX_ARCHIVE_SOURCES") == "1"
534 archive_packaged = d.getVar("SPDX_ARCHIVE_PACKAGED") == "1"
535
536 creation_time = datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
537
538 doc = oe.spdx.SPDXDocument()
539
540 doc.name = "recipe-" + d.getVar("PN")
541 doc.documentNamespace = get_doc_namespace(d, doc)
542 doc.creationInfo.created = creation_time
543 doc.creationInfo.comment = "This document was created by analyzing recipe files during the build."
544 doc.creationInfo.licenseListVersion = d.getVar("SPDX_LICENSE_DATA")["licenseListVersion"]
545 doc.creationInfo.creators.append("Tool: OpenEmbedded Core create-spdx.bbclass")
546 doc.creationInfo.creators.append("Organization: %s" % d.getVar("SPDX_ORG"))
547 doc.creationInfo.creators.append("Person: N/A ()")
548
549 recipe = oe.spdx.SPDXPackage()
550 recipe.name = d.getVar("PN")
551 recipe.versionInfo = d.getVar("PV")
552 recipe.SPDXID = oe.sbom.get_recipe_spdxid(d)
553 recipe.supplier = d.getVar("SPDX_SUPPLIER")
554 if bb.data.inherits_class("native", d) or bb.data.inherits_class("cross", d):
555 recipe.annotations.append(create_annotation(d, "isNative"))
556
Patrick Williams7784c422022-11-17 07:29:11 -0600557 homepage = d.getVar("HOMEPAGE")
558 if homepage:
559 recipe.homepage = homepage
560
561 license = d.getVar("LICENSE")
562 if license:
563 recipe.licenseDeclared = convert_license_to_spdx(license, doc, d)
564
565 summary = d.getVar("SUMMARY")
566 if summary:
567 recipe.summary = summary
568
569 description = d.getVar("DESCRIPTION")
570 if description:
571 recipe.description = description
572
Andrew Geissler6aa7eec2023-03-03 12:41:14 -0600573 if d.getVar("SPDX_CUSTOM_ANNOTATION_VARS"):
574 for var in d.getVar('SPDX_CUSTOM_ANNOTATION_VARS').split():
575 recipe.annotations.append(create_annotation(d, var + "=" + d.getVar(var)))
576
Patrick Williams7784c422022-11-17 07:29:11 -0600577 # Some CVEs may be patched during the build process without incrementing the version number,
578 # so querying for CVEs based on the CPE id can lead to false positives. To account for this,
579 # save the CVEs fixed by patches to source information field in the SPDX.
580 patched_cves = oe.cve_check.get_patched_cves(d)
581 patched_cves = list(patched_cves)
582 patched_cves = ' '.join(patched_cves)
583 if patched_cves:
584 recipe.sourceInfo = "CVEs fixed: " + patched_cves
585
586 cpe_ids = oe.cve_check.get_cpe_ids(d.getVar("CVE_PRODUCT"), d.getVar("CVE_VERSION"))
587 if cpe_ids:
588 for cpe_id in cpe_ids:
589 cpe = oe.spdx.SPDXExternalReference()
590 cpe.referenceCategory = "SECURITY"
591 cpe.referenceType = "http://spdx.org/rdf/references/cpe23Type"
592 cpe.referenceLocator = cpe_id
593 recipe.externalRefs.append(cpe)
594
595 doc.packages.append(recipe)
596 doc.add_relationship(doc, "DESCRIBES", recipe)
597
Andrew Geissler6aa7eec2023-03-03 12:41:14 -0600598 add_download_packages(d, doc, recipe)
599
Patrick Williams7784c422022-11-17 07:29:11 -0600600 if process_sources(d) and include_sources:
601 recipe_archive = deploy_dir_spdx / "recipes" / (doc.name + ".tar.zst")
602 with optional_tarfile(recipe_archive, archive_sources) as archive:
603 spdx_get_src(d)
604
605 add_package_files(
606 d,
607 doc,
608 recipe,
609 spdx_workdir,
610 lambda file_counter: "SPDXRef-SourceFile-%s-%d" % (d.getVar("PN"), file_counter),
611 lambda filepath: ["SOURCE"],
612 ignore_dirs=[".git"],
613 ignore_top_level_dirs=["temp"],
614 archive=archive,
615 )
616
617 if archive is not None:
618 recipe.packageFileName = str(recipe_archive.name)
619
620 dep_recipes = collect_dep_recipes(d, doc, recipe)
621
Patrick Williamsb542dec2023-06-09 01:26:37 -0500622 doc_sha1 = oe.sbom.write_doc(d, doc, d.getVar("SSTATE_PKGARCH"), "recipes", indent=get_json_indent(d))
Patrick Williams7784c422022-11-17 07:29:11 -0600623 dep_recipes.append(oe.sbom.DepRecipe(doc, doc_sha1, recipe))
624
625 recipe_ref = oe.spdx.SPDXExternalDocumentRef()
626 recipe_ref.externalDocumentId = "DocumentRef-recipe-" + recipe.name
627 recipe_ref.spdxDocument = doc.documentNamespace
628 recipe_ref.checksum.algorithm = "SHA1"
629 recipe_ref.checksum.checksumValue = doc_sha1
630
631 sources = collect_dep_sources(d, dep_recipes)
632 found_licenses = {license.name:recipe_ref.externalDocumentId + ":" + license.licenseId for license in doc.hasExtractedLicensingInfos}
633
634 if not recipe_spdx_is_native(d, recipe):
635 bb.build.exec_func("read_subpackage_metadata", d)
636
637 pkgdest = Path(d.getVar("PKGDEST"))
638 for package in d.getVar("PACKAGES").split():
639 if not oe.packagedata.packaged(package, d):
640 continue
641
642 package_doc = oe.spdx.SPDXDocument()
643 pkg_name = d.getVar("PKG:%s" % package) or package
644 package_doc.name = pkg_name
645 package_doc.documentNamespace = get_doc_namespace(d, package_doc)
646 package_doc.creationInfo.created = creation_time
647 package_doc.creationInfo.comment = "This document was created by analyzing packages created during the build."
648 package_doc.creationInfo.licenseListVersion = d.getVar("SPDX_LICENSE_DATA")["licenseListVersion"]
649 package_doc.creationInfo.creators.append("Tool: OpenEmbedded Core create-spdx.bbclass")
650 package_doc.creationInfo.creators.append("Organization: %s" % d.getVar("SPDX_ORG"))
651 package_doc.creationInfo.creators.append("Person: N/A ()")
652 package_doc.externalDocumentRefs.append(recipe_ref)
653
654 package_license = d.getVar("LICENSE:%s" % package) or d.getVar("LICENSE")
655
656 spdx_package = oe.spdx.SPDXPackage()
657
658 spdx_package.SPDXID = oe.sbom.get_package_spdxid(pkg_name)
659 spdx_package.name = pkg_name
660 spdx_package.versionInfo = d.getVar("PV")
661 spdx_package.licenseDeclared = convert_license_to_spdx(package_license, package_doc, d, found_licenses)
662 spdx_package.supplier = d.getVar("SPDX_SUPPLIER")
663
664 package_doc.packages.append(spdx_package)
665
666 package_doc.add_relationship(spdx_package, "GENERATED_FROM", "%s:%s" % (recipe_ref.externalDocumentId, recipe.SPDXID))
667 package_doc.add_relationship(package_doc, "DESCRIBES", spdx_package)
668
669 package_archive = deploy_dir_spdx / "packages" / (package_doc.name + ".tar.zst")
670 with optional_tarfile(package_archive, archive_packaged) as archive:
671 package_files = add_package_files(
672 d,
673 package_doc,
674 spdx_package,
675 pkgdest / package,
676 lambda file_counter: oe.sbom.get_packaged_file_spdxid(pkg_name, file_counter),
677 lambda filepath: ["BINARY"],
678 ignore_top_level_dirs=['CONTROL', 'DEBIAN'],
679 archive=archive,
680 )
681
682 if archive is not None:
683 spdx_package.packageFileName = str(package_archive.name)
684
685 add_package_sources_from_debug(d, package_doc, spdx_package, package, package_files, sources)
686
Patrick Williamsb542dec2023-06-09 01:26:37 -0500687 oe.sbom.write_doc(d, package_doc, d.getVar("SSTATE_PKGARCH"), "packages", indent=get_json_indent(d))
Patrick Williams7784c422022-11-17 07:29:11 -0600688}
Patrick Williamsb542dec2023-06-09 01:26:37 -0500689do_create_spdx[vardepsexclude] += "BB_NUMBER_THREADS"
Patrick Williams7784c422022-11-17 07:29:11 -0600690# NOTE: depending on do_unpack is a hack that is necessary to get it's dependencies for archive the source
Patrick Williamsb542dec2023-06-09 01:26:37 -0500691addtask do_create_spdx after do_package do_packagedata do_unpack do_collect_spdx_deps before do_populate_sdk do_build do_rm_work
Patrick Williams7784c422022-11-17 07:29:11 -0600692
693SSTATETASKS += "do_create_spdx"
694do_create_spdx[sstate-inputdirs] = "${SPDXDEPLOY}"
695do_create_spdx[sstate-outputdirs] = "${DEPLOY_DIR_SPDX}"
696
697python do_create_spdx_setscene () {
698 sstate_setscene(d)
699}
700addtask do_create_spdx_setscene
701
702do_create_spdx[dirs] = "${SPDXWORK}"
703do_create_spdx[cleandirs] = "${SPDXDEPLOY} ${SPDXWORK}"
704do_create_spdx[depends] += "${PATCHDEPENDENCY}"
Patrick Williams7784c422022-11-17 07:29:11 -0600705
706def collect_package_providers(d):
707 from pathlib import Path
708 import oe.sbom
709 import oe.spdx
710 import json
711
712 deploy_dir_spdx = Path(d.getVar("DEPLOY_DIR_SPDX"))
713
714 providers = {}
715
Patrick Williamsb542dec2023-06-09 01:26:37 -0500716 deps = collect_direct_deps(d, "do_create_spdx")
717 deps.append((d.getVar("PN"), d.getVar("BB_HASHFILENAME")))
Patrick Williams7784c422022-11-17 07:29:11 -0600718
Patrick Williamsb542dec2023-06-09 01:26:37 -0500719 for dep_pn, dep_hashfn in deps:
720 localdata = d
721 recipe_data = oe.packagedata.read_pkgdata(dep_pn, localdata)
722 if not recipe_data:
723 localdata = bb.data.createCopy(d)
724 localdata.setVar("PKGDATA_DIR", "${PKGDATA_DIR_SDK}")
725 recipe_data = oe.packagedata.read_pkgdata(dep_pn, localdata)
Patrick Williams7784c422022-11-17 07:29:11 -0600726
727 for pkg in recipe_data.get("PACKAGES", "").split():
728
Patrick Williamsb542dec2023-06-09 01:26:37 -0500729 pkg_data = oe.packagedata.read_subpkgdata_dict(pkg, localdata)
Patrick Williams7784c422022-11-17 07:29:11 -0600730 rprovides = set(n for n, _ in bb.utils.explode_dep_versions2(pkg_data.get("RPROVIDES", "")).items())
731 rprovides.add(pkg)
732
Patrick Williamsb542dec2023-06-09 01:26:37 -0500733 if "PKG" in pkg_data:
734 pkg = pkg_data["PKG"]
735 rprovides.add(pkg)
736
Patrick Williams7784c422022-11-17 07:29:11 -0600737 for r in rprovides:
Patrick Williamsb542dec2023-06-09 01:26:37 -0500738 providers[r] = (pkg, dep_hashfn)
Patrick Williams7784c422022-11-17 07:29:11 -0600739
740 return providers
741
742collect_package_providers[vardepsexclude] += "BB_TASKDEPDATA"
743
744python do_create_runtime_spdx() {
745 from datetime import datetime, timezone
746 import oe.sbom
747 import oe.spdx
748 import oe.packagedata
749 from pathlib import Path
750
751 deploy_dir_spdx = Path(d.getVar("DEPLOY_DIR_SPDX"))
752 spdx_deploy = Path(d.getVar("SPDXRUNTIMEDEPLOY"))
753 is_native = bb.data.inherits_class("native", d) or bb.data.inherits_class("cross", d)
754
755 creation_time = datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
756
757 providers = collect_package_providers(d)
758
759 if not is_native:
760 bb.build.exec_func("read_subpackage_metadata", d)
761
762 dep_package_cache = {}
763
764 pkgdest = Path(d.getVar("PKGDEST"))
765 for package in d.getVar("PACKAGES").split():
766 localdata = bb.data.createCopy(d)
767 pkg_name = d.getVar("PKG:%s" % package) or package
768 localdata.setVar("PKG", pkg_name)
769 localdata.setVar('OVERRIDES', d.getVar("OVERRIDES", False) + ":" + package)
770
771 if not oe.packagedata.packaged(package, localdata):
772 continue
773
Patrick Williamsb542dec2023-06-09 01:26:37 -0500774 pkg_spdx_path = oe.sbom.doc_path(deploy_dir_spdx, pkg_name, d.getVar("SSTATE_PKGARCH"), "packages")
Patrick Williams7784c422022-11-17 07:29:11 -0600775
776 package_doc, package_doc_sha1 = oe.sbom.read_doc(pkg_spdx_path)
777
778 for p in package_doc.packages:
779 if p.name == pkg_name:
780 spdx_package = p
781 break
782 else:
783 bb.fatal("Package '%s' not found in %s" % (pkg_name, pkg_spdx_path))
784
785 runtime_doc = oe.spdx.SPDXDocument()
786 runtime_doc.name = "runtime-" + pkg_name
787 runtime_doc.documentNamespace = get_doc_namespace(localdata, runtime_doc)
788 runtime_doc.creationInfo.created = creation_time
789 runtime_doc.creationInfo.comment = "This document was created by analyzing package runtime dependencies."
790 runtime_doc.creationInfo.licenseListVersion = d.getVar("SPDX_LICENSE_DATA")["licenseListVersion"]
791 runtime_doc.creationInfo.creators.append("Tool: OpenEmbedded Core create-spdx.bbclass")
792 runtime_doc.creationInfo.creators.append("Organization: %s" % d.getVar("SPDX_ORG"))
793 runtime_doc.creationInfo.creators.append("Person: N/A ()")
794
795 package_ref = oe.spdx.SPDXExternalDocumentRef()
796 package_ref.externalDocumentId = "DocumentRef-package-" + package
797 package_ref.spdxDocument = package_doc.documentNamespace
798 package_ref.checksum.algorithm = "SHA1"
799 package_ref.checksum.checksumValue = package_doc_sha1
800
801 runtime_doc.externalDocumentRefs.append(package_ref)
802
803 runtime_doc.add_relationship(
804 runtime_doc.SPDXID,
805 "AMENDS",
806 "%s:%s" % (package_ref.externalDocumentId, package_doc.SPDXID)
807 )
808
809 deps = bb.utils.explode_dep_versions2(localdata.getVar("RDEPENDS") or "")
810 seen_deps = set()
811 for dep, _ in deps.items():
812 if dep in seen_deps:
813 continue
814
815 if dep not in providers:
816 continue
817
Patrick Williamsb542dec2023-06-09 01:26:37 -0500818 (dep, dep_hashfn) = providers[dep]
Patrick Williams7784c422022-11-17 07:29:11 -0600819
820 if not oe.packagedata.packaged(dep, localdata):
821 continue
822
823 dep_pkg_data = oe.packagedata.read_subpkgdata_dict(dep, d)
824 dep_pkg = dep_pkg_data["PKG"]
825
826 if dep in dep_package_cache:
827 (dep_spdx_package, dep_package_ref) = dep_package_cache[dep]
828 else:
Patrick Williamsb542dec2023-06-09 01:26:37 -0500829 dep_path = oe.sbom.doc_path_by_hashfn(deploy_dir_spdx, dep_pkg, dep_hashfn)
Patrick Williams7784c422022-11-17 07:29:11 -0600830
831 spdx_dep_doc, spdx_dep_sha1 = oe.sbom.read_doc(dep_path)
832
833 for pkg in spdx_dep_doc.packages:
834 if pkg.name == dep_pkg:
835 dep_spdx_package = pkg
836 break
837 else:
838 bb.fatal("Package '%s' not found in %s" % (dep_pkg, dep_path))
839
840 dep_package_ref = oe.spdx.SPDXExternalDocumentRef()
841 dep_package_ref.externalDocumentId = "DocumentRef-runtime-dependency-" + spdx_dep_doc.name
842 dep_package_ref.spdxDocument = spdx_dep_doc.documentNamespace
843 dep_package_ref.checksum.algorithm = "SHA1"
844 dep_package_ref.checksum.checksumValue = spdx_dep_sha1
845
846 dep_package_cache[dep] = (dep_spdx_package, dep_package_ref)
847
848 runtime_doc.externalDocumentRefs.append(dep_package_ref)
849
850 runtime_doc.add_relationship(
851 "%s:%s" % (dep_package_ref.externalDocumentId, dep_spdx_package.SPDXID),
852 "RUNTIME_DEPENDENCY_OF",
853 "%s:%s" % (package_ref.externalDocumentId, spdx_package.SPDXID)
854 )
855 seen_deps.add(dep)
856
Patrick Williamsb542dec2023-06-09 01:26:37 -0500857 oe.sbom.write_doc(d, runtime_doc, d.getVar("SSTATE_PKGARCH"), "runtime", spdx_deploy, indent=get_json_indent(d))
Patrick Williams7784c422022-11-17 07:29:11 -0600858}
859
Patrick Williamsb542dec2023-06-09 01:26:37 -0500860do_create_runtime_spdx[vardepsexclude] += "OVERRIDES"
861
Patrick Williams7784c422022-11-17 07:29:11 -0600862addtask do_create_runtime_spdx after do_create_spdx before do_build do_rm_work
863SSTATETASKS += "do_create_runtime_spdx"
864do_create_runtime_spdx[sstate-inputdirs] = "${SPDXRUNTIMEDEPLOY}"
865do_create_runtime_spdx[sstate-outputdirs] = "${DEPLOY_DIR_SPDX}"
866
867python do_create_runtime_spdx_setscene () {
868 sstate_setscene(d)
869}
870addtask do_create_runtime_spdx_setscene
871
872do_create_runtime_spdx[dirs] = "${SPDXRUNTIMEDEPLOY}"
873do_create_runtime_spdx[cleandirs] = "${SPDXRUNTIMEDEPLOY}"
874do_create_runtime_spdx[rdeptask] = "do_create_spdx"
875
876def spdx_get_src(d):
877 """
878 save patched source of the recipe in SPDX_WORKDIR.
879 """
880 import shutil
881 spdx_workdir = d.getVar('SPDXWORK')
882 spdx_sysroot_native = d.getVar('STAGING_DIR_NATIVE')
883 pn = d.getVar('PN')
884
885 workdir = d.getVar("WORKDIR")
886
887 try:
888 # The kernel class functions require it to be on work-shared, so we dont change WORKDIR
889 if not is_work_shared_spdx(d):
890 # Change the WORKDIR to make do_unpack do_patch run in another dir.
891 d.setVar('WORKDIR', spdx_workdir)
892 # Restore the original path to recipe's native sysroot (it's relative to WORKDIR).
893 d.setVar('STAGING_DIR_NATIVE', spdx_sysroot_native)
894
895 # The changed 'WORKDIR' also caused 'B' changed, create dir 'B' for the
896 # possibly requiring of the following tasks (such as some recipes's
897 # do_patch required 'B' existed).
898 bb.utils.mkdirhier(d.getVar('B'))
899
900 bb.build.exec_func('do_unpack', d)
901 # Copy source of kernel to spdx_workdir
902 if is_work_shared_spdx(d):
903 share_src = d.getVar('WORKDIR')
904 d.setVar('WORKDIR', spdx_workdir)
905 d.setVar('STAGING_DIR_NATIVE', spdx_sysroot_native)
906 src_dir = spdx_workdir + "/" + d.getVar('PN')+ "-" + d.getVar('PV') + "-" + d.getVar('PR')
907 bb.utils.mkdirhier(src_dir)
908 if bb.data.inherits_class('kernel',d):
909 share_src = d.getVar('STAGING_KERNEL_DIR')
910 cmd_copy_share = "cp -rf " + share_src + "/* " + src_dir + "/"
911 cmd_copy_shared_res = os.popen(cmd_copy_share).read()
912 bb.note("cmd_copy_shared_result = " + cmd_copy_shared_res)
913
914 git_path = src_dir + "/.git"
915 if os.path.exists(git_path):
916 shutils.rmtree(git_path)
917
918 # Make sure gcc and kernel sources are patched only once
919 if not (d.getVar('SRC_URI') == "" or is_work_shared_spdx(d)):
920 bb.build.exec_func('do_patch', d)
921
922 # Some userland has no source.
923 if not os.path.exists( spdx_workdir ):
924 bb.utils.mkdirhier(spdx_workdir)
925 finally:
926 d.setVar("WORKDIR", workdir)
927
Patrick Williamsb542dec2023-06-09 01:26:37 -0500928spdx_get_src[vardepsexclude] += "STAGING_KERNEL_DIR"
929
Patrick Williams7784c422022-11-17 07:29:11 -0600930do_rootfs[recrdeptask] += "do_create_spdx do_create_runtime_spdx"
Andrew Geissler6aa7eec2023-03-03 12:41:14 -0600931do_rootfs[cleandirs] += "${SPDXIMAGEWORK}"
Patrick Williams7784c422022-11-17 07:29:11 -0600932
933ROOTFS_POSTUNINSTALL_COMMAND =+ "image_combine_spdx ; "
934
935do_populate_sdk[recrdeptask] += "do_create_spdx do_create_runtime_spdx"
Andrew Geissler6aa7eec2023-03-03 12:41:14 -0600936do_populate_sdk[cleandirs] += "${SPDXSDKWORK}"
Patrick Williams7784c422022-11-17 07:29:11 -0600937POPULATE_SDK_POST_HOST_COMMAND:append:task-populate-sdk = " sdk_host_combine_spdx; "
938POPULATE_SDK_POST_TARGET_COMMAND:append:task-populate-sdk = " sdk_target_combine_spdx; "
939
940python image_combine_spdx() {
941 import os
942 import oe.sbom
943 from pathlib import Path
944 from oe.rootfs import image_list_installed_packages
945
946 image_name = d.getVar("IMAGE_NAME")
947 image_link_name = d.getVar("IMAGE_LINK_NAME")
948 imgdeploydir = Path(d.getVar("IMGDEPLOYDIR"))
949 img_spdxid = oe.sbom.get_image_spdxid(image_name)
950 packages = image_list_installed_packages(d)
951
Andrew Geissler6aa7eec2023-03-03 12:41:14 -0600952 combine_spdx(d, image_name, imgdeploydir, img_spdxid, packages, Path(d.getVar("SPDXIMAGEWORK")))
Patrick Williams7784c422022-11-17 07:29:11 -0600953
954 def make_image_link(target_path, suffix):
955 if image_link_name:
956 link = imgdeploydir / (image_link_name + suffix)
957 if link != target_path:
958 link.symlink_to(os.path.relpath(target_path, link.parent))
959
Patrick Williams7784c422022-11-17 07:29:11 -0600960 spdx_tar_path = imgdeploydir / (image_name + ".spdx.tar.zst")
961 make_image_link(spdx_tar_path, ".spdx.tar.zst")
Patrick Williams7784c422022-11-17 07:29:11 -0600962}
963
964python sdk_host_combine_spdx() {
965 sdk_combine_spdx(d, "host")
966}
967
968python sdk_target_combine_spdx() {
969 sdk_combine_spdx(d, "target")
970}
971
972def sdk_combine_spdx(d, sdk_type):
973 import oe.sbom
974 from pathlib import Path
975 from oe.sdk import sdk_list_installed_packages
976
977 sdk_name = d.getVar("SDK_NAME") + "-" + sdk_type
978 sdk_deploydir = Path(d.getVar("SDKDEPLOYDIR"))
979 sdk_spdxid = oe.sbom.get_sdk_spdxid(sdk_name)
980 sdk_packages = sdk_list_installed_packages(d, sdk_type == "target")
Andrew Geissler6aa7eec2023-03-03 12:41:14 -0600981 combine_spdx(d, sdk_name, sdk_deploydir, sdk_spdxid, sdk_packages, Path(d.getVar('SPDXSDKWORK')))
Patrick Williams7784c422022-11-17 07:29:11 -0600982
Andrew Geissler6aa7eec2023-03-03 12:41:14 -0600983def combine_spdx(d, rootfs_name, rootfs_deploydir, rootfs_spdxid, packages, spdx_workdir):
Patrick Williams7784c422022-11-17 07:29:11 -0600984 import os
985 import oe.spdx
986 import oe.sbom
987 import io
988 import json
989 from datetime import timezone, datetime
990 from pathlib import Path
991 import tarfile
992 import bb.compress.zstd
993
Patrick Williamsb542dec2023-06-09 01:26:37 -0500994 providers = collect_package_providers(d)
995
Patrick Williams7784c422022-11-17 07:29:11 -0600996 creation_time = datetime.now(tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
997 deploy_dir_spdx = Path(d.getVar("DEPLOY_DIR_SPDX"))
998 source_date_epoch = d.getVar("SOURCE_DATE_EPOCH")
999
1000 doc = oe.spdx.SPDXDocument()
1001 doc.name = rootfs_name
1002 doc.documentNamespace = get_doc_namespace(d, doc)
1003 doc.creationInfo.created = creation_time
1004 doc.creationInfo.comment = "This document was created by analyzing the source of the Yocto recipe during the build."
1005 doc.creationInfo.licenseListVersion = d.getVar("SPDX_LICENSE_DATA")["licenseListVersion"]
1006 doc.creationInfo.creators.append("Tool: OpenEmbedded Core create-spdx.bbclass")
1007 doc.creationInfo.creators.append("Organization: %s" % d.getVar("SPDX_ORG"))
1008 doc.creationInfo.creators.append("Person: N/A ()")
1009
1010 image = oe.spdx.SPDXPackage()
1011 image.name = d.getVar("PN")
1012 image.versionInfo = d.getVar("PV")
1013 image.SPDXID = rootfs_spdxid
1014 image.supplier = d.getVar("SPDX_SUPPLIER")
1015
1016 doc.packages.append(image)
1017
1018 for name in sorted(packages.keys()):
Patrick Williamsb542dec2023-06-09 01:26:37 -05001019 if name not in providers:
1020 bb.fatal("Unable to find provider for '%s'" % name)
1021
1022 pkg_name, pkg_hashfn = providers[name]
1023
1024 pkg_spdx_path = oe.sbom.doc_path_by_hashfn(deploy_dir_spdx, pkg_name, pkg_hashfn)
Patrick Williams7784c422022-11-17 07:29:11 -06001025 pkg_doc, pkg_doc_sha1 = oe.sbom.read_doc(pkg_spdx_path)
1026
1027 for p in pkg_doc.packages:
1028 if p.name == name:
1029 pkg_ref = oe.spdx.SPDXExternalDocumentRef()
1030 pkg_ref.externalDocumentId = "DocumentRef-%s" % pkg_doc.name
1031 pkg_ref.spdxDocument = pkg_doc.documentNamespace
1032 pkg_ref.checksum.algorithm = "SHA1"
1033 pkg_ref.checksum.checksumValue = pkg_doc_sha1
1034
1035 doc.externalDocumentRefs.append(pkg_ref)
1036 doc.add_relationship(image, "CONTAINS", "%s:%s" % (pkg_ref.externalDocumentId, p.SPDXID))
1037 break
1038 else:
1039 bb.fatal("Unable to find package with name '%s' in SPDX file %s" % (name, pkg_spdx_path))
1040
Patrick Williamsb542dec2023-06-09 01:26:37 -05001041 runtime_spdx_path = oe.sbom.doc_path_by_hashfn(deploy_dir_spdx, "runtime-" + name, pkg_hashfn)
Patrick Williams7784c422022-11-17 07:29:11 -06001042 runtime_doc, runtime_doc_sha1 = oe.sbom.read_doc(runtime_spdx_path)
1043
1044 runtime_ref = oe.spdx.SPDXExternalDocumentRef()
1045 runtime_ref.externalDocumentId = "DocumentRef-%s" % runtime_doc.name
1046 runtime_ref.spdxDocument = runtime_doc.documentNamespace
1047 runtime_ref.checksum.algorithm = "SHA1"
1048 runtime_ref.checksum.checksumValue = runtime_doc_sha1
1049
1050 # "OTHER" isn't ideal here, but I can't find a relationship that makes sense
1051 doc.externalDocumentRefs.append(runtime_ref)
1052 doc.add_relationship(
1053 image,
1054 "OTHER",
1055 "%s:%s" % (runtime_ref.externalDocumentId, runtime_doc.SPDXID),
1056 comment="Runtime dependencies for %s" % name
1057 )
1058
Andrew Geissler6aa7eec2023-03-03 12:41:14 -06001059 image_spdx_path = spdx_workdir / (rootfs_name + ".spdx.json")
Patrick Williams7784c422022-11-17 07:29:11 -06001060
1061 with image_spdx_path.open("wb") as f:
1062 doc.to_json(f, sort_keys=True, indent=get_json_indent(d))
1063
1064 num_threads = int(d.getVar("BB_NUMBER_THREADS"))
1065
1066 visited_docs = set()
1067
1068 index = {"documents": []}
1069
1070 spdx_tar_path = rootfs_deploydir / (rootfs_name + ".spdx.tar.zst")
1071 with bb.compress.zstd.open(spdx_tar_path, "w", num_threads=num_threads) as f:
1072 with tarfile.open(fileobj=f, mode="w|") as tar:
1073 def collect_spdx_document(path):
1074 nonlocal tar
1075 nonlocal deploy_dir_spdx
1076 nonlocal source_date_epoch
1077 nonlocal index
1078
1079 if path in visited_docs:
1080 return
1081
1082 visited_docs.add(path)
1083
1084 with path.open("rb") as f:
1085 doc, sha1 = oe.sbom.read_doc(f)
1086 f.seek(0)
1087
1088 if doc.documentNamespace in visited_docs:
1089 return
1090
1091 bb.note("Adding SPDX document %s" % path)
1092 visited_docs.add(doc.documentNamespace)
1093 info = tar.gettarinfo(fileobj=f)
1094
1095 info.name = doc.name + ".spdx.json"
1096 info.uid = 0
1097 info.gid = 0
1098 info.uname = "root"
1099 info.gname = "root"
1100
1101 if source_date_epoch is not None and info.mtime > int(source_date_epoch):
1102 info.mtime = int(source_date_epoch)
1103
1104 tar.addfile(info, f)
1105
1106 index["documents"].append({
1107 "filename": info.name,
1108 "documentNamespace": doc.documentNamespace,
1109 "sha1": sha1,
1110 })
1111
1112 for ref in doc.externalDocumentRefs:
Patrick Williamsb542dec2023-06-09 01:26:37 -05001113 ref_path = oe.sbom.doc_path_by_namespace(deploy_dir_spdx, ref.spdxDocument)
Patrick Williams7784c422022-11-17 07:29:11 -06001114 collect_spdx_document(ref_path)
1115
1116 collect_spdx_document(image_spdx_path)
1117
1118 index["documents"].sort(key=lambda x: x["filename"])
1119
1120 index_str = io.BytesIO(json.dumps(
1121 index,
1122 sort_keys=True,
1123 indent=get_json_indent(d),
1124 ).encode("utf-8"))
1125
1126 info = tarfile.TarInfo()
1127 info.name = "index.json"
1128 info.size = len(index_str.getvalue())
1129 info.uid = 0
1130 info.gid = 0
1131 info.uname = "root"
1132 info.gname = "root"
1133
1134 tar.addfile(info, fileobj=index_str)
Patrick Williamsb542dec2023-06-09 01:26:37 -05001135
1136combine_spdx[vardepsexclude] += "BB_NUMBER_THREADS"