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 of c2py). This approach is recommended, since it ensures that both the module and c2py are built with consistent compiler options and linked against the same Python interpreter. As c2py 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 option Update_Python_Bindings is set to ON:

    1. Find the clair-c2py executable.

    2. Regenerate the bindings before compiling the module.

Note

  • clair-c2py automatically uses the compile_commands.json file generated by CMake for the file my_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.