2323 required : true
2424 type : string
2525
26+ # Normalize the release tag across both trigger types.
27+ env :
28+ RELEASE_TAG : ${{ github.event.inputs.tag || github.ref_name }}
29+
2630permissions :
2731 contents : write # create GitHub Release + upload assets
2832 id-token : write # OIDC trusted publishing to PyPI
3135 # ---------------------------------------------------------------------------
3236 # 1. Gate: full functional test suite must be green on all supported Pythons
3337 # ---------------------------------------------------------------------------
34- test-gate :
35- name : " Gate – Python ${{ matrix.python-version }}"
38+ test :
39+ name : " Test – Python ${{ matrix.python-version }}"
3640 runs-on : ubuntu-latest
3741 strategy :
3842 fail-fast : true
@@ -54,33 +58,33 @@ jobs:
5458 - name : Build and install (including C extension)
5559 run : pip install -e . --no-build-isolation
5660
57- - name : Run functional tests (excluding slow/perf)
61+ - name : Run test suite (excluding slow/perf)
5862 run : |
5963 pytest \
6064 -m "not slow and not perf" \
61- --junit-xml=test-results/py${{ matrix.python-version }} .xml \
62- --cov-report=xml:coverage-py${{ matrix.python-version }} .xml
65+ --junit-xml=test-results.xml \
66+ --cov-report=xml:coverage.xml
6367
6468 - name : Upload test results
6569 if : always()
6670 uses : actions/upload-artifact@v4
6771 with :
6872 name : test-results-py${{ matrix.python-version }}
69- path : test-results/
73+ path : test-results.xml
7074
7175 - name : Upload coverage report
7276 if : always()
7377 uses : actions/upload-artifact@v4
7478 with :
7579 name : coverage-py${{ matrix.python-version }}
76- path : coverage-py${{ matrix.python-version }} .xml
80+ path : coverage.xml
7781
7882 # ---------------------------------------------------------------------------
7983 # 2. Source distribution
8084 # ---------------------------------------------------------------------------
8185 build-sdist :
8286 name : Build sdist
83- needs : test-gate
87+ needs : test
8488 runs-on : ubuntu-latest
8589
8690 steps :
9296
9397 - name : Build source distribution
9498 run : |
95- pip install build twine
99+ pip install build
96100 python -m build --sdist
97- twine check dist/*.tar.gz
98101
99102 - uses : actions/upload-artifact@v4
100103 with :
@@ -109,7 +112,7 @@ jobs:
109112 # ---------------------------------------------------------------------------
110113 build-wheels :
111114 name : " Wheels – ${{ matrix.os }}"
112- needs : test-gate
115+ needs : test
113116 runs-on : ${{ matrix.os }}
114117 strategy :
115118 fail-fast : false
@@ -141,9 +144,7 @@ jobs:
141144 CIBW_ARCHS_LINUX : " x86_64 aarch64"
142145 CIBW_ARCHS_MACOS : " x86_64 arm64"
143146 CIBW_ARCHS_WINDOWS : " AMD64"
144- # Enable OpenMP on Linux (gomp is available in manylinux_2_28)
145147 CIBW_ENVIRONMENT_LINUX : " CFLAGS='-fopenmp' LDFLAGS='-lgomp'"
146- # Post-build smoke test verifies the wheel installs and the C ext loads
147148 CIBW_TEST_COMMAND : >
148149 python -c "
149150 import metbit;
@@ -152,7 +153,7 @@ jobs:
152153 import numpy as np;
153154 r = pearson_columns(np.eye(5), anchor_index=0);
154155 assert abs(r[0] - 1.0) < 1e-12, r[0];
155- print('OK – backend:', backend_info())
156+ print('OK - backend:', backend_info())
156157 "
157158 CIBW_TEST_REQUIRES : " numpy scipy scikit-learn"
158159 CIBW_BUILD_VERBOSITY : 1
@@ -164,73 +165,57 @@ jobs:
164165 path : wheelhouse/*.whl
165166
166167 # ---------------------------------------------------------------------------
167- # 4. Security scan (Trivy on the sdist)
168+ # 4. Verify all artifacts: integrity check + security scan
168169 # ---------------------------------------------------------------------------
169- security-scan :
170- name : Security scan
171- needs : build-sdist
170+ verify :
171+ name : Verify artifacts
172+ needs : [ build-sdist, build-wheels]
172173 runs-on : ubuntu-latest
173174
174175 steps :
175176 - uses : actions/checkout@v4
176177
177- - name : Download sdist
178+ - name : Download all built artifacts
178179 uses : actions/download-artifact@v4
179180 with :
180- name : sdist
181+ pattern : " sdist wheels-* "
181182 path : dist/
183+ merge-multiple : true
182184
183- - name : Scan with Trivy (CRITICAL/HIGH only, fail on CRITICAL)
184- uses : aquasecurity/trivy-action@master
185+ - name : List artifacts
186+ run : ls -lh dist/
187+
188+ - name : Integrity check (twine)
189+ run : |
190+ pip install twine
191+ twine check dist/*
192+
193+ - name : Security scan (Trivy, warn only)
194+ uses : aquasecurity/trivy-action@0.31.0
185195 with :
186196 scan-type : fs
187197 scan-ref : .
188198 severity : " CRITICAL,HIGH"
189- exit-code : " 0" # warn only; set to "1" to fail on CRITICAL
199+ exit-code : " 0"
190200
191201 # ---------------------------------------------------------------------------
192- # 5. Publish to PyPI (OIDC trusted publishing – no API token needed)
202+ # 5. Publish to PyPI (OIDC trusted publishing - no API token needed)
193203 # ---------------------------------------------------------------------------
194204 publish-pypi :
195205 name : Publish to PyPI
196- needs : [build-sdist, build-wheels, security-scan]
206+ needs : verify
197207 runs-on : ubuntu-latest
198208 environment :
199209 name : pypi
200- url : https://pypi.org/project/metbit/${{ github.ref_name }}
210+ url : https://pypi.org/project/metbit/${{ env.RELEASE_TAG }}
201211
202212 steps :
203- - name : Download sdist
204- uses : actions/download-artifact@v4
205- with :
206- name : sdist
207- path : dist/
208-
209- - name : Download Linux wheels
210- uses : actions/download-artifact@v4
211- with :
212- name : wheels-ubuntu-latest
213- path : dist/
214-
215- - name : Download macOS wheels
216- uses : actions/download-artifact@v4
217- with :
218- name : wheels-macos-latest
219- path : dist/
220-
221- - name : Download Windows wheels
213+ - name : Download all built artifacts
222214 uses : actions/download-artifact@v4
223215 with :
224- name : wheels-windows-latest
216+ pattern : " sdist wheels-* "
225217 path : dist/
226-
227- - name : List artifacts
228- run : ls -lh dist/
229-
230- - name : Final twine check
231- run : |
232- pip install twine
233- twine check dist/*
218+ merge-multiple : true
234219
235220 - name : Publish to PyPI
236221 uses : pypa/gh-action-pypi-publish@release/v1
@@ -255,25 +240,28 @@ jobs:
255240 id : changelog
256241 shell : bash
257242 run : |
258- VERSION="${GITHUB_REF_NAME #v}"
243+ VERSION="${RELEASE_TAG #v}"
259244 NOTES=$(awk "/^## \[${VERSION}\]/{found=1; next} found && /^## \[/{exit} found{print}" CHANGELOG.md)
260245 {
261246 echo "notes<<EOF"
262247 echo "$NOTES"
263248 echo "EOF"
264249 } >> "$GITHUB_OUTPUT"
250+ env :
251+ RELEASE_TAG : ${{ env.RELEASE_TAG }}
265252
266253 - name : Download all built artifacts
267254 uses : actions/download-artifact@v4
268255 with :
269- pattern : " wheels-* sdist "
256+ pattern : " sdist wheels-*"
270257 path : release-assets/
271258 merge-multiple : true
272259
273260 - name : Create GitHub Release
274261 uses : softprops/action-gh-release@v2
275262 with :
276- name : " metbit ${{ github.ref_name }}"
263+ tag_name : ${{ env.RELEASE_TAG }}
264+ name : " metbit ${{ env.RELEASE_TAG }}"
277265 body : ${{ steps.changelog.outputs.notes }}
278266 draft : false
279267 prerelease : false
0 commit comments