From 26fe1478d57702b5e7dc45952986e6227d8020f2 Mon Sep 17 00:00:00 2001 From: Spencer Judge Date: Mon, 6 Jul 2026 16:18:41 -0700 Subject: [PATCH] Add musl/alpine support --- .../actions/alpine-package-smoke/action.yml | 72 +++++++++++++++++++ .github/scripts/release_verify.py | 6 +- .github/workflows/ci.yml | 29 ++++++++ .github/workflows/release-publish.yml | 70 ++++++++++++++++++ pyproject.toml | 10 ++- scripts/cibuildwheel_before_all_linux.sh | 13 ++++ scripts/cibuildwheel_before_build_linux.sh | 8 +++ 7 files changed, 203 insertions(+), 5 deletions(-) create mode 100644 .github/actions/alpine-package-smoke/action.yml create mode 100644 scripts/cibuildwheel_before_all_linux.sh create mode 100644 scripts/cibuildwheel_before_build_linux.sh diff --git a/.github/actions/alpine-package-smoke/action.yml b/.github/actions/alpine-package-smoke/action.yml new file mode 100644 index 000000000..ad72f2eb9 --- /dev/null +++ b/.github/actions/alpine-package-smoke/action.yml @@ -0,0 +1,72 @@ +name: Alpine package smoke test +description: Install a local or published temporalio package in Alpine and run a minimal SDK workflow. +inputs: + wheel-dir: + description: "Directory containing local wheel files to install" + required: false + default: "" + version: + description: "Published package version to install and verify" + required: false + default: "" + index-url: + description: "Primary package index URL for published package installs" + required: false + default: "" + dependency-index-url: + description: "Optional dependency package index URL for published package installs" + required: false + default: "" + python-image: + description: "Alpine Python Docker image" + required: false + default: "python:3.10-alpine" +runs: + using: composite + steps: + - name: Install package and run SDK smoke test + shell: bash + env: + WHEEL_DIR: ${{ inputs.wheel-dir }} + VERSION: ${{ inputs.version }} + INDEX_URL: ${{ inputs.index-url }} + DEPENDENCY_INDEX_URL: ${{ inputs.dependency-index-url }} + PYTHON_IMAGE: ${{ inputs.python-image }} + run: | + set -euo pipefail + + if [[ -z "$WHEEL_DIR" && ( -z "$VERSION" || -z "$INDEX_URL" ) ]]; then + echo "Either wheel-dir or both version and index-url must be provided" >&2 + exit 1 + fi + + docker run --rm -v "$(pwd):/workspace" -w /tmp \ + -e WHEEL_DIR="$WHEEL_DIR" \ + -e VERSION="$VERSION" \ + -e INDEX_URL="$INDEX_URL" \ + -e DEPENDENCY_INDEX_URL="$DEPENDENCY_INDEX_URL" \ + "$PYTHON_IMAGE" sh -c ' + set -eu + python -m venv /tmp/alpine-smoke + /tmp/alpine-smoke/bin/python -m pip install --upgrade pip + + if [ -n "$WHEEL_DIR" ]; then + /tmp/alpine-smoke/bin/python -m pip install --prefer-binary /workspace/$WHEEL_DIR/*.whl + elif [ -n "$DEPENDENCY_INDEX_URL" ]; then + /tmp/alpine-smoke/bin/python /workspace/.github/scripts/install_release_package.py \ + --version "$VERSION" \ + --index-url "$INDEX_URL" \ + --dependency-index-url "$DEPENDENCY_INDEX_URL" + else + /tmp/alpine-smoke/bin/python /workspace/.github/scripts/install_release_package.py \ + --version "$VERSION" \ + --index-url "$INDEX_URL" + fi + + if [ -z "$VERSION" ]; then + VERSION=$(/tmp/alpine-smoke/bin/python -c "import importlib.metadata; print(importlib.metadata.version(\"temporalio\"))") + export VERSION + fi + + /tmp/alpine-smoke/bin/python /workspace/.github/scripts/release_smoke_package.py + ' diff --git a/.github/scripts/release_verify.py b/.github/scripts/release_verify.py index 390252854..280b2049e 100644 --- a/.github/scripts/release_verify.py +++ b/.github/scripts/release_verify.py @@ -75,9 +75,9 @@ def verify_dist(args: argparse.Namespace) -> None: 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: + if len(wheels) != 7: raise RuntimeError( - f"Expected 5 platform wheels, found {len(wheels)}: {wheels!r}" + f"Expected 7 platform wheels, found {len(wheels)}: {wheels!r}" ) for name in files: @@ -90,6 +90,8 @@ def verify_dist(args: argparse.Namespace) -> None: 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, + "linux-musl-x86_64": lambda name: "musllinux" in name and "x86_64" in name, + "linux-musl-aarch64": lambda name: "musllinux" 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, diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 55eb7eb56..86dbe6253 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,6 +100,35 @@ jobs: npx doctoc README.md [[ -z $(git status --porcelain README.md) ]] || (git diff README.md; echo "README changed"; exit 1) + alpine-package-test: + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + include: + - cibw-build: cp310-musllinux_x86_64 + runsOn: ubuntu-latest + - cibw-build: cp310-musllinux_aarch64 + runsOn: ubuntu-24.04-arm64-2-core + runs-on: ${{ matrix.runsOn }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + submodules: recursive + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: "3.14" + - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8 + - run: uv sync --all-extras + - name: Build Alpine wheel + run: uv run cibuildwheel --output-dir dist + env: + CIBW_BUILD: ${{ matrix.cibw-build }} + - name: Test Alpine wheel + uses: ./.github/actions/alpine-package-smoke + with: + wheel-dir: dist + check-protos: timeout-minutes: 30 runs-on: ubuntu-latest diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index 3be7ad849..d745804a8 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -20,17 +20,29 @@ jobs: include: - os: ubuntu-latest package-suffix: linux-amd64 + cibw-build: cp310-manylinux_x86_64 - os: ubuntu-arm package-suffix: linux-aarch64 + cibw-build: cp310-manylinux_aarch64 + runsOn: ubuntu-24.04-arm64-2-core + - os: ubuntu-latest + package-suffix: linux-musl-amd64 + cibw-build: cp310-musllinux_x86_64 + - os: ubuntu-arm + package-suffix: linux-musl-aarch64 + cibw-build: cp310-musllinux_aarch64 runsOn: ubuntu-24.04-arm64-2-core - os: macos-intel package-suffix: macos-amd64 + cibw-build: cp310-macosx_x86_64 runsOn: macos-15-intel - os: macos-arm package-suffix: macos-aarch64 + cibw-build: cp310-macosx_arm64 runsOn: macos-14 - os: windows-latest package-suffix: windows-amd64 + cibw-build: cp310-win_amd64 runs-on: ${{ matrix.runsOn || matrix.os }} permissions: contents: read @@ -63,9 +75,12 @@ jobs: # Build the wheel - run: uv run cibuildwheel --output-dir dist + env: + CIBW_BUILD: ${{ matrix.cibw-build }} # Install the wheel in a new venv and run a test - name: Test wheel + if: ${{ !contains(matrix.package-suffix, 'musl') }} shell: bash run: | mkdir __test_wheel__ @@ -80,6 +95,12 @@ jobs: ./.venv/$bindir/pip install --prefer-binary ../dist/*.whl ./.venv/$bindir/python -m pytest -s tests/worker/test_workflow.py -k test_workflow_hello + - name: Test Alpine wheel + if: ${{ contains(matrix.package-suffix, 'musl') }} + uses: ./.github/actions/alpine-package-smoke + with: + wheel-dir: dist + # Upload dist - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: @@ -200,11 +221,36 @@ jobs: index-url: https://test.pypi.org/simple/ dependency-index-url: https://pypi.org/simple/ + smoke_testpypi_alpine: + name: Smoke test TestPyPI package on Alpine (${{ matrix.arch }}) + needs: + - verify_artifacts + - publish_testpypi + strategy: + fail-fast: false + matrix: + include: + - arch: x64 + runsOn: ubuntu-latest + - arch: arm64 + runsOn: ubuntu-24.04-arm64-2-core + runs-on: ${{ matrix.runsOn }} + timeout-minutes: 10 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - name: Install and load package + uses: ./.github/actions/alpine-package-smoke + with: + version: ${{ needs.verify_artifacts.outputs.version }} + index-url: https://test.pypi.org/simple/ + dependency-index-url: https://pypi.org/simple/ + publish_pypi: name: Publish to PyPI needs: - verify_artifacts - smoke_testpypi + - smoke_testpypi_alpine runs-on: ubuntu-latest timeout-minutes: 10 environment: pypi @@ -247,11 +293,35 @@ jobs: version: ${{ needs.verify_artifacts.outputs.version }} index-url: https://pypi.org/simple/ + smoke_pypi_alpine: + name: Smoke test PyPI package on Alpine (${{ matrix.arch }}) + needs: + - verify_artifacts + - publish_pypi + strategy: + fail-fast: false + matrix: + include: + - arch: x64 + runsOn: ubuntu-latest + - arch: arm64 + runsOn: ubuntu-24.04-arm64-2-core + runs-on: ${{ matrix.runsOn }} + timeout-minutes: 10 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - name: Install and load package + uses: ./.github/actions/alpine-package-smoke + 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 + - smoke_pypi_alpine runs-on: ubuntu-latest timeout-minutes: 5 permissions: diff --git a/pyproject.toml b/pyproject.toml index 99aea21bf..3b4ad3fc3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -151,17 +151,21 @@ filterwarnings = [ [tool.cibuildwheel] before-all = "pip install protoc-wheel-0" -build = "cp310-win_amd64 cp310-manylinux_x86_64 cp310-manylinux_aarch64 cp310-macosx_x86_64 cp310-macosx_arm64" +build = "cp310-win_amd64 cp310-manylinux_x86_64 cp310-manylinux_aarch64 cp310-musllinux_x86_64 cp310-musllinux_aarch64 cp310-macosx_x86_64 cp310-macosx_arm64" build-verbosity = 1 [tool.cibuildwheel.macos] environment = { MACOSX_DEPLOYMENT_TARGET = "10.12" } [tool.cibuildwheel.linux] -before-all = "curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain stable -y && yum install -y openssl-devel" -before-build = "pip install protoc-wheel-0" +before-all = "sh scripts/cibuildwheel_before_all_linux.sh" +before-build = "sh scripts/cibuildwheel_before_build_linux.sh" environment = { PATH = "$PATH:$HOME/.cargo/bin", CARGO_NET_GIT_FETCH_WITH_CLI = "true" } +[[tool.cibuildwheel.overrides]] +select = "*musllinux*" +environment = { PATH = "$PATH:$HOME/.cargo/bin", CARGO_NET_GIT_FETCH_WITH_CLI = "true", RUSTFLAGS = "-C target-feature=-crt-static" } + [tool.mypy] ignore_missing_imports = true exclude = [ diff --git a/scripts/cibuildwheel_before_all_linux.sh b/scripts/cibuildwheel_before_all_linux.sh new file mode 100644 index 000000000..be9b08986 --- /dev/null +++ b/scripts/cibuildwheel_before_all_linux.sh @@ -0,0 +1,13 @@ +#!/bin/sh +set -eu + +if command -v apk >/dev/null 2>&1; then + apk add --no-cache build-base curl openssl-dev protobuf-dev +elif command -v yum >/dev/null 2>&1; then + yum install -y openssl-devel +else + echo "Unsupported Linux image: expected apk or yum" >&2 + exit 1 +fi + +curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain stable -y diff --git a/scripts/cibuildwheel_before_build_linux.sh b/scripts/cibuildwheel_before_build_linux.sh new file mode 100644 index 000000000..838c7dd72 --- /dev/null +++ b/scripts/cibuildwheel_before_build_linux.sh @@ -0,0 +1,8 @@ +#!/bin/sh +set -eu + +if command -v protoc >/dev/null 2>&1; then + protoc --version +else + pip install protoc-wheel-0 +fi