Release #58
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| # Triggers on version tags: v9.0.0, v9.1.0, etc. | |
| # | |
| # Release checklist (run before pushing the tag): | |
| # 1. All tests green on main. | |
| # 2. Version bumped in setup.py and metbit/__init__.py. | |
| # 3. CHANGELOG.md updated. | |
| # 4. git tag v9.0.0 && git push origin v9.0.0 | |
| # | |
| # PyPI trusted publishing must be configured at: | |
| # https://pypi.org/manage/project/metbit/settings/publishing/ | |
| # with workflow name "python-publish.yml" and environment "pypi". | |
| on: | |
| push: | |
| tags: | |
| - "v[0-9]+.[0-9]+.[0-9]+" | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: "Tag to release (e.g. v9.0.0)" | |
| required: true | |
| type: string | |
| # Normalize the release tag across both trigger types. | |
| env: | |
| RELEASE_TAG: ${{ github.event.inputs.tag || github.ref_name }} | |
| permissions: | |
| contents: write # create GitHub Release + upload assets | |
| id-token: write # OIDC trusted publishing to PyPI | |
| jobs: | |
| # --------------------------------------------------------------------------- | |
| # 1. Gate: full functional test suite must be green on all supported Pythons | |
| # --------------------------------------------------------------------------- | |
| test: | |
| name: "Test – Python ${{ matrix.python-version }}" | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: true | |
| matrix: | |
| python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Install build tools + dev dependencies | |
| run: | | |
| python -m pip install --upgrade pip setuptools wheel | |
| pip install -r requirements-dev.txt | |
| - name: Build and install (including C extension) | |
| run: pip install -e . --no-build-isolation | |
| - name: Run test suite (excluding slow/perf) | |
| run: | | |
| pytest \ | |
| -m "not slow and not perf" \ | |
| --junit-xml=test-results.xml \ | |
| --cov-report=xml:coverage.xml | |
| - name: Upload test results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: test-results-py${{ matrix.python-version }} | |
| path: test-results.xml | |
| - name: Upload coverage report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-py${{ matrix.python-version }} | |
| path: coverage.xml | |
| # --------------------------------------------------------------------------- | |
| # 2. Source distribution | |
| # --------------------------------------------------------------------------- | |
| build-sdist: | |
| name: Build sdist | |
| needs: test | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Build source distribution | |
| run: | | |
| pip install build | |
| python -m build --sdist | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: sdist | |
| path: dist/*.tar.gz | |
| # --------------------------------------------------------------------------- | |
| # 3. Binary wheels via cibuildwheel | |
| # Linux: manylinux_2_28 x86_64 + aarch64 (QEMU), musllinux x86_64 | |
| # macOS: x86_64 + arm64 | |
| # Windows: AMD64 | |
| # --------------------------------------------------------------------------- | |
| build-wheels: | |
| name: "Wheels – ${{ matrix.os }}" | |
| needs: test | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up QEMU (Linux aarch64 cross-compilation) | |
| if: matrix.os == 'ubuntu-latest' | |
| uses: docker/setup-qemu-action@v3 | |
| with: | |
| platforms: arm64 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Install cibuildwheel | |
| run: pip install cibuildwheel==2.21.3 | |
| - name: Build wheels | |
| env: | |
| CIBW_BUILD: "cp310-* cp311-* cp312-* cp313-*" | |
| CIBW_SKIP: "*-win32 *-manylinux_i686 pp*" | |
| CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28 | |
| CIBW_MANYLINUX_AARCH64_IMAGE: manylinux_2_28 | |
| CIBW_ARCHS_LINUX: "x86_64 aarch64" | |
| CIBW_ARCHS_MACOS: "x86_64 arm64" | |
| CIBW_ARCHS_WINDOWS: "AMD64" | |
| CIBW_ENVIRONMENT_LINUX: "CFLAGS='-fopenmp' LDFLAGS='-lgomp'" | |
| CIBW_TEST_COMMAND: > | |
| python -c " | |
| import metbit; | |
| assert metbit.__version__, 'version missing'; | |
| from metbit._native import pearson_columns, backend_info; | |
| import numpy as np; | |
| r = pearson_columns(np.eye(5), anchor_index=0); | |
| assert abs(r[0] - 1.0) < 1e-12, r[0]; | |
| print('OK - backend:', backend_info()) | |
| " | |
| CIBW_TEST_REQUIRES: "numpy scipy scikit-learn" | |
| CIBW_BUILD_VERBOSITY: 1 | |
| run: cibuildwheel --output-dir wheelhouse | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: wheels-${{ matrix.os }} | |
| path: wheelhouse/*.whl | |
| # --------------------------------------------------------------------------- | |
| # 4. Verify all artifacts: integrity check + security scan | |
| # --------------------------------------------------------------------------- | |
| verify: | |
| name: Verify artifacts | |
| needs: [build-sdist, build-wheels] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Download all built artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: "sdist wheels-*" | |
| path: dist/ | |
| merge-multiple: true | |
| - name: List artifacts | |
| run: ls -lh dist/ | |
| - name: Integrity check (twine) | |
| run: | | |
| pip install twine | |
| twine check dist/* | |
| - name: Security scan (Trivy, warn only) | |
| uses: aquasecurity/trivy-action@v0.36.0 | |
| with: | |
| scan-type: fs | |
| scan-ref: . | |
| severity: "CRITICAL,HIGH" | |
| exit-code: "0" | |
| # --------------------------------------------------------------------------- | |
| # 5. Publish to PyPI (OIDC trusted publishing - no API token needed) | |
| # --------------------------------------------------------------------------- | |
| publish-pypi: | |
| name: Publish to PyPI | |
| needs: verify | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: pypi | |
| url: https://pypi.org/project/metbit/${{ env.RELEASE_TAG }} | |
| steps: | |
| - name: Download all built artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: "sdist wheels-*" | |
| path: dist/ | |
| merge-multiple: true | |
| - name: Publish to PyPI | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| packages-dir: dist/ | |
| skip-existing: false | |
| # --------------------------------------------------------------------------- | |
| # 6. Create GitHub Release and attach all artifacts | |
| # --------------------------------------------------------------------------- | |
| github-release: | |
| name: GitHub Release | |
| needs: publish-pypi | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Extract changelog section for this version | |
| id: changelog | |
| shell: bash | |
| run: | | |
| VERSION="${RELEASE_TAG#v}" | |
| NOTES=$(awk "/^## \[${VERSION}\]/{found=1; next} found && /^## \[/{exit} found{print}" CHANGELOG.md) | |
| { | |
| echo "notes<<EOF" | |
| echo "$NOTES" | |
| echo "EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| env: | |
| RELEASE_TAG: ${{ env.RELEASE_TAG }} | |
| - name: Download all built artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: "sdist wheels-*" | |
| path: release-assets/ | |
| merge-multiple: true | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ env.RELEASE_TAG }} | |
| name: "metbit ${{ env.RELEASE_TAG }}" | |
| body: ${{ steps.changelog.outputs.notes }} | |
| draft: false | |
| prerelease: false | |
| files: release-assets/* | |
| generate_release_notes: true |