CMake FindPython#
When building Python packages, it is crucial that CMake locates the appropriate version of Python. To this end, py-build-cmake sets the necessary hints and artifacts for CMake’s FindPython module.
It is important to differentiate between Python installations for use during the
build process (e.g. for code generation) and Python installations that are
compatible with the target system that you’ll be building for (e.g. the location
of Python.h
when building extension modules).
Failure to keep these two kinds of Python installations separate may result in
build failures, or worse: packages that are silently broken or that are not
portable.
Locating a Python interpreter to use during the build process#
The first use case is invoking a Python interpreter to perform certain code generation steps during the CMake build process. This is an interpreter that is compatible with the build system, and will usually be the same interpreter that was used to invoke py-build-cmake.
To locate such an interpreter, look for the Interpreter
component of the
FindPython module only:
find_package(Python3 REQUIRED COMPONENTS Interpreter)
You can then use the Python executable during the build process using the
Python3_EXECUTABLE
variable:
add_custom_command(
OUTPUT "generated.cpp"
COMMAND ${Python3_EXECUTABLE} codegen.py ...)
Warning
Do not use this Python interpreter for anything related to the target platform
that you’re building for! There are no guarantees that the Python interpreter on
the build system is compatible with the interpreter on the target system
that your package will eventually be installed on. The interpreters may be
different versions of Python, different ABIs (e.g. free-threading,
debug/release), different implementations (e.g. CPython, PyPy, Pyodide), or even
entirely different architectures (when cross-compiling).
If you find yourself querying the sysconfig
or platform
modules of
Python3_EXECUTABLE
to make platform-specific decisions about the build
process, there’s a good chance you’re doing something wrong.
Locating the Python development files for building extension modules#
To build extension modules, you need to locate a Python installation for the target system (the system where your package will eventually be installed). The procedure here is slightly different depending on whether you’re cross-compiling or not:
When performing a native build (no cross-compilation), it is recommended to always look for both the interpreter and the development components, to make sure that the development files are consistent with the interpreter that’s creating the package.
When cross-compiling, the interpreter is generally not usable, so you should look for the development files only. It is assumed that the user sets the necessary paths in
tool.py-build-cmake.cross
.When cross-compiling for a target system that is compatible with the system you’re building on (most notably, macOS systems with universal binaries), locating the interpreter first helps CMake locate the appropriate development files.
To handle these different situations, the following CMake snippet is recommended:
if (CMAKE_CROSSCOMPILING AND NOT (APPLE AND "$ENV{CIBUILDWHEEL}" STREQUAL "1"))
find_package(Python3 REQUIRED COMPONENTS Development.Module)
else()
find_package(Python3 REQUIRED COMPONENTS Interpreter Development.Module)
endif()
If you’re embedding a Python interpreter in your project instead of building
extension modules, you can use the Development.Embed
component instead of
(or in addition to) Development.Module
.
Locating two Python interpreters#
There may be cases where you need Python for code generation during the build
process and where you need the Python development files for building extension
modules for the target system. If you need them in different subdirectories of
your CMake project (separated by add_subdirectory
), you can simply use the
instructions from the previous sections (with one find_package(Python3 ...)
per directory, and without using the GLOBAL
find_package
flag).
If you need both interpreters in the same directory, things are a bit more involved:
# CMake 4.0 or later is required for this technique to work
cmake_minimum_required(VERSION 4.0)
# Look for the development files for the target first
if (CMAKE_CROSSCOMPILING AND NOT (APPLE AND "$ENV{CIBUILDWHEEL}" STREQUAL "1"))
find_package(Python3 REQUIRED COMPONENTS Development.Module)
else()
find_package(Python3 REQUIRED COMPONENTS Interpreter Development.Module)
endif()
# Then look for the interpreter for the build system,
# but return its variables using a different name
set(Python3_ARTIFACTS_PREFIX "_HOST")
find_package(Python3 REQUIRED COMPONENTS Interpreter)
You can then use the unqualified names (e.g. Python3_add_library
or
Python3::Module
) for building extension modules, and the names with the
_HOST
prefix (e.g. Python3_HOST_EXECUTABLE
) for code generation or other
uses during the build process.
If you set Python3_ARTIFACTS_PREFIX
in your CMake script, you must inform
py-build-cmake of this change by setting the
tool.py-build-cmake.cmake.find_python3_build_artifacts_prefix
option to
the same value as the Python3_ARTIFACTS_PREFIX
that is used for locating the
Python interpreter. This ensures that the FindPython hints generated by
py-build-cmake also include the prefix.