name: Python Package Release on: release: types: - published workflow_dispatch: inputs: package: description: 'Python package to build' type: choice required: true options: - icu_skeleton_parser - icu_messageformat_parser - icu_messageformat - intl dry_run: description: 'Build wheels without publishing to PyPI' type: boolean required: false default: true concurrency: group: ${{ github.workflow }}-${{ github.event.release.tag_name || github.ref }} cancel-in-progress: false permissions: contents: read jobs: select: name: Select Package runs-on: ubuntu-latest outputs: package: ${{ steps.package.outputs.package }} publish: ${{ steps.package.outputs.publish }} selected: ${{ steps.package.outputs.selected }} steps: - name: Select Package id: package env: EVENT_NAME: ${{ github.event_name }} INPUT_DRY_RUN: ${{ inputs.dry_run || 'false' }} INPUT_PACKAGE: ${{ inputs.package }} RELEASE_TAG: ${{ github.event.release.tag_name }} run: | set -euo pipefail if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then package="$INPUT_PACKAGE" publish=$([[ "$INPUT_DRY_RUN" == "true" ]] && echo false || echo true) else publish=true case "$RELEASE_TAG" in icu_skeleton_parser@*) package=icu_skeleton_parser ;; icu_messageformat_parser@*) package=icu_messageformat_parser ;; icu_messageformat@*) package=icu_messageformat ;; py_intl@*) package=intl ;; *) echo "selected=false" >> "$GITHUB_OUTPUT" exit 0 ;; esac fi { echo "package=$package" echo "publish=$publish" echo "selected=true" } >> "$GITHUB_OUTPUT" build: name: Build Wheels needs: select if: ${{ needs.select.outputs.selected == 'true' }} runs-on: ubuntu-latest steps: - name: Checkout Repository uses: actions/checkout@v7 with: ref: ${{ github.event.release.tag_name || github.ref }} - name: Setup Python uses: actions/setup-python@v7 with: python-version: '3.15' - name: Setup Bazel uses: bazel-contrib/setup-bazel@0.19.1 with: bazelisk-cache: true disk-cache: ${{ github.workflow }}-python-wheels repository-cache: true - name: Test Python Binding env: PACKAGE: ${{ needs.select.outputs.package }} run: bazel test "//python/$PACKAGE:python_test" --test_output=errors - name: Build Wheels env: MACOSX_DEPLOYMENT_TARGET: '11.0' PACKAGE: ${{ needs.select.outputs.package }} run: | set -euo pipefail target="//python/$PACKAGE:wheel" mkdir -p dist platforms=( //platforms:linux_x86_64_gnu //platforms:linux_aarch64_gnu //platforms:macos_x86_64 //platforms:macos_aarch64 ) for platform in "${platforms[@]}"; do bazel_args=( --action_env=MACOSX_DEPLOYMENT_TARGET --compilation_mode=opt --platforms="$platform" ) bazel build "${bazel_args[@]}" "$target" wheel=$(bazel cquery "${bazel_args[@]}" --output=files "$target" | tail -n 1) cp "$wheel" dist/ done - name: Validate Native Wheels run: | set -euo pipefail check_dir=$(mktemp -d) for wheel in dist/*.whl; do wheel_dir="$check_dir/$(basename "$wheel" .whl)" mkdir "$wheel_dir" unzip -q "$wheel" -d "$wheel_dir" extension=$(find "$wheel_dir" -name '*.abi3.so' -print -quit) description=$(file "$extension") case "$wheel" in *manylinux_2_28_x86_64.whl) expected=('ELF 64-bit' 'x86-64') ;; *manylinux_2_28_aarch64.whl) expected=('ELF 64-bit' 'ARM aarch64') ;; *macosx_11_0_x86_64.whl) expected=('Mach-O 64-bit' 'x86_64') ;; *macosx_11_0_arm64.whl) expected=('Mach-O 64-bit' 'arm64') ;; *) echo "Unexpected wheel: $wheel" >&2; exit 1 ;; esac for fragment in "${expected[@]}"; do [[ "$description" == *"$fragment"* ]] || { echo "Unexpected native extension: $description" >&2 exit 1 } done if [[ "$wheel" == *manylinux* ]]; then max_glibc=$(strings "$extension" | grep -oE 'GLIBC_[0-9.]+' | sort -Vu | tail -n 1) [[ $(printf 'GLIBC_2.28\n%s\n' "$max_glibc" | sort -V | tail -n 1) == GLIBC_2.28 ]] || { echo "Wheel requires $max_glibc" >&2 exit 1 } fi done - name: Smoke Test Linux Wheel env: PACKAGE: ${{ needs.select.outputs.package }} run: | python -m pip install --no-index dist/*-manylinux_2_28_x86_64.whl python -c 'import importlib, os; importlib.import_module(os.environ["PACKAGE"])' - name: Upload Wheel uses: actions/upload-artifact@v7 with: name: python-wheels path: dist/*.whl if-no-files-found: error retention-days: 7 publish: name: Publish to PyPI needs: - select - build if: ${{ needs.select.outputs.selected == 'true' && needs.select.outputs.publish == 'true' }} runs-on: ubuntu-latest environment: name: pypi-${{ needs.select.outputs.package }} url: https://pypi.org/p/${{ needs.select.outputs.package }} permissions: id-token: write steps: - name: Download Wheels uses: actions/download-artifact@v8 with: name: python-wheels path: dist - name: Publish Wheels uses: pypa/gh-action-pypi-publish@release/v1