From 4036dac9e4359626a40cffb439052c74ed919579 Mon Sep 17 00:00:00 2001 From: Hugh Sorby Date: Sun, 20 Sep 2026 10:39:30 +1200 Subject: [PATCH 1/4] Create stable ABI wheels for distribution. --- .github/workflows/deploy-on-release.yml | 73 ++++++++++------------- CMakeLists.txt | 25 +++++++- cmake/environmentchecks.cmake | 13 +++- src/bindings/python/CMakeLists.txt | 13 ++-- src/bindings/python/cibuildwheel.setup.py | 1 - src/bindings/python/pyproject.toml | 44 ++++++++++++-- 6 files changed, 112 insertions(+), 57 deletions(-) diff --git a/.github/workflows/deploy-on-release.yml b/.github/workflows/deploy-on-release.yml index b17d440ce2..f8284ba8b6 100644 --- a/.github/workflows/deploy-on-release.yml +++ b/.github/workflows/deploy-on-release.yml @@ -275,33 +275,32 @@ jobs: wheels: needs: setup-jobs if: needs.setup-jobs.outputs.allowed-jobs == 'Wheels' || needs.setup-jobs.outputs.allowed-jobs == 'All' - name: ${{ matrix.name }} ${{ matrix.py }} wheel + name: ${{ matrix.name }} Stable ABI wheel runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - os: [macos-15, ubuntu-24.04, ubuntu-24.04-arm, windows-2022, windows-11-arm] - py: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] + os: [macos-15, macos-15-intel, ubuntu-24.04, ubuntu-24.04-arm, windows-2025-vs2026, windows-11-arm] + py: ['3.14'] include: - - name: 'Windows' - os: windows-2022 - - name: 'Windows ARM' - os: windows-11-arm - name: 'Linux' - os: ubuntu-24.04-arm - - name: 'Linux ARM' os: ubuntu-24.04 + - name: 'LinuxARM' + os: ubuntu-24.04-arm - name: 'macOS' os: macos-15 - exclude: - - os: ubuntu-24.04 - py: '3.9' - - os: ubuntu-24.04-arm - py: '3.9' + - name: 'macOSintel' + os: macos-15-intel + - name: 'Windows' + os: windows-2025-vs2026 + arch: amd64 + - name: 'WindowsARM' + os: windows-11-arm + arch: arm64 steps: - name: Check out libCellML - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: ref: ${{ github.event.release.tag_name }} @@ -309,52 +308,44 @@ jobs: id: setup shell: bash run: | - mkdir src/bindings/python/libcellml - cp src/bindings/python/README.rst src/bindings/python/libcellml/README.rst - mv src/bindings/python/cibuildwheel.setup.py src/bindings/python/setup.py + VERSION="${GITHUB_REF#refs/tags/v}" + echo "Setting version as: VERSION=${VERSION}" + if [[ "$OSTYPE" == darwin* ]]; then + sed -i '' "s/__VERSION__/${VERSION}/" src/bindings/python/pyproject.toml + else + sed -i "s/__VERSION__/${VERSION}/" src/bindings/python/pyproject.toml + fi + grep '^version' src/bindings/python/pyproject.toml + v=${{ matrix.py }} - echo "Setting tag as: tag=${GITHUB_REF#refs/tags/}" - echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT echo "Setting build as: build=cp${v/./}-*" echo "build=cp${v/./}-*" >> $GITHUB_OUTPUT - if [[ "macOS" == "${{ matrix.name }}" ]]; then - echo "Setting macos_archs as: macos_archs='x86_64 arm64'" - echo "macos_archs=x86_64 arm64" >> $GITHUB_OUTPUT - echo "macos_deployment_target=14.0" >> $GITHUB_OUTPUT - elif [[ "Windows" == "${{ matrix.name }}" ]]; then - echo "Setting win_arch as: win_arch=amd64" - echo "win_arch=amd64" >> $GITHUB_OUTPUT - elif [[ "Windows ARM" == "${{ matrix.name }}" ]]; then - echo "Setting win_arch as: win_arch=arm64" - echo "win_arch=arm64" >> $GITHUB_OUTPUT - fi - name: Configure MSVC (Windows) if: runner.os == 'Windows' uses: TheMrMilchmann/setup-msvc-dev@v4 with: - arch: x64 + arch: ${{ matrix.arch }} - name: Install SWIG (macOS) if: runner.os == 'macOS' run: brew install swig - name: Build wheels - uses: pypa/cibuildwheel@v3.4 + uses: pypa/cibuildwheel@v4.2 env: #CIBW_BUILD_VERBOSITY: 1 CIBW_ARCHS: auto64 - CIBW_ARCHS_MACOS: ${{ steps.setup.outputs.macos_archs }} CIBW_BUILD: ${{ steps.setup.outputs.build }} - CIBW_TEST_SKIP: "*_arm64" CIBW_BEFORE_ALL_LINUX: yum install -y libxml2-devel || (apk add libxml2-dev && rm /usr/lib/cmake/libxml2/libxml2-config.cmake) CIBW_ENVIRONMENT: > - LIBCELLML_VERSION_TAG=${{ steps.setup.outputs.tag }} - MACOSX_DEPLOYMENT_TARGET=${{ steps.setup.outputs.macos_deployment_target }} - CIBW_REPAIR_WHEEL_COMMAND_MACOS: "" + LIBCELLML_VERSION_TAG=${{ github.event.release.tag_name }} + MACOSX_DEPLOYMENT_TARGET=14.0 CIBW_BEFORE_BUILD_LINUX: pip install renamewheel - CIBW_BEFORE_BUILD_WINDOWS: call "%VSINSTALLDIR%\VC\Auxiliary\Build\vcvarsall.bat" ${{ steps.setup.outputs.win_arch }} && cd src/bindings/python && cmake -S wheel_dependencies -B build-wheel_dependencies -G Ninja && cd build-wheel_dependencies && ninja + CIBW_BEFORE_BUILD_WINDOWS: cd src/bindings/python && cmake -S wheel_dependencies -B build-wheel_dependencies -G Ninja && cd build-wheel_dependencies && ninja CIBW_REPAIR_WHEEL_COMMAND_LINUX: renamewheel -w {dest_dir} {wheel} + CIBW_REPAIR_WHEEL_COMMAND_MACOS: "" + CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: "" with: output-dir: wheelhouse package-dir: src/bindings/python/ @@ -363,13 +354,13 @@ jobs: uses: actions/upload-artifact@v7 with: path: ./wheelhouse/*.whl - name: ${{ matrix.name }}-${{ matrix.py }}-wheel + name: ${{ matrix.name }}-wheel retention-days: ${{ env.ARTIFACT_RETENTION_DAYS }} publish-wheels: if: needs.setup-jobs.outputs.binaries-destination == 'Publish' needs: wheels - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 timeout-minutes: 5 # Docker+network are slow sometimes. permissions: id-token: write diff --git a/CMakeLists.txt b/CMakeLists.txt index 728e7416c1..7b8066d0a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -cmake_minimum_required(VERSION 3.18.0) +cmake_minimum_required(VERSION 3.26.0) if(APPLE) set(CMAKE_OSX_DEPLOYMENT_TARGET 14.0 CACHE STRING "Minimum OS X deployment version.") @@ -32,6 +32,29 @@ foreach(NEW_POLICY ${NEW_POLICIES}) endif() endforeach() + +message(STATUS "----------------------------------------------") +message(STATUS "SKBUILD: ${SKBUILD}") +message(STATUS "Environment Variables: ${ENV}") +get_cmake_property(_vars VARIABLES) +foreach(_v ${_vars}) + if(_v MATCHES "^Python") + message(STATUS "${_v}=${${_v}}") + endif() +endforeach() +foreach(_v Interpreter Module SABIModule Python NumPy) + if (TARGET Python::${_v}) + message(STATUS "Python::${_v}=${Python::${_v}}") + endif() +endforeach() + +message(STATUS "Python_EXECUTABLE=${Python_EXECUTABLE}") +message(STATUS "Python_INCLUDE_DIRS=${Python_INCLUDE_DIRS}") +message(STATUS "Python_SABI_LIBRARIES=${Python_SABI_LIBRARIES}") + +message(STATUS "--------------------- 123 -------------------------") + + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") include(common) diff --git a/cmake/environmentchecks.cmake b/cmake/environmentchecks.cmake index 21ba618b5a..72b7aec537 100644 --- a/cmake/environmentchecks.cmake +++ b/cmake/environmentchecks.cmake @@ -35,7 +35,16 @@ else () test_undefined_symbols_allowed() - find_package(Python ${PREFERRED_PYTHON_VERSION} COMPONENTS Interpreter ${_FIND_PYTHON_DEVELOPMENT_TYPE}) + find_package(Python ${PREFERRED_PYTHON_VERSION} COMPONENTS Interpreter Development.SABIModule Development.Module) + + if (TARGET Python::SABIModule) + set(_PYTHON_LINK_TARGET Python::SABIModule) + elseif (TARGET Python::Module) + message(STATUS "Using backup Python::Module target for linking.") + set(_PYTHON_LINK_TARGET Python::Module) + else() + message(STATUS "Could not find Python::SABIModule or Python::Module target.") + endif() find_program(BUILDCACHE_EXE buildcache) if(NOT BUILDCACHE_EXE) @@ -152,7 +161,7 @@ if(SWIG_EXECUTABLE) set(BINDINGS_AVAILABLE TRUE CACHE INTERNAL "Executable required to generate bindings is available.") endif() -if(BINDINGS_AVAILABLE AND (Python_Development.Module_FOUND OR Python_Development_FOUND)) +if(BINDINGS_AVAILABLE AND (Python_Development.SABIModule_FOUND OR Python_Development_FOUND)) set(PYTHON_BINDINGS_AVAILABLE TRUE CACHE INTERNAL "Requirements for creating Python bindings are available.") endif() diff --git a/src/bindings/python/CMakeLists.txt b/src/bindings/python/CMakeLists.txt index d4f702aa58..42c3f39af7 100644 --- a/src/bindings/python/CMakeLists.txt +++ b/src/bindings/python/CMakeLists.txt @@ -83,6 +83,12 @@ if(MSVC) list(APPEND FINALISE_PYTHON_BINDINGS_ARGUMENTS -DTARGET_FILE_NAME_libxml2=${LIBXML2_DLL_NAMES}) endif() +# if(FIND_PYTHON_DEVELOPMENT_MODULE) +# set(_PYTHON_LINK_TARGET Python::Module) +# else() +# set(_PYTHON_LINK_TARGET Python::Python) +# endif() + set(SWIG_PYTHON_BINDINGS_TARGETS) # Create bindings for each interface file foreach(SWIG_INTERFACE_SRC ${SWIG_INTERFACE_SRCS}) @@ -94,17 +100,12 @@ foreach(SWIG_INTERFACE_SRC ${SWIG_INTERFACE_SRCS}) SOURCES ${SWIG_INTERFACE_SRC} ) target_link_libraries(${MODULE_TARGET} PRIVATE cellml) + target_compile_definitions(${MODULE_TARGET} PRIVATE Py_LIMITED_API=0x030a0000) if(SWIG_VERSION VERSION_GREATER_EQUAL 4.1.0) set_target_properties(${MODULE_TARGET} PROPERTIES SWIG_COMPILE_OPTIONS -flatstaticmethod) endif() - if(FIND_PYTHON_DEVELOPMENT_MODULE) - set(_PYTHON_LINK_TARGET Python::Module) - else() - set(_PYTHON_LINK_TARGET Python::Python) - endif() - if (APPLE) # Specifically allow dynamic lookup, here we are really targeting Python symbols. set_target_properties(${MODULE_TARGET} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") diff --git a/src/bindings/python/cibuildwheel.setup.py b/src/bindings/python/cibuildwheel.setup.py index a832d5e6be..52b9cebc84 100644 --- a/src/bindings/python/cibuildwheel.setup.py +++ b/src/bindings/python/cibuildwheel.setup.py @@ -8,7 +8,6 @@ Intended Audience :: Developers Intended Audience :: Education Intended Audience :: Science/Research -License :: OSI Approved :: Apache Software License Programming Language :: Python Operating System :: Microsoft :: Windows Operating System :: Unix diff --git a/src/bindings/python/pyproject.toml b/src/bindings/python/pyproject.toml index 22bf36b343..4a659763a7 100644 --- a/src/bindings/python/pyproject.toml +++ b/src/bindings/python/pyproject.toml @@ -1,10 +1,42 @@ [build-system] requires = [ - 'setuptools', - 'wheel', - 'scikit-build>=0.12', - 'cmake', - 'ninja', + "wheel", + "scikit-build-core", ] -build-backend = 'setuptools.build_meta' +build-backend = "scikit_build_core.build" +[project] +name = "libCellML" +version = "__VERSION__" +license = { text = "Apache-2.0" } +description = "libCellML a library for the creation, printing, parsing, and manipulation of CellML models." +authors = [{ name = "libCellML developers", email = "libcellml@googlegroups.com" }] +urls = { "homepage" = "https://libcellml.org" } +readme = { file = "README.rst", content-type = "text/x-rst" } +classifiers = [ + "Programming Language :: Python :: 3", + "Intended Audience :: Developers", + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Intended Audience :: Education", + "Intended Audience :: Science/Research", + "Programming Language :: Python", + "Operating System :: Microsoft :: Windows", + "Operating System :: Unix", + "Operating System :: MacOS :: MacOS X", + "Topic :: Software Development :: Libraries :: Python Modules", +] + +[tool.scikit-build] +cmake.args = ["-DUNIT_TESTS=OFF", "-DCOVERAGE=OFF", "-DMEMCHECK=OFF", "-DLLVM_COVERAGE=OFF", "-DCLANG_TIDY=OFF"] +cmake.build-type = "Release" +cmake.source-dir = "../../.." +cmake.version = ">=3.26.1" +install.targets = ["install-wheel"] +ninja.version = ">=1.11" +wheel.py-api = "cp310" + +[[tool.scikit-build.overrides]] +if.platform-system = "^win32" +inherit.cmake.args = "append" +cmake.args = ["-DLibXml2_DIR=C:/Program Files (x86)/libxml2/libxml2-2.9.10/CMake/"] From 2b237332888e4c017f0011e2c59d6277ab8313ab Mon Sep 17 00:00:00 2001 From: Hugh Sorby Date: Tue, 22 Sep 2026 11:02:49 +1200 Subject: [PATCH 2/4] Clean up CMake code. --- CMakeLists.txt | 23 ----------------------- src/bindings/python/CMakeLists.txt | 6 ------ 2 files changed, 29 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b8066d0a3..52cb7b5be5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,29 +32,6 @@ foreach(NEW_POLICY ${NEW_POLICIES}) endif() endforeach() - -message(STATUS "----------------------------------------------") -message(STATUS "SKBUILD: ${SKBUILD}") -message(STATUS "Environment Variables: ${ENV}") -get_cmake_property(_vars VARIABLES) -foreach(_v ${_vars}) - if(_v MATCHES "^Python") - message(STATUS "${_v}=${${_v}}") - endif() -endforeach() -foreach(_v Interpreter Module SABIModule Python NumPy) - if (TARGET Python::${_v}) - message(STATUS "Python::${_v}=${Python::${_v}}") - endif() -endforeach() - -message(STATUS "Python_EXECUTABLE=${Python_EXECUTABLE}") -message(STATUS "Python_INCLUDE_DIRS=${Python_INCLUDE_DIRS}") -message(STATUS "Python_SABI_LIBRARIES=${Python_SABI_LIBRARIES}") - -message(STATUS "--------------------- 123 -------------------------") - - list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") include(common) diff --git a/src/bindings/python/CMakeLists.txt b/src/bindings/python/CMakeLists.txt index 42c3f39af7..3e314d0203 100644 --- a/src/bindings/python/CMakeLists.txt +++ b/src/bindings/python/CMakeLists.txt @@ -83,12 +83,6 @@ if(MSVC) list(APPEND FINALISE_PYTHON_BINDINGS_ARGUMENTS -DTARGET_FILE_NAME_libxml2=${LIBXML2_DLL_NAMES}) endif() -# if(FIND_PYTHON_DEVELOPMENT_MODULE) -# set(_PYTHON_LINK_TARGET Python::Module) -# else() -# set(_PYTHON_LINK_TARGET Python::Python) -# endif() - set(SWIG_PYTHON_BINDINGS_TARGETS) # Create bindings for each interface file foreach(SWIG_INTERFACE_SRC ${SWIG_INTERFACE_SRCS}) From 3d9bab49c040f5862b1c32219c3cd71da1864af4 Mon Sep 17 00:00:00 2001 From: Hugh Sorby Date: Tue, 22 Sep 2026 11:03:06 +1200 Subject: [PATCH 3/4] Check for correct component of Python found. --- cmake/environmentchecks.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/environmentchecks.cmake b/cmake/environmentchecks.cmake index 72b7aec537..2771253910 100644 --- a/cmake/environmentchecks.cmake +++ b/cmake/environmentchecks.cmake @@ -161,7 +161,7 @@ if(SWIG_EXECUTABLE) set(BINDINGS_AVAILABLE TRUE CACHE INTERNAL "Executable required to generate bindings is available.") endif() -if(BINDINGS_AVAILABLE AND (Python_Development.SABIModule_FOUND OR Python_Development_FOUND)) +if(BINDINGS_AVAILABLE AND (Python_Development.SABIModule_FOUND OR Python_Development.Module_FOUND)) set(PYTHON_BINDINGS_AVAILABLE TRUE CACHE INTERNAL "Requirements for creating Python bindings are available.") endif() From 0557a2ba535123e893335bead3c4d404a28f951e Mon Sep 17 00:00:00 2001 From: Hugh Sorby Date: Fri, 25 Sep 2026 23:01:48 +1200 Subject: [PATCH 4/4] Update labels for Python wheels step names in deploy workflow. --- .github/workflows/deploy-on-release.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy-on-release.yml b/.github/workflows/deploy-on-release.yml index f8284ba8b6..b4689a958b 100644 --- a/.github/workflows/deploy-on-release.yml +++ b/.github/workflows/deploy-on-release.yml @@ -283,20 +283,20 @@ jobs: os: [macos-15, macos-15-intel, ubuntu-24.04, ubuntu-24.04-arm, windows-2025-vs2026, windows-11-arm] py: ['3.14'] include: - - name: 'Linux' - os: ubuntu-24.04 - name: 'LinuxARM' os: ubuntu-24.04-arm - - name: 'macOS' + - name: 'LinuxIntel' + os: ubuntu-24.04 + - name: 'macOSARM' os: macos-15 - - name: 'macOSintel' + - name: 'macOSIntel' os: macos-15-intel - - name: 'Windows' - os: windows-2025-vs2026 - arch: amd64 - name: 'WindowsARM' os: windows-11-arm arch: arm64 + - name: 'WindowsIntel' + os: windows-2025-vs2026 + arch: amd64 steps: - name: Check out libCellML