CMake integration

Let us consider a simple piece of C++ code:

// my_module.cpp

#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; }
};

Compiling with clang and the clair plugin

To use the clair plugin, we can use the following CMakeLists.txt file

 1cmake_minimum_required(VERSION 3.20 FATAL_ERROR)
 2project(clairexample VERSION 3.2.0 LANGUAGES C CXX)
 3set(CMAKE_CXX_STANDARD 20)
 4
 5# Find Python, c2py and clair
 6find_package(Python COMPONENTS Interpreter Development NumPy)
 7find_package(c2py REQUIRED)
 8find_package(clair REQUIRED)
 9
10Python_add_library(my_module MODULE my_module.cpp)
11
12# Link to low level c2py and add the -fplugin option using a cmake target. 
13target_link_libraries(my_module PRIVATE c2py::c2py clair::c2py_plugin)

This is a standard CMake configuration file. Python_add_library declares a C++ Python extension using the standard CMake FindPython package. The additional steps required by clair are:

  • [line 7-8] Finds the required cmake packages c2py and clair.

  • [line 13] Compiles against two targets:

    • c2py::c2py: compile and link against c2py

    • clair::c2py_plugin: add the -fplugin instruction to clang

A more general setup

The previous example is fine to develop the project, but needs to be generalized if we want to deploy the code on more platforms, without clang or clair, i.e. just using the bindings without generating them. We assume that the bindings have already been generated and copied into the source directory. The CMakeLists.txt reads:

 1cmake_minimum_required(VERSION 3.20 FATAL_ERROR)
 2project(clairexample VERSION 3.2.0 LANGUAGES C CXX)
 3set(CMAKE_CXX_STANDARD 20)
 4
 5find_package(Python COMPONENTS Interpreter Development NumPy)
 6find_package(c2py REQUIRED)
 7
 8# We can compile with or without the clair plugin
 9option(GENERATE_PYTHON_BINDINGS "Use clair python bindings generators" OFF)
10if (GENERATE_PYTHON_BINDINGS)
11  find_package(clair REQUIRED)
12endif()
13
14# If GENERATE_PYTHON_BINDINGS, we compile my_module.cpp else the bindings my_module.wrap.cxx 
15Python_add_library(my_module MODULE my_module.$<IF:$<BOOL:${GENERATE_PYTHON_BINDINGS}>,cpp,wrap.cxx>)
16
17# Link to low level c2py. 
18# Use the clair plugin if we want to generated the bindings 
19target_link_libraries(my_module PRIVATE c2py::c2py $<$<BOOL:${GENERATE_PYTHON_BINDINGS}>:clair::c2py_plugin>)

We introduce an option GENERATE_PYTHON_BINDINGS so that the code can be used in two ways:

  • If GENERATE_PYTHON_BINDINGS=ON, it is the same as before.

  • If GENERATE_PYTHON_BINDINGS=OFF, it simply compiles the bindings my_module.wrap.cxx from the sources, without any plugin. This compiles with any C++20 compiler (tested actually on clang >=16, gcc >= 12), and requires only Python and c2py.

Note

The CMake syntax $<…> are expression generators, and basically an if expression in CMake.

A variant avoiding the installation of c2py

It is often convenient to avoid the need to install the c2py library completely. As c2py is a very small library, the cost of recompiling it in each project is negligible, but it is often a good strategy to ensure it is compiled with the same compilers, standard libraries, etc.

In order to do this, we can simply modify the CMakeLists.txt file as

 1cmake_minimum_required(VERSION 3.20 FATAL_ERROR)
 2project(clairexample VERSION 3.2.0 LANGUAGES C CXX)
 3set(CMAKE_CXX_STANDARD 20)
 4
 5find_package(Python COMPONENTS Interpreter Development NumPy)
 6
 7include(FetchContent)
 8FetchContent_Declare(
 9  c2py
10  GIT_REPOSITORY https://github.com/flatironinstitute/c2py
11  GIT_TAG        unstable 
12  EXCLUDE_FROM_ALL
13)
14FetchContent_MakeAvailable(c2py )
15
16# We can compile with or without the clair plugin
17option(GENERATE_PYTHON_BINDINGS "Use clair python bindings generators" OFF)
18if (GENERATE_PYTHON_BINDINGS)
19  find_package(clair REQUIRED)
20endif()
21
22# If GENERATE_PYTHON_BINDINGS, we compile my_module.cpp (with the plugin) else the bindings my_module.wrap.cxx 
23Python_add_library(my_module MODULE my_module.$<IF:$<BOOL:${GENERATE_PYTHON_BINDINGS}>,cpp,wrap.cxx>)
24
25# Link to low level c2py. 
26# Use the clair plugin if we want to generated the bindings 
27target_link_libraries(my_module PRIVATE c2py::c2py $<$<BOOL:${GENERATE_PYTHON_BINDINGS}>:clair::c2py_plugin>)

In line 7-14, we use the FetchContent command of CMake to fetch the sources of c2py during the configuration. The rest of the script is unchanged.