diff --git a/.github/workflows/build_all_wheels.yml b/.github/workflows/build_all_wheels.yml index ebffbcd43c..b08989ca14 100644 --- a/.github/workflows/build_all_wheels.yml +++ b/.github/workflows/build_all_wheels.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: - python-version: ['3.11', '3.12', '3.13'] + python-version: ['3.11'] outputs: version: ${{ steps.configure.outputs.version }} @@ -91,7 +91,7 @@ jobs: runs-on: macos-15 strategy: matrix: - python-version: ['3.11', '3.12', '3.13'] + python-version: ['3.11'] outputs: version: ${{ steps.configure.outputs.version }} @@ -173,7 +173,7 @@ jobs: strategy: matrix: - python: ["cp311", "cp312", "cp313"] + python: ["cp311"] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 00e0a7fb35..4b2a3e2409 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -75,7 +75,7 @@ jobs: id: configure run: | cmake -G "Visual Studio 17 2022" . --preset ci-msvc-windows-release -LAH ` - -DPython3_ROOT_DIR=$env:pythonLocation + -DPython3_ROOT_DIR=$env:pythonLocation -DOPENSIM_USE_PYTHON_SABI=OFF $env:match = cmake -N -L build/ci-msvc-windows-release | Select-String -Pattern OPENSIM_QUALIFIED_VERSION $version = $env:match.split('=')[1] @@ -100,6 +100,24 @@ jobs: Copy-Item -Path "${{ steps.configure.outputs.CMAKE_INSTALL_PREFIX }}" -Destination "opensim-core-${{ steps.configure.outputs.version }}" -Recurse 7z a "opensim-core-${{ steps.configure.outputs.version }}.zip" "opensim-core-${{ steps.configure.outputs.version }}" + - name: Build wheel + run: | + cd ${{ steps.configure.outputs.CMAKE_INSTALL_PREFIX }}/sdk/Python + python3 setup.py bdist_wheel + + - name: Capture wheel path + id: wheel + run: | + cd ${{ steps.configure.outputs.CMAKE_INSTALL_PREFIX }}/sdk/Python/dist + $wheel = (Get-ChildItem -Filter "opensim*.whl" -File | Select-Object -First 1).Name + "path=$wheel" >> $env:GITHUB_OUTPUT + + - name: Upload Windows wheel + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.wheel.outputs.path }} + path: ${{ steps.configure.outputs.CMAKE_INSTALL_PREFIX }}/sdk/Python/dist/${{ steps.wheel.outputs.path }} + - name: Upload opensim-core uses: actions/upload-artifact@v4 with: @@ -162,7 +180,7 @@ jobs: id: configure run: | cmake -S $GITHUB_WORKSPACE --preset ci-make-macos-release -LAH \ - -DSWIG_EXECUTABLE=$HOME/swig/bin/swig + -DSWIG_EXECUTABLE=$HOME/swig/bin/swig -DOPENSIM_USE_PYTHON_SABI=OFF VERSION=`cmake -N -L $GITHUB_WORKSPACE/build/ci-make-macos-release | grep OPENSIM_QUALIFIED_VERSION | cut -d "=" -f2` echo "version=$VERSION" >> $GITHUB_OUTPUT @@ -281,7 +299,7 @@ jobs: - name: Configure opensim-core id: configure run: | - cmake -S $GITHUB_WORKSPACE --preset ci-make-linux-release -LAH + cmake -S $GITHUB_WORKSPACE --preset ci-make-linux-release -DPython3_ROOT_DIR=/opt/python/cp311-cp311 -DOPENSIM_USE_PYTHON_SABI=OFF -LAH VERSION=`cmake -N -L $GITHUB_WORKSPACE/build/ci-make-linux-release | grep OPENSIM_QUALIFIED_VERSION | cut -d "=" -f2` echo "version=$VERSION" >> $GITHUB_OUTPUT diff --git a/Bindings/CMakeLists.txt b/Bindings/CMakeLists.txt index 7d189f7865..9b460aed0c 100644 --- a/Bindings/CMakeLists.txt +++ b/Bindings/CMakeLists.txt @@ -1,5 +1,5 @@ if(BUILD_PYTHON_WRAPPING OR BUILD_JAVA_WRAPPING) - find_package(SWIG 4.1.0 REQUIRED) + find_package(SWIG 4.4.1 REQUIRED) endif() # Flags are both Python and Java bindings will use. diff --git a/Bindings/Python/CMakeLists.txt b/Bindings/Python/CMakeLists.txt index 7cae151d36..653bcbb3bc 100644 --- a/Bindings/Python/CMakeLists.txt +++ b/Bindings/Python/CMakeLists.txt @@ -16,6 +16,7 @@ endif() # We set these COMPILE_OPTIONS in the root CMakeLists.txt. set_directory_properties(PROPERTIES COMPILE_OPTIONS "") +set(OPENSIM_PYTHON_LIMITED_API_HEX 0x030A0000) # Helper macros. # ============== @@ -69,7 +70,11 @@ macro(OpenSimAddPythonModule) set(_output_header_file "${_output_file_prefix}.h") set(_interface_file "${CMAKE_CURRENT_SOURCE_DIR}/swig/python_${OSIMSWIGPY_MODULE}.i") - + if (OPENSIM_USE_PYTHON_SABI) + set_property(SOURCE ${_output_cxx_file} PROPERTY + COMPILE_DEFINITIONS "Py_LIMITED_API=${OPENSIM_PYTHON_LIMITED_API_HEX}" + ) + endif() # We run swig once to get dependencies and then again to actually generate # the wrappers. This variable holds the parts of the swig command that # are shared between both invocations. @@ -132,12 +137,14 @@ macro(OpenSimAddPythonModule) /wd4996 # Disable warning 4996: deprecated functions. /wd4114 # Disable warning 4114: "const const T" > - ) + ) target_compile_definitions(${_libname} PRIVATE # SWIG_PYTHON was added to account for generated cxx files # failing to compile due to operators defined in simbody (removed # by this symbol) -Ayman 3/25 SWIG_PYTHON + # Pass Limited_API version to the compiler if needed + $<$:-DPy_LIMITED_API=${OPENSIM_PYTHON_LIMITED_API_HEX}> ) target_include_directories(${_libname} PRIVATE @@ -161,11 +168,15 @@ macro(OpenSimAddPythonModule) if("${PYTHON_LDSHARED}" MATCHES "dynamic_lookup") set_target_properties(${_libname} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") - else() - target_link_libraries(${_libname} ${Python3_LIBRARIES}) + elseif(WIN32) + if (OPENSIM_USE_PYTHON_SABI) + target_link_libraries(${_libname} PRIVATE Python3::SABIModule) + else() + target_link_libraries(${_libname} PRIVATE Python3::Module) + endif() endif() - target_link_libraries(${_libname} osimTools osimExampleComponents osimMoco) + target_link_libraries(${_libname} PUBLIC osimTools osimExampleComponents osimMoco) # Set target properties for various platforms. # -------------------------------------------- @@ -177,9 +188,13 @@ macro(OpenSimAddPythonModule) ) if(WIN32) set_target_properties(${_libname} PROPERTIES SUFFIX ".pyd") - elseif(APPLE) - # Defaults to .dylib; change to .so. - set_target_properties(${_libname} PROPERTIES SUFFIX ".so") + else() + # Defaults to .dylib; change to .so on MacOS, also on both mac, linux add abi3 for SABI + if (OPENSIM_USE_PYTHON_SABI) + set_target_properties(${_libname} PROPERTIES SUFFIX ".abi3.so") + else() + set_target_properties(${_libname} PROPERTIES SUFFIX ".so") + endif() endif() # RPATH: We always set a relative RPATH but only use an absolute RPATH if diff --git a/Bindings/Python/setup.py b/Bindings/Python/setup.py index b5225f28ac..823ded3cbd 100644 --- a/Bindings/Python/setup.py +++ b/Bindings/Python/setup.py @@ -36,6 +36,9 @@ def has_ext_modules(foo): setup(name='opensim', version=__version__, + # The build number is used to differentiate between different builds of the same + # version. Typically on testpypi. py_limited_api is used to specify the Python version + # compatibility for the wheel. If upgrading the Python 3.11, change this to cp311 for example. options={"bdist_wheel": {"build_number": "1"}}, description='OpenSim Simulation Framework', long_description=long_description, diff --git a/CMakeLists.txt b/CMakeLists.txt index b379af9ed7..2a8a61b254 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -433,12 +433,24 @@ if(${BUILD_JAVA_WRAPPING}) endif() if(${BUILD_PYTHON_WRAPPING}) - set(required_python_version 3) - set(Python3_ROOT_DIR "" CACHE PATH "Top level directory containing Python3. For conda environments, this can also be set to the top level directory for a specific environment.") - find_package(Python3 3.6 REQUIRED COMPONENTS Interpreter Development.Module NumPy) + # Default OPENSIM_USE_PYTHON_SABI is on and version 3.10 If you want to use the stable ABI, + # you must use Python 3.8 or higher. If actual ABI version needs to be changed then + # modify the value of the Py_LIMITED_API definition in this file. If building wheels, + # you must modify the value of py_limited_api in setup.py as well. + option(OPENSIM_USE_PYTHON_SABI + "Use the stable ABI for Python bindings" ON) + + set(Python3_ROOT_DIR "" CACHE PATH + "Top level directory containing Python3. For conda environments, this can also be set to the top level directory for a specific environment.") + if(${OPENSIM_USE_PYTHON_SABI}) + find_package(Python3 3.10 REQUIRED COMPONENTS Interpreter Development.SABIModule NumPy) + else() + find_package(Python3 3.10 REQUIRED COMPONENTS Interpreter Development.Module NumPy) + endif() message("Python3_FOUND:${Python3_FOUND}") message("Python3_VERSION:${Python3_VERSION}") message("Python3_Development_FOUND:${Python3_Development_FOUND}") + message("Python_Development.SABIModule_FOUND:${Python3_Development_SABIModule_FOUND}") message("Python3_LIBRARIES:${Python3_LIBRARIES}") message("Python3_Numpy_Include:${Python3_NumPy_INCLUDE_DIRS}") message("Python3_Numpy_VERSION:${Python3_NumPy_VERSION}")