1919import sys
2020import tarfile
2121import time
22- from typing import IO , TYPE_CHECKING , Any , Dict , List , Optional , Union
22+ from typing import IO , TYPE_CHECKING , Any
2323
2424import relenv .relocate
2525from relenv .common import (
@@ -425,9 +425,10 @@ def generate_relenv_sbom(env: MutableMapping[str, str], dirs: Dirs) -> None:
425425 :param dirs: The working directories
426426 :type dirs: ``relenv.build.common.Dirs``
427427 """
428- from .builder import get_dependency_version
429428 import relenv
430429
430+ from .builder import get_dependency_version
431+
431432 python_version = dirs .version
432433
433434 platform_map = {
@@ -438,10 +439,10 @@ def generate_relenv_sbom(env: MutableMapping[str, str], dirs: Dirs) -> None:
438439 platform = platform_map .get (sys .platform , sys .platform )
439440
440441 # Build dependency list - get versions from python-versions.json
441- packages : List [ Dict [str , Any ]] = []
442+ packages : list [ dict [str , Any ]] = []
442443
443444 # Add Python itself as the primary package
444- python_package : Dict [str , Any ] = {
445+ python_package : dict [str , Any ] = {
445446 "SPDXID" : "SPDXRef-PACKAGE-Python" ,
446447 "name" : "Python" ,
447448 "versionInfo" : python_version ,
@@ -503,8 +504,7 @@ def generate_relenv_sbom(env: MutableMapping[str, str], dirs: Dirs) -> None:
503504 [
504505 (
505506 "tirpc" ,
506- "https://downloads.sourceforge.net/project/libtirpc/"
507- "libtirpc/{version}/libtirpc-{version}.tar.bz2" ,
507+ "https://downloads.sourceforge.net/project/libtirpc/libtirpc/{version}/libtirpc-{version}.tar.bz2" ,
508508 ),
509509 (
510510 "krb5" ,
@@ -526,7 +526,7 @@ def generate_relenv_sbom(env: MutableMapping[str, str], dirs: Dirs) -> None:
526526 )
527527 checksum = dep_info .get ("sha256" , "" )
528528
529- package : Dict [str , Any ] = {
529+ package : dict [str , Any ] = {
530530 "SPDXID" : f"SPDXRef-PACKAGE-{ dep_name } " ,
531531 "name" : dep_name ,
532532 "versionInfo" : version ,
@@ -557,7 +557,7 @@ def generate_relenv_sbom(env: MutableMapping[str, str], dirs: Dirs) -> None:
557557 parts = dist_name .rsplit ("-" , 1 )
558558 if len (parts ) == 2 :
559559 pkg_name , pkg_version = parts
560- package2 : Dict [str , Any ] = {
560+ package2 : dict [str , Any ] = {
561561 "SPDXID" : f"SPDXRef-PACKAGE-python-{ pkg_name } " ,
562562 "name" : pkg_name ,
563563 "versionInfo" : pkg_version ,
@@ -575,11 +575,12 @@ def generate_relenv_sbom(env: MutableMapping[str, str], dirs: Dirs) -> None:
575575 bundled_deps = []
576576
577577 # Try to read Python's SBOM to get accurate versions of bundled components
578- python_sbom_path = pathlib .Path (str (dirs .source )) / "Misc" / "sbom.spdx.json"
579- python_bundled_versions : Dict [str , Dict [str , Any ]] = {}
578+ python_source_dir = dirs .sources / f"Python-{ dirs .version } "
579+ python_sbom_path = python_source_dir / "Misc" / "sbom.spdx.json"
580+ python_bundled_versions : dict [str , dict [str , Any ]] = {}
580581 if python_sbom_path .exists ():
581582 try :
582- with io . open (python_sbom_path , "r" ) as fp :
583+ with open (python_sbom_path ) as fp :
583584 python_sbom = json .load (fp )
584585 for pkg in python_sbom .get ("packages" , []):
585586 pkg_name = pkg .get ("name" )
@@ -602,7 +603,7 @@ def generate_relenv_sbom(env: MutableMapping[str, str], dirs: Dirs) -> None:
602603 for comp_name , comp_desc in bundled_components .items ():
603604 if comp_name in python_bundled_versions :
604605 src_pkg = python_bundled_versions [comp_name ]
605- bundled_pkg : Dict [str , Any ] = {
606+ bundled_pkg : dict [str , Any ] = {
606607 "SPDXID" : f"SPDXRef-PACKAGE-{ comp_name } " ,
607608 "name" : comp_name ,
608609 "versionInfo" : src_pkg .get ("versionInfo" , "NOASSERTION" ),
@@ -627,6 +628,16 @@ def generate_relenv_sbom(env: MutableMapping[str, str], dirs: Dirs) -> None:
627628 timestamp = time .strftime ("%Y%m%d%H%M%S" , time .gmtime ())
628629 doc_name = f"relenv-{ env .get ('RELENV_PY_VERSION' , 'unknown' )} -{ env .get ('RELENV_HOST' , 'unknown' )} "
629630
631+ # Create relationships - SPDX requires DESCRIBES relationship
632+ # The document DESCRIBES the Python package (the primary component)
633+ relationships = [
634+ {
635+ "spdxElementId" : "SPDXRef-DOCUMENT" ,
636+ "relatedSpdxElement" : "SPDXRef-PACKAGE-Python" ,
637+ "relationshipType" : "DESCRIBES" ,
638+ }
639+ ]
640+
630641 sbom = {
631642 "SPDXID" : "SPDXRef-DOCUMENT" ,
632643 "spdxVersion" : "SPDX-2.3" ,
@@ -643,11 +654,12 @@ def generate_relenv_sbom(env: MutableMapping[str, str], dirs: Dirs) -> None:
643654 "vulnerability scanning and compliance." ,
644655 },
645656 "packages" : packages ,
657+ "relationships" : relationships ,
646658 }
647659
648660 # Write the SBOM file
649661 sbom_path = pathlib .Path (dirs .prefix ) / "relenv-sbom.spdx.json"
650- with io . open (sbom_path , "w" ) as fp :
662+ with open (sbom_path , "w" ) as fp :
651663 json .dump (sbom , fp , indent = 2 )
652664 log .info (
653665 "Generated relenv-sbom.spdx.json with %d packages (Python %s + dependencies + pip packages)" ,
0 commit comments