Skip to content

Commit

Permalink
Merge pull request #51 from Chaste/50-list-unwrapped-classes
Browse files Browse the repository at this point in the history
List unwrapped classes
  • Loading branch information
kwabenantim authored Sep 16, 2024
2 parents ac8b2ec + 3c3ef0f commit 6a50892
Show file tree
Hide file tree
Showing 280 changed files with 731 additions and 64,996 deletions.
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

0 comments on commit 6a50892

Please sign in to comment.