# cmake_cpp_pybind11_tutorial **Repository Path**: laizibi/cmake_cpp_pybind11_tutorial ## Basic Information - **Project Name**: cmake_cpp_pybind11_tutorial - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-06-27 - **Last Updated**: 2024-06-27 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Setting up a C++/Python project with pybind11 and CMake * [Read this in a nicer format on Medium.](https://medium.com/practical-coding/setting-up-a-c-python-project-with-pybind11-and-cmake-8de391494fca) * [Follow my publication 'practical coding' on Medium for more articles like this.](https://medium.com/practical-coding) * [Tutorial on more advanced pybind11 features here.](https://github.com/smrfeld/advanced_pybind11_features) This tutorial shows how to set up a `pybind11` project with `CMake` for wrapping a `C++` library into `Python`. The final result will be: * A `C++` project you can build independent of `pybind11`. * A `Python` library generated from wrapping the `C++` code. * Both using `CMake`. drawing [Image source.](https://www.flickr.com/photos/akira_1972/15689067279/) ## Create a C++ project We will use the outer (current) working directory to build python, and an inner directory called `cpp` to build the `C++` code. First make a C++ directory. ``` mkdir cpp cd cpp ``` Next, we will initialize a C++ project. Two ways (of many more) are: 1. Using `VS Code`. Install the `CMake Tools` extension. Then, bring up the command pallette and select `CMake: Quick start`. Follow the prompts and enter a name - I chose `automobile`. When prompted for library or executable, choose `library`. Your directory should now look like this: ``` cpp/build/ cpp/automobile.cpp cpp/CMakeLists.txt ``` We will separate the source and header files - this is always good practice. In the `cpp` directory, make two new directories: ``` cd cpp mkdir include mkdir src ``` and move the source file: ``` mv automobile.cpp src/ ``` In the include directory, we would like to have a single header to import. This way, we could later simply `#include `. We can organize it as follows: ``` cd cpp/include mkdir automobile_bits touch automobile ``` Finally, let us create a header file in the `cpp/include/automobile_bits` directory: ``` cd cpp/include/automobile_bits touch motorcycle.hpp ``` The final directory structure should now look like this: ``` cpp/build cpp/CMakeLists.txt cpp/include/automobile cpp/include/automobile_bits/motorcycle.hpp cpp/src/motorcycle.cpp ``` 2. Manually create the files and directories, such that the final structure is: ``` cpp/build cpp/CMakeLists.txt cpp/include/automobile cpp/include/automobile_bits/motorcycle.hpp cpp/src/motorcycle.cpp ``` We will need to edit the current `CMakeLists.txt` such that it can find the header and source files. I edited mine to read as follows: ``` cmake_minimum_required(VERSION 3.1) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) project(automobile VERSION 0.1.0) # Include dir include_directories(/usr/local/include) # Src AUX_SOURCE_DIRECTORY(src SRC_FILES) # Headers set(PROJECT_SOURCE_DIR "src") set(PROJECT_INCLUDE_DIR "include/automobile_bits") # Source files set(SOURCE_FILES ${PROJECT_INCLUDE_DIR}/motorcycle.hpp ${PROJECT_SOURCE_DIR}/motorcycle.cpp ) # Set up such that XCode organizes the files correctly source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCE_FILES}) # Add library add_library(automobile SHARED ${SOURCE_FILES}) # Include directories target_include_directories(automobile PRIVATE include/automobile_bits) # Install install(TARGETS automobile DESTINATION lib) # Install the headers install(FILES include/automobile DESTINATION include) # Create base directory install(DIRECTORY include/automobile_bits DESTINATION include) ``` Let's also give the `motorcycle.hpp` and `motorcycle.cpp` files some reasonable content. For the header: ``` #include #ifndef CAR_H #define CAR_H namespace vehicles { class Motorcycle { private: /// Name std::string _name; public: /// Constructor Motorcycle(std::string name); /// Get motorcycle name /// @return Motorcycle name std::string get_name() const; /// Drive the motorcycle void ride() const; }; } #endif ``` and the source: ``` #include "../include/automobile_bits/motorcycle.hpp" #include namespace vehicles { Motorcycle::Motorcycle(std::string name) { _name = name; } std::string Motorcycle::get_name() const { return _name; } void Motorcycle::ride() const { std::cout << "Zoom Zoom" << std::endl; } } ``` Yes! I know they're dumb. Note that we introduced a namespace `vehicles` - this is always a good idea. We also need to have the header file find the actual library. Edit the `include/automobile` file to read: ``` #ifndef AUTOMOBILE_LIBRARY_H #define AUTOMOBILE_LIBRARY_H #include "automobile_bits/motorcycle.hpp" #endif ``` We can now already build the library: 1. Using the command line: ``` cd cpp/build cmake .. make make install ``` 2. Using your favorite IDE, e.g. `XCode`: ``` cd cpp/build cmake .. -GXcode ``` should generate `automobile.xcodeproject` in the `build` directory. Either way, you should get the library to build and install. ## Testing the C++ library Before we go on to wrapping the library into `Python`, let's create a test for the `C++` library (not a real test, just somewhere for us to mess around!). Create a new directory in `cpp`: ``` cd cpp mkdir tests ``` Here we will again set up a `CMake` project for our test. Make the directory structure look as follows: ``` cpp/tests/CMakeLists.txt cpp/tests/src/test_cpp.cpp ``` Edit the `test_cpp.cpp` file to read: ``` #include #include int main() { vehicles::Motorcycle c("Yamaha"); std::cout << "Made a motorcycle called: " << c.get_name() << std::endl; c.ride(); return 0; } ``` and edit the `CMakeLists.txt` file: ``` cmake_minimum_required(VERSION 3.1) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) project(test_cpp) include_directories(/usr/local/include) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/../bin) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/../bin) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/../bin) find_library(AUTOMOBILE_LIB automobile HINTS /usr/local/lib/) add_executable(test_cpp src/test_cpp.cpp) target_link_libraries(test_cpp PUBLIC ${AUTOMOBILE_LIB}) ``` Make and run that bad boy using `XCode` as before, or from the command line: ``` mkdir build cd build cmake .. make cd ../bin ./test_cpp ``` Note that the binary will be in the `bin` directory. The output should be: ``` Made a motorcycle called: Yamaha Zoom Zoom on road: mullholland ``` ## Setting up the Python wrapper Finally, let's get to wrapping the library into a Python. We're moving up a directory! In the main directory, let's make a new directory called `python`. It will hold all the glue code: ``` mkdir python ``` We also need a `CMakeLists.txt` file, with contents: ``` cmake_minimum_required(VERSION 3.1) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() set(CMAKE_CXX_FLAGS "-O3") set(CMAKE_CXX_FLAGS_RELEASE "-O3") project(automobile) include_directories("${CMAKE_SOURCE_DIR}/cpp/include/automobile_bits") include_directories("${CMAKE_SOURCE_DIR}/python") file (GLOB SOURCE_FILES "cpp/src/*.cpp") file (GLOB HEADER_FILES "cpp/include/automobile_bits/*.hpp") file (GLOB PYTHON_FILES "python/*.cpp" "python/*.hpp") # Set up such that XCode organizes the files source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCE_FILES} ${HEADER_FILES} ${PYTHON_FILES} ) find_package(pybind11 REQUIRED) ``` If pybind11 hasn't been installed in the system (e.g.: `conda install -c conda-forge pybind11`), you can use the `pybind11.cmake` file to fetch the package (see `CMakeLists.txt` as an example). ``` pybind11_add_module(automobile ${SOURCE_FILES} ${HEADER_FILES} ${PYTHON_FILES} ) target_link_libraries(automobile PUBLIC) install(TARGETS automobile COMPONENT python LIBRARY DESTINATION "${PYTHON_LIBRARY_DIR}" ) ``` You should be ready to build your `Python` library! Try: ``` mkdir build cd build cmake .. -DPYTHON_LIBRARY_DIR="/path/to/site-packages" -DPYTHON_EXECUTABLE="/path/to/executable/python3" make make install ``` As usual, you could also generate code using a generator for your favorite IDE, e.g. by adding `-GXcode` to the `cmake` command. My paths were: ``` DPYTHON_LIBRARY_DIR="/Users/USERNAME/opt/anaconda3/lib/python3.7/site-packages" DPYTHON_EXECUTABLE="/Users/USERNAME/opt/anaconda3/bin/python3" ``` Note that if you are lazy like me, you can try to add for **testing**: ``` set(PYTHON_LIBRARY_DIR "/Users/USERNAME/opt/anaconda3/lib/python3.7/site-packages") set(PYTHON_EXECUTABLE "/Users/USERNAME/opt/anaconda3/bin/python3") ``` in your `CMakeLists.txt` - obviously not a good trick for production! Fire up `python` (make sure it's the same as you specified in `PYTHON_EXECUTABLE` above) and try: ``` >>> import automobile Traceback (most recent call last): File "", line 1, in ImportError: dynamic module does not define module export function (PyInit_automobile) ``` You got a nice fat error, but that's OK! We didn't write the glue code yet, but at least your `CMake` is working and `Python` can find your library. ## Wrapping the library into Python Now for the actual logic of wrapping the `C++` code into `Python`. It will take place in the `python` directory. First create a file which will define the "module export function" that `python` was complaning about in the last part: ``` touch python/automobile.cpp ``` Give it the following content: ``` #include namespace py = pybind11; void init_motorcycle(py::module &); namespace mcl { PYBIND11_MODULE(automobile, m) { // Optional docstring m.doc() = "Automobile library"; init_motorcycle(m); } } ``` Next, we will define the `init_motorcycle` method that was declared. We will do this in a separate file: ``` touch python/motorcycle.cpp ``` Edit it to read: ``` #include "../cpp/include/automobile_bits/motorcycle.hpp" #include #include namespace py = pybind11; void init_motorcycle(py::module &m) { py::class_(m, "Motorcycle") .def(py::init(), py::arg("name")) .def("get_name", py::overload_cast<>( &vehicles::Motorcycle::get_name, py::const_)) .def("ride", py::overload_cast( &vehicles::Motorcycle::ride, py::const_), py::arg("road")); } ``` I always find the code itself to be the best explanation, but some pointers: - `py::class_(m, "Motorcycle")` defines the class. The `"Motorcycle"` defines the name of the class in `Python` - you could change it if you want! Notice also the appearance of the namespace. - `.def(py::init(), py::arg("name"))` defines the constructor. The `py::arg("name")` allows you to use named arguments in `Python`. - `.def("get_name", py::overload_cast<>( &vehicles::Motorcycle::get_name, py::const_))` wraps the `get_name` method. Note how the `const` declaration is wrapped. - `.def("ride", py::overload_cast( &vehicles::Motorcycle::ride, py::const_), py::arg("road"));` wraps the `ride` method. The arguments to the method are declared in `py::overload_cast` (separated by commas if multiple), and can again be named using `py::arg("road")`. Also note the semicolon at the end - often forgotten, but this should be proper `C++` code. You can now test your library. Run `make` and `make install` again to rebuild and install the library. Fire up `python` and try it: ``` import automobile c = automobile.Motorcycle("Yamaha") print("Made a motorcycle called: %s" % c.get_name()) c.ride("mullholland") ``` should give the same output as before: ``` Made a motorcycle called: Yamaha Zoom Zoom on road: mullholland ``` You could make another test script with those contents, located in a directory `tests/test.py`. ## Conclusion That's it for this tutorial. The nice part about this setup is that you can build your `C++` project in peace from the `cpp` directory, and then at the end in the outer layer worry about wrapping it into `Python`. [You can read about more advanced pybindy11 features in another tutorial here](https://github.com/smrfeld/advanced_pybind11_features).