Skip to content

Build & Publish to PyPI #12

Build & Publish to PyPI

Build & Publish to PyPI #12

Workflow file for this run

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