From 7bc4838acaf55c66303d40f8cc2598103a16413e Mon Sep 17 00:00:00 2001 From: Cuiys Date: Mon, 16 Mar 2026 14:20:01 +0800 Subject: [PATCH] feat: buildwheel in ghrunner (#221) --- .github/workflows/_build_wheel_job.yml | 102 ++++++++++++++++++++++ .github/workflows/build_test_wheel.yml | 113 +++++------------------- .github/workflows/build_wheel.yml | 114 +++++-------------------- pyproject.toml | 12 ++- 4 files changed, 151 insertions(+), 190 deletions(-) create mode 100644 .github/workflows/_build_wheel_job.yml diff --git a/.github/workflows/_build_wheel_job.yml b/.github/workflows/_build_wheel_job.yml new file mode 100644 index 0000000..34b46b7 --- /dev/null +++ b/.github/workflows/_build_wheel_job.yml @@ -0,0 +1,102 @@ +name: "(Reusable) Build, Publish and Smoke-test a Wheel" + +on: + workflow_call: + inputs: + runner: + description: "GitHub Actions runner label" + required: true + type: string + pypi_repository_url: + description: "PyPI repository URL (empty string means official PyPI)" + required: false + type: string + default: "" + secrets: + PYPI_API_TOKEN: + required: true + +jobs: + build_publish_test: + name: Build / publish / smoke-test on ${{ inputs.runner }} + runs-on: ${{ inputs.runner }} + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + submodules: recursive + + - name: Set up Python (for cibuildwheel controller) + uses: actions/setup-python@v6 + with: + python-version: '3.11' + + - name: Install cibuildwheel + run: | + pip install --upgrade pip + pip install cibuildwheel==3.4.0 + + - name: Build wheels using cibuildwheel + run: | + python -m cibuildwheel --output-dir wheelhouse + # Save list of built wheels for publishing + ls wheelhouse/*.whl | tee $GITHUB_STEP_SUMMARY + echo "wheels=$(ls wheelhouse/*.whl | tr '\n' ' ')" >> $GITHUB_ENV + + - name: Publish to PyPI + if: success() && github.event_name == 'workflow_dispatch' + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + TWINE_REPOSITORY_URL: ${{ inputs.pypi_repository_url }} + run: | + pip install twine + twine upload --skip-existing --verbose wheelhouse/*.whl + + - name: Smoke test from PyPI + if: success() && github.event_name == 'workflow_dispatch' + shell: bash + env: + PYPI_REPOSITORY_URL: ${{ inputs.pypi_repository_url }} + run: | + # Extract version from wheel filename (e.g. zvec-0.2.1.dev24-cp311-...whl -> 0.2.1.dev24) + WHEEL_FILE=$(ls wheelhouse/zvec-*.whl | head -1) + ZVEC_VERSION=$(basename "$WHEEL_FILE" | sed 's/zvec-\([^-]*\)-.*/\1/') + + # Build index-url flags: use TestPyPI when repository URL is set, otherwise official PyPI + if [ -n "$PYPI_REPOSITORY_URL" ]; then + INDEX_FLAGS="--index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/" + echo "Waiting for zvec==$ZVEC_VERSION to become available on TestPyPI..." + else + INDEX_FLAGS="" + echo "Waiting for zvec==$ZVEC_VERSION to become available on PyPI..." + fi + # Poll until the version is available (max 5 minutes) + FOUND=0 + for i in $(seq 1 30); do + if pip install $INDEX_FLAGS --dry-run "zvec==$ZVEC_VERSION" > /dev/null 2>&1; then + echo "Version $ZVEC_VERSION is available." + FOUND=1 + break + fi + echo "Attempt $i/30: not yet available, retrying in 10s..." + sleep 10 + done + + if [ "$FOUND" -eq 0 ]; then + echo "ERROR: Timed out (5 min) waiting for zvec==$ZVEC_VERSION on PyPI. Aborting smoke test." + exit 1 + fi + + # Create a clean venv and install + python -m venv test_env + source test_env/bin/activate + pip install --upgrade pip + pip install $INDEX_FLAGS "zvec==$ZVEC_VERSION" + pip install --upgrade pip + pip install $INDEX_FLAGS "zvec==$ZVEC_VERSION" + # Run a simple smoke test + python -c "import zvec; print('Import OK:', zvec.__version__)" diff --git a/.github/workflows/build_test_wheel.yml b/.github/workflows/build_test_wheel.yml index 8636d5e..60f431a 100644 --- a/.github/workflows/build_test_wheel.yml +++ b/.github/workflows/build_test_wheel.yml @@ -8,97 +8,28 @@ permissions: jobs: build_wheels_linux_x64: - name: Build wheels on self-hosted manylinux_2_28_x64 for TestPyPi - runs-on: linux_x64 - - steps: - - name: Checkout code - uses: actions/checkout@v6 - with: - submodules: recursive - - - name: Set up Python (for cibuildwheel controller) - uses: actions/setup-python@v6 - with: - python-version: '3.11' - - - name: Install cibuildwheel - run: | - pip install --upgrade pip - pip install cibuildwheel==2.17.0 - - name: Build wheels using cibuildwheel - run: | - python -m cibuildwheel --output-dir wheelhouse - # Save list of built wheels for publishing - ls wheelhouse/*.whl | tee $GITHUB_STEP_SUMMARY - echo "wheels=$(ls wheelhouse/*.whl | tr '\n' ' ')" >> $GITHUB_ENV - - name: Publish to TestPyPI - if: success() && github.event_name == 'workflow_dispatch' - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }} - TWINE_REPOSITORY_URL: https://test.pypi.org/legacy/ - run: | - pip install twine - twine upload --skip-existing --verbose wheelhouse/*.whl - - name: (Optional) Install and test from TestPyPI - if: success() && github.event_name == 'workflow_dispatch' - run: | - # Create a clean venv - python -m venv test_env - source test_env/bin/activate - pip install --upgrade pip - # Install from TestPyPI (must allow pre-releases if version has dev/alpha) - pip install numpy - pip install --index-url https://test.pypi.org/simple/ zvec - # Run a simple smoke test - python -c "import zvec; print('Import OK:', zvec.__version__)" - shell: bash + name: Build wheels on ubuntu-24.04 (x64) for TestPyPi + uses: ./.github/workflows/_build_wheel_job.yml + with: + runner: ubuntu-24.04 + pypi_repository_url: https://test.pypi.org/legacy/ + secrets: + PYPI_API_TOKEN: ${{ secrets.TEST_PYPI_API_TOKEN }} build_wheels_linux_arm64: - name: Build wheels on self-hosted manylinux_2_28_arm64 for TestPyPi - runs-on: linux_arm64 + name: Build wheels on ubuntu-24.04-arm (arm64) for TestPyPi + uses: ./.github/workflows/_build_wheel_job.yml + with: + runner: ubuntu-24.04-arm + pypi_repository_url: https://test.pypi.org/legacy/ + secrets: + PYPI_API_TOKEN: ${{ secrets.TEST_PYPI_API_TOKEN }} - steps: - - name: Checkout code - uses: actions/checkout@v6 - with: - submodules: recursive - - - name: Set up Python (for cibuildwheel controller) - uses: actions/setup-python@v6 - with: - python-version: '3.11' - - - name: Install cibuildwheel - run: | - pip install --upgrade pip - pip install cibuildwheel==2.17.0 - - name: Build wheels using cibuildwheel - run: | - python -m cibuildwheel --output-dir wheelhouse - # Save list of built wheels for publishing - ls wheelhouse/*.whl | tee $GITHUB_STEP_SUMMARY - echo "wheels=$(ls wheelhouse/*.whl | tr '\n' ' ')" >> $GITHUB_ENV - - name: Publish to TestPyPI - if: success() && github.event_name == 'workflow_dispatch' - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }} - TWINE_REPOSITORY_URL: https://test.pypi.org/legacy/ - run: | - pip install twine - twine upload --skip-existing --verbose wheelhouse/*.whl - - name: (Optional) Install and test from TestPyPI - if: success() && github.event_name == 'workflow_dispatch' - run: | - # Create a clean venv - python -m venv test_env - source test_env/bin/activate - pip install --upgrade pip - # Install from TestPyPI (must allow pre-releases if version has dev/alpha) - pip install numpy - pip install --index-url https://test.pypi.org/simple/ zvec - # Run a simple smoke test - python -c "import zvec; print('Import OK:', zvec.__version__)" - shell: bash \ No newline at end of file + build_wheels_macos_arm64: + name: Build wheels on macos-15 (arm64) for TestPyPi + uses: ./.github/workflows/_build_wheel_job.yml + with: + runner: macos-15 + pypi_repository_url: https://test.pypi.org/legacy/ + secrets: + PYPI_API_TOKEN: ${{ secrets.TEST_PYPI_API_TOKEN }} diff --git a/.github/workflows/build_wheel.yml b/.github/workflows/build_wheel.yml index 21cf3c4..0b7f2a3 100644 --- a/.github/workflows/build_wheel.yml +++ b/.github/workflows/build_wheel.yml @@ -8,101 +8,25 @@ permissions: jobs: build_wheels_linux_x64: - name: Build wheels on self-hosted manylinux_2_28_x64 - runs-on: linux_x64 - - steps: - - name: Checkout code - uses: actions/checkout@v6 - with: - submodules: recursive - - - name: Set up Python (for cibuildwheel controller) - uses: actions/setup-python@v6 - with: - python-version: '3.11' - - - name: Install cibuildwheel - run: | - pip install --upgrade pip - pip install cibuildwheel==2.17.0 - - - name: Build wheels using cibuildwheel - run: | - python -m cibuildwheel --output-dir wheelhouse - # Save list of built wheels for publishing - ls wheelhouse/*.whl | tee $GITHUB_STEP_SUMMARY - echo "wheels=$(ls wheelhouse/*.whl | tr '\n' ' ')" >> $GITHUB_ENV - - - name: Publish to PyPI - if: success() && github.event_name == 'workflow_dispatch' - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} - TWINE_REPOSITORY: pypi - run: | - pip install twine - twine upload --skip-existing --verbose wheelhouse/*.whl - - - name: (Optional) Install and test from PyPI - if: success() && github.event_name == 'workflow_dispatch' - run: | - # Create a clean venv - python -m venv test_env - source test_env/bin/activate - pip install --upgrade pip - # Install from PyPI - pip install zvec - # Run a simple smoke test - python -c "import zvec; print('Import OK:', zvec.__version__)" - shell: bash + name: Build wheels on ubuntu-24.04 (x64) for PyPi + uses: ./.github/workflows/_build_wheel_job.yml + with: + runner: ubuntu-24.04 + secrets: + PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }} build_wheels_linux_arm64: - name: Build wheels on self-hosted manylinux_2_28_arm64 - runs-on: linux_arm64 + name: Build wheels on ubuntu-24.04-arm (arm64) for PyPi + uses: ./.github/workflows/_build_wheel_job.yml + with: + runner: ubuntu-24.04-arm + secrets: + PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }} - steps: - - name: Checkout code - uses: actions/checkout@v6 - with: - submodules: recursive - - - name: Set up Python (for cibuildwheel controller) - uses: actions/setup-python@v6 - with: - python-version: '3.11' - - - name: Install cibuildwheel - run: | - pip install --upgrade pip - pip install cibuildwheel==2.17.0 - - - name: Build wheels using cibuildwheel - run: | - python -m cibuildwheel --output-dir wheelhouse - # Save list of built wheels for publishing - ls wheelhouse/*.whl | tee $GITHUB_STEP_SUMMARY - echo "wheels=$(ls wheelhouse/*.whl | tr '\n' ' ')" >> $GITHUB_ENV - - - name: Publish to PyPI - if: success() && github.event_name == 'workflow_dispatch' - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} - TWINE_REPOSITORY: pypi - run: | - pip install twine - twine upload --skip-existing --verbose wheelhouse/*.whl - - - name: (Optional) Install and test from PyPI - if: success() && github.event_name == 'workflow_dispatch' - run: | - # Create a clean venv - python -m venv test_env - source test_env/bin/activate - pip install --upgrade pip - # Install from PyPI - pip install zvec - # Run a simple smoke test - python -c "import zvec; print('Import OK:', zvec.__version__)" - shell: bash \ No newline at end of file + build_wheels_macos_arm64: + name: Build wheels on macos-15 (arm64) for PyPi + uses: ./.github/workflows/_build_wheel_job.yml + with: + runner: macos-15 + secrets: + PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }} diff --git a/pyproject.toml b/pyproject.toml index 349a6c2..486b0b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,7 @@ test = [ "pytest >=8.0", "pytest-cov >=4.1", "pytest-mock >=3.12", - "cibuildwheel == 2.17.0", + "cibuildwheel == 3.4.0", ] docs = [ "mkdocs >=1.5", @@ -70,7 +70,7 @@ dev = [ "pytest >=8.0", "pytest-cov >=4.1", "pytest-mock >=3.12", - "cibuildwheel == 2.17.0", + "cibuildwheel == 3.4.0", # Inherit docs deps "mkdocs >=1.5", "mkdocs-material >=9.5", @@ -130,6 +130,7 @@ BUILD_PYTHON_BINDINGS = "ON" [tool.setuptools_scm] local_scheme = "no-local-version" version_scheme = "guess-next-dev" +fallback_version = "0.2.1b1" ###################################################################################################### # TESTING & QUALITY ###################################################################################################### @@ -169,11 +170,12 @@ build = [ ] build-frontend = "build" test-requires = ["pytest", "numpy"] +test-command = "cd {project} && pytest python/tests -v --tb=short" build-verbosity = 1 [tool.cibuildwheel.linux] archs = ["auto"] -test-command = "cd {project} && pytest python/tests -v --tb=short" +environment = { CMAKE_GENERATOR = "Unix Makefiles", CMAKE_BUILD_PARALLEL_LEVEL = "16" } manylinux-x86_64-image = "manylinux_2_28" manylinux-aarch64-image = "manylinux_2_28" # Skip 32-bit builds and musllinux @@ -181,7 +183,9 @@ skip = ["*-manylinux_i686", "*-musllinux*"] [tool.cibuildwheel.macos] archs = ["arm64"] -environment = { MACOSX_DEPLOYMENT_TARGET = "11.0" } +# Inherits CMAKE_GENERATOR and CMAKE_BUILD_PARALLEL_LEVEL from [tool.cibuildwheel] won't work; +# platform-level environment overrides the top-level entirely, so all vars must be listed here +environment = { CMAKE_GENERATOR = "Unix Makefiles", CMAKE_BUILD_PARALLEL_LEVEL = "16", MACOSX_DEPLOYMENT_TARGET = "11.0" } ###################################################################################################### # CODE QUALITY & FORMATTING (Ruff) ######################################################################################################