Headers processing#
Filtering header content#
A C/C++ header can contains different zone, some of which are parts of the public API, and some of which may correspond to specific low-level options.
litgen (and srcmlcpp) can filter a header based on preprocessor #ifdef / #ifndef
occurrences.
Let’s look at an example header: its code is defined in the cpp_code
variable below.
cpp_code = """
#ifndef MY_HEADER_H // This is an inclusion guard, it should not be filtered out
void Foo() {}
#ifdef ARCANE_OPTION
// We are entering a zone that handle arcane options that should not be included in the bindings
void Foo2() {}
#else
// this should also not be included in the bindings
void Foo3() {}
#endif
#ifdef COMMON_OPTION
// We are entering a zone for which we would like to publish bindings
void Foo4();
#endif
#endif // #ifndef MY_HEADER_H
"""
Let’s try to generate bindings for it:
import litgen
from litgen.demo import litgen_demo
options = litgen.LitgenOptions()
litgen_demo.demo(options, cpp_code)
#ifndef MY_HEADER_H // This is an inclusion guard, it should not be filtered out
void Foo() {}
#ifdef ARCANE_OPTION
// We are entering a zone that handle arcane options that should not be included in the bindings
void Foo2() {}
#else
// this should also not be included in the bindings
void Foo3() {}
#endif
#ifdef COMMON_OPTION
// We are entering a zone for which we would like to publish bindings
void Foo4();
#endif
#endif // #ifndef MY_HEADER_H
# #ifndef MY_HEADER_H
def foo() -> None:
pass
# #endif
// #ifndef MY_HEADER_H
m.def("foo",
Foo);
// #endif
// #ifndef MY_HEADER_H
m.def("foo",
Foo);
// #endif
We see that elements inside #ifdef ARCANE_OPTION
were not processed. However, elements inside #ifdef COMMON_OPTION
were not processed. Let’s correct this by adjusting the options:
options = litgen.LitgenOptions()
# the default value for header_filter_acceptable__regex was
# "__cplusplus|_h_$|_h$|_H$|_H_$|hpp$|HPP$|hxx$|HXX$"
# (which includes support for common header guards)
# Let's add support for COMMON_OPTION
options.srcmlcpp_options.header_filter_acceptable__regex = "_H$|^COMMON_OPTION$"
litgen_demo.demo(options, cpp_code)
#ifndef MY_HEADER_H // This is an inclusion guard, it should not be filtered out
void Foo() {}
#ifdef ARCANE_OPTION
// We are entering a zone that handle arcane options that should not be included in the bindings
void Foo2() {}
#else
// this should also not be included in the bindings
void Foo3() {}
#endif
#ifdef COMMON_OPTION
// We are entering a zone for which we would like to publish bindings
void Foo4();
#endif
#endif // #ifndef MY_HEADER_H
# #ifndef MY_HEADER_H
def foo() -> None:
pass
# #ifdef COMMON_OPTION
#
def foo4() -> None:
""" We are entering a zone for which we would like to publish bindings"""
pass
# #endif
#
# #endif
// #ifndef MY_HEADER_H
m.def("foo",
Foo);
// #ifdef COMMON_OPTION
//
m.def("foo4",
Foo4, "We are entering a zone for which we would like to publish bindings");
// #endif
//
// #endif
// #ifndef MY_HEADER_H
m.def("foo",
Foo);
// #ifdef COMMON_OPTION
//
m.def("foo4",
Foo4, "We are entering a zone for which we would like to publish bindings");
// #endif
//
// #endif
API Prefixes#
In a given header files, function can have an “API Prefix”, that denotes whether they should be published or not in a shared library.
cpp_code = """
// This function has an API marker and is exported in a shared library
MY_API int add(int, int b);
// This function does not have an API marker, and is probably private
int mul(int a, int b);
"""
You can set the API marker in the options:
options = litgen.LitgenOptions()
options.srcmlcpp_options.functions_api_prefixes = "MY_API"
litgen_demo.demo(options, cpp_code)
// This function has an API marker and is exported in a shared library
MY_API int add(int, int b);
// This function does not have an API marker, and is probably private
int mul(int a, int b);
def add(param_0: int, b: int) -> int:
""" This function has an API marker and is exported in a shared library"""
pass
m.def("add",
add,
py::arg("param_0"), py::arg("b"),
"This function has an API marker and is exported in a shared library");
m.def("add",
add,
nb::arg("param_0"), nb::arg("b"),
"This function has an API marker and is exported in a shared library");
You can also decide to export non API function, with an optional comment.
options = litgen.LitgenOptions()
options.srcmlcpp_options.functions_api_prefixes = "MY_API"
options.fn_exclude_non_api = False
options.fn_non_api_comment = "Private API!"
litgen_demo.demo(options, cpp_code)
// This function has an API marker and is exported in a shared library
MY_API int add(int, int b);
// This function does not have an API marker, and is probably private
int mul(int a, int b);
def add(param_0: int, b: int) -> int:
""" This function has an API marker and is exported in a shared library"""
pass
def mul(a: int, b: int) -> int:
""" This function does not have an API marker, and is probably private
Private API!
"""
pass
m.def("add",
add,
py::arg("param_0"), py::arg("b"),
"This function has an API marker and is exported in a shared library");
m.def("mul",
mul,
py::arg("a"), py::arg("b"),
" This function does not have an API marker, and is probably private\nPrivate API!");
m.def("add",
add,
nb::arg("param_0"), nb::arg("b"),
"This function has an API marker and is exported in a shared library");
m.def("mul",
mul,
nb::arg("a"), nb::arg("b"),
" This function does not have an API marker, and is probably private\nPrivate API!");
Header preprocessing#
If you need to preprocess header code before the generation, you can create a function that transforms the source code, and store it inside options.srcmlcpp_options.code_preprocess_function
.
For example:
def preprocess_change_int(code: str) -> str:
return code.replace("int", "Int32") # This is a *very* dumb preprocessor
cpp_code = """
int add(int, int b);
"""
options = litgen.LitgenOptions()
options.srcmlcpp_options.code_preprocess_function = preprocess_change_int
generated_code = litgen.generate_code(options, cpp_code)
litgen_demo.show_cpp_code(generated_code.stub_code)
def add(param_0: Int32, b: Int32) -> Int32:
pass