From b3e5f16aed9ec57fcb760fa5c54dfa7fb35f762f Mon Sep 17 00:00:00 2001 From: shaia Date: Fri, 28 Nov 2025 16:07:10 +0200 Subject: [PATCH 01/47] Add GitHub Actions CI/CD workflow - Add build-wheels.yml for automated wheel building: - Triggered on push to main, tags, and workflow_dispatch - Supports matching tags from cfd library releases - Builds wheels for Linux, macOS, and Windows - Uses cibuildwheel for cross-platform compatibility - Static links CFD library for self-contained wheels - Uploads artifacts for distribution - Publishes to PyPI on tagged releases --- .github/workflows/build-wheels.yml | 218 +++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 .github/workflows/build-wheels.yml diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml new file mode 100644 index 0000000..8002fb5 --- /dev/null +++ b/.github/workflows/build-wheels.yml @@ -0,0 +1,218 @@ +name: Build and Test Wheels + +on: + push: + branches: [main, master] + tags: + - "v*" + pull_request: + branches: [main, master] + release: + types: [published] + # Allow triggering from cfd repo or manually + workflow_dispatch: + inputs: + cfd_ref: + description: "CFD library ref (tag/branch/commit). Leave empty to auto-detect." + required: false + default: "" + +jobs: + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # Determine which version of CFD library to use + # Priority: workflow_dispatch input > release tag > push tag > main branch + - name: Determine CFD library version + id: cfd-version + shell: bash + run: | + # Check for workflow_dispatch input first + if [[ -n "${{ github.event.inputs.cfd_ref }}" ]]; then + echo "ref=${{ github.event.inputs.cfd_ref }}" >> $GITHUB_OUTPUT + echo "Using CFD library ref from input: ${{ github.event.inputs.cfd_ref }}" + elif [[ "${{ github.event_name }}" == "release" ]]; then + # Use the release tag + echo "ref=${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT + echo "Using CFD library tag: ${{ github.event.release.tag_name }}" + elif [[ "${{ github.ref }}" == refs/tags/* ]]; then + # Push of a tag + TAG=${GITHUB_REF#refs/tags/} + echo "ref=$TAG" >> $GITHUB_OUTPUT + echo "Using CFD library tag: $TAG" + elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + # Manual trigger without input - use the current ref + echo "ref=${{ github.ref_name }}" >> $GITHUB_OUTPUT + echo "Using CFD library ref matching current branch/tag: ${{ github.ref_name }}" + else + # For PR/push to branch, use main + echo "ref=main" >> $GITHUB_OUTPUT + echo "Using CFD library branch: main" + fi + + # Checkout the CFD C library (sibling repository) + - name: Checkout CFD C library + uses: actions/checkout@v4 + with: + repository: ${{ github.repository_owner }}/cfd + path: ../cfd + ref: ${{ steps.cfd-version.outputs.ref }} + fetch-depth: 0 + + # Build CFD C library (static) + - name: Build CFD C library (Linux/macOS) + if: runner.os != 'Windows' + run: | + cd ../cfd + cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF + cmake --build build --config Release + + - name: Build CFD C library (Windows) + if: runner.os == 'Windows' + run: | + cd ..\cfd + cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF + cmake --build build --config Release + + - name: Build wheels + uses: pypa/cibuildwheel@v2.21.3 + env: + CIBW_BUILD: cp38-* cp39-* cp310-* cp311-* cp312-* + CIBW_SKIP: "*-musllinux_* pp*" + CIBW_ARCHS_WINDOWS: AMD64 + CIBW_ARCHS_MACOS: x86_64 arm64 + CIBW_ARCHS_LINUX: x86_64 + + # Environment for static linking + CIBW_ENVIRONMENT: "CMAKE_BUILD_TYPE=Release CFD_STATIC_LINK=ON" + + # Test the built wheels + CIBW_TEST_COMMAND: > + python -c "import cfd_python; print('CFD Python version:', cfd_python.__version__); print('Solvers:', cfd_python.list_solvers())" + + # macOS wheel repair (static linking handles most deps) + CIBW_REPAIR_WHEEL_COMMAND_MACOS: delocate-wheel --require-archs {delocate_archs} -w {dest_dir} {wheel} + + # Linux wheel repair + CIBW_REPAIR_WHEEL_COMMAND_LINUX: auditwheel repair -w {dest_dir} {wheel} + + # Windows - no repair needed with static linking + CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: "" + + - uses: actions/upload-artifact@v4 + with: + name: wheels-${{ matrix.os }} + path: ./wheelhouse/*.whl + + build_sdist: + name: Build source distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Build sdist + run: pipx run build --sdist + + - uses: actions/upload-artifact@v4 + with: + name: sdist + path: dist/*.tar.gz + + test_package: + name: Test package installation + needs: [build_wheels] + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - uses: actions/download-artifact@v4 + with: + name: wheels-${{ matrix.os }} + path: wheelhouse + + - name: Install wheel + run: | + python -m pip install --upgrade pip + python -m pip install --find-links wheelhouse cfd-python + + - name: Test import and version + run: | + python -c "import cfd_python; print('Version:', cfd_python.__version__)" + + - name: Test solver discovery + run: | + python -c "import cfd_python; solvers = cfd_python.list_solvers(); print('Solvers:', solvers); assert len(solvers) > 0" + + - name: Test simulation + run: | + python -c "import cfd_python; result = cfd_python.run_simulation(5, 5, steps=3); assert len(result) == 25; print('Simulation OK')" + + - name: Test grid creation + run: | + python -c "import cfd_python; grid = cfd_python.create_grid(5, 5, 0, 1, 0, 1); assert grid['nx'] == 5; print('Grid OK')" + + - name: Test solver params + run: | + python -c "import cfd_python; params = cfd_python.get_default_solver_params(); assert 'dt' in params; print('Params OK')" + + upload_pypi: + name: Upload to PyPI + needs: [build_wheels, build_sdist, test_package] + runs-on: ubuntu-latest + if: github.event_name == 'release' + environment: + name: pypi + url: https://pypi.org/p/cfd-python + permissions: + id-token: write + + steps: + - uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: dist/ + + upload_test_pypi: + name: Upload to Test PyPI + needs: [build_wheels, build_sdist, test_package] + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + environment: + name: testpypi + url: https://test.pypi.org/p/cfd-python + + steps: + - uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + + - name: Publish to Test PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + packages-dir: dist/ From 2c7a3529c388ebdb24203d9207fe22dd08fb170f Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 10:02:43 +0200 Subject: [PATCH 02/47] Update CI workflow for cross-repo release triggering - Add CFD_USE_STABLE_ABI=ON to CIBW_ENVIRONMENT for stable ABI wheels - Expand PyPI publish condition to support: - GitHub release events - Direct tag pushes (v*) - workflow_dispatch on tags (triggered from cfd repo) This enables the cfd repo's version-release workflow to trigger Python package builds and PyPI publishing. --- .github/workflows/build-wheels.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 8002fb5..76f0aea 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -92,8 +92,8 @@ jobs: CIBW_ARCHS_MACOS: x86_64 arm64 CIBW_ARCHS_LINUX: x86_64 - # Environment for static linking - CIBW_ENVIRONMENT: "CMAKE_BUILD_TYPE=Release CFD_STATIC_LINK=ON" + # Environment for static linking and stable ABI + CIBW_ENVIRONMENT: "CMAKE_BUILD_TYPE=Release CFD_STATIC_LINK=ON CFD_USE_STABLE_ABI=ON" # Test the built wheels CIBW_TEST_COMMAND: > @@ -178,7 +178,11 @@ jobs: name: Upload to PyPI needs: [build_wheels, build_sdist, test_package] runs-on: ubuntu-latest - if: github.event_name == 'release' + # Publish on: release event, tag push (v*), or workflow_dispatch on a tag + if: | + github.event_name == 'release' || + (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) || + (github.event_name == 'workflow_dispatch' && startsWith(github.ref, 'refs/tags/v')) environment: name: pypi url: https://pypi.org/p/cfd-python From f7eabff55cf5409ef18c5745fd920ffa6932f1f1 Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 10:07:16 +0200 Subject: [PATCH 03/47] Fix CI checkout path - use workspace subdirectory actions/checkout@v4 doesn't allow paths outside the workspace. Changed from `../cfd` to `cfd` subdirectory within workspace. --- .github/workflows/build-wheels.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 76f0aea..d30cb99 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -59,12 +59,12 @@ jobs: echo "Using CFD library branch: main" fi - # Checkout the CFD C library (sibling repository) + # Checkout the CFD C library into workspace subdirectory - name: Checkout CFD C library uses: actions/checkout@v4 with: repository: ${{ github.repository_owner }}/cfd - path: ../cfd + path: cfd ref: ${{ steps.cfd-version.outputs.ref }} fetch-depth: 0 @@ -72,14 +72,14 @@ jobs: - name: Build CFD C library (Linux/macOS) if: runner.os != 'Windows' run: | - cd ../cfd + cd cfd cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF cmake --build build --config Release - name: Build CFD C library (Windows) if: runner.os == 'Windows' run: | - cd ..\cfd + cd cfd cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF cmake --build build --config Release @@ -93,7 +93,8 @@ jobs: CIBW_ARCHS_LINUX: x86_64 # Environment for static linking and stable ABI - CIBW_ENVIRONMENT: "CMAKE_BUILD_TYPE=Release CFD_STATIC_LINK=ON CFD_USE_STABLE_ABI=ON" + # CFD_ROOT points to the cfd subdirectory in the workspace + CIBW_ENVIRONMENT: "CMAKE_BUILD_TYPE=Release CFD_STATIC_LINK=ON CFD_USE_STABLE_ABI=ON CFD_ROOT=./cfd" # Test the built wheels CIBW_TEST_COMMAND: > From e0a2aa16b0d18d494e551b6640ed41162234b6b9 Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 10:13:21 +0200 Subject: [PATCH 04/47] Simplify build-wheels workflow and improve testing - Remove duplicate cibuildwheel config, rely on pyproject.toml - Use CIBW_ENVIRONMENT_PASS_* to override CFD_ROOT for CI - Remove redundant CFD library build steps (handled by before-build) - Use pattern: wheels-* for explicit artifact download - Download wheels and sdist separately for clarity - Add id-token: write permission for Test PyPI - Replace smoke tests with full pytest suite --- .github/workflows/build-wheels.yml | 99 +++++++++++------------------- 1 file changed, 35 insertions(+), 64 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index d30cb99..1c62d93 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -9,7 +9,8 @@ on: branches: [main, master] release: types: [published] - # Allow triggering from cfd repo or manually + # Allow manual triggering or remote triggering via GitHub CLI/API + # (used by cfd repo's version-release.yml via: gh workflow run build-wheels.yml) workflow_dispatch: inputs: cfd_ref: @@ -31,7 +32,7 @@ jobs: fetch-depth: 0 # Determine which version of CFD library to use - # Priority: workflow_dispatch input > release tag > push tag > main branch + # Priority: workflow_dispatch input > release tag > push tag > master branch - name: Determine CFD library version id: cfd-version shell: bash @@ -44,7 +45,7 @@ jobs: # Use the release tag echo "ref=${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT echo "Using CFD library tag: ${{ github.event.release.tag_name }}" - elif [[ "${{ github.ref }}" == refs/tags/* ]]; then + elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then # Push of a tag TAG=${GITHUB_REF#refs/tags/} echo "ref=$TAG" >> $GITHUB_OUTPUT @@ -54,9 +55,9 @@ jobs: echo "ref=${{ github.ref_name }}" >> $GITHUB_OUTPUT echo "Using CFD library ref matching current branch/tag: ${{ github.ref_name }}" else - # For PR/push to branch, use main - echo "ref=main" >> $GITHUB_OUTPUT - echo "Using CFD library branch: main" + # For PR/push to branch, use master (cfd repo's default branch) + echo "ref=master" >> $GITHUB_OUTPUT + echo "Using CFD library branch: master" fi # Checkout the CFD C library into workspace subdirectory @@ -68,46 +69,18 @@ jobs: ref: ${{ steps.cfd-version.outputs.ref }} fetch-depth: 0 - # Build CFD C library (static) - - name: Build CFD C library (Linux/macOS) - if: runner.os != 'Windows' - run: | - cd cfd - cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF - cmake --build build --config Release - - - name: Build CFD C library (Windows) - if: runner.os == 'Windows' - run: | - cd cfd - cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF - cmake --build build --config Release - + # Build wheels using cibuildwheel + # Configuration is in pyproject.toml - only override CFD_ROOT for CI + # (pyproject.toml uses ../cfd for local dev, CI uses ./cfd) - name: Build wheels uses: pypa/cibuildwheel@v2.21.3 env: - CIBW_BUILD: cp38-* cp39-* cp310-* cp311-* cp312-* - CIBW_SKIP: "*-musllinux_* pp*" - CIBW_ARCHS_WINDOWS: AMD64 - CIBW_ARCHS_MACOS: x86_64 arm64 - CIBW_ARCHS_LINUX: x86_64 - - # Environment for static linking and stable ABI - # CFD_ROOT points to the cfd subdirectory in the workspace - CIBW_ENVIRONMENT: "CMAKE_BUILD_TYPE=Release CFD_STATIC_LINK=ON CFD_USE_STABLE_ABI=ON CFD_ROOT=./cfd" - - # Test the built wheels - CIBW_TEST_COMMAND: > - python -c "import cfd_python; print('CFD Python version:', cfd_python.__version__); print('Solvers:', cfd_python.list_solvers())" - - # macOS wheel repair (static linking handles most deps) - CIBW_REPAIR_WHEEL_COMMAND_MACOS: delocate-wheel --require-archs {delocate_archs} -w {dest_dir} {wheel} - - # Linux wheel repair - CIBW_REPAIR_WHEEL_COMMAND_LINUX: auditwheel repair -w {dest_dir} {wheel} - - # Windows - no repair needed with static linking - CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: "" + # Override CFD_ROOT to point to the cfd subdirectory in CI workspace + # All other settings come from pyproject.toml [tool.cibuildwheel] + CIBW_ENVIRONMENT_PASS_LINUX: CFD_ROOT + CIBW_ENVIRONMENT_PASS_MACOS: CFD_ROOT + CIBW_ENVIRONMENT_PASS_WINDOWS: CFD_ROOT + CFD_ROOT: ./cfd - uses: actions/upload-artifact@v4 with: @@ -150,30 +123,14 @@ jobs: name: wheels-${{ matrix.os }} path: wheelhouse - - name: Install wheel + - name: Install wheel and test dependencies run: | python -m pip install --upgrade pip python -m pip install --find-links wheelhouse cfd-python + python -m pip install pytest numpy - - name: Test import and version - run: | - python -c "import cfd_python; print('Version:', cfd_python.__version__)" - - - name: Test solver discovery - run: | - python -c "import cfd_python; solvers = cfd_python.list_solvers(); print('Solvers:', solvers); assert len(solvers) > 0" - - - name: Test simulation - run: | - python -c "import cfd_python; result = cfd_python.run_simulation(5, 5, steps=3); assert len(result) == 25; print('Simulation OK')" - - - name: Test grid creation - run: | - python -c "import cfd_python; grid = cfd_python.create_grid(5, 5, 0, 1, 0, 1); assert grid['nx'] == 5; print('Grid OK')" - - - name: Test solver params - run: | - python -c "import cfd_python; params = cfd_python.get_default_solver_params(); assert 'dt' in params; print('Params OK')" + - name: Run full test suite + run: pytest tests/ -v upload_pypi: name: Upload to PyPI @@ -193,9 +150,15 @@ jobs: steps: - uses: actions/download-artifact@v4 with: + pattern: wheels-* path: dist merge-multiple: true + - uses: actions/download-artifact@v4 + with: + name: sdist + path: dist + - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: @@ -205,17 +168,25 @@ jobs: name: Upload to Test PyPI needs: [build_wheels, build_sdist, test_package] runs-on: ubuntu-latest - if: github.event_name == 'push' && github.ref == 'refs/heads/main' + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') environment: name: testpypi url: https://test.pypi.org/p/cfd-python + permissions: + id-token: write steps: - uses: actions/download-artifact@v4 with: + pattern: wheels-* path: dist merge-multiple: true + - uses: actions/download-artifact@v4 + with: + name: sdist + path: dist + - name: Publish to Test PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: From 7daa3be754ef4829ecee9ba2f3303f434d0137e3 Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 10:25:18 +0200 Subject: [PATCH 05/47] Remove unnecessary before-all in Linux cibuildwheel config The manylinux images already have cmake and gcc installed, so the before-all step attempting to install them was failing with exit code 127 (command not found) due to incorrect shell logic. --- pyproject.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f2ad85a..e6f1255 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -125,9 +125,7 @@ environment = { CMAKE_BUILD_TYPE = "Release", CFD_STATIC_LINK = "ON", CFD_USE_ST [tool.cibuildwheel.linux] # Linux-specific configuration -before-all = [ - "yum install -y cmake3 gcc-c++ || apt-get update && apt-get install -y cmake g++", -] +# manylinux images already have cmake and gcc installed # CFD_ROOT environment variable specifies the CFD library location (default: ../cfd) before-build = [ "cd ${CFD_ROOT:-../cfd} && cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF", From 11a413aca8f845a6dd342936faf10ea65c480563 Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 10:28:37 +0200 Subject: [PATCH 06/47] Fix CFD_ROOT override in CI workflow Use CIBW_ENVIRONMENT instead of CIBW_ENVIRONMENT_PASS_* to properly override the CFD_ROOT setting from pyproject.toml. The PASS variant only passes through variables but doesn't override existing settings. --- .github/workflows/build-wheels.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 1c62d93..7bee272 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -76,11 +76,8 @@ jobs: uses: pypa/cibuildwheel@v2.21.3 env: # Override CFD_ROOT to point to the cfd subdirectory in CI workspace - # All other settings come from pyproject.toml [tool.cibuildwheel] - CIBW_ENVIRONMENT_PASS_LINUX: CFD_ROOT - CIBW_ENVIRONMENT_PASS_MACOS: CFD_ROOT - CIBW_ENVIRONMENT_PASS_WINDOWS: CFD_ROOT - CFD_ROOT: ./cfd + # CIBW_ENVIRONMENT overrides pyproject.toml environment settings + CIBW_ENVIRONMENT: "CMAKE_BUILD_TYPE=Release CFD_STATIC_LINK=ON CFD_USE_STABLE_ABI=ON CFD_ROOT=./cfd" - uses: actions/upload-artifact@v4 with: From 0456ac401183b5bf621481329b022c80ac39fb9c Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 10:33:25 +0200 Subject: [PATCH 07/47] Add debug output and simplify before-build commands Remove shell default syntax ${VAR:-default} in favor of direct $VAR usage since the environment is set by cibuildwheel. Add echo to show CFD_ROOT value for debugging CI failures. --- pyproject.toml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e6f1255..fdbc7e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -114,10 +114,10 @@ environment = { CMAKE_BUILD_TYPE = "Release", CFD_STATIC_LINK = "ON", CFD_USE_ST [tool.cibuildwheel.macos] # macOS-specific configuration -# CFD_ROOT environment variable specifies the CFD library location (default: ../cfd) +# CFD_ROOT environment variable specifies the CFD library location 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", + "echo Building CFD library at CFD_ROOT=$CFD_ROOT && cd $CFD_ROOT && cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF", + "cd $CFD_ROOT && cmake --build build --config Release", ] # delocate handles any remaining dynamic dependencies (system libs) repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} {wheel}" @@ -126,10 +126,10 @@ environment = { CMAKE_BUILD_TYPE = "Release", CFD_STATIC_LINK = "ON", CFD_USE_ST [tool.cibuildwheel.linux] # Linux-specific configuration # manylinux images already have cmake and gcc installed -# CFD_ROOT environment variable specifies the CFD library location (default: ../cfd) +# CFD_ROOT environment variable specifies the CFD library location 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", + "echo Building CFD library at CFD_ROOT=$CFD_ROOT && cd $CFD_ROOT && cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF", + "cd $CFD_ROOT && cmake --build build --config Release", ] # auditwheel handles any remaining dynamic dependencies (system libs) repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel}" From bb022275f35a06023e76fc4ec150477d618957f6 Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 10:36:36 +0200 Subject: [PATCH 08/47] Fix before-build commands to use cmake -S/-B flags Use cmake's -S (source) and -B (build) flags instead of cd to avoid path issues. The previous approach failed because the second cd command was relative to the already-changed directory. --- pyproject.toml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fdbc7e6..34c4c2c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,12 +102,11 @@ environment = { CMAKE_BUILD_TYPE = "Release", CFD_STATIC_LINK = "ON", CFD_USE_ST [tool.cibuildwheel.windows] # Windows-specific configuration # Build CFD library first (static), then build Python extension -# CFD_ROOT is set in environment below # Only build 64-bit wheels (AMD64) - skip x86 (32-bit) archs = ["AMD64"] before-build = [ - "cd %CFD_ROOT% && cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF", - "cd %CFD_ROOT% && cmake --build build --config Release", + "cmake -S %CFD_ROOT% -B %CFD_ROOT%/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF", + "cmake --build %CFD_ROOT%/build --config Release", ] # No repair needed for statically linked wheels environment = { CMAKE_BUILD_TYPE = "Release", CFD_STATIC_LINK = "ON", CFD_USE_STABLE_ABI = "ON", CFD_ROOT = "../cfd" } @@ -116,8 +115,8 @@ environment = { CMAKE_BUILD_TYPE = "Release", CFD_STATIC_LINK = "ON", CFD_USE_ST # macOS-specific configuration # CFD_ROOT environment variable specifies the CFD library location before-build = [ - "echo Building CFD library at CFD_ROOT=$CFD_ROOT && cd $CFD_ROOT && cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF", - "cd $CFD_ROOT && cmake --build build --config Release", + "cmake -S $CFD_ROOT -B $CFD_ROOT/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF", + "cmake --build $CFD_ROOT/build --config Release", ] # delocate handles any remaining dynamic dependencies (system libs) repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} {wheel}" @@ -128,8 +127,8 @@ environment = { CMAKE_BUILD_TYPE = "Release", CFD_STATIC_LINK = "ON", CFD_USE_ST # manylinux images already have cmake and gcc installed # CFD_ROOT environment variable specifies the CFD library location before-build = [ - "echo Building CFD library at CFD_ROOT=$CFD_ROOT && cd $CFD_ROOT && cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF", - "cd $CFD_ROOT && cmake --build build --config Release", + "cmake -S $CFD_ROOT -B $CFD_ROOT/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF", + "cmake --build $CFD_ROOT/build --config Release", ] # auditwheel handles any remaining dynamic dependencies (system libs) repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel}" From 648f57ae7d30dc2be886b6996d3f576ec6bb716b Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 11:55:42 +0200 Subject: [PATCH 09/47] Fix CMake library path for single-config generators Multi-config generators (Visual Studio, Xcode) output libraries to build/lib//, while single-config generators (Make, Ninja) output directly to build/lib/. This fix searches both paths to support all build systems in CI. --- CMakeLists.txt | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f2e89d..d0f6dd9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,11 +30,19 @@ else() endif() set(CFD_INCLUDE_DIR "${CFD_ROOT_DIR}/lib/include") -# Determine library directory based on build type +# Determine library search paths +# Multi-config generators (Visual Studio, Xcode) put libs in build/lib// +# Single-config generators (Make, Ninja) put libs directly in build/lib/ if(CMAKE_BUILD_TYPE STREQUAL "Debug") - set(CFD_LIBRARY_DIR "${CFD_ROOT_DIR}/build/lib/Debug") + set(CFD_LIBRARY_DIRS + "${CFD_ROOT_DIR}/build/lib/Debug" # Multi-config (Windows/VS) + "${CFD_ROOT_DIR}/build/lib" # Single-config (Linux/macOS Make) + ) else() - set(CFD_LIBRARY_DIR "${CFD_ROOT_DIR}/build/lib/Release") + set(CFD_LIBRARY_DIRS + "${CFD_ROOT_DIR}/build/lib/Release" # Multi-config (Windows/VS) + "${CFD_ROOT_DIR}/build/lib" # Single-config (Linux/macOS Make) + ) endif() # Check if CFD library exists (prefer static library for packaging) @@ -43,13 +51,13 @@ if(CFD_STATIC_LINK) if(WIN32) find_library(CFD_LIBRARY NAMES cfd_library_static cfd_library - PATHS ${CFD_LIBRARY_DIR} + PATHS ${CFD_LIBRARY_DIRS} NO_DEFAULT_PATH ) else() find_library(CFD_LIBRARY - NAMES libcfd_library.a cfd_library - PATHS ${CFD_LIBRARY_DIR} + NAMES cfd_library + PATHS ${CFD_LIBRARY_DIRS} NO_DEFAULT_PATH ) endif() @@ -57,13 +65,13 @@ else() # Look for shared library find_library(CFD_LIBRARY NAMES cfd_library - PATHS ${CFD_LIBRARY_DIR} + PATHS ${CFD_LIBRARY_DIRS} NO_DEFAULT_PATH ) endif() if(NOT CFD_LIBRARY) - message(FATAL_ERROR "CFD library not found in ${CFD_LIBRARY_DIR}. Please build the C library first.") + message(FATAL_ERROR "CFD library not found in ${CFD_LIBRARY_DIRS}. Please build the C library first.") endif() message(STATUS "Found CFD library: ${CFD_LIBRARY}") From 9b95179b228fdea2aaef3ca6533c7132994405f0 Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 15:00:16 +0200 Subject: [PATCH 10/47] Improve error handling for C extension import failures When the C extension exists but fails to import, now raises ImportError with a helpful message instead of silently falling back to development mode. This surfaces the actual error (e.g., missing dependencies, ABI incompatibility) instead of causing confusing AttributeErrors later. --- cfd_python/__init__.py | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/cfd_python/__init__.py b/cfd_python/__init__.py index 1ff4b9f..04ca5ee 100644 --- a/cfd_python/__init__.py +++ b/cfd_python/__init__.py @@ -97,8 +97,27 @@ # Build complete __all__ list __all__ = _CORE_EXPORTS + _solver_constants -except ImportError: - # Development mode - module not yet built - __all__ = _CORE_EXPORTS - if __version__ is None: - __version__ = "0.0.0-dev" \ No newline at end of file +except ImportError as e: + # Check if this is a development environment (source checkout without built extension) + # vs a broken installation (extension exists but fails to load) + import os as _os + _package_dir = _os.path.dirname(__file__) + + # Look for compiled extension files + _extension_exists = any( + f.startswith('cfd_python') and (f.endswith('.pyd') or f.endswith('.so')) + for f in _os.listdir(_package_dir) + ) + + if _extension_exists: + # Extension file exists but failed to load - this is an error + raise ImportError( + f"Failed to load cfd_python C extension: {e}\n" + "The extension file exists but could not be imported. " + "This may indicate a missing dependency or ABI incompatibility." + ) from e + else: + # Development mode - module not yet built + __all__ = _CORE_EXPORTS + if __version__ is None: + __version__ = "0.0.0-dev" \ No newline at end of file From ab09e9170abdeabb35d4c35c57263b64a6aeed0c Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 15:02:05 +0200 Subject: [PATCH 11/47] Add wheelhouse/ and src/cfd_lib/ to .gitignore These are build artifacts that shouldn't be tracked: - wheelhouse/ - built wheel files from cibuildwheel - src/cfd_lib/ - local copy of CFD library created during builds --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 3b0e77b..5a02d47 100644 --- a/.gitignore +++ b/.gitignore @@ -144,6 +144,10 @@ _deps/ # Build directories _skbuild/ *.egg-info/ +wheelhouse/ + +# Local copy of CFD library (created during builds) +src/cfd_lib/ # VTK output files *.vtk From 29c4903ac5a8c67378af73bc20b674c7683dd5e4 Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 15:37:45 +0200 Subject: [PATCH 12/47] Use latest cfd release tag for wheel builds Instead of building against cfd master (which may have untested changes), the workflow now fetches the latest cfd release tag via GitHub API. This ensures: - Stability: builds against tested, released code - Reproducibility: exact cfd version is a known release - No surprises: master breaking changes won't affect builds Falls back to master only if no releases exist yet. --- .github/workflows/build-wheels.yml | 32 ++++++++++++++---------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 7bee272..87f8397 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -32,32 +32,30 @@ jobs: fetch-depth: 0 # Determine which version of CFD library to use - # Priority: workflow_dispatch input > release tag > push tag > master branch + # Priority: workflow_dispatch input > latest cfd release tag + # This ensures we always build against stable, tested cfd releases - name: Determine CFD library version id: cfd-version shell: bash + env: + GH_TOKEN: ${{ github.token }} run: | # Check for workflow_dispatch input first if [[ -n "${{ github.event.inputs.cfd_ref }}" ]]; then echo "ref=${{ github.event.inputs.cfd_ref }}" >> $GITHUB_OUTPUT echo "Using CFD library ref from input: ${{ github.event.inputs.cfd_ref }}" - elif [[ "${{ github.event_name }}" == "release" ]]; then - # Use the release tag - echo "ref=${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT - echo "Using CFD library tag: ${{ github.event.release.tag_name }}" - elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then - # Push of a tag - TAG=${GITHUB_REF#refs/tags/} - echo "ref=$TAG" >> $GITHUB_OUTPUT - echo "Using CFD library tag: $TAG" - elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then - # Manual trigger without input - use the current ref - echo "ref=${{ github.ref_name }}" >> $GITHUB_OUTPUT - echo "Using CFD library ref matching current branch/tag: ${{ github.ref_name }}" else - # For PR/push to branch, use master (cfd repo's default branch) - echo "ref=master" >> $GITHUB_OUTPUT - echo "Using CFD library branch: master" + # Fetch the latest release tag from cfd repo + LATEST_TAG=$(gh api repos/${{ github.repository_owner }}/cfd/releases/latest --jq '.tag_name' 2>/dev/null || echo "") + + if [[ -n "$LATEST_TAG" ]]; then + echo "ref=$LATEST_TAG" >> $GITHUB_OUTPUT + echo "Using latest CFD release: $LATEST_TAG" + else + # Fallback to master if no releases exist + echo "ref=master" >> $GITHUB_OUTPUT + echo "No CFD releases found, falling back to master branch" + fi fi # Checkout the CFD C library into workspace subdirectory From 5e304d06a29f87ac920b1dd882a1fe6373f3cf88 Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 15:51:14 +0200 Subject: [PATCH 13/47] Pin pypa/gh-action-pypi-publish to v1.12.4 Using branch references like @release/v1 is a security risk as maintainers could push breaking changes or malicious code. Pin to specific version tag for security. --- .github/workflows/build-wheels.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 87f8397..774c270 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -155,7 +155,7 @@ jobs: path: dist - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@v1.12.4 # Pin to specific version for security with: packages-dir: dist/ @@ -183,7 +183,7 @@ jobs: path: dist - name: Publish to Test PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@v1.12.4 # Pin to specific version for security with: repository-url: https://test.pypi.org/legacy/ packages-dir: dist/ From 6eae1372f2d56c900fe228310e7c6a09ba9f953a Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 16:01:35 +0200 Subject: [PATCH 14/47] Improve test robustness and CI error handling - Add tests for import error handling logic - Update conftest.py to fail in CI if extension not available (instead of silently skipping tests) - Update test_integration.py to handle dev mode gracefully - Clarify CIBW_ENVIRONMENT behavior in workflow comments --- .github/workflows/build-wheels.yml | 6 +- tests/conftest.py | 19 ++- tests/test_import_handling.py | 183 +++++++++++++++++++++++++++++ tests/test_integration.py | 12 +- 4 files changed, 216 insertions(+), 4 deletions(-) create mode 100644 tests/test_import_handling.py diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 774c270..785bea3 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -73,8 +73,10 @@ jobs: - name: Build wheels uses: pypa/cibuildwheel@v2.21.3 env: - # Override CFD_ROOT to point to the cfd subdirectory in CI workspace - # CIBW_ENVIRONMENT overrides pyproject.toml environment settings + # CIBW_ENVIRONMENT completely replaces (not merges with) the environment + # settings in pyproject.toml, including platform-specific ones. + # We must specify all required variables here for CI builds. + # CFD_ROOT=./cfd points to the cfd checkout in CI workspace. CIBW_ENVIRONMENT: "CMAKE_BUILD_TYPE=Release CFD_STATIC_LINK=ON CFD_USE_STABLE_ABI=ON CFD_ROOT=./cfd" - uses: actions/upload-artifact@v4 diff --git a/tests/conftest.py b/tests/conftest.py index cd5afa5..f6d88e9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,9 +18,23 @@ def _check_module_import(module_name, error_message): pytest.skip(f"{error_message} (ImportError: {e})") return False -# Verify cfd_python C extension is available +# Verify cfd_python C extension is available and functional try: import cfd_python + # Check if extension actually loaded (not just dev mode stub) + if not hasattr(cfd_python, 'list_solvers'): + # In CI (indicated by CI env var), fail instead of skip + if os.environ.get('CI'): + raise RuntimeError( + "CFD Python C extension not built. " + "The wheel may be missing the compiled extension." + ) + else: + pytest.skip( + "CFD Python C extension not built (development mode). " + "Run 'pip install -e .' to build the extension.", + allow_module_level=True + ) except ImportError as e: error_str = str(e) if "DLL load failed" in error_str or "cannot open shared object" in error_str: @@ -29,4 +43,7 @@ def _check_module_import(module_name, error_message): reason = "CFD Python C extension not built. Run 'pip install -e .' first." else: reason = f"CFD Python import failed: {e}" + # In CI, fail instead of skip for ImportError + if os.environ.get('CI'): + raise RuntimeError(reason) from e pytest.skip(reason, allow_module_level=True) diff --git a/tests/test_import_handling.py b/tests/test_import_handling.py new file mode 100644 index 0000000..089f090 --- /dev/null +++ b/tests/test_import_handling.py @@ -0,0 +1,183 @@ +""" +Tests for import error handling in cfd_python/__init__.py + +These tests verify the behavior when: +1. Extension exists but fails to load (broken installation) +2. No extension exists (development mode) +""" +import os +import sys +import tempfile +import shutil +import pytest + + +class TestImportErrorHandling: + """Test import error handling logic""" + + def test_broken_extension_raises_import_error(self, tmp_path): + """Test that a broken extension file raises ImportError with helpful message""" + # Create a fake package directory with a broken extension + # Use a unique name to avoid conflicts with real cfd_python + fake_package = tmp_path / "fake_cfd_broken" + fake_package.mkdir() + + # Create __init__.py with the same logic as the real one + # but importing from fake_cfd_broken submodule + init_content = ''' +import os as _os + +_CORE_EXPORTS = ["run_simulation", "list_solvers"] +__version__ = None + +try: + from .fake_cfd_broken import run_simulation, list_solvers +except ImportError as e: + _package_dir = _os.path.dirname(__file__) + _extension_exists = any( + f.startswith('fake_cfd_broken') and (f.endswith('.pyd') or f.endswith('.so')) + for f in _os.listdir(_package_dir) + ) + if _extension_exists: + raise ImportError( + f"Failed to load C extension: {e}\\n" + "The extension file exists but could not be imported. " + "This may indicate a missing dependency or ABI incompatibility." + ) from e + else: + __all__ = _CORE_EXPORTS + if __version__ is None: + __version__ = "0.0.0-dev" +''' + (fake_package / "__init__.py").write_text(init_content) + + # Create a fake broken extension file + if sys.platform == "win32": + (fake_package / "fake_cfd_broken.pyd").write_text("not a real extension") + else: + (fake_package / "fake_cfd_broken.so").write_text("not a real extension") + + # Add to path and try to import + sys.path.insert(0, str(tmp_path)) + try: + with pytest.raises(ImportError) as exc_info: + import fake_cfd_broken + + # Verify the error message is helpful + assert "Failed to load C extension" in str(exc_info.value) + assert "missing dependency or ABI incompatibility" in str(exc_info.value) + finally: + sys.path.remove(str(tmp_path)) + # Clean up module cache + if "fake_cfd_broken" in sys.modules: + del sys.modules["fake_cfd_broken"] + + def test_dev_mode_no_extension(self, tmp_path): + """Test that missing extension falls back to dev mode gracefully""" + # Create a fake package directory without extension + fake_package = tmp_path / "fake_cfd_dev" + fake_package.mkdir() + + # Create __init__.py with the same logic + init_content = ''' +import os as _os + +_CORE_EXPORTS = ["run_simulation", "list_solvers"] +__version__ = None +__all__ = [] + +try: + from .cfd_python import run_simulation, list_solvers + __all__ = _CORE_EXPORTS +except ImportError as e: + _package_dir = _os.path.dirname(__file__) + _extension_exists = any( + f.startswith('cfd_python') and (f.endswith('.pyd') or f.endswith('.so')) + for f in _os.listdir(_package_dir) + ) + if _extension_exists: + raise ImportError( + f"Failed to load cfd_python C extension: {e}\\n" + "The extension file exists but could not be imported. " + "This may indicate a missing dependency or ABI incompatibility." + ) from e + else: + __all__ = _CORE_EXPORTS + if __version__ is None: + __version__ = "0.0.0-dev" +''' + (fake_package / "__init__.py").write_text(init_content) + + # Add to path and import + sys.path.insert(0, str(tmp_path)) + try: + import fake_cfd_dev + + # Should have dev version + assert fake_cfd_dev.__version__ == "0.0.0-dev" + # Should have __all__ set + assert fake_cfd_dev.__all__ == ["run_simulation", "list_solvers"] + finally: + sys.path.remove(str(tmp_path)) + if "fake_cfd_dev" in sys.modules: + del sys.modules["fake_cfd_dev"] + + def test_real_module_loads_successfully(self): + """Test that the real cfd_python module loads without error""" + # This verifies the actual installed module works + import cfd_python + + # Should have version + assert hasattr(cfd_python, '__version__') + assert cfd_python.__version__ is not None + + # Should have core functions + assert hasattr(cfd_python, 'list_solvers') + assert callable(cfd_python.list_solvers) + + # list_solvers should return a non-empty list + solvers = cfd_python.list_solvers() + assert isinstance(solvers, list) + assert len(solvers) > 0 + + def test_extension_detection_logic(self, tmp_path): + """Test that extension detection correctly identifies .pyd and .so files""" + test_dir = tmp_path / "test_detection" + test_dir.mkdir() + + # Test with no extension files + files = list(test_dir.iterdir()) + has_extension = any( + f.name.startswith('cfd_python') and (f.name.endswith('.pyd') or f.name.endswith('.so')) + for f in files + ) + assert not has_extension + + # Test with .pyd file + (test_dir / "cfd_python.cp311-win_amd64.pyd").touch() + files = list(test_dir.iterdir()) + has_extension = any( + f.name.startswith('cfd_python') and (f.name.endswith('.pyd') or f.name.endswith('.so')) + for f in files + ) + assert has_extension + + # Clean and test with .so file + (test_dir / "cfd_python.cp311-win_amd64.pyd").unlink() + (test_dir / "cfd_python.cpython-311-x86_64-linux-gnu.so").touch() + files = list(test_dir.iterdir()) + has_extension = any( + f.name.startswith('cfd_python') and (f.name.endswith('.pyd') or f.name.endswith('.so')) + for f in files + ) + assert has_extension + + # Test with unrelated .so file (should not match) + (test_dir / "cfd_python.cpython-311-x86_64-linux-gnu.so").unlink() + (test_dir / "other_module.so").touch() + files = list(test_dir.iterdir()) + has_extension = any( + f.name.startswith('cfd_python') and (f.name.endswith('.pyd') or f.name.endswith('.so')) + for f in files + ) + assert not has_extension diff --git a/tests/test_integration.py b/tests/test_integration.py index 9efcafe..012d697 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -4,8 +4,18 @@ import pytest import cfd_python +# Check if extension is available (not in dev mode) +_EXTENSION_AVAILABLE = hasattr(cfd_python, 'list_solvers') + # Get solver list at module load time for parametrization -_AVAILABLE_SOLVERS = cfd_python.list_solvers() +# Returns empty list if extension not built (dev mode) +_AVAILABLE_SOLVERS = cfd_python.list_solvers() if _EXTENSION_AVAILABLE else [] + +# Skip all tests in this module if extension not available +pytestmark = pytest.mark.skipif( + not _EXTENSION_AVAILABLE, + reason="C extension not built (development mode)" +) class TestIntegration: From e54b613ab595895c84a311f5c9cde8f35bd87bbc Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 16:15:26 +0200 Subject: [PATCH 15/47] Fix cibuildwheel CFD_ROOT path for Linux containers Move CFD library checkout from ./cfd to ./src/cfd_lib so it's inside the source tree. This ensures the library is accessible from within cibuildwheel's Docker containers on Linux, where only the source tree is mounted. --- .github/workflows/build-wheels.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 785bea3..dddb955 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -58,26 +58,27 @@ jobs: fi fi - # Checkout the CFD C library into workspace subdirectory + # Checkout the CFD C library into 'src/cfd_lib' subdirectory + # This location is inside the source tree so it's accessible from cibuildwheel + # build environments (including Linux Docker containers where source is mounted) - name: Checkout CFD C library uses: actions/checkout@v4 with: repository: ${{ github.repository_owner }}/cfd - path: cfd + path: src/cfd_lib ref: ${{ steps.cfd-version.outputs.ref }} fetch-depth: 0 # Build wheels using cibuildwheel - # Configuration is in pyproject.toml - only override CFD_ROOT for CI - # (pyproject.toml uses ../cfd for local dev, CI uses ./cfd) + # The before-build commands in pyproject.toml will build the CFD library - name: Build wheels uses: pypa/cibuildwheel@v2.21.3 env: # CIBW_ENVIRONMENT completely replaces (not merges with) the environment # settings in pyproject.toml, including platform-specific ones. # We must specify all required variables here for CI builds. - # CFD_ROOT=./cfd points to the cfd checkout in CI workspace. - CIBW_ENVIRONMENT: "CMAKE_BUILD_TYPE=Release CFD_STATIC_LINK=ON CFD_USE_STABLE_ABI=ON CFD_ROOT=./cfd" + # CFD_ROOT=./src/cfd_lib points to the cfd checkout inside source tree. + CIBW_ENVIRONMENT: "CMAKE_BUILD_TYPE=Release CFD_STATIC_LINK=ON CFD_USE_STABLE_ABI=ON CFD_ROOT=./src/cfd_lib" - uses: actions/upload-artifact@v4 with: From 2b5b90656171c4f309307046bc905d009d3b97b5 Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 16:22:54 +0200 Subject: [PATCH 16/47] Override platform-specific cibuildwheel settings for CI The pyproject.toml platform-specific environment/before-build settings override the global CIBW_ENVIRONMENT. Add explicit overrides for each platform (CIBW_ENVIRONMENT_LINUX, CIBW_BEFORE_BUILD_LINUX, etc.) to ensure CFD_ROOT points to ./src/cfd_lib in CI builds. Also includes -DCMAKE_POSITION_INDEPENDENT_CODE=ON for Linux to ensure the static library can be linked into a shared object. --- .github/workflows/build-wheels.yml | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index dddb955..e540f09 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -70,15 +70,26 @@ jobs: fetch-depth: 0 # Build wheels using cibuildwheel - # The before-build commands in pyproject.toml will build the CFD library + # We override both environment and before-build for each platform since + # pyproject.toml has platform-specific settings that use ../cfd (for local dev) - name: Build wheels uses: pypa/cibuildwheel@v2.21.3 env: - # CIBW_ENVIRONMENT completely replaces (not merges with) the environment - # settings in pyproject.toml, including platform-specific ones. - # We must specify all required variables here for CI builds. - # CFD_ROOT=./src/cfd_lib points to the cfd checkout inside source tree. + # Override environment for all platforms - CFD_ROOT points to checkout inside source tree CIBW_ENVIRONMENT: "CMAKE_BUILD_TYPE=Release CFD_STATIC_LINK=ON CFD_USE_STABLE_ABI=ON CFD_ROOT=./src/cfd_lib" + CIBW_ENVIRONMENT_LINUX: "CMAKE_BUILD_TYPE=Release CFD_STATIC_LINK=ON CFD_USE_STABLE_ABI=ON CFD_ROOT=./src/cfd_lib" + CIBW_ENVIRONMENT_MACOS: "CMAKE_BUILD_TYPE=Release CFD_STATIC_LINK=ON CFD_USE_STABLE_ABI=ON CFD_ROOT=./src/cfd_lib" + CIBW_ENVIRONMENT_WINDOWS: "CMAKE_BUILD_TYPE=Release CFD_STATIC_LINK=ON CFD_USE_STABLE_ABI=ON CFD_ROOT=./src/cfd_lib" + # Override before-build to use the correct CFD_ROOT path + CIBW_BEFORE_BUILD_LINUX: > + cmake -S ./src/cfd_lib -B ./src/cfd_lib/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON && + cmake --build ./src/cfd_lib/build --config Release + CIBW_BEFORE_BUILD_MACOS: > + cmake -S ./src/cfd_lib -B ./src/cfd_lib/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF && + cmake --build ./src/cfd_lib/build --config Release + CIBW_BEFORE_BUILD_WINDOWS: > + cmake -S ./src/cfd_lib -B ./src/cfd_lib/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF && + cmake --build ./src/cfd_lib/build --config Release - uses: actions/upload-artifact@v4 with: From 776e3470dd71cc239ea414aa746b53cbc9ef0e99 Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 16:26:49 +0200 Subject: [PATCH 17/47] Consolidate cibuildwheel config in pyproject.toml - Remove duplicate platform-specific environment settings from pyproject.toml (now inherit from global environment setting) - Add -DCMAKE_POSITION_INDEPENDENT_CODE=ON to Linux before-build - Simplify CI workflow to only override CFD_ROOT (the only CI-specific value) - All build configuration now lives in pyproject.toml as single source of truth --- .github/workflows/build-wheels.yml | 20 ++++---------------- pyproject.toml | 7 ++----- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index e540f09..50d6c7e 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -70,26 +70,14 @@ jobs: fetch-depth: 0 # Build wheels using cibuildwheel - # We override both environment and before-build for each platform since - # pyproject.toml has platform-specific settings that use ../cfd (for local dev) + # Configuration is in pyproject.toml - we only override CFD_ROOT for CI + # (pyproject.toml defaults to ../cfd for local dev, CI uses ./src/cfd_lib) - name: Build wheels uses: pypa/cibuildwheel@v2.21.3 env: - # Override environment for all platforms - CFD_ROOT points to checkout inside source tree + # Override CFD_ROOT to point to the cfd checkout inside the source tree + # All other settings (build flags, before-build, etc.) come from pyproject.toml CIBW_ENVIRONMENT: "CMAKE_BUILD_TYPE=Release CFD_STATIC_LINK=ON CFD_USE_STABLE_ABI=ON CFD_ROOT=./src/cfd_lib" - CIBW_ENVIRONMENT_LINUX: "CMAKE_BUILD_TYPE=Release CFD_STATIC_LINK=ON CFD_USE_STABLE_ABI=ON CFD_ROOT=./src/cfd_lib" - CIBW_ENVIRONMENT_MACOS: "CMAKE_BUILD_TYPE=Release CFD_STATIC_LINK=ON CFD_USE_STABLE_ABI=ON CFD_ROOT=./src/cfd_lib" - CIBW_ENVIRONMENT_WINDOWS: "CMAKE_BUILD_TYPE=Release CFD_STATIC_LINK=ON CFD_USE_STABLE_ABI=ON CFD_ROOT=./src/cfd_lib" - # Override before-build to use the correct CFD_ROOT path - CIBW_BEFORE_BUILD_LINUX: > - cmake -S ./src/cfd_lib -B ./src/cfd_lib/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON && - cmake --build ./src/cfd_lib/build --config Release - CIBW_BEFORE_BUILD_MACOS: > - cmake -S ./src/cfd_lib -B ./src/cfd_lib/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF && - cmake --build ./src/cfd_lib/build --config Release - CIBW_BEFORE_BUILD_WINDOWS: > - cmake -S ./src/cfd_lib -B ./src/cfd_lib/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF && - cmake --build ./src/cfd_lib/build --config Release - uses: actions/upload-artifact@v4 with: diff --git a/pyproject.toml b/pyproject.toml index 34c4c2c..f1d3ebb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -108,8 +108,6 @@ before-build = [ "cmake -S %CFD_ROOT% -B %CFD_ROOT%/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF", "cmake --build %CFD_ROOT%/build --config Release", ] -# No repair needed for statically linked wheels -environment = { CMAKE_BUILD_TYPE = "Release", CFD_STATIC_LINK = "ON", CFD_USE_STABLE_ABI = "ON", CFD_ROOT = "../cfd" } [tool.cibuildwheel.macos] # macOS-specific configuration @@ -120,19 +118,18 @@ before-build = [ ] # delocate handles any remaining dynamic dependencies (system libs) 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" } [tool.cibuildwheel.linux] # Linux-specific configuration # manylinux images already have cmake and gcc installed # CFD_ROOT environment variable specifies the CFD library location +# -DCMAKE_POSITION_INDEPENDENT_CODE=ON is required for static lib linked into shared object before-build = [ - "cmake -S $CFD_ROOT -B $CFD_ROOT/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF", + "cmake -S $CFD_ROOT -B $CFD_ROOT/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON", "cmake --build $CFD_ROOT/build --config Release", ] # auditwheel handles any remaining dynamic dependencies (system libs) 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" } [tool.pytest.ini_options] testpaths = ["tests"] From 3e7140b84f6b56a9a6238c7e76cd4e215a4986d6 Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 16:50:43 +0200 Subject: [PATCH 18/47] Add wheel contents debugging to CI List wheel contents after build to diagnose missing extension issue. --- .github/workflows/build-wheels.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 50d6c7e..003aa1a 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -79,6 +79,18 @@ jobs: # All other settings (build flags, before-build, etc.) come from pyproject.toml CIBW_ENVIRONMENT: "CMAKE_BUILD_TYPE=Release CFD_STATIC_LINK=ON CFD_USE_STABLE_ABI=ON CFD_ROOT=./src/cfd_lib" + - name: List wheel contents for debugging + shell: bash + run: | + echo "=== Wheels built ===" + ls -la wheelhouse/ + echo "" + echo "=== Inspecting wheel contents ===" + for wheel in wheelhouse/*.whl; do + echo "--- $wheel ---" + python -m zipfile -l "$wheel" | grep -E '\.(so|pyd|dylib)$' || echo "No extension found!" + done + - uses: actions/upload-artifact@v4 with: name: wheels-${{ matrix.os }} From ca575f423d09999cbbc0796cef07713edb0e2e4e Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 16:52:01 +0200 Subject: [PATCH 19/47] Add more debugging to test_package job - List available wheels before installing - Show Python version - Use verbose pip install - Print package directory contents after install Co-Authored-By: Claude --- .github/workflows/build-wheels.yml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 003aa1a..0af586d 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -132,12 +132,30 @@ jobs: name: wheels-${{ matrix.os }} path: wheelhouse + - name: List available wheels + shell: bash + run: | + echo "=== Available wheels ===" + ls -la wheelhouse/ + echo "" + echo "=== Python version ===" + python --version + - name: Install wheel and test dependencies run: | python -m pip install --upgrade pip - python -m pip install --find-links wheelhouse cfd-python + python -m pip install -v --find-links wheelhouse cfd-python python -m pip install pytest numpy + - name: Verify installation + shell: bash + run: | + echo "=== Installed package location ===" + python -c "import cfd_python; print(cfd_python.__file__)" + echo "" + echo "=== Package directory contents ===" + python -c "import cfd_python, os; print(os.listdir(os.path.dirname(cfd_python.__file__)))" + - name: Run full test suite run: pytest tests/ -v From 0fe789c1eb53cc289ce5cfe2c8c8142c404cb04d Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 17:04:39 +0200 Subject: [PATCH 20/47] Add CMake debug output and env var support for options - CMake options now read from environment variables if set - Add debug messages to show option values during configuration - This helps diagnose why extensions are missing from wheels --- CMakeLists.txt | 25 ++++++++++++++++++++++--- pyproject.toml | 2 ++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d0f6dd9..372a55d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,10 +3,29 @@ cmake_minimum_required(VERSION 3.15...3.27) project(cfd_python LANGUAGES C CXX) # Option to control static vs dynamic linking -option(CFD_STATIC_LINK "Statically link the CFD library" ON) +# Can be set via CMake option or CFD_STATIC_LINK environment variable +if(DEFINED ENV{CFD_STATIC_LINK}) + set(CFD_STATIC_LINK_DEFAULT $ENV{CFD_STATIC_LINK}) +else() + set(CFD_STATIC_LINK_DEFAULT ON) +endif() +option(CFD_STATIC_LINK "Statically link the CFD library" ${CFD_STATIC_LINK_DEFAULT}) -# Option to disable stable ABI (useful for development with Windows Store Python) -option(CFD_USE_STABLE_ABI "Use Python stable ABI (abi3)" OFF) +# Option to enable stable ABI (useful for wheel builds) +# Can be set via CMake option or CFD_USE_STABLE_ABI environment variable +if(DEFINED ENV{CFD_USE_STABLE_ABI}) + set(CFD_USE_STABLE_ABI_DEFAULT $ENV{CFD_USE_STABLE_ABI}) +else() + set(CFD_USE_STABLE_ABI_DEFAULT OFF) +endif() +option(CFD_USE_STABLE_ABI "Use Python stable ABI (abi3)" ${CFD_USE_STABLE_ABI_DEFAULT}) + +# Debug output for build configuration +message(STATUS "CFD_STATIC_LINK: ${CFD_STATIC_LINK}") +message(STATUS "CFD_USE_STABLE_ABI: ${CFD_USE_STABLE_ABI}") +message(STATUS "CFD_STATIC_LINK env: $ENV{CFD_STATIC_LINK}") +message(STATUS "CFD_USE_STABLE_ABI env: $ENV{CFD_USE_STABLE_ABI}") +message(STATUS "CFD_ROOT env: $ENV{CFD_ROOT}") # Find Python and required components find_package(Python 3.8 REQUIRED COMPONENTS Interpreter Development.Module) diff --git a/pyproject.toml b/pyproject.toml index f1d3ebb..55800d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,6 +80,8 @@ wheel.expand-macos-universal-tags = true [tool.scikit-build.cmake.define] CMAKE_BUILD_TYPE = "Release" CFD_STATIC_LINK = "ON" +# Note: CFD_USE_STABLE_ABI and CFD_ROOT are passed via environment variables +# from cibuildwheel (CIBW_ENVIRONMENT) since they differ between local dev and CI [tool.scikit-build.metadata] [tool.scikit-build.metadata.version] From c72dfd2b4c4d083f81b9204b3248bcb8a3f194cd Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 17:07:42 +0200 Subject: [PATCH 21/47] Add debug output to Linux before-build commands Print CFD_ROOT value, working directory, and library build output to diagnose why extensions are not being included in wheels. --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 55800d9..f20236d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -127,8 +127,10 @@ repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest # CFD_ROOT environment variable specifies the CFD library location # -DCMAKE_POSITION_INDEPENDENT_CODE=ON is required for static lib linked into shared object before-build = [ + "echo 'DEBUG: CFD_ROOT='$CFD_ROOT && echo 'DEBUG: pwd='$(pwd) && ls -la $CFD_ROOT || echo 'CFD_ROOT not found'", "cmake -S $CFD_ROOT -B $CFD_ROOT/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON", "cmake --build $CFD_ROOT/build --config Release", + "echo 'DEBUG: Library built:' && ls -la $CFD_ROOT/build/lib/ || echo 'No lib directory'", ] # auditwheel handles any remaining dynamic dependencies (system libs) repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel}" From 2fb07618794951f46d2614424a12244f0b1410fb Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 18:55:02 +0200 Subject: [PATCH 22/47] Add debug output to test-command to show extension files List package directory contents and extension files before attempting import to help diagnose PyInit_cfd_python not found error. --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f20236d..0b256d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,8 +93,8 @@ build = ["cp38-*", "cp39-*", "cp310-*", "cp311-*", "cp312-*"] skip = ["*-musllinux_*", "pp*"] # Skip PyPy and musl Linux # Test command to verify wheels work -# Use double quotes for Windows compatibility -test-command = "python -c \"import cfd_python; print(cfd_python.__version__); print(cfd_python.list_solvers()); r=cfd_python.run_simulation(5,5,3); print(len(r))\"" +# First list package contents to debug extension location, then try import +test-command = "python -c \"import os,sys; import importlib.util; spec=importlib.util.find_spec('cfd_python'); print('Package location:', spec.submodule_search_locations); pkgdir=spec.submodule_search_locations[0]; print('Contents:', os.listdir(pkgdir)); exts=[f for f in os.listdir(pkgdir) if f.endswith('.so') or f.endswith('.pyd')]; print('Extensions:', exts); import cfd_python; print('Version:', cfd_python.__version__)\"" # Global environment - static linking for self-contained wheels # CFD_ROOT can be set to override the default ../cfd location From a84e6a4ff9038715084fc427f32e5ed375cff271 Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 19:05:22 +0200 Subject: [PATCH 23/47] Fix PyInit symbol visibility for Python extension - Remove CMAKE_C_VISIBILITY_PRESET hidden setting that was causing PyInit_cfd_python to be hidden despite PyMODINIT_FUNC - Disable INTERPROCEDURAL_OPTIMIZATION which can strip symbols during link-time optimization This fixes "dynamic module does not define module export function" errors when importing the built wheel. --- CMakeLists.txt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 372a55d..3c4c336 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,9 +31,10 @@ message(STATUS "CFD_ROOT env: $ENV{CFD_ROOT}") find_package(Python 3.8 REQUIRED COMPONENTS Interpreter Development.Module) # Enable stable ABI (abi3) for Python 3.8+ if requested +# Note: We do NOT set CMAKE_C_VISIBILITY_PRESET to hidden here because +# Python_add_library handles visibility correctly, and setting hidden globally +# can cause PyInit_* symbols to be hidden even when PyMODINIT_FUNC is used. if(CFD_USE_STABLE_ABI AND Python_VERSION VERSION_GREATER_EQUAL "3.8") - set(CMAKE_C_VISIBILITY_PRESET hidden) - set(CMAKE_CXX_VISIBILITY_PRESET hidden) add_definitions(-DPy_LIMITED_API=0x03080000) message(STATUS "Using Python stable ABI (abi3)") endif() @@ -103,11 +104,12 @@ Python_add_library(cfd_python MODULE WITH_SOABI src/cfd_python.c ) -# Set properties for stable ABI +# Set properties for the extension module +# Note: INTERPROCEDURAL_OPTIMIZATION (LTO) is disabled because it can cause +# the PyInit_* symbol to be stripped on some platforms/compilers set_target_properties(cfd_python PROPERTIES C_STANDARD 11 CXX_STANDARD 17 - INTERPROCEDURAL_OPTIMIZATION ON ) # Use stable ABI if requested and available From f78f633225afd70b499bb7808bdcc4089fceffe2 Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 19:13:47 +0200 Subject: [PATCH 24/47] Add detailed debugging for wheel extension verification - Show wheel contents (looking for .so/.pyd) before installing - Add detailed extension import debugging in verify step - Try direct import of cfd_python.cfd_python to catch import errors --- .github/workflows/build-wheels.yml | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 0af586d..7155744 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -140,6 +140,12 @@ jobs: echo "" echo "=== Python version ===" python --version + echo "" + echo "=== Wheel contents (looking for .so/.pyd files) ===" + for wheel in wheelhouse/*.whl; do + echo "--- $wheel ---" + python -m zipfile -l "$wheel" | grep -E '\.(so|pyd|dylib)$' || echo "No extension found in wheel!" + done - name: Install wheel and test dependencies run: | @@ -151,10 +157,30 @@ jobs: shell: bash run: | echo "=== Installed package location ===" - python -c "import cfd_python; print(cfd_python.__file__)" + python -c "import cfd_python; print('Package __file__:', cfd_python.__file__)" echo "" echo "=== Package directory contents ===" - python -c "import cfd_python, os; print(os.listdir(os.path.dirname(cfd_python.__file__)))" + python -c "import cfd_python, os; pkgdir=os.path.dirname(cfd_python.__file__); print('Contents:', os.listdir(pkgdir)); exts=[f for f in os.listdir(pkgdir) if '.so' in f or '.pyd' in f]; print('Extensions found:', exts)" + echo "" + echo "=== Try importing C extension directly ===" + python -c " + import sys, os + import cfd_python + pkgdir = os.path.dirname(cfd_python.__file__) + print('Package dir:', pkgdir) + print('Has list_solvers:', hasattr(cfd_python, 'list_solvers')) + # Try direct import of the extension + try: + from cfd_python import cfd_python as ext + print('Direct extension import OK') + print('Extension has PyInit:', hasattr(ext, '__name__')) + print('list_solvers available:', hasattr(ext, 'list_solvers')) + except ImportError as e: + print('Direct extension import FAILED:', e) + # Check __all__ + print('__all__:', getattr(cfd_python, '__all__', 'NOT DEFINED')) + print('__version__:', getattr(cfd_python, '__version__', 'NOT DEFINED')) + " - name: Run full test suite run: pytest tests/ -v From a2ea2bf37d88195a11493a3c3c066998c3835f40 Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 19:25:36 +0200 Subject: [PATCH 25/47] Add verbose cibuildwheel output for debugging --- .github/workflows/build-wheels.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 7155744..60a15f3 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -78,6 +78,8 @@ jobs: # Override CFD_ROOT to point to the cfd checkout inside the source tree # All other settings (build flags, before-build, etc.) come from pyproject.toml CIBW_ENVIRONMENT: "CMAKE_BUILD_TYPE=Release CFD_STATIC_LINK=ON CFD_USE_STABLE_ABI=ON CFD_ROOT=./src/cfd_lib" + # Enable verbose output to see CMake and compiler commands + CIBW_BUILD_VERBOSITY: "3" - name: List wheel contents for debugging shell: bash From f0b8274cef7c54f0361ddd574f4fb9139f984938 Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 19:28:12 +0200 Subject: [PATCH 26/47] Enable stable ABI (abi3) wheel generation in scikit-build - Add wheel.py-api = "cp38" for abi3 wheel tags - Move CFD_USE_STABLE_ABI to cmake.define for consistency This should produce cp38-abi3 wheels that work with Python 3.8+ instead of version-specific cp38-cp38 wheels. --- pyproject.toml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0b256d1..18d69ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,13 +75,15 @@ minimum-version = "0.4" build-dir = "build/{wheel_tag}" # Build stable ABI wheels for CPython 3.8+ +# This creates cp38-abi3 wheels that work with Python 3.8+ +wheel.py-api = "cp38" wheel.expand-macos-universal-tags = true [tool.scikit-build.cmake.define] CMAKE_BUILD_TYPE = "Release" CFD_STATIC_LINK = "ON" -# Note: CFD_USE_STABLE_ABI and CFD_ROOT are passed via environment variables -# from cibuildwheel (CIBW_ENVIRONMENT) since they differ between local dev and CI +CFD_USE_STABLE_ABI = "ON" +# Note: CFD_ROOT is passed via CIBW_ENVIRONMENT since it differs between local dev and CI [tool.scikit-build.metadata] [tool.scikit-build.metadata.version] From ad87845f2aee9959e01e3b1ababf56ab09ea452a Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 20:06:19 +0200 Subject: [PATCH 27/47] Fix extension suffix for stable ABI (abi3) wheels The extension was being named cfd_python.cpython-38-x86_64-linux-gnu.so but abi3 wheels need cfd_python.abi3.so for cross-version compatibility. Python 3.9+ couldn't find the module because it looks for either: - cfd_python.cpython-39-x86_64-linux-gnu.so (version-specific) - cfd_python.abi3.so (stable ABI) Now explicitly set: - Linux/macOS: .abi3.so suffix - Windows: .abi3.pyd suffix --- CMakeLists.txt | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c4c336..b234d1e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,11 +115,22 @@ set_target_properties(cfd_python PROPERTIES # Use stable ABI if requested and available if(CFD_USE_STABLE_ABI AND Python_VERSION VERSION_GREATER_EQUAL "3.8") target_compile_definitions(cfd_python PRIVATE Py_LIMITED_API=0x03080000) -endif() -# Set proper extension name -if(WIN32) - set_target_properties(cfd_python PROPERTIES SUFFIX ".pyd") + # Set the correct extension suffix for stable ABI + # This ensures the .so/.pyd file is named correctly for abi3 wheels + if(WIN32) + set_target_properties(cfd_python PROPERTIES SUFFIX ".abi3.pyd") + else() + # Linux and macOS both use .abi3.so for stable ABI + set_target_properties(cfd_python PROPERTIES SUFFIX ".abi3.so") + endif() + message(STATUS "Using stable ABI extension suffix") +else() + # Non-stable ABI: use platform default suffix + # Python_add_library with WITH_SOABI handles this, but Windows needs explicit .pyd + if(WIN32) + set_target_properties(cfd_python PROPERTIES SUFFIX ".pyd") + endif() endif() # Include directories From b1da0bd0336c6240b813072700a75e5672bd5038 Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 20:15:34 +0200 Subject: [PATCH 28/47] Fix Windows extension suffix - use .pyd not .abi3.pyd Windows Python does not recognize .abi3.pyd as a valid extension suffix. Unlike Linux/macOS which support .abi3.so, Windows only looks for: - .cp38-win_amd64.pyd (version-specific) - .pyd (generic) The abi3 compatibility is indicated by the wheel tag (cp38-abi3-win_amd64), not the extension filename on Windows. --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b234d1e..13004d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,11 +117,11 @@ if(CFD_USE_STABLE_ABI AND Python_VERSION VERSION_GREATER_EQUAL "3.8") target_compile_definitions(cfd_python PRIVATE Py_LIMITED_API=0x03080000) # Set the correct extension suffix for stable ABI - # This ensures the .so/.pyd file is named correctly for abi3 wheels + # - Linux/macOS: .abi3.so (Python recognizes this suffix) + # - Windows: .pyd (Windows Python does NOT recognize .abi3.pyd suffix) if(WIN32) - set_target_properties(cfd_python PROPERTIES SUFFIX ".abi3.pyd") + set_target_properties(cfd_python PROPERTIES SUFFIX ".pyd") else() - # Linux and macOS both use .abi3.so for stable ABI set_target_properties(cfd_python PROPERTIES SUFFIX ".abi3.so") endif() message(STATUS "Using stable ABI extension suffix") From 67394eab491604a441bd8f50ac91ac042b48fe5b Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 20:21:01 +0200 Subject: [PATCH 29/47] Use static MSVC runtime on Windows to avoid DLL dependencies The "DLL load failed" error was caused by the extension depending on vcruntime DLLs that may not be present on all systems. - Build both CFD library and Python extension with static MSVC runtime (MultiThreaded instead of MultiThreadedDLL) - This makes the wheel self-contained without requiring VC++ redistributable --- CMakeLists.txt | 6 ++++++ pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 13004d4..3ba658f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,6 +112,12 @@ set_target_properties(cfd_python PROPERTIES CXX_STANDARD 17 ) +# On Windows, use static MSVC runtime to avoid vcruntime DLL dependencies +if(WIN32 AND MSVC) + set_property(TARGET cfd_python PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +endif() + # Use stable ABI if requested and available if(CFD_USE_STABLE_ABI AND Python_VERSION VERSION_GREATER_EQUAL "3.8") target_compile_definitions(cfd_python PRIVATE Py_LIMITED_API=0x03080000) diff --git a/pyproject.toml b/pyproject.toml index 18d69ea..bb33a5a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -109,7 +109,7 @@ environment = { CMAKE_BUILD_TYPE = "Release", CFD_STATIC_LINK = "ON", CFD_USE_ST # Only build 64-bit wheels (AMD64) - skip x86 (32-bit) archs = ["AMD64"] before-build = [ - "cmake -S %CFD_ROOT% -B %CFD_ROOT%/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF", + "cmake -S %CFD_ROOT% -B %CFD_ROOT%/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded", "cmake --build %CFD_ROOT%/build --config Release", ] From 74235aadc74bba516ff239cc8dd6f22e53ad33b4 Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 20:25:00 +0200 Subject: [PATCH 30/47] Skip Windows builds for now due to DLL linking issues Windows wheel builds have complex MSVC runtime and DLL dependency issues that need more investigation. Focus on Linux and macOS first to get the CI pipeline working. TODO: Re-enable Windows builds after resolving: - Static MSVC runtime linking - python3.lib stable ABI linking - Potential vcruntime dependencies --- .github/workflows/build-wheels.yml | 6 ++++-- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 60a15f3..accd19d 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -24,7 +24,8 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, windows-latest, macos-latest] + # Skip Windows for now due to DLL linking issues + os: [ubuntu-latest, macos-latest] steps: - uses: actions/checkout@v4 @@ -120,7 +121,8 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, windows-latest, macos-latest] + # Skip Windows for now due to DLL linking issues + os: [ubuntu-latest, macos-latest] python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: diff --git a/pyproject.toml b/pyproject.toml index bb33a5a..15feedf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -92,7 +92,7 @@ provider = "scikit_build_core.metadata.setuptools_scm" [tool.cibuildwheel] # Build wheels for Python 3.8+ build = ["cp38-*", "cp39-*", "cp310-*", "cp311-*", "cp312-*"] -skip = ["*-musllinux_*", "pp*"] # Skip PyPy and musl Linux +skip = ["*-musllinux_*", "pp*", "*-win*"] # Skip PyPy, musl Linux, and Windows for now # Test command to verify wheels work # First list package contents to debug extension location, then try import From ed15adf913a1aca344a1f9c69712d52f9a96395b Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 20:32:58 +0200 Subject: [PATCH 31/47] Simplify CI to Python 3.12 on Linux only for debugging Build and test only cp312-manylinux_x86_64 to isolate issues faster. Will expand to other platforms/versions once this works. --- .github/workflows/build-wheels.yml | 10 +++++----- pyproject.toml | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index accd19d..29cef9d 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -24,8 +24,8 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - # Skip Windows for now due to DLL linking issues - os: [ubuntu-latest, macos-latest] + # Build only on Linux for now (simplified debugging) + os: [ubuntu-latest] steps: - uses: actions/checkout@v4 @@ -121,9 +121,9 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - # Skip Windows for now due to DLL linking issues - os: [ubuntu-latest, macos-latest] - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + # Test only on Linux with Python 3.12 for now (simplified debugging) + os: [ubuntu-latest] + python-version: ["3.12"] steps: - uses: actions/checkout@v4 diff --git a/pyproject.toml b/pyproject.toml index 15feedf..42f09c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -90,9 +90,9 @@ CFD_USE_STABLE_ABI = "ON" provider = "scikit_build_core.metadata.setuptools_scm" [tool.cibuildwheel] -# Build wheels for Python 3.8+ -build = ["cp38-*", "cp39-*", "cp310-*", "cp311-*", "cp312-*"] -skip = ["*-musllinux_*", "pp*", "*-win*"] # Skip PyPy, musl Linux, and Windows for now +# Build wheels for Python 3.12 on Linux only for now (simplified debugging) +build = ["cp312-manylinux_x86_64"] +skip = ["*-musllinux_*", "pp*"] # Skip PyPy and musl Linux # Test command to verify wheels work # First list package contents to debug extension location, then try import From 2e5701cb737287ceccdc3cf460e212d352a55d27 Mon Sep 17 00:00:00 2001 From: shaia Date: Sat, 29 Nov 2025 20:35:39 +0200 Subject: [PATCH 32/47] Build cp38-abi3 wheel and test on Python 3.8 and 3.12 Since we're using wheel.py-api = "cp38" for stable ABI, we should build with cp38 to create a proper abi3 wheel that works on 3.8+. Test on both 3.8 (native) and 3.12 (abi3 compatibility) to verify. --- .github/workflows/build-wheels.yml | 4 ++-- pyproject.toml | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 29cef9d..3082e4d 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -121,9 +121,9 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - # Test only on Linux with Python 3.12 for now (simplified debugging) + # Test on Linux with Python 3.8 (matches abi3 wheel) and 3.12 (to verify abi3 compat) os: [ubuntu-latest] - python-version: ["3.12"] + python-version: ["3.8", "3.12"] steps: - uses: actions/checkout@v4 diff --git a/pyproject.toml b/pyproject.toml index 42f09c9..76264b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -90,8 +90,9 @@ CFD_USE_STABLE_ABI = "ON" provider = "scikit_build_core.metadata.setuptools_scm" [tool.cibuildwheel] -# Build wheels for Python 3.12 on Linux only for now (simplified debugging) -build = ["cp312-manylinux_x86_64"] +# Build wheels for Python 3.8 on Linux only for now (simplified debugging) +# Using cp38 since we're building abi3 wheels with wheel.py-api = "cp38" +build = ["cp38-manylinux_x86_64"] skip = ["*-musllinux_*", "pp*"] # Skip PyPy and musl Linux # Test command to verify wheels work From 95cf38ac281a1143aa4712cd992e394650b12224 Mon Sep 17 00:00:00 2001 From: shaia Date: Sun, 30 Nov 2025 09:13:27 +0200 Subject: [PATCH 33/47] Simplify CI to basic Python 3.8 Linux build Start fresh with a simple workflow: - Direct python -m build instead of cibuildwheel - Build CFD library explicitly before wheel - No stable ABI complexity - Python 3.8 on Linux only Previous configs backed up to backups/ --- .github/workflows/build-wheels.yml | 260 +++++++---------------------- CMakeLists.txt | 157 +++-------------- backups/build-wheels.yml.bak | 251 ++++++++++++++++++++++++++++ backups/pyproject.toml.bak | 143 ++++++++++++++++ pyproject.toml | 70 +------- 5 files changed, 472 insertions(+), 409 deletions(-) create mode 100644 backups/build-wheels.yml.bak create mode 100644 backups/pyproject.toml.bak diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 3082e4d..2cd46b2 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -2,250 +2,102 @@ name: Build and Test Wheels on: push: - branches: [main, master] - tags: - - "v*" + branches: [main, master, feature/ci-cd] pull_request: branches: [main, master] - release: - types: [published] - # Allow manual triggering or remote triggering via GitHub CLI/API - # (used by cfd repo's version-release.yml via: gh workflow run build-wheels.yml) workflow_dispatch: - inputs: - cfd_ref: - description: "CFD library ref (tag/branch/commit). Leave empty to auto-detect." - required: false - default: "" jobs: - build_wheels: - name: Build wheels on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - # Build only on Linux for now (simplified debugging) - os: [ubuntu-latest] + build_wheel: + name: Build wheel on Linux + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - # Determine which version of CFD library to use - # Priority: workflow_dispatch input > latest cfd release tag - # This ensures we always build against stable, tested cfd releases - - name: Determine CFD library version - id: cfd-version - shell: bash - env: - GH_TOKEN: ${{ github.token }} - run: | - # Check for workflow_dispatch input first - if [[ -n "${{ github.event.inputs.cfd_ref }}" ]]; then - echo "ref=${{ github.event.inputs.cfd_ref }}" >> $GITHUB_OUTPUT - echo "Using CFD library ref from input: ${{ github.event.inputs.cfd_ref }}" - else - # Fetch the latest release tag from cfd repo - LATEST_TAG=$(gh api repos/${{ github.repository_owner }}/cfd/releases/latest --jq '.tag_name' 2>/dev/null || echo "") - - if [[ -n "$LATEST_TAG" ]]; then - echo "ref=$LATEST_TAG" >> $GITHUB_OUTPUT - echo "Using latest CFD release: $LATEST_TAG" - else - # Fallback to master if no releases exist - echo "ref=master" >> $GITHUB_OUTPUT - echo "No CFD releases found, falling back to master branch" - fi - fi - - # Checkout the CFD C library into 'src/cfd_lib' subdirectory - # This location is inside the source tree so it's accessible from cibuildwheel - # build environments (including Linux Docker containers where source is mounted) + # Checkout CFD C library - name: Checkout CFD C library uses: actions/checkout@v4 with: repository: ${{ github.repository_owner }}/cfd - path: src/cfd_lib - ref: ${{ steps.cfd-version.outputs.ref }} + path: cfd fetch-depth: 0 - # Build wheels using cibuildwheel - # Configuration is in pyproject.toml - we only override CFD_ROOT for CI - # (pyproject.toml defaults to ../cfd for local dev, CI uses ./src/cfd_lib) - - name: Build wheels - uses: pypa/cibuildwheel@v2.21.3 + - name: Set up Python 3.8 + uses: actions/setup-python@v5 + with: + python-version: "3.8" + + - name: Install build dependencies + run: | + python -m pip install --upgrade pip + pip install build scikit-build-core setuptools-scm + + - name: Build CFD library + 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 + echo "=== CFD library built ===" + ls -la cfd/build/lib/ + + - name: Build wheel env: - # Override CFD_ROOT to point to the cfd checkout inside the source tree - # All other settings (build flags, before-build, etc.) come from pyproject.toml - CIBW_ENVIRONMENT: "CMAKE_BUILD_TYPE=Release CFD_STATIC_LINK=ON CFD_USE_STABLE_ABI=ON CFD_ROOT=./src/cfd_lib" - # Enable verbose output to see CMake and compiler commands - CIBW_BUILD_VERBOSITY: "3" - - - name: List wheel contents for debugging - shell: bash + CFD_ROOT: ./cfd + CFD_STATIC_LINK: "ON" + CFD_USE_STABLE_ABI: "OFF" + run: | + python -m build --wheel + echo "=== Wheel built ===" + ls -la dist/ + + - name: Inspect wheel contents run: | - echo "=== Wheels built ===" - ls -la wheelhouse/ - echo "" - echo "=== Inspecting wheel contents ===" - for wheel in wheelhouse/*.whl; do - echo "--- $wheel ---" - python -m zipfile -l "$wheel" | grep -E '\.(so|pyd|dylib)$' || echo "No extension found!" + for wheel in dist/*.whl; do + echo "=== Contents of $wheel ===" + python -m zipfile -l "$wheel" done - uses: actions/upload-artifact@v4 with: - name: wheels-${{ matrix.os }} - path: ./wheelhouse/*.whl + name: wheel + path: dist/*.whl - build_sdist: - name: Build source distribution + test_wheel: + name: Test wheel + needs: [build_wheel] runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Build sdist - run: pipx run build --sdist - - - uses: actions/upload-artifact@v4 - with: - name: sdist - path: dist/*.tar.gz - - test_package: - name: Test package installation - needs: [build_wheels] - runs-on: ${{ matrix.os }} - strategy: - matrix: - # Test on Linux with Python 3.8 (matches abi3 wheel) and 3.12 (to verify abi3 compat) - os: [ubuntu-latest] - python-version: ["3.8", "3.12"] steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + + - name: Set up Python 3.8 + uses: actions/setup-python@v5 with: - python-version: ${{ matrix.python-version }} + python-version: "3.8" - uses: actions/download-artifact@v4 with: - name: wheels-${{ matrix.os }} - path: wheelhouse - - - name: List available wheels - shell: bash - run: | - echo "=== Available wheels ===" - ls -la wheelhouse/ - echo "" - echo "=== Python version ===" - python --version - echo "" - echo "=== Wheel contents (looking for .so/.pyd files) ===" - for wheel in wheelhouse/*.whl; do - echo "--- $wheel ---" - python -m zipfile -l "$wheel" | grep -E '\.(so|pyd|dylib)$' || echo "No extension found in wheel!" - done + name: wheel + path: dist - - name: Install wheel and test dependencies + - name: Install wheel run: | python -m pip install --upgrade pip - python -m pip install -v --find-links wheelhouse cfd-python - python -m pip install pytest numpy + pip install dist/*.whl + pip install pytest numpy - - name: Verify installation - shell: bash + - name: Test import run: | - echo "=== Installed package location ===" - python -c "import cfd_python; print('Package __file__:', cfd_python.__file__)" - echo "" - echo "=== Package directory contents ===" - python -c "import cfd_python, os; pkgdir=os.path.dirname(cfd_python.__file__); print('Contents:', os.listdir(pkgdir)); exts=[f for f in os.listdir(pkgdir) if '.so' in f or '.pyd' in f]; print('Extensions found:', exts)" - echo "" - echo "=== Try importing C extension directly ===" python -c " - import sys, os import cfd_python - pkgdir = os.path.dirname(cfd_python.__file__) - print('Package dir:', pkgdir) + print('Package loaded:', cfd_python.__file__) + print('Version:', cfd_python.__version__) print('Has list_solvers:', hasattr(cfd_python, 'list_solvers')) - # Try direct import of the extension - try: - from cfd_python import cfd_python as ext - print('Direct extension import OK') - print('Extension has PyInit:', hasattr(ext, '__name__')) - print('list_solvers available:', hasattr(ext, 'list_solvers')) - except ImportError as e: - print('Direct extension import FAILED:', e) - # Check __all__ - print('__all__:', getattr(cfd_python, '__all__', 'NOT DEFINED')) - print('__version__:', getattr(cfd_python, '__version__', 'NOT DEFINED')) + if hasattr(cfd_python, 'list_solvers'): + print('Solvers:', cfd_python.list_solvers()) " - - name: Run full test suite + - name: Run tests run: pytest tests/ -v - - upload_pypi: - name: Upload to PyPI - needs: [build_wheels, build_sdist, test_package] - runs-on: ubuntu-latest - # Publish on: release event, tag push (v*), or workflow_dispatch on a tag - if: | - github.event_name == 'release' || - (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) || - (github.event_name == 'workflow_dispatch' && startsWith(github.ref, 'refs/tags/v')) - environment: - name: pypi - url: https://pypi.org/p/cfd-python - permissions: - id-token: write - - steps: - - uses: actions/download-artifact@v4 - with: - pattern: wheels-* - path: dist - merge-multiple: true - - - uses: actions/download-artifact@v4 - with: - name: sdist - path: dist - - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@v1.12.4 # Pin to specific version for security - with: - packages-dir: dist/ - - upload_test_pypi: - name: Upload to Test PyPI - needs: [build_wheels, build_sdist, test_package] - runs-on: ubuntu-latest - if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') - environment: - name: testpypi - url: https://test.pypi.org/p/cfd-python - permissions: - id-token: write - - steps: - - uses: actions/download-artifact@v4 - with: - pattern: wheels-* - path: dist - merge-multiple: true - - - uses: actions/download-artifact@v4 - with: - name: sdist - path: dist - - - name: Publish to Test PyPI - uses: pypa/gh-action-pypi-publish@v1.12.4 # Pin to specific version for security - with: - repository-url: https://test.pypi.org/legacy/ - packages-dir: dist/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ba658f..0a9d670 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,8 @@ cmake_minimum_required(VERSION 3.15...3.27) -project(cfd_python LANGUAGES C CXX) +project(cfd_python LANGUAGES C) # Option to control static vs dynamic linking -# Can be set via CMake option or CFD_STATIC_LINK environment variable if(DEFINED ENV{CFD_STATIC_LINK}) set(CFD_STATIC_LINK_DEFAULT $ENV{CFD_STATIC_LINK}) else() @@ -11,173 +10,59 @@ else() endif() option(CFD_STATIC_LINK "Statically link the CFD library" ${CFD_STATIC_LINK_DEFAULT}) -# Option to enable stable ABI (useful for wheel builds) -# Can be set via CMake option or CFD_USE_STABLE_ABI environment variable -if(DEFINED ENV{CFD_USE_STABLE_ABI}) - set(CFD_USE_STABLE_ABI_DEFAULT $ENV{CFD_USE_STABLE_ABI}) -else() - set(CFD_USE_STABLE_ABI_DEFAULT OFF) -endif() -option(CFD_USE_STABLE_ABI "Use Python stable ABI (abi3)" ${CFD_USE_STABLE_ABI_DEFAULT}) - -# Debug output for build configuration +# Debug output message(STATUS "CFD_STATIC_LINK: ${CFD_STATIC_LINK}") -message(STATUS "CFD_USE_STABLE_ABI: ${CFD_USE_STABLE_ABI}") -message(STATUS "CFD_STATIC_LINK env: $ENV{CFD_STATIC_LINK}") -message(STATUS "CFD_USE_STABLE_ABI env: $ENV{CFD_USE_STABLE_ABI}") message(STATUS "CFD_ROOT env: $ENV{CFD_ROOT}") -# Find Python and required components +# Find Python find_package(Python 3.8 REQUIRED COMPONENTS Interpreter Development.Module) +message(STATUS "Python version: ${Python_VERSION}") +message(STATUS "Python executable: ${Python_EXECUTABLE}") -# Enable stable ABI (abi3) for Python 3.8+ if requested -# Note: We do NOT set CMAKE_C_VISIBILITY_PRESET to hidden here because -# Python_add_library handles visibility correctly, and setting hidden globally -# can cause PyInit_* symbols to be hidden even when PyMODINIT_FUNC is used. -if(CFD_USE_STABLE_ABI AND Python_VERSION VERSION_GREATER_EQUAL "3.8") - add_definitions(-DPy_LIMITED_API=0x03080000) - message(STATUS "Using Python stable ABI (abi3)") -endif() - -# Find the CFD library from the parent project -# Check for CFD_ROOT environment variable, fall back to default location +# Find CFD library if(DEFINED ENV{CFD_ROOT}) set(CFD_ROOT_DIR "$ENV{CFD_ROOT}") - message(STATUS "Using CFD_ROOT from environment: ${CFD_ROOT_DIR}") else() set(CFD_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../cfd") - message(STATUS "Using default CFD_ROOT: ${CFD_ROOT_DIR}") endif() -set(CFD_INCLUDE_DIR "${CFD_ROOT_DIR}/lib/include") +message(STATUS "CFD_ROOT_DIR: ${CFD_ROOT_DIR}") -# Determine library search paths -# Multi-config generators (Visual Studio, Xcode) put libs in build/lib// -# Single-config generators (Make, Ninja) put libs directly in build/lib/ -if(CMAKE_BUILD_TYPE STREQUAL "Debug") - set(CFD_LIBRARY_DIRS - "${CFD_ROOT_DIR}/build/lib/Debug" # Multi-config (Windows/VS) - "${CFD_ROOT_DIR}/build/lib" # Single-config (Linux/macOS Make) - ) -else() - set(CFD_LIBRARY_DIRS - "${CFD_ROOT_DIR}/build/lib/Release" # Multi-config (Windows/VS) - "${CFD_ROOT_DIR}/build/lib" # Single-config (Linux/macOS Make) - ) -endif() +set(CFD_INCLUDE_DIR "${CFD_ROOT_DIR}/lib/include") +set(CFD_LIBRARY_DIRS + "${CFD_ROOT_DIR}/build/lib/Release" + "${CFD_ROOT_DIR}/build/lib" +) -# Check if CFD library exists (prefer static library for packaging) -if(CFD_STATIC_LINK) - # Look for static library first - if(WIN32) - find_library(CFD_LIBRARY - NAMES cfd_library_static cfd_library - PATHS ${CFD_LIBRARY_DIRS} - NO_DEFAULT_PATH - ) - else() - find_library(CFD_LIBRARY - NAMES cfd_library - PATHS ${CFD_LIBRARY_DIRS} - NO_DEFAULT_PATH - ) - endif() -else() - # Look for shared library - find_library(CFD_LIBRARY - NAMES cfd_library - PATHS ${CFD_LIBRARY_DIRS} - NO_DEFAULT_PATH - ) -endif() +find_library(CFD_LIBRARY + NAMES cfd_library cfd_library_static + PATHS ${CFD_LIBRARY_DIRS} + NO_DEFAULT_PATH +) if(NOT CFD_LIBRARY) - message(FATAL_ERROR "CFD library not found in ${CFD_LIBRARY_DIRS}. Please build the C library first.") + message(FATAL_ERROR "CFD library not found in ${CFD_LIBRARY_DIRS}") endif() message(STATUS "Found CFD library: ${CFD_LIBRARY}") -message(STATUS "CFD include directory: ${CFD_INCLUDE_DIR}") -message(STATUS "Static linking: ${CFD_STATIC_LINK}") +message(STATUS "CFD include dir: ${CFD_INCLUDE_DIR}") # Create the Python extension module -# Use WITH_SOABI to get proper ABI suffix for stable ABI builds Python_add_library(cfd_python MODULE WITH_SOABI src/cfd_python.c ) -# Set properties for the extension module -# Note: INTERPROCEDURAL_OPTIMIZATION (LTO) is disabled because it can cause -# the PyInit_* symbol to be stripped on some platforms/compilers set_target_properties(cfd_python PROPERTIES C_STANDARD 11 - CXX_STANDARD 17 ) -# On Windows, use static MSVC runtime to avoid vcruntime DLL dependencies -if(WIN32 AND MSVC) - set_property(TARGET cfd_python PROPERTY - MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") -endif() - -# Use stable ABI if requested and available -if(CFD_USE_STABLE_ABI AND Python_VERSION VERSION_GREATER_EQUAL "3.8") - target_compile_definitions(cfd_python PRIVATE Py_LIMITED_API=0x03080000) - - # Set the correct extension suffix for stable ABI - # - Linux/macOS: .abi3.so (Python recognizes this suffix) - # - Windows: .pyd (Windows Python does NOT recognize .abi3.pyd suffix) - if(WIN32) - set_target_properties(cfd_python PROPERTIES SUFFIX ".pyd") - else() - set_target_properties(cfd_python PROPERTIES SUFFIX ".abi3.so") - endif() - message(STATUS "Using stable ABI extension suffix") -else() - # Non-stable ABI: use platform default suffix - # Python_add_library with WITH_SOABI handles this, but Windows needs explicit .pyd - if(WIN32) - set_target_properties(cfd_python PROPERTIES SUFFIX ".pyd") - endif() -endif() - -# Include directories target_include_directories(cfd_python PRIVATE ${CFD_INCLUDE_DIR} ${Python_INCLUDE_DIRS} ) -# Link libraries target_link_libraries(cfd_python PRIVATE ${CFD_LIBRARY} ) -# On Windows, handle stable ABI linking explicitly -if(WIN32) - if(CFD_USE_STABLE_ABI) - # For stable ABI, we need to link against python3.lib - # Find the Python library directory and link python3.lib explicitly - get_filename_component(Python_LIBRARY_DIR "${Python_LIBRARY_DIRS}" ABSOLUTE) - if(NOT Python_LIBRARY_DIR) - # Fallback: derive from Python executable path - get_filename_component(Python_ROOT "${Python_EXECUTABLE}" DIRECTORY) - set(Python_LIBRARY_DIR "${Python_ROOT}/libs") - endif() - find_library(Python3_LIBRARY - NAMES python3 - PATHS "${Python_LIBRARY_DIR}" - NO_DEFAULT_PATH - ) - if(Python3_LIBRARY) - message(STATUS "Using stable ABI library: ${Python3_LIBRARY}") - target_link_libraries(cfd_python PRIVATE ${Python3_LIBRARY}) - else() - message(WARNING "python3.lib not found, falling back to versioned library") - target_link_libraries(cfd_python PRIVATE Python::Module) - endif() - else() - # For non-stable ABI, use the versioned library - target_link_libraries(cfd_python PRIVATE Python::Module) - endif() -endif() - -# Install the extension module in the cfd_python package directory -install(TARGETS cfd_python DESTINATION cfd_python) \ No newline at end of file +# Install the extension module +install(TARGETS cfd_python DESTINATION cfd_python) diff --git a/backups/build-wheels.yml.bak b/backups/build-wheels.yml.bak new file mode 100644 index 0000000..3082e4d --- /dev/null +++ b/backups/build-wheels.yml.bak @@ -0,0 +1,251 @@ +name: Build and Test Wheels + +on: + push: + branches: [main, master] + tags: + - "v*" + pull_request: + branches: [main, master] + release: + types: [published] + # Allow manual triggering or remote triggering via GitHub CLI/API + # (used by cfd repo's version-release.yml via: gh workflow run build-wheels.yml) + workflow_dispatch: + inputs: + cfd_ref: + description: "CFD library ref (tag/branch/commit). Leave empty to auto-detect." + required: false + default: "" + +jobs: + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + # Build only on Linux for now (simplified debugging) + os: [ubuntu-latest] + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # Determine which version of CFD library to use + # Priority: workflow_dispatch input > latest cfd release tag + # This ensures we always build against stable, tested cfd releases + - name: Determine CFD library version + id: cfd-version + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + # Check for workflow_dispatch input first + if [[ -n "${{ github.event.inputs.cfd_ref }}" ]]; then + echo "ref=${{ github.event.inputs.cfd_ref }}" >> $GITHUB_OUTPUT + echo "Using CFD library ref from input: ${{ github.event.inputs.cfd_ref }}" + else + # Fetch the latest release tag from cfd repo + LATEST_TAG=$(gh api repos/${{ github.repository_owner }}/cfd/releases/latest --jq '.tag_name' 2>/dev/null || echo "") + + if [[ -n "$LATEST_TAG" ]]; then + echo "ref=$LATEST_TAG" >> $GITHUB_OUTPUT + echo "Using latest CFD release: $LATEST_TAG" + else + # Fallback to master if no releases exist + echo "ref=master" >> $GITHUB_OUTPUT + echo "No CFD releases found, falling back to master branch" + fi + fi + + # Checkout the CFD C library into 'src/cfd_lib' subdirectory + # This location is inside the source tree so it's accessible from cibuildwheel + # build environments (including Linux Docker containers where source is mounted) + - name: Checkout CFD C library + uses: actions/checkout@v4 + with: + repository: ${{ github.repository_owner }}/cfd + path: src/cfd_lib + ref: ${{ steps.cfd-version.outputs.ref }} + fetch-depth: 0 + + # Build wheels using cibuildwheel + # Configuration is in pyproject.toml - we only override CFD_ROOT for CI + # (pyproject.toml defaults to ../cfd for local dev, CI uses ./src/cfd_lib) + - name: Build wheels + uses: pypa/cibuildwheel@v2.21.3 + env: + # Override CFD_ROOT to point to the cfd checkout inside the source tree + # All other settings (build flags, before-build, etc.) come from pyproject.toml + CIBW_ENVIRONMENT: "CMAKE_BUILD_TYPE=Release CFD_STATIC_LINK=ON CFD_USE_STABLE_ABI=ON CFD_ROOT=./src/cfd_lib" + # Enable verbose output to see CMake and compiler commands + CIBW_BUILD_VERBOSITY: "3" + + - name: List wheel contents for debugging + shell: bash + run: | + echo "=== Wheels built ===" + ls -la wheelhouse/ + echo "" + echo "=== Inspecting wheel contents ===" + for wheel in wheelhouse/*.whl; do + echo "--- $wheel ---" + python -m zipfile -l "$wheel" | grep -E '\.(so|pyd|dylib)$' || echo "No extension found!" + done + + - uses: actions/upload-artifact@v4 + with: + name: wheels-${{ matrix.os }} + path: ./wheelhouse/*.whl + + build_sdist: + name: Build source distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Build sdist + run: pipx run build --sdist + + - uses: actions/upload-artifact@v4 + with: + name: sdist + path: dist/*.tar.gz + + test_package: + name: Test package installation + needs: [build_wheels] + runs-on: ${{ matrix.os }} + strategy: + matrix: + # Test on Linux with Python 3.8 (matches abi3 wheel) and 3.12 (to verify abi3 compat) + os: [ubuntu-latest] + python-version: ["3.8", "3.12"] + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - uses: actions/download-artifact@v4 + with: + name: wheels-${{ matrix.os }} + path: wheelhouse + + - name: List available wheels + shell: bash + run: | + echo "=== Available wheels ===" + ls -la wheelhouse/ + echo "" + echo "=== Python version ===" + python --version + echo "" + echo "=== Wheel contents (looking for .so/.pyd files) ===" + for wheel in wheelhouse/*.whl; do + echo "--- $wheel ---" + python -m zipfile -l "$wheel" | grep -E '\.(so|pyd|dylib)$' || echo "No extension found in wheel!" + done + + - name: Install wheel and test dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -v --find-links wheelhouse cfd-python + python -m pip install pytest numpy + + - name: Verify installation + shell: bash + run: | + echo "=== Installed package location ===" + python -c "import cfd_python; print('Package __file__:', cfd_python.__file__)" + echo "" + echo "=== Package directory contents ===" + python -c "import cfd_python, os; pkgdir=os.path.dirname(cfd_python.__file__); print('Contents:', os.listdir(pkgdir)); exts=[f for f in os.listdir(pkgdir) if '.so' in f or '.pyd' in f]; print('Extensions found:', exts)" + echo "" + echo "=== Try importing C extension directly ===" + python -c " + import sys, os + import cfd_python + pkgdir = os.path.dirname(cfd_python.__file__) + print('Package dir:', pkgdir) + print('Has list_solvers:', hasattr(cfd_python, 'list_solvers')) + # Try direct import of the extension + try: + from cfd_python import cfd_python as ext + print('Direct extension import OK') + print('Extension has PyInit:', hasattr(ext, '__name__')) + print('list_solvers available:', hasattr(ext, 'list_solvers')) + except ImportError as e: + print('Direct extension import FAILED:', e) + # Check __all__ + print('__all__:', getattr(cfd_python, '__all__', 'NOT DEFINED')) + print('__version__:', getattr(cfd_python, '__version__', 'NOT DEFINED')) + " + + - name: Run full test suite + run: pytest tests/ -v + + upload_pypi: + name: Upload to PyPI + needs: [build_wheels, build_sdist, test_package] + runs-on: ubuntu-latest + # Publish on: release event, tag push (v*), or workflow_dispatch on a tag + if: | + github.event_name == 'release' || + (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) || + (github.event_name == 'workflow_dispatch' && startsWith(github.ref, 'refs/tags/v')) + environment: + name: pypi + url: https://pypi.org/p/cfd-python + permissions: + id-token: write + + steps: + - uses: actions/download-artifact@v4 + with: + pattern: wheels-* + path: dist + merge-multiple: true + + - uses: actions/download-artifact@v4 + with: + name: sdist + path: dist + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@v1.12.4 # Pin to specific version for security + with: + packages-dir: dist/ + + upload_test_pypi: + name: Upload to Test PyPI + needs: [build_wheels, build_sdist, test_package] + runs-on: ubuntu-latest + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') + environment: + name: testpypi + url: https://test.pypi.org/p/cfd-python + permissions: + id-token: write + + steps: + - uses: actions/download-artifact@v4 + with: + pattern: wheels-* + path: dist + merge-multiple: true + + - uses: actions/download-artifact@v4 + with: + name: sdist + path: dist + + - name: Publish to Test PyPI + uses: pypa/gh-action-pypi-publish@v1.12.4 # Pin to specific version for security + with: + repository-url: https://test.pypi.org/legacy/ + packages-dir: dist/ diff --git a/backups/pyproject.toml.bak b/backups/pyproject.toml.bak new file mode 100644 index 0000000..76264b3 --- /dev/null +++ b/backups/pyproject.toml.bak @@ -0,0 +1,143 @@ +[build-system] +requires = [ + "scikit-build-core>=0.4.3", + "setuptools-scm>=6.2", +] +build-backend = "scikit_build_core.build" + +[project] +name = "cfd-python" +dynamic = ["version"] +description = "High-performance CFD simulation library with Python bindings" +readme = "README.md" +license = {text = "MIT"} +authors = [ + {name = "CFD Team"}, +] +requires-python = ">=3.8" +keywords = ["cfd", "fluid-dynamics", "simulation", "numerical-methods", "physics"] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Science/Research", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: C", + "Topic :: Scientific/Engineering", + "Topic :: Scientific/Engineering :: Physics", + "Topic :: Software Development :: Libraries :: Python Modules", + "Operating System :: OS Independent", +] +dependencies = [] + +# TODO: Add project URLs before release +# [project.urls] +# Homepage = "https://github.com/your-org/cfd-python" +# Documentation = "https://cfd-python.readthedocs.io" +# Repository = "https://github.com/your-org/cfd-python.git" +# Issues = "https://github.com/your-org/cfd-python/issues" +# Changelog = "https://github.com/your-org/cfd-python/blob/main/CHANGELOG.md" + +[project.optional-dependencies] +test = [ + "pytest>=7.0", + "pytest-benchmark", + "numpy", +] +docs = [ + "sphinx>=5.0", + "sphinx-rtd-theme", + "myst-parser", +] +dev = [ + "black", + "isort", + "flake8", + "mypy", + "pre-commit", +] +viz = [ + "matplotlib", + "plotly", + "vtk", +] + +[tool.scikit-build] +# Protect the configuration against future changes in scikit-build-core +minimum-version = "0.4" + +# Setuptools-style build caching in a local directory +build-dir = "build/{wheel_tag}" + +# Build stable ABI wheels for CPython 3.8+ +# This creates cp38-abi3 wheels that work with Python 3.8+ +wheel.py-api = "cp38" +wheel.expand-macos-universal-tags = true + +[tool.scikit-build.cmake.define] +CMAKE_BUILD_TYPE = "Release" +CFD_STATIC_LINK = "ON" +CFD_USE_STABLE_ABI = "ON" +# Note: CFD_ROOT is passed via CIBW_ENVIRONMENT since it differs between local dev and CI + +[tool.scikit-build.metadata] +[tool.scikit-build.metadata.version] +provider = "scikit_build_core.metadata.setuptools_scm" + +[tool.cibuildwheel] +# Build wheels for Python 3.8 on Linux only for now (simplified debugging) +# Using cp38 since we're building abi3 wheels with wheel.py-api = "cp38" +build = ["cp38-manylinux_x86_64"] +skip = ["*-musllinux_*", "pp*"] # Skip PyPy and musl Linux + +# Test command to verify wheels work +# First list package contents to debug extension location, then try import +test-command = "python -c \"import os,sys; import importlib.util; spec=importlib.util.find_spec('cfd_python'); print('Package location:', spec.submodule_search_locations); pkgdir=spec.submodule_search_locations[0]; print('Contents:', os.listdir(pkgdir)); exts=[f for f in os.listdir(pkgdir) if f.endswith('.so') or f.endswith('.pyd')]; print('Extensions:', exts); import cfd_python; print('Version:', cfd_python.__version__)\"" + +# 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" } + +[tool.cibuildwheel.windows] +# Windows-specific configuration +# Build CFD library first (static), then build Python extension +# Only build 64-bit wheels (AMD64) - skip x86 (32-bit) +archs = ["AMD64"] +before-build = [ + "cmake -S %CFD_ROOT% -B %CFD_ROOT%/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded", + "cmake --build %CFD_ROOT%/build --config Release", +] + +[tool.cibuildwheel.macos] +# macOS-specific configuration +# CFD_ROOT environment variable specifies the CFD library location +before-build = [ + "cmake -S $CFD_ROOT -B $CFD_ROOT/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF", + "cmake --build $CFD_ROOT/build --config Release", +] +# delocate handles any remaining dynamic dependencies (system libs) +repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} {wheel}" + +[tool.cibuildwheel.linux] +# Linux-specific configuration +# manylinux images already have cmake and gcc installed +# CFD_ROOT environment variable specifies the CFD library location +# -DCMAKE_POSITION_INDEPENDENT_CODE=ON is required for static lib linked into shared object +before-build = [ + "echo 'DEBUG: CFD_ROOT='$CFD_ROOT && echo 'DEBUG: pwd='$(pwd) && ls -la $CFD_ROOT || echo 'CFD_ROOT not found'", + "cmake -S $CFD_ROOT -B $CFD_ROOT/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON", + "cmake --build $CFD_ROOT/build --config Release", + "echo 'DEBUG: Library built:' && ls -la $CFD_ROOT/build/lib/ || echo 'No lib directory'", +] +# auditwheel handles any remaining dynamic dependencies (system libs) +repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel}" + +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = ["test_*.py"] \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 76264b3..e8423db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,14 +35,6 @@ classifiers = [ ] dependencies = [] -# TODO: Add project URLs before release -# [project.urls] -# Homepage = "https://github.com/your-org/cfd-python" -# Documentation = "https://cfd-python.readthedocs.io" -# Repository = "https://github.com/your-org/cfd-python.git" -# Issues = "https://github.com/your-org/cfd-python/issues" -# Changelog = "https://github.com/your-org/cfd-python/blob/main/CHANGELOG.md" - [project.optional-dependencies] test = [ "pytest>=7.0", @@ -68,76 +60,16 @@ viz = [ ] [tool.scikit-build] -# Protect the configuration against future changes in scikit-build-core minimum-version = "0.4" - -# Setuptools-style build caching in a local directory build-dir = "build/{wheel_tag}" -# Build stable ABI wheels for CPython 3.8+ -# This creates cp38-abi3 wheels that work with Python 3.8+ -wheel.py-api = "cp38" -wheel.expand-macos-universal-tags = true - [tool.scikit-build.cmake.define] CMAKE_BUILD_TYPE = "Release" CFD_STATIC_LINK = "ON" -CFD_USE_STABLE_ABI = "ON" -# Note: CFD_ROOT is passed via CIBW_ENVIRONMENT since it differs between local dev and CI -[tool.scikit-build.metadata] [tool.scikit-build.metadata.version] provider = "scikit_build_core.metadata.setuptools_scm" -[tool.cibuildwheel] -# Build wheels for Python 3.8 on Linux only for now (simplified debugging) -# Using cp38 since we're building abi3 wheels with wheel.py-api = "cp38" -build = ["cp38-manylinux_x86_64"] -skip = ["*-musllinux_*", "pp*"] # Skip PyPy and musl Linux - -# Test command to verify wheels work -# First list package contents to debug extension location, then try import -test-command = "python -c \"import os,sys; import importlib.util; spec=importlib.util.find_spec('cfd_python'); print('Package location:', spec.submodule_search_locations); pkgdir=spec.submodule_search_locations[0]; print('Contents:', os.listdir(pkgdir)); exts=[f for f in os.listdir(pkgdir) if f.endswith('.so') or f.endswith('.pyd')]; print('Extensions:', exts); import cfd_python; print('Version:', cfd_python.__version__)\"" - -# 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" } - -[tool.cibuildwheel.windows] -# Windows-specific configuration -# Build CFD library first (static), then build Python extension -# Only build 64-bit wheels (AMD64) - skip x86 (32-bit) -archs = ["AMD64"] -before-build = [ - "cmake -S %CFD_ROOT% -B %CFD_ROOT%/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded", - "cmake --build %CFD_ROOT%/build --config Release", -] - -[tool.cibuildwheel.macos] -# macOS-specific configuration -# CFD_ROOT environment variable specifies the CFD library location -before-build = [ - "cmake -S $CFD_ROOT -B $CFD_ROOT/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF", - "cmake --build $CFD_ROOT/build --config Release", -] -# delocate handles any remaining dynamic dependencies (system libs) -repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} {wheel}" - -[tool.cibuildwheel.linux] -# Linux-specific configuration -# manylinux images already have cmake and gcc installed -# CFD_ROOT environment variable specifies the CFD library location -# -DCMAKE_POSITION_INDEPENDENT_CODE=ON is required for static lib linked into shared object -before-build = [ - "echo 'DEBUG: CFD_ROOT='$CFD_ROOT && echo 'DEBUG: pwd='$(pwd) && ls -la $CFD_ROOT || echo 'CFD_ROOT not found'", - "cmake -S $CFD_ROOT -B $CFD_ROOT/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON", - "cmake --build $CFD_ROOT/build --config Release", - "echo 'DEBUG: Library built:' && ls -la $CFD_ROOT/build/lib/ || echo 'No lib directory'", -] -# auditwheel handles any remaining dynamic dependencies (system libs) -repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel}" - [tool.pytest.ini_options] testpaths = ["tests"] -python_files = ["test_*.py"] \ No newline at end of file +python_files = ["test_*.py"] From 7ec99bc0c21cb40f3cbe5d6718334b3719bc8e3a Mon Sep 17 00:00:00 2001 From: shaia Date: Sun, 30 Nov 2025 09:17:35 +0200 Subject: [PATCH 34/47] Fix test job to use installed wheel, not source directory The test was importing from the source checkout instead of the installed wheel. Fixed by: - Running import test from /tmp to avoid source shadowing - Using sparse checkout to only get tests directory - Running pytest from /tmp pointing to tests --- .github/workflows/build-wheels.yml | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 2cd46b2..1f4b048 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -44,11 +44,14 @@ jobs: - name: Build wheel env: - CFD_ROOT: ./cfd + CFD_ROOT: ${{ github.workspace }}/cfd CFD_STATIC_LINK: "ON" - CFD_USE_STABLE_ABI: "OFF" run: | - python -m build --wheel + echo "CFD_ROOT=$CFD_ROOT" + echo "Checking CFD library location..." + ls -la $CFD_ROOT/build/lib/ || echo "Library dir not found" + # Use pip wheel to build, which respects env vars better + pip wheel . --no-deps --wheel-dir dist/ echo "=== Wheel built ===" ls -la dist/ @@ -70,8 +73,6 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Set up Python 3.8 uses: actions/setup-python@v5 with: @@ -90,6 +91,8 @@ jobs: - name: Test import run: | + # Run from temp dir to avoid importing from source + cd /tmp python -c " import cfd_python print('Package loaded:', cfd_python.__file__) @@ -99,5 +102,14 @@ jobs: print('Solvers:', cfd_python.list_solvers()) " + # Checkout only tests directory for running tests + - uses: actions/checkout@v4 + with: + sparse-checkout: tests + sparse-checkout-cone-mode: false + - name: Run tests - run: pytest tests/ -v + run: | + # Run from a different directory to use installed package + cd /tmp + pytest $GITHUB_WORKSPACE/tests/ -v From 9e98294e4d5acf20b6152dfd3eb933e20f09443b Mon Sep 17 00:00:00 2001 From: shaia Date: Sun, 30 Nov 2025 09:22:47 +0200 Subject: [PATCH 35/47] Add macOS builds to CI Build and test wheels on both Linux and macOS. --- .github/workflows/build-wheels.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 1f4b048..e2e8aa7 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -9,8 +9,11 @@ on: jobs: build_wheel: - name: Build wheel on Linux - runs-on: ubuntu-latest + name: Build wheel on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest] steps: - uses: actions/checkout@v4 @@ -64,13 +67,16 @@ jobs: - uses: actions/upload-artifact@v4 with: - name: wheel + name: wheel-${{ matrix.os }} path: dist/*.whl test_wheel: - name: Test wheel + name: Test wheel on ${{ matrix.os }} needs: [build_wheel] - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest] steps: - name: Set up Python 3.8 @@ -80,7 +86,7 @@ jobs: - uses: actions/download-artifact@v4 with: - name: wheel + name: wheel-${{ matrix.os }} path: dist - name: Install wheel From b0c6670fd3a2983d86a7b95b0baf1369c164aa16 Mon Sep 17 00:00:00 2001 From: shaia Date: Sun, 30 Nov 2025 09:28:46 +0200 Subject: [PATCH 36/47] Add stable ABI (abi3) wheel support for Python 3.8+ - Add CFD_USE_STABLE_ABI option to CMakeLists.txt with Py_LIMITED_API - Configure .abi3.so extension suffix for Linux/macOS - Add wheel.py-api = "cp38" to pyproject.toml - Test wheel on both Python 3.8 and 3.12 to verify abi3 compatibility --- .github/workflows/build-wheels.yml | 7 ++++--- CMakeLists.txt | 21 +++++++++++++++++++++ pyproject.toml | 2 ++ 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index e2e8aa7..1e19cf7 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -71,18 +71,19 @@ jobs: path: dist/*.whl test_wheel: - name: Test wheel on ${{ matrix.os }} + name: Test wheel on ${{ matrix.os }} with Python ${{ matrix.python }} needs: [build_wheel] runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest] + python: ["3.8", "3.12"] steps: - - name: Set up Python 3.8 + - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v5 with: - python-version: "3.8" + python-version: "${{ matrix.python }}" - uses: actions/download-artifact@v4 with: diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a9d670..ab0956a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,8 +10,12 @@ else() endif() option(CFD_STATIC_LINK "Statically link the CFD library" ${CFD_STATIC_LINK_DEFAULT}) +# Option to use stable ABI (abi3) +option(CFD_USE_STABLE_ABI "Build with Python stable ABI for cross-version compatibility" ON) + # Debug output message(STATUS "CFD_STATIC_LINK: ${CFD_STATIC_LINK}") +message(STATUS "CFD_USE_STABLE_ABI: ${CFD_USE_STABLE_ABI}") message(STATUS "CFD_ROOT env: $ENV{CFD_ROOT}") # Find Python @@ -55,6 +59,23 @@ set_target_properties(cfd_python PROPERTIES C_STANDARD 11 ) +# Configure stable ABI if enabled +if(CFD_USE_STABLE_ABI) + # Define Py_LIMITED_API for Python 3.8+ stable ABI + target_compile_definitions(cfd_python PRIVATE Py_LIMITED_API=0x03080000) + + # Set the correct extension suffix for abi3 + if(WIN32) + # Windows uses .pyd for all Python extensions + set_target_properties(cfd_python PROPERTIES SUFFIX ".pyd") + else() + # Linux/macOS use .abi3.so for stable ABI + set_target_properties(cfd_python PROPERTIES SUFFIX ".abi3.so") + endif() + + message(STATUS "Building with stable ABI (abi3) for Python 3.8+") +endif() + target_include_directories(cfd_python PRIVATE ${CFD_INCLUDE_DIR} ${Python_INCLUDE_DIRS} diff --git a/pyproject.toml b/pyproject.toml index e8423db..fb54f4f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,10 +62,12 @@ viz = [ [tool.scikit-build] minimum-version = "0.4" build-dir = "build/{wheel_tag}" +wheel.py-api = "cp38" [tool.scikit-build.cmake.define] CMAKE_BUILD_TYPE = "Release" CFD_STATIC_LINK = "ON" +CFD_USE_STABLE_ABI = "ON" [tool.scikit-build.metadata.version] provider = "scikit_build_core.metadata.setuptools_scm" From a6e1808fe2af79948900fe6aeac44f60e31c157b Mon Sep 17 00:00:00 2001 From: shaia Date: Sun, 30 Nov 2025 09:33:04 +0200 Subject: [PATCH 37/47] Add Windows support to CI build matrix - Add windows-latest to build and test matrices - Split build/test steps into Unix and Windows variants - Use PowerShell syntax for Windows (dir, $env:, Get-ChildItem) - Use cross-platform Python for wheel inspection --- .github/workflows/build-wheels.yml | 74 ++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 1e19cf7..fe1743b 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -13,7 +13,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, macos-latest] + os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/checkout@v4 @@ -38,14 +38,24 @@ jobs: python -m pip install --upgrade pip pip install build scikit-build-core setuptools-scm - - 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 echo "=== CFD library built ===" ls -la cfd/build/lib/ - - name: Build wheel + - 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 + echo "=== CFD library built ===" + dir cfd\build\lib\Release + + - name: Build wheel (Unix) + if: runner.os != 'Windows' env: CFD_ROOT: ${{ github.workspace }}/cfd CFD_STATIC_LINK: "ON" @@ -53,17 +63,33 @@ jobs: echo "CFD_ROOT=$CFD_ROOT" echo "Checking CFD library location..." ls -la $CFD_ROOT/build/lib/ || echo "Library dir not found" - # Use pip wheel to build, which respects env vars better 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" + run: | + echo "CFD_ROOT=$env:CFD_ROOT" + echo "Checking CFD library location..." + dir "$env:CFD_ROOT\build\lib\Release" + pip wheel . --no-deps --wheel-dir dist/ + echo "=== Wheel built ===" + dir dist + - name: Inspect wheel contents run: | - for wheel in dist/*.whl; do - echo "=== Contents of $wheel ===" - python -m zipfile -l "$wheel" - done + 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) + " - uses: actions/upload-artifact@v4 with: @@ -76,7 +102,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, macos-latest] + os: [ubuntu-latest, macos-latest, windows-latest] python: ["3.8", "3.12"] steps: @@ -90,15 +116,23 @@ jobs: name: wheel-${{ matrix.os }} path: dist - - name: Install wheel + - name: Install wheel (Unix) + if: runner.os != 'Windows' run: | python -m pip install --upgrade pip pip install dist/*.whl pip install pytest numpy - - name: Test import + - name: Install wheel (Windows) + if: runner.os == 'Windows' + run: | + python -m pip install --upgrade pip + pip install (Get-ChildItem dist/*.whl).FullName + pip install pytest numpy + + - name: Test import (Unix) + if: runner.os != 'Windows' run: | - # Run from temp dir to avoid importing from source cd /tmp python -c " import cfd_python @@ -109,14 +143,26 @@ jobs: 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" + # Checkout only tests directory for running tests - uses: actions/checkout@v4 with: sparse-checkout: tests sparse-checkout-cone-mode: false - - name: Run tests + - name: Run tests (Unix) + if: runner.os != 'Windows' run: | - # Run from a different directory to use installed package 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 From a32b65a3d05ba992d018867abbe0c70ee6c3095c Mon Sep 17 00:00:00 2001 From: shaia Date: Sun, 30 Nov 2025 09:53:18 +0200 Subject: [PATCH 38/47] Fix Windows stable ABI linking by explicitly linking python3.lib On Windows with stable ABI, we need to link against python3.lib instead of the version-specific python38.lib. CMake's FindPython doesn't handle this automatically, so we find and link it explicitly. --- CMakeLists.txt | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index ab0956a..4dbea8e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,6 +68,34 @@ if(CFD_USE_STABLE_ABI) if(WIN32) # Windows uses .pyd for all Python extensions set_target_properties(cfd_python PROPERTIES SUFFIX ".pyd") + + # On Windows, we need to link against python3.lib for stable ABI + # Find the Python library directory and link against python3.lib + get_filename_component(PYTHON_LIB_DIR "${Python_LIBRARY_DIRS}" DIRECTORY) + if(NOT PYTHON_LIB_DIR) + # Fallback: derive from Python executable path + get_filename_component(PYTHON_DIR "${Python_EXECUTABLE}" DIRECTORY) + set(PYTHON_LIB_DIR "${PYTHON_DIR}/libs") + endif() + find_library(PYTHON3_STABLE_LIB + NAMES python3 + PATHS "${PYTHON_LIB_DIR}" "${Python_LIBRARY_DIRS}" + NO_DEFAULT_PATH + ) + if(PYTHON3_STABLE_LIB) + target_link_libraries(cfd_python PRIVATE ${PYTHON3_STABLE_LIB}) + message(STATUS "Linking against stable ABI library: ${PYTHON3_STABLE_LIB}") + else() + # Manual fallback - construct the path + get_filename_component(PYTHON_DIR "${Python_EXECUTABLE}" DIRECTORY) + set(PYTHON3_STABLE_LIB "${PYTHON_DIR}/libs/python3.lib") + if(EXISTS "${PYTHON3_STABLE_LIB}") + target_link_libraries(cfd_python PRIVATE ${PYTHON3_STABLE_LIB}) + message(STATUS "Linking against stable ABI library (fallback): ${PYTHON3_STABLE_LIB}") + else() + message(WARNING "Could not find python3.lib for stable ABI linking") + endif() + endif() else() # Linux/macOS use .abi3.so for stable ABI set_target_properties(cfd_python PROPERTIES SUFFIX ".abi3.so") From 46fc738f3261d80ddb30d1d2ac16a504f29310e1 Mon Sep 17 00:00:00 2001 From: shaia Date: Sun, 30 Nov 2025 16:43:37 +0200 Subject: [PATCH 39/47] Fix Windows DLL load by using static MSVC runtime - Add MSVC_RUNTIME_LIBRARY=MultiThreaded to statically link MSVC runtime - Build CFD library with same static runtime to avoid runtime mismatch - This eliminates dependency on VC++ redistributable --- .github/workflows/build-wheels.yml | 2 +- CMakeLists.txt | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index fe1743b..9f417e9 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -49,7 +49,7 @@ jobs: - 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 -S cfd -B cfd/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded cmake --build cfd/build --config Release echo "=== CFD library built ===" dir cfd\build\lib\Release diff --git a/CMakeLists.txt b/CMakeLists.txt index 4dbea8e..8995a2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,12 @@ set_target_properties(cfd_python PROPERTIES C_STANDARD 11 ) +# On Windows, use static MSVC runtime to avoid requiring VC++ redistributable +if(MSVC) + set_property(TARGET cfd_python PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +endif() + # Configure stable ABI if enabled if(CFD_USE_STABLE_ABI) # Define Py_LIMITED_API for Python 3.8+ stable ABI From 761fcafa80b395f93f9baf645d856787081f0460 Mon Sep 17 00:00:00 2001 From: shaia Date: Sun, 30 Nov 2025 16:50:56 +0200 Subject: [PATCH 40/47] Use default MSVC runtime (/MD) to match CFD library Remove static runtime override - both CFD library and Python extension need to use the same runtime. The default /MD (DLL runtime) is standard for Python extensions on Windows. --- .github/workflows/build-wheels.yml | 2 +- CMakeLists.txt | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 9f417e9..fe1743b 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -49,7 +49,7 @@ jobs: - name: Build CFD library (Windows) if: runner.os == 'Windows' run: | - cmake -S cfd -B cfd/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded + cmake -S cfd -B cfd/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF cmake --build cfd/build --config Release echo "=== CFD library built ===" dir cfd\build\lib\Release diff --git a/CMakeLists.txt b/CMakeLists.txt index 8995a2e..4dbea8e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,12 +59,6 @@ set_target_properties(cfd_python PROPERTIES C_STANDARD 11 ) -# On Windows, use static MSVC runtime to avoid requiring VC++ redistributable -if(MSVC) - set_property(TARGET cfd_python PROPERTY - MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") -endif() - # Configure stable ABI if enabled if(CFD_USE_STABLE_ABI) # Define Py_LIMITED_API for Python 3.8+ stable ABI From 356e785fa581eefaad54a41ff297f2668943d6c8 Mon Sep 17 00:00:00 2001 From: shaia Date: Sun, 30 Nov 2025 16:53:42 +0200 Subject: [PATCH 41/47] Run CI on push to main/master only, PRs trigger separately Prevents duplicate runs when pushing to a branch with an open PR. Push triggers only for main/master, PRs trigger for any branch. --- .github/workflows/build-wheels.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index fe1743b..5b1f644 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -2,9 +2,8 @@ name: Build and Test Wheels on: push: - branches: [main, master, feature/ci-cd] - pull_request: branches: [main, master] + pull_request: workflow_dispatch: jobs: From 5e3dadae2514b038db7a7e31592105a283c3afa4 Mon Sep 17 00:00:00 2001 From: shaia Date: Sun, 30 Nov 2025 16:56:38 +0200 Subject: [PATCH 42/47] Use static MSVC runtime for self-contained Windows wheel Both CFD library and Python extension now use /MT (static runtime) to eliminate dependency on MSVC redistributable DLLs. --- .github/workflows/build-wheels.yml | 2 +- CMakeLists.txt | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 5b1f644..ee47e24 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -48,7 +48,7 @@ jobs: - 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 -S cfd -B cfd/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_POLICY_DEFAULT_CMP0091=NEW -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded cmake --build cfd/build --config Release echo "=== CFD library built ===" dir cfd\build\lib\Release diff --git a/CMakeLists.txt b/CMakeLists.txt index 4dbea8e..51c9e9f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,15 @@ set_target_properties(cfd_python PROPERTIES C_STANDARD 11 ) +# On Windows, use static MSVC runtime to create self-contained extension +# This must match how the CFD library was built +if(MSVC) + cmake_policy(SET CMP0091 NEW) + set_property(TARGET cfd_python PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + message(STATUS "Using static MSVC runtime (MultiThreaded)") +endif() + # Configure stable ABI if enabled if(CFD_USE_STABLE_ABI) # Define Py_LIMITED_API for Python 3.8+ stable ABI From 7a7a00dfe9cd68518cb0b8cace295c50d13c1e0e Mon Sep 17 00:00:00 2001 From: shaia Date: Sun, 30 Nov 2025 17:25:43 +0200 Subject: [PATCH 43/47] Use MinGW instead of MSVC for Windows builds MinGW (GCC) produces more portable Windows binaries without MSVC runtime dependencies. This should fix the DLL load failures. --- .github/workflows/build-wheels.yml | 14 +++++++++++--- CMakeLists.txt | 9 --------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index ee47e24..c7b84d0 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -45,13 +45,19 @@ jobs: echo "=== CFD library built ===" ls -la cfd/build/lib/ + - name: Setup MinGW (Windows) + if: runner.os == 'Windows' + uses: egor-tensin/setup-mingw@v2 + with: + platform: x64 + - name: Build CFD library (Windows) if: runner.os == 'Windows' run: | - cmake -S cfd -B cfd/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_POLICY_DEFAULT_CMP0091=NEW -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded + cmake -S cfd -B cfd/build -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF cmake --build cfd/build --config Release echo "=== CFD library built ===" - dir cfd\build\lib\Release + dir cfd\build\lib - name: Build wheel (Unix) if: runner.os != 'Windows' @@ -74,7 +80,9 @@ jobs: run: | echo "CFD_ROOT=$env:CFD_ROOT" echo "Checking CFD library location..." - dir "$env:CFD_ROOT\build\lib\Release" + dir "$env:CFD_ROOT\build\lib" + # Set CMAKE_GENERATOR for scikit-build-core to use MinGW + $env:CMAKE_GENERATOR = "MinGW Makefiles" pip wheel . --no-deps --wheel-dir dist/ echo "=== Wheel built ===" dir dist diff --git a/CMakeLists.txt b/CMakeLists.txt index 51c9e9f..4dbea8e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,15 +59,6 @@ set_target_properties(cfd_python PROPERTIES C_STANDARD 11 ) -# On Windows, use static MSVC runtime to create self-contained extension -# This must match how the CFD library was built -if(MSVC) - cmake_policy(SET CMP0091 NEW) - set_property(TARGET cfd_python PROPERTY - MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - message(STATUS "Using static MSVC runtime (MultiThreaded)") -endif() - # Configure stable ABI if enabled if(CFD_USE_STABLE_ABI) # Define Py_LIMITED_API for Python 3.8+ stable ABI From e367e22e6decc45f58ada64e2ec896a4b13e5ec7 Mon Sep 17 00:00:00 2001 From: shaia Date: Sun, 30 Nov 2025 17:35:26 +0200 Subject: [PATCH 44/47] Fix MinGW setup by using choco directly The egor-tensin/setup-mingw action has a bug with MinGW 15.x. Use chocolatey directly to install MinGW. --- .github/workflows/build-wheels.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index c7b84d0..39854d0 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -47,9 +47,9 @@ jobs: - name: Setup MinGW (Windows) if: runner.os == 'Windows' - uses: egor-tensin/setup-mingw@v2 - with: - platform: x64 + run: | + choco install mingw -y --no-progress + echo "C:\ProgramData\mingw64\mingw64\bin" >> $env:GITHUB_PATH - name: Build CFD library (Windows) if: runner.os == 'Windows' From fadb721f1ee7cbf177240ccc162d8e6473dcd177 Mon Sep 17 00:00:00 2001 From: shaia Date: Sun, 30 Nov 2025 17:48:10 +0200 Subject: [PATCH 45/47] Statically link MinGW runtime for self-contained Windows wheel Add -static and -static-libgcc flags to eliminate dependency on MinGW runtime DLLs (libgcc, libwinpthread, etc.) --- .github/workflows/build-wheels.yml | 2 +- CMakeLists.txt | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 39854d0..51d368e 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -54,7 +54,7 @@ jobs: - name: Build CFD library (Windows) if: runner.os == 'Windows' run: | - cmake -S cfd -B cfd/build -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF + cmake -S cfd -B cfd/build -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_C_FLAGS="-static" -DCMAKE_EXE_LINKER_FLAGS="-static" cmake --build cfd/build --config Release echo "=== CFD library built ===" dir cfd\build\lib diff --git a/CMakeLists.txt b/CMakeLists.txt index 4dbea8e..54c4f3f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,12 @@ set_target_properties(cfd_python PROPERTIES C_STANDARD 11 ) +# On Windows with MinGW, statically link the runtime to avoid DLL dependencies +if(WIN32 AND MINGW) + target_link_options(cfd_python PRIVATE -static-libgcc -static) + message(STATUS "Using static MinGW runtime linking") +endif() + # Configure stable ABI if enabled if(CFD_USE_STABLE_ABI) # Define Py_LIMITED_API for Python 3.8+ stable ABI From 3558227d3518101adab6ffe36c3e436839c38315 Mon Sep 17 00:00:00 2001 From: shaia Date: Sun, 30 Nov 2025 18:22:08 +0200 Subject: [PATCH 46/47] Switch back to MSVC and add DLL dependency diagnostics MinGW causes ABI incompatibility with MSVC-built Python. Add dumpbin diagnostic step to identify missing DLLs. --- .github/workflows/build-wheels.yml | 26 +++++++++++++++----------- CMakeLists.txt | 6 ------ 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 51d368e..ceb4f2c 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -45,19 +45,13 @@ jobs: echo "=== CFD library built ===" ls -la cfd/build/lib/ - - name: Setup MinGW (Windows) - if: runner.os == 'Windows' - run: | - choco install mingw -y --no-progress - echo "C:\ProgramData\mingw64\mingw64\bin" >> $env:GITHUB_PATH - - name: Build CFD library (Windows) if: runner.os == 'Windows' run: | - cmake -S cfd -B cfd/build -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_C_FLAGS="-static" -DCMAKE_EXE_LINKER_FLAGS="-static" + cmake -S cfd -B cfd/build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF cmake --build cfd/build --config Release echo "=== CFD library built ===" - dir cfd\build\lib + dir cfd\build\lib\Release - name: Build wheel (Unix) if: runner.os != 'Windows' @@ -80,9 +74,7 @@ jobs: run: | echo "CFD_ROOT=$env:CFD_ROOT" echo "Checking CFD library location..." - dir "$env:CFD_ROOT\build\lib" - # Set CMAKE_GENERATOR for scikit-build-core to use MinGW - $env:CMAKE_GENERATOR = "MinGW Makefiles" + dir "$env:CFD_ROOT\build\lib\Release" pip wheel . --no-deps --wheel-dir dist/ echo "=== Wheel built ===" dir dist @@ -137,6 +129,18 @@ jobs: pip install (Get-ChildItem dist/*.whl).FullName pip install pytest numpy + - name: Debug Windows DLL (Windows) + if: runner.os == 'Windows' + run: | + $pkg_dir = python -c "import os, cfd_python; print(os.path.dirname(cfd_python.__file__))" + echo "Package directory: $pkg_dir" + dir $pkg_dir + # Check what the .pyd file depends on + $pyd_file = Get-ChildItem "$pkg_dir\*.pyd" | Select-Object -First 1 + echo "PYD file: $pyd_file" + # Use dumpbin to show dependencies + & "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.44.35207\bin\Hostx64\x64\dumpbin.exe" /dependents $pyd_file.FullName + - name: Test import (Unix) if: runner.os != 'Windows' run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 54c4f3f..4dbea8e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,12 +59,6 @@ set_target_properties(cfd_python PROPERTIES C_STANDARD 11 ) -# On Windows with MinGW, statically link the runtime to avoid DLL dependencies -if(WIN32 AND MINGW) - target_link_options(cfd_python PRIVATE -static-libgcc -static) - message(STATUS "Using static MinGW runtime linking") -endif() - # Configure stable ABI if enabled if(CFD_USE_STABLE_ABI) # Define Py_LIMITED_API for Python 3.8+ stable ABI From 4746ebdfd1a03ce1a47b358f9c08e77e2526fe8c Mon Sep 17 00:00:00 2001 From: shaia Date: Sun, 30 Nov 2025 18:51:10 +0200 Subject: [PATCH 47/47] Fix Windows stable ABI by manually linking python3.lib Python_add_library with WITH_SOABI always links to version-specific python3X.lib. For stable ABI on Windows, manually create the module and link against python3.lib to get python3.dll dependency. --- .github/workflows/build-wheels.yml | 12 +++- CMakeLists.txt | 95 ++++++++++++++++-------------- 2 files changed, 60 insertions(+), 47 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index ceb4f2c..e96f934 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -132,14 +132,20 @@ jobs: - name: Debug Windows DLL (Windows) if: runner.os == 'Windows' run: | - $pkg_dir = python -c "import os, cfd_python; print(os.path.dirname(cfd_python.__file__))" + # Find package directory via pip + $pkg_dir = python -c "import sysconfig; print(sysconfig.get_paths()['purelib'])" + $pkg_dir = "$pkg_dir\cfd_python" echo "Package directory: $pkg_dir" dir $pkg_dir # Check what the .pyd file depends on $pyd_file = Get-ChildItem "$pkg_dir\*.pyd" | Select-Object -First 1 echo "PYD file: $pyd_file" - # Use dumpbin to show dependencies - & "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.44.35207\bin\Hostx64\x64\dumpbin.exe" /dependents $pyd_file.FullName + if ($pyd_file) { + # Use dumpbin to show dependencies + $dumpbin = Get-ChildItem "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\*\bin\Hostx64\x64\dumpbin.exe" | Select-Object -First 1 + echo "Using dumpbin: $dumpbin" + & $dumpbin.FullName /dependents $pyd_file.FullName + } - name: Test import (Unix) if: runner.os != 'Windows' diff --git a/CMakeLists.txt b/CMakeLists.txt index 4dbea8e..d871edb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,57 +51,64 @@ message(STATUS "Found CFD library: ${CFD_LIBRARY}") message(STATUS "CFD include dir: ${CFD_INCLUDE_DIR}") # Create the Python extension module -Python_add_library(cfd_python MODULE WITH_SOABI - src/cfd_python.c -) - -set_target_properties(cfd_python PROPERTIES - C_STANDARD 11 -) - -# Configure stable ABI if enabled -if(CFD_USE_STABLE_ABI) - # Define Py_LIMITED_API for Python 3.8+ stable ABI +# For stable ABI on Windows, we need to manually create the library +# to avoid Python_add_library linking against version-specific python3X.lib +if(CFD_USE_STABLE_ABI AND WIN32) + # Create module manually for Windows stable ABI + add_library(cfd_python MODULE src/cfd_python.c) + + set_target_properties(cfd_python PROPERTIES + C_STANDARD 11 + PREFIX "" + SUFFIX ".pyd" + ) + + # Define Py_LIMITED_API for stable ABI target_compile_definitions(cfd_python PRIVATE Py_LIMITED_API=0x03080000) - # Set the correct extension suffix for abi3 - if(WIN32) - # Windows uses .pyd for all Python extensions - set_target_properties(cfd_python PROPERTIES SUFFIX ".pyd") - - # On Windows, we need to link against python3.lib for stable ABI - # Find the Python library directory and link against python3.lib - get_filename_component(PYTHON_LIB_DIR "${Python_LIBRARY_DIRS}" DIRECTORY) - if(NOT PYTHON_LIB_DIR) - # Fallback: derive from Python executable path - get_filename_component(PYTHON_DIR "${Python_EXECUTABLE}" DIRECTORY) - set(PYTHON_LIB_DIR "${PYTHON_DIR}/libs") - endif() - find_library(PYTHON3_STABLE_LIB - NAMES python3 - PATHS "${PYTHON_LIB_DIR}" "${Python_LIBRARY_DIRS}" - NO_DEFAULT_PATH - ) - if(PYTHON3_STABLE_LIB) - target_link_libraries(cfd_python PRIVATE ${PYTHON3_STABLE_LIB}) - message(STATUS "Linking against stable ABI library: ${PYTHON3_STABLE_LIB}") - else() - # Manual fallback - construct the path - get_filename_component(PYTHON_DIR "${Python_EXECUTABLE}" DIRECTORY) + # Find and link python3.lib (stable ABI library) + get_filename_component(PYTHON_DIR "${Python_EXECUTABLE}" DIRECTORY) + # Try multiple possible locations for python3.lib + find_library(PYTHON3_STABLE_LIB + NAMES python3 + PATHS + "${PYTHON_DIR}/libs" + "${PYTHON_DIR}/../libs" + "${Python_LIBRARY_DIRS}" + NO_DEFAULT_PATH + ) + if(NOT PYTHON3_STABLE_LIB) + # Last resort: construct path directly + if(EXISTS "${PYTHON_DIR}/libs/python3.lib") set(PYTHON3_STABLE_LIB "${PYTHON_DIR}/libs/python3.lib") - if(EXISTS "${PYTHON3_STABLE_LIB}") - target_link_libraries(cfd_python PRIVATE ${PYTHON3_STABLE_LIB}) - message(STATUS "Linking against stable ABI library (fallback): ${PYTHON3_STABLE_LIB}") - else() - message(WARNING "Could not find python3.lib for stable ABI linking") - endif() + elseif(EXISTS "${PYTHON_DIR}/../libs/python3.lib") + set(PYTHON3_STABLE_LIB "${PYTHON_DIR}/../libs/python3.lib") endif() + endif() + + if(PYTHON3_STABLE_LIB) + target_link_libraries(cfd_python PRIVATE ${PYTHON3_STABLE_LIB}) + message(STATUS "Linking against stable ABI library: ${PYTHON3_STABLE_LIB}") else() - # Linux/macOS use .abi3.so for stable ABI - set_target_properties(cfd_python PROPERTIES SUFFIX ".abi3.so") + message(FATAL_ERROR "Could not find python3.lib for stable ABI linking") endif() - message(STATUS "Building with stable ABI (abi3) for Python 3.8+") + message(STATUS "Building Windows stable ABI extension") +else() + # Use Python_add_library for Unix or non-stable-ABI builds + Python_add_library(cfd_python MODULE WITH_SOABI + src/cfd_python.c + ) + + set_target_properties(cfd_python PROPERTIES + C_STANDARD 11 + ) + + if(CFD_USE_STABLE_ABI) + target_compile_definitions(cfd_python PRIVATE Py_LIMITED_API=0x03080000) + set_target_properties(cfd_python PROPERTIES SUFFIX ".abi3.so") + message(STATUS "Building Unix stable ABI extension") + endif() endif() target_include_directories(cfd_python PRIVATE