CMake helper for creating cross-platform binary Python packages.
In your CMakeLists.txt, make sure to include the python-cmake-wheel
directory. Then make a call to add_wheel
for your library, such as the
following:
# Adapt according to your checkout directory
set(CMAKE_MODULE_PATH "path/to/python-cmake-wheel")
# Adapt according to your build directory layout
set(WHEEL_DEPLOY_DIRECTORY "${CMAKE_BINARY_DIR}")
include(python-wheel)
# Placeholder statement to create the 'mylib' target.
# In practice, this will probably be pybind11_add_module(...).
add_library(mylib ...)
# Adapt using the following parameters:
# NAME
# Wheel name. Defaults to extension module cmake target name.
# VERSION
# Python package version.
# AUTHOR
# Package author name.
# EMAIL
# Package author email address.
# URL
# Package website.
# PYTHON_REQUIRES
# Python version requirement. Default: >=3.8
# DESCRIPTION
# Python package short description.
# DEPLOY_FILES
# Additional files that should be packaged with your wheel.
# TARGET_DEPENDENCIES
# CMake targets which belong into the same wheel.
# MODULE_DEPENDENCIES
# Python module dependencies (requirements.txt content)
# SCRIPTS
# Additional scripts that should be part of the wheel.
# PYTHON_PACKAGE_DIRS
# Paths to directories of additional Python packages, which
# are bundled with the wheel. This could for example contain
# pybind11 binding module wrappers.
# SUBMODULES
# Any pybind11 submodules must be listed here to support imports like
# "from mod.sub import x". A nested submodule must be listed like
# "sub.subsub". Parent submodules must be listed explicitly.
add_wheel(mylib-python-bindings
NAME mylib
VERSION "0.0.1"
AUTHOR "Bob Ross"
EMAIL "[email protected]"
URL "http://python.org"
PYTHON_REQUIRES ">=3.8"
DESCRIPTION "Binary Python wheel."
DEPLOY_FILES "MY_LICENSE.txt"
TARGET_DEPENDENCIES
dependency-lib
MODULE_DEPENDENCIES
pypi-dependency1 pypi-dependency2
SCRIPTS
/path/to/python/script1
/path/to/python/script2
PYTHON_PACKAGE_DIRS
/path/to/python/project1
/path/to/python/project2
)
The add_wheel
command will create a temporary setup.py
for your project in the build folder, which bundles the necessary files. The execution of this setup.py
is attached to the custom target wheelname-setup-py
. It will be executed when you run cmake --build .
in your build directory.
Note: On macOS, when the MACOSX_DEPLOYMENT_TARGET
env is set, the wheel will be
tagged with the indicated deployment target version.
The python-wheel.cmake
include also provides a function that lets you easily add a cmake test for your wheel: add_wheel_test(...)
.
This function can be used as follows:
add_wheel_test(mylib-test
WORKING_DIRECTORY
# Working directory where the test commands should be executed
"${CMAKE_CURRENT_LIST_DIR}"
COMMANDS
# Two types of commands are available:
# -f (--foreground) are synchronous test tasks. They are executed
# within a clean temporary python environment, in which all
# wheels from your current WHEEL_DEPLOY_DIRECTORY are installed.
# -b (--background) are asynchronous background services that need
# to run while the synchronous tasks are running, for example
# to implement an integration test. They will be killed when
# all synchronous tasks are finished.
# Note: You can specify an arbitrary number of commands for
# both types. They are launched in the specified order, so
# make sure to put a background service before a foreground
# test script which depends on it.
-b "${CMAKE_BINARY_DIR}/a-service"
-f "pytest test.py"
)
This repository also provides several utilities to facilitate additional wheel deployment steps that are needed on macOS and Linux.
For CI jobs, this repo provides the following docker images:
manylinux-cpp17-py3.9-x86_64
manylinux-cpp17-py3.10-x86_64
manylinux-cpp17-py3.11-x86_64
manylinux-cpp17-py3.12-x86_64
This images are based on GLIBC 2.28, so e.g. the minimum Ubuntu version for wheels from your CI will be 21.04.
Note: aarch64
images are not yet deployed. Let us know if you need them!
You may use a Github Actions Snippet like this to build your wheels:
jobs:
build-manylinux:
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]
runs-on: ubuntu-latest
container: ghcr.io/klebert-engineering/manylinux-cpp17-py${{ matrix.python-version }}-x86_64:2024.1
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Configure and Build
run: |
mkdir build && cd build
cmake ..
cmake --build .
# Important step: Audit the wheels!
mv bin/wheel bin/wheel-auditme
auditwheel repair bin/wheel-auditme/mapget*.whl -w bin/wheel
- name: Deploy
uses: actions/upload-artifact@v3
with:
name: mapget-py${{ matrix.python-version }}-ubuntu-latest
path: build/**/bin/wheel/*.whl
- name: Test
run: |
cd build
ctest --verbose --no-tests=error
For macOS, this repo provides the repair-wheel-macos.bash
script, which controls
invocations of the delocate-path
tool which bundles dependencies into your wheel.
Use it in your Github action like this:
jobs:
build-macos:
runs-on: macos-13
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]
env:
SCCACHE_GHA_ENABLED: "true"
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
architecture: x64
- name: Build (macOS)
if: matrix.os == 'macos-13'
run: |
python -m pip install delocate
export MACOSX_DEPLOYMENT_TARGET=10.15
mkdir build && cd build
cmake ..
cmake --build .
# Important step: Audit the wheels!
mv bin/wheel bin/wheel-auditme # Same as on Linux
./_deps/python-cmake-wheel-src/repair-wheel-macos.bash \
"$(pwd)"/bin/wheel-auditme/mapget*.whl \
"$(pwd)"/bin/wheel mapget
- name: Deploy
uses: actions/upload-artifact@v3
with:
name: mapget-py${{ matrix.python-version }}-macos-13
path: build/**/bin/wheel/*.whl
- name: Test
run: |
cd build
ctest --verbose --no-tests=error
No special utilties/audit steps are needed when building on Windows.