Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 71 additions & 10 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,79 @@ jobs:
path: dist/*.tar.gz
if-no-files-found: error

publish:
publish_testpypi:
needs: [install_test, build_sdist]
if: github.event_name == 'workflow_dispatch' && inputs['publish-testpypi'] == true
runs-on: ubuntu-24.04
environment: testpypi
permissions:
contents: read
id-token: write # TestPyPI trusted publishing (OIDC)
attestations: write
steps:
- uses: actions/download-artifact@v8.0.1
with:
name: sdist
path: dist

- uses: actions/download-artifact@v8.0.1
with:
pattern: wheels-*
path: dist
merge-multiple: true

- name: verify release artifacts before publish
shell: bash
run: |
shopt -s nullglob

sdists=(dist/pyrewire-*.tar.gz)
if [ "${#sdists[@]}" -ne 1 ]; then
echo "expected exactly one pyrewire sdist, found ${#sdists[@]}"
printf '%s\n' "${sdists[@]}"
exit 1
fi

expected=()
for py_tag in cp311 cp312 cp313 cp314; do
expected+=("pyrewire-*-${py_tag}-${py_tag}-*manylinux_2_28_x86_64.whl")
expected+=("pyrewire-*-${py_tag}-${py_tag}-macosx_*_arm64.whl")
expected+=("pyrewire-*-${py_tag}-${py_tag}-win_amd64.whl")
done

for pattern in "${expected[@]}"; do
matches=(dist/$pattern)
if [ "${#matches[@]}" -ne 1 ]; then
echo "expected exactly one wheel matching $pattern, found ${#matches[@]}"
ls -la dist
exit 1
fi
done

wheels=(dist/pyrewire-*.whl)
if [ "${#wheels[@]}" -ne "${#expected[@]}" ]; then
echo "expected ${#expected[@]} wheels, found ${#wheels[@]}"
ls -la dist
exit 1
fi

- name: attest release artifacts
uses: actions/attest@v4
with:
subject-path: |
dist/pyrewire-*.whl
dist/pyrewire-*.tar.gz

- name: publish to TestPyPI (trusted publishing)
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/

publish_pypi:
needs: [install_test, build_sdist]
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-24.04
environment: pypi
permissions:
contents: write
id-token: write # PyPI trusted publishing (OIDC)
Expand Down Expand Up @@ -306,14 +376,7 @@ jobs:
dist/pyrewire-*.whl
dist/pyrewire-*.tar.gz

- name: publish to TestPyPI (trusted publishing)
if: github.event_name == 'workflow_dispatch' && inputs['publish-testpypi'] == true
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/

- name: extract GitHub Release notes
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
run: |
tag="${GITHUB_REF_NAME#v}"
python scripts/ci/extract_changelog_section.py \
Expand All @@ -322,11 +385,9 @@ jobs:
"$RUNNER_TEMP/release-notes.md"

- name: publish to PyPI (trusted publishing)
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
uses: pypa/gh-action-pypi-publish@release/v1

- name: create GitHub Release
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@v3
with:
files: dist/*
Expand Down
6 changes: 3 additions & 3 deletions docs/release-candidate-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ Record that SHA in the release issue or release PR. The `v1.0.0` tag must point
| Wheel workflow | Packaging owner | Workflow: `.github/workflows/wheels.yml` on the frozen release commit. | Successful GitHub Actions run URL for `wheels.yml`, including Python 3.11, 3.12, 3.13, and 3.14 wheel jobs. | Link a GitHub issue, PR, or follow-up task before retrying or tagging. |
| Code scanning alerts | Security owner | Manual verification in GitHub Security that code-scanning alerts are clean (no open alerts) for the default branch and frozen release commit. | Linked screenshot or URL showing no open code-scanning alerts at RC time. | Link a GitHub issue, PR, or follow-up task before retrying or tagging. |
| Dependabot coverage | Release manager | Manual verification of `.github/dependabot.yml`: Dependabot is configured for both `github-actions` and `pip` at `/` on a regular schedule. | Linked review note referencing the merged `.github/dependabot.yml` and both ecosystems. | Link a GitHub issue, PR, or follow-up task before retrying or tagging. |
| Release workflow dry run review | Release manager | Manual verification of `.github/workflows/release.yml`: it builds wheels, runs dynamic-link verification, performs clean install tests, verifies release-local wheels and sdist artifacts before publish, uses trusted publishing via OIDC, keeps least-privilege top-level permissions, restricts `id-token: write` to the publish job, and publishes only after tag-triggered gates pass. Do not actually tag or publish during RC validation. | Linked review note confirming the tag-triggered `release.yml` gates, least-privilege/OIDC scope, and no pre-tag publish occurred. | Link a GitHub issue, PR, or follow-up task before retrying or tagging. |
| Release workflow dry run review | Release manager | Manual verification of `.github/workflows/release.yml`: it builds wheels, runs dynamic-link verification, performs clean install tests, verifies release-local wheels and sdist artifacts before publish, uses trusted publishing via OIDC, keeps least-privilege top-level permissions, restricts `id-token: write` to the publish jobs, separates TestPyPI and production PyPI trusted publishing into explicit `testpypi` and `pypi` GitHub environments, and publishes production only after tag-triggered gates pass. Do not actually tag or publish production during RC validation. | Linked review note confirming the tag-triggered `release.yml` gates, least-privilege/OIDC scope, separate publish environments, and no pre-tag production publish occurred. | Link a GitHub issue, PR, or follow-up task before retrying or tagging. |
| TestPyPI dry run evidence | Release manager + Packaging owner | Manual verification: on the frozen RC commit, manually dispatch `.github/workflows/release.yml` with `publish-testpypi: true` and complete a TestPyPI end-to-end dry run before any production tag push. Use install command shape: `python -m pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple pyrewire==<candidate-version>`. | Frozen commit SHA, successful `release.yml` workflow run URL, TestPyPI project/version URL, artifact filenames and candidate version, SHA256 hashes, successful TestPyPI upload logs, clean install command logs/results for Linux/macOS/Windows supported Python versions, import smoke output for `pyrewire.__version__` and `pyrewire.wirelog_version()`, confirmation wheel install uses bundled `libwirelog` with no system `libwirelog`, and documented sdist behavior if any sdist install behavior is intentional. Production PyPI release remains gated on this dry-run evidence. | Link a GitHub issue, PR, or follow-up task before retrying or tagging. |
| Artifact attestation verification | Release manager | Workflow: `.github/workflows/release.yml` publish job generates release artifact attestations using `actions/attest@v4`. For tagged release artifacts, command: `gh attestation verify <artifact> -R semantic-reasoning/PyreWire --signer-workflow semantic-reasoning/PyreWire/.github/workflows/release.yml --source-ref refs/tags/v1.0.0` for every release wheel (`dist/pyrewire-*.whl`) and the sdist (`dist/pyrewire-*.tar.gz`). For RC/frozen-commit validation before the final tag exists, also verify with `--source-digest <frozen-sha>`. Repo-only `-R` verification alone is not sufficient for this gate. | Successful `release.yml` run URL, release tag or frozen RC commit SHA, artifact filenames, SHA256 hashes for each wheel/sdist, and successful `gh attestation verify` output showing enforced signer workflow and source identity (`--source-ref refs/tags/v1.0.0` or `--source-digest <frozen-sha>`) for each verified artifact. | Link a GitHub issue, PR, or follow-up task before retrying or tagging. |
| Artifact attestation verification | Release manager | Workflow: `.github/workflows/release.yml` publish jobs generate release artifact attestations using `actions/attest@v4`. For tagged release artifacts, command: `gh attestation verify <artifact> -R semantic-reasoning/PyreWire --signer-workflow semantic-reasoning/PyreWire/.github/workflows/release.yml --source-ref refs/tags/v1.0.0` for every release wheel (`dist/pyrewire-*.whl`) and the sdist (`dist/pyrewire-*.tar.gz`). For RC/frozen-commit validation before the final tag exists, also verify with `--source-digest <frozen-sha>`. Repo-only `-R` verification alone is not sufficient for this gate. | Successful `release.yml` run URL, release tag or frozen RC commit SHA, artifact filenames, SHA256 hashes for each wheel/sdist, and successful `gh attestation verify` output showing enforced signer workflow and source identity (`--source-ref refs/tags/v1.0.0` or `--source-digest <frozen-sha>`) for each verified artifact. | Link a GitHub issue, PR, or follow-up task before retrying or tagging. |
| Wheel dynamic-link check | Packaging owner | Command: `python scripts/ci/check_dynamic_link.py wheelhouse/*.whl`. | Passing command log for the release wheel artifacts. | Link a GitHub issue, PR, or follow-up task before retrying or tagging. |
| Clean wheel install | Bindings owner | Manual verification in a clean environment with no system `libwirelog`: install the candidate wheel, import `pyrewire`, confirm PyreWire version, confirm bundled wirelog version, and run integration tests. | Environment description, install command log, import/version log, wirelog version log, and integration test log. | Link a GitHub issue, PR, or follow-up task before retrying or tagging. |
| Release notes and changelog | Release manager | Manual verification that the v1.0.0 changelog section is extractable and matches the GitHub Release body generated from `CHANGELOG.md`. | Extracted release notes artifact or command log plus reviewer confirmation. | Link a GitHub issue, PR, or follow-up task before retrying or tagging. |
| Metadata, API, support, and security consistency | Release manager | Manual verification that package metadata, public API stability, support matrix, and security policy all describe the same v1.0.0 contract. | Linked review note covering `pyproject.toml`, API stability docs, support docs, and `SECURITY.md`. | Link a GitHub issue, PR, or follow-up task before retrying or tagging. |

Attestation verification for this gate requires signer workflow and source identity constraints; repo-only verification is not treated as sufficient provenance evidence for PyreWire release artifacts.
This does not independently attest upstream wirelog builds. For bundled wirelog traceability, rely on the pinned wirelog v0.50.0 SHA `272edf3a24b25676f12c4b843d55510f5048dd2f` in release configuration and docs, plus the wheel dynamic-link and clean-install gates above.
External blocker for the TestPyPI dry run gate: TestPyPI trusted publishing must be configured for the `.github/workflows/release.yml` workflow identity before dry-run uploads can pass. If TestPyPI requires an environment for trusted publishing, the workflow configuration must match that environment.
External blocker for the TestPyPI dry run gate: TestPyPI trusted publishing must be configured for the `.github/workflows/release.yml` workflow identity with GitHub environment `testpypi` before dry-run uploads can pass.
11 changes: 6 additions & 5 deletions tests/docs/test_release_candidate_checklist.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,11 @@ def test_required_workflows_and_release_workflow_guards_are_documented():
"verifies release-local wheels and sdist artifacts before publish",
"trusted publishing via OIDC",
"least-privilege top-level permissions",
"restricts `id-token: write` to the publish job",
"publishes only after tag-triggered gates pass",
"Do not actually tag or publish",
"restricts `id-token: write` to the publish jobs",
"separates TestPyPI and production PyPI trusted publishing into explicit "
"`testpypi` and `pypi` GitHub environments",
"publishes production only after tag-triggered gates pass",
"Do not actually tag or publish production",
):
assert release_guard in text

Expand Down Expand Up @@ -155,8 +157,7 @@ def test_testpypi_dry_run_gate_and_evidence_requirements_are_documented():
"Production PyPI release remains gated on this dry-run evidence",
"TestPyPI trusted publishing must be configured",
"workflow identity",
"If TestPyPI requires an environment for trusted publishing, the workflow "
"configuration must match that environment",
"GitHub environment `testpypi`",
):
assert required in text

Expand Down
Loading