Tutorial: Building a C++ package#
This example shows how to build a C++ project with CMake and use it together with pixi-build
.
To read more about how building packages work with pixi see the Getting Started guide.
We'll start off by creating a project that use nanobind to build Python bindings. That we can also test using pixi. We'll later combine this example together with a Python package.
Creating a new project#
To get started, create a new project with pixi:
This should give you the basic pixi.toml
to get started.
We'll now create the following source directory structure:
Creating the project files#
Next up we'll create the:
pixi.toml
file that will be used to configure pixi.CMakeLists.txt
file that will be used to build the bindings.src/bindings.cpp
file that will contain the bindings.
The pixi.toml
file#
Use the following pixi.toml
file, you can hover over the annotations to see why each step was added.
[workspace]
channels = ["https://prefix.dev/conda-forge"]
platforms = ["osx-arm64", "linux-64", "win-64"]
preview = ["pixi-build"] # (1)!
[dependencies] # (2)!
python = ">=3.12, <3.13"
python_bindings = { path = "." }
[tasks]
start = "python -c 'import python_bindings as b; print(b.add(1, 2))'" # (3)!
[package] # (4)!
name = "python_bindings"
version = "0.1.0"
[build-system]
build-backend = { name = "pixi-build-cmake", version = "*" } # (5)!
channels = [
"https://prefix.dev/pixi-build-backends",
"https://prefix.dev/conda-forge",
]
[host-dependencies]
cmake = ">=3.20, <3.27" # (8)!
nanobind = ">=2.4.0, <2.5.0" # (6)!
python_abi = ">=3.12, <3.13" # (7)!
- Add the preview feature
pixi-build
that enables pixi to build the package. - These are the workspace dependencies, and we add a reference to our own package.
- Let's add a task that will run our test, for this we require a python interpreter.
- This is where we specify the package name and version.
This section denotes that there is both a workspace and a package defined by this
pixi.toml
file. - We use
pixi-build-cmake
as the build-system, so that we get the backend to build cmake packages. - We use the nanobind package to build our bindings.
- We need python to build the bindings, so we add a host dependency on the
python_abi
package. - We override the cmake version to ensure it matches our
CMakeLists.txt
file.
The CMakeLists.txt
file#
Next lets add the CMakeList.txt
file:
cmake_minimum_required(VERSION 3.20...3.27)
project(python_bindings)
find_package(Python 3.8 COMPONENTS Interpreter Development.Module REQUIRED) # (1)!
execute_process(
COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir
OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT
) # (2)!
execute_process(
COMMAND ${Python_EXECUTABLE} -c "import sysconfig; print(sysconfig.get_path('purelib'))"
OUTPUT_VARIABLE PYTHON_SITE_PACKAGES
OUTPUT_STRIP_TRAILING_WHITESPACE
) # (3)!
find_package(nanobind CONFIG REQUIRED) # (4)!
nanobind_add_module(${PROJECT_NAME} src/bindings.cpp) # (5)!
install( # (6)!
TARGETS ${PROJECT_NAME}
EXPORT ${PROJECT_NAME}Targets
LIBRARY DESTINATION ${PYTHON_SITE_PACKAGES}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${BINDIR}
)
- Find
python
, this actually finds anything above 3.8, but we are using 3.8 as a minimum version. - Because we are using
python
in a conda environment we need to query the python interpreter to find thenanobind
package. - Because we want to make the installation directory independent of the python version, we query the python
site-packages
directory. - Find the installed nanobind package.
- Use our bindings file as the source file.
- Install the bindings into the designated prefix.
The src/bindings.cpp
file#
Next lets add the src/bindings.cpp
file, this one is quite simple:
#include <nanobind/nanobind.h>
int add(int a, int b) { return a + b; } // (1)!
NB_MODULE(python_bindings, m)
{
m.def("add", &add); // (2)!
}
- We define a function that will be used to add two numbers together.
- We bind this function to the python module using the
nanobind
package.
Testing if everything works#
Now that we have created the files we can test if everything works:
This command builds the bindings, installs them and then runs the test
task.
Conclusion#
In this tutorial, we created a pixi package using C++. It can be used as-is, to upload to a conda channel. In another tutorial we will learn how to add multiple pixi packages to the same workspace and let one pixi package use another.
Thanks for reading! Happy Coding 🚀
Any questions? Feel free to reach out or share this tutorial on X, join our Discord, e-mail us or follow our GitHub.