1616from pathlib import Path
1717
1818
19- def record_entry (path : str , data : bytes ) -> str :
19+ def record_entry (path : str , data : bytes ) -> bytes :
2020 """Build a RECORD line: `path,sha256=<base64url-nopad>,<size>`."""
2121 digest = base64 .urlsafe_b64encode (hashlib .sha256 (data ).digest ())
22- return f"{ path } ,sha256={ digest .rstrip (b'=' ).decode ()} ,{ len (data )} "
22+ return (
23+ path .encode ("utf-8" )
24+ + b",sha256="
25+ + digest .rstrip (b"=" )
26+ + b","
27+ + str (len (data )).encode ()
28+ )
2329
2430
25- def embed (wheel : Path , sbom_name : str , sbom_bytes : bytes ) -> None :
31+ def embed (wheel : Path , sbom : Path ) -> None :
2632 with zipfile .ZipFile (wheel ) as zf :
2733 infos = zf .infolist ()
2834 contents = {info .filename : zf .read (info .filename ) for info in infos }
@@ -33,12 +39,14 @@ def embed(wheel: Path, sbom_name: str, sbom_bytes: bytes) -> None:
3339 if name .endswith (".dist-info/RECORD" ) and name .count ("/" ) == 1
3440 )
3541 dist_info = record_name .rsplit ("/" , 1 )[0 ]
36- sbom_path = f"{ dist_info } /sboms/{ sbom_name } "
42+
43+ sbom_bytes = sbom .read_bytes ()
44+ sbom_path = f"{ dist_info } /sboms/{ sbom .name } "
3745
3846 # Append a matching RECORD line for the SBOM (RECORD's own line has no hash).
39- lines = contents [record_name ].decode ( "utf-8" ). splitlines ()
47+ lines = contents [record_name ].splitlines ()
4048 lines .append (record_entry (sbom_path , sbom_bytes ))
41- contents [record_name ] = ( "\n " .join (lines ) + "\n " ). encode ( "utf-8" )
49+ contents [record_name ] = b "\n " .join (lines ) + b "\n "
4250
4351 tmp = wheel .with_name (wheel .name + ".tmp" )
4452 with zipfile .ZipFile (tmp , "w" , zipfile .ZIP_DEFLATED ) as zf :
@@ -49,16 +57,18 @@ def embed(wheel: Path, sbom_name: str, sbom_bytes: bytes) -> None:
4957 zf .writestr (sbom_path , sbom_bytes )
5058 tmp .replace (wheel )
5159
60+ print (f"Embedded { sbom .name } in { wheel .name } " )
61+
5262
53- def load_sbom (path : Path ) -> tuple [ str , bytes ] :
54- """Read the SBOM; `path` may be the file or a directory containing one ."""
63+ def scan_dir (path : Path ) -> Path :
64+ """If `path` is a directory, return the path of the SBOM within ."""
5565 if path .is_dir ():
5666 candidates = list (path .glob ("*.cdx.json" ))
5767 if len (candidates ) != 1 :
5868 msg = f"expected exactly one *.cdx.json in { path } , found { len (candidates )} "
5969 raise SystemExit (msg )
60- path = candidates [0 ]
61- return path . name , path . read_bytes ()
70+ return candidates [0 ]
71+ return path
6272
6373
6474def main () -> None :
@@ -75,16 +85,15 @@ def main() -> None:
7585 )
7686 args = parser .parse_args ()
7787
78- sbom_name , sbom_bytes = load_sbom (args .sbom )
88+ sbom = scan_dir (args .sbom )
7989
8090 wheels = sorted (args .wheelhouse .glob ("*.whl" ))
8191 if not wheels :
8292 print (f"error: no wheels found in { args .wheelhouse } " , file = sys .stderr )
8393 raise SystemExit (1 )
8494
8595 for wheel in wheels :
86- embed (wheel , sbom_name , sbom_bytes )
87- print (f"Embedded { sbom_name } in { wheel .name } " )
96+ embed (wheel , sbom )
8897
8998
9099if __name__ == "__main__" :
0 commit comments