Skip to content

Publish all on line 0.9 to PyPI #26

Publish all on line 0.9 to PyPI

Publish all on line 0.9 to PyPI #26

Workflow file for this run

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