Build & Publish to PyPI #12
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: Build & Publish to PyPI | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| name: | |
| description: "Upload to PyPi?" | |
| required: true | |
| default: "true" | |
| type: choice | |
| options: | |
| - "true" | |
| - "false" | |
| jobs: | |
| build_wheels_base: | |
| runs-on: ${{ matrix.os }} | |
| defaults: | |
| run: | |
| shell: bash | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [macos-14, ubuntu-latest, windows-latest] | |
| env: | |
| # Base `svv` wheel: build once per platform (tagged py3-none-<plat>) | |
| CIBW_BUILD: "cp39-*" | |
| CIBW_ARCHS_LINUX: "x86_64 aarch64" | |
| CIBW_ARCHS_MACOS: "universal2" | |
| CIBW_ARCHS_WINDOWS: "AMD64" | |
| CIBW_BUILD_VERBOSITY: 3 | |
| # Ensure a broad Linux compatibility baseline (glibc 2.17) | |
| CIBW_MANYLINUX_X86_64: "manylinux2014" | |
| CIBW_MANYLINUX_AARCH64: "manylinux2014" | |
| # Build MMG + svZeroDSolver into the package layout before wheel build | |
| CIBW_BEFORE_BUILD_LINUX: "python -m pip install -U pip setuptools wheel cmake && python .github/scripts/build_mmg.py --version 5.8.0 --jobs 2 && python .github/scripts/build_0d.py --version 3.0 --jobs 2" | |
| CIBW_BEFORE_BUILD_MACOS: "python -m pip install -U pip setuptools wheel cmake && python .github/scripts/build_mmg.py --version 5.8.0 --arch universal2 --jobs 2 && python .github/scripts/build_0d.py --version 3.0 --arch universal2 --jobs 2" | |
| CIBW_BEFORE_BUILD_WINDOWS: "python -m pip install -U pip setuptools wheel cmake && python .github/scripts/build_mmg.py --version 5.8.0 --arch x86_64 --jobs 2 && python .github/scripts/build_0d.py --version 3.0 --arch x86_64 --jobs 2" | |
| # Enforce that expected executables exist at wheel-build time | |
| CIBW_ENVIRONMENT_LINUX: "PIP_NO_CACHE_DIR=1 SVV_REQUIRE_MMG=1 SVV_REQUIRE_SOLVER_0D=1" | |
| CIBW_ENVIRONMENT_WINDOWS: "PIP_NO_CACHE_DIR=1 SVV_REQUIRE_MMG=1 SVV_REQUIRE_SOLVER_0D=1" | |
| CIBW_ENVIRONMENT_MACOS: "PIP_NO_CACHE_DIR=1 SVV_REQUIRE_MMG=1 SVV_REQUIRE_SOLVER_0D=1 SVV_MMG_ARCH=universal2 SVV_SOLVER_0D_ARCH=universal2 MACOSX_DEPLOYMENT_TARGET=11.0" | |
| steps: | |
| - name: Check out code | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Install cibuildwheel | |
| run: python -m pip install --upgrade pip && python -m pip install cibuildwheel | |
| - name: Set up QEMU (Linux only) | |
| if: runner.os == 'Linux' | |
| uses: docker/setup-qemu-action@v3 | |
| with: | |
| platforms: arm64 | |
| - name: Build wheels (svv) | |
| run: cibuildwheel --output-dir dist | |
| - name: Validate built wheel(s) (macOS) | |
| if: runner.os == 'macOS' | |
| run: | | |
| set -euo pipefail | |
| python -m venv .venv | |
| source .venv/bin/activate | |
| python -m pip install -U pip | |
| wheel=$(ls dist/svv-*.whl | head -n 1) | |
| python -m pip install --no-deps "$wheel" | |
| python - <<'PY' | |
| import os | |
| import subprocess | |
| import tempfile | |
| os.chdir(tempfile.mkdtemp(prefix="svv-wheel-validate-")) | |
| from svv.utils.remeshing.mmg import get_mmg_exe | |
| from svv.utils.solvers.solver_0d import get_solver_0d_exe | |
| exe = get_mmg_exe("mmgs") | |
| print("MMG selected:", exe) | |
| # MMG may return non-zero for help/usage; only validate it executes. | |
| proc = subprocess.run( | |
| [str(exe), "-h"], | |
| stdout=subprocess.DEVNULL, | |
| stderr=subprocess.DEVNULL, | |
| timeout=10, | |
| check=False, | |
| ) | |
| print("mmgs -h exit:", proc.returncode) | |
| solver_0d = get_solver_0d_exe() | |
| print("0D solver selected:", solver_0d) | |
| proc = subprocess.run( | |
| [str(solver_0d), "-h"], | |
| stdout=subprocess.DEVNULL, | |
| stderr=subprocess.DEVNULL, | |
| timeout=10, | |
| check=False, | |
| ) | |
| print("svzerodsolver -h exit:", proc.returncode) | |
| PY | |
| - name: Set up Python 3.9 for release smoke test (macOS) | |
| if: runner.os == 'macOS' | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.9" | |
| - name: Run release smoke test in fresh Python 3.9 env (macOS) | |
| if: runner.os == 'macOS' | |
| run: bash .github/scripts/release_smoke_macos_py39.sh | |
| - name: Validate built wheel(s) (Windows) | |
| if: runner.os == 'Windows' | |
| run: | | |
| set -euo pipefail | |
| python -m venv .venv | |
| source .venv/Scripts/activate | |
| python -m pip install -U pip | |
| wheel=$(ls dist/svv-*win_amd64.whl | head -n 1) | |
| python -m pip install --no-deps "$wheel" | |
| python - <<'PY' | |
| import os | |
| import subprocess | |
| import tempfile | |
| os.chdir(tempfile.mkdtemp(prefix="svv-wheel-validate-")) | |
| from svv.utils.remeshing.mmg import get_mmg_exe | |
| from svv.utils.solvers.solver_0d import get_solver_0d_exe | |
| exe = get_mmg_exe("mmgs") | |
| print("MMG selected:", exe) | |
| proc = subprocess.run( | |
| [str(exe), "-h"], | |
| stdout=subprocess.DEVNULL, | |
| stderr=subprocess.DEVNULL, | |
| timeout=10, | |
| check=False, | |
| ) | |
| print("mmgs -h exit:", proc.returncode) | |
| solver_0d = get_solver_0d_exe() | |
| print("0D solver selected:", solver_0d) | |
| proc = subprocess.run( | |
| [str(solver_0d), "-h"], | |
| stdout=subprocess.DEVNULL, | |
| stderr=subprocess.DEVNULL, | |
| timeout=10, | |
| check=False, | |
| ) | |
| print("svzerodsolver -h exit:", proc.returncode) | |
| PY | |
| - name: Validate built wheel(s) (Linux) | |
| if: runner.os == 'Linux' | |
| run: | | |
| set -euo pipefail | |
| ls -la dist | |
| for wheel in dist/svv-*linux*.whl; do | |
| python .github/scripts/validate_linux_wheel.py "$wheel" | |
| done | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: cibw-wheels-base-${{ matrix.os }} | |
| path: dist | |
| build_wheels_accelerated: | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| matrix: | |
| os: [macos-latest, ubuntu-latest, windows-latest] | |
| env: | |
| CIBW_BUILD: "cp39-* cp310-* cp311-* cp312-* cp313-*" | |
| CIBW_ARCHS_LINUX: "x86_64 aarch64" | |
| CIBW_ARCHS_MACOS: "universal2" | |
| CIBW_ARCHS_WINDOWS: "AMD64" | |
| CIBW_BUILD_VERBOSITY: 3 | |
| # Install build deps before building each wheel (use python -m pip for Windows compatibility) | |
| CIBW_BEFORE_BUILD: "python -m pip install -U pip setuptools wheel cython numpy cmake" | |
| # Tell setup.py to build the companion 'svv-accelerated' distribution | |
| CIBW_ENVIRONMENT: "PIP_NO_CACHE_DIR=1 SVV_ACCEL_COMPANION=1" | |
| steps: | |
| - name: Check out code | |
| uses: actions/checkout@v4 | |
| - name: Install cibuildwheel | |
| run: python -m pip install --upgrade pip && pip install cibuildwheel | |
| - name: Set up QEMU (Linux only) | |
| if: runner.os == 'Linux' | |
| uses: docker/setup-qemu-action@v3 | |
| with: | |
| platforms: arm64 | |
| - name: Build wheels (svv-accelerated) | |
| run: cibuildwheel --output-dir dist | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: cibw-wheels-accelerated-${{ matrix.os }} | |
| path: dist | |
| build_sdist: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install build dependencies | |
| run: | | |
| python -m pip install --upgrade pip && pip install build | |
| - name: Build sdist | |
| run: python -m build --sdist | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: cibw-sdist | |
| path: dist/*.tar.gz | |
| upload_pypi: | |
| needs: [build_wheels_base, build_wheels_accelerated, build_sdist] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| # download all artifacts from previous jobs (base wheels, sdist, accelerated wheels) | |
| pattern: '*' | |
| path: dist | |
| merge-multiple: true | |
| - name: Install build dependencies | |
| run: | | |
| pip install --upgrade pip && pip install twine | |
| - name: Upload base package (svv) to PyPI | |
| run: | | |
| set -euo pipefail | |
| files=$(ls dist/svv-*.whl dist/svv-*.tar.gz 2>/dev/null | grep -v 'svv[-_]accelerated' || true) | |
| if [ -z "$files" ]; then | |
| echo "No base svv artifacts found; skipping upload." | |
| exit 0 | |
| fi | |
| if [ -z "${{ secrets.PYPI_PASSWORD_SVV }}" ]; then | |
| echo "::error title=Missing secret::The repository secret 'PYPI_PASSWORD_SVV' is not configured. Set it to a PyPI API token with access to the 'svv' project."; | |
| exit 1 | |
| fi | |
| twine upload --non-interactive --skip-existing --verbose --repository pypi $files | |
| env: | |
| TWINE_USERNAME: __token__ | |
| TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD_SVV }} | |
| - name: Upload accelerated wheels (svv-accelerated) to PyPI | |
| run: | | |
| set -euo pipefail | |
| # Wheel filenames normalize '-' to '_' (PEP 427), e.g. svv_accelerated-0.0.42-... | |
| files=$(ls dist/svv[-_]accelerated-*.whl 2>/dev/null || true) | |
| if [ -z "$files" ]; then | |
| echo "No svv-accelerated artifacts found; skipping upload." | |
| exit 0 | |
| fi | |
| if [ -z "${{ secrets.PYPI_PASSWORD_ACCEL }}" ]; then | |
| echo "::error title=Missing secret::The repository secret 'PYPI_PASSWORD_ACCEL' is not configured. Set it to a PyPI API token with access to the 'svv-accelerated' project."; | |
| exit 1 | |
| fi | |
| twine upload --non-interactive --skip-existing --verbose --repository pypi $files | |
| env: | |
| TWINE_USERNAME: __token__ | |
| TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD_ACCEL }} | |
| - name: Extract version from artifacts | |
| id: extract_version | |
| run: | | |
| set -euo pipefail | |
| file=$(ls dist/svv-*.whl 2>/dev/null | head -n1 || true) | |
| if [ -z "$file" ]; then file=$(ls dist/svv-*.tar.gz 2>/dev/null | head -n1 || true); fi | |
| if [ -z "$file" ]; then | |
| echo "::error title=Version detection failed::No base svv artifact found to determine version."; exit 1 | |
| fi | |
| base=$(basename "$file") | |
| # Extract version after 'svv-' up to next '-' | |
| version=$(echo "$base" | sed -E 's/^svv-([^\-]+).*/\1/') | |
| echo "Detected version: $version" | |
| echo "version=$version" >> "$GITHUB_OUTPUT" | |
| - name: Create GitHub Release and upload artifacts | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ steps.extract_version.outputs.version }} | |
| name: svv ${{ steps.extract_version.outputs.version }} | |
| generate_release_notes: true | |
| draft: false | |
| prerelease: false | |
| files: | | |
| dist/svv-*.whl | |
| dist/svv-*.tar.gz | |
| dist/svv_accelerated-*.whl | |
| dist/svv-accelerated-*.whl |