Setting up a build system¶
This section assumes that you have followed the instructions to install nanobind. The easiest way to compile a nanobind-based extension involves a CMake-based build system. Other build systems can likely be used as well, but they are not officially supported. (The first section of the CMake API reference mentions some alternatives.)
Here, we will create a new package from scratch. If you already have an existing CMake build system, it should be straightforward to merge some of the following snippets into it.
Preliminaries¶
Begin by creating a new file named CMakeLists.txt
in the root directory of
your project. It should start with the following lines that declare a project
name and tested CMake version range. The third line line searches for Python >=
3.8 including the Development.Module
component required by nanobind. The
name of this module changed across CMake versions, hence the additional
conditional check.
cmake_minimum_required(VERSION 3.15...3.27)
project(my_project) # Replace 'my_project' with the name of your project
if (CMAKE_VERSION VERSION_LESS 3.18)
set(DEV_MODULE Development)
else()
set(DEV_MODULE Development.Module)
endif()
find_package(Python 3.8 COMPONENTS Interpreter ${DEV_MODULE} REQUIRED)
Add the following lines below. They configure CMake to perform an optimized release build by default unless another build type is specified. Without this addition, binding code may run slowly and produce large binaries.
if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
endif()
Finding nanobind¶
Next, we must inform CMake about the presence of nanobind so that it can load the functionality needed to compile extension modules. The details of this step depend on how you installed nanobind, in the previous section.
If you installed nanobind as a Pip or Conda package, append the following lines at the end of
CMakeLists.txt
. They query the package to determine its installation path and then import it.# Detect the installed nanobind package and import it into CMake execute_process( COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) find_package(nanobind CONFIG REQUIRED)
If you installed nanobind as a Git submodule, append the following lines at the end of
CMakeLists.txt
to point CMake to the directory where nanobind is checked out.add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/ext/nanobind)
Building an extension¶
Finally, we are ready to build an extension! Append the following line at the end of
CMakeLists.txt
. It will compile a new extension named my_ext
from the
source code contained in the file my_ext.cpp
.
nanobind_add_module(my_ext my_ext.cpp)
nanobind_add_module()
resembles standard CMake commands like
add_executable()
and add_library()
. Any number of source code and
header files can be declared when the extension is more complex and spread out
over multiple files.
Note
One opinionated choice of nanobind_add_module()
is that it
optimizes the size of the extension by default (i.e., -Os
is passed to
the compiler regardless of the project-wide settings). You must specify the
NOMINSIZE
parameter to the command to disable this behavior and, e.g.,
optimize extension code for speed (i.e., -O3
):
nanobind_add_module(my_ext NOMINSIZE my_ext.cpp)
The default is chosen this way since extension code usually wraps existing C++ libraries, in which the main computation takes place. Optimizing the bindings for speed does not measurably improve performance, but it does make the bindings significantly larger.
If you observe slowdowns when porting a pybind11 extension, or if your
extension performs significant amounts of work within the binding layer,
then you may want to experiment with passing the NOMINSIZE
parameter.
The next section will review the contents of example module
implementation in my_ext.cpp
.