diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ffb66ad02..163165d98c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,20 +21,27 @@ else() set(SCRIPT_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/lib/cmake") endif() -# Install scripts to the standard CMake script directory. -install(FILES cmake/gtwrapConfig.cmake cmake/PybindWrap.cmake - cmake/GtwrapUtils.cmake +# Install CMake scripts to the standard CMake script directory. +install(FILES cmake/gtwrapConfig.cmake cmake/MatlabWrap.cmake + cmake/PybindWrap.cmake cmake/GtwrapUtils.cmake DESTINATION "${SCRIPT_INSTALL_DIR}/gtwrap") +# Needed for the CMAKE_INSTALL_X variables used below. include(GNUInstallDirs) -# Install the gtwrap python package as a directory so it can be found for -# wrapping. + +# Install the gtwrap python package as a directory so it can be found by CMake +# for wrapping. install(DIRECTORY gtwrap DESTINATION "${CMAKE_INSTALL_DATADIR}/gtwrap") # Install wrapping scripts as binaries to `CMAKE_INSTALL_PREFIX/bin` so they can -# be invoked for wrapping. -install(PROGRAMS scripts/pybind_wrap.py scripts/matlab_wrap.py TYPE BIN) +# be invoked for wrapping. We use DESTINATION (instead of TYPE) so we can +# support older CMake versions. +install(PROGRAMS scripts/pybind_wrap.py scripts/matlab_wrap.py + DESTINATION ${CMAKE_INSTALL_BINDIR}) -# Install pybind11 directory to `CMAKE_INSTALL_PREFIX/lib/pybind11` This will -# allow the gtwrapConfig.cmake file to load it later. +# Install pybind11 directory to `CMAKE_INSTALL_PREFIX/lib/gtwrap/pybind11` This +# will allow the gtwrapConfig.cmake file to load it later. install(DIRECTORY pybind11 DESTINATION "${CMAKE_INSTALL_LIBDIR}/gtwrap") + +# Install the matlab.h file to `CMAKE_INSTALL_PREFIX/lib/gtwrap/matlab.h`. +install(FILES matlab.h DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/gtwrap") diff --git a/README.md b/README.md index 5b5cbb902f..1b2783a4cf 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - # WRAP The wrap library wraps the GTSAM library into a Python library or MATLAB toolbox. @@ -43,36 +42,64 @@ pybind_wrap(${PROJECT_NAME}_py # target For more information, please follow our [tutorial](https://github.com/borglab/gtsam-project-python). -## GTSAM Python wrapper +## Python Wrapper **WARNING: On macOS, you have to statically build GTSAM to use the wrapper.** 1. Set `GTSAM_BUILD_PYTHON=ON` while configuring the build with `cmake`. 1. What you can do in the `build` folder: - 1. Just run python then import GTSAM and play around: - ``` - - import gtsam - gtsam.__dir__() - ``` - - 1. Run the unittests: - ``` - python -m unittest discover - ``` - 1. Edit the unittests in `python/gtsam/*.py` and simply rerun the test. - They were symlinked to `/gtsam/*.py` to facilitate fast development. - ``` - python -m unittest gtsam/tests/test_Pose3.py - ``` - - NOTE: You might need to re-run `cmake ..` if files are deleted or added. + + 1. Just run python then import GTSAM and play around: + + ``` + import gtsam + gtsam.__dir__() + ``` + + 1. Run the unittests: + ``` + python -m unittest discover + ``` + 1. Edit the unittests in `python/gtsam/*.py` and simply rerun the test. + They were symlinked to `/gtsam/*.py` to facilitate fast development. + `python -m unittest gtsam/tests/test_Pose3.py` - NOTE: You might need to re-run `cmake ..` if files are deleted or added. + 1. Do `make install` and `cd /python`. Here, you can: - 1. Run the unittests: - ``` - python setup.py test - ``` - 2. Install `gtsam` to your current Python environment. - ``` - python setup.py install - ``` - - NOTE: It's a good idea to create a virtual environment otherwise it will be installed in your system Python's site-packages. + 1. Run the unittests: + ``` + python setup.py test + ``` + 2. Install `gtsam` to your current Python environment. + ``` + python setup.py install + ``` + - NOTE: It's a good idea to create a virtual environment otherwise it will be installed in your system Python's site-packages. + +## Matlab Wrapper + +In the CMake, simply include the `MatlabWrap.cmake` file. + +```cmake +include(MatlabWrap) +``` + +This cmake file defines functions for generating MATLAB wrappers. + +- `wrap_and_install_library(interfaceHeader linkLibraries extraIncludeDirs extraMexFlags)` Generates wrap code and compiles the wrapper. + +Usage example: + + `wrap_and_install_library("lba.h" "" "" "")` + +Arguments: + +- `interfaceHeader`: The relative or absolute path to the wrapper interface definition file. +- `linkLibraries`: Any _additional_ libraries to link. Your project library + (e.g. `lba`), libraries it depends on, and any necessary + MATLAB libraries will be linked automatically. So normally, + leave this empty. +- `extraIncludeDirs`: Any _additional_ include paths required by dependent + libraries that have not already been added by + include_directories. Again, normally, leave this empty. +- `extraMexFlags`: Any _additional_ flags to pass to the compiler when building + the wrap code. Normally, leave this empty. diff --git a/cmake/MatlabWrap.cmake b/cmake/MatlabWrap.cmake new file mode 100644 index 0000000000..75654238ac --- /dev/null +++ b/cmake/MatlabWrap.cmake @@ -0,0 +1,477 @@ +find_package( + Matlab + COMPONENTS MEX_COMPILER + REQUIRED) + +if(NOT Matlab_MEX_COMPILER) + message( + FATAL_ERROR + "Cannot find MEX compiler binary. Please check your Matlab installation and ensure MEX in installed as well." + ) +endif() + +if(WRAP_BUILD_TYPE_POSTFIXES) + set(CURRENT_POSTFIX ${CMAKE_${CMAKE_BUILD_TYPE_UPPER}_POSTFIX}) +endif() + +# WRAP_MEX_BUILD_STATIC_MODULE is not for Windows - on Windows any static +# are already compiled into the library by the linker +if(WRAP_MEX_BUILD_STATIC_MODULE AND WIN32) + message(FATAL_ERROR "WRAP_MEX_BUILD_STATIC_MODULE should not be set on Windows - the linker already automatically compiles in any dependent static libraries. To create a standalone toolbox pacakge, simply ensure that CMake finds the static versions of all dependent libraries (Boost, etc).") +endif() + +set(MEX_COMMAND ${Matlab_MEX_COMPILER} CACHE PATH "Path to MATLAB MEX compiler") +set(MATLAB_ROOT ${Matlab_ROOT_DIR} CACHE PATH "Path to MATLAB installation root (e.g. /usr/local/MATLAB/R2012a)") + +# Try to automatically configure mex path from provided custom `bin` path. +if(WRAP_CUSTOM_MATLAB_PATH) + set(matlab_bin_directory ${WRAP_CUSTOM_MATLAB_PATH}) + + if(WIN32) + set(mex_program_name "mex.bat") + else() + set(mex_program_name "mex") + endif() + + # Run find_program explicitly putting $PATH after our predefined program + # directories using 'ENV PATH' and 'NO_SYSTEM_ENVIRONMENT_PATH' - this prevents + # finding the LaTeX mex program (totally unrelated to MATLAB Mex) when LaTeX is + # on the system path. + find_program(MEX_COMMAND ${mex_program_name} + PATHS ${matlab_bin_directory} ENV PATH + NO_DEFAULT_PATH) + mark_as_advanced(FORCE MEX_COMMAND) + # Now that we have mex, trace back to find the Matlab installation root + get_filename_component(MEX_COMMAND "${MEX_COMMAND}" REALPATH) + get_filename_component(mex_path "${MEX_COMMAND}" PATH) + if(mex_path MATCHES ".*/win64$") + get_filename_component(MATLAB_ROOT "${mex_path}/../.." ABSOLUTE) + else() + get_filename_component(MATLAB_ROOT "${mex_path}/.." ABSOLUTE) + endif() +endif() + +# Consistent and user-friendly wrap function +function(matlab_wrap interfaceHeader linkLibraries + extraIncludeDirs extraMexFlags ignore_classes) + wrap_and_install_library("${interfaceHeader}" "${linkLibraries}" + "${extraIncludeDirs}" "${extraMexFlags}" + "${ignore_classes}") +endfunction() + +# Wrapping function. Builds a mex module from the provided +# interfaceHeader. For example, for the interface header gtsam.h, this will +# build the wrap module 'gtsam'. +# +# Arguments: +# +# interfaceHeader: The relative path to the wrapper interface definition file. +# linkLibraries: Any *additional* libraries to link. Your project library +# (e.g. `lba`), libraries it depends on, and any necessary MATLAB libraries will +# be linked automatically. So normally, leave this empty. +# extraIncludeDirs: Any *additional* include paths required by dependent libraries that have not +# already been added by include_directories. Again, normally, leave this empty. +# extraMexFlags: Any *additional* flags to pass to the compiler when building +# the wrap code. Normally, leave this empty. +# ignore_classes: List of classes to ignore in the wrapping. +function(wrap_and_install_library interfaceHeader linkLibraries + extraIncludeDirs extraMexFlags ignore_classes) + wrap_library_internal("${interfaceHeader}" "${linkLibraries}" + "${extraIncludeDirs}" "${mexFlags}") + install_wrapped_library_internal("${interfaceHeader}") +endfunction() + +# Internal function that wraps a library and compiles the wrapper +function(wrap_library_internal interfaceHeader linkLibraries extraIncludeDirs + extraMexFlags) + if(UNIX AND NOT APPLE) + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(mexModuleExt mexa64) + else() + set(mexModuleExt mexglx) + endif() + elseif(APPLE) + set(mexModuleExt mexmaci64) + elseif(MSVC) + if(CMAKE_CL_64) + set(mexModuleExt mexw64) + else() + set(mexModuleExt mexw32) + endif() + endif() + + # Wrap codegen interface usage: wrap interfacePath moduleName toolboxPath + # headerPath interfacePath : *absolute* path to directory of module interface + # file moduleName : the name of the module, interface file must be called + # moduleName.h toolboxPath : the directory in which to generate the wrappers + # headerPath : path to matlab.h + + # Extract module name from interface header file name + get_filename_component(interfaceHeader "${interfaceHeader}" ABSOLUTE) + get_filename_component(modulePath "${interfaceHeader}" PATH) + get_filename_component(moduleName "${interfaceHeader}" NAME_WE) + + # Paths for generated files + set(generated_files_path "${PROJECT_BINARY_DIR}/wrap/${moduleName}") + set(generated_cpp_file "${generated_files_path}/${moduleName}_wrapper.cpp") + set(compiled_mex_modules_root "${PROJECT_BINARY_DIR}/wrap/${moduleName}_mex") + + message(STATUS "Building wrap module ${moduleName}") + + # Set matlab.h in project + set(matlab_h_path "${CMAKE_SOURCE_DIR}") + + # If building a static mex module, add all cmake-linked libraries to the + # explicit link libraries list so that the next block of code can unpack any + # static libraries + set(automaticDependencies "") + foreach(lib ${moduleName} ${linkLibraries}) + # message("MODULE NAME: ${moduleName}") + if(TARGET "${lib}") + get_target_property(dependentLibraries ${lib} INTERFACE_LINK_LIBRARIES) + # message("DEPENDENT LIBRARIES: ${dependentLibraries}") + if(dependentLibraries) + list(APPEND automaticDependencies ${dependentLibraries}) + endif() + endif() + endforeach() + + # CHRIS: Temporary fix. On my system the get_target_property above returned + # Not-found for gtsam module This needs to be fixed!! + if(UNIX AND NOT APPLE) + list( + APPEND + automaticDependencies + ${Boost_SERIALIZATION_LIBRARY_RELEASE} + ${Boost_FILESYSTEM_LIBRARY_RELEASE} + ${Boost_SYSTEM_LIBRARY_RELEASE} + ${Boost_THREAD_LIBRARY_RELEASE} + ${Boost_DATE_TIME_LIBRARY_RELEASE}) + # Only present in Boost >= 1.48.0 + if(Boost_TIMER_LIBRARY_RELEASE) + list(APPEND automaticDependencies ${Boost_TIMER_LIBRARY_RELEASE} + ${Boost_CHRONO_LIBRARY_RELEASE}) + if(WRAP_MEX_BUILD_STATIC_MODULE) + # list(APPEND automaticDependencies -Wl,--no-as-needed -lrt) + endif() + endif() + endif() + + # message("AUTOMATIC DEPENDENCIES: ${automaticDependencies}") CHRIS: End + # temporary fix + + # Separate dependencies + set(correctedOtherLibraries "") + set(otherLibraryTargets "") + set(otherLibraryNontargets "") + set(otherSourcesAndObjects "") + foreach(lib ${moduleName} ${linkLibraries} ${automaticDependencies}) + if(TARGET "${lib}") + if(WRAP_MEX_BUILD_STATIC_MODULE) + get_target_property(target_sources ${lib} SOURCES) + list(APPEND otherSourcesAndObjects ${target_sources}) + else() + list(APPEND correctedOtherLibraries ${lib}) + list(APPEND otherLibraryTargets ${lib}) + endif() + else() + get_filename_component(file_extension "${lib}" EXT) + get_filename_component(lib_name "${lib}" NAME_WE) + if(file_extension STREQUAL ".a" AND WRAP_MEX_BUILD_STATIC_MODULE) + # For building a static MEX module, unpack the static library and + # compile its object files into our module + file(MAKE_DIRECTORY "${generated_files_path}/${lib_name}_objects") + execute_process( + COMMAND ar -x "${lib}" + WORKING_DIRECTORY "${generated_files_path}/${lib_name}_objects" + RESULT_VARIABLE ar_result) + if(NOT ar_result EQUAL 0) + message(FATAL_ERROR "Failed extracting ${lib}") + endif() + + # Get list of object files + execute_process( + COMMAND ar -t "${lib}" + OUTPUT_VARIABLE object_files + RESULT_VARIABLE ar_result) + if(NOT ar_result EQUAL 0) + message(FATAL_ERROR "Failed listing ${lib}") + endif() + + # Add directory to object files + string(REPLACE "\n" ";" object_files_list "${object_files}") + foreach(object_file ${object_files_list}) + get_filename_component(file_extension "${object_file}" EXT) + if(file_extension STREQUAL ".o") + list(APPEND otherSourcesAndObjects + "${generated_files_path}/${lib_name}_objects/${object_file}") + endif() + endforeach() + else() + list(APPEND correctedOtherLibraries ${lib}) + list(APPEND otherLibraryNontargets ${lib}) + endif() + endif() + endforeach() + + # Check libraries for conflicting versions built-in to MATLAB + set(dependentLibraries "") + if(NOT "${otherLibraryTargets}" STREQUAL "") + foreach(target ${otherLibraryTargets}) + get_target_property(dependentLibrariesOne ${target} + INTERFACE_LINK_LIBRARIES) + list(APPEND dependentLibraries ${dependentLibrariesOne}) + endforeach() + endif() + list(APPEND dependentLibraries ${otherLibraryNontargets}) + check_conflicting_libraries_internal("${dependentLibraries}") + + # Set up generation of module source file + file(MAKE_DIRECTORY "${generated_files_path}") + + find_package(PythonInterp ${WRAP_PYTHON_VERSION} EXACT) + find_package(PythonLibs ${WRAP_PYTHON_VERSION} EXACT) + + add_custom_command( + OUTPUT ${generated_cpp_file} + DEPENDS ${interfaceHeader} ${module_library_target} ${otherLibraryTargets} + ${otherSourcesAndObjects} + COMMAND + ${CMAKE_COMMAND} -E env + "PYTHONPATH=${GTWRAP_PACKAGE_DIR}${GTWRAP_PATH_SEPARATOR}$ENV{PYTHONPATH}" + ${PYTHON_EXECUTABLE} ${MATLAB_WRAP_SCRIPT} --src ${interfaceHeader} + --module_name ${moduleName} --out ${generated_files_path} + --top_module_namespaces ${moduleName} --ignore ${ignore_classes} + VERBATIM + WORKING_DIRECTORY ${generated_files_path}) + + # Set up building of mex module + string(REPLACE ";" " " extraMexFlagsSpaced "${extraMexFlags}") + string(REPLACE ";" " " mexFlagsSpaced "${WRAP_BUILD_MEX_BINARY_FLAGS}") + add_library( + ${moduleName}_matlab_wrapper MODULE + ${generated_cpp_file} ${interfaceHeader} ${otherSourcesAndObjects}) + target_link_libraries(${moduleName}_matlab_wrapper ${correctedOtherLibraries}) + target_link_libraries(${moduleName}_matlab_wrapper ${moduleName}) + set_target_properties( + ${moduleName}_matlab_wrapper + PROPERTIES OUTPUT_NAME "${moduleName}_wrapper" + PREFIX "" + SUFFIX ".${mexModuleExt}" + LIBRARY_OUTPUT_DIRECTORY "${compiled_mex_modules_root}" + ARCHIVE_OUTPUT_DIRECTORY "${compiled_mex_modules_root}" + RUNTIME_OUTPUT_DIRECTORY "${compiled_mex_modules_root}" + CLEAN_DIRECT_OUTPUT 1) + set_property( + TARGET ${moduleName}_matlab_wrapper + APPEND_STRING + PROPERTY + COMPILE_FLAGS + " ${extraMexFlagsSpaced} ${mexFlagsSpaced} \"-I${MATLAB_ROOT}/extern/include\" -DMATLAB_MEX_FILE -DMX_COMPAT_32" + ) + set_property( + TARGET ${moduleName}_matlab_wrapper + APPEND + PROPERTY INCLUDE_DIRECTORIES ${extraIncludeDirs}) + # Disable build type postfixes for the mex module - we install in different + # directories for each build type instead + foreach(build_type ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER "${build_type}" build_type_upper) + set_target_properties(${moduleName}_matlab_wrapper + PROPERTIES ${build_type_upper}_POSTFIX "") + endforeach() + # Set up platform-specific flags + if(MSVC) + if(CMAKE_CL_64) + set(mxLibPath "${MATLAB_ROOT}/extern/lib/win64/microsoft") + else() + set(mxLibPath "${MATLAB_ROOT}/extern/lib/win32/microsoft") + endif() + target_link_libraries( + ${moduleName}_matlab_wrapper "${mxLibPath}/libmex.lib" + "${mxLibPath}/libmx.lib" "${mxLibPath}/libmat.lib") + set_target_properties(${moduleName}_matlab_wrapper + PROPERTIES LINK_FLAGS "/export:mexFunction") + set_property( + SOURCE "${generated_cpp_file}" + APPEND + PROPERTY COMPILE_FLAGS "/bigobj") + elseif(APPLE) + set(mxLibPath "${MATLAB_ROOT}/bin/maci64") + target_link_libraries( + ${moduleName}_matlab_wrapper "${mxLibPath}/libmex.dylib" + "${mxLibPath}/libmx.dylib" "${mxLibPath}/libmat.dylib") + endif() + + # Hacking around output issue with custom command Deletes generated build + # folder + add_custom_target( + wrap_${moduleName}_matlab_distclean + COMMAND cmake -E remove_directory ${generated_files_path} + COMMAND cmake -E remove_directory ${compiled_mex_modules_root}) +endfunction() + +# Internal function that installs a wrap toolbox +function(install_wrapped_library_internal interfaceHeader) + get_filename_component(moduleName "${interfaceHeader}" NAME_WE) + set(generated_files_path "${PROJECT_BINARY_DIR}/wrap/${moduleName}") + + # NOTE: only installs .m and mex binary files (not .cpp) - the trailing slash + # on the directory name here prevents creating the top-level module name + # directory in the destination. + message(STATUS "Installing Matlab Toolbox to ${WRAP_TOOLBOX_INSTALL_PATH}") + if(WRAP_BUILD_TYPE_POSTFIXES) + foreach(build_type ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER "${build_type}" build_type_upper) + if(${build_type_upper} STREQUAL "RELEASE") + set(build_type_tag "") # Don't create release mode tag on installed + # directory + else() + set(build_type_tag "${build_type}") + endif() + # Split up filename to strip trailing '/' in WRAP_TOOLBOX_INSTALL_PATH if + # there is one + get_filename_component(location "${WRAP_TOOLBOX_INSTALL_PATH}" PATH) + get_filename_component(name "${WRAP_TOOLBOX_INSTALL_PATH}" NAME) + install( + DIRECTORY "${generated_files_path}/" + DESTINATION "${location}/${name}${build_type_tag}" + CONFIGURATIONS "${build_type}" + FILES_MATCHING + PATTERN "*.m") + install( + TARGETS ${moduleName}_matlab_wrapper + LIBRARY DESTINATION "${location}/${name}${build_type_tag}" + CONFIGURATIONS "${build_type}" + RUNTIME DESTINATION "${location}/${name}${build_type_tag}" + CONFIGURATIONS "${build_type}") + endforeach() + else() + install( + DIRECTORY "${generated_files_path}/" + DESTINATION ${WRAP_TOOLBOX_INSTALL_PATH} + FILES_MATCHING + PATTERN "*.m") + install( + TARGETS ${moduleName}_matlab_wrapper + LIBRARY DESTINATION ${WRAP_TOOLBOX_INSTALL_PATH} + RUNTIME DESTINATION ${WRAP_TOOLBOX_INSTALL_PATH}) + endif() +endfunction() + +# Internal function to check for libraries installed with MATLAB that may +# conflict and prints a warning to move them if problems occur. +function(check_conflicting_libraries_internal libraries) + if(UNIX) + # Set path for matlab's built-in libraries + if(APPLE) + set(mxLibPath "${MATLAB_ROOT}/bin/maci64") + else() + if(CMAKE_CL_64) + set(mxLibPath "${MATLAB_ROOT}/bin/glnxa64") + else() + set(mxLibPath "${MATLAB_ROOT}/bin/glnx86") + endif() + endif() + + # List matlab's built-in libraries + file( + GLOB matlabLibs + RELATIVE "${mxLibPath}" + "${mxLibPath}/lib*") + + # Convert to base names + set(matlabLibNames "") + foreach(lib ${matlabLibs}) + get_filename_component(libName "${lib}" NAME_WE) + list(APPEND matlabLibNames "${libName}") + endforeach() + + # Get names of link libraries + set(linkLibNames "") + foreach(lib ${libraries}) + string(FIND "${lib}" "/" slashPos) + if(NOT slashPos EQUAL -1) + # If the name is a path, just get the library name + get_filename_component(libName "${lib}" NAME_WE) + list(APPEND linkLibNames "${libName}") + else() + # It's not a path, so see if it looks like a filename + get_filename_component(ext "${lib}" EXT) + if(NOT "${ext}" STREQUAL "") + # It's a filename, so get the base name + get_filename_component(libName "${lib}" NAME_WE) + list(APPEND linkLibNames "${libName}") + else() + # It's not a filename so it must be a short name, add the "lib" prefix + list(APPEND linkLibNames "lib${lib}") + endif() + endif() + endforeach() + + # Remove duplicates + list(REMOVE_DUPLICATES linkLibNames) + + set(conflictingLibs "") + foreach(lib ${linkLibNames}) + list(FIND matlabLibNames "${lib}" libPos) + if(NOT libPos EQUAL -1) + if(NOT conflictingLibs STREQUAL "") + set(conflictingLibs "${conflictingLibs}, ") + endif() + set(conflictingLibs "${conflictingLibs}${lib}") + endif() + endforeach() + + if(NOT "${conflictingLibs}" STREQUAL "") + message( + WARNING + "The project links to the libraries [ ${conflictingLibs} ] on your system, but " + "MATLAB is distributed with its own versions of these libraries which may conflict. " + "If you get strange errors or crashes with the MATLAB wrapper, move these " + "libraries out of MATLAB's built-in library directory, which is ${mxLibPath} on " + "your system. MATLAB will usually still work with these libraries moved away, but " + "if not, you'll have to compile the static MATLAB wrapper module." + ) + endif() + endif() +endfunction() + +# Helper function to install MATLAB scripts and handle multiple build types +# where the scripts should be installed to all build type toolboxes +function(install_matlab_scripts source_directory patterns) + set(patterns_args "") + set(exclude_patterns "") + + foreach(pattern ${patterns}) + list(APPEND patterns_args PATTERN "${pattern}") + endforeach() + if(WRAP_BUILD_TYPE_POSTFIXES) + foreach(build_type ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER "${build_type}" build_type_upper) + if(${build_type_upper} STREQUAL "RELEASE") + set(build_type_tag "") # Don't create release mode tag on installed + # directory + else() + set(build_type_tag "${build_type}") + endif() + # Split up filename to strip trailing '/' in WRAP_TOOLBOX_INSTALL_PATH if + # there is one + get_filename_component(location "${WRAP_TOOLBOX_INSTALL_PATH}" PATH) + get_filename_component(name "${WRAP_TOOLBOX_INSTALL_PATH}" NAME) + install( + DIRECTORY "${source_directory}" + DESTINATION "${location}/${name}${build_type_tag}" + CONFIGURATIONS "${build_type}" + FILES_MATCHING ${patterns_args} + PATTERN "${exclude_patterns}" EXCLUDE) + endforeach() + else() + install( + DIRECTORY "${source_directory}" + DESTINATION "${WRAP_TOOLBOX_INSTALL_PATH}" + FILES_MATCHING ${patterns_args} + PATTERN "${exclude_patterns}" EXCLUDE) + endif() + +endfunction() diff --git a/gtwrap/matlab_wrapper.py b/gtwrap/matlab_wrapper.py index 669bf474f0..aeaf221bd8 100755 --- a/gtwrap/matlab_wrapper.py +++ b/gtwrap/matlab_wrapper.py @@ -49,6 +49,8 @@ class MatlabWrapper(object): } """Methods that should not be wrapped directly""" whitelist = ['serializable', 'serialize'] + """Methods that should be ignored""" + ignore_methods = ['pickle'] """Datatypes that do not need to be checked in methods""" not_check_type = [] """Data types that are primitive types""" @@ -563,6 +565,8 @@ def class_comment(self, instantiated_class): for method in methods: if method.name in self.whitelist: continue + if method.name in self.ignore_methods: + continue comment += '%{name}({args})'.format(name=method.name, args=self._wrap_args(method.args)) @@ -587,7 +591,7 @@ def generate_matlab_wrapper(self): file_name = self._wrapper_name() + '.cpp' wrapper_file = textwrap.dedent('''\ - # include + # include # include ''') @@ -612,6 +616,9 @@ def wrap_methods(self, methods, globals=False, global_ns=None): methods = self._group_methods(methods) for method in methods: + if method in self.ignore_methods: + continue + if globals: self._debug("[wrap_methods] wrapping: {}..{}={}".format(method[0].parent.name, method[0].name, type(method[0].parent.name))) @@ -861,6 +868,8 @@ def wrap_class_methods(self, namespace_name, inst_class, methods, serialize=[Fal method_name = method[0].name if method_name in self.whitelist and method_name != 'serialize': continue + if method_name in self.ignore_methods: + continue if method_name == 'serialize': serialize[0] = True @@ -932,6 +941,9 @@ def wrap_static_methods(self, namespace_name, instantiated_class, serialize): format_name = list(static_method[0].name) format_name[0] = format_name[0].upper() + if static_method[0].name in self.ignore_methods: + continue + method_text += textwrap.indent(textwrap.dedent('''\ function varargout = {name}(varargin) '''.format(name=''.join(format_name))), @@ -1464,7 +1476,7 @@ def generate_wrapper(self, namespace): """Generate the c++ wrapper.""" # Includes wrapper_file = textwrap.dedent('''\ - #include + #include #include \n #include #include @@ -1473,7 +1485,16 @@ def generate_wrapper(self, namespace): includes_list = sorted(list(self.includes.keys()), key=lambda include: include.header) - wrapper_file += reduce(lambda x, y: str(x) + '\n' + str(y), includes_list) + '\n' + # Check the number of includes. + # If no includes, do nothing, if 1 then just append newline. + # if more than one, concatenate them with newlines. + if len(includes_list) == 0: + pass + elif len(includes_list) == 1: + wrapper_file += (str(includes_list[0]) + '\n') + else: + wrapper_file += reduce(lambda x, y: str(x) + '\n' + str(y), includes_list) + wrapper_file += '\n' typedef_instances = '\n' typedef_collectors = '' diff --git a/gtwrap/pybind_wrapper.py b/gtwrap/pybind_wrapper.py index c0e88e37af..a045afcbd6 100755 --- a/gtwrap/pybind_wrapper.py +++ b/gtwrap/pybind_wrapper.py @@ -76,6 +76,21 @@ def _wrap_method(self, method, cpp_class, prefix, suffix, method_suffix=""): gtsam::deserialize(serialized, *self); }}, py::arg("serialized")) '''.format(class_inst=cpp_class + '*')) + if cpp_method == "pickle": + if not cpp_class in self._serializing_classes: + raise ValueError("Cannot pickle a class which is not serializable") + return textwrap.dedent(''' + .def(py::pickle( + [](const {cpp_class} &a){{ // __getstate__ + /* Returns a string that encodes the state of the object */ + return py::make_tuple(gtsam::serialize(a)); + }}, + [](py::tuple t){{ // __setstate__ + {cpp_class} obj; + gtsam::deserialize(t[0].cast(), obj); + return obj; + }})) + '''.format(cpp_class=cpp_class)) is_method = isinstance(method, instantiator.InstantiatedMethod) is_static = isinstance(method, parser.StaticMethod) @@ -318,3 +333,4 @@ def wrap(self): wrapped_namespace=wrapped_namespace, boost_class_export=boost_class_export, ) + diff --git a/tests/expected-matlab/geometry_wrapper.cpp b/tests/expected-matlab/geometry_wrapper.cpp index 70f673d258..40cd184b2e 100644 --- a/tests/expected-matlab/geometry_wrapper.cpp +++ b/tests/expected-matlab/geometry_wrapper.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/tests/expected-python/geometry_pybind.cpp b/tests/expected-python/geometry_pybind.cpp index 3eee55bf44..be6482d89a 100644 --- a/tests/expected-python/geometry_pybind.cpp +++ b/tests/expected-python/geometry_pybind.cpp @@ -47,6 +47,17 @@ PYBIND11_MODULE(geometry_py, m_) { [](gtsam::Point2* self, string serialized){ gtsam::deserialize(serialized, *self); }, py::arg("serialized")) + +.def(py::pickle( + [](const gtsam::Point2 &a){ // __getstate__ + /* Returns a string that encodes the state of the object */ + return py::make_tuple(gtsam::serialize(a)); + }, + [](py::tuple t){ // __setstate__ + gtsam::Point2 obj; + gtsam::deserialize(t[0].cast(), obj); + return obj; + })) ; py::class_>(m_gtsam, "Point3") @@ -62,6 +73,17 @@ PYBIND11_MODULE(geometry_py, m_) { gtsam::deserialize(serialized, *self); }, py::arg("serialized")) +.def(py::pickle( + [](const gtsam::Point3 &a){ // __getstate__ + /* Returns a string that encodes the state of the object */ + return py::make_tuple(gtsam::serialize(a)); + }, + [](py::tuple t){ // __setstate__ + gtsam::Point3 obj; + gtsam::deserialize(t[0].cast(), obj); + return obj; + })) + .def_static("staticFunction",[](){return gtsam::Point3::staticFunction();}) .def_static("StaticFunctionRet",[]( double z){return gtsam::Point3::StaticFunctionRet(z);}, py::arg("z")); diff --git a/tests/geometry.h b/tests/geometry.h index 40d878c9fc..ec5d3b2774 100644 --- a/tests/geometry.h +++ b/tests/geometry.h @@ -22,6 +22,9 @@ class Point2 { VectorNotEigen vectorConfusion(); void serializable() const; // Sets flag and creates export, but does not make serialization functions + + // enable pickling in python + void pickle() const; }; #include @@ -35,6 +38,9 @@ class Point3 { // enabling serialization functionality void serialize() const; // Just triggers a flag internally and removes actual function + + // enable pickling in python + void pickle() const; }; }