Commit a075b594 authored by John Parent's avatar John Parent
Browse files

Merge branch 'master' of https://gitlab.kitware.com/autopybind11/autopybind11...

Merge branch 'master' of https://gitlab.kitware.com/autopybind11/autopybind11 into dev/configure-cxx-compiler-version
parents 95726268 9cb9754f
Pipeline #229221 passed with stages
in 15 minutes and 25 seconds
......@@ -390,43 +390,98 @@ This occurs when a YAML file is supplied as a ``CONFIG_INPUT`` to the
This YAML file
+------------------------------+----------------------------------+
| Customization Key | Description |
+==============================+==================================+
| enforce_namespace_structure | ``false`` to place all found |
| | objects at the module level |
| | ``true`` will enforce the |
| | C++ namespace structure on the |
| | Python Code |
| +----------------------------------+
| | Example: |
| | enforce_namespace_structure: true|
+------------------------------+----------------------------------+
| private_members_as_fields | ``False`` to bind getters and |
| | setters as normal. |
| | ``True`` to bind |
| | the corresponding private member |
| | variables as public members |
| | |
| | See `Pybind11's Documentation`_ |
| | |
| +----------------------------------+
| | Example: |
| | private_members_as_fields: true |
| | |
+------------------------------+----------------------------------+
| enable_doc_strings | ``False`` to not supply doc |
| | strings from the C++ code |
| | ``True`` to pass C++ comments |
| | from the source code as doc |
| | strings to the Python code |
| | |
| | |
| +----------------------------------+
| | Example: |
| | enable_doc_strings: false |
| | |
+------------------------------+----------------------------------+
+------------------------------+---------------------------------------+
| Customization Key | Description |
+==============================+=======================================+
| enforce_namespace_structure | ``false`` to place all found |
| | objects at the module level |
| | ``true`` will enforce the |
| | C++ namespace structure on the |
| | Python Code |
| +---------------------------------------+
| | Example: |
| | enforce_namespace_structure: true |
+------------------------------+---------------------------------------+
| private_members_as_fields | ``False`` to bind getters and |
| | setters as normal. |
| | ``True`` to bind |
| | the corresponding private member |
| | variables as public members |
| | |
| | See `Pybind11's Documentation`_ |
| | |
| +---------------------------------------+
| | Example: |
| | private_members_as_fields: true |
| | |
+------------------------------+---------------------------------------+
| enable_doc_strings | ``False`` to not supply doc |
| | strings from the C++ code |
| | ``True`` to pass C++ comments |
| | from the source code as doc |
| | strings to the Python code |
| | |
| | |
| +---------------------------------------+
| | Example: |
| | enable_doc_strings: false |
| | |
+------------------------------+---------------------------------------+
| expand_declarations | ``False`` to limit output of |
| | binding code to as minimally |
| | verbose output as possible |
| | ``True`` (default) to produce |
| | binding code with declarations |
| | fully expanded by compiler |
| +---------------------------------------+
| | Example: |
| | expand_declarations: False |
+------------------------------+---------------------------------------+
| expand_stl_declarations | ``False`` to reduce binding code |
| | of stl declarations to minimal |
| | verbosity, removing any expansion |
| | of default params |
| | ``True`` to allow stl default |
| | and fully expanded declarations |
| | in binding code |
| | |
| +---------------------------------------+
| | Example: |
| | expand_stl_declarations: False |
+------------------------------+---------------------------------------+
| apply_global_namespace | ``False`` to remove all instances |
| | of the global namespace qualifier |
| | ``True`` to allow the GNS to |
| | persist |
| +---------------------------------------+
| | Example: |
| | apply_global_namespace: False |
+------------------------------+---------------------------------------+
| assumed_qualifiers | Default is an empty list. |
| | Add scoping qualifier to list to |
| | remove qualifier(including GNS) |
| | from the binding output |
| +---------------------------------------+
| | Example: |
| | assumed_qualifiers: ["First"] |
+------------------------------+---------------------------------------+
| template_defaults | Default is an empty list |
| | Each entry in list specifies a |
| | templated type with default |
| | parameters and for each template |
| | parameter a value of 1 or 0 |
| | indicating whether or not that |
| | param will be included in the |
| | binding output. References to |
| | params are done in order and all |
| | parameters must be specified. |
| +---------------------------------------+
| | Example: |
| | template_defaults:"Eigen::Ref|1,0,0" |
+------------------------------+---------------------------------------+
pybind11 structure
^^^^^^^^^^^^^^^^^^
......
......@@ -42,6 +42,8 @@ if(Eigen_INCLUDE_DIR)
set_tests_properties(eigen_ref PROPERTIES ENVIRONMENT Eigen_INCLUDE_DIR=${Eigen_INCLUDE_DIR})
add_autopybind11_test(eigen_ref_toggle)
set_tests_properties(eigen_ref_toggle PROPERTIES ENVIRONMENT Eigen_INCLUDE_DIR=${Eigen_INCLUDE_DIR})
add_autopybind11_test(denoise)
set_tests_properties(denoise PROPERTIES ENVIRONMENT Eigen_INCLUDE_DIR=${Eigen_INCLUDE_DIR})
endif()
add_autopybind11_test(simple)
......
cmake_minimum_required(VERSION 3.15)
project(Denoise CXX)
find_package(AutoPyBind11)
autopybind11_fetch_build_pybind11( PYBIND11_DIR ${PYBIND11_SRC_DIR})
add_library(noisy INTERFACE)
target_sources(noisy INTERFACE )
target_include_directories(noisy INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
INTERFACE $ENV{Eigen_INCLUDE_DIR})
file(COPY data DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
autopybind11_add_module(denoise_mod
YAML_INPUT ${CMAKE_CURRENT_SOURCE_DIR}/wrapper.yml
CONFIG_INPUT ${CMAKE_CURRENT_SOURCE_DIR}/conf.yml
DESTINATION ${CMAKE_CURRENT_BINARY_DIR}
LINK_LIBRARIES noisy)
apply_global_namespace: False
assumed_qualifiers: ["First"]
pass_eigen_by_ref: False
use_eigen_return_policy: False
template_defaults: "Eigen::Ref|1,0,0"
expand_declarations: False
\ No newline at end of file
#include <pybind11/pybind11.h>
#include <pybind11/iostream.h>
#include <pybind11/stl.h>
#include "noisey.hpp"
#include <pybind11/eigen.h>
namespace py = pybind11;
void apb11_denoise_mod_NoisyClass_py_register(py::module &m)
{
py::class_<NoisyClass> PyNoisyClass(m, "NoisyClass");
PyNoisyClass.def(py::init<NoisyClass const &>(),py::arg("arg0"))
.def(py::init<>())
.def("noisyEigen", static_cast<Eigen::Ref< RowMatrixXd > ( NoisyClass::* )( RowMatrixXd )>(&NoisyClass::noisyEigen), py::arg("m"))
.def("noisyStd", static_cast<void ( NoisyClass::* )( std::list<Coupler<Mid>, std::allocator<Coupler<Mid>>>,std::vector<int, std::allocator<int>> )>(&NoisyClass::noisyStd), py::arg("m") = (std::list<Coupler<Mid>, std::allocator<Coupler<Mid>>>)std::list<MidCouple>(), py::arg("vec") = (std::vector<int, std::allocator<int>>)std::vector<int>())
;
}
#include <pybind11/pybind11.h>
#include <pybind11/iostream.h>
#include <pybind11/stl.h>
#include "noisey.hpp"
#include <pybind11/eigen.h>
#include <pybind11/eigen.h>
#include "C:\apb_fork\src\Tests\denoise\defs.h"
namespace py = pybind11;
template <typename Class, typename... Options>
py::class_<Class, Options...> DefineTemplateClass(
py::handle scope, const char* name,
const char* doc_string = "") {
py::class_<Class, Options...> py_class(
scope, name, doc_string);
return py_class;
};
void apb11_denoise_mod_TNoisyClass_py_register(py::module &m)
{
// Instantiation of TNoisyClass<Eigen::Matrix3d>
py::class_<TNoisyClass<Eigen::Matrix3d>> PyTNoisyClass_Matrix3d(m, "TNoisyClass_Matrix3d");
PyTNoisyClass_Matrix3d.def(py::init<>())
.def(py::init<TNoisyClass<Eigen::Matrix3d> const &>(),py::arg("arg0"))
;
// Instantiation of TNoisyClass<Eigen::Vector4f>
py::class_<TNoisyClass<Eigen::Vector4f>> PyTNoisyClass_Vector4f(m, "TNoisyClass_Vector4f");
PyTNoisyClass_Vector4f.def(py::init<>())
.def(py::init<TNoisyClass<Eigen::Vector4f> const &>(),py::arg("arg0"))
;
// Instantiation of TNoisyClass<Coupler<Mid>>
py::class_<TNoisyClass<Coupler<Mid>>> PyTNoisyClass_MidCouple(m, "TNoisyClass_MidCouple");
PyTNoisyClass_MidCouple.def(py::init<>())
.def(py::init<TNoisyClass<Coupler<Mid>> const &>(),py::arg("arg0"))
;
}
#ifndef DEF
#define DEF
#include <Eigen/Core>
namespace First{
enum CouplingTag {
High,
Mid,
Low
};
template <CouplingTag T = CouplingTag::Low>
class Coupler
{
public:
Coupler() {};
private:
CouplingTag couple_level;
};
typedef Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> RowMatrixXd;
typedef Coupler<CouplingTag::Mid> MidCouple;
}
#endif
\ No newline at end of file
import os, sys, unittest, re
sys.path.append(os.getcwd())
def import_denoise():
import denoise_mod
class DenoiseTest(unittest.TestCase):
# Taken from smoke_test.py
def whitespace_normalize(self, s):
# Naive and ugly, but gets the job done.
return s.replace(" ", "").replace("\n", "").replace("/", "\\")
def remove_includes(self, in_str):
pat = r"#include.+"
repl = ""
return re.sub(pat, repl, in_str)
@unittest.expectedFailure
def test_Import(self):
# No module means an import error is thrown.
self.assertRaises(ImportError, import_denoise)
def test_Construct(self):
import denoise_mod
denoise_mod.NoisyClass()
denoise_mod.TNoisyClass_MidCouple()
denoise_mod.TNoisyClass_Vector4f()
denoise_mod.TNoisyClass_Matrix3d()
def test_Normalization(self):
for filename in ["NoisyClass_py.cpp", "TNoisyClass_py.cpp"]:
with open(filename, "r") as f:
with open("data/" + filename, "r") as df:
expected = self.remove_includes(df.read())
expected = self.whitespace_normalize(expected)
in_text = self.remove_includes(f.read())
in_text = self.whitespace_normalize(in_text)
self.assertEqual(expected, in_text)
if __name__ == "__main__":
unittest.main()
#ifndef NOISY
#define NOISY
#include "defs.h"
#include <Eigen/Dense>
#include <list>
#include <vector>
using namespace First;
class NoisyClass
{
public:
NoisyClass() {}
void noisyStd(std::list<MidCouple> m = std::list<MidCouple>(), std::vector<int> vec = std::vector<int>()) {}
Eigen::Ref<RowMatrixXd> noisyEigen(RowMatrixXd m) {return Eigen::Ref<RowMatrixXd>(m);}
};
template <typename T = Coupler<>>
class TNoisyClass
{
public:
TNoisyClass() {}
};
#endif
files:
noisey.hpp:
classes:
NoisyClass:
TNoisyClass:
inst: [Eigen::Matrix3d, Eigen::Vector4f, MidCouple]
......@@ -22,8 +22,7 @@ public:
Tagged(): my_tag(T){}
Tagged(std::string &s) : name(s), my_tag(T){}
private:
typedef tag tag_t;
tag_t my_tag;
tag my_tag;
std::string name;
};
......
......@@ -91,6 +91,94 @@ class BindingsGenerator:
include_str += '#include "%s"\n' % f
return include_str
def normalize(self, file_content):
if not self.opts.gns_flag:
file_content = self.format_remove_global_namespace(file_content)
if not self.opts.stdexdec_flag:
file_content = self.stl_replace_partial_decl(file_content)
if self.opts.td:
file_content = self.remove_templ_default(file_content)
if self.opts.aq:
for nmspc in self.opts.aq:
file_content = self.remove_assumed_nmspc(file_content, nmspc)
return file_content
def get_std_namespace(self):
return self.name_data.namespace("std")
def stl_replace_partial_decl(self, binding_str):
std = self.get_std_namespace()
for x_decl in std.declarations:
if x_decl.decl_string.strip("::") in binding_str:
binding_str = binding_str.replace(
x_decl.decl_string.strip("::"),
x_decl.partial_decl_string.strip("::"),
)
return binding_str
def get_eigen_namespace(self):
return self.name_data.namespace("Eigen")
def remove_templ_default(self, binding_str):
for default in self.opts.td:
templ_type, default_types = default.split("|")
default_types = default_types.split(",")
for match in re.finditer(templ_type, binding_str):
end_idx = self.extract_templ_indicies_rec(
match.end() + 1, binding_str
)
strt = match.start()
edx = end_idx + 1
templ_str = binding_str[strt:edx]
templ_lst = list(
filter(
self.templ_default_filter,
zip(dec.templates.split(templ_str)[1], default_types),
)
)
templ_lst = [x[0] for x in templ_lst]
templ_lst = self.sub_templ_insts(templ_lst)
no_default_str = dec.templates.join(match.group(), templ_lst)
binding_str = binding_str.replace(templ_str, no_default_str)
return binding_str
def templ_default_filter(self, inst):
if inst[1] == "1":
return True
return False
def format_remove_global_namespace(self, binding_member_str):
pat = r"([\s.,<>()&*])::(\S*)"
repl = r"\1\2"
return self.reg_replace(pat, repl, binding_member_str)
def remove_assumed_nmspc(self, binding_member_str, assumed_nmspc):
pat = r":*%s:+" % assumed_nmspc
repl = ""
return self.reg_replace(pat, repl, binding_member_str)
def reg_replace(self, pat, repl, str_):
return re.sub(pat, repl, str_)
def sub_templ_insts(self, templ_lst):
templ_lst_ret = []
for inst in templ_lst:
templ_lst_ret.append(self.substitute_templ_insts(inst))
return templ_lst_ret
def substitute_templ_insts(self, templ_inst):
try:
typedef = self.name_data.decls(
lambda x: x.decl_type.decl_string.strip("::") == templ_inst
and type(x.parent) is dec.namespace_t,
recursive=True,
decl_type=dec.typedef_t,
)
if typedef:
return typedef[0].name
except pygccxml.declarations.runtime_errors.declaration_not_found_t:
return templ_inst
def generate_keep_alive_string(self, decl_data, keep_alive_data):
num_args = len(decl_data.arguments)
keep_alive_str = ""
......@@ -292,7 +380,6 @@ class BindingsGenerator:
as submodules are created.
:return: A string which contains all PyBind11 declarations for single function
"""
# If a different name is requested on the Python side, set it here
fun_name = py_name if py_name else member_function.name
......@@ -700,16 +787,27 @@ class BindingsGenerator:
if module_name != free_fun_mod_name:
keys["defs"] = keys["defs"].replace("::%s::" % module_name, "")
self.write_data_to_file(file_name, cpp_body.format(**keys))
content = cpp_body.format(**keys)
if self.opts.exdec:
content = self.normalize(content)
self.write_data_to_file(file_name, content)
self.indent = self.indent[:-2]
def return_policy_reference_internal(self):
return self.opts.return_policy_fmt.format(policy="reference_internal")
def extract_templ_indicies_rec(self, idx, search_str):
while search_str[idx] != ">":
if search_str[idx] == "<":
idx = self.extract_templ_indicies_rec(idx + 1, search_str)
idx += 1
return idx
# TODO: Add recursion to handle further template nesting
def extract_type_from_str(self, in_str, dep_set):
itr1, itr2 = 0, 0
while itr2 < len(in_str):
while itr2 != ">":
if in_str[itr2] == ",":
dep_set.add(in_str[itr1:itr2])
itr2 += 1
......@@ -928,7 +1026,6 @@ class BindingsGenerator:
)
return type_check.intersection(types)
###
def load_validation_filters(self):
self.validation_filters.append(self.check_component_deps)
self.validation_filters.append(self.static_const_filter)
......@@ -1669,6 +1766,12 @@ class BindingsGenerator:
self.alias_data[desired_param] = alias_param.lstrip(
"::"
)
if not self.opts.exdec:
inst = self.substitute_templ_insts(template_param)
pyclass_name = self.template_args_to_underscores(
instance_data.name.replace(template_param, inst)
)
mod_name = "Py%s" % pyclass_name
# Get the arguments to the py::class_<>() call
# The first of which is the class name
......@@ -2124,7 +2227,7 @@ class BindingsGenerator:
# brutal fix, but not the worst
file_content = self.opts.class_module_cpp_fmt.format(**keys)
file_content = self.normalize(file_content)
for indx, alias_value in enumerate(self.alias_data):
if alias_value in file_content:
file_content = re.sub(
......@@ -2135,6 +2238,7 @@ class BindingsGenerator:
], # noqa \g is valid substitution
file_content,
)
self.write_data_to_file(file_name, file_content)
self.indent = self.indent[:-4]
......@@ -2737,8 +2841,10 @@ class BindingsGenerator:
if "classes" in yaml_dict:
classes_dict = yaml_dict["classes"]
# Take this one class at a time
class_out = {}
include_list = set({file_name})
for class_name, class_data in classes_dict.items():
include_list = set({file_name})
desired_name = ""
names_to_find = self.get_all_inst_names(class_name, class_data)
gen_data_for_class = pygccxml_data.classes(
......@@ -2780,13 +2886,20 @@ class BindingsGenerator:
self.alias_data[
typedef.decl_type.declaration.decl_string
] = typedef.decl_string.lstrip("::")
class_out[class_name] = {
"gen_data": gen_data_for_class,
"desired_name": desired_name,
"class_data": class_data,
}
for class_name in class_out:
self.write_class_data(
class_name,
gen_data_for_class,
class_out[class_name]["gen_data"],
self.opts.output_dir,
include_list,
desired_name,
class_data,
class_out[class_name]["desired_name"],
class_out[class_name]["class_data"],
namespace=curr_nmspc,
)
......@@ -3208,6 +3321,49 @@ def main(argv=None):
dest="ens_flag",
default=True,
)
arg.add(
"-agns",
"--apply_global_namespace",
required=False,
action="store",
type=lambda x: bool(distutils.util.strtobool(x)),
dest="gns_flag",
default=True,
)
arg.add(
"-stdexdec",
"--expand_stl_declaration",
required=False,
action="store",