Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,14 @@ jobs:
cppwg src/ \
--wrapper_root wrapper/ \
--package_info wrapper/package_info.yaml \
--includes src/*/ \
--std c++17
--includes src/*/ extern/*/ \
--std c++17 \
--logfile cppwg.log

- name: Check for new classes
run: |
cd examples/shapes
cat cppwg.log | grep "Unknown class"

- name: Build Python module
run: |
Expand Down
164 changes: 96 additions & 68 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

# cppwg

Automatically generate PyBind11 Python wrapper code for C++ projects.
Automatically generate pybind11 Python wrapper code for C++ projects.

## Installation

Clone the repository and install cppwg:

```bash
Expand All @@ -15,106 +16,133 @@ pip install .

## Usage

This project generates PyBind11 wrapper code, saving lots of boilerplate in
bigger projects. Please see the [PyBind11 documentation](https://pybind11.readthedocs.io/en/stable/)
for help on the generated wrapper code.

### First Example
```
usage: cppwg [-h] [-w WRAPPER_ROOT] [-p PACKAGE_INFO] [-c CASTXML_BINARY]
[--std STD] [-i [INCLUDES ...]] [-q] [-l [LOGFILE]] [-v]
SOURCE_ROOT

Generate Python Wrappers for C++ code

positional arguments:
SOURCE_ROOT Path to the root directory of the input C++ source
code.

options:
-h, --help show this help message and exit
-w WRAPPER_ROOT, --wrapper_root WRAPPER_ROOT
Path to the output directory for the Pybind11 wrapper
code.
-p PACKAGE_INFO, --package_info PACKAGE_INFO
Path to the package info file.
-c CASTXML_BINARY, --castxml_binary CASTXML_BINARY
Path to the castxml executable.
--std STD C++ standard e.g. c++17.
-i [INCLUDES ...], --includes [INCLUDES ...]
List of paths to include directories.
-q, --quiet Disable informational messages.
-l [LOGFILE], --logfile [LOGFILE]
Output log messages to a file.
-v, --version Print cppwg version.
```

The `examples/shapes/` directory is a full example project, demonstrating how to
generate a Python package `pyshapes` from C++ source code. It is recommended
that you use it as a template project when getting started.
## Example

As a small example, we can start with a free function in
`examples/shapes/src/math_funcs/SimpleMathFunctions.hpp`:
The project in `examples/shapes` demonstrates `cppwg` usage. We can walk through
the process with the `Rectangle` class in `examples/shapes/src/primitives`

```c++
#ifndef _SIMPLEMATHFUNCTIONS_HPP
#define _SIMPLEMATHFUNCTIONS_HPP
**Rectangle.hpp**

/**
* Add the two input numbers and return the result
* @param i the first number
* @param j the second number
* @return the sum of the numbers
*/
int add(int i, int j)
```cpp
class Rectangle : public Shape<2>
{
return i + j;
}

#endif // _SIMPLEMATHFUNCTIONS_HPP
public:
Rectangle(double width=2.0, double height=1.0);
~Rectangle();
//...
};
```

Add a package description to `examples/shapes/wrapper/package_info.yaml`:
Cppwg needs a configuration file that has a list of classes to wrap and
describes the structure of the Python package to be created.

There is an example configuration file in
`examples/shapes/wrapper/package_info.yaml`.

The extract below from the example configuration file describes a Python package
named `pyshapes` which has a `primitives` module that includes the `Rectangle`
class.

```yaml
name: pyshapes
modules:
- name: math_funcs
free_functions: CPPWG_ALL
- name: primitives
classes:
- name: Rectangle
```

Generate the wrappers with:
See `package_info.yaml` for more configuration options.

To generate the wrappers:

```bash
cd examples/shapes
cppwg src/ \
--wrapper_root wrapper/ \
cppwg src \
--wrapper_root wrapper \
--package_info wrapper/package_info.yaml \
--includes src/math_funcs/
--includes src/geometry src/math_funcs src/mesh src/primitives extern/meshgen
```

The following PyBind11 wrapper code will be output to
`examples/shapes/wrapper/math_funcs/math_funcs.main.cpp`:
For the `Rectangle` class, this creates two files in
`examples/shapes/wrapper/primitives`.

```c++
#include <pybind11/pybind11.h>
#include "wrapper_header_collection.hpp"

namespace py = pybind11;
**Rectangle.cppwg.hpp**

PYBIND11_MODULE(_pyshapes_math_funcs, m)
{
m.def("add", &add, "");
}
```cpp
void register_Rectangle_class(pybind11::module &m);
```

The wrapper code can be built into a Python module and used as follows:
**Rectangle.cppwg.cpp**

```python
from pyshapes import math_funcs
a = 4
b = 5
c = math_funcs.add(4, 5)
print c
>>> 9
```cpp
namespace py = pybind11;
void register_Rectangle_class(py::module &m)
{
py::class_<Rectangle, Shape<2> >(m, "Rectangle")
.def(py::init<double, double>(), py::arg("width")=2, py::arg("height")=1)
//...
;
}
```

### Full Example
The wrapper for `Rectangle` is registered in the `primitives` module.

To generate Pybind11 wrappers for all the C++ code in `examples/shapes`:
**primitives.main.cpp**

```bash
cd examples/shapes
cppwg src/ \
--wrapper_root wrapper/ \
--package_info wrapper/package_info.yaml \
--includes src/geometry/ src/math_funcs/ src/mesh/ src/primitives
```cpp
PYBIND11_MODULE(_pyshapes_primitives, m)
{
register_Rectangle_class(m);
//...
}
```

To build the example `pyshapes` package:
To compile the wrappers into a Python package:

```bash
mkdir build
cd build
mkdir build && cd build
cmake ..
make
```

## Starting a New Project
* Make a wrapper directory in your source tree e.g. `mkdir wrappers`
* Copy the template in `examples/shapes/wrapper/generate.py` to the wrapper directory and fill it in as appropriate.
* Copy the template in `examples/shapes/wrapper/package_info.yaml` to the wrapper directory and fill it in as appropriate.
* Run `cppwg` with appropriate arguments to generate the PyBind11 wrapper code in the wrapper directory.
* Follow the [PyBind11 guide](https://pybind11.readthedocs.io/en/stable/compiling.html) for building with CMake, using `examples/shapes/CMakeLists.txt` as an initial guide.
The compiled wrapper code can now be imported in Python:

```python
from pyshapes import Rectangle
r = Rectangle(4, 5)
```

## Tips

- Use `examples/shapes` as a starting point.
- See the [pybind11 docs](https://pybind11.readthedocs.io/) for help on pybind11
wrapper code.
16 changes: 15 additions & 1 deletion cppwg/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,16 @@ def parse_args() -> argparse.Namespace:
help="Disable informational messages.",
)

parser.add_argument(
"-l",
"--logfile",
type=str,
nargs="?",
default=None,
const="cppwg.log",
help="Output log messages to a file.",
)

parser.add_argument(
"-v",
"--version",
Expand Down Expand Up @@ -110,9 +120,13 @@ def main() -> None:
"""Generate wrappers from command line arguments."""
args = parse_args()

log_handlers = [logging.StreamHandler()]
if args.logfile:
log_handlers.append(logging.FileHandler(args.logfile))

logging.basicConfig(
format="%(levelname)s %(message)s",
handlers=[logging.StreamHandler()],
handlers=log_handlers,
)
logger = logging.getLogger()

Expand Down
Loading