From f8c25661c27741043aa7a9dba59a170b99ca7579 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Wed, 3 Jun 2026 09:52:27 -0700 Subject: [PATCH 1/3] Add trusted publishing release workflow --- .../actions/release-smoke-package/action.yml | 43 +++ .github/scripts/release_smoke_package.py | 59 ++++ .github/scripts/release_verify.py | 128 +++++++++ .github/workflows/build-binaries.yml | 81 ------ .github/workflows/release-publish.yml | 270 ++++++++++++++++++ 5 files changed, 500 insertions(+), 81 deletions(-) create mode 100644 .github/actions/release-smoke-package/action.yml create mode 100644 .github/scripts/release_smoke_package.py create mode 100644 .github/scripts/release_verify.py delete mode 100644 .github/workflows/build-binaries.yml create mode 100644 .github/workflows/release-publish.yml diff --git a/.github/actions/release-smoke-package/action.yml b/.github/actions/release-smoke-package/action.yml new file mode 100644 index 000000000..b4233c30d --- /dev/null +++ b/.github/actions/release-smoke-package/action.yml @@ -0,0 +1,43 @@ +name: Release package smoke test +description: Install a published temporalio package and run a minimal SDK workflow. +inputs: + version: + description: "Package version to install and verify" + required: true + index-url: + description: "Primary package index URL" + required: true + extra-index-url: + description: "Optional fallback package index URL" + required: false + default: "" +runs: + using: composite + steps: + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: "3.10" + - name: Install package + shell: bash + env: + VERSION: ${{ inputs.version }} + INDEX_URL: ${{ inputs.index-url }} + EXTRA_INDEX_URL: ${{ inputs.extra-index-url }} + run: | + set -euo pipefail + python -m venv .venv + .venv/bin/python -m pip install --upgrade pip + + install_args=(--prefer-binary --index-url "$INDEX_URL") + if [[ -n "$EXTRA_INDEX_URL" ]]; then + install_args+=(--extra-index-url "$EXTRA_INDEX_URL") + fi + + .venv/bin/python -m pip install "${install_args[@]}" "temporalio==$VERSION" + - name: Run SDK smoke test + shell: bash + env: + VERSION: ${{ inputs.version }} + run: | + set -euo pipefail + .venv/bin/python .github/scripts/release_smoke_package.py diff --git a/.github/scripts/release_smoke_package.py b/.github/scripts/release_smoke_package.py new file mode 100644 index 000000000..00a192e85 --- /dev/null +++ b/.github/scripts/release_smoke_package.py @@ -0,0 +1,59 @@ +"""Smoke test an installed temporalio release package.""" + +from __future__ import annotations + +import asyncio +import os +import uuid +from datetime import timedelta + +import temporalio +from temporalio import activity, workflow +from temporalio.testing import WorkflowEnvironment +from temporalio.worker import UnsandboxedWorkflowRunner, Worker + + +@activity.defn +async def say_hello(name: str) -> str: + return f"Hello, {name}!" + + +@workflow.defn +class SmokeWorkflow: + @workflow.run + async def run(self, name: str) -> str: + return await workflow.execute_activity( + say_hello, + name, + start_to_close_timeout=timedelta(seconds=10), + ) + + +async def main() -> None: + expected_version = os.environ["VERSION"] + if temporalio.__version__ != expected_version: + raise RuntimeError( + f"Expected temporalio {expected_version}, got {temporalio.__version__}" + ) + + task_queue = f"release-smoke-{uuid.uuid4()}" + async with await WorkflowEnvironment.start_local() as env: + async with Worker( + env.client, + task_queue=task_queue, + workflows=[SmokeWorkflow], + activities=[say_hello], + workflow_runner=UnsandboxedWorkflowRunner(), + ): + result = await env.client.execute_workflow( + SmokeWorkflow.run, + "trusted publishing", + id=task_queue, + task_queue=task_queue, + ) + if result != "Hello, trusted publishing!": + raise RuntimeError(f"Unexpected workflow result: {result!r}") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/.github/scripts/release_verify.py b/.github/scripts/release_verify.py new file mode 100644 index 000000000..b5d7a6f04 --- /dev/null +++ b/.github/scripts/release_verify.py @@ -0,0 +1,128 @@ +"""Release workflow validation helpers.""" + +from __future__ import annotations + +import argparse +import ast +import pathlib +import re +from collections.abc import Sequence + +try: + import tomllib +except ModuleNotFoundError: + import toml as tomllib # type: ignore[no-redef] + + +def _checked_in_version() -> str: + 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 RuntimeError( + f"pyproject.toml version {pyproject_version!r} does not match " + f"temporalio/service.py version {service_version!r}" + ) + if pyproject_version.startswith("v"): + raise RuntimeError("Checked-in version must not start with 'v'") + if not re.fullmatch( + r"[0-9]+(?:\.[0-9]+)+(?:[a-zA-Z0-9_.+-]+)?", pyproject_version + ): + raise RuntimeError(f"Invalid checked-in version: {pyproject_version!r}") + return pyproject_version + + +def _write_github_output(path: pathlib.Path, *, version: str, sha: str) -> None: + with path.open("a", encoding="utf-8") as output: + print(f"version={version}", file=output) + print(f"sha={sha}", file=output) + + +def validate_version(args: argparse.Namespace) -> None: + version = _checked_in_version() + if args.github_output: + _write_github_output( + pathlib.Path(args.github_output), + version=version, + sha=args.sha, + ) + else: + print(version) + + +def verify_dist(args: argparse.Namespace) -> None: + dist_dir = pathlib.Path(args.dist_dir) + 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 RuntimeError("Duplicate distribution filenames found") + expected_sdist = f"temporalio-{args.version}.tar.gz" + if sdists != [expected_sdist]: + raise RuntimeError(f"Expected only sdist {expected_sdist!r}, found {sdists!r}") + if len(wheels) != 5: + raise RuntimeError(f"Expected 5 platform wheels, found {len(wheels)}: {wheels!r}") + + for name in files: + if not name.startswith(f"temporalio-{args.version}"): + raise RuntimeError( + f"Distribution filename does not match requested version " + f"{args.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 RuntimeError(f"Missing expected platform wheels: {missing!r}; found {wheels!r}") + + print("Verified release artifacts:") + for name in files: + print(f" {name}") + + +def main(argv: Sequence[str] | None = None) -> None: + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers(required=True) + + validate_parser = subparsers.add_parser("validate-version") + validate_parser.add_argument("--sha", required=True) + validate_parser.add_argument("--github-output") + validate_parser.set_defaults(func=validate_version) + + verify_parser = subparsers.add_parser("verify-dist") + verify_parser.add_argument("--version", required=True) + verify_parser.add_argument("--dist-dir", default="dist") + verify_parser.set_defaults(func=verify_dist) + + args = parser.parse_args(argv) + args.func(args) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml deleted file mode 100644 index eb95216d0..000000000 --- a/.github/workflows/build-binaries.yml +++ /dev/null @@ -1,81 +0,0 @@ -name: Build Binaries -on: - push: - branches: - - main - - "releases/*" - - build_binaries_otel -permissions: - contents: read - -jobs: - # Compile the binaries and upload artifacts - compile-binaries: - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-latest - package-suffix: linux-amd64 - - os: ubuntu-arm - package-suffix: linux-aarch64 - runsOn: ubuntu-24.04-arm64-2-core - - os: macos-intel - package-suffix: macos-amd64 - runsOn: macos-15-intel - - os: macos-arm - package-suffix: macos-aarch64 - runsOn: macos-14 - - os: windows-latest - package-suffix: windows-amd64 - runs-on: ${{ matrix.runsOn || matrix.os }} - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - submodules: recursive - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: - python-version: "3.14" - - # Install Rust locally for non-Linux (Linux uses an internal docker - # command to build with cibuildwheel which uses rustup install defined - # in pyproject.toml) - - if: ${{ runner.os != 'Linux' }} - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable - - if: ${{ runner.os != 'Linux' }} - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - with: - cache-bin: false - workspaces: temporalio/bridge -> target - key: ${{ env.pythonLocation }} - - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8 - - run: uv sync --all-extras - - # Add the source dist only for Linux x64 for now - - if: ${{ matrix.package-suffix == 'linux-amd64' }} - run: uv build --sdist - - # Build the wheel - - run: uv run cibuildwheel --output-dir dist - - # Install the wheel in a new venv and run a test - - name: Test wheel - shell: bash - run: | - mkdir __test_wheel__ - cd __test_wheel__ - cp -r ../tests . - python -m venv .venv - bindir=bin - if [ "$RUNNER_OS" = "Windows" ]; then - bindir=Scripts - fi - ./.venv/$bindir/pip install pytest pytest_asyncio grpcio pydantic opentelemetry-api opentelemetry-sdk - ./.venv/$bindir/pip install --prefer-binary ../dist/*.whl - ./.venv/$bindir/python -m pytest -s tests/worker/test_workflow.py -k test_workflow_hello - - # Upload dist - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: packages-${{ matrix.package-suffix }} - path: dist diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml new file mode 100644 index 000000000..a4ed8326a --- /dev/null +++ b/.github/workflows/release-publish.yml @@ -0,0 +1,270 @@ +name: Release Publish +run-name: Release from main + +on: + pull_request: + paths: + - ".github/actions/release-smoke-package/action.yml" + - ".github/scripts/release_smoke_package.py" + - ".github/scripts/release_verify.py" + - ".github/workflows/release-publish.yml" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: release-publish-${{ github.ref }} + cancel-in-progress: false + +jobs: + build_binaries: + name: Build binaries (${{ matrix.package-suffix }}) + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + package-suffix: linux-amd64 + - os: ubuntu-arm + package-suffix: linux-aarch64 + runsOn: ubuntu-24.04-arm64-2-core + - os: macos-intel + package-suffix: macos-amd64 + runsOn: macos-15-intel + - os: macos-arm + package-suffix: macos-aarch64 + runsOn: macos-14 + - os: windows-latest + package-suffix: windows-amd64 + runs-on: ${{ matrix.runsOn || matrix.os }} + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + ref: ${{ github.sha }} + submodules: recursive + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: "3.14" + + # Install Rust locally for non-Linux (Linux uses an internal docker + # command to build with cibuildwheel which uses rustup install defined + # in pyproject.toml) + - if: ${{ runner.os != 'Linux' }} + uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable + - if: ${{ runner.os != 'Linux' }} + uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 + with: + cache-bin: false + workspaces: temporalio/bridge -> target + key: ${{ env.pythonLocation }} + - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8 + - run: uv sync --all-extras + + # Add the source dist only for Linux x64 for now + - if: ${{ matrix.package-suffix == 'linux-amd64' }} + run: uv build --sdist + + # Build the wheel + - run: uv run cibuildwheel --output-dir dist + + # Install the wheel in a new venv and run a test + - name: Test wheel + shell: bash + run: | + mkdir __test_wheel__ + cd __test_wheel__ + cp -r ../tests . + python -m venv .venv + bindir=bin + if [ "$RUNNER_OS" = "Windows" ]; then + bindir=Scripts + fi + ./.venv/$bindir/pip install pytest pytest_asyncio grpcio pydantic opentelemetry-api opentelemetry-sdk + ./.venv/$bindir/pip install --prefer-binary ../dist/*.whl + ./.venv/$bindir/python -m pytest -s tests/worker/test_workflow.py -k test_workflow_hello + + # Upload dist + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: packages-${{ matrix.package-suffix }} + path: dist + + verify_artifacts: + name: Verify release artifacts + needs: build_binaries + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + actions: read + contents: read + outputs: + release_sha: ${{ steps.validate_versions.outputs.sha }} + version: ${{ steps.validate_versions.outputs.version }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + ref: ${{ github.sha }} + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: "3.11" + - name: Validate checked-in versions + id: validate_versions + run: | + set -euo pipefail + python .github/scripts/release_verify.py validate-version \ + --sha "$(git rev-parse HEAD)" \ + --github-output "$GITHUB_OUTPUT" + - name: Download and flatten artifacts + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + mkdir artifacts dist + gh run download "$GITHUB_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 .github/scripts/release_verify.py verify-dist --version "$VERSION" + - 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 + needs: verify_artifacts + runs-on: ubuntu-latest + timeout-minutes: 10 + environment: testpypi + permissions: + actions: read + contents: read + id-token: write + steps: + - name: Download verified release artifact + env: + GH_TOKEN: ${{ github.token }} + VERSION: ${{ needs.verify_artifacts.outputs.version }} + 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@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1 + with: + packages-dir: dist/ + repository-url: https://test.pypi.org/legacy/ + skip-existing: true + + smoke_testpypi: + name: Smoke test TestPyPI package + needs: + - verify_artifacts + - publish_testpypi + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: ./.github/actions/release-smoke-package + with: + version: ${{ needs.verify_artifacts.outputs.version }} + index-url: https://test.pypi.org/simple/ + extra-index-url: https://pypi.org/simple/ + + publish_pypi: + name: Publish to PyPI + needs: + - verify_artifacts + - smoke_testpypi + runs-on: ubuntu-latest + timeout-minutes: 10 + environment: pypi + permissions: + actions: read + contents: read + id-token: write + steps: + - name: Download verified release artifact + env: + GH_TOKEN: ${{ github.token }} + VERSION: ${{ needs.verify_artifacts.outputs.version }} + 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@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1 + with: + packages-dir: dist/ + + smoke_pypi: + name: Smoke test PyPI package + needs: + - verify_artifacts + - publish_pypi + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: ./.github/actions/release-smoke-package + with: + version: ${{ needs.verify_artifacts.outputs.version }} + index-url: https://pypi.org/simple/ + + create_draft_release: + name: Create draft GitHub Release + needs: + - verify_artifacts + - smoke_pypi + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: write + steps: + - name: Create draft release with generated notes + env: + GH_TOKEN: ${{ github.token }} + RELEASE_SHA: ${{ needs.verify_artifacts.outputs.release_sha }} + VERSION: ${{ needs.verify_artifacts.outputs.version }} + run: | + set -euo pipefail + gh release create "$VERSION" \ + --repo "$GITHUB_REPOSITORY" \ + --target "$RELEASE_SHA" \ + --title "$VERSION" \ + --draft \ + --generate-notes From ddf189adba26fae3778d425304c5afc7a4f206bd Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Wed, 3 Jun 2026 13:18:17 -0700 Subject: [PATCH 2/3] Format release verification script --- .github/scripts/release_verify.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/scripts/release_verify.py b/.github/scripts/release_verify.py index b5d7a6f04..7f69f2408 100644 --- a/.github/scripts/release_verify.py +++ b/.github/scripts/release_verify.py @@ -40,9 +40,7 @@ def _checked_in_version() -> str: ) if pyproject_version.startswith("v"): raise RuntimeError("Checked-in version must not start with 'v'") - if not re.fullmatch( - r"[0-9]+(?:\.[0-9]+)+(?:[a-zA-Z0-9_.+-]+)?", pyproject_version - ): + if not re.fullmatch(r"[0-9]+(?:\.[0-9]+)+(?:[a-zA-Z0-9_.+-]+)?", pyproject_version): raise RuntimeError(f"Invalid checked-in version: {pyproject_version!r}") return pyproject_version @@ -77,7 +75,9 @@ def verify_dist(args: argparse.Namespace) -> None: if sdists != [expected_sdist]: raise RuntimeError(f"Expected only sdist {expected_sdist!r}, found {sdists!r}") if len(wheels) != 5: - raise RuntimeError(f"Expected 5 platform wheels, found {len(wheels)}: {wheels!r}") + raise RuntimeError( + f"Expected 5 platform wheels, found {len(wheels)}: {wheels!r}" + ) for name in files: if not name.startswith(f"temporalio-{args.version}"): @@ -99,7 +99,9 @@ def verify_dist(args: argparse.Namespace) -> None: if not any(predicate(name) for name in wheels) ] if missing: - raise RuntimeError(f"Missing expected platform wheels: {missing!r}; found {wheels!r}") + raise RuntimeError( + f"Missing expected platform wheels: {missing!r}; found {wheels!r}" + ) print("Verified release artifacts:") for name in files: From e4ace882c4c57f041b42046326b5f0ae7518280e Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Wed, 3 Jun 2026 15:59:18 -0700 Subject: [PATCH 3/3] Harden release smoke package install --- .../actions/release-smoke-package/action.yml | 14 ++--- .github/scripts/install_release_package.py | 54 +++++++++++++++++++ .github/workflows/release-publish.yml | 8 +-- 3 files changed, 62 insertions(+), 14 deletions(-) create mode 100644 .github/scripts/install_release_package.py diff --git a/.github/actions/release-smoke-package/action.yml b/.github/actions/release-smoke-package/action.yml index b4233c30d..bd557cd2b 100644 --- a/.github/actions/release-smoke-package/action.yml +++ b/.github/actions/release-smoke-package/action.yml @@ -7,8 +7,8 @@ inputs: index-url: description: "Primary package index URL" required: true - extra-index-url: - description: "Optional fallback package index URL" + dependency-index-url: + description: "Optional dependency package index URL" required: false default: "" runs: @@ -22,18 +22,18 @@ runs: env: VERSION: ${{ inputs.version }} INDEX_URL: ${{ inputs.index-url }} - EXTRA_INDEX_URL: ${{ inputs.extra-index-url }} + DEPENDENCY_INDEX_URL: ${{ inputs.dependency-index-url }} run: | set -euo pipefail python -m venv .venv .venv/bin/python -m pip install --upgrade pip - install_args=(--prefer-binary --index-url "$INDEX_URL") - if [[ -n "$EXTRA_INDEX_URL" ]]; then - install_args+=(--extra-index-url "$EXTRA_INDEX_URL") + install_args=(--version "$VERSION" --index-url "$INDEX_URL") + if [[ -n "$DEPENDENCY_INDEX_URL" ]]; then + install_args+=(--dependency-index-url "$DEPENDENCY_INDEX_URL") fi - .venv/bin/python -m pip install "${install_args[@]}" "temporalio==$VERSION" + .venv/bin/python .github/scripts/install_release_package.py "${install_args[@]}" - name: Run SDK smoke test shell: bash env: diff --git a/.github/scripts/install_release_package.py b/.github/scripts/install_release_package.py new file mode 100644 index 000000000..723267e2b --- /dev/null +++ b/.github/scripts/install_release_package.py @@ -0,0 +1,54 @@ +"""Install a release package for smoke testing.""" + +from __future__ import annotations + +import argparse +import importlib.metadata +import subprocess +import sys +from collections.abc import Sequence + + +def _pip_install(args: Sequence[str]) -> None: + subprocess.check_call([sys.executable, "-m", "pip", "install", *args]) + + +def install_package(args: argparse.Namespace) -> None: + package = f"temporalio=={args.version}" + if args.dependency_index_url: + _pip_install( + [ + "--prefer-binary", + "--index-url", + args.index_url, + "--no-deps", + package, + ] + ) + + requirements = importlib.metadata.requires("temporalio") or [] + if requirements: + _pip_install( + [ + "--prefer-binary", + "--index-url", + args.dependency_index_url, + *requirements, + ] + ) + else: + _pip_install(["--prefer-binary", "--index-url", args.index_url, package]) + + subprocess.check_call([sys.executable, "-m", "pip", "check"]) + + +def main(argv: Sequence[str] | None = None) -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--version", required=True) + parser.add_argument("--index-url", required=True) + parser.add_argument("--dependency-index-url") + install_package(parser.parse_args(argv)) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index a4ed8326a..5c8d54196 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -2,12 +2,6 @@ name: Release Publish run-name: Release from main on: - pull_request: - paths: - - ".github/actions/release-smoke-package/action.yml" - - ".github/scripts/release_smoke_package.py" - - ".github/scripts/release_verify.py" - - ".github/workflows/release-publish.yml" workflow_dispatch: permissions: @@ -196,7 +190,7 @@ jobs: with: version: ${{ needs.verify_artifacts.outputs.version }} index-url: https://test.pypi.org/simple/ - extra-index-url: https://pypi.org/simple/ + dependency-index-url: https://pypi.org/simple/ publish_pypi: name: Publish to PyPI