Skip to content

Add Python wheel packaging and CI wheel builds#1

Merged
jcschaff merged 9 commits into
mainfrom
python-wheels-and-packaging
Jun 23, 2026
Merged

Add Python wheel packaging and CI wheel builds#1
jcschaff merged 9 commits into
mainfrom
python-wheels-and-packaging

Conversation

@jcschaff

Copy link
Copy Markdown
Member

Summary

Adds a Python packaging path so the moving-boundary solver can be distributed as installable wheels (and, when you choose to enable it, published to PyPI). Inspired by the sibling vcell-fvsolver setup.

  • pyproject.tomlscikit-build-core PEP 517 backend that drives the existing CMake build into a wheel. The wheel contains only the vcellmbsolver_py extension + the vcellmbsolver pure-Python wrapper (C++ static lib, CLI binary, and headers are excluded via a python install component + a vcellmbsolver_py-only build target). BUILD_TESTING and OPTION_TARGET_MESSAGING are forced off for wheel builds, and the wheel version is scraped from the project(VCellMovingBoundary VERSION …) line so there's a single source of truth.
  • python/CMakeLists.txt — installs to a relative dir under SKBUILD (wheel) vs. Python3_SITEARCH for a plain cmake --install; tags the Python install rules with COMPONENT python. Plain cmake --install behavior is unchanged.
  • .github/workflows/wheels.ymlcibuildwheel across Linux (manylinux_2_28), macOS (x86_64 + arm64), and Windows (vcpkg) plus an sdist, all uploaded as downloadable artifacts. A publish job does PyPI trusted publishing on v* tags only — dormant until PyPI is configured.
  • README / CLAUDE.md — install-from-wheel, build-locally, and one-time PyPI trusted-publisher setup instructions.

Verified locally (macOS arm64)

  • python -m build --wheel produces vcellmbsolver-1.0.0-cp312-cp312-macosx_*.whl containing exactly the extension + wrapper (no C++ lib/binary/headers).
  • ✅ Installs into a clean venv and import vcellmbsolver_py, vcellmbsolver works (enums/exceptions re-export correctly).
  • python -m build --sdist produces a self-contained sdist — scikit-build-core bundles the vcell-messaging and vcell-expressionparser submodule sources and vendored FronTierLib.

Needs a CI run to validate (cannot be tested locally)

  • Linux manylinux deps: installs hdf5-devel + boost-devel via dnf (EPEL) inside manylinux_2_28. If those packages aren't available/complete (esp. HDF5 C++ bindings), this is the most likely thing to need adjustment — the fallback is a custom manylinux image or Conan, as vcell-fvsolver does.
  • Windows: HDF5/Boost via vcpkg with the Visual Studio generator passed through CIBW_ENVIRONMENT_WINDOWS. Riskiest platform; may need iteration.
  • Submodule checkout in CI reuses the existing SUBMODULE_TOKEN secret pattern from build-and-release.yml.

Not included

  • Actual PyPI publishing is gated behind the one-time trusted-publisher setup documented in the README; nothing publishes on normal pushes/PRs.
  • No change to the existing build-and-release.yml (native binary artifacts) — this workflow is additive and wheel-only.

🤖 Generated with Claude Code

jcschaff and others added 3 commits June 22, 2026 16:20
Add a scikit-build-core PEP 517 build (pyproject.toml) that drives the
existing CMake build to produce an installable wheel containing only the
vcellmbsolver_py extension and the vcellmbsolver wrapper. The wheel build
limits CMake to the vcellmbsolver_py target and the "python" install
component, and forces BUILD_TESTING/OPTION_TARGET_MESSAGING off.

python/CMakeLists.txt installs to a relative dir under SKBUILD (wheel) and
to Python3_SITEARCH otherwise, tagging the python install rules with a
COMPONENT so the C++ lib/binary/headers stay out of the wheel.

Add .github/workflows/wheels.yml: cibuildwheel across Linux/macOS/Windows
plus an sdist, uploaded as artifacts; a publish job does PyPI trusted
publishing on v* tags only (dormant until PyPI is configured).

Document install/build/publish in README and add a packaging note to
CLAUDE.md. Wheel version is scraped from the CMake project() version.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Match the sibling vcell-fvsolver / pyvcell_fvsolver convention: ship the
bindings as a package whose compiled extension is the private submodule
pyvcell_mbsolver._core, and whose __init__.py is the high-level wrapper.

- pyvcellmbsolver.cpp: PYBIND11_MODULE(vcellmbsolver_py) -> (_core)
- python/vcellmbsolver.py -> python/pyvcell_mbsolver/__init__.py; imports
  the extension via `from . import _core as _mb` and re-exports the API
- python/CMakeLists.txt: build target `_core`, assemble the package under
  build/bin/pyvcell_mbsolver/ (extension + copied __init__.py) so it imports
  with PYTHONPATH=build/bin; install into pyvcell_mbsolver/ (relative under
  SKBUILD, else ${Python3_SITEARCH}/pyvcell_mbsolver)
- pyproject.toml: distribution name pyvcell_mbsolver, build target _core,
  import-check + pytest configured with --import-mode=importlib so the source
  tree does not shadow the built/installed package
- tests: import `from pyvcell_mbsolver import _core as _mb` / the wrapper API
- README/CLAUDE.md updated to the new import names

Verified locally: wheel builds, installs into a clean venv, imports, and the
40-case pytest suite passes against the installed wheel; the native build-tree
ctest (PyVcellMbSolver) also passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Runner matrix now matches vcell-fvsolver: macos-15-intel + macos-15,
  ubuntu-latest + ubuntu-24.04-arm, windows-latest (drops the deprecated/
  scarce macos-13 and macos-14 Intel/arm labels that were stuck queued).
- Use the proven separate-step submodule auth from build-and-release.yml
  (checkout with token, configure insteadOf with SUBMODULE_TOKEN, then
  submodule update) instead of a single condensed step.
- cibuildwheel pinned to v3.4.0 (matches fvsolver) with CIBW_ARCHS=native;
  add manylinux_2_28 aarch64 image for ubuntu-24.04-arm.
- Python range aligned to 3.10-3.13 (requires-python + classifiers + build).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jcschaff jcschaff closed this Jun 22, 2026
@jcschaff jcschaff reopened this Jun 22, 2026
jcschaff and others added 6 commits June 22, 2026 20:52
First real CI run surfaced one root cause per platform:

- Linux: find_package(Python3 ... Development) failed because manylinux ships
  no shared libpython. Switch to Development.Module (headers/SOABI only, the
  correct policy for extension wheels). HDF5/Boost via dnf already worked.
- macOS: configure/compile succeeded but delocate rejected the wheel because
  Homebrew's HDF5 requires a newer minimum macOS than the 11.0 target. Raise
  MACOSX_DEPLOYMENT_TARGET to 15.0 (macOS wheels now require macOS 15+).
- Windows: vcpkg succeeded but CMake's "Visual Studio 17 2022" generator could
  not find VS on windows-latest (windows-2025). Pin the runner to windows-2022.

Verified locally: cmake configure resolves Development.Module (CMake 3.29).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The windows-2022 pin fixed VS detection but surfaced two more issues:

- Replace vcpkg (which built HDF5 + full Boost from source, ~40 min) with
  Conan: prebuilt HDF5 binary from Conan Center and header-only Boost (nothing
  compiles). full_deploy lays packages out predictably; CMake module-mode finds
  are pointed at them via HDF5_ROOT/BOOST_ROOT and the Conan toolchain, and the
  HDF5 DLL dir is added to PATH so delvewheel vendors it. Matches the Conan
  approach used by vcell-fvsolver.
- find_package(Python3 ... Development.Module) does not resolve under
  cibuildwheel's build venv on Windows, so request only Interpreter there and
  let pybind11 locate the Python import library (Linux/macOS keep
  Development.Module).

Linux + macOS already build green; this commit only touches the Windows path
and the WIN32 branch of the Python find.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Conan Center ships no prebuilt HDF5 binaries (recipe-only), so it would
compile HDF5 from source. conda-forge provides genuinely prebuilt HDF5 +
Boost for Windows in ~1-2 min, in a standard <env>/Library layout that the
project's module-mode find_package(HDF5/Boost) consumes via HDF5_ROOT/
BOOST_ROOT. The Library/bin dir is added to PATH so delvewheel vendors the
HDF5/zlib/aec DLLs. Uses the Miniconda preinstalled on windows-2022.

Linux + macOS already build green; this only changes the Windows dep setup.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Passing -DCMAKE_PREFIX_PATH=<conda> replaced the prefix scikit-build-core
sets to locate pybind11, so find_package(pybind11) failed. HDF5_ROOT and
BOOST_ROOT are sufficient for the module-mode HDF5/Boost finds; drop the
CMAKE_PREFIX_PATH override so pybind11 is still discoverable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ools)

pybind11 v3 uses FindPython "new tools" mode, whose pybind11_add_module calls
python3_add_library — a command only defined when the Development.Module
component is found. The earlier Windows-only Interpreter fallback therefore
broke pybind11_add_module ("Unknown CMake command python3_add_library").

The original Windows Development.Module failure was caused by the vcpkg
toolchain interfering with scikit-build-core's Python hints; now that Windows
uses conda-forge (no toolchain file), Development.Module resolves like Linux/
macOS, so require it unconditionally.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The bundled FronTier front-tracking library is Unix/GCC code that does not
compile under MSVC (POINT/boolean clash with the Windows SDK, GCC anonymous
struct tags), and there is no MSVC equivalent of the -fpermissive flag used
for it on Linux/macOS. (vcell-fvsolver builds on Windows only because it does
not depend on FronTier.) Porting FronTier to MSVC is a separate, substantial
effort, so remove Windows from the wheel matrix for now.

Wheels are built for Linux (x86_64 + arm64) and macOS (x86_64 + arm64). Remove
the Windows conda/cibuildwheel setup and document the limitation in README and
CLAUDE.md; Windows users can build under WSL via the Linux wheels.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jcschaff jcschaff merged commit 3f16581 into main Jun 23, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant