From 8a61cd8fdaf75040a48d5af9206212729ee986c0 Mon Sep 17 00:00:00 2001 From: shaia Date: Mon, 1 Dec 2025 08:30:28 +0200 Subject: [PATCH 1/3] Update DISTRIBUTION.md to reflect current CI/CD setup - Document stable ABI configuration (CFD_USE_STABLE_ABI, wheel.py-api) - Replace outdated cibuildwheel docs with current pip wheel approach - Document build-wheels.yml reusable workflow - Document publish.yml with trusted publishing (OIDC) - Add trusted publishing setup instructions - Update action versions (v3 -> v4) --- DISTRIBUTION.md | 230 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 153 insertions(+), 77 deletions(-) diff --git a/DISTRIBUTION.md b/DISTRIBUTION.md index db8d201..a6a7297 100644 --- a/DISTRIBUTION.md +++ b/DISTRIBUTION.md @@ -41,46 +41,55 @@ cfd-workspace/ ### CMakeLists.txt -The build system uses `CFD_STATIC_LINK` to control linking with platform-specific library names: +The build system uses `CFD_STATIC_LINK` to control linking and `CFD_USE_STABLE_ABI` for Python version compatibility: ```cmake option(CFD_STATIC_LINK "Statically link the CFD library" ON) +option(CFD_USE_STABLE_ABI "Use Python stable ABI for cross-version compatibility" OFF) +# Static library discovery with platform-specific names if(CFD_STATIC_LINK) if(WIN32) - # Windows: look for cfd_library_static.lib or cfd_library.lib - find_library(CFD_LIBRARY - NAMES cfd_library_static cfd_library - PATHS ${CFD_LIBRARY_DIR} - NO_DEFAULT_PATH - ) + find_library(CFD_LIBRARY NAMES cfd_library_static cfd_library ...) else() - # Unix: look for libcfd_library.a - find_library(CFD_LIBRARY - NAMES libcfd_library.a cfd_library - PATHS ${CFD_LIBRARY_DIR} - NO_DEFAULT_PATH - ) + find_library(CFD_LIBRARY NAMES libcfd_library.a cfd_library ...) + endif() +endif() + +# Stable ABI support for single-wheel-per-platform builds +if(CFD_USE_STABLE_ABI AND WIN32) + # Windows: manually link against python3.lib (stable ABI) + add_library(cfd_python MODULE src/cfd_python.c) + target_compile_definitions(cfd_python PRIVATE Py_LIMITED_API=0x03080000) + target_link_libraries(cfd_python PRIVATE ${PYTHON3_STABLE_LIB}) +else() + # Unix: use Python_add_library with .abi3.so suffix + Python_add_library(cfd_python MODULE WITH_SOABI src/cfd_python.c) + if(CFD_USE_STABLE_ABI) + target_compile_definitions(cfd_python PRIVATE Py_LIMITED_API=0x03080000) + set_target_properties(cfd_python PROPERTIES SUFFIX ".abi3.so") endif() endif() ``` ### pyproject.toml -Static linking is enabled in the build configuration: +Static linking and stable ABI are enabled in the build configuration: ```toml +[tool.scikit-build] +minimum-version = "0.4" +build-dir = "build/{wheel_tag}" +wheel.py-api = "cp38" # Target Python 3.8+ stable ABI + [tool.scikit-build.cmake.define] CMAKE_BUILD_TYPE = "Release" CFD_STATIC_LINK = "ON" - -[tool.cibuildwheel] -# Global environment - static linking for self-contained wheels -# CFD_ROOT can be set to override the default ../cfd location -# CFD_USE_STABLE_ABI is enabled for wheel builds to support multiple Python versions -environment = { CMAKE_BUILD_TYPE = "Release", CFD_STATIC_LINK = "ON", CFD_USE_STABLE_ABI = "ON", CFD_ROOT = "../cfd" } +CFD_USE_STABLE_ABI = "ON" ``` +The `wheel.py-api = "cp38"` setting combined with `CFD_USE_STABLE_ABI` produces a single wheel per platform that works with Python 3.8+. + --- ## Dynamic Solver Discovery @@ -123,91 +132,158 @@ cd ../cfd-python pip install -e . ``` -### Build Wheels with cibuildwheel +### CI Build Process -```bash -pip install cibuildwheel -python -m cibuildwheel --output-dir wheelhouse -``` +The GitHub Actions workflow builds wheels directly using `pip wheel` with the CFD library checked out as a sibling directory. This approach: -This creates wheels for Python 3.8-3.12 on all platforms. +- Produces one wheel per platform (stable ABI) +- Works with Python 3.8-3.12+ +- Statically links the CFD C library -### Platform-Specific Build Steps +**Build steps (all platforms):** -The `pyproject.toml` configures platform-specific build steps. The `CFD_ROOT` environment variable specifies the C library location (defaults to `../cfd`): +1. Checkout cfd-python repository +2. Checkout cfd C library to `./cfd` directory +3. Build CFD library with CMake (static, release) +4. Build wheel with `pip wheel . --no-deps` -**Windows:** -```toml -[tool.cibuildwheel.windows] -before-build = [ - "cd %CFD_ROOT% && cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF", - "cd %CFD_ROOT% && cmake --build build --config Release", -] -environment = { CMAKE_BUILD_TYPE = "Release", CFD_STATIC_LINK = "ON", CFD_USE_STABLE_ABI = "ON", CFD_ROOT = "../cfd" } -``` +**Environment variables:** -**macOS:** -```toml -[tool.cibuildwheel.macos] -before-build = [ - "cd ${CFD_ROOT:-../cfd} && cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF", - "cd ${CFD_ROOT:-../cfd} && cmake --build build --config Release", -] -repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} {wheel}" -environment = { CMAKE_BUILD_TYPE = "Release", CFD_STATIC_LINK = "ON", CFD_USE_STABLE_ABI = "ON", CFD_ROOT = "../cfd" } -``` - -**Linux:** -```toml -[tool.cibuildwheel.linux] -before-all = [ - "yum install -y cmake3 gcc-c++ || apt-get update && apt-get install -y cmake g++", -] -before-build = [ - "cd ${CFD_ROOT:-../cfd} && cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF", - "cd ${CFD_ROOT:-../cfd} && cmake --build build --config Release", -] -repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel}" -environment = { CMAKE_BUILD_TYPE = "Release", CFD_STATIC_LINK = "ON", CFD_USE_STABLE_ABI = "ON", CFD_ROOT = "../cfd" } -``` +- `CFD_ROOT`: Path to CFD C library (set to `./cfd` in CI) +- `CFD_STATIC_LINK=ON`: Enable static linking +- `CFD_USE_STABLE_ABI=ON`: Build for Python stable ABI --- ## CI/CD Pipeline -### GitHub Actions Workflow +### GitHub Actions Workflows + +The project uses two workflows: + +#### build-wheels.yml (Reusable) + +Builds and tests wheels on all platforms. Triggered on push, PR, or called by other workflows. ```yaml -name: Build and Test +name: Build and Test Wheels -on: [push, pull_request] +on: + push: + branches: [main, master] + pull_request: + workflow_dispatch: + workflow_call: # Allows publish.yml to reuse this workflow jobs: - build_wheels: - name: Build wheels on ${{ matrix.os }} + build_wheel: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - + os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/checkout@v4 + - name: Checkout CFD C library + uses: actions/checkout@v4 with: - submodules: true - - uses: pypa/cibuildwheel@v2.16.2 - - uses: actions/upload-artifact@v3 + repository: ${{ github.repository_owner }}/cfd + path: cfd + - name: Build CFD library + run: | + cmake -S cfd -B cfd/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF + cmake --build cfd/build --config Release + - name: Build wheel + env: + CFD_ROOT: ${{ github.workspace }}/cfd + CFD_STATIC_LINK: "ON" + CFD_USE_STABLE_ABI: "ON" + run: pip wheel . --no-deps --wheel-dir dist/ + - uses: actions/upload-artifact@v4 with: - path: ./wheelhouse/*.whl + name: wheel-${{ matrix.os }} + path: dist/*.whl - upload_pypi: - needs: [build_wheels] + test_wheel: + needs: [build_wheel] + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python: ["3.8", "3.12"] + steps: + # Install wheel, run import test, run pytest +``` + +#### publish.yml (PyPI Publishing) + +Publishes to TestPyPI or PyPI using trusted publishing (OIDC). + +```yaml +name: Publish to PyPI + +on: + release: + types: [published] + push: + tags: ['v*'] + workflow_dispatch: + inputs: + target: + type: choice + options: [testpypi, pypi] + +concurrency: + group: publish-${{ github.ref }} + cancel-in-progress: false + +jobs: + build: + uses: ./.github/workflows/build-wheels.yml # Reuse build workflow + + build_sdist: runs-on: ubuntu-latest - if: github.event_name == 'release' steps: - - uses: actions/download-artifact@v3 - - uses: pypa/gh-action-pypi-publish@v1.8.10 + - uses: actions/checkout@v4 + - run: python -m build --sdist + - uses: actions/upload-artifact@v4 + + publish_testpypi: + needs: [build, build_sdist] + if: github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'testpypi' + environment: testpypi + permissions: + id-token: write # Required for trusted publishing + steps: + - uses: actions/download-artifact@v4 + - name: Validate artifacts + run: | + # Ensure at least 3 wheels (linux, macos, windows) and 1 sdist + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + + publish_pypi: + needs: [build, build_sdist] + if: github.event_name == 'release' || startsWith(github.ref, 'refs/tags/v') + environment: pypi + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v4 + - uses: pypa/gh-action-pypi-publish@release/v1 ``` +### Trusted Publishing Setup + +The workflows use PyPI trusted publishing (OIDC) instead of API tokens: + +1. Go to PyPI/TestPyPI → Your Project → Settings → Publishing +2. Add a new trusted publisher with: + - Owner: `` + - Repository: `cfd-python` + - Workflow: `publish.yml` + - Environment: `pypi` or `testpypi` + --- ## Distribution Methods Comparison From 338539cff67415d7dd271cbc99f812e4904b0533 Mon Sep 17 00:00:00 2001 From: shaia Date: Mon, 1 Dec 2025 21:45:33 +0200 Subject: [PATCH 2/3] Fix documentation and pin pypi-publish action SHA - Fix CFD_USE_STABLE_ABI default value (ON not OFF) - Add platform-specific CFD library build steps (Unix needs -DCMAKE_POSITION_INDEPENDENT_CODE=ON) - Add complete artifact download steps with patterns and validation - Fix publish_pypi conditional to include workflow_dispatch target - Pin pypa/gh-action-pypi-publish to SHA to prevent supply-chain attacks --- .github/workflows/publish.yml | 4 +-- DISTRIBUTION.md | 59 ++++++++++++++++++++++++++++++----- 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9d27d5e..4a79886 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -97,7 +97,7 @@ jobs: fi - name: Publish to TestPyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4 with: repository-url: https://test.pypi.org/legacy/ @@ -144,4 +144,4 @@ jobs: fi - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4 diff --git a/DISTRIBUTION.md b/DISTRIBUTION.md index a6a7297..076ffd1 100644 --- a/DISTRIBUTION.md +++ b/DISTRIBUTION.md @@ -45,7 +45,7 @@ The build system uses `CFD_STATIC_LINK` to control linking and `CFD_USE_STABLE_A ```cmake option(CFD_STATIC_LINK "Statically link the CFD library" ON) -option(CFD_USE_STABLE_ABI "Use Python stable ABI for cross-version compatibility" OFF) +option(CFD_USE_STABLE_ABI "Use Python stable ABI for cross-version compatibility" ON) # Static library discovery with platform-specific names if(CFD_STATIC_LINK) @@ -188,7 +188,13 @@ jobs: with: repository: ${{ github.repository_owner }}/cfd path: cfd - - name: Build CFD library + - name: Build CFD library (Unix) + if: runner.os != 'Windows' + run: | + cmake -S cfd -B cfd/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON + cmake --build cfd/build --config Release + - name: Build CFD library (Windows) + if: runner.os == 'Windows' run: | cmake -S cfd -B cfd/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF cmake --build cfd/build --config Release @@ -254,23 +260,60 @@ jobs: permissions: id-token: write # Required for trusted publishing steps: - - uses: actions/download-artifact@v4 + - name: Download wheels + uses: actions/download-artifact@v4 + with: + pattern: wheel-* + path: dist + merge-multiple: true + - name: Download sdist + uses: actions/download-artifact@v4 + with: + name: sdist + path: dist - name: Validate artifacts run: | - # Ensure at least 3 wheels (linux, macos, windows) and 1 sdist - - uses: pypa/gh-action-pypi-publish@release/v1 + ls -la dist/ + WHEELS=$(ls dist/*.whl 2>/dev/null | wc -l) + SDIST=$(ls dist/*.tar.gz 2>/dev/null | wc -l) + echo "Found $WHEELS wheel(s) and $SDIST sdist(s)" + if [ "$WHEELS" -lt 3 ]; then + echo "ERROR: Expected at least 3 wheels (linux, macos, windows)" + exit 1 + fi + # Pin to SHA to prevent supply-chain attacks (id-token: write is sensitive) + - uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4 with: repository-url: https://test.pypi.org/legacy/ publish_pypi: needs: [build, build_sdist] - if: github.event_name == 'release' || startsWith(github.ref, 'refs/tags/v') + if: github.event_name == 'release' || (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) || (github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'pypi') environment: pypi permissions: id-token: write steps: - - uses: actions/download-artifact@v4 - - uses: pypa/gh-action-pypi-publish@release/v1 + - name: Download wheels + uses: actions/download-artifact@v4 + with: + pattern: wheel-* + path: dist + merge-multiple: true + - name: Download sdist + uses: actions/download-artifact@v4 + with: + name: sdist + path: dist + - name: Validate artifacts + run: | + ls -la dist/ + WHEELS=$(ls dist/*.whl 2>/dev/null | wc -l) + SDIST=$(ls dist/*.tar.gz 2>/dev/null | wc -l) + if [ "$WHEELS" -lt 3 ] || [ "$SDIST" -lt 1 ]; then + echo "ERROR: Missing artifacts"; exit 1 + fi + # Pin to SHA to prevent supply-chain attacks (id-token: write is sensitive) + - uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4 ``` ### Trusted Publishing Setup From 759c9a1ac73d6cb78d5ab5b76dbc1e5877ca4251 Mon Sep 17 00:00:00 2001 From: shaia Date: Mon, 1 Dec 2025 22:02:36 +0200 Subject: [PATCH 3/3] Remove DISTRIBUTION.md CI/CD workflows are self-documenting with comments. --- DISTRIBUTION.md | 402 ------------------------------------------------ 1 file changed, 402 deletions(-) delete mode 100644 DISTRIBUTION.md diff --git a/DISTRIBUTION.md b/DISTRIBUTION.md deleted file mode 100644 index 076ffd1..0000000 --- a/DISTRIBUTION.md +++ /dev/null @@ -1,402 +0,0 @@ -# CFD Python Distribution Guide - -## Quick Start - -**For users:** `pip install cfd-python` (works everywhere) -**For developers:** Push to GitHub -> Automated builds -> PyPI publishing - ---- - -## Distribution Strategy - -### Self-Contained Python Package with Static Linking - -The cfd-python package uses **static linking** to embed the CFD C library directly into the Python extension. This creates self-contained wheels that don't require users to have the C library installed separately. - -``` -cfd-workspace/ -├── cfd/ # C library (external, statically linked) -│ ├── lib/include/ # Headers -│ └── build/lib/Release/ # Static library (.a or .lib) -└── cfd-python/ # Python bindings - ├── pyproject.toml # Build configuration - ├── CMakeLists.txt # CMake build with CFD_STATIC_LINK - ├── src/cfd_python.c # Python C extension - ├── cfd_python/ # Python package - │ └── __init__.py # Dynamic solver discovery - └── tests/ # Test suite -``` - -**Benefits:** - -- One command install: `pip install cfd-python` -- Cross-platform: Windows, macOS, Linux -- No build dependencies for users -- Dynamic solver discovery (new solvers automatically available) -- Static linking means no DLL/shared library issues - ---- - -## Static Linking Configuration - -### CMakeLists.txt - -The build system uses `CFD_STATIC_LINK` to control linking and `CFD_USE_STABLE_ABI` for Python version compatibility: - -```cmake -option(CFD_STATIC_LINK "Statically link the CFD library" ON) -option(CFD_USE_STABLE_ABI "Use Python stable ABI for cross-version compatibility" ON) - -# Static library discovery with platform-specific names -if(CFD_STATIC_LINK) - if(WIN32) - find_library(CFD_LIBRARY NAMES cfd_library_static cfd_library ...) - else() - find_library(CFD_LIBRARY NAMES libcfd_library.a cfd_library ...) - endif() -endif() - -# Stable ABI support for single-wheel-per-platform builds -if(CFD_USE_STABLE_ABI AND WIN32) - # Windows: manually link against python3.lib (stable ABI) - add_library(cfd_python MODULE src/cfd_python.c) - target_compile_definitions(cfd_python PRIVATE Py_LIMITED_API=0x03080000) - target_link_libraries(cfd_python PRIVATE ${PYTHON3_STABLE_LIB}) -else() - # Unix: use Python_add_library with .abi3.so suffix - Python_add_library(cfd_python MODULE WITH_SOABI src/cfd_python.c) - if(CFD_USE_STABLE_ABI) - target_compile_definitions(cfd_python PRIVATE Py_LIMITED_API=0x03080000) - set_target_properties(cfd_python PROPERTIES SUFFIX ".abi3.so") - endif() -endif() -``` - -### pyproject.toml - -Static linking and stable ABI are enabled in the build configuration: - -```toml -[tool.scikit-build] -minimum-version = "0.4" -build-dir = "build/{wheel_tag}" -wheel.py-api = "cp38" # Target Python 3.8+ stable ABI - -[tool.scikit-build.cmake.define] -CMAKE_BUILD_TYPE = "Release" -CFD_STATIC_LINK = "ON" -CFD_USE_STABLE_ABI = "ON" -``` - -The `wheel.py-api = "cp38"` setting combined with `CFD_USE_STABLE_ABI` produces a single wheel per platform that works with Python 3.8+. - ---- - -## Dynamic Solver Discovery - -The Python wrapper automatically discovers all registered solvers from the C library at import time. When new solvers are added to the C library: - -1. Register the solver in the C library's solver registry -2. Rebuild the Python extension -3. The new solver is automatically available as `SOLVER_` constant - -```python -import cfd_python - -# List all available solvers -print(cfd_python.list_solvers()) -# ['explicit_euler', 'explicit_euler_optimized', 'projection', ...] - -# Check for a specific solver -if cfd_python.has_solver('explicit_euler_gpu'): - print("GPU solver available!") - -# Dynamic constants are created automatically -print(cfd_python.SOLVER_EXPLICIT_EULER) # 'explicit_euler' -``` - ---- - -## Building Wheels - -### Local Development Build - -```bash -# Build the C library first (static) -cd ../cfd -cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -cmake --build build --config Release - -# Build and install Python package -cd ../cfd-python -pip install -e . -``` - -### CI Build Process - -The GitHub Actions workflow builds wheels directly using `pip wheel` with the CFD library checked out as a sibling directory. This approach: - -- Produces one wheel per platform (stable ABI) -- Works with Python 3.8-3.12+ -- Statically links the CFD C library - -**Build steps (all platforms):** - -1. Checkout cfd-python repository -2. Checkout cfd C library to `./cfd` directory -3. Build CFD library with CMake (static, release) -4. Build wheel with `pip wheel . --no-deps` - -**Environment variables:** - -- `CFD_ROOT`: Path to CFD C library (set to `./cfd` in CI) -- `CFD_STATIC_LINK=ON`: Enable static linking -- `CFD_USE_STABLE_ABI=ON`: Build for Python stable ABI - ---- - -## CI/CD Pipeline - -### GitHub Actions Workflows - -The project uses two workflows: - -#### build-wheels.yml (Reusable) - -Builds and tests wheels on all platforms. Triggered on push, PR, or called by other workflows. - -```yaml -name: Build and Test Wheels - -on: - push: - branches: [main, master] - pull_request: - workflow_dispatch: - workflow_call: # Allows publish.yml to reuse this workflow - -jobs: - build_wheel: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - steps: - - uses: actions/checkout@v4 - - name: Checkout CFD C library - uses: actions/checkout@v4 - with: - repository: ${{ github.repository_owner }}/cfd - path: cfd - - name: Build CFD library (Unix) - if: runner.os != 'Windows' - run: | - cmake -S cfd -B cfd/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON - cmake --build cfd/build --config Release - - name: Build CFD library (Windows) - if: runner.os == 'Windows' - run: | - cmake -S cfd -B cfd/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF - cmake --build cfd/build --config Release - - name: Build wheel - env: - CFD_ROOT: ${{ github.workspace }}/cfd - CFD_STATIC_LINK: "ON" - CFD_USE_STABLE_ABI: "ON" - run: pip wheel . --no-deps --wheel-dir dist/ - - uses: actions/upload-artifact@v4 - with: - name: wheel-${{ matrix.os }} - path: dist/*.whl - - test_wheel: - needs: [build_wheel] - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - python: ["3.8", "3.12"] - steps: - # Install wheel, run import test, run pytest -``` - -#### publish.yml (PyPI Publishing) - -Publishes to TestPyPI or PyPI using trusted publishing (OIDC). - -```yaml -name: Publish to PyPI - -on: - release: - types: [published] - push: - tags: ['v*'] - workflow_dispatch: - inputs: - target: - type: choice - options: [testpypi, pypi] - -concurrency: - group: publish-${{ github.ref }} - cancel-in-progress: false - -jobs: - build: - uses: ./.github/workflows/build-wheels.yml # Reuse build workflow - - build_sdist: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - run: python -m build --sdist - - uses: actions/upload-artifact@v4 - - publish_testpypi: - needs: [build, build_sdist] - if: github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'testpypi' - environment: testpypi - permissions: - id-token: write # Required for trusted publishing - steps: - - name: Download wheels - uses: actions/download-artifact@v4 - with: - pattern: wheel-* - path: dist - merge-multiple: true - - name: Download sdist - uses: actions/download-artifact@v4 - with: - name: sdist - path: dist - - name: Validate artifacts - run: | - ls -la dist/ - WHEELS=$(ls dist/*.whl 2>/dev/null | wc -l) - SDIST=$(ls dist/*.tar.gz 2>/dev/null | wc -l) - echo "Found $WHEELS wheel(s) and $SDIST sdist(s)" - if [ "$WHEELS" -lt 3 ]; then - echo "ERROR: Expected at least 3 wheels (linux, macos, windows)" - exit 1 - fi - # Pin to SHA to prevent supply-chain attacks (id-token: write is sensitive) - - uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4 - with: - repository-url: https://test.pypi.org/legacy/ - - publish_pypi: - needs: [build, build_sdist] - if: github.event_name == 'release' || (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) || (github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'pypi') - environment: pypi - permissions: - id-token: write - steps: - - name: Download wheels - uses: actions/download-artifact@v4 - with: - pattern: wheel-* - path: dist - merge-multiple: true - - name: Download sdist - uses: actions/download-artifact@v4 - with: - name: sdist - path: dist - - name: Validate artifacts - run: | - ls -la dist/ - WHEELS=$(ls dist/*.whl 2>/dev/null | wc -l) - SDIST=$(ls dist/*.tar.gz 2>/dev/null | wc -l) - if [ "$WHEELS" -lt 3 ] || [ "$SDIST" -lt 1 ]; then - echo "ERROR: Missing artifacts"; exit 1 - fi - # Pin to SHA to prevent supply-chain attacks (id-token: write is sensitive) - - uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4 -``` - -### Trusted Publishing Setup - -The workflows use PyPI trusted publishing (OIDC) instead of API tokens: - -1. Go to PyPI/TestPyPI → Your Project → Settings → Publishing -2. Add a new trusted publisher with: - - Owner: `` - - Repository: `cfd-python` - - Workflow: `publish.yml` - - Environment: `pypi` or `testpypi` - ---- - -## Distribution Methods Comparison - -| Method | User Experience | Maintenance | Best For | -|--------|-----------------|-------------|----------| -| **PyPI Wheels** | `pip install cfd-python` | Automated CI/CD | Everyone | -| **Conda Package** | `conda install cfd-python` | Manual recipe | Scientists | -| **Docker Image** | `docker run cfd-python` | Image maintenance | Reproducible research | -| **Source Build** | Complex setup required | User support issues | Developers only | - ---- - -## Testing Installation - -```bash -# Install from wheel -pip install wheelhouse/cfd_python-*.whl - -# Verify installation -python -c "import cfd_python; print(cfd_python.__version__)" - -# List available solvers -python -c "import cfd_python; print(cfd_python.list_solvers())" - -# Run a quick test -python -c "import cfd_python; result = cfd_python.run_simulation(5, 5, 3); print(f'Computed {len(result)} points')" -``` - ---- - -## Release Process - -Version is automatically determined from git tags via setuptools-scm (configured in `pyproject.toml`). Do not manually update version numbers. - -1. Ensure all changes are committed and pushed -2. Create and push a release tag: - - ```bash - git tag v0.3.0 - git push origin v0.3.0 - ``` - -3. GitHub Actions automatically builds wheels and publishes to PyPI - -The version string is derived from the git tag (e.g., tag `v0.3.0` → version `0.3.0`). - ---- - -## Troubleshooting - -### Build Fails: "CFD library not found" - -Ensure the C library is built first: -```bash -cd ../cfd -cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -cmake --build build --config Release -``` - -### Import Error: Missing Symbols - -This usually means static linking wasn't enabled. Check that: - -- `CFD_STATIC_LINK=ON` is set in CMake -- The static library (`.a` or `_static.lib`) exists in the build directory - -### New Solver Not Appearing - -1. Verify the solver is registered in the C library's solver registry -2. Rebuild the C library -3. Rebuild the Python extension -4. Check with `cfd_python.list_solvers()`