@@ -60,12 +60,33 @@ def resolve_manifest_resources(resource, package_registry):
6060 return packages
6161
6262
63- def get_packages ( project , package_registry , manifest_resources , model = None ):
63+ def get_dependencies_from_manifest ( resource ):
6464 """
65- Get package data from package manifests/lockfiles/SBOMs or
66- get package data for resolved packages from package requirements.
65+ Get dependency data from resource.
66+ This is used for SPDX where the dependency data is stored as its own
67+ entry in the SBOM.
68+ On the CycloneDX side, the dependency data is stored inline in the
69+ component entries, it is stored on the package ``extra_data``.
70+ """
71+ dependencies = []
72+
73+ default_package_type = get_default_package_type (resource .location )
74+ if not default_package_type :
75+ return []
76+
77+ if default_package_type == "spdx" :
78+ dependencies = resolve_spdx_dependencies (input_location = resource .location )
79+
80+ return dependencies
81+
82+
83+ def get_data_from_manifests (project , package_registry , manifest_resources , model = None ):
84+ """
85+ Get package and dependency data from package manifests/lockfiles/SBOMs or
86+ for resolved packages from package requirements.
6787 """
6888 resolved_packages = []
89+ resolved_dependencies = []
6990 sboms_headers = {}
7091
7192 if not manifest_resources .exists ():
@@ -76,7 +97,8 @@ def get_packages(project, package_registry, manifest_resources, model=None):
7697 return []
7798
7899 for resource in manifest_resources :
79- if packages := resolve_manifest_resources (resource , package_registry ):
100+ packages = resolve_manifest_resources (resource , package_registry )
101+ if packages :
80102 resolved_packages .extend (packages )
81103 if headers := get_manifest_headers (resource ):
82104 sboms_headers [resource .name ] = headers
@@ -87,10 +109,14 @@ def get_packages(project, package_registry, manifest_resources, model=None):
87109 object_instance = resource ,
88110 )
89111
112+ dependencies = get_dependencies_from_manifest (resource )
113+ if dependencies :
114+ resolved_dependencies .extend (dependencies )
115+
90116 if sboms_headers :
91117 project .update_extra_data ({"sboms_headers" : sboms_headers })
92118
93- return resolved_packages
119+ return resolved_packages , resolved_dependencies
94120
95121
96122def create_packages_and_dependencies (project , packages , resolved = False ):
@@ -139,7 +165,7 @@ def create_dependencies_from_packages_extra_data(project):
139165
140166 for bom_ref in for_package .extra_data .get ("depends_on" , []):
141167 try :
142- resolved_to_package = project_packages .get (extra_data__bom_ref = bom_ref )
168+ resolved_to_package = project_packages .get (package_uid = bom_ref )
143169 except (ObjectDoesNotExist , MultipleObjectsReturned ):
144170 project .add_error (
145171 description = f"Could not find resolved_to package entry: { bom_ref } ." ,
@@ -284,8 +310,12 @@ def convert_spdx_expression(license_expression_spdx):
284310 return get_license_detections_and_expression (license_expression_spdx )[1 ]
285311
286312
287- def spdx_package_to_discovered_package_data (spdx_package ):
313+ def spdx_package_to_package_data (spdx_package ):
314+ """Convert the provided spdx_package into package_data."""
288315 package_url_dict = {}
316+ # Store the original "SPDXID" as package_uid for dependencies resolution.
317+ package_uid = spdx_package .spdx_id
318+
289319 for ref in spdx_package .external_refs :
290320 if ref .type == "purl" :
291321 purl = ref .locator
@@ -302,6 +332,7 @@ def spdx_package_to_discovered_package_data(spdx_package):
302332 declared_expression = convert_spdx_expression (declared_license_expression_spdx )
303333
304334 package_data = {
335+ "package_uid" : package_uid ,
305336 "name" : spdx_package .name ,
306337 "download_url" : spdx_package .download_location ,
307338 "declared_license_expression" : declared_expression ,
@@ -324,8 +355,28 @@ def spdx_package_to_discovered_package_data(spdx_package):
324355 }
325356
326357
327- def resolve_spdx_packages (input_location ):
328- """Resolve the packages from the `input_location` SPDX document file."""
358+ def spdx_relationship_to_dependency_data (spdx_relationship ):
359+ """Convert the provided spdx_relationship into dependency_data."""
360+ # spdx_id is a dependency of related_spdx_id
361+ if spdx_relationship .is_dependency_relationship :
362+ for_package_uid = spdx_relationship .related_spdx_id
363+ resolve_to_package_uid = spdx_relationship .spdx_id
364+ else : # spdx_id depends on related_spdx_id
365+ for_package_uid = spdx_relationship .spdx_id
366+ resolve_to_package_uid = spdx_relationship .related_spdx_id
367+
368+ dependency_data = {
369+ "for_package_uid" : for_package_uid ,
370+ "resolve_to_package_uid" : resolve_to_package_uid ,
371+ "is_runtime" : True ,
372+ "is_resolved" : True ,
373+ "is_direct" : True ,
374+ }
375+ return dependency_data
376+
377+
378+ def get_spdx_document_from_file (input_location ):
379+ """Return the loaded SPDX document from the `input_location` file."""
329380 input_path = Path (input_location )
330381 spdx_document = json .loads (input_path .read_text ())
331382
@@ -334,12 +385,32 @@ def resolve_spdx_packages(input_location):
334385 except Exception as e :
335386 raise Exception (f'SPDX document "{ input_path .name } " is not valid: { e } ' )
336387
388+ return spdx_document
389+
390+
391+ def resolve_spdx_packages (input_location ):
392+ """Resolve the packages from the `input_location` SPDX document file."""
393+ spdx_document = get_spdx_document_from_file (input_location )
337394 return [
338- spdx_package_to_discovered_package_data (spdx .Package .from_data (spdx_package ))
395+ spdx_package_to_package_data (spdx .Package .from_data (spdx_package ))
339396 for spdx_package in spdx_document .get ("packages" , [])
340397 ]
341398
342399
400+ def resolve_spdx_dependencies (input_location ):
401+ """Resolve the dependencies from the `input_location` SPDX document file."""
402+ spdx_document = get_spdx_document_from_file (input_location )
403+ spdx_relationships = [
404+ spdx .Relationship .from_data (spdx_relationship )
405+ for spdx_relationship in spdx_document .get ("relationships" , [])
406+ ]
407+
408+ return [
409+ spdx_relationship_to_dependency_data (spdx_relationship )
410+ for spdx_relationship in spdx_relationships
411+ ]
412+
413+
343414def get_default_package_type (input_location ):
344415 """
345416 Return the package type associated with the provided `input_location`.
0 commit comments