Demo Project

This walkthrough will take you through all the steps to create a simple robotpy-build project that autogenerates a working wrapper around a C++ class and it’s methods.

This demo should work on Linux, OSX, and Windows. Make sure you have robotpy_build installed first!

Note

This demo shows building a python wrapper around C++ code that is self-contained in this project. However, robotpy-build also supports wrapping externally compiled libraries and inter-package shared library dependencies.

Files + descriptions

Note

If you’re lazy, the files for this demo are checked into the robotpy-build repository at examples/demo.

All of the content required for this demo is contained inline below. Let’s start by creating a new directory for your project, and we’re going to create the following files:

setup.py

Traditionally python projects required a setup.py that contained all the information and logic needed to compile/install the project. Every project that uses robotpy-build has an identical setup.py that looks like this:

#!/usr/bin/env python3
from robotpy_build.setup import setup

setup()

pyproject.toml

Projects that use robotpy-build must add a pyproject.toml to the root of their project as specified in PEP 518. This file is used to configure your project.

Comments describing the function of each section can be found inline below.


# This section tells pip to install robotpy-build before starting a build
[build-system]
requires = ["robotpy-build"]

# Tells robotpy-build where to place autogenerated metadata
[tool.robotpy-build]
base_package = "rpydemo"

# This section configures the 'rpydemo' python package. Multiple
# sections are possible to build multiple packages
[tool.robotpy-build.wrappers."rpydemo"]
name = "rpydemo"

# C++ source files to compile, path is relative to the root of the project
sources = [
    "rpydemo/src/demo.cpp",
    "rpydemo/src/main.cpp"
]

# This is a directory that can be used to customize the autogenerated
# C++ code
# -> autogenerate those files via `robotpy-build create-gen`
generation_data = "gen"

# This tells robotpy-build to parse include/demo.h and autogenerate pybind11
# wrappers for the contents of the header.
# -> autogenerate this via `robotpy-build scan-headers`
[tool.robotpy-build.wrappers."rpydemo".autogen_headers]
demo = "demo.h"


# Standard python package metadata
[tool.robotpy-build.metadata]
name = "robotpy-build-demo"
description = "robotpy-build demo program"
author = "RobotPy Development Team"
author_email = "robotpy@googlegroups.com"
url = "https://github.com/robotpy/robotpy-build"
license = "BSD-3-Clause"
install_requires = []

See also

For detailed information about the contents of pyproject.toml see setup.py and pyproject.toml.

rpydemo/__init__.py

# file is empty for now

rpydemo/src/demo.cpp

This is the (very simple) C++ code that we will wrap so that it can be called from python.


#include "demo.h"

int add2(int x) {
    return x + 2;
}

void DemoClass::setX(int x) {
    m_x = x;
}

int DemoClass::getX() const {
    return m_x;
}

rpydemo/include/demo.h

This is the C++ header file for the code that we’re wrapping. In pyproject.toml we told robotpy-build to parse this file and autogenerate wrappers for it.

For simple C++ code such as this, autogeneration will ‘just work’ and no other customization is required. However, certain C++ code (templates and sometimes code that depends on templated types, and other complex circumstances) will require providing customization in a YAML file.


#pragma once

/** Adds 2 to the first parameter and returns it */
int add2(int x);

/**
    Doxygen documentation is automatically added to your python objects
    when the bindings are autogenerated.
*/
class DemoClass {
public:

    /** Sets X */
    void setX(int x);

    /** Gets X */
    int getX() const;

private:
    int m_x = 0;
};

rpydemo/src/main.cpp

Finally, you need to define your pybind11 python module. Custom pybind11 projects would use a PYBIND11_MODULE macro to define a module, but it’s easier to use the RPYBUILD_PYBIND11_MODULE macro which automatically sets the module name when robotpy-build compiles the file.

#include <rpygen_wrapper.hpp>

RPYBUILD_PYBIND11_MODULE(m) { initWrapper(m); }

Note

If you wanted to add your own handwritten pybind11 code here, you can add it in addition to the initWrapper call made here. See the pybind11 documentation for more details.

Install the project

When developing a new project, it’s easiest to just install in ‘develop’ mode which will build/install everything in the currect directory.

$ python3 setup.py develop

Note

When using develop mode for robotpy-build projects, it is recommended to invoke setup.py directly instead of using pip3 install -e

Adjust the project

As we’ve currently built the project, the CPython extension will be built as rpydemo._rpydemo. For example:

>>> from rpydemo._rpydemo import DemoClass
>>> DemoClass
<class 'rpydemo._rpydemo.DemoClass'>

While that works, we really would like users to be able to access our module directly by importing them into __init__.py. There’s a robotpy_build command for generating the contents of __init__.py:

$ python -m robotpy_build create-imports rpydemo rpydemo._rpydemo

The output from this you can put into your rpydemo/__init__.py. It’ll look like this:

# autogenerated by 'robotpy-build create-imports rpydemo rpydemo._rpydemo'
from ._rpydemo import DemoClass, add2

__all__ = ["DemoClass", "add2"]

Now when we put this in our __init__.py, that allows this to work instead:

>>> from rpydemo import DemoClass
>>> DemoClass
<class 'rpydemo._rpydemo.DemoClass'>

Trying out the project

Alright, now that all the pieces are assembled, we can try out our project:

>>> import rpydemo
>>> rpydemo.add2(2)
4
>>> d = rpydemo.DemoClass()
>>> d.setX(2)
>>> d.getX()
2

More Examples

The integration tests in tests/cpp contains a python project that contains several autogenerated wrappers packages and various customizations.