Merging 'master' into 'wrap'

release/4.3a0
Varun Agrawal 2021-12-22 14:14:47 -05:00
commit a9c48b158a
3 changed files with 80 additions and 38 deletions

View File

@ -55,15 +55,44 @@ function(
set(GTWRAP_PATH_SEPARATOR ";") set(GTWRAP_PATH_SEPARATOR ";")
endif() endif()
# Create a copy of interface_headers so we can freely manipulate it
set(interface_files ${interface_headers})
# Pop the main interface file so that interface_files has only submodules.
list(POP_FRONT interface_files main_interface)
# Convert .i file names to .cpp file names. # Convert .i file names to .cpp file names.
foreach(filepath ${interface_headers}) foreach(interface_file ${interface_files})
get_filename_component(interface ${filepath} NAME) # This block gets the interface file name and does the replacement
string(REPLACE ".i" ".cpp" cpp_file ${interface}) get_filename_component(interface ${interface_file} NAME_WLE)
set(cpp_file "${interface}.cpp")
list(APPEND cpp_files ${cpp_file}) list(APPEND cpp_files ${cpp_file})
# Wrap the specific interface header
# This is done so that we can create CMake dependencies in such a way so that when changing a single .i file,
# the others don't need to be regenerated.
# NOTE: We have to use `add_custom_command` so set the dependencies correctly.
# https://stackoverflow.com/questions/40032593/cmake-does-not-rebuild-dependent-after-prerequisite-changes
add_custom_command(
OUTPUT ${cpp_file}
COMMAND
${CMAKE_COMMAND} -E env
"PYTHONPATH=${GTWRAP_PACKAGE_DIR}${GTWRAP_PATH_SEPARATOR}$ENV{PYTHONPATH}"
${PYTHON_EXECUTABLE} ${PYBIND_WRAP_SCRIPT} --src "${interface_file}"
--out "${cpp_file}" --module_name ${module_name}
--top_module_namespaces "${top_namespace}" --ignore ${ignore_classes}
--template ${module_template} --is_submodule ${_WRAP_BOOST_ARG}
DEPENDS "${interface_file}" ${module_template} "${module_name}/specializations/${interface}.h" "${module_name}/preamble/${interface}.h"
VERBATIM)
endforeach() endforeach()
get_filename_component(main_interface_name ${main_interface} NAME_WLE)
set(main_cpp_file "${main_interface_name}.cpp")
list(PREPEND cpp_files ${main_cpp_file})
add_custom_command( add_custom_command(
OUTPUT ${cpp_files} OUTPUT ${main_cpp_file}
COMMAND COMMAND
${CMAKE_COMMAND} -E env ${CMAKE_COMMAND} -E env
"PYTHONPATH=${GTWRAP_PACKAGE_DIR}${GTWRAP_PATH_SEPARATOR}$ENV{PYTHONPATH}" "PYTHONPATH=${GTWRAP_PACKAGE_DIR}${GTWRAP_PATH_SEPARATOR}$ENV{PYTHONPATH}"
@ -71,23 +100,10 @@ function(
--out "${generated_cpp}" --module_name ${module_name} --out "${generated_cpp}" --module_name ${module_name}
--top_module_namespaces "${top_namespace}" --ignore ${ignore_classes} --top_module_namespaces "${top_namespace}" --ignore ${ignore_classes}
--template ${module_template} ${_WRAP_BOOST_ARG} --template ${module_template} ${_WRAP_BOOST_ARG}
DEPENDS "${interface_headers}" ${module_template} DEPENDS "${main_interface}" ${module_template} "${module_name}/specializations/${main_interface_name}.h" "${module_name}/specializations/${main_interface_name}.h"
VERBATIM) VERBATIM)
add_custom_target(pybind_wrap_${module_name} ALL DEPENDS ${cpp_files}) add_custom_target(pybind_wrap_${module_name} DEPENDS ${cpp_files})
# Late dependency injection, to make sure this gets called whenever the
# interface header or the wrap library are updated.
# ~~~
# See: https://stackoverflow.com/questions/40032593/cmake-does-not-rebuild-dependent-after-prerequisite-changes
# ~~~
add_custom_command(
OUTPUT ${cpp_files}
DEPENDS ${interface_headers}
# @GTWRAP_SOURCE_DIR@/gtwrap/interface_parser.py
# @GTWRAP_SOURCE_DIR@/gtwrap/pybind_wrapper.py
# @GTWRAP_SOURCE_DIR@/gtwrap/template_instantiator.py
APPEND)
pybind11_add_module(${target} "${cpp_files}") pybind11_add_module(${target} "${cpp_files}")

View File

@ -631,28 +631,47 @@ class PybindWrapper:
submodules_init="\n".join(submodules_init), submodules_init="\n".join(submodules_init),
) )
def wrap(self, sources, main_output): def wrap_submodule(self, source):
""" """
Wrap all the source interface files. Wrap a list of submodule files, i.e. a set of interface files which are
in support of a larger wrapping project.
E.g. This is used in GTSAM where we have a main gtsam.i, but various smaller .i files
which are the submodules.
The benefit of this scheme is that it reduces compute and memory usage during compilation.
Args:
source: Interface file which forms the submodule.
"""
filename = Path(source).name
module_name = Path(source).stem
# Read in the complete interface (.i) file
with open(source, "r") as f:
content = f.read()
# Wrap the read-in content
cc_content = self.wrap_file(content, module_name=module_name)
# Generate the C++ code which Pybind11 will use.
with open(filename.replace(".i", ".cpp"), "w") as f:
f.write(cc_content)
def wrap(self, sources, main_module_name):
"""
Wrap all the main interface file.
Args: Args:
sources: List of all interface files. sources: List of all interface files.
main_output: The name for the main module. The first file should be the main module.
main_module_name: The name for the main module.
""" """
main_module = sources[0] main_module = sources[0]
# Get all the submodule names.
submodules = [] submodules = []
for source in sources[1:]: for source in sources[1:]:
filename = Path(source).name
module_name = Path(source).stem module_name = Path(source).stem
# Read in the complete interface (.i) file
with open(source, "r") as f:
content = f.read()
submodules.append(module_name) submodules.append(module_name)
cc_content = self.wrap_file(content, module_name=module_name)
# Generate the C++ code which Pybind11 will use.
with open(filename.replace(".i", ".cpp"), "w") as f:
f.write(cc_content)
with open(main_module, "r") as f: with open(main_module, "r") as f:
content = f.read() content = f.read()
@ -661,5 +680,5 @@ class PybindWrapper:
submodules=submodules) submodules=submodules)
# Generate the C++ code which Pybind11 will use. # Generate the C++ code which Pybind11 will use.
with open(main_output, "w") as f: with open(main_module_name, "w") as f:
f.write(cc_content) f.write(cc_content)

View File

@ -19,7 +19,7 @@ def main():
arg_parser.add_argument("--src", arg_parser.add_argument("--src",
type=str, type=str,
required=True, required=True,
help="Input interface .i/.h file") help="Input interface .i/.h file(s)")
arg_parser.add_argument( arg_parser.add_argument(
"--module_name", "--module_name",
type=str, type=str,
@ -31,7 +31,7 @@ def main():
"--out", "--out",
type=str, type=str,
required=True, required=True,
help="Name of the output pybind .cc file", help="Name of the output pybind .cc file(s)",
) )
arg_parser.add_argument( arg_parser.add_argument(
"--use-boost", "--use-boost",
@ -60,7 +60,10 @@ def main():
) )
arg_parser.add_argument("--template", arg_parser.add_argument("--template",
type=str, type=str,
help="The module template file") help="The module template file (e.g. module.tpl).")
arg_parser.add_argument("--is_submodule",
default=False,
action="store_true")
args = arg_parser.parse_args() args = arg_parser.parse_args()
top_module_namespaces = args.top_module_namespaces.split("::") top_module_namespaces = args.top_module_namespaces.split("::")
@ -78,9 +81,13 @@ def main():
module_template=template_content, module_template=template_content,
) )
# Wrap the code and get back the cpp/cc code. if args.is_submodule:
sources = args.src.split(';') wrapper.wrap_submodule(args.src)
wrapper.wrap(sources, args.out)
else:
# Wrap the code and get back the cpp/cc code.
sources = args.src.split(';')
wrapper.wrap(sources, args.out)
if __name__ == "__main__": if __name__ == "__main__":