Skip to content

Commit dfb4631

Browse files
committed
Cleanup
- use `packaging.version` to compare versions - minor fixes
1 parent 80c516e commit dfb4631

File tree

2 files changed

+28
-13
lines changed

2 files changed

+28
-13
lines changed

gen_sbom.py

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#
33
# /// script
44
# requires-python = ">=3.10"
5-
# dependencies = ["openpyxl", "pydantic"]
5+
# dependencies = ["openpyxl", "pydantic", "packaging"]
66
# ///
77

88
import argparse
@@ -12,6 +12,7 @@
1212
from typing import Generator, Literal
1313

1414
import openpyxl
15+
from packaging import version
1516
from pydantic import AliasPath, BaseModel, Field, computed_field
1617

1718
logger = logging.getLogger(__name__)
@@ -36,7 +37,7 @@ class SPDXPackage(BaseModel):
3637
# I haven't yet seen a case where it is missing and it should be included in the human-readable SBOM
3738
versionInfo: str
3839
supplier: str = "Open-source software"
39-
externalRefs: list[SPDXRef] = []
40+
externalRefs: list[SPDXRef] = Field(default_factory=list)
4041

4142
@computed_field
4243
def purl(self) -> str | None:
@@ -109,20 +110,31 @@ def to_fda_records(self, author: str | None) -> Generator["FDARecord"]:
109110

110111

111112
class FDARecord(BaseModel):
112-
"""RDA required fields."""
113+
"""FDA required fields."""
113114

114115
author: str
115116
timestamp: str
116117
supplier: str = "Open-source software"
117118
component: str
118119
version: str
119120
unique_identifier: str
120-
relationship: Literal["is contained by"] = "is contained by"
121-
122-
123-
def newer2(p1, p2):
124-
"""Return the package with the newer version."""
125-
return p1 if p1.version > p2.version else p2
121+
relationship: Literal["Is contained by"] = "Is contained by"
122+
123+
124+
def newer(p1: FDARecord, p2: FDARecord) -> FDARecord:
125+
"""Return the package with the newer version using semantic version comparison."""
126+
if p1.version == p2.version:
127+
return p2 # Arbitrary choice if versions are equal
128+
try:
129+
v1 = version.parse(p1.version)
130+
v2 = version.parse(p2.version)
131+
return p1 if v1 > v2 else p2
132+
except Exception as e:
133+
# Fallback to string comparison if version parsing fails
134+
logger.warning(
135+
f"Failed to parse versions '{p1.version}' or '{p2.version}': {e}"
136+
)
137+
return p1 if p1.version > p2.version else p2
126138

127139

128140
def merge_sboms(sbom1: list[FDARecord], sbom2: list[FDARecord]) -> list[FDARecord]:
@@ -131,7 +143,7 @@ def merge_sboms(sbom1: list[FDARecord], sbom2: list[FDARecord]) -> list[FDARecor
131143
for r in sbom2:
132144
key = (r.component, r.supplier)
133145
if key in records:
134-
records[key] = newer2(records[key], r)
146+
records[key] = newer(records[key], r)
135147
else:
136148
records[key] = r
137149
return list(records.values())
@@ -156,7 +168,7 @@ def gen_sbom(
156168
):
157169
"""Generate a combined SBOM from multiple SPDX and CycloneDX SBOMs in the input directory."""
158170
bom_parsers: list[type[BaseSBOM]] = [SPDX2_3, Cyclone1_6]
159-
boms = []
171+
boms: list[list[FDARecord]] = []
160172

161173
for bom_file in input_directory_path.glob("*.json"):
162174
for bom_parser in bom_parsers:
@@ -173,9 +185,9 @@ def gen_sbom(
173185
logger.error(f"Failed to parse {bom_file} with all known parsers")
174186
raise ValueError(f"Unknown BOM format in {bom_file}")
175187

176-
merged_bom = reduce(merge_sboms, boms, [])
188+
merged_bom: list[FDARecord] = reduce(merge_sboms, boms, [])
177189
save_as_xlsx(merged_bom, output_file_path)
178-
# Check for duplicates
190+
# Check for duplicates (side effect: log warnings)
179191
deduplicate(merged_bom)
180192

181193

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ et-xmlfile==2.0.0 \
99
openpyxl==3.1.5 \
1010
--hash=sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2 \
1111
--hash=sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050
12+
packaging==25.0 \
13+
--hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \
14+
--hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f
1215
pydantic==2.11.9 \
1316
--hash=sha256:6b8ffda597a14812a7975c90b82a8a2e777d9257aba3453f973acd3c032a18e2 \
1417
--hash=sha256:c42dd626f5cfc1c6950ce6205ea58c93efa406da65f479dcb4029d5934857da2

0 commit comments

Comments
 (0)