Skip to content

feat: Add and update tests for v0.2.0 Python bindings #175

feat: Add and update tests for v0.2.0 Python bindings

feat: Add and update tests for v0.2.0 Python bindings #175

Workflow file for this run

name: Build and Test Wheels
on:
push:
branches: [main, master]
pull_request:
workflow_dispatch:
inputs:
ref:
description: 'Git ref to build from (tag like v0.1.6)'
required: false
type: string
workflow_call:
inputs:
ref:
description: 'Git ref to build from'
required: false
type: string
env:
# CFD C library version to build against
# v0.2.0 adds 3D support, Poisson solver, GPU management, logging
CFD_VERSION: "v0.2.0"
jobs:
build_wheel:
name: Build ${{ matrix.variant }} wheel on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
variant: [cpu, cuda]
exclude:
# macOS doesn't support CUDA
- os: macos-latest
variant: cuda
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ inputs.ref || github.ref }}
- name: Checkout CFD C library
uses: actions/checkout@v4
with:
repository: ${{ github.repository_owner }}/cfd
ref: ${{ env.CFD_VERSION }}
path: cfd
fetch-depth: 0
- name: Set up Python 3.9
uses: actions/setup-python@v5
with:
python-version: "3.9"
- name: Install uv
uses: astral-sh/setup-uv@v4
with:
enable-cache: true
cache-dependency-glob: "pyproject.toml"
- name: Install build dependencies
run: uv pip install --system build scikit-build-core setuptools-scm
# ============ CPU-only builds ============
# Note: Linux CPU build is done inside manylinux container (see Build wheel step)
- name: Build CFD library (Linux - CPU only)
if: runner.os == 'Linux' && matrix.variant == 'cpu'
run: |
echo "Linux CPU build will be done inside manylinux container"
echo "Skipping host build to ensure glibc compatibility"
- name: Build CFD library (macOS - CPU only)
if: runner.os == 'macOS'
run: |
cmake -S cfd -B cfd/build \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DCFD_ENABLE_CUDA=OFF
cmake --build cfd/build --config Release
echo "=== CFD library built (CPU-only) ==="
ls -la cfd/build/lib/
- name: Build CFD library (Windows - CPU only)
if: runner.os == 'Windows' && matrix.variant == 'cpu'
run: |
# Build CPU-only for maximum compatibility
cmake -S cfd -B cfd/build `
-DCMAKE_BUILD_TYPE=Release `
-DBUILD_SHARED_LIBS=OFF `
-DCMAKE_POSITION_INDEPENDENT_CODE=ON `
-DCFD_ENABLE_CUDA=OFF
cmake --build cfd/build --config Release
echo "=== CFD library built (CPU-only) ==="
dir cfd\build\lib\Release
# ============ CUDA builds ============
# Note: Linux CUDA build is done inside NVIDIA manylinux container (see Build wheel step)
- name: Build CFD library (Linux with CUDA)
if: runner.os == 'Linux' && matrix.variant == 'cuda'
run: |
echo "Linux CUDA build will be done inside NVIDIA manylinux container"
echo "Skipping host build to ensure manylinux compatibility"
- name: Install CUDA Toolkit (Windows)
if: runner.os == 'Windows' && matrix.variant == 'cuda'
uses: Jimver/cuda-toolkit@v0.2.18
with:
cuda: '12.4.0'
method: 'network'
# visual_studio_integration required for CMake to find CUDA toolset
sub-packages: '["nvcc", "cudart", "nvrtc_dev", "cublas_dev", "cusparse_dev", "visual_studio_integration"]'
- name: Build CFD library (Windows with CUDA)
if: runner.os == 'Windows' && matrix.variant == 'cuda'
run: |
# Build with CUDA for Turing+ architectures
cmake -S cfd -B cfd/build `
-DCMAKE_BUILD_TYPE=Release `
-DBUILD_SHARED_LIBS=OFF `
-DCMAKE_POSITION_INDEPENDENT_CODE=ON `
-DCFD_ENABLE_CUDA=ON `
-DCFD_CUDA_ARCHITECTURES="75;80;86;89;90"
cmake --build cfd/build --config Release
echo "=== CFD library built with CUDA ==="
dir cfd\build\lib\Release
# ============ Build wheels ============
# Linux wheels must be built inside manylinux container for glibc compatibility
- name: Build wheel (Linux - CPU)
if: runner.os == 'Linux' && matrix.variant == 'cpu'
run: |
docker run --rm \
-v "${{ github.workspace }}:/workspace" \
-w /workspace \
-e CFD_STATIC_LINK=ON \
-e CFD_USE_STABLE_ABI=ON \
quay.io/pypa/manylinux_2_28_x86_64 \
bash -c "
set -e
# Build CFD C library inside container
cmake -S cfd -B cfd/build \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DCFD_ENABLE_CUDA=OFF
cmake --build cfd/build --config Release
# Build Python wheel
export CFD_ROOT=/workspace/cfd
/opt/python/cp39-cp39/bin/pip install scikit-build-core setuptools-scm
/opt/python/cp39-cp39/bin/pip wheel . --no-deps --wheel-dir dist_raw/
# Repair wheel for manylinux compatibility
/opt/python/cp39-cp39/bin/pip install auditwheel
auditwheel repair dist_raw/*.whl --plat manylinux_2_28_x86_64 -w dist/
"
echo "=== Wheel built (manylinux) ==="
ls -la dist/
- name: Build wheel (Linux - CUDA)
if: runner.os == 'Linux' && matrix.variant == 'cuda'
run: |
# Build inside NVIDIA's manylinux-compatible CUDA container
# Rocky Linux 8 is manylinux_2_28 compatible
# Use --user to match host UID/GID for proper file ownership
docker run --rm \
-v "${{ github.workspace }}:/workspace" \
-w /workspace \
-e CFD_STATIC_LINK=ON \
-e CFD_USE_STABLE_ABI=ON \
-e HOME=/tmp \
nvidia/cuda:12.4.0-devel-rockylinux8 \
bash -c "
set -e
# Install build tools (git for CMake FetchContent)
dnf install -y cmake git python3.11 python3.11-pip python3.11-devel
# Install patchelf via pip (EPEL version is too old for auditwheel)
python3.11 -m pip install patchelf
# Clean any previous build artifacts
rm -rf cfd/build build dist dist_raw
# Build CFD C library with CUDA
# 75=Turing, 80=Ampere, 86=Ampere, 89=Ada, 90=Hopper
cmake -S cfd -B cfd/build \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DCFD_ENABLE_CUDA=ON \
-DCFD_CUDA_ARCHITECTURES='75;80;86;89;90'
cmake --build cfd/build --config Release
# Build Python wheel
export CFD_ROOT=/workspace/cfd
python3.11 -m pip install scikit-build-core setuptools-scm
python3.11 -m pip wheel . --no-deps --wheel-dir dist_raw/
# Repair wheel for manylinux compatibility
python3.11 -m pip install auditwheel
auditwheel repair dist_raw/*.whl --plat manylinux_2_28_x86_64 -w dist/
# Fix permissions for host user
chmod -R 777 dist/
"
echo "=== Wheel built (CUDA manylinux) ==="
ls -la dist/
- name: Build wheel (macOS)
if: runner.os == 'macOS'
env:
CFD_ROOT: ${{ github.workspace }}/cfd
CFD_STATIC_LINK: "ON"
CFD_USE_STABLE_ABI: "ON"
run: |
pip wheel . --no-deps --wheel-dir dist/
echo "=== Wheel built ==="
ls -la dist/
- name: Build wheel (Windows)
if: runner.os == 'Windows'
env:
CFD_ROOT: ${{ github.workspace }}/cfd
CFD_STATIC_LINK: "ON"
CFD_USE_STABLE_ABI: "ON"
run: |
pip wheel . --no-deps --wheel-dir dist/
echo "=== Wheel built ==="
dir dist
- name: Inspect wheel contents
run: |
python -c "
import glob, zipfile
for wheel in glob.glob('dist/*.whl'):
print(f'=== Contents of {wheel} ===')
with zipfile.ZipFile(wheel) as zf:
for name in zf.namelist():
print(name)
"
# Upload wheels with variant in artifact name
# Note: Wheel filenames are standard (no variant suffix) to comply with PEP 427
# The variant (cpu/cuda) is encoded in the artifact name for differentiation
- uses: actions/upload-artifact@v4
with:
name: wheel-${{ matrix.os }}-${{ matrix.variant }}
path: dist/*.whl
test_wheel:
name: Test ${{ matrix.variant }} wheel on ${{ matrix.os }} with Python ${{ matrix.python }}
needs: [build_wheel]
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python: ["3.9", "3.13"]
variant: [cpu, cuda]
exclude:
# macOS doesn't have CUDA wheels
- os: macos-latest
variant: cuda
steps:
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v5
with:
python-version: "${{ matrix.python }}"
- name: Install uv
uses: astral-sh/setup-uv@v4
with:
enable-cache: false
# Install CUDA runtime for CUDA wheel tests (must match build version)
- name: Install CUDA Toolkit (Linux - for testing)
if: runner.os == 'Linux' && matrix.variant == 'cuda'
run: |
# Install CUDA runtime libraries via apt (more reliable than runfile installer)
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt-get update
sudo apt-get install -y cuda-cudart-12-4
echo "LD_LIBRARY_PATH=/usr/local/cuda-12.4/lib64:$LD_LIBRARY_PATH" >> $GITHUB_ENV
- name: Install CUDA Toolkit (Windows - for testing)
if: runner.os == 'Windows' && matrix.variant == 'cuda'
uses: Jimver/cuda-toolkit@v0.2.18
with:
cuda: '12.4.0'
- uses: actions/download-artifact@v4
with:
name: wheel-${{ matrix.os }}-${{ matrix.variant }}
path: dist
- name: Install wheel (Unix)
if: runner.os != 'Windows'
run: |
python -m pip install dist/*.whl
python -m pip install pytest numpy
- name: Install wheel (Windows)
if: runner.os == 'Windows'
run: |
python -m pip install (Get-ChildItem dist/*.whl).FullName
python -m pip install pytest numpy
- name: Test import (Unix)
if: runner.os != 'Windows'
run: |
cd /tmp
python -c "
import cfd_python
print('Package loaded:', cfd_python.__file__)
print('Version:', cfd_python.__version__)
print('Has list_solvers:', hasattr(cfd_python, 'list_solvers'))
if hasattr(cfd_python, 'list_solvers'):
print('Solvers:', cfd_python.list_solvers())
"
- name: Test import (Windows)
if: runner.os == 'Windows'
run: |
cd $env:TEMP
python -c "import cfd_python; print('Package loaded:', cfd_python.__file__); print('Version:', cfd_python.__version__); print('Has list_solvers:', hasattr(cfd_python, 'list_solvers')); print('Solvers:', cfd_python.list_solvers()) if hasattr(cfd_python, 'list_solvers') else None"
- uses: actions/checkout@v4
with:
sparse-checkout: tests
sparse-checkout-cone-mode: false
- name: Run tests (Unix)
if: runner.os != 'Windows'
run: |
cd /tmp
pytest $GITHUB_WORKSPACE/tests/ -v
- name: Run tests (Windows)
if: runner.os == 'Windows'
run: |
cd $env:TEMP
pytest "$env:GITHUB_WORKSPACE\tests" -v