Skip to content

Commit 4c49265

Browse files
committed
PYTHON-5433: Create an sbom-requirements.txt file to capture optional dependencies
1 parent 2f7946f commit 4c49265

File tree

2 files changed

+83
-5
lines changed

2 files changed

+83
-5
lines changed

.github/workflows/sbom.yml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ name: Generate SBOM
88
on:
99
workflow_dispatch: {}
1010
push:
11-
branches: ['master']
11+
branches: ["master"]
1212
paths:
13-
- 'requirements.txt'
13+
- "requirements.txt"
14+
- "requirements/**.txt"
15+
- "!requirements/docs.txt"
16+
- "!requirements/test.txt"
1417

1518
permissions:
1619
contents: write
@@ -39,7 +42,8 @@ jobs:
3942
run: |
4043
python -m venv .venv
4144
source .venv/bin/activate
42-
pip install -r requirements.txt
45+
python tools/generate_sbom_requirements.py
46+
pip install -r sbom-requirements.txt
4347
pip install .
4448
pip uninstall -y pip setuptools
4549
deactivate
@@ -73,10 +77,10 @@ jobs:
7377
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676
7478
with:
7579
token: ${{ secrets.GITHUB_TOKEN }}
76-
commit-message: 'chore: Update SBOM after dependency changes'
80+
commit-message: "chore: Update SBOM after dependency changes"
7781
branch: auto-update-sbom-${{ github.run_id }}
7882
delete-branch: true
79-
title: 'chore: Update SBOM'
83+
title: "chore: Update SBOM"
8084
body: |
8185
## Automated SBOM Update
8286
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
"""
2+
Concatenate requirements files into sbom-requirements.txt at repository root.
3+
4+
- Includes repo_root/requirements.txt if present
5+
- Includes all files matching repo_root/requirements/**/*.txt
6+
- Excludes docs.txt and test.txt in the requirements folder
7+
- Writes output to sbom-requirements.txt (overwrites)
8+
"""
9+
from __future__ import annotations
10+
11+
from pathlib import Path
12+
13+
EXCLUDED_NAMES = {"docs.txt", "test.txt"}
14+
15+
16+
def collect_files(root: Path) -> list[Path]:
17+
"""Collect requirement files to include in SBOM requirements.
18+
19+
Args:
20+
root (Path): The root directory of the repository.
21+
22+
Returns:
23+
list[Path]: A list of Paths to requirement files to include in the SBOM.
24+
"""
25+
files = []
26+
27+
# requirements.txt + all requirements/**/*.txt
28+
for p in [root / "requirements.txt", *root.glob("requirements/**/*.txt")]:
29+
if p.is_file() and p.name not in EXCLUDED_NAMES:
30+
files.append(p)
31+
32+
return files
33+
34+
35+
def write_combined_req_files(root: Path, files: list[Path], outname: str) -> Path:
36+
"""Concatenate requirement files and write to outname file.
37+
38+
Args:
39+
root (Path): The root directory of the repository.
40+
files (list[Path]): A list of Paths to requirement files to include in the SBOM.
41+
outname (str): The name of the output file.
42+
43+
Raises:
44+
RuntimeError: If writing to the output file fails.
45+
46+
Returns:
47+
Path: The path to the output file.
48+
"""
49+
outpath = root / outname
50+
try:
51+
with outpath.open("w", encoding="utf-8") as f:
52+
for p in files:
53+
with p.open("r", encoding="utf-8") as r:
54+
content = r.read().rstrip()
55+
if content:
56+
f.write(content + "\n")
57+
return outpath
58+
except Exception as e:
59+
raise RuntimeError(f"Failed to write {outpath}: {e}") from e
60+
61+
62+
def main():
63+
root = Path(__file__).parent.parent.resolve()
64+
65+
files = collect_files(root)
66+
if not files:
67+
raise FileNotFoundError(f"No requirement files found from {root}")
68+
69+
outpath = write_combined_req_files(root, files, "sbom-requirements.txt")
70+
print(f"Wrote concatenated requirements to: {outpath}")
71+
72+
73+
if __name__ == "__main__":
74+
main()

0 commit comments

Comments
 (0)