Publish all on line 0.9 to PyPI #26
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: Publish | |
| run-name: >- | |
| Publish ${{ inputs.publish_packages || 'all' }} on line ${{ inputs.release_line }} ${{ inputs.publish_to_pypi && 'to PyPI' || 'dry run' }} | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| release_line: | |
| description: "Prepared release line to publish, for example 0.9" | |
| required: true | |
| type: string | |
| publish_packages: | |
| description: "Publish keys: all or comma-separated keys like server,imap,meta:all,client,skill" | |
| required: true | |
| default: all | |
| type: string | |
| publish_to_pypi: | |
| description: "Publish built distributions to PyPI and promote draft GitHub releases" | |
| required: true | |
| default: false | |
| type: boolean | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: >- | |
| ${{ github.workflow }}-${{ github.ref }}-${{ inputs.release_line }}-${{ inputs.publish_packages }} | |
| cancel-in-progress: false | |
| jobs: | |
| validate: | |
| name: Validate prepared release | |
| runs-on: ubuntu-latest | |
| outputs: | |
| has_publish: ${{ steps.pypi-plan.outputs.has_publish }} | |
| integration_matrix: ${{ steps.integration-matrix.outputs.matrix }} | |
| publish_keys: ${{ steps.pypi-plan.outputs.publish_keys }} | |
| publish_matrix: ${{ steps.pypi-plan.outputs.publish_matrix }} | |
| publish_specs: ${{ steps.pypi-plan.outputs.publish_specs }} | |
| publish_title: ${{ steps.pypi-plan.outputs.publish_title }} | |
| release_tags: ${{ steps.drafts.outputs.release_tags }} | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| GH_REPO: ${{ github.repository }} | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Resolve release line | |
| id: release | |
| shell: bash | |
| env: | |
| RELEASE_LINE_INPUT: ${{ inputs.release_line }} | |
| run: | | |
| set -euo pipefail | |
| release_line="$RELEASE_LINE_INPUT" | |
| if [[ ! "$release_line" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then | |
| echo "::error::release_line must use MAJOR.MINOR, got: $release_line" | |
| exit 1 | |
| fi | |
| echo "release_line=$release_line" >> "$GITHUB_OUTPUT" | |
| - name: Verify release line | |
| run: tools/upgrade_release_line "${{ steps.release.outputs.release_line }}" --check | |
| - name: Plan publish set | |
| id: pypi-plan | |
| shell: bash | |
| env: | |
| PUBLISH_PACKAGES: ${{ inputs.publish_packages }} | |
| run: tools/plan_pypi_publish --packages "$PUBLISH_PACKAGES" | |
| - name: Validate draft GitHub Releases | |
| id: drafts | |
| shell: bash | |
| env: | |
| PUBLISH_KEYS: ${{ steps.pypi-plan.outputs.publish_keys }} | |
| PUBLISH_SPECS: ${{ steps.pypi-plan.outputs.publish_specs }} | |
| run: | | |
| set -euo pipefail | |
| if [[ -z "${PUBLISH_SPECS:-}" ]]; then | |
| echo "release_tags=" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| current_sha="$(git rev-parse HEAD)" | |
| IFS=',' read -ra package_keys <<< "$PUBLISH_KEYS" | |
| IFS=',' read -ra publish_specs <<< "$PUBLISH_SPECS" | |
| release_tags=() | |
| for index in "${!package_keys[@]}"; do | |
| package_key="${package_keys[$index]}" | |
| package_version="${publish_specs[$index]##*==}" | |
| if [[ "$package_version" == *".dev"* ]]; then | |
| continue | |
| fi | |
| release_tag="v$package_version" | |
| if [[ " ${release_tags[*]} " != *" $release_tag "* ]]; then | |
| release_tags+=("$release_tag") | |
| fi | |
| done | |
| for release_tag in "${release_tags[@]}"; do | |
| draft="$(gh api --paginate "repos/{owner}/{repo}/releases" \ | |
| --jq ".[] | select(.tag_name == \"$release_tag\") | .draft")" | |
| if [[ "$draft" != "true" ]]; then | |
| echo "::error::Release $release_tag must exist and still be a draft." | |
| exit 1 | |
| fi | |
| tag_sha="$(git rev-list -n 1 "$release_tag")" | |
| if [[ "$tag_sha" != "$current_sha" ]]; then | |
| echo "::error::Release tag $release_tag points at $tag_sha, expected $current_sha." | |
| exit 1 | |
| fi | |
| done | |
| { | |
| printf "release_tags=" | |
| local_prefix="" | |
| for release_tag in "${release_tags[@]}"; do | |
| printf "%s%s" "$local_prefix" "$release_tag" | |
| local_prefix="," | |
| done | |
| printf "\n" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Build integration test matrix | |
| id: integration-matrix | |
| shell: bash | |
| run: | | |
| python - <<'PY' | |
| from pathlib import Path | |
| import json | |
| import os | |
| platforms = [ | |
| ("Linux x64", "ubuntu-24.04", "linux-x64"), | |
| ("Linux arm64", "ubuntu-24.04-arm", "linux-arm64"), | |
| ("macOS x64", "macos-15-intel", "macos-x64"), | |
| ("macOS arm64", "macos-15", "macos-arm64"), | |
| ("Windows x64", "windows-2025", "windows-x64"), | |
| ("Windows arm64", "windows-11-arm", "windows-arm64"), | |
| ] | |
| suites = [{"suite": "Server", "target": "server/tests/integration", "go_tests": True}] | |
| for path in sorted(Path("plugins").glob("*/tests/integration")): | |
| if path.is_dir(): | |
| suites.append( | |
| { | |
| "suite": path.parts[1].upper(), | |
| "target": str(path), | |
| "go_tests": False, | |
| } | |
| ) | |
| include = [] | |
| for suite in suites: | |
| for label, runner, platform_id in platforms: | |
| include.append( | |
| { | |
| "name": f"{suite['suite']} integration {label}", | |
| "runner": runner, | |
| "python_version": "3.13", | |
| "platform_id": platform_id, | |
| "test_target": suite["target"], | |
| "go_tests": suite["go_tests"], | |
| } | |
| ) | |
| matrix = json.dumps({"include": include}, separators=(",", ":")) | |
| with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output: | |
| output.write(f"matrix={matrix}\n") | |
| PY | |
| platform-tests: | |
| name: ${{ matrix.name }} | |
| needs: validate | |
| runs-on: ${{ matrix.runner }} | |
| if: needs.validate.outputs.has_publish == 'true' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - name: Python 3.10 | |
| runner: ubuntu-latest | |
| python_version: "3.10" | |
| nox_session: compat-3.10 | |
| - name: Python 3.11 | |
| runner: ubuntu-latest | |
| python_version: "3.11" | |
| nox_session: compat-3.11 | |
| - name: Python 3.12 | |
| runner: ubuntu-latest | |
| python_version: "3.12" | |
| nox_session: compat-3.12 | |
| - name: Python 3.14 | |
| runner: ubuntu-latest | |
| python_version: "3.14" | |
| nox_session: compat-3.14 | |
| - name: Platform Linux x64 | |
| runner: ubuntu-24.04 | |
| python_version: "3.13" | |
| nox_session: unit | |
| platform_id: linux-x64 | |
| go_tests: true | |
| - name: Platform Linux arm64 | |
| runner: ubuntu-24.04-arm | |
| python_version: "3.13" | |
| nox_session: unit | |
| platform_id: linux-arm64 | |
| go_tests: true | |
| - name: Platform macOS x64 | |
| runner: macos-15-intel | |
| python_version: "3.13" | |
| nox_session: unit | |
| platform_id: macos-x64 | |
| go_tests: true | |
| - name: Platform macOS arm64 | |
| runner: macos-15 | |
| python_version: "3.13" | |
| nox_session: unit | |
| platform_id: macos-arm64 | |
| go_tests: true | |
| - name: Platform Windows x64 | |
| runner: windows-2025 | |
| python_version: "3.13" | |
| nox_session: unit | |
| platform_id: windows-x64 | |
| go_tests: true | |
| - name: Platform Windows arm64 | |
| runner: windows-11-arm | |
| python_version: "3.13" | |
| nox_session: unit | |
| platform_id: windows-arm64 | |
| go_tests: true | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python_version }} | |
| cache: pip | |
| - name: Set up Go | |
| if: ${{ matrix.go_tests == true }} | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version-file: client/go-cli/go.mod | |
| - name: Cache Go client smoke binary | |
| if: ${{ matrix.go_tests == true }} | |
| uses: actions/cache@v4 | |
| with: | |
| path: .ci/go-client-smoke | |
| key: go-client-smoke-${{ matrix.platform_id }}-${{ hashFiles('client/go-cli/**', 'tools/build_go_client') }} | |
| - name: Install nox | |
| run: python -m pip install "nox[pbs]>=2024.10,<2026.0" | |
| - name: Set up OpenSSL for Windows arm64 Python dependencies | |
| if: ${{ matrix.platform_id == 'windows-arm64' }} | |
| shell: pwsh | |
| run: | | |
| $vcpkgRoot = Join-Path $env:RUNNER_TEMP "vcpkg" | |
| git clone https://github.com/microsoft/vcpkg $vcpkgRoot | |
| & (Join-Path $vcpkgRoot "bootstrap-vcpkg.bat") -disableMetrics | |
| & (Join-Path $vcpkgRoot "vcpkg.exe") install openssl:arm64-windows-static-md | |
| $opensslRoot = Join-Path $vcpkgRoot "installed\arm64-windows-static-md" | |
| "VCPKG_ROOT=$vcpkgRoot" | Out-File -FilePath $env:GITHUB_ENV -Append | |
| "OPENSSL_DIR=$opensslRoot" | Out-File -FilePath $env:GITHUB_ENV -Append | |
| - name: Run Go client tests | |
| if: ${{ matrix.go_tests == true }} | |
| working-directory: client/go-cli | |
| env: | |
| GOCACHE: ${{ runner.temp }}/go-build | |
| run: go test ./... | |
| - name: Run Python compatibility unit suite | |
| if: ${{ matrix.go_tests != true }} | |
| run: nox -s ${{ matrix.nox_session }} | |
| - name: Run platform unit test suite | |
| if: ${{ matrix.go_tests == true }} | |
| env: | |
| ARBITER_GO_CLIENT_SMOKE_OUTDIR: .ci/go-client-smoke | |
| ARBITER_GO_CLIENT_SMOKE_REUSE: "1" | |
| GOCACHE: ${{ runner.temp }}/go-build | |
| run: nox -s ${{ matrix.nox_session }} | |
| build: | |
| name: Build distributions | |
| needs: validate | |
| runs-on: ubuntu-latest | |
| if: needs.validate.outputs.has_publish == 'true' | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v4 | |
| - name: Set up bootstrap Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.13" | |
| cache: pip | |
| - name: Set up Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version-file: client/go-cli/go.mod | |
| - name: Install build tools | |
| run: >- | |
| python -m pip install | |
| "nox[pbs]>=2024.10,<2026.0" | |
| build | |
| "agent-skill-installer>=0.3.0,<0.4.0" | |
| - name: Run lint | |
| run: nox -s lint | |
| - name: Run compatibility unit tests | |
| run: nox -s compat | |
| - name: Build selected distributions | |
| env: | |
| PUBLISH_PACKAGES: ${{ inputs.publish_packages }} | |
| run: tools/build_release_dists --clean --packages "$PUBLISH_PACKAGES" | |
| - name: Prepare publishable distributions | |
| env: | |
| PUBLISH_PACKAGES: ${{ inputs.publish_packages }} | |
| run: tools/plan_pypi_publish --prepare-output-dir --packages "$PUBLISH_PACKAGES" | |
| - name: Smoke install built release | |
| env: | |
| PUBLISH_SPECS: ${{ needs.validate.outputs.publish_specs }} | |
| run: tools/smoke_release_install --publish-specs "$PUBLISH_SPECS" --dist-dir dist --asi-python python | |
| - name: Upload distributions | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: python-distributions | |
| path: dist/* | |
| if-no-files-found: error | |
| - name: Upload publishable distributions | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: python-distributions-to-publish | |
| path: dist-publish/** | |
| if-no-files-found: error | |
| integration-tests: | |
| name: ${{ matrix.name }} | |
| needs: validate | |
| runs-on: ${{ matrix.runner }} | |
| if: needs.validate.outputs.has_publish == 'true' | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJson(needs.validate.outputs.integration_matrix) }} | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python_version }} | |
| cache: pip | |
| - name: Set up Go | |
| if: ${{ matrix.go_tests == true }} | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version-file: client/go-cli/go.mod | |
| - name: Cache Go client smoke binary | |
| if: ${{ matrix.go_tests == true }} | |
| uses: actions/cache@v4 | |
| with: | |
| path: .ci/go-client-smoke | |
| key: go-client-smoke-${{ matrix.platform_id }}-${{ hashFiles('client/go-cli/**', 'tools/build_go_client') }} | |
| - name: Install nox | |
| run: python -m pip install "nox[pbs]>=2024.10,<2026.0" | |
| - name: Set up OpenSSL for Windows arm64 Python dependencies | |
| if: ${{ matrix.platform_id == 'windows-arm64' }} | |
| shell: pwsh | |
| run: | | |
| $vcpkgRoot = Join-Path $env:RUNNER_TEMP "vcpkg" | |
| git clone https://github.com/microsoft/vcpkg $vcpkgRoot | |
| & (Join-Path $vcpkgRoot "bootstrap-vcpkg.bat") -disableMetrics | |
| & (Join-Path $vcpkgRoot "vcpkg.exe") install openssl:arm64-windows-static-md | |
| $opensslRoot = Join-Path $vcpkgRoot "installed\arm64-windows-static-md" | |
| "VCPKG_ROOT=$vcpkgRoot" | Out-File -FilePath $env:GITHUB_ENV -Append | |
| "OPENSSL_DIR=$opensslRoot" | Out-File -FilePath $env:GITHUB_ENV -Append | |
| - name: Run Go client tests | |
| if: ${{ matrix.go_tests == true }} | |
| working-directory: client/go-cli | |
| env: | |
| GOCACHE: ${{ runner.temp }}/go-build | |
| run: go test ./... | |
| - name: Run platform integration suite | |
| env: | |
| ARBITER_GO_CLIENT_SMOKE_OUTDIR: .ci/go-client-smoke | |
| ARBITER_GO_CLIENT_SMOKE_REUSE: "1" | |
| GOCACHE: ${{ runner.temp }}/go-build | |
| run: nox -s integration -- ${{ matrix.test_target }} | |
| docker-deploy-test: | |
| name: Docker deploy integration | |
| needs: validate | |
| runs-on: ubuntu-latest | |
| if: needs.validate.outputs.has_publish == 'true' | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.13" | |
| cache: pip | |
| - name: Install nox | |
| run: python -m pip install "nox[pbs]>=2024.10,<2026.0" | |
| - name: Show Docker environment | |
| run: | | |
| docker info | |
| docker compose version | |
| - name: Run Docker deploy integration | |
| run: nox -s deploy-test | |
| publish: | |
| name: Publish ${{ matrix.package_name }} ${{ matrix.version }} to PyPI | |
| needs: | |
| - build | |
| - docker-deploy-test | |
| - integration-tests | |
| - platform-tests | |
| - validate | |
| runs-on: ubuntu-latest | |
| if: >- | |
| needs.build.result == 'success' && | |
| needs['integration-tests'].result == 'success' && | |
| needs['docker-deploy-test'].result == 'success' && | |
| needs['platform-tests'].result == 'success' && | |
| github.ref == 'refs/heads/main' && | |
| inputs.publish_to_pypi | |
| environment: pypi | |
| permissions: | |
| contents: read | |
| id-token: write | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJson(needs.validate.outputs.publish_matrix) }} | |
| steps: | |
| - name: Download publishable distributions | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: python-distributions-to-publish | |
| path: dist-publish | |
| - name: Publish distributions | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| packages-dir: dist-publish/${{ matrix.artifact_dir }} | |
| promote: | |
| name: Promote GitHub Releases | |
| needs: | |
| - publish | |
| - validate | |
| runs-on: ubuntu-latest | |
| if: >- | |
| needs.publish.result == 'success' && | |
| needs.validate.outputs.release_tags != '' | |
| permissions: | |
| contents: write | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| GH_REPO: ${{ github.repository }} | |
| steps: | |
| - name: Publish draft releases | |
| env: | |
| RELEASE_TAGS: ${{ needs.validate.outputs.release_tags }} | |
| run: | | |
| set -euo pipefail | |
| IFS=',' read -ra release_tags <<< "$RELEASE_TAGS" | |
| for release_tag in "${release_tags[@]}"; do | |
| gh release edit "$release_tag" --draft=false | |
| done |