Skip to content

Commit b040570

Browse files
authored
ci: check extension versions are updated if changed (#929)
Based on CQCL/hugr#2364 See commits 2 and 3 for demo
1 parent e5621fe commit b040570

File tree

3 files changed

+119
-2
lines changed

3 files changed

+119
-2
lines changed

.github/change-filters.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,6 @@ python:
1919
- "tket2-exts/**"
2020
- "pyproject.toml"
2121
- "uv.lock"
22+
23+
extensions:
24+
- "tket2-exts/src/tket2_exts/data/tket2/**"

.github/workflows/ci.yml

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ env:
3131

3232
jobs:
3333
# Check if changes were made to the relevant files.
34-
# Always returns true if running on the default branch, to ensure all changes are throughly checked.
34+
# Always returns true if running on the default branch, to ensure all changes are thoroughly checked.
3535
changes:
3636
name: Check for changes
3737
runs-on: ubuntu-latest
@@ -43,6 +43,7 @@ jobs:
4343
outputs:
4444
rust: ${{ steps.filter.outputs.rust == 'true' || steps.override.outputs.out == 'true' }}
4545
python: ${{ steps.filter.outputs.python == 'true' || steps.override.outputs.out == 'true' }}
46+
extensions: ${{ steps.filter.outputs.extensions == 'true' || steps.override.outputs.out == 'true' }}
4647
steps:
4748
- uses: actions/checkout@v4
4849
- name: Override label
@@ -308,12 +309,36 @@ jobs:
308309
"
309310
exit 1
310311
fi
312+
extension-versions:
313+
runs-on: ubuntu-latest
314+
needs: [changes]
315+
if: ${{ needs.changes.outputs.extensions == 'true' }}
316+
name: Check extension versions
317+
steps:
318+
- uses: actions/checkout@v4
319+
with:
320+
fetch-depth: 0 # Need full history to compare with main
321+
322+
- name: Set up Python
323+
uses: actions/setup-python@v4
324+
with:
325+
python-version: '3.10'
326+
327+
- name: Check if extension versions are updated
328+
# check against latest tag on the target branch
329+
# base_ref should be empty if not a pull request, so goes from HEAD
330+
run: |
331+
BASE_SHA=$(git rev-parse origin/${{ github.base_ref }})
332+
LAST_TAG=$(git describe --abbrev=0 --match="tket2-*" $BASE_SHA)
333+
echo "Latest tag on target: $LAST_TAG"
334+
335+
python ./scripts/check_extension_versions.py $LAST_TAG
311336
312337
# This is a meta job to mark successful completion of the required checks,
313338
# even if they are skipped due to no changes in the relevant files.
314339
required-checks:
315340
name: Required checks 🦀+🐍
316-
needs: [changes, check-rs, check-py, tests-rs-stable-no-features, tests-rs-stable-all-features, tests-py, tket2-extensions]
341+
needs: [changes, check-rs, check-py, tests-rs-stable-no-features, tests-rs-stable-all-features, tests-py, tket2-extensions, extension-versions]
317342
if: ${{ !cancelled() }}
318343
runs-on: ubuntu-latest
319344
steps:

scripts/check_extension_versions.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#!/usr/bin/env python
2+
3+
import json
4+
import subprocess
5+
import sys
6+
from pathlib import Path
7+
8+
9+
def get_changed_files(target: str) -> list[Path]:
10+
"""Get list of changed extension files in the PR"""
11+
# Use git to get the list of files changed compared to target
12+
cmd = [
13+
"git",
14+
"diff",
15+
"--name-only",
16+
target,
17+
"--",
18+
"tket2-exts/src/tket2_exts/data/tket2/",
19+
]
20+
result = subprocess.run(cmd, capture_output=True, text=True, check=True) # noqa: S603
21+
changed_files = [Path(f) for f in result.stdout.splitlines() if f.endswith(".json")]
22+
return changed_files
23+
24+
25+
def check_version_changes(changed_files: list[Path], target: str) -> list[str]:
26+
"""Check if versions have been updated in changed files"""
27+
errors = []
28+
29+
for file_path in changed_files:
30+
# Skip files that don't exist anymore (deleted files)
31+
if not file_path.exists():
32+
continue
33+
34+
# Get the version in the current branch
35+
with file_path.open("r") as f:
36+
current = json.load(f)
37+
current_version = current.get("version")
38+
39+
# Get the version in the target branch
40+
try:
41+
cmd = ["git", "show", f"{target}:{file_path}"]
42+
result = subprocess.run(cmd, capture_output=True, text=True) # noqa: S603
43+
44+
if result.returncode == 0:
45+
# File exists in target
46+
target_content = json.loads(result.stdout)
47+
target_version = target_content.get("version")
48+
49+
if current_version == target_version:
50+
errors.append(
51+
f"Error: {file_path} was modified but version {current_version} was not updated."
52+
)
53+
else:
54+
print(
55+
f"Version updated in {file_path}: {target_version} -> {current_version}"
56+
)
57+
58+
else:
59+
# New file - no version check needed
60+
pass
61+
62+
except json.JSONDecodeError:
63+
# File is new or not valid JSON in target
64+
pass
65+
66+
return errors
67+
68+
69+
def main() -> int:
70+
target = sys.argv[1] if len(sys.argv) > 1 else "origin/main"
71+
changed_files = get_changed_files(target)
72+
if not changed_files:
73+
print("No extension files changed.")
74+
return 0
75+
76+
print(f"Changed extension files: {', '.join(map(str, changed_files))}")
77+
78+
errors = check_version_changes(changed_files, target)
79+
if errors:
80+
for error in errors:
81+
print(error)
82+
return 1
83+
84+
print("All changed extension files have updated versions.")
85+
return 0
86+
87+
88+
if __name__ == "__main__":
89+
sys.exit(main())

0 commit comments

Comments
 (0)