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.