From 3eb4994c779ce08d296425dae29bd823f9315114 Mon Sep 17 00:00:00 2001 From: Nathan Gillett Date: Sat, 30 May 2026 18:36:32 -0500 Subject: [PATCH] Restore PyPI release workflow for v0.1 tags Publish to PyPI on v* tags after CI-parity tests; sign via tools release-build-sign workflow on main. --- .github/workflows/release-pypi.yml | 258 +++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 .github/workflows/release-pypi.yml diff --git a/.github/workflows/release-pypi.yml b/.github/workflows/release-pypi.yml new file mode 100644 index 0000000..019ffb6 --- /dev/null +++ b/.github/workflows/release-pypi.yml @@ -0,0 +1,258 @@ +name: release pypi + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + release_ref: + description: Git ref to build and publish from. + required: true + default: refs/heads/main + type: string + release_version: + description: SemVer version for dry-run metadata (without v prefix). + required: true + default: 0.0.0-dryrun + type: string + publish_to_pypi: + description: Publish to PyPI with PEP 740 attestations (requires trusted publisher). + required: true + default: true + type: boolean + +permissions: + contents: write + id-token: write + +jobs: + test: + name: "IntentProof Release: Test Python Package" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ github.event_name == 'workflow_dispatch' && inputs.release_ref || github.ref }} + + - name: Checkout intentproof-tools (SPEC_REF source) + uses: actions/checkout@v6 + with: + repository: IntentProof/intentproof-tools + ref: main + path: intentproof-tools + + - name: Read pinned spec ref + id: spec_ref + run: | + ref="$(tr -d '[:space:]' < intentproof-tools/SPEC_REF)" + if ! echo "$ref" | grep -qE '^[0-9a-f]{40}$'; then + echo "Invalid SPEC_REF in intentproof-tools: '$ref'" >&2 + exit 1 + fi + echo "ref=$ref" >> "$GITHUB_OUTPUT" + + - name: Checkout intentproof-spec at pinned ref + uses: actions/checkout@v6 + with: + repository: IntentProof/intentproof-spec + ref: ${{ steps.spec_ref.outputs.ref }} + path: intentproof-spec + + - uses: actions/setup-python@v6 + with: + python-version: '3.11' + + - name: Install dependencies + run: pip install -e ".[dev]" + + - name: Verify sdk-signing fixtures match pinned spec + env: + INTENTPROOF_SPEC_DIR: intentproof-spec + run: bash scripts/check-sdk-signing-fixtures-sync.sh + + - name: Run tests + env: + INTENTPROOF_SPEC_DIR: intentproof-spec + run: python3 -m pytest + + build: + name: "IntentProof Release: Build Python Distributions" + needs: test + runs-on: ubuntu-latest + outputs: + artifact_paths: ${{ steps.dists.outputs.artifact_paths }} + release_ref: ${{ steps.release.outputs.release_ref }} + release_version: ${{ steps.release.outputs.release_version }} + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ github.event_name == 'workflow_dispatch' && inputs.release_ref || github.ref }} + + - uses: actions/setup-python@v6 + with: + python-version: '3.11' + + - name: Resolve release metadata + id: release + env: + INPUT_RELEASE_REF: ${{ inputs.release_ref }} + INPUT_RELEASE_VERSION: ${{ inputs.release_version }} + run: | + set -euo pipefail + semver_tag='^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$' + + if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then + release_ref="$INPUT_RELEASE_REF" + release_version="$INPUT_RELEASE_VERSION" + else + release_ref="${GITHUB_REF}" + release_version="${GITHUB_REF_NAME#v}" + if [[ ! "$release_ref" =~ $semver_tag ]]; then + echo "tag ref must match vMAJOR.MINOR.PATCH: ${release_ref}" >&2 + exit 1 + fi + fi + + { + echo "release_ref=${release_ref}" + echo "release_version=${release_version}" + } >> "$GITHUB_OUTPUT" + + - name: Sync package version + env: + RELEASE_VERSION: ${{ steps.release.outputs.release_version }} + run: | + set -euo pipefail + python3 - <<'PY' + import os + import re + from pathlib import Path + + version = os.environ["RELEASE_VERSION"] + path = Path("pyproject.toml") + text = path.read_text(encoding="utf-8") + updated, count = re.subn( + r'^version = "[^"]+"$', + f'version = "{version}"', + text, + count=1, + flags=re.MULTILINE, + ) + if count != 1: + raise SystemExit("failed to update pyproject.toml version") + path.write_text(updated, encoding="utf-8") + PY + + - name: Build Python distributions + run: | + python -m pip install --upgrade build + python -m build --outdir dist . + + - name: Export distribution paths + id: dists + run: | + set -euo pipefail + mapfile -t files < <(find dist -maxdepth 1 -type f | sort) + test "${#files[@]}" -gt 0 + { + echo "artifact_paths<> "$GITHUB_OUTPUT" + + - uses: actions/upload-artifact@v7 + with: + name: python-release-package + path: dist/* + + publish: + name: "IntentProof Release: Publish Python Package" + needs: build + if: github.event_name != 'workflow_dispatch' || inputs.publish_to_pypi + runs-on: ubuntu-latest + permissions: + attestations: write + contents: read + id-token: write + packages: write + steps: + - uses: actions/download-artifact@v8 + with: + name: python-release-package + path: dist + + - uses: actions/setup-python@v6 + with: + python-version: '3.12' + + - name: Validate Python distributions + run: | + set -euo pipefail + python -m pip install --upgrade twine + test -d dist + twine check dist/* + + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: dist + attestations: true + + sign: + name: "IntentProof Release: Sign Python Distributions" + needs: build + permissions: + attestations: write + contents: write + id-token: write + packages: write + uses: IntentProof/intentproof-tools/.github/workflows/release-build-sign.yml@main + with: + artifact_kind: generic + subject_name: intentproof + release_version: ${{ needs.build.outputs.release_version }} + release_ref: ${{ needs.build.outputs.release_ref }} + artifact_paths: ${{ needs.build.outputs.artifact_paths }} + artifact_download_name: python-release-package + artifact_download_path: dist + attest_to_rekor: ${{ github.event_name != 'workflow_dispatch' }} + + upload-release: + name: "IntentProof Release: Publish Python Release Artifacts" + needs: + - build + - sign + - publish + if: >- + github.event_name != 'workflow_dispatch' + && needs.build.result == 'success' + && needs.sign.result == 'success' + && needs.publish.result == 'success' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/download-artifact@v8 + with: + name: python-release-package + path: release-package + + - uses: actions/download-artifact@v8 + with: + name: release-signing-metadata + path: release-signing-metadata + + - name: Upload distributions and signing metadata to GitHub Release + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + RELEASE_TAG: ${{ github.ref_name }} + run: | + set -euo pipefail + mapfile -t files < <(find release-package release-signing-metadata -type f | sort) + test "${#files[@]}" -gt 0 + + if ! gh release view "$RELEASE_TAG" >/dev/null 2>&1; then + gh release create "$RELEASE_TAG" --generate-notes + fi + gh release upload "$RELEASE_TAG" "${files[@]}" --clobber