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 

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 

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");

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!");

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