CMake Integration
Let us consider a simple piece of C++ code:
#include "c2py/c2py.hpp"
/// A wonderful little class
class my_class {
int a, b;
public:
my_class(int a_, int b_) : a(a_), b(b_) {}
int f(int u) const { return u + a; }
};
CMake configuration
A complete CMake configuration file for this module is:
1cmake_minimum_required(VERSION 3.20 FATAL_ERROR)
2project(clairexample VERSION 3.2.0 LANGUAGES C CXX)
3set(CMAKE_CXX_STANDARD 20)
4set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
5include(FetchContent)
6
7# Locate Python and NumPy components
8find_package(Python COMPONENTS Interpreter Development NumPy)
9
10# Fetch the c2py library
11FetchContent_Declare(
12 c2py
13 GIT_REPOSITORY https://github.com/flatironinstitute/c2py
14 GIT_TAG unstable
15 EXCLUDE_FROM_ALL
16)
17FetchContent_MakeAvailable(c2py)
18
19# Build the Python C++ extension module
20Python_add_library(my_module MODULE my_module.cpp)
21target_link_libraries(my_module PRIVATE c2py::c2py)
22
23# clair-c2py: optionally regenerate bindings
24option(Update_Python_Bindings "Use clair python bindings generators" OFF)
25
26if (Update_Python_Bindings)
27 find_program(clair-c2py clair-c2py REQUIRED) # locate clair-c2py in the path or fail
28
29 add_custom_command(
30 COMMAND ${clair-c2py} -p ${PROJECT_BINARY_DIR} my_module.cpp # -p path/to/compile/commands
31 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} # Execute in source directory
32 OUTPUT my_module.wrap.cxx # Generates the .wrap.cxx file
33 DEPENDS my_module.cpp # which depends on the source (hence will be regenerated if it changes)
34 )
35
36 add_custom_target(bindings_generation DEPENDS my_module.wrap.cxx) # We make the module depend on the bindings
37 add_dependencies(my_module bindings_generation) # so that the generation happends before the compilation
38endif()
Brief explanation of the CMake file:
[Lines 1–19] This section provides a standard CMake configuration for building a Python C++ extension.
The
Python_add_library
command declares the extension using CMake’s FindPython module.Note that the
c2py
library is fetched and linked to the module (rather than using any installed version ofc2py
). This approach is recommended, since it ensures that both the module andc2py
are built with consistent compiler options and linked against the same Python interpreter. Asc2py
compiles quickly, it does not add significant overhead to the build process.
[Line 23-end] This is the
clair-c2py
specific part, which is trigged if the optionUpdate_Python_Bindings
is set toON
:Find the
clair-c2py
executable.Regenerate the bindings before compiling the module.
Note
clair-c2py
automatically uses the compile_commands.json file generated by CMake for the filemy_module.cpp
to detect all include flags from the project’s linked libraries (targets).The
-p
option is used to specify the path to the compile_commands.json file (in the build directory).However the tools acts in the source directory, producing some additional
.cxx
source files.An additionnal
my_module.wrap.hxx
file is also generated, only used in multiple module situations, cf multiple_modules.
Compiling
To compile the module, run the following commands:
$ mkdir build
$ cd build
$ cmake .. -DUpdate_Python_Bindings=ON
$ make -j 8
You can then use the module in Python:
>>> import my_module as M
>>> a = M.MyClass(1, 2)
>>> a.f(3)
4
Workflow
This CMakeLists.txt
file is designed to be used in two different modes, depending on the value of the Update_Python_Bindings
option:
User Mode (
Update_Python_Bindings == OFF
) [default]:The compilation uses the bindings already present in the source, as they are included in
my_module.cpp
.clair-c2py
is not required.Any C++20-compliant compiler can be used.
Developer Mode (
Update_Python_Bindings == ON
):The bindings are automatically regenerated using
clair-c2py
when the C++ code or the options TOML file are modified.At the end, the regenerated bindings should be committed along with the rest of the source (they are produced in the source directory).
Any C++20-compliant compiler can still be used to compile the module, even though
clair-c2py
itself relies on Clang and its libraries.