Release from main (dry run) #2
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
| name: Release Publish | |
| run-name: Release from main${{ (github.event_name == 'pull_request' || inputs.dry_run) && ' (dry run)' || '' }} | |
| on: | |
| pull_request: | |
| paths: | |
| - ".github/actions/release-smoke-package/action.yml" | |
| - ".github/workflows/release-publish.yml" | |
| workflow_dispatch: | |
| inputs: | |
| dry_run: | |
| description: "Verify release inputs and artifacts without publishing to package indexes or creating a release" | |
| required: true | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: release-publish-main-${{ (github.event_name == 'pull_request' || inputs.dry_run) && 'dry-run' || 'publish' }} | |
| cancel-in-progress: false | |
| jobs: | |
| collect_artifacts: | |
| name: Collect release artifacts | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| actions: read | |
| contents: read | |
| outputs: | |
| build_run_id: ${{ steps.resolve_build.outputs.run_id }} | |
| release_sha: ${{ steps.resolve_build.outputs.sha }} | |
| version: ${{ steps.validate_versions.outputs.version }} | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| with: | |
| ref: main | |
| - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 | |
| with: | |
| python-version: "3.11" | |
| - name: Validate checked-in versions | |
| id: validate_versions | |
| run: | | |
| set -euo pipefail | |
| python - <<'PY' | |
| import ast | |
| import os | |
| import pathlib | |
| import re | |
| import tomllib | |
| pyproject_version = tomllib.loads(pathlib.Path("pyproject.toml").read_text())["project"]["version"] | |
| service_tree = ast.parse(pathlib.Path("temporalio/service.py").read_text()) | |
| service_version = None | |
| for stmt in service_tree.body: | |
| if ( | |
| isinstance(stmt, ast.Assign) | |
| and any(isinstance(target, ast.Name) and target.id == "__version__" for target in stmt.targets) | |
| and isinstance(stmt.value, ast.Constant) | |
| and isinstance(stmt.value.value, str) | |
| ): | |
| service_version = stmt.value.value | |
| break | |
| if pyproject_version != service_version: | |
| raise SystemExit( | |
| f"pyproject.toml version {pyproject_version!r} does not match " | |
| f"temporalio/service.py version {service_version!r}" | |
| ) | |
| if pyproject_version.startswith("v"): | |
| raise SystemExit("Checked-in version must not start with 'v'") | |
| if not re.fullmatch(r"[0-9]+(?:\.[0-9]+)+(?:[a-zA-Z0-9_.+-]+)?", pyproject_version): | |
| raise SystemExit(f"Invalid checked-in version: {pyproject_version!r}") | |
| with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output: | |
| print(f"version={pyproject_version}", file=output) | |
| PY | |
| - name: Resolve latest successful Build Binaries run on main | |
| id: resolve_build | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| main_sha="$(git rev-parse HEAD)" | |
| run_json="$(gh run list \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --workflow build-binaries.yml \ | |
| --branch main \ | |
| --status success \ | |
| --json databaseId,headSha,url \ | |
| --limit 1)" | |
| run_id="$(jq -r '.[0].databaseId // empty' <<<"$run_json")" | |
| run_sha="$(jq -r '.[0].headSha // empty' <<<"$run_json")" | |
| run_url="$(jq -r '.[0].url // empty' <<<"$run_json")" | |
| if [[ -z "$run_id" ]]; then | |
| echo "No successful Build Binaries run found on main" >&2 | |
| exit 1 | |
| fi | |
| if [[ "$run_sha" != "$main_sha" ]]; then | |
| echo "Latest successful Build Binaries run is not for current main" >&2 | |
| echo "main SHA: $main_sha" >&2 | |
| echo "run SHA: $run_sha" >&2 | |
| echo "run URL: $run_url" >&2 | |
| exit 1 | |
| fi | |
| echo "Using Build Binaries run $run_id at $run_sha" | |
| echo "run_id=$run_id" >>"$GITHUB_OUTPUT" | |
| echo "sha=$main_sha" >>"$GITHUB_OUTPUT" | |
| - name: Download and flatten artifacts | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| RUN_ID: ${{ steps.resolve_build.outputs.run_id }} | |
| run: | | |
| set -euo pipefail | |
| mkdir artifacts dist | |
| gh run download "$RUN_ID" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --dir artifacts \ | |
| --pattern 'packages-*' | |
| while IFS= read -r -d '' file; do | |
| dest="dist/$(basename "$file")" | |
| if [[ -e "$dest" ]]; then | |
| echo "Duplicate distribution filename: $(basename "$file")" >&2 | |
| exit 1 | |
| fi | |
| cp "$file" "$dest" | |
| done < <(find artifacts -type f \( -name '*.whl' -o -name '*.tar.gz' \) -print0) | |
| - name: Verify release artifacts | |
| env: | |
| VERSION: ${{ steps.validate_versions.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| python - <<'PY' | |
| import os | |
| import pathlib | |
| version = os.environ["VERSION"] | |
| dist_dir = pathlib.Path("dist") | |
| files = sorted(path.name for path in dist_dir.iterdir() if path.is_file()) | |
| wheels = [name for name in files if name.endswith(".whl")] | |
| sdists = [name for name in files if name.endswith(".tar.gz")] | |
| if len(files) != len(set(files)): | |
| raise SystemExit("Duplicate distribution filenames found") | |
| expected_sdist = f"temporalio-{version}.tar.gz" | |
| if sdists != [expected_sdist]: | |
| raise SystemExit(f"Expected only sdist {expected_sdist!r}, found {sdists!r}") | |
| if len(wheels) != 5: | |
| raise SystemExit(f"Expected 5 platform wheels, found {len(wheels)}: {wheels!r}") | |
| for name in files: | |
| if not name.startswith(f"temporalio-{version}"): | |
| raise SystemExit(f"Distribution filename does not match requested version {version!r}: {name}") | |
| expected_platforms = { | |
| "linux-x86_64": lambda name: "manylinux" in name and "x86_64" in name, | |
| "linux-aarch64": lambda name: "manylinux" in name and "aarch64" in name, | |
| "macos-x86_64": lambda name: "macosx" in name and "x86_64" in name, | |
| "macos-arm64": lambda name: "macosx" in name and "arm64" in name, | |
| "windows-amd64": lambda name: "win_amd64" in name, | |
| } | |
| missing = [ | |
| platform | |
| for platform, predicate in expected_platforms.items() | |
| if not any(predicate(name) for name in wheels) | |
| ] | |
| if missing: | |
| raise SystemExit(f"Missing expected platform wheels: {missing!r}; found {wheels!r}") | |
| print("Verified release artifacts:") | |
| for name in files: | |
| print(f" {name}") | |
| PY | |
| - name: Upload verified release artifact | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: release-dist-${{ steps.validate_versions.outputs.version }} | |
| path: dist | |
| if-no-files-found: error | |
| retention-days: 14 | |
| publish_testpypi: | |
| name: Publish to TestPyPI | |
| if: ${{ github.event_name != 'pull_request' && !inputs.dry_run }} | |
| needs: collect_artifacts | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| environment: testpypi | |
| env: | |
| VERSION: ${{ needs.collect_artifacts.outputs.version }} | |
| permissions: | |
| actions: read | |
| contents: read | |
| id-token: write | |
| steps: | |
| - name: Download verified release artifact | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| mkdir downloaded dist | |
| gh run download "$GITHUB_RUN_ID" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --name "release-dist-$VERSION" \ | |
| --dir downloaded | |
| while IFS= read -r -d '' file; do | |
| cp "$file" "dist/$(basename "$file")" | |
| done < <(find downloaded -type f \( -name '*.whl' -o -name '*.tar.gz' \) -print0) | |
| test "$(find dist -type f | wc -l)" -gt 0 | |
| - name: Publish to TestPyPI | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| packages-dir: dist/ | |
| repository-url: https://test.pypi.org/legacy/ | |
| skip-existing: true | |
| smoke_testpypi: | |
| name: Smoke test TestPyPI package | |
| if: ${{ github.event_name != 'pull_request' && !inputs.dry_run }} | |
| needs: | |
| - collect_artifacts | |
| - publish_testpypi | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| env: | |
| VERSION: ${{ needs.collect_artifacts.outputs.version }} | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| - uses: ./.github/actions/release-smoke-package | |
| with: | |
| version: ${{ env.VERSION }} | |
| index-url: https://test.pypi.org/simple/ | |
| extra-index-url: https://pypi.org/simple/ | |
| publish_pypi: | |
| name: Publish to PyPI | |
| if: ${{ github.event_name != 'pull_request' && !inputs.dry_run }} | |
| needs: | |
| - collect_artifacts | |
| - smoke_testpypi | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| environment: pypi | |
| env: | |
| VERSION: ${{ needs.collect_artifacts.outputs.version }} | |
| permissions: | |
| actions: read | |
| contents: read | |
| id-token: write | |
| steps: | |
| - name: Download verified release artifact | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| mkdir downloaded dist | |
| gh run download "$GITHUB_RUN_ID" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --name "release-dist-$VERSION" \ | |
| --dir downloaded | |
| while IFS= read -r -d '' file; do | |
| cp "$file" "dist/$(basename "$file")" | |
| done < <(find downloaded -type f \( -name '*.whl' -o -name '*.tar.gz' \) -print0) | |
| test "$(find dist -type f | wc -l)" -gt 0 | |
| - name: Publish to PyPI | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| packages-dir: dist/ | |
| smoke_pypi: | |
| name: Smoke test PyPI package | |
| if: ${{ github.event_name != 'pull_request' && !inputs.dry_run }} | |
| needs: | |
| - collect_artifacts | |
| - publish_pypi | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| env: | |
| VERSION: ${{ needs.collect_artifacts.outputs.version }} | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| - uses: ./.github/actions/release-smoke-package | |
| with: | |
| version: ${{ env.VERSION }} | |
| index-url: https://pypi.org/simple/ | |
| create_draft_release: | |
| name: Create draft GitHub Release | |
| if: ${{ github.event_name != 'pull_request' && !inputs.dry_run }} | |
| needs: | |
| - collect_artifacts | |
| - smoke_pypi | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| env: | |
| VERSION: ${{ needs.collect_artifacts.outputs.version }} | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Create draft release with generated notes | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| RELEASE_SHA: ${{ needs.collect_artifacts.outputs.release_sha }} | |
| run: | | |
| set -euo pipefail | |
| gh release create "$VERSION" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --target "$RELEASE_SHA" \ | |
| --title "$VERSION" \ | |
| --draft \ | |
| --generate-notes |