diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml new file mode 100644 index 00000000..24c65d63 --- /dev/null +++ b/.github/workflows/wheels.yml @@ -0,0 +1,137 @@ +name: Build Wheels + +on: + workflow_dispatch: + inputs: + publish_testpypi: + description: 'Publish to TestPyPI' + type: boolean + default: false + push: + tags: + - "v*" + pull_request: + branches: [master] + paths: + - ".github/workflows/wheels.yml" + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + # ========================================================================= + # Build source distribution + # ========================================================================= + build_sdist: + name: Build source distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - uses: astral-sh/setup-uv@v7 + + - name: Build sdist + run: uvx --from build pyproject-build --sdist + + - uses: actions/upload-artifact@v5 + with: + name: cibw-sdist + path: dist/*.tar.gz + + # ========================================================================= + # Build wheels on all platforms + # ========================================================================= + build_wheels: + name: Wheels on ${{ matrix.os }} (${{ matrix.cibw_archs }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + # Linux x86_64 + - os: ubuntu-latest + cibw_archs: "x86_64" + + # Linux aarch64 (native ARM runner) + - os: ubuntu-24.04-arm + cibw_archs: "aarch64" + + # macOS Apple Silicon only + - os: macos-latest + cibw_archs: "arm64" + + # Windows x64 + - os: windows-latest + cibw_archs: "AMD64" + + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Build wheels + uses: pypa/cibuildwheel@v3.3 + env: + CIBW_ARCHS: ${{ matrix.cibw_archs }} + + - uses: actions/upload-artifact@v5 + with: + name: cibw-wheels-${{ matrix.os }}-${{ matrix.cibw_archs }} + path: ./wheelhouse/*.whl + + # ========================================================================= + # Publish to TestPyPI (manual trigger) + # ========================================================================= + publish_testpypi: + name: Publish to TestPyPI + needs: [build_sdist, build_wheels] + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' && github.event.inputs.publish_testpypi == 'true' + environment: + name: testpypi + url: https://test.pypi.org/p/simcoon + permissions: + id-token: write + + steps: + - uses: actions/download-artifact@v6 + with: + pattern: cibw-* + path: dist + merge-multiple: true + + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + + # ========================================================================= + # Publish to PyPI (release tags only) + # ========================================================================= + publish_pypi: + name: Publish to PyPI + needs: [build_sdist, build_wheels] + runs-on: ubuntu-latest + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + environment: + name: pypi + url: https://pypi.org/p/simcoon + permissions: + id-token: write + + steps: + - uses: actions/download-artifact@v6 + with: + pattern: cibw-* + path: dist + merge-multiple: true + + - name: Display artifacts + run: ls -la dist/ + + - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/CMakeLists.txt b/CMakeLists.txt index d229db02..f1664ea8 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -173,7 +173,7 @@ set_target_properties(dylib PROPERTIES UNITY_BUILD OFF) # Python dependencies (only if building Python bindings) if(BUILD_PYTHON_BINDINGS) - find_package(Python3 COMPONENTS Interpreter Development NumPy REQUIRED) + find_package(Python3 COMPONENTS Interpreter Development.Module NumPy REQUIRED) find_package(pybind11 REQUIRED) # Try to find carma from conda/system first diff --git a/pyproject.toml b/pyproject.toml index fc127051..8b25ae76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -110,4 +110,82 @@ addopts = "-v --tb=short" # Requires build dependencies to be installed first: scikit-build-core, pybind11, numpy no-build-isolation-package = ["simcoon"] +# ============================================================================= +# cibuildwheel configuration +# ============================================================================= + +[tool.cibuildwheel] +# Build for CPython 3.10-3.14 +build = "cp310-* cp311-* cp312-* cp313-* cp314-*" + +# Skip unsupported configurations +skip = [ + "*-musllinux*", # musl libc: complex C++ deps don't support musl + "pp*", # PyPy: pybind11/numpy compatibility issues + "*-win32", # 32-bit Windows: not supported + "*_i686", # 32-bit Linux: not supported + "*-macosx_x86_64", # macOS Intel: not supported +] + +# Build verbosity +build-verbosity = 1 + +# Test configuration +test-requires = ["pytest>=7.0", "numpy>=2.0"] +test-command = "pytest {project}/simcoon-python-builder/test -v --tb=short" + +# ----------------------------------------------------------------------------- +# Linux configuration +# ----------------------------------------------------------------------------- + +[tool.cibuildwheel.linux] +# Use manylinux_2_28 for C++20 compiler support (GCC 12+) and newer tools +manylinux-x86_64-image = "manylinux_2_28" +manylinux-aarch64-image = "manylinux_2_28" + +# Install dependencies before build +before-all = [ + "dnf install -y epel-release", + "dnf install -y cmake ninja-build openblas-devel lapack-devel wget", + # Build Armadillo from source (manylinux packages are too old) + "wget -q https://sourceforge.net/projects/arma/files/armadillo-14.0.2.tar.xz", + "tar -xf armadillo-14.0.2.tar.xz", + "cd armadillo-14.0.2 && cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr/local -DDETECT_HDF5=OFF -DBUILD_SHARED_LIBS=ON && cmake --build build && cmake --install build", +] + +# Environment for finding installed dependencies +environment = { CMAKE_PREFIX_PATH = "/usr/local", LD_LIBRARY_PATH = "/usr/local/lib:/usr/local/lib64" } + +# auditwheel bundles shared libraries +repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel}" + +# ----------------------------------------------------------------------------- +# macOS configuration (arm64 only) +# ----------------------------------------------------------------------------- + +[tool.cibuildwheel.macos] +# Install dependencies via Homebrew +before-all = "brew install armadillo openblas ninja" + +# Build for arm64 only (native on Apple Silicon runners) +archs = ["arm64"] + +# Deployment target (must match Homebrew library targets) +environment = { MACOSX_DEPLOYMENT_TARGET = "15.0" } + +# delocate bundles shared libraries +repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}" + +# ----------------------------------------------------------------------------- +# Windows configuration +# ----------------------------------------------------------------------------- + +[tool.cibuildwheel.windows] +# Install dependencies via vcpkg +before-all = "vcpkg install armadillo:x64-windows openblas:x64-windows" + +# Point CMake to vcpkg (use forward slashes for TOML compatibility) +environment = { CMAKE_TOOLCHAIN_FILE = "C:/vcpkg/scripts/buildsystems/vcpkg.cmake" } +# Skip wheel repair - CMake installs all needed DLLs directly into the wheel +repair-wheel-command = ""