Giter VIP home page Giter VIP logo

cppwg's Introduction

build

cppwg

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

Installation

Clone the repository and install cppwg:

git clone https://github.com/Chaste/cppwg.git
cd cppwg
pip install .

Usage

This project generates PyBind11 wrapper code, saving lots of boilerplate in bigger projects. Please see the PyBind11 documentation for help on the generated wrapper code.

First Example

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.

As a small example, we can start with a free function in examples/shapes/src/math_funcs/SimpleMathFunctions.hpp:

#ifndef _SIMPLEMATHFUNCTIONS_HPP
#define _SIMPLEMATHFUNCTIONS_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)
{
    return i + j;
}

#endif  // _SIMPLEMATHFUNCTIONS_HPP

Add a package description to examples/shapes/wrapper/package_info.yaml:

name: pyshapes
modules:
- name: math_funcs
  free_functions: CPPWG_ALL

Generate the wrappers with:

cd examples/shapes
cppwg src/ \
  --wrapper_root wrapper/ \
  --package_info wrapper/package_info.yaml \
  --includes src/math_funcs/

The following PyBind11 wrapper code will be output to examples/shapes/wrapper/math_funcs/math_funcs.main.cpp:

#include <pybind11/pybind11.h>
#include "wrapper_header_collection.hpp"

namespace py = pybind11;

PYBIND11_MODULE(_pyshapes_math_funcs, m)
{
    m.def("add", &add, "");
}

The wrapper code can be built into a Python module and used as follows:

from pyshapes import math_funcs
a = 4
b = 5
c = math_funcs.add(4, 5)
print c
>>> 9

Full Example

To generate Pybind11 wrappers for all the C++ code in examples/shapes:

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

To build the example pyshapes package:

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 for building with CMake, using examples/shapes/CMakeLists.txt as an initial guide.

cppwg's People

Contributors

kwabenantim avatar jmsgrogan avatar dependabot[bot] avatar

Stargazers

Faisal Shahzad avatar  avatar  avatar  avatar  avatar Jarl Robert Pedersen Heer avatar Jonatan Kronander avatar Alex Koen avatar Hailin Wang avatar  avatar Jeff Bingham avatar firejq avatar Pandarinath M avatar Michael Jang avatar  avatar Florian Bruggisser avatar  avatar  avatar Gen avatar Giulio Romualdi avatar Shea Richardson avatar p.a.a.a.db.c.c.c avatar Qian Cao avatar  avatar  avatar Xinya Zhang avatar methylDragon avatar Pierre Kestener avatar Doug Johnston avatar ChangIn.Choi avatar Haibao Tang avatar  avatar Jack Liu avatar Andrew Hundt avatar Ellis Breen avatar AU avatar Zihan Chen avatar Amal Khailtash avatar

Watchers

Felix Ulber avatar  avatar Gary Mirams avatar Dave Wen avatar  avatar 龍共每文 avatar  avatar

cppwg's Issues

Wrapper Annotations

Description
Add text in wrappers explaining that the code is auto-generated.

Template specializations

Summary

Wrappers are created for methods in template specializations that may not have been implemented. In the example below, the Foo(unsigned u) constructor is not implemented in the specialization.

Foo.hpp

template<unsigned DIM_A, unsigned DIM_B>
class Foo : public AbstractFoo<DIM_A, DIM_B>
{
public:
  Foo(unsigned u);
  Foo(unsigned u, double d);
};

// Specialization
template<unsigned DIM_B>
class Foo<1, DIM_B> : public AbstractFoo<1,DIM_B>
{
public:
  Foo(unsigned u, double d);
  Foo(unsigned u);
};

Foo.cpp

template<unsigned DIM_A, unsigned DIM_B>
Foo<DIM_A, DIM_B>::Foo(unsigned u) : AbstractFoo<DIM_A, DIM_B>(u)
{
  // implementation
}

template<unsigned DIM_A, unsigned DIM_B>
Foo<DIM_A, DIM_B>::Foo(unsigned u, double d) : AbstractFoo<DIM_A, DIM_B>(u, d)
{
  // implementation
}

// Specialization
template<unsigned DIM_B>
Foo<1, DIM_B>::Foo(unsigned u, double d) : AbstractFoo<1, DIM_B>(u, d)
{
  // implementation
}

The wrapper below is generated for Foo<1,2>:

py::class_<Foo1_2, Foo1_2_Overloads>, AbstractFoo<1,2> >(m, "Foo1_2")
  .def(py::init<unsigned int>(), py::arg("u"))
  .def(py::init<unsigned int, double>(), py::arg("u"), py::arg("d"))

This results in a linker error:

undefined reference to `Foo<1u, 2u>::Foo(unsigned int)'

Syntactic Sugar for Templated Classes

Summary

Add a clearer way to call explicitly instantiated C++ template classes from Python.

For example:

# This:
simulation = OffLatticeSimulation2_2(cell_population)

# would become:
simulation = OffLatticeSimulation[2][2](cell_population)
# and this:
cell_population = VertexBasedCellPopulation2(mesh, cells)

# would become:
cell_population = VertexBasedCellPopulation[2](mesh, cells)

Empty initializer list warning

Summary

Empty initializer lists {} like

.def(py::init<::std::vector<Foo *> >(), py::arg("laminas") = {})

generate a warning

error: converting to ‘pybind11::arg’ from initializer list would use explicit 
constructor ‘constexpr pybind11::arg::arg(const char*)’ [-Werror]                                                                                                ^
note: in C++11 and above a default constructor can be explicit
cc1plus: all warnings being treated as errors

Possible fix:

.def(py::init<::std::vector<Foo *> >(), py::arg("laminas") = std::vector<Foo *>{})

Use templated wrappers

Description

Use templating to reduce number of generated wrapper files. Many wrappers only vary in template arguments and the rest of the code is exactly the same.

For example, the aim would be to refactor this

register_AbstractCellPopulation2_2_class(m);
register_AbstractCellPopulation3_3_class(m);

into this:

register_AbstractCellPopulation_class<2, 2>(m);
register_AbstractCellPopulation_class<3, 3>(m);

The first requires separate wrappers to be created for AbstractCellPopulation2_2 and AbstractCellPopulation3_3. Counting hpp and cpp, this generates 4 files.

The second requires a single templated wrapper to be created for AbstractCellPopulation. This would only generate 2 files regardless of the number of different sets of template arguments required. This approach would only need each set of arguments to be explicitly instantiated.

Make package installable via pip

Summary

Add a pyproject.toml to enable pip install . :

  • Eliminates the need to modify PYTHONPATH manually.
  • Dependencies can be specified for auto installation e.g. pygccxml and castxml.
  • Prepares for packaging to PyPI or conda.

Fix example imports

Description

This works fine in the example:

from pyshapes.geometry import Point2

This does not seem to work, however:

import pyshapes.geometry.Point2

It throws this error:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'pyshapes.geometry.Point2'

Add Testing

Description

  • Add tests to check wrappers generated for various scenarios.
  • Add CI scripts to run the tests on PRs etc.

Error with modules built for Python 3.5

Dear saviours,

Firstly, I want to thank you for this wonderful library. Seems robust and should make the lives of plethora of python developers in our world a good one. I tried generating the library using the examples but with one tiny change in the Cmakelists.txt i.e., setting the version of python from 2.7 to 3.5

#set(PYBIND11_PYTHON_VERSION 2.7)
set(PYBIND11_PYTHON_VERSION 3.5)

The library was sucessfully built inside the build directory with the test_classes.py and test_functions.py contained in the same directory. When I run the examples as shown in the README.md

python3 test_classes.py

I get this error (not able to import the module)

Traceback (most recent call last):
  File "test_classes.py", line 2, in <module>
    import pyshapes.geometry
  File "/home/ashok/libraries/cppwg/build/pyshapes/geometry/__init__.py", line 2, in <module>
    from _pyshapes_geometry import *
ImportError: No module named '_pyshapes_geometry'

I checked if those modules are available and indeed the pyshapes directory can be seen with the subdirectories containing the relevant so files. I am using Ubuntu 16.04 with the GNOME flavor on an AMD machine.

Any ideas where this is going wrong?

Regards,

#0K

Fix handling of double pointers

Description
Wrappers generated for member functions with double pointer args do not compile.

Example:

class TysonNovak2001OdeSystem : public AbstractOdeSystemWithAnalyticJacobian
{
public:
    virtual void AnalyticJacobian(const std::vector<double>& rSolutionGuess, double** jacobian, double time, double timeStep);
};

produces:

.def(
    "AnalyticJacobian", 
    (void(TysonNovak2001OdeSystem::*)(::std::vector<double> const &, double * *, double, double)) &TysonNovak2001OdeSystem::AnalyticJacobian, 
    " " , py::arg("rSolutionGuess"), py::arg("jacobian"), py::arg("time"), py::arg("timeStep") )

Error

pybind11/include/pybind11/cast.h:1217
error: no matching function for call to ‘pybind11::detail::type_caster<double, void>::cast(double**&, pybind11::return_value_policy, std::nullptr_t)’

Fix missing inheritance

Description
Parent class is missing from some generated wrappers.

Original Class

template<unsigned DIM>
class VertexBasedCellPopulation : public AbstractOffLatticeCellPopulation<DIM>
{
...
    VertexBasedCellPopulation(MutableVertexMesh<DIM, DIM>& rMesh,
                              std::vector<CellPtr>& rCells,
                              bool deleteMesh=false,
                              bool validate=true,
                              const std::vector<unsigned> locationIndices=std::vector<unsigned>());
...
}

Generated wrapper

 py::class_<VertexBasedCellPopulation2 , VertexBasedCellPopulation2_Overloads , boost::shared_ptr<VertexBasedCellPopulation2 >   >(m, "VertexBasedCellPopulation2")

Correct wrapper

py::class_<VertexBasedCellPopulation2 , VertexBasedCellPopulation2_Overloads , boost::shared_ptr<VertexBasedCellPopulation2 > , AbstractOffLatticeCellPopulation<2, 2> >(m, "VertexBasedCellPopulation2")

Bug: Instantiation of a single template type breaks writing of wrapped code

Hello! The following is a set of steps to reproduce the error of a bug in the writing of the wrapped code that can be reproduced in the "shapes" directory within the repository. The "wrapper_header_collection" template instantiations are not created when the total number of template replacements per substitution is 1.

Steps to reproduce:
rm -rf shapes/src/primatives
Alter shapes/wrapper/package_info.yaml to match the following diff:

diff --git a/shapes/wrapper/package_info.yaml b/shapes/wrapper/package_info.yaml
index f1cace8..d66268d 100644
--- a/shapes/wrapper/package_info.yaml
+++ b/shapes/wrapper/package_info.yaml
@@ -2,7 +2,7 @@ name: pyshapes # Unique name prepended to all modules
smart_ptr_type: std::shared_ptr
template_substitutions:

  • signature:
  • replacement: [[2], [3]]
  • replacement: [[2]]

modules:

  • name: math_funcs # Name of the module
    @@ -13,9 +13,3 @@ modules:
    source_locations:
    classes:
    • name: Point
      -- name: primitives
  • source_locations:
  • classes:
    • name: Shape
    • name: Cuboid
    • name: Rectangle

The run of cppwg

python3 wrapper/generate.py --source_root ~/Work/TRI/cppwg/shapes --wrapper_root ~/Work/TRI/cppwg/shapes/wrapper/ --castxml_binary ~/Work/TRI/CastXML/build/bin/castxml --package_info ~/Work/TRI/cppwg/shapes/wrapper/package_info.yaml --includes ~/Work/TRI/cppwg/shapes/src/

reports the following error:

> INFO: Cleaning Decls > INFO: Optimizing Decls > Generating Wrapper Code for: math_funcs Module. > Generating Wrapper Code for: geometry Module. > Generating Wrapper Code for: Point Class. > Traceback (most recent call last): > File "wrapper/generate.py", line 45, in > args.package_info, all_includes) > File "wrapper/generate.py", line 21, in generate_wrapper_code > generator.generate_wrapper() > File "/home/softhat/Work/TRI/cppwg/cppwg/generators.py", line 200, in generate_wrapper > module_writer.write() > File "/home/softhat/Work/TRI/cppwg/cppwg/writers/module_writer.py", line 112, in write > class_decl = self.source_ns.class_(fullName.replace(" ","")) > File "/home/softhat/.local/lib/python3.6/site-packages/pygccxml/declarations/scopedef.py", line 574, in class_ > recursive=recursive) > File "/home/softhat/.local/lib/python3.6/site-packages/pygccxml/declarations/scopedef.py", line 484, in _find_single > found = matcher.get_single(decl_matcher, decls, False) > File "/home/softhat/.local/lib/python3.6/site-packages/pygccxml/declarations/scopedef.py", line 87, in get_single > raise runtime_errors.declaration_not_found_t(decl_matcher) > pygccxml.declarations.runtime_errors.declaration_not_found_t: Unable to find declaration. Matcher: [(decl type==class_t) and (name==Point< 2 >)]

Thank you for making this code available!

Fix template parameters in method default args

Description

Template parameters are not correctly handled in some cases e.g.

.def(
"SetUpBoxCollection", 
(void(PeriodicNodesOnlyMesh3::*)(double, ::boost::numeric::ublas::c_vector<double, 6>, int, ::boost::numeric::ublas::c_vector<bool, 3>)) &PeriodicNodesOnlyMesh3::SetUpBoxCollection, " " , 
py::arg("cutOffLength"), 
py::arg("domainSize"), 
py::arg("numLocalRows") = -1, 
py::arg("isDimPeriodic") = zero_vector<bool(PeriodicNodesOnlyMesh::SPACE_DIM) )

should be instead (SPACE_DIM should be replaced):

.def(
"SetUpBoxCollection", 
(void(PeriodicNodesOnlyMesh3::*)(double, ::boost::numeric::ublas::c_vector<double, 6>, int, ::boost::numeric::ublas::c_vector<bool, 3>)) &PeriodicNodesOnlyMesh3::SetUpBoxCollection, " " , 
py::arg("cutOffLength"), 
py::arg("domainSize"), 
py::arg("numLocalRows") = -1, 
py::arg("isDimPeriodic") = zero_vector<bool>(3) )

Compile Error

~/Chaste/projects/PyChaste/dynamic/wrappers/mesh/PeriodicNodesOnlyMesh2.cppwg.cpp: In function ‘void register_PeriodicNodesOnlyMesh2_class(pybind11::module&)’:
~/Chaste/projects/PyChaste/dynamic/wrappers/mesh/PeriodicNodesOnlyMesh2.cppwg.cpp:78:141: error: expected primary-expression before ‘(’ token
   78 | g("domainSize"), py::arg("numLocalRows") = -1, py::arg("isDimPeriodic") = zero_vector<bool>(PeriodicNodesOnlyMesh::SPACE_DIM) )
      |                                                                                            ^

~/Chaste/projects/PyChaste/dynamic/wrappers/mesh/PeriodicNodesOnlyMesh2.cppwg.cpp:78:142: error: ‘template<unsigned int SPACE_DIM> class PeriodicNodesOnlyMesh’ used without template arguments
   78 | ("domainSize"), py::arg("numLocalRows") = -1, py::arg("isDimPeriodic") = zero_vector<bool>(PeriodicNodesOnlyMesh::SPACE_DIM) )
      |                                                                                            ^~~~~~~~~~~~~~~~~~~~~

Get help text

Summary

Retrieve Python help text from C++ docstrings.

Housekeeping

Description
The pybind11 template code and other TODO bits need updating.

TODO

  • Replace PYBIND11_OVERLOAD -> PYBIND11_OVERRIDE and PYBIND11_OVERLOAD_PURE -> PYBIND11_OVERRIDE_PURE : https://pybind11.readthedocs.io/en/stable/upgrade.html#v2-6
  • Replace pyplusplus in wrapper templates with cppwg.
  • Add #include <pybind11/pybind11.h> in wrapper hpp files and remove namespace py = pybind11;.
  • Remove unused code.
  • Return strings from generators rather than appending to input strings.
  • Fix TODOs in code.

Order Classes Automatically

Summary

The order in which classes are added in package_info.yml is important. This order is propragated forward to the include order in the wrapper header collection. Base classes come before derived classes, a class should come before other classes that refer to it etc. When there are many classes in package_info.yml, figuring out an order that works can be hit or miss by trial and error, when adding a new class to an existing configuration.

It would be a useful feature if cppwg could automatically work out the order to include classes in. Users could add classes to package_info.yml in any order without having to manually work out an order that won't break things. Classes could be added in alphabetical order, for instance, which would make it easier to find things when manually inspecting the configuration.

C++ 17 Compatibility

Description

Wrapper generation throws errors when it encounters C++ 17 features. For example:

INFO: Parsing Code
In file included from /tmp/tmpabzfnu6m.h:1:
In file included from Chaste/projects/PyChaste/dynamic/wrappers//wrapper_header_collection.hpp:9:
Chaste/global/src/FileFinder.hpp:42:21: error: expected namespace name
namespace fs = std::filesystem;

Fix unable to find declarations

Description
Generating wrappers throws errors for some classes: Unable to find declaration Clazz or Unable to find declaration Clazz< 2, 2 >

Error Message

Traceback (most recent call last):
  File "~/Chaste/projects/PyChaste/dynamic/wrapper_generators/generate.py", line 55, in <module>
    generator.generate_wrapper()
  File "~/cppwg/cppwg/generators.py", line 200, in generate_wrapper
    module_writer.write()
  File "~/cppwg/cppwg/writers/module_writer.py", line 112, in write
    class_decl = self.source_ns.class_(fullName.replace(" ",""))
  File "~/.local/lib/python3.10/site-packages/pygccxml/declarations/scopedef.py", line 546, in class_
    self._find_single(
  File "~/.local/lib/python3.10/site-packages/pygccxml/declarations/scopedef.py", line 464, in _find_single
    found = matcher.get_single(decl_matcher, decls, False)
  File "~/.local/lib/python3.10/site-packages/pygccxml/declarations/scopedef.py", line 90, in get_single
    raise runtime_errors.declaration_not_found_t(decl_matcher)
pygccxml.declarations.runtime_errors.declaration_not_found_t: Unable to find declaration. Matcher: [(decl type==class_t) and (name==AbstractCellBasedSimulationModifier< 2, 2 >)]
make[3]: *** [projects/PyChaste/CMakeFiles/project_PyChaste_Python_Bindings.dir/build.make:70: project_PyChaste_Python_Bindings] Error 1
make[2]: *** [CMakeFiles/Makefile2:21607: projects/PyChaste/CMakeFiles/project_PyChaste_Python_Bindings.dir/all] Error 2
make[1]: *** [CMakeFiles/Makefile2:21614: projects/PyChaste/CMakeFiles/project_PyChaste_Python_Bindings.dir/rule] Error 2
make: *** [Makefile:8103: project_PyChaste_Python_Bindings] Error 2

Consider using Jinja for templating

Summary

Jinja.

Pros

  • Easier to maintain templates.
  • Easier to write tests to check if the correct bindings are being generated.
  • May make it easier to create custom generators.
  • May make it easier to generate code for other targets apart from Pybind11.

Cons

  • Adding an extra dependency

Expand Testing

Summary

Tests are currently run for the shapes example, but don't cover all uses cases.

TODO

  • Test on multiple Python versions (3.8 - 3.12).
  • Update example shapes project CMake configuration.
  • Update pybind11 version in example shapes project.
  • Add testing with flake8
  • Measure current test coverage.
  • Increase test coverage to 25%.
  • Increase test coverage to 50%.
  • Increase test coverage to 75%.
  • Increase test coverage to 100%.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.