From 8eb5304d1897c02acba394ccad0e2345cd51a97c Mon Sep 17 00:00:00 2001 From: Kwabena N Amponsah Date: Wed, 1 May 2024 11:47:00 +0000 Subject: [PATCH 01/11] #3 update readme --- README.md | 98 +++++++++++++++++++++++++------------------------------ 1 file changed, 45 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 2e6b242..ddac632 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,30 @@ Automatically generate PyBind11 Python wrapper code for C++ projects. -## Example +## Installation +Clone the repository and install cppwg: -`examples/shapes/` 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. +```bash +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](https://pybind11.readthedocs.io/en/stable/) +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`: -As a small example, we can start with a free function in `examples/shapes/src/math_funcs/SimpleMathFunctions.hpp`: ```c++ #ifndef _SIMPLEMATHFUNCTIONS_HPP #define _SIMPLEMATHFUNCTIONS_HPP @@ -28,7 +46,7 @@ int add(int i, int j) #endif // _SIMPLEMATHFUNCTIONS_HPP ``` -add a package description to `examples/shapes/wrapper/package_info.yaml`: +Add a package description to `examples/shapes/wrapper/package_info.yaml`: ```yaml name: pyshapes @@ -37,9 +55,19 @@ modules: free_functions: cppwg_ALL ``` -and do `python examples/shapes/wrapper/generate.py` (with some suitable arguments). +Generate the wrappers with: + +```bash +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`: -The generator will make the following PyBind11 wrapper code in `examples/shapes/wrapper/math_funcs/math_funcs.main.cpp`: ```c++ #include #include "wrapper_header_collection.hpp" @@ -52,7 +80,8 @@ PYBIND11_MODULE(_pyshapes_math_funcs, m) } ``` -which can be built into a Python module and (with some import tidying) used as follows: +The wrapper code can be built into a Python module and used as follows: + ```python from pyshapes import math_funcs a = 4 @@ -62,49 +91,19 @@ print c >>> 9 ``` -## Usage -It is recommended that you [learn how to use PyBind11 first](https://pybind11.readthedocs.io/en/stable/). This project -generates PyBind11 wrapper code, saving lots of boilerplate in bigger projects. - -### Dependencies -Developed on the [latest Ubuntu LTS](https://ubuntu.com/about/release-cycle) -version and tested with [supported versions of Python 3](https://devguide.python.org/versions/). - -The main dependencies are [pyyaml](https://github.com/yaml/pyyaml), -[pygccxml](https://github.com/CastXML/pygccxml), and [castxml](https://github.com/CastXML/CastXML), -which will be automatically pip-installed along with cppwg. Alternatively, -they can be installed directly with: - - ```bash - pip install pyyaml pygccxml castxml - ``` - -### Test the Installation -First, clone the repository with: +### Full Example -```bash -git clone https://github.com/Chaste/cppwg.git -``` - -Install cppwg by doing: - -```bash -cd cppwg -pip install . -``` - -To generate the full `pyshapes` wrapper, do: +To generate Pybind11 wrappers for all the C++ code in `examples/shapes`: ```bash cd examples/shapes cppwg src/ \ --wrapper_root wrapper/ \ --package_info wrapper/package_info.yaml \ - --includes src/geometry/ src/math_funcs/ src/primitives/ \ - --std c++17 + --includes src/geometry/ src/math_funcs/ src/mesh/ src/primitives ``` -To build the example package do: +To build the example `pyshapes` package: ```bash mkdir build @@ -113,16 +112,9 @@ cmake .. make ``` -To test the resulting package do: - -```bash -python test_functions.py -python test_classes.py -``` - -### Starting a New Project +## 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. -* Copy the template in `examples/shapes/wrapper/package_info.yaml` to the wrapper directory and fill it in. -* Do `python wrappers/generate.py` to generate the PyBind11 wrapper code in the wrapper directory. +* 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. From 70c98a510c55453d30e6b816c3ee5445ba83da3b Mon Sep 17 00:00:00 2001 From: Kwabena N Amponsah Date: Wed, 1 May 2024 12:30:33 +0000 Subject: [PATCH 02/11] #3 add mesh example --- examples/shapes/CMakeLists.txt | 49 +++++++++++++++++------ examples/shapes/src/mesh/Mesh.cpp | 26 ++++++++++++ examples/shapes/src/mesh/Mesh.hpp | 38 ++++++++++++++++++ examples/shapes/wrapper/package_info.yaml | 5 +++ 4 files changed, 106 insertions(+), 12 deletions(-) create mode 100644 examples/shapes/src/mesh/Mesh.cpp create mode 100644 examples/shapes/src/mesh/Mesh.hpp diff --git a/examples/shapes/CMakeLists.txt b/examples/shapes/CMakeLists.txt index 951f174..a77ea0f 100644 --- a/examples/shapes/CMakeLists.txt +++ b/examples/shapes/CMakeLists.txt @@ -1,10 +1,11 @@ cmake_minimum_required(VERSION 3.16.3) project(shapes) -set (CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 17) # Add main source dir include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/math_funcs) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/mesh) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/geometry) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/primitives) @@ -22,29 +23,53 @@ add_subdirectory(wrapper/pybind11) include_directories(${PYTHON_INCLUDE_DIRS}) # Copy the Python source and test trees to the build location -file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/src/python/pyshapes DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/) -file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/src/python/test/ DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/) +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/src/python/pyshapes + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/) +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/src/python/test/ + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/) # Add the autogenerated files to the module target file(GLOB MODULE_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/wrapper/math_funcs/*.cpp) add_library(_pyshapes_math_funcs SHARED ${MODULE_SOURCES}) target_link_libraries(_pyshapes_math_funcs PRIVATE pybind11::module shapes) + +file(GLOB MODULE_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/wrapper/mesh/*.cpp) +add_library(_pyshapes_mesh SHARED ${MODULE_SOURCES}) +target_link_libraries(_pyshapes_mesh PRIVATE pybind11::module mesh) + file(GLOB MODULE_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/wrapper/geometry/*.cpp) add_library(_pyshapes_geometry SHARED ${MODULE_SOURCES}) target_link_libraries(_pyshapes_geometry PRIVATE pybind11::module shapes) + file(GLOB MODULE_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/wrapper/primitives/*.cpp) add_library(_pyshapes_primitives SHARED ${MODULE_SOURCES}) target_link_libraries(_pyshapes_primitives PRIVATE pybind11::module shapes) # Set suitable extensions and put the module into a suitable location in the package -set_target_properties(_pyshapes_math_funcs PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" - SUFFIX "${PYTHON_MODULE_EXTENSION}" - LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/pyshapes/math_funcs/) +set_target_properties( + _pyshapes_math_funcs + PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" + SUFFIX "${PYTHON_MODULE_EXTENSION}" + LIBRARY_OUTPUT_DIRECTORY + ${CMAKE_CURRENT_BINARY_DIR}/pyshapes/math_funcs/) + +set_target_properties( + _pyshapes_mesh + PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" + SUFFIX "${PYTHON_MODULE_EXTENSION}" + LIBRARY_OUTPUT_DIRECTORY + ${CMAKE_CURRENT_BINARY_DIR}/pyshapes/mesh/) -set_target_properties(_pyshapes_geometry PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" - SUFFIX "${PYTHON_MODULE_EXTENSION}" - LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/pyshapes/geometry/) +set_target_properties( + _pyshapes_geometry + PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" + SUFFIX "${PYTHON_MODULE_EXTENSION}" + LIBRARY_OUTPUT_DIRECTORY + ${CMAKE_CURRENT_BINARY_DIR}/pyshapes/geometry/) -set_target_properties(_pyshapes_primitives PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" - SUFFIX "${PYTHON_MODULE_EXTENSION}" - LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/pyshapes/primitives/) \ No newline at end of file +set_target_properties( + _pyshapes_primitives + PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" + SUFFIX "${PYTHON_MODULE_EXTENSION}" + LIBRARY_OUTPUT_DIRECTORY + ${CMAKE_CURRENT_BINARY_DIR}/pyshapes/primitives/) diff --git a/examples/shapes/src/mesh/Mesh.cpp b/examples/shapes/src/mesh/Mesh.cpp new file mode 100644 index 0000000..6efea8c --- /dev/null +++ b/examples/shapes/src/mesh/Mesh.cpp @@ -0,0 +1,26 @@ +#include "Mesh.hpp" + +template +Mesh::Mesh() : mIndex(0) +{ +} + +template +Mesh::~Mesh() +{ +} + +template +unsigned Mesh::GetIndex() const +{ + return mIndex; +} + +template +void Mesh::SetIndex(unsigned index) +{ + mIndex = index; +} + +template class Mesh<2, 2>; +template class Mesh<3, 3>; diff --git a/examples/shapes/src/mesh/Mesh.hpp b/examples/shapes/src/mesh/Mesh.hpp new file mode 100644 index 0000000..e1f6b57 --- /dev/null +++ b/examples/shapes/src/mesh/Mesh.hpp @@ -0,0 +1,38 @@ +#ifndef _MESH_HPP +#define _MESH_HPP + +/** + * A mesh in SPACE_DIM space with ELEMENT_DIM dimensional elements + */ +template +class Mesh +{ +private: + /** + * Mesh index + */ + unsigned mIndex; + +public: + /** + * Default Constructor + */ + Mesh(); + + /** + * Destructor + */ + ~Mesh(); + + /** + * Return the index + */ + unsigned GetIndex() const; + + /** + * Set the index + */ + void SetIndex(unsigned index); +}; + +#endif // _MESH_HPP diff --git a/examples/shapes/wrapper/package_info.yaml b/examples/shapes/wrapper/package_info.yaml index f1cace8..c3891cb 100644 --- a/examples/shapes/wrapper/package_info.yaml +++ b/examples/shapes/wrapper/package_info.yaml @@ -19,3 +19,8 @@ modules: - name: Shape - name: Cuboid - name: Rectangle +- name: mesh + source_locations: + classes: + - name: Mesh + \ No newline at end of file From c37df1ef9e369c10ec8f2c45144fca9a1ea632ae Mon Sep 17 00:00:00 2001 From: Kwabena N Amponsah Date: Wed, 1 May 2024 12:35:56 +0000 Subject: [PATCH 03/11] #3 update class name generation --- cppwg/generators.py | 2 + cppwg/input/class_info.py | 117 +++++++++++++++++++++- cppwg/input/cpp_type_info.py | 103 ------------------- cppwg/writers/class_writer.py | 4 +- cppwg/writers/header_collection_writer.py | 8 +- cppwg/writers/module_writer.py | 8 +- 6 files changed, 125 insertions(+), 117 deletions(-) diff --git a/cppwg/generators.py b/cppwg/generators.py index 7aa30de..86dcd10 100644 --- a/cppwg/generators.py +++ b/cppwg/generators.py @@ -203,6 +203,8 @@ def extract_templates_from_source(self) -> None: info_helper = CppInfoHelper(module_info) for class_info in module_info.class_info_collection: info_helper.extract_templates_from_source(class_info) + class_info.update_short_names() + class_info.update_full_names() def map_classes_to_hpp_files(self) -> None: """ diff --git a/cppwg/input/class_info.py b/cppwg/input/class_info.py index 3ab57af..31f120c 100644 --- a/cppwg/input/class_info.py +++ b/cppwg/input/class_info.py @@ -1,17 +1,130 @@ """Class information structure.""" -from typing import Any, Dict, Optional +from typing import Any, Dict, List, Optional from cppwg.input.cpp_type_info import CppTypeInfo class CppClassInfo(CppTypeInfo): - """An information structure for individual C++ classes to be wrapped.""" + """ + An information structure for individual C++ classes to be wrapped. + + Attributes + ---------- + full_names : List[str] + The C++ names of the class e.g. ["Foo<2,2>", "Foo<3,3>"] + short_names : List[str] + The Python names of the class e.g. ["Foo2_2", "Foo3_3"] + """ def __init__(self, name: str, class_config: Optional[Dict[str, Any]] = None): super(CppClassInfo, self).__init__(name, class_config) + self.full_names: List[str] = None + self.short_names: List[str] = None + + def update_short_names(self): + """ + Get the Python name for the class e.g. Foo2_2. + + Return the name of the class as it will appear on the Python side. This + collapses template arguments, separating them by underscores and removes + special characters. The return type is a list, as a class can have + multiple names if it is templated. For example, a class "Foo" with + template arguments [[2, 2], [3, 3]] will have a short name list + ["Foo2_2", "Foo3_3"]. + + Returns + ------- + List[str] + The list of short names + """ + # Handles untemplated classes + if self.template_arg_lists is None: + if self.name_override: + self.short_names = [self.name_override] + else: + self.short_names = [self.name] + return + + self.short_names = [] + + # Table of special characters for removal + rm_chars = {"<": None, ">": None, ",": None, " ": None} + rm_table = str.maketrans(rm_chars) + + # Clean the type name + type_name = self.name + if self.name_override is not None: + type_name = self.name_override + + # Do standard name replacements e.g. "unsigned int" -> "Unsigned" + for name, replacement in self.name_replacements.items(): + type_name = type_name.replace(name, replacement) + + # Remove special characters + type_name = type_name.translate(rm_table) + + # Capitalize the first letter e.g. "foo" -> "Foo" + if len(type_name) > 1: + type_name = type_name[0].capitalize() + type_name[1:] + + # Create a string of template args separated by "_" e.g. 2_2 + for template_arg_list in self.template_arg_lists: + # Example template_arg_list : [2, 2] + + template_string = "" + for idx, arg in enumerate(template_arg_list): + + # Do standard name replacements + arg_str = str(arg) + for name, replacement in self.name_replacements.items(): + arg_str = arg_str.replace(name, replacement) + + # Remove special characters + arg_str = arg_str.translate(rm_table) + + # Capitalize the first letter + if len(arg_str) > 1: + arg_str = arg_str[0].capitalize() + arg_str[1:] + + # Add "_" between template arguments + template_string += arg_str + if idx < len(template_arg_list) - 1: + template_string += "_" + + self.short_names.append(type_name + template_string) + + def update_full_names(self): + """ + Get the C++ name for the class e.g. Foo<2,2>. + + Return the name (declaration) of the class as it appears on the C++ + side. The return type is a list, as a class can have multiple names + (declarations) if it is templated. For example, a class "Foo" with + template arguments [[2, 2], [3, 3]] will have a full name list + ["Foo<2,2 >", "Foo<3,3 >"]. + + Returns + ------- + List[str] + The list of full names + """ + # Handles untemplated classes + if self.template_arg_lists is None: + self.full_names = [self.name] + return + + self.full_names = [] + for template_arg_list in self.template_arg_lists: + # Create template string from arg list e.g. [2, 2] -> "<2,2 >" + template_string = ",".join([str(arg) for arg in template_arg_list]) + template_string = "<" + template_string + " >" + + # Join full name e.g. "Foo<2,2 >" + self.full_names.append(self.name + template_string) + @property def parent(self) -> "ModuleInfo": # noqa: F821 """Returns the parent module info object.""" diff --git a/cppwg/input/cpp_type_info.py b/cppwg/input/cpp_type_info.py index 23d1655..0165a41 100644 --- a/cppwg/input/cpp_type_info.py +++ b/cppwg/input/cpp_type_info.py @@ -42,109 +42,6 @@ def __init__(self, name: str, type_config: Optional[Dict[str, Any]] = None): for key, value in type_config.items(): setattr(self, key, value) - # TODO: Consider setting short and full names on init as read-only properties - def get_short_names(self) -> List[str]: - """ - Get the Python name for the class e.g. Foo2_2. - - Return the name of the class as it will appear on the Python side. This - collapses template arguments, separating them by underscores and removes - special characters. The return type is a list, as a class can have - multiple names if it is templated. For example, a class "Foo" with - template arguments [[2, 2], [3, 3]] will have a short name list - ["Foo2_2", "Foo3_3"]. - - Returns - ------- - List[str] - The list of short names - """ - # Handles untemplated classes - if self.template_arg_lists is None: - if self.name_override is None: - return [self.name] - return [self.name_override] - - short_names = [] - - # Table of special characters for removal - rm_chars = {"<": None, ">": None, ",": None, " ": None} - rm_table = str.maketrans(rm_chars) - - # Clean the type name - type_name = self.name - if self.name_override is not None: - type_name = self.name_override - - # Do standard name replacements e.g. "unsigned int" -> "Unsigned" - for name, replacement in self.name_replacements.items(): - type_name = type_name.replace(name, replacement) - - # Remove special characters - type_name = type_name.translate(rm_table) - - # Capitalize the first letter e.g. "foo" -> "Foo" - if len(type_name) > 1: - type_name = type_name[0].capitalize() + type_name[1:] - - # Create a string of template args separated by "_" e.g. 2_2 - for template_arg_list in self.template_arg_lists: - # Example template_arg_list : [2, 2] - - template_string = "" - for idx, arg in enumerate(template_arg_list): - - # Do standard name replacements - arg_str = str(arg) - for name, replacement in self.name_replacements.items(): - arg_str = arg_str.replace(name, replacement) - - # Remove special characters - arg_str = arg_str.translate(rm_table) - - # Capitalize the first letter - if len(arg_str) > 1: - arg_str = arg_str[0].capitalize() + arg_str[1:] - - # Add "_" between template arguments - template_string += arg_str - if idx < len(template_arg_list) - 1: - template_string += "_" - - short_names.append(type_name + template_string) - - return short_names - - def get_full_names(self) -> List[str]: - """ - Get the C++ name for the class e.g. Foo<2,2>. - - Return the name (declaration) of the class as it appears on the C++ - side. The return type is a list, as a class can have multiple names - (declarations) if it is templated. For example, a class "Foo" with - template arguments [[2, 2], [3, 3]] will have a full name list - ["Foo<2,2 >", "Foo<3,3 >"]. - - Returns - ------- - List[str] - The list of full names - """ - # Handles untemplated classes - if self.template_arg_lists is None: - return [self.name] - - full_names = [] - for template_arg_list in self.template_arg_lists: - # Create template string from arg list e.g. [2, 2] -> "<2,2 >" - template_string = ",".join([str(arg) for arg in template_arg_list]) - template_string = "<" + template_string + " >" - - # Join full name e.g. "Foo<2,2 >" - full_names.append(self.name + template_string) - - return full_names - # TODO: This method is not used, remove it? def needs_header_file_instantiation(self): """Check if this class needs to be instantiated in the header file.""" diff --git a/cppwg/writers/class_writer.py b/cppwg/writers/class_writer.py index b6241fe..7091a3d 100644 --- a/cppwg/writers/class_writer.py +++ b/cppwg/writers/class_writer.py @@ -60,10 +60,10 @@ def __init__( self.class_info: CppClassInfo = class_info # Class full names eg. ["Foo<2,2>", "Foo<3,3>"] - self.class_full_names: List[str] = self.class_info.get_full_names() + self.class_full_names: List[str] = self.class_info.full_names # Class short names eg. ["Foo2_2", "Foo3_3"] - self.class_short_names: List[str] = self.class_info.get_short_names() + self.class_short_names: List[str] = self.class_info.full_names if len(self.class_full_names) != len(self.class_short_names): logger.error("Full and short name lists should be the same length") diff --git a/cppwg/writers/header_collection_writer.py b/cppwg/writers/header_collection_writer.py index e78105b..0be1660 100644 --- a/cppwg/writers/header_collection_writer.py +++ b/cppwg/writers/header_collection_writer.py @@ -130,14 +130,10 @@ def write(self) -> None: continue # Class full names eg. ["Foo<2,2>", "Foo<3,3>"] - full_names = [ - name.replace(" ", "") for name in class_info.get_full_names() - ] + full_names = [name.replace(" ", "") for name in class_info.full_names] # Class short names eg. ["Foo2_2", "Foo3_3"] - short_names = [ - name.replace(" ", "") for name in class_info.get_short_names() - ] + short_names = [name.replace(" ", "") for name in class_info.short_names] for full_name, short_name in zip(full_names, short_names): template_instantiations += f"template class {full_name};\n" diff --git a/cppwg/writers/module_writer.py b/cppwg/writers/module_writer.py index 71225f3..7c0115f 100644 --- a/cppwg/writers/module_writer.py +++ b/cppwg/writers/module_writer.py @@ -59,7 +59,7 @@ def __init__( self.exposed_class_full_names: List[str] = [] for class_info in self.module_info.class_info_collection: - for full_name in class_info.get_full_names(): + for full_name in class_info.full_names: self.exposed_class_full_names.append(full_name.replace(" ", "")) def write_module_wrapper(self) -> None: @@ -92,7 +92,7 @@ def write_module_wrapper(self) -> None: # Add includes for class wrappers in the module for class_info in self.module_info.class_info_collection: - for short_name in class_info.get_short_names(): + for short_name in class_info.short_names: # Example: #include "Foo2_2.cppwg.hpp" cpp_string += f'#include "{short_name}.{CPPWG_EXT}.hpp"\n' @@ -115,7 +115,7 @@ def write_module_wrapper(self) -> None: # Add classes for class_info in self.module_info.class_info_collection: - for short_name in class_info.get_short_names(): + for short_name in class_info.short_names: # Example: register_Foo2_2_class(m);" cpp_string += f" register_{short_name}_class(m);\n" @@ -148,7 +148,7 @@ def write_class_wrappers(self) -> None: # Get the declaration for each class and add it to the class writer # TODO: Consider using class_info.decl instead - for full_name in class_info.get_full_names(): + for full_name in class_info.full_names: name = full_name.replace(" ", "") # e.g. Foo<2,2> class_decl: class_t = self.source_ns.class_(name) From 417414d6052c05e3c8e360675d8b1610640ca7d2 Mon Sep 17 00:00:00 2001 From: Kwabena N Amponsah Date: Wed, 1 May 2024 13:46:59 +0000 Subject: [PATCH 04/11] #3 add mesh generated code --- cppwg/writers/class_writer.py | 2 +- examples/shapes/wrapper/mesh/mesh.main.cpp | 12 ++++++ examples/shapes/wrapper/package_info.yaml | 41 ++++++++++--------- .../wrapper/wrapper_header_collection.hpp | 5 +++ 4 files changed, 39 insertions(+), 21 deletions(-) create mode 100644 examples/shapes/wrapper/mesh/mesh.main.cpp diff --git a/cppwg/writers/class_writer.py b/cppwg/writers/class_writer.py index 7091a3d..b1b1cc2 100644 --- a/cppwg/writers/class_writer.py +++ b/cppwg/writers/class_writer.py @@ -63,7 +63,7 @@ def __init__( self.class_full_names: List[str] = self.class_info.full_names # Class short names eg. ["Foo2_2", "Foo3_3"] - self.class_short_names: List[str] = self.class_info.full_names + self.class_short_names: List[str] = self.class_info.short_names if len(self.class_full_names) != len(self.class_short_names): logger.error("Full and short name lists should be the same length") diff --git a/examples/shapes/wrapper/mesh/mesh.main.cpp b/examples/shapes/wrapper/mesh/mesh.main.cpp new file mode 100644 index 0000000..d6d098a --- /dev/null +++ b/examples/shapes/wrapper/mesh/mesh.main.cpp @@ -0,0 +1,12 @@ +#include +#include "wrapper_header_collection.hpp" +#include "Mesh2_2.cppwg.hpp" +#include "Mesh3_3.cppwg.hpp" + +namespace py = pybind11; + +PYBIND11_MODULE(_pyshapes_mesh, m) +{ + register_Mesh2_2_class(m); + register_Mesh3_3_class(m); +} diff --git a/examples/shapes/wrapper/package_info.yaml b/examples/shapes/wrapper/package_info.yaml index c3891cb..9a14694 100644 --- a/examples/shapes/wrapper/package_info.yaml +++ b/examples/shapes/wrapper/package_info.yaml @@ -2,25 +2,26 @@ name: pyshapes # Unique name prepended to all modules smart_ptr_type: std::shared_ptr template_substitutions: - signature: - replacement: [[2], [3]] + replacement: [[2], [3]] + - signature: + replacement: [[2, 2], [3, 3]] modules: -- name: math_funcs # Name of the module - source_locations: # Only use decls with hpp files found in these directories. Leave blank for 'cppwg_ALL' - classes: # Classes to include. Blank means none, cppwg_ALL means any found - free_functions: cppwg_ALL # Free functions to include. Blank means none, cppwg_ALL means any found -- name: geometry - source_locations: - classes: - - name: Point -- name: primitives - source_locations: - classes: - - name: Shape - - name: Cuboid - - name: Rectangle -- name: mesh - source_locations: - classes: - - name: Mesh - \ No newline at end of file + - name: math_funcs # Name of the module + source_locations: # Only use decls with hpp files found in these directories. Leave blank for 'cppwg_ALL' + classes: # Classes to include. Blank means none, cppwg_ALL means any found + free_functions: cppwg_ALL # Free functions to include. Blank means none, cppwg_ALL means any found + - name: geometry + source_locations: + classes: + - name: Point + - name: primitives + source_locations: + classes: + - name: Shape + - name: Cuboid + - name: Rectangle + - name: mesh + source_locations: + classes: + - name: Mesh diff --git a/examples/shapes/wrapper/wrapper_header_collection.hpp b/examples/shapes/wrapper/wrapper_header_collection.hpp index 61f5c89..ed3571b 100644 --- a/examples/shapes/wrapper/wrapper_header_collection.hpp +++ b/examples/shapes/wrapper/wrapper_header_collection.hpp @@ -2,6 +2,7 @@ #define pyshapes_HEADERS_HPP_ // Includes +#include "Mesh.hpp" #include "SimpleMathFunctions.hpp" #include "Shape.hpp" #include "Cuboid.hpp" @@ -13,6 +14,8 @@ template class Point<2>; template class Point<3>; template class Shape<2>; template class Shape<3>; +template class Mesh<2,2>; +template class Mesh<3,3>; // Typedefs for nicer naming namespace cppwg @@ -21,6 +24,8 @@ typedef Point<2> Point2; typedef Point<3> Point3; typedef Shape<2> Shape2; typedef Shape<3> Shape3; +typedef Mesh<2,2> Mesh2_2; +typedef Mesh<3,3> Mesh3_3; } // namespace cppwg #endif // pyshapes_HEADERS_HPP_ From 113ecc730d9115643a804d77a782c58b516ad755 Mon Sep 17 00:00:00 2001 From: Kwabena N Amponsah Date: Wed, 1 May 2024 16:22:57 +0000 Subject: [PATCH 05/11] #3 clarify decl setting for class info --- cppwg/generators.py | 74 ++++++++++++++++------------------ cppwg/writers/class_writer.py | 33 +++++++++------ cppwg/writers/module_writer.py | 27 +++++-------- 3 files changed, 64 insertions(+), 70 deletions(-) diff --git a/cppwg/generators.py b/cppwg/generators.py index 86dcd10..5dc516f 100644 --- a/cppwg/generators.py +++ b/cppwg/generators.py @@ -249,69 +249,60 @@ def parse_package_info(self) -> None: # If no package info file exists, create a PackageInfo object with default settings self.package_info = PackageInfo("cppwg_package", self.source_root) - def update_class_info(self) -> None: + def add_discovered_classes(self) -> None: """ - Add decls to class info objects. + Add discovered classes. - Update the class info with class declarations parsed by pygccxml from - the C++ source code. + Add class info objects for classes discovered by pygccxml from + parsing the C++ source code. This is run for modules which set + `use_all_classes` to True. No class info objects were created for + those modules while parsing the package info yaml file. """ for module_info in self.package_info.module_info_collection: if module_info.use_all_classes: - # Create class info objects for all class declarations found - # from parsing the source code with pygccxml. - # Note: as module_info.use_all_classes == True, no class info - # objects were created while parsing the package info yaml file. class_decls = self.source_ns.classes(allow_empty=True) + for class_decl in class_decls: if module_info.is_decl_in_source_path(class_decl): class_info = CppClassInfo(class_decl.name) + class_info.update_short_names() + class_info.update_full_names() class_info.module_info = module_info - class_info.decl = class_decl module_info.class_info_collection.append(class_info) - else: - # As module_info.use_all_classes == False, class info objects - # have already been created while parsing the package info file. - # We only need to add the decl from pygccxml's output. - for class_info in module_info.class_info_collection: - class_decls = self.source_ns.classes( - class_info.name, allow_empty=True - ) - if len(class_decls) == 1: - class_info.decl = class_decls[0] - - def update_free_function_info(self) -> None: + def add_discovered_free_functions(self) -> None: """ - Add decls to free function info objects. + Add discovered free function. - Update the free function info with declarations parsed by pygccxml from - the C++ source code. + Add free function info objects discovered by pygccxml from + parsing the C++ source code. This is run for modules which set + `use_all_free_functions` to True. No free function info objects were + created for those modules while parsing the package info yaml file. """ for module_info in self.package_info.module_info_collection: if module_info.use_all_free_functions: - # Create free function info objects for all free function - # declarations found from parsing the source code with pygccxml. - # Note: as module_info.use_all_free_functions == True, no class info - # objects were created while parsing the package info yaml file. free_functions = self.source_ns.free_functions(allow_empty=True) + for free_function in free_functions: if module_info.is_decl_in_source_path(free_function): function_info = CppFreeFunctionInfo(free_function.name) function_info.module_info = module_info - function_info.decl = free_function module_info.free_function_info_collection.append(function_info) - else: - # As module_info.use_all_free_functions == False, free function - # info objects have already been created while parsing the - # package info file. We only need to add the decl from pygccxml's output. + def add_free_function_decls(self) -> None: + """ + Add declarations to free function info objects. + + Update all free function info objects with their corresponding + declarations found by pygccxml in the C++ source code. + """ + for module_info in self.package_info.module_info_collection: + if module_info.use_all_free_functions: for free_function_info in module_info.free_function_info_collection: - free_functions = self.source_ns.free_functions( + decls = self.source_ns.free_functions( free_function_info.name, allow_empty=True ) - if len(free_functions) == 1: - free_function_info.decl = free_functions[0] + free_function_info.decl = decls[0] def write_header_collection(self) -> None: """Write the header collection to file.""" @@ -353,11 +344,14 @@ def generate_wrapper(self) -> None: # Parse the headers with pygccxml and castxml self.parse_header_collection() - # Update the Class Info from the parsed code - self.update_class_info() + # Add discovered classes from the parsed code + self.add_discovered_classes() + + # Add discovered free functions from the parsed code + self.add_discovered_free_functions() - # Update the Free Function Info from the parsed code - self.update_free_function_info() + # Add declarations to free function info objects + self.add_free_function_decls() # Write all the wrappers required self.write_wrappers() diff --git a/cppwg/writers/class_writer.py b/cppwg/writers/class_writer.py index b1b1cc2..7917b68 100644 --- a/cppwg/writers/class_writer.py +++ b/cppwg/writers/class_writer.py @@ -5,10 +5,7 @@ from typing import Dict, List from pygccxml import declarations -from pygccxml.declarations.calldef_members import member_function_t -from pygccxml.declarations.class_declaration import class_t -from cppwg.input.class_info import CppClassInfo from cppwg.utils.constants import ( CPPWG_CLASS_OVERRIDE_SUFFIX, CPPWG_EXT, @@ -25,6 +22,8 @@ class CppClassWrapperWriter(CppBaseWrapperWriter): Attributes ---------- + source_ns : pygccxml.declarations.namespace_t + The pygccxml namespace containing declarations from the source code class_info : CppClassInfo The class information wrapper_templates : Dict[str, str] @@ -35,7 +34,7 @@ class CppClassWrapperWriter(CppBaseWrapperWriter): A list of full names for this class e.g. ["Foo<2,2>", "Foo<3,3>"] class_short_names : List[str] A list of short names for this class e.g. ["Foo2_2", "Foo3_3"] - class_decls : List[class_t] + class_decls : List[pygccxml.declarations.class_t] A list of class declarations associated with the class has_shared_ptr : bool Whether the class uses shared pointers @@ -49,7 +48,8 @@ class CppClassWrapperWriter(CppBaseWrapperWriter): def __init__( self, - class_info: CppClassInfo, + source_ns: "namespace_t", # noqa: F821 + class_info: "CppClassInfo", # noqa: F821 wrapper_templates: Dict[str, str], exposed_class_full_names: List[str], ) -> None: @@ -57,7 +57,8 @@ def __init__( super(CppClassWrapperWriter, self).__init__(wrapper_templates) - self.class_info: CppClassInfo = class_info + self.source_ns: "namespace_t" = source_ns # noqa: F821 + self.class_info: "CppClassInfo" = class_info # noqa: F821 # Class full names eg. ["Foo<2,2>", "Foo<3,3>"] self.class_full_names: List[str] = self.class_info.full_names @@ -69,9 +70,17 @@ def __init__( logger.error("Full and short name lists should be the same length") raise AssertionError() + # Get the declaration for each class + self.class_decls: List["class_t"] = [] # noqa: F821 + + for full_name in self.class_info.full_names: + name = full_name.replace(" ", "") # e.g. Foo<2,2> + + class_decl: "class_t" = self.source_ns.class_(name) # noqa: F821 + self.class_decls.append(class_decl) + self.exposed_class_full_names: List[str] = exposed_class_full_names - self.class_decls: List[class_t] = [] self.has_shared_ptr: bool = True self.is_abstract: bool = False # TODO: Consider removing unused attribute @@ -161,8 +170,8 @@ def add_cpp_header(self, class_full_name: str, class_short_name: str) -> None: ) def add_virtual_overrides( - self, class_decl: class_t, short_class_name: str - ) -> List[member_function_t]: + self, class_decl: "class_t", short_class_name: str # noqa: F821 + ) -> List["member_function_t"]: # noqa: F821 """ Add virtual "trampoline" overrides for the class. @@ -178,9 +187,9 @@ def add_virtual_overrides( Returns ------- - list[member_function_t]: A list of member functions needing override + list[pygccxml.declarations.member_function_t]: A list of member functions needing override """ - methods_needing_override: List[member_function_t] = [] + methods_needing_override: List["member_function_t"] = [] # noqa: F821 return_types: List[str] = [] # e.g. ["void", "unsigned int", "::Bar<2> *"] # Collect all virtual methods and their return types @@ -297,7 +306,7 @@ def write(self, work_dir: str) -> None: continue # Find and define virtual function "trampoline" overrides - methods_needing_override: List[member_function_t] = ( + methods_needing_override: List["member_function_t"] = ( # noqa: F821 self.add_virtual_overrides(class_decl, short_name) ) diff --git a/cppwg/writers/module_writer.py b/cppwg/writers/module_writer.py index 7c0115f..bfd9800 100644 --- a/cppwg/writers/module_writer.py +++ b/cppwg/writers/module_writer.py @@ -4,10 +4,6 @@ import os from typing import Dict, List -from pygccxml.declarations.class_declaration import class_t -from pygccxml.declarations.namespace import namespace_t - -from cppwg.input.module_info import ModuleInfo from cppwg.utils.constants import CPPWG_EXT, CPPWG_HEADER_COLLECTION_FILENAME from cppwg.writers.class_writer import CppClassWrapperWriter from cppwg.writers.free_function_writer import CppFreeFunctionWrapperWriter @@ -24,7 +20,7 @@ class CppModuleWrapperWriter: Attributes ---------- - source_ns : namespace_t + source_ns : pygccxml.declarations.namespace_t The pygccxml namespace containing declarations from the source code module_info : ModuleInfo The module information to generate Python bindings for @@ -40,14 +36,14 @@ class CppModuleWrapperWriter: def __init__( self, - source_ns: namespace_t, - module_info: ModuleInfo, + source_ns: "namespace_t", # noqa: F821 + module_info: "ModuleInfo", # noqa: F821 wrapper_templates: Dict[str, str], wrapper_root: str, package_license: str = "", ): - self.source_ns: namespace_t = source_ns - self.module_info: ModuleInfo = module_info + self.source_ns: "namespace_t" = source_ns # noqa: F821 + self.module_info: "ModuleInfo" = module_info # noqa: F821 self.wrapper_templates: Dict[str, str] = wrapper_templates self.wrapper_root: str = wrapper_root self.package_license: str = ( @@ -143,17 +139,12 @@ def write_class_wrappers(self) -> None: logger.info(f"Generating wrapper for class {class_info.name}") class_writer = CppClassWrapperWriter( - class_info, self.wrapper_templates, self.exposed_class_full_names + self.source_ns, + class_info, + self.wrapper_templates, + self.exposed_class_full_names, ) - # Get the declaration for each class and add it to the class writer - # TODO: Consider using class_info.decl instead - for full_name in class_info.full_names: - name = full_name.replace(" ", "") # e.g. Foo<2,2> - - class_decl: class_t = self.source_ns.class_(name) - class_writer.class_decls.append(class_decl) - # Write the class wrappers into /path/to/wrapper_root/modulename/ module_dir = os.path.join(self.wrapper_root, self.module_info.name) class_writer.write(module_dir) From 243f1bd3494e91ab1bee4c304c5514a665d98aeb Mon Sep 17 00:00:00 2001 From: Kwabena N Amponsah Date: Wed, 1 May 2024 18:14:18 +0000 Subject: [PATCH 06/11] #3 add workaround for default template args --- cppwg/generators.py | 71 ++++++++++++++++++++------- cppwg/input/class_info.py | 31 +++++------- cppwg/input/cpp_type_info.py | 13 ++--- cppwg/input/info_helper.py | 12 ++--- cppwg/writers/class_writer.py | 37 +++----------- cppwg/writers/free_function_writer.py | 8 +-- cppwg/writers/module_writer.py | 1 - 7 files changed, 90 insertions(+), 83 deletions(-) diff --git a/cppwg/generators.py b/cppwg/generators.py index 5dc516f..9e0bf68 100644 --- a/cppwg/generators.py +++ b/cppwg/generators.py @@ -7,10 +7,9 @@ import subprocess import uuid from pathlib import Path -from typing import List, Optional +from typing import Any, Dict, List, Optional -import pygccxml.utils -from pygccxml import __version__ as pygccxml_version +import pygccxml from cppwg.input.class_info import CppClassInfo from cppwg.input.free_function_info import CppFreeFunctionInfo @@ -95,7 +94,7 @@ def __init__( r"castxml version \d+\.\d+\.\d+", castxml_version ).group(0) logger.info(castxml_version) - logger.info(f"pygccxml version {pygccxml_version}") + logger.info(f"pygccxml version {pygccxml.version}") # Sanitize castxml_cflags self.castxml_cflags: str = "" @@ -203,8 +202,7 @@ def extract_templates_from_source(self) -> None: info_helper = CppInfoHelper(module_info) for class_info in module_info.class_info_collection: info_helper.extract_templates_from_source(class_info) - class_info.update_short_names() - class_info.update_full_names() + class_info.update_names() def map_classes_to_hpp_files(self) -> None: """ @@ -265,11 +263,50 @@ def add_discovered_classes(self) -> None: for class_decl in class_decls: if module_info.is_decl_in_source_path(class_decl): class_info = CppClassInfo(class_decl.name) - class_info.update_short_names() - class_info.update_full_names() + class_info.update_names() class_info.module_info = module_info module_info.class_info_collection.append(class_info) + def add_class_decls(self) -> None: + """ + Add declarations to class info objects. + + Update all class info objects with their corresponding + declarations found by pygccxml in the C++ source code. + """ + for module_info in self.package_info.module_info_collection: + for class_info in module_info.class_info_collection: + class_info.decls: List["class_t"] = [] # noqa: F821 + + for full_name in class_info.full_names: + decl_name = full_name.replace(" ", "") # e.g. Foo<2,2> + + try: + class_decl = self.source_ns.class_(decl_name) + + except pygccxml.declarations.runtime_errors.declaration_not_found_t: + if "=" in class_info.template_signature: + # Try to find the class without default template args + # e.g. for template class Foo {}; + # convert Foo<2,2> -> Foo<2 > + pos = 0 + for i, s in enumerate( + class_info.template_signature.split(",") + ): + if "=" in s: + pos = i + break + + decl_name = ",".join(decl_name.split(",")[0:pos]) + " >" + class_decl = self.source_ns.class_(decl_name) + + else: + logging.error( + f"Could not find class declaration for {decl_name}" + ) + + class_info.decls.append(class_decl) + def add_discovered_free_functions(self) -> None: """ Add discovered free function. @@ -285,9 +322,9 @@ def add_discovered_free_functions(self) -> None: for free_function in free_functions: if module_info.is_decl_in_source_path(free_function): - function_info = CppFreeFunctionInfo(free_function.name) - function_info.module_info = module_info - module_info.free_function_info_collection.append(function_info) + ff_info = CppFreeFunctionInfo(free_function.name) + ff_info.module_info = module_info + module_info.free_function_info_collection.append(ff_info) def add_free_function_decls(self) -> None: """ @@ -297,12 +334,9 @@ def add_free_function_decls(self) -> None: declarations found by pygccxml in the C++ source code. """ for module_info in self.package_info.module_info_collection: - if module_info.use_all_free_functions: - for free_function_info in module_info.free_function_info_collection: - decls = self.source_ns.free_functions( - free_function_info.name, allow_empty=True - ) - free_function_info.decl = decls[0] + for ff_info in module_info.free_function_info_collection: + decls = self.source_ns.free_functions(ff_info.name, allow_empty=True) + ff_info.decls = [decls[0]] def write_header_collection(self) -> None: """Write the header collection to file.""" @@ -347,6 +381,9 @@ def generate_wrapper(self) -> None: # Add discovered classes from the parsed code self.add_discovered_classes() + # Add declarations to class info objects + self.add_class_decls() + # Add discovered free functions from the parsed code self.add_discovered_free_functions() diff --git a/cppwg/input/class_info.py b/cppwg/input/class_info.py index 31f120c..8622c29 100644 --- a/cppwg/input/class_info.py +++ b/cppwg/input/class_info.py @@ -24,21 +24,16 @@ def __init__(self, name: str, class_config: Optional[Dict[str, Any]] = None): self.full_names: List[str] = None self.short_names: List[str] = None - def update_short_names(self): + def update_short_names(self) -> None: """ - Get the Python name for the class e.g. Foo2_2. + Set the Python names for the class, accounting for template args. - Return the name of the class as it will appear on the Python side. This + Set the name of the class as it will appear on the Python side. This collapses template arguments, separating them by underscores and removes special characters. The return type is a list, as a class can have multiple names if it is templated. For example, a class "Foo" with template arguments [[2, 2], [3, 3]] will have a short name list ["Foo2_2", "Foo3_3"]. - - Returns - ------- - List[str] - The list of short names """ # Handles untemplated classes if self.template_arg_lists is None: @@ -96,20 +91,15 @@ def update_short_names(self): self.short_names.append(type_name + template_string) - def update_full_names(self): + def update_full_names(self) -> None: """ - Get the C++ name for the class e.g. Foo<2,2>. + Set the C++ names for the class, accounting for template args. - Return the name (declaration) of the class as it appears on the C++ - side. The return type is a list, as a class can have multiple names - (declarations) if it is templated. For example, a class "Foo" with + Set the name of the class as it should appear in C++. + The return type is a list, as a class can have multiple names + if it is templated. For example, a class "Foo" with template arguments [[2, 2], [3, 3]] will have a full name list ["Foo<2,2 >", "Foo<3,3 >"]. - - Returns - ------- - List[str] - The list of full names """ # Handles untemplated classes if self.template_arg_lists is None: @@ -125,6 +115,11 @@ def update_full_names(self): # Join full name e.g. "Foo<2,2 >" self.full_names.append(self.name + template_string) + def update_names(self) -> None: + """Update the full and short names for the class.""" + self.update_full_names() + self.update_short_names() + @property def parent(self) -> "ModuleInfo": # noqa: F821 """Returns the parent module info object.""" diff --git a/cppwg/input/cpp_type_info.py b/cppwg/input/cpp_type_info.py index 0165a41..d5d7578 100644 --- a/cppwg/input/cpp_type_info.py +++ b/cppwg/input/cpp_type_info.py @@ -2,8 +2,6 @@ from typing import Any, Dict, List, Optional -from pygccxml.declarations import declaration_t - from cppwg.input.base_info import BaseInfo @@ -21,10 +19,12 @@ class CppTypeInfo(BaseInfo): The full path to the source file containing the type name_override : str The name override specified in config e.g. "CustomFoo" -> "Foo" + template_signature : str + The template signature of the type e.g. "" template_arg_lists : List[List[Any]] List of template replacement arguments for the type e.g. [[2, 2], [3, 3]] - decl : declaration_t - The pygccxml declaration associated with this type + decls : pygccxml.declarations.declaration_t + The pygccxml declarations associated with this type, one per template arg if templated """ def __init__(self, name: str, type_config: Optional[Dict[str, Any]] = None): @@ -35,8 +35,9 @@ def __init__(self, name: str, type_config: Optional[Dict[str, Any]] = None): self.source_file_full_path: Optional[str] = None self.source_file: Optional[str] = None self.name_override: Optional[str] = None - self.template_arg_lists: Optional[list[List[Any]]] = None - self.decl: Optional[declaration_t] = None + self.template_signature: Optional[str] = None + self.template_arg_lists: Optional[List[List[Any]]] = None + self.decls: Optional[List["declaration_t"]] = None # noqa: F821 if type_config: for key, value in type_config.items(): diff --git a/cppwg/input/info_helper.py b/cppwg/input/info_helper.py index 9d1671f..9b81d5a 100644 --- a/cppwg/input/info_helper.py +++ b/cppwg/input/info_helper.py @@ -77,11 +77,6 @@ def extract_templates_from_source(self, feature_info: BaseInfo) -> None: if len(template_substitutions) == 0: return - # Remove spaces from template signatures - # e.g. -> - for tpl_sub in template_substitutions: - tpl_sub["signature"] = tpl_sub["signature"].replace(" ", "") - # Remove whitespaces, blank lines, and directives from the source file whitespace_regex = re.compile(r"\s+") with open(source_path, "r") as in_file: @@ -95,7 +90,9 @@ def extract_templates_from_source(self, feature_info: BaseInfo) -> None: for template_substitution in template_substitutions: # e.g. template - signature: str = "template" + template_substitution["signature"] + signature: str = "template" + template_substitution[ + "signature" + ].replace(" ", "") # e.g. [[2,2], [3,3]] replacement: List[List[Any]] = template_substitution["replacement"] @@ -134,4 +131,7 @@ def extract_templates_from_source(self, feature_info: BaseInfo) -> None: if declaration_found: feature_info.template_arg_lists = replacement + feature_info.template_signature = template_substitution[ + "signature" + ] break diff --git a/cppwg/writers/class_writer.py b/cppwg/writers/class_writer.py index 7917b68..32c21bb 100644 --- a/cppwg/writers/class_writer.py +++ b/cppwg/writers/class_writer.py @@ -22,20 +22,12 @@ class CppClassWrapperWriter(CppBaseWrapperWriter): Attributes ---------- - source_ns : pygccxml.declarations.namespace_t - The pygccxml namespace containing declarations from the source code class_info : CppClassInfo The class information wrapper_templates : Dict[str, str] String templates with placeholders for generating wrapper code exposed_class_full_names : List[str] A list of full names for all classes in the module - class_full_names : List[str] - A list of full names for this class e.g. ["Foo<2,2>", "Foo<3,3>"] - class_short_names : List[str] - A list of short names for this class e.g. ["Foo2_2", "Foo3_3"] - class_decls : List[pygccxml.declarations.class_t] - A list of class declarations associated with the class has_shared_ptr : bool Whether the class uses shared pointers is_abstract : bool @@ -48,7 +40,6 @@ class CppClassWrapperWriter(CppBaseWrapperWriter): def __init__( self, - source_ns: "namespace_t", # noqa: F821 class_info: "CppClassInfo", # noqa: F821 wrapper_templates: Dict[str, str], exposed_class_full_names: List[str], @@ -57,28 +48,12 @@ def __init__( super(CppClassWrapperWriter, self).__init__(wrapper_templates) - self.source_ns: "namespace_t" = source_ns # noqa: F821 self.class_info: "CppClassInfo" = class_info # noqa: F821 - # Class full names eg. ["Foo<2,2>", "Foo<3,3>"] - self.class_full_names: List[str] = self.class_info.full_names - - # Class short names eg. ["Foo2_2", "Foo3_3"] - self.class_short_names: List[str] = self.class_info.short_names - - if len(self.class_full_names) != len(self.class_short_names): + if len(self.class_info.full_names) != len(self.class_info.short_names): logger.error("Full and short name lists should be the same length") raise AssertionError() - # Get the declaration for each class - self.class_decls: List["class_t"] = [] # noqa: F821 - - for full_name in self.class_info.full_names: - name = full_name.replace(" ", "") # e.g. Foo<2,2> - - class_decl: "class_t" = self.source_ns.class_(name) # noqa: F821 - self.class_decls.append(class_decl) - self.exposed_class_full_names: List[str] = exposed_class_full_names self.has_shared_ptr: bool = True @@ -134,7 +109,7 @@ def add_cpp_header(self, class_full_name: str, class_short_name: str) -> None: source_file = self.class_info.source_file if not source_file: - source_file = os.path.basename(self.class_info.decl.location.file_name) + source_file = os.path.basename(self.class_info.decls[0].location.file_name) includes += f'#include "{source_file}"\n' # Check for custom smart pointers e.g. "boost::shared_ptr" @@ -262,13 +237,13 @@ def write(self, work_dir: str) -> None: """ logger = logging.getLogger() - if len(self.class_decls) != len(self.class_full_names): + if len(self.class_info.decls) != len(self.class_info.full_names): logger.error("Not enough class decls added to do write.") raise AssertionError() - for idx, full_name in enumerate(self.class_full_names): - short_name = self.class_short_names[idx] - class_decl = self.class_decls[idx] + for idx, full_name in enumerate(self.class_info.full_names): + short_name = self.class_info.short_names[idx] + class_decl = self.class_info.decls[idx] self.hpp_string = "" self.cpp_string = "" diff --git a/cppwg/writers/free_function_writer.py b/cppwg/writers/free_function_writer.py index 6264404..690c0aa 100644 --- a/cppwg/writers/free_function_writer.py +++ b/cppwg/writers/free_function_writer.py @@ -49,7 +49,7 @@ def generate_wrapper(self) -> str: # e.g. with default values: ', py::arg("foo") = 1, py::arg("bar") = 2' default_args = "" if not self.default_arg_exclusion_criteria(): - for argument in self.free_function_info.decl.arguments: + for argument in self.free_function_info.decls[0].arguments: default_args += f', py::arg("{argument.name}")' if argument.default_value is not None: default_args += f" = {argument.default_value}" @@ -57,7 +57,7 @@ def generate_wrapper(self) -> str: # Add the free function wrapper code to the wrapper string func_dict = { "def_adorn": def_adorn, - "function_name": self.free_function_info.decl.name, + "function_name": self.free_function_info.decls[0].name, "function_docs": '" "', "default_args": default_args, } @@ -75,14 +75,14 @@ def exclusion_criteria(self) -> bool: True if the function should be excluded from wrapper code, False otherwise. """ # Check if any return types are not wrappable - return_type = self.free_function_info.decl.return_type.decl_string.replace( + return_type = self.free_function_info.decls[0].return_type.decl_string.replace( " ", "" ) if return_type in self.exclusion_args: return True # Check if any arguments not wrappable - for decl_arg_type in self.free_function_info.decl.argument_types: + for decl_arg_type in self.free_function_info.decls[0].argument_types: arg_type = decl_arg_type.decl_string.split()[0].replace(" ", "") if arg_type in self.exclusion_args: return True diff --git a/cppwg/writers/module_writer.py b/cppwg/writers/module_writer.py index bfd9800..e2adceb 100644 --- a/cppwg/writers/module_writer.py +++ b/cppwg/writers/module_writer.py @@ -139,7 +139,6 @@ def write_class_wrappers(self) -> None: logger.info(f"Generating wrapper for class {class_info.name}") class_writer = CppClassWrapperWriter( - self.source_ns, class_info, self.wrapper_templates, self.exposed_class_full_names, From 8c8866f39246161168820f4f3456662eec31ceb6 Mon Sep 17 00:00:00 2001 From: Kwabena N Amponsah Date: Wed, 1 May 2024 20:33:48 +0000 Subject: [PATCH 07/11] #3 replace exposed names with class decls --- cppwg/writers/class_writer.py | 17 +++++++++-------- cppwg/writers/module_writer.py | 15 +++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/cppwg/writers/class_writer.py b/cppwg/writers/class_writer.py index 32c21bb..677f44b 100644 --- a/cppwg/writers/class_writer.py +++ b/cppwg/writers/class_writer.py @@ -26,8 +26,8 @@ class CppClassWrapperWriter(CppBaseWrapperWriter): The class information wrapper_templates : Dict[str, str] String templates with placeholders for generating wrapper code - exposed_class_full_names : List[str] - A list of full names for all classes in the module + module_class_decls : List[pygccxml.declarations.class_t] + A list of decls for all classes in the module has_shared_ptr : bool Whether the class uses shared pointers is_abstract : bool @@ -42,7 +42,7 @@ def __init__( self, class_info: "CppClassInfo", # noqa: F821 wrapper_templates: Dict[str, str], - exposed_class_full_names: List[str], + module_class_decls: List["class_t"], # noqa: F821 ) -> None: logger = logging.getLogger() @@ -54,7 +54,7 @@ def __init__( logger.error("Full and short name lists should be the same length") raise AssertionError() - self.exposed_class_full_names: List[str] = exposed_class_full_names + self.module_class_decls: List["class_t"] = module_class_decls # noqa: F821 self.has_shared_ptr: bool = True self.is_abstract: bool = False # TODO: Consider removing unused attribute @@ -109,7 +109,9 @@ def add_cpp_header(self, class_full_name: str, class_short_name: str) -> None: source_file = self.class_info.source_file if not source_file: - source_file = os.path.basename(self.class_info.decls[0].location.file_name) + source_file = os.path.basename( + self.class_info.decls[0].location.file_name + ) includes += f'#include "{source_file}"\n' # Check for custom smart pointers e.g. "boost::shared_ptr" @@ -308,9 +310,8 @@ def write(self, work_dir: str) -> None: if base.access_type == "private": continue - # Check if the base class is exposed (i.e. to be wrapped in the module) - base_class_name: str = base.related_class.name.replace(" ", "") - if base_class_name in self.exposed_class_full_names: + # Check if the base class is also wrapped in the module + if base.related_class in self.module_class_decls: bases += f", {base.related_class.name} " # Add the class registration diff --git a/cppwg/writers/module_writer.py b/cppwg/writers/module_writer.py index e2adceb..8c61984 100644 --- a/cppwg/writers/module_writer.py +++ b/cppwg/writers/module_writer.py @@ -30,8 +30,8 @@ class CppModuleWrapperWriter: The output directory for the generated wrapper code package_license : str The license to include in the generated wrapper code - exposed_class_full_names : List[str] - A list of full names of all classes to be wrapped in the module + class_decls : List[pygccxml.declarations.class_t] + A list of declarations of all classes to be wrapped in the module """ def __init__( @@ -50,13 +50,12 @@ def __init__( package_license # TODO: use this in the generated wrappers ) - # For convenience, create a list of all classes to be wrapped in the module - # e.g. ['Foo', 'Bar<2>', 'Bar<3>'] - self.exposed_class_full_names: List[str] = [] + # For convenience, store a list of declarations of all + # classes to be wrapped in the module + self.class_decls: List["class_t"] = [] # noqa: F821 for class_info in self.module_info.class_info_collection: - for full_name in class_info.full_names: - self.exposed_class_full_names.append(full_name.replace(" ", "")) + self.class_decls.extend(class_info.decls) def write_module_wrapper(self) -> None: """ @@ -141,7 +140,7 @@ def write_class_wrappers(self) -> None: class_writer = CppClassWrapperWriter( class_info, self.wrapper_templates, - self.exposed_class_full_names, + self.class_decls, ) # Write the class wrappers into /path/to/wrapper_root/modulename/ From bed23b60fd1d326ee84cdceaa010f540df33d1eb Mon Sep 17 00:00:00 2001 From: Kwabena N Amponsah Date: Wed, 1 May 2024 20:36:22 +0000 Subject: [PATCH 08/11] #3 update mesh example --- examples/shapes/CMakeLists.txt | 2 +- examples/shapes/src/mesh/AbstractMesh.cpp | 26 +++++++++++++ .../src/mesh/{Mesh.hpp => AbstractMesh.hpp} | 13 +++++-- examples/shapes/src/mesh/ConcreteMesh.cpp | 19 +++++++++ examples/shapes/src/mesh/ConcreteMesh.hpp | 29 ++++++++++++++ examples/shapes/src/mesh/Mesh.cpp | 26 ------------- .../src/python/pyshapes/mesh/__init__.py | 2 + .../shapes/src/python/test/test_classes.py | 8 ++++ .../wrapper/mesh/AbstractMesh2_2.cppwg.cpp | 39 +++++++++++++++++++ .../wrapper/mesh/AbstractMesh2_2.cppwg.hpp | 7 ++++ .../wrapper/mesh/AbstractMesh3_3.cppwg.cpp | 39 +++++++++++++++++++ .../wrapper/mesh/AbstractMesh3_3.cppwg.hpp | 7 ++++ .../wrapper/mesh/ConcreteMesh2.cppwg.cpp | 31 +++++++++++++++ .../wrapper/mesh/ConcreteMesh2.cppwg.hpp | 7 ++++ .../wrapper/mesh/ConcreteMesh3.cppwg.cpp | 31 +++++++++++++++ .../wrapper/mesh/ConcreteMesh3.cppwg.hpp | 7 ++++ examples/shapes/wrapper/mesh/mesh.main.cpp | 12 ++++-- examples/shapes/wrapper/package_info.yaml | 3 +- .../wrapper/wrapper_header_collection.hpp | 15 ++++--- 19 files changed, 282 insertions(+), 41 deletions(-) create mode 100644 examples/shapes/src/mesh/AbstractMesh.cpp rename examples/shapes/src/mesh/{Mesh.hpp => AbstractMesh.hpp} (72%) create mode 100644 examples/shapes/src/mesh/ConcreteMesh.cpp create mode 100644 examples/shapes/src/mesh/ConcreteMesh.hpp delete mode 100644 examples/shapes/src/mesh/Mesh.cpp create mode 100644 examples/shapes/src/python/pyshapes/mesh/__init__.py create mode 100644 examples/shapes/wrapper/mesh/AbstractMesh2_2.cppwg.cpp create mode 100644 examples/shapes/wrapper/mesh/AbstractMesh2_2.cppwg.hpp create mode 100644 examples/shapes/wrapper/mesh/AbstractMesh3_3.cppwg.cpp create mode 100644 examples/shapes/wrapper/mesh/AbstractMesh3_3.cppwg.hpp create mode 100644 examples/shapes/wrapper/mesh/ConcreteMesh2.cppwg.cpp create mode 100644 examples/shapes/wrapper/mesh/ConcreteMesh2.cppwg.hpp create mode 100644 examples/shapes/wrapper/mesh/ConcreteMesh3.cppwg.cpp create mode 100644 examples/shapes/wrapper/mesh/ConcreteMesh3.cppwg.hpp diff --git a/examples/shapes/CMakeLists.txt b/examples/shapes/CMakeLists.txt index a77ea0f..23161ca 100644 --- a/examples/shapes/CMakeLists.txt +++ b/examples/shapes/CMakeLists.txt @@ -35,7 +35,7 @@ target_link_libraries(_pyshapes_math_funcs PRIVATE pybind11::module shapes) file(GLOB MODULE_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/wrapper/mesh/*.cpp) add_library(_pyshapes_mesh SHARED ${MODULE_SOURCES}) -target_link_libraries(_pyshapes_mesh PRIVATE pybind11::module mesh) +target_link_libraries(_pyshapes_mesh PRIVATE pybind11::module shapes) file(GLOB MODULE_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/wrapper/geometry/*.cpp) add_library(_pyshapes_geometry SHARED ${MODULE_SOURCES}) diff --git a/examples/shapes/src/mesh/AbstractMesh.cpp b/examples/shapes/src/mesh/AbstractMesh.cpp new file mode 100644 index 0000000..d03d193 --- /dev/null +++ b/examples/shapes/src/mesh/AbstractMesh.cpp @@ -0,0 +1,26 @@ +#include "AbstractMesh.hpp" + +template +AbstractMesh::AbstractMesh() : mIndex(0) +{ +} + +template +AbstractMesh::~AbstractMesh() +{ +} + +template +unsigned AbstractMesh::GetIndex() const +{ + return mIndex; +} + +template +void AbstractMesh::SetIndex(unsigned index) +{ + mIndex = index; +} + +template class AbstractMesh<2, 2>; +template class AbstractMesh<3, 3>; diff --git a/examples/shapes/src/mesh/Mesh.hpp b/examples/shapes/src/mesh/AbstractMesh.hpp similarity index 72% rename from examples/shapes/src/mesh/Mesh.hpp rename to examples/shapes/src/mesh/AbstractMesh.hpp index e1f6b57..91817f4 100644 --- a/examples/shapes/src/mesh/Mesh.hpp +++ b/examples/shapes/src/mesh/AbstractMesh.hpp @@ -5,11 +5,11 @@ * A mesh in SPACE_DIM space with ELEMENT_DIM dimensional elements */ template -class Mesh +class AbstractMesh { private: /** - * Mesh index + * AbstractMesh index */ unsigned mIndex; @@ -17,12 +17,12 @@ class Mesh /** * Default Constructor */ - Mesh(); + AbstractMesh(); /** * Destructor */ - ~Mesh(); + ~AbstractMesh(); /** * Return the index @@ -33,6 +33,11 @@ class Mesh * Set the index */ void SetIndex(unsigned index); + + /** + * Scale the mesh by a factor + */ + virtual void Scale(const double factor) = 0; }; #endif // _MESH_HPP diff --git a/examples/shapes/src/mesh/ConcreteMesh.cpp b/examples/shapes/src/mesh/ConcreteMesh.cpp new file mode 100644 index 0000000..65cee4f --- /dev/null +++ b/examples/shapes/src/mesh/ConcreteMesh.cpp @@ -0,0 +1,19 @@ +#include "AbstractMesh.hpp" +#include "ConcreteMesh.hpp" + +template +ConcreteMesh::ConcreteMesh() : AbstractMesh() +{ +} + +template +ConcreteMesh::~ConcreteMesh() +{ +} +template +void ConcreteMesh::Scale(const double factor){ + // Scale the mesh +}; + +template class ConcreteMesh<2>; +template class ConcreteMesh<3>; diff --git a/examples/shapes/src/mesh/ConcreteMesh.hpp b/examples/shapes/src/mesh/ConcreteMesh.hpp new file mode 100644 index 0000000..84d917e --- /dev/null +++ b/examples/shapes/src/mesh/ConcreteMesh.hpp @@ -0,0 +1,29 @@ +#ifndef _CONCRETE_MESH_HPP +#define _CONCRETE_MESH_HPP + +#include "AbstractMesh.hpp" + +/** + * A concrete mesh implementation + */ +template +class ConcreteMesh : public AbstractMesh +{ +public: + /** + * Default Constructor + */ + ConcreteMesh(); + + /** + * Destructor + */ + ~ConcreteMesh(); + + /** + * Scale the mesh by a factor + */ + void Scale(const double factor) override; +}; + +#endif // _CONCRETE_MESH_HPP diff --git a/examples/shapes/src/mesh/Mesh.cpp b/examples/shapes/src/mesh/Mesh.cpp deleted file mode 100644 index 6efea8c..0000000 --- a/examples/shapes/src/mesh/Mesh.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "Mesh.hpp" - -template -Mesh::Mesh() : mIndex(0) -{ -} - -template -Mesh::~Mesh() -{ -} - -template -unsigned Mesh::GetIndex() const -{ - return mIndex; -} - -template -void Mesh::SetIndex(unsigned index) -{ - mIndex = index; -} - -template class Mesh<2, 2>; -template class Mesh<3, 3>; diff --git a/examples/shapes/src/python/pyshapes/mesh/__init__.py b/examples/shapes/src/python/pyshapes/mesh/__init__.py new file mode 100644 index 0000000..2dc4b4e --- /dev/null +++ b/examples/shapes/src/python/pyshapes/mesh/__init__.py @@ -0,0 +1,2 @@ +# Bring in everything from the shared module +from pyshapes.mesh._pyshapes_mesh import * diff --git a/examples/shapes/src/python/test/test_classes.py b/examples/shapes/src/python/test/test_classes.py index 4c37616..7e09511 100644 --- a/examples/shapes/src/python/test/test_classes.py +++ b/examples/shapes/src/python/test/test_classes.py @@ -2,6 +2,7 @@ import pyshapes.geometry import pyshapes.primitives +import pyshapes.mesh class TestClasses(unittest.TestCase): @@ -22,6 +23,13 @@ def testGeometry(self): cuboid = pyshapes.primitives.Cuboid(5.0, 10.0, 20.0) self.assertTrue(len(cuboid.rGetVertices()) == 8) + def testMesh(self): + + cmesh = pyshapes.mesh.ConcreteMesh2() + self.assertTrue(cmesh.GetIndex() == 0) + + cmesh.SetIndex(1) + self.assertTrue(cmesh.GetIndex() == 1) if __name__ == "__main__": unittest.main() diff --git a/examples/shapes/wrapper/mesh/AbstractMesh2_2.cppwg.cpp b/examples/shapes/wrapper/mesh/AbstractMesh2_2.cppwg.cpp new file mode 100644 index 0000000..8706232 --- /dev/null +++ b/examples/shapes/wrapper/mesh/AbstractMesh2_2.cppwg.cpp @@ -0,0 +1,39 @@ +#include +#include +#include "wrapper_header_collection.hpp" + +#include "AbstractMesh2_2.cppwg.hpp" + +namespace py = pybind11; +typedef AbstractMesh<2,2 > AbstractMesh2_2; +PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); + +class AbstractMesh2_2_Overrides : public AbstractMesh2_2{ + public: + using AbstractMesh2_2::AbstractMesh; + void Scale(double const factor) override { + PYBIND11_OVERRIDE_PURE( + void, + AbstractMesh2_2, + Scale, + factor); + } + +}; +void register_AbstractMesh2_2_class(py::module &m){ +py::class_ >(m, "AbstractMesh2_2") + .def(py::init< >()) + .def( + "GetIndex", + (unsigned int(AbstractMesh2_2::*)() const ) &AbstractMesh2_2::GetIndex, + " " ) + .def( + "SetIndex", + (void(AbstractMesh2_2::*)(unsigned int)) &AbstractMesh2_2::SetIndex, + " " , py::arg("index") ) + .def( + "Scale", + (void(AbstractMesh2_2::*)(double const)) &AbstractMesh2_2::Scale, + " " , py::arg("factor") ) + ; +} diff --git a/examples/shapes/wrapper/mesh/AbstractMesh2_2.cppwg.hpp b/examples/shapes/wrapper/mesh/AbstractMesh2_2.cppwg.hpp new file mode 100644 index 0000000..9cef714 --- /dev/null +++ b/examples/shapes/wrapper/mesh/AbstractMesh2_2.cppwg.hpp @@ -0,0 +1,7 @@ +#ifndef AbstractMesh2_2_hpp__cppwg_wrapper +#define AbstractMesh2_2_hpp__cppwg_wrapper + +#include + +void register_AbstractMesh2_2_class(pybind11::module &m); +#endif // AbstractMesh2_2_hpp__cppwg_wrapper diff --git a/examples/shapes/wrapper/mesh/AbstractMesh3_3.cppwg.cpp b/examples/shapes/wrapper/mesh/AbstractMesh3_3.cppwg.cpp new file mode 100644 index 0000000..a5eea68 --- /dev/null +++ b/examples/shapes/wrapper/mesh/AbstractMesh3_3.cppwg.cpp @@ -0,0 +1,39 @@ +#include +#include +#include "wrapper_header_collection.hpp" + +#include "AbstractMesh3_3.cppwg.hpp" + +namespace py = pybind11; +typedef AbstractMesh<3,3 > AbstractMesh3_3; +PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); + +class AbstractMesh3_3_Overrides : public AbstractMesh3_3{ + public: + using AbstractMesh3_3::AbstractMesh; + void Scale(double const factor) override { + PYBIND11_OVERRIDE_PURE( + void, + AbstractMesh3_3, + Scale, + factor); + } + +}; +void register_AbstractMesh3_3_class(py::module &m){ +py::class_ >(m, "AbstractMesh3_3") + .def(py::init< >()) + .def( + "GetIndex", + (unsigned int(AbstractMesh3_3::*)() const ) &AbstractMesh3_3::GetIndex, + " " ) + .def( + "SetIndex", + (void(AbstractMesh3_3::*)(unsigned int)) &AbstractMesh3_3::SetIndex, + " " , py::arg("index") ) + .def( + "Scale", + (void(AbstractMesh3_3::*)(double const)) &AbstractMesh3_3::Scale, + " " , py::arg("factor") ) + ; +} diff --git a/examples/shapes/wrapper/mesh/AbstractMesh3_3.cppwg.hpp b/examples/shapes/wrapper/mesh/AbstractMesh3_3.cppwg.hpp new file mode 100644 index 0000000..a583113 --- /dev/null +++ b/examples/shapes/wrapper/mesh/AbstractMesh3_3.cppwg.hpp @@ -0,0 +1,7 @@ +#ifndef AbstractMesh3_3_hpp__cppwg_wrapper +#define AbstractMesh3_3_hpp__cppwg_wrapper + +#include + +void register_AbstractMesh3_3_class(pybind11::module &m); +#endif // AbstractMesh3_3_hpp__cppwg_wrapper diff --git a/examples/shapes/wrapper/mesh/ConcreteMesh2.cppwg.cpp b/examples/shapes/wrapper/mesh/ConcreteMesh2.cppwg.cpp new file mode 100644 index 0000000..9a33857 --- /dev/null +++ b/examples/shapes/wrapper/mesh/ConcreteMesh2.cppwg.cpp @@ -0,0 +1,31 @@ +#include +#include +#include "wrapper_header_collection.hpp" + +#include "ConcreteMesh2.cppwg.hpp" + +namespace py = pybind11; +typedef ConcreteMesh<2 > ConcreteMesh2; +PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); + +class ConcreteMesh2_Overrides : public ConcreteMesh2{ + public: + using ConcreteMesh2::ConcreteMesh; + void Scale(double const factor) override { + PYBIND11_OVERRIDE( + void, + ConcreteMesh2, + Scale, + factor); + } + +}; +void register_ConcreteMesh2_class(py::module &m){ +py::class_ , AbstractMesh<2> >(m, "ConcreteMesh2") + .def(py::init< >()) + .def( + "Scale", + (void(ConcreteMesh2::*)(double const)) &ConcreteMesh2::Scale, + " " , py::arg("factor") ) + ; +} diff --git a/examples/shapes/wrapper/mesh/ConcreteMesh2.cppwg.hpp b/examples/shapes/wrapper/mesh/ConcreteMesh2.cppwg.hpp new file mode 100644 index 0000000..858dfeb --- /dev/null +++ b/examples/shapes/wrapper/mesh/ConcreteMesh2.cppwg.hpp @@ -0,0 +1,7 @@ +#ifndef ConcreteMesh2_hpp__cppwg_wrapper +#define ConcreteMesh2_hpp__cppwg_wrapper + +#include + +void register_ConcreteMesh2_class(pybind11::module &m); +#endif // ConcreteMesh2_hpp__cppwg_wrapper diff --git a/examples/shapes/wrapper/mesh/ConcreteMesh3.cppwg.cpp b/examples/shapes/wrapper/mesh/ConcreteMesh3.cppwg.cpp new file mode 100644 index 0000000..817aac3 --- /dev/null +++ b/examples/shapes/wrapper/mesh/ConcreteMesh3.cppwg.cpp @@ -0,0 +1,31 @@ +#include +#include +#include "wrapper_header_collection.hpp" + +#include "ConcreteMesh3.cppwg.hpp" + +namespace py = pybind11; +typedef ConcreteMesh<3 > ConcreteMesh3; +PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); + +class ConcreteMesh3_Overrides : public ConcreteMesh3{ + public: + using ConcreteMesh3::ConcreteMesh; + void Scale(double const factor) override { + PYBIND11_OVERRIDE( + void, + ConcreteMesh3, + Scale, + factor); + } + +}; +void register_ConcreteMesh3_class(py::module &m){ +py::class_ , AbstractMesh<3> >(m, "ConcreteMesh3") + .def(py::init< >()) + .def( + "Scale", + (void(ConcreteMesh3::*)(double const)) &ConcreteMesh3::Scale, + " " , py::arg("factor") ) + ; +} diff --git a/examples/shapes/wrapper/mesh/ConcreteMesh3.cppwg.hpp b/examples/shapes/wrapper/mesh/ConcreteMesh3.cppwg.hpp new file mode 100644 index 0000000..c2ba401 --- /dev/null +++ b/examples/shapes/wrapper/mesh/ConcreteMesh3.cppwg.hpp @@ -0,0 +1,7 @@ +#ifndef ConcreteMesh3_hpp__cppwg_wrapper +#define ConcreteMesh3_hpp__cppwg_wrapper + +#include + +void register_ConcreteMesh3_class(pybind11::module &m); +#endif // ConcreteMesh3_hpp__cppwg_wrapper diff --git a/examples/shapes/wrapper/mesh/mesh.main.cpp b/examples/shapes/wrapper/mesh/mesh.main.cpp index d6d098a..bda9842 100644 --- a/examples/shapes/wrapper/mesh/mesh.main.cpp +++ b/examples/shapes/wrapper/mesh/mesh.main.cpp @@ -1,12 +1,16 @@ #include #include "wrapper_header_collection.hpp" -#include "Mesh2_2.cppwg.hpp" -#include "Mesh3_3.cppwg.hpp" +#include "AbstractMesh2_2.cppwg.hpp" +#include "AbstractMesh3_3.cppwg.hpp" +#include "ConcreteMesh2.cppwg.hpp" +#include "ConcreteMesh3.cppwg.hpp" namespace py = pybind11; PYBIND11_MODULE(_pyshapes_mesh, m) { - register_Mesh2_2_class(m); - register_Mesh3_3_class(m); + register_AbstractMesh2_2_class(m); + register_AbstractMesh3_3_class(m); + register_ConcreteMesh2_class(m); + register_ConcreteMesh3_class(m); } diff --git a/examples/shapes/wrapper/package_info.yaml b/examples/shapes/wrapper/package_info.yaml index 9a14694..43711d2 100644 --- a/examples/shapes/wrapper/package_info.yaml +++ b/examples/shapes/wrapper/package_info.yaml @@ -24,4 +24,5 @@ modules: - name: mesh source_locations: classes: - - name: Mesh + - name: AbstractMesh + - name: ConcreteMesh diff --git a/examples/shapes/wrapper/wrapper_header_collection.hpp b/examples/shapes/wrapper/wrapper_header_collection.hpp index ed3571b..6c20a76 100644 --- a/examples/shapes/wrapper/wrapper_header_collection.hpp +++ b/examples/shapes/wrapper/wrapper_header_collection.hpp @@ -2,7 +2,8 @@ #define pyshapes_HEADERS_HPP_ // Includes -#include "Mesh.hpp" +#include "ConcreteMesh.hpp" +#include "AbstractMesh.hpp" #include "SimpleMathFunctions.hpp" #include "Shape.hpp" #include "Cuboid.hpp" @@ -14,8 +15,10 @@ template class Point<2>; template class Point<3>; template class Shape<2>; template class Shape<3>; -template class Mesh<2,2>; -template class Mesh<3,3>; +template class AbstractMesh<2,2>; +template class AbstractMesh<3,3>; +template class ConcreteMesh<2>; +template class ConcreteMesh<3>; // Typedefs for nicer naming namespace cppwg @@ -24,8 +27,10 @@ typedef Point<2> Point2; typedef Point<3> Point3; typedef Shape<2> Shape2; typedef Shape<3> Shape3; -typedef Mesh<2,2> Mesh2_2; -typedef Mesh<3,3> Mesh3_3; +typedef AbstractMesh<2,2> AbstractMesh2_2; +typedef AbstractMesh<3,3> AbstractMesh3_3; +typedef ConcreteMesh<2> ConcreteMesh2; +typedef ConcreteMesh<3> ConcreteMesh3; } // namespace cppwg #endif // pyshapes_HEADERS_HPP_ From 5faf8b124423b814e446bf21e37d1ff13518768c Mon Sep 17 00:00:00 2001 From: Kwabena N Amponsah Date: Wed, 1 May 2024 20:39:10 +0000 Subject: [PATCH 09/11] #3 fix formatting --- README.md | 2 +- cppwg/generators.py | 2 +- examples/shapes/src/python/test/test_classes.py | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ddac632..cc0c250 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ cd examples/shapes cppwg src/ \ --wrapper_root wrapper/ \ --package_info wrapper/package_info.yaml \ - --includes src/geometry/ src/math_funcs/ src/mesh/ src/primitives + --includes src/geometry/ src/math_funcs/ src/mesh/ src/primitives ``` To build the example `pyshapes` package: diff --git a/cppwg/generators.py b/cppwg/generators.py index 9e0bf68..0ad0caa 100644 --- a/cppwg/generators.py +++ b/cppwg/generators.py @@ -7,7 +7,7 @@ import subprocess import uuid from pathlib import Path -from typing import Any, Dict, List, Optional +from typing import List, Optional import pygccxml diff --git a/examples/shapes/src/python/test/test_classes.py b/examples/shapes/src/python/test/test_classes.py index 7e09511..a173760 100644 --- a/examples/shapes/src/python/test/test_classes.py +++ b/examples/shapes/src/python/test/test_classes.py @@ -1,8 +1,8 @@ import unittest import pyshapes.geometry -import pyshapes.primitives import pyshapes.mesh +import pyshapes.primitives class TestClasses(unittest.TestCase): @@ -31,5 +31,6 @@ def testMesh(self): cmesh.SetIndex(1) self.assertTrue(cmesh.GetIndex() == 1) + if __name__ == "__main__": unittest.main() From 9df10c145ef9cae2ede1c9579315ce0bc7613970 Mon Sep 17 00:00:00 2001 From: Kwabena N Amponsah Date: Wed, 1 May 2024 20:53:40 +0000 Subject: [PATCH 10/11] #3 remove unused source_ns from module writer --- cppwg/generators.py | 1 - cppwg/writers/module_writer.py | 4 ---- 2 files changed, 5 deletions(-) diff --git a/cppwg/generators.py b/cppwg/generators.py index 0ad0caa..3b539a8 100644 --- a/cppwg/generators.py +++ b/cppwg/generators.py @@ -351,7 +351,6 @@ def write_wrappers(self) -> None: """Write all the wrappers required for the package.""" for module_info in self.package_info.module_info_collection: module_writer = CppModuleWrapperWriter( - self.source_ns, module_info, wrapper_templates.template_collection, self.wrapper_root, diff --git a/cppwg/writers/module_writer.py b/cppwg/writers/module_writer.py index 8c61984..a4abc9f 100644 --- a/cppwg/writers/module_writer.py +++ b/cppwg/writers/module_writer.py @@ -20,8 +20,6 @@ class CppModuleWrapperWriter: Attributes ---------- - source_ns : pygccxml.declarations.namespace_t - The pygccxml namespace containing declarations from the source code module_info : ModuleInfo The module information to generate Python bindings for wrapper_templates : Dict[str, str] @@ -36,13 +34,11 @@ class CppModuleWrapperWriter: def __init__( self, - source_ns: "namespace_t", # noqa: F821 module_info: "ModuleInfo", # noqa: F821 wrapper_templates: Dict[str, str], wrapper_root: str, package_license: str = "", ): - self.source_ns: "namespace_t" = source_ns # noqa: F821 self.module_info: "ModuleInfo" = module_info # noqa: F821 self.wrapper_templates: Dict[str, str] = wrapper_templates self.wrapper_root: str = wrapper_root From 744f41126e50ffdb5fb1c527a65ae942709dc3f0 Mon Sep 17 00:00:00 2001 From: Kwabena N Amponsah Date: Wed, 1 May 2024 20:58:21 +0000 Subject: [PATCH 11/11] #3 fix mesh example header guard --- examples/shapes/src/mesh/AbstractMesh.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/shapes/src/mesh/AbstractMesh.hpp b/examples/shapes/src/mesh/AbstractMesh.hpp index 91817f4..83cf7de 100644 --- a/examples/shapes/src/mesh/AbstractMesh.hpp +++ b/examples/shapes/src/mesh/AbstractMesh.hpp @@ -1,5 +1,5 @@ -#ifndef _MESH_HPP -#define _MESH_HPP +#ifndef _ABSTRACT_MESH_HPP +#define _ABSTRACT_MESH_HPP /** * A mesh in SPACE_DIM space with ELEMENT_DIM dimensional elements @@ -40,4 +40,4 @@ class AbstractMesh virtual void Scale(const double factor) = 0; }; -#endif // _MESH_HPP +#endif // _ABSTRACT_MESH_HPP