Merge pull request #137 from SMI-Lab-Inha/chore/bump-1.17.0 #18
Workflow file for this run
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
| # PyPI publish workflow. | |
| # | |
| # Fires when a tag matching ``v*.*.*`` is pushed (e.g. ``v1.7.0``). | |
| # Builds the sdist + wheel, runs the same smoke tests the release | |
| # workflow does, requires that the external-data validation workflow | |
| # already passed on this exact commit, and — only if all gates pass — | |
| # publishes to PyPI via Trusted Publishing. | |
| # | |
| # Release-integrity gate | |
| # ---------------------- | |
| # | |
| # Publishing is blocked unless ``validation.yml`` ("Validation | |
| # (external data)") has a *successful* run recorded against the commit | |
| # being tagged. That workflow clones the manifest-pinned upstream | |
| # decks and runs ``pytest -m integration`` hard-fail, enforcing the | |
| # published 0.01 % tolerance claim. The ``validation-gate`` job below | |
| # queries the Actions API for that run; a tag with no green validation | |
| # run on its commit cannot reach PyPI. The required ordering is: run | |
| # "Validation (external data)" on the release commit, confirm it is | |
| # green, *then* push the ``vX.Y.Z`` tag (see the release checklist). | |
| # | |
| # Trusted Publishing setup (one-time, maintainer-side): | |
| # 1. Register the project name ``pybmodes`` on PyPI. | |
| # 2. Under Publishing -> Trusted Publishers, add a new GitHub | |
| # publisher with: | |
| # owner = SMI-Lab-Inha | |
| # repository = pyBModes | |
| # workflow = publish.yml | |
| # environment = pypi | |
| # 3. Create a GitHub environment named ``pypi`` in the repo settings | |
| # with required-reviewer protection if desired. | |
| # | |
| # After step 3 a tag push triggers an automatic, token-less publish. | |
| # Until the maintainer completes setup, the publish step in this | |
| # workflow is a no-op: build + smoke runs, ``pypa/gh-action-pypi-publish`` | |
| # fails the auth check and the workflow exits non-zero. Drop the tag | |
| # and re-tag once setup is complete; nothing has been published. | |
| name: Publish to PyPI | |
| on: | |
| push: | |
| tags: | |
| - "v*.*.*" | |
| permissions: | |
| contents: read | |
| jobs: | |
| build-and-smoke: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| - name: Install build deps | |
| run: pip install --upgrade pip build | |
| - name: Confirm tag matches pyproject.toml version | |
| run: | | |
| tag="${GITHUB_REF#refs/tags/v}" | |
| pyproject=$(grep '^version = ' pyproject.toml | head -1 | sed -E 's/version = "([^"]+)"/\1/') | |
| echo "tag: ${tag}" | |
| echo "pyproject: ${pyproject}" | |
| if [ "${tag}" != "${pyproject}" ]; then | |
| echo "::error::tag and pyproject.toml version disagree — refusing to publish." | |
| exit 1 | |
| fi | |
| - name: Build sdist + wheel | |
| run: python -m build | |
| - name: Install wheel into a fresh venv + version sanity | |
| run: | | |
| python -m venv smoke | |
| ./smoke/bin/pip install --upgrade pip | |
| ./smoke/bin/pip install dist/pybmodes-*.whl | |
| cd /tmp | |
| actual=$($GITHUB_WORKSPACE/smoke/bin/python -c "import pybmodes; print(pybmodes.__version__)") | |
| expected="${GITHUB_REF#refs/tags/v}" | |
| echo "expected: ${expected}" | |
| echo "actual: ${actual}" | |
| test "${expected}" = "${actual}" | |
| - name: Install sdist into a fresh venv (catches MANIFEST.in gaps) | |
| run: | | |
| python -m venv sdist-venv | |
| ./sdist-venv/bin/pip install --upgrade pip | |
| ./sdist-venv/bin/pip install dist/pybmodes-*.tar.gz | |
| cd /tmp | |
| $GITHUB_WORKSPACE/sdist-venv/bin/python -c "import pybmodes; print(pybmodes.__version__)" | |
| - name: Upload built artifacts for the publish job | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: dist | |
| path: dist/ | |
| retention-days: 7 | |
| validation-gate: | |
| name: Require a passing validation run on this commit | |
| runs-on: ubuntu-latest | |
| permissions: | |
| # ``actions: read`` is needed to query workflow-run history via | |
| # the Actions API; ``contents: read`` for the implicit checkout | |
| # context. No write scopes — this job only inspects. | |
| contents: read | |
| actions: read | |
| steps: | |
| - name: Assert validation.yml succeeded for this commit | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| GH_REPO: ${{ github.repository }} | |
| # ``gh run list --commit`` filters runs by the head SHA they | |
| # executed against. A maintainer runs "Validation (external | |
| # data)" on the release commit *before* tagging; this asserts | |
| # that green run exists for ``github.sha`` (the commit the tag | |
| # points at). If it doesn't, publishing fails closed. | |
| run: | | |
| sha="${GITHUB_SHA}" | |
| echo "Looking for a successful 'Validation (external data)' run on ${sha}…" | |
| count=$(gh run list \ | |
| --workflow validation.yml \ | |
| --commit "${sha}" \ | |
| --status success \ | |
| --limit 50 \ | |
| --json databaseId \ | |
| --jq 'length') | |
| echo "matching successful validation runs: ${count}" | |
| if [ "${count}" -lt 1 ]; then | |
| echo "::error::No successful validation.yml run found for commit ${sha}." | |
| echo "::error::Run 'Validation (external data)' on this exact commit (Actions tab" | |
| echo "::error::→ Run workflow), confirm it is green, then re-tag. Refusing to publish." | |
| exit 1 | |
| fi | |
| echo "Validation gate satisfied: ${count} successful run(s) on ${sha}." | |
| publish: | |
| needs: [build-and-smoke, validation-gate] | |
| runs-on: ubuntu-latest | |
| # Trusted Publishing requires an environment with the same name as | |
| # the publisher's environment field in the PyPI config. | |
| environment: | |
| name: pypi | |
| url: https://pypi.org/project/pybmodes/ | |
| permissions: | |
| # ``id-token: write`` is the entire auth — no API token, no | |
| # secret. PyPI verifies the OIDC claim from GitHub Actions | |
| # against the Trusted Publisher config. | |
| id-token: write | |
| steps: | |
| - name: Download the built distributions | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: dist | |
| path: dist/ | |
| - name: Publish to PyPI | |
| # The official PyPA action — handles the Trusted Publishing | |
| # handshake and uploads dist/* to PyPI. | |
| uses: pypa/gh-action-pypi-publish@release/v1 |