@@ -223,6 +223,38 @@ def recursive_sort_in_place(value: list[Any] | dict[str, Any]) -> None:
223223 recursive_sort_in_place (cast (dict [str , Any ], sbom_data ))
224224
225225
226+ def check_sbom_data (sbom_data : SBOM ) -> None :
227+ """Check SBOM data for common issues"""
228+
229+ def check_id_duplicates (sbom_components : list [Package ] | list [File ]) -> set [str ]:
230+ all_ids = set ()
231+ for sbom_component in sbom_components :
232+ sbom_component_id = sbom_component ["SPDXID" ]
233+ assert sbom_component_id not in all_ids
234+ all_ids .add (sbom_component_id )
235+ return all_ids
236+
237+ all_package_ids = check_id_duplicates (sbom_data ["packages" ])
238+ all_file_ids = check_id_duplicates (sbom_data ["files" ])
239+
240+ # Check that no files and packages have the same ID.
241+ assert not all_package_ids .intersection (all_file_ids )
242+ all_sbom_ids = all_package_ids | all_file_ids
243+
244+ # Check that all relationships use existing IDs.
245+ for sbom_relationship in sbom_data ["relationships" ]:
246+
247+ # The exception being 'DESCRIBES' with the meta 'document' ID
248+ if (
249+ sbom_relationship ["spdxElementId" ] == "SPDXRef-DOCUMENT"
250+ and sbom_relationship ["relationshipType" ] == "DESCRIBES"
251+ ):
252+ continue
253+
254+ assert sbom_relationship ["spdxElementId" ] in all_sbom_ids
255+ assert sbom_relationship ["relatedSpdxElement" ] in all_sbom_ids
256+
257+
226258def fetch_package_metadata_from_pypi (
227259 project : str , version : str , filename : str | None = None
228260) -> tuple [str , str ]:
@@ -686,6 +718,11 @@ def create_sbom_for_windows_artifact(
686718 with (cpython_source_dir / "Misc/sbom.spdx.json" ).open () as f :
687719 source_sbom_data = json .loads (f .read ())
688720 for sbom_package in source_sbom_data ["packages" ]:
721+ # Update the SPDX ID to avoid collisions with
722+ # the 'externals' SBOM.
723+ sbom_package ["SPDXID" ] = spdx_id (
724+ f"SPDXRef-PACKAGE-{ sbom_package ['name' ]} -{ sbom_package ['versionInfo' ]} "
725+ )
689726 sbom_data ["packages" ].append (sbom_package )
690727
691728 create_cpython_sbom (
@@ -755,6 +792,10 @@ def main() -> None:
755792
756793 # Normalize SBOM data for reproducibility.
757794 normalize_sbom_data (sbom_data )
795+
796+ # Check SBOM for validity.
797+ check_sbom_data (sbom_data )
798+
758799 with open (artifact_path + ".spdx.json" , mode = "w" ) as f :
759800 f .truncate ()
760801 f .write (json .dumps (sbom_data , indent = 2 , sort_keys = True ))
0 commit comments