Skip to content

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.

Warning

pixi-build is a preview feature, and will change until it is stabilized. Please keep that in mind when you use it for your projects.

Creating a new project#

To get started, create a new project with pixi:

pixi init python_bindings

This should give you the basic pixi.toml to get started.

We'll now create the following source directory structure:

python_bindings/
├── CMakeLists.txt
├── pixi.toml
├── .gitignore
└── src
    └── bindings.cpp

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/pixi-build-backends",
  "https://prefix.dev/conda-forge",
]
platforms = ["osx-arm64", "osx-64", "linux-64", "win-64"]
preview = ["pixi-build"] 

[dependencies] 
python = "3.12.*"
python_bindings = { path = "." }

[tasks]
start = "python -c 'import python_bindings as b; print(b.add(1, 2))'" 

[package] 
name = "python_bindings"
version = "0.1.0"

[package.build]
backend = { name = "pixi-build-cmake", version = "0.1.*" } 
[package.build.configuration]
extra-args = ["-DCMAKE_BUILD_TYPE=Release"] 

[package.host-dependencies]
cmake = "3.20.*"   
nanobind = "2.4.*" 
python = "3.12.*"  

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) 

execute_process(
  COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir
  OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT
) 

execute_process(
    COMMAND ${Python_EXECUTABLE} -c "import sysconfig; print(sysconfig.get_path('purelib'))"
    OUTPUT_VARIABLE PYTHON_SITE_PACKAGES
    OUTPUT_STRIP_TRAILING_WHITESPACE
) 

find_package(nanobind CONFIG REQUIRED) 

nanobind_add_module(${PROJECT_NAME} src/bindings.cpp) 

install( 
    TARGETS ${PROJECT_NAME}
    EXPORT ${PROJECT_NAME}Targets
    LIBRARY DESTINATION ${PYTHON_SITE_PACKAGES}
    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${BINDIR}
)

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

NB_MODULE(python_bindings, m)
{
    m.def("add", &add); 
}

Testing if everything works#

Now that we have created the files we can test if everything works:

$ pixi run start
3

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.