gtsam/cmake/GtsamMatlabWrap.cmake

275 lines
12 KiB
CMake

# Set up cache options
option(GTSAM_MEX_BUILD_STATIC_MODULE "Build MATLAB wrapper statically (increases build time)" OFF)
set(GTSAM_BUILD_MEX_BINARY_FLAGS "" CACHE STRING "Extra flags for running Matlab MEX compilation")
set(GTSAM_TOOLBOX_INSTALL_PATH "" CACHE PATH "Matlab toolbox destination, blank defaults to CMAKE_INSTALL_PREFIX/borg/toolbox")
if(NOT GTSAM_TOOLBOX_INSTALL_PATH)
set(GTSAM_TOOLBOX_INSTALL_PATH "${CMAKE_INSTALL_PREFIX}/borg/toolbox")
endif()
# GTSAM_MEX_BUILD_STATIC_MODULE is not for Windows - on Windows any static
# are already compiled into the library by the linker
if(GTSAM_MEX_BUILD_STATIC_MODULE AND WIN32)
message(FATAL_ERROR "GTSAM_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()
# Try to automatically configure mex path
if(APPLE)
file(GLOB matlab_bin_directories "/Applications/MATLAB*/bin")
set(mex_program_name "mex")
elseif(WIN32)
file(GLOB matlab_bin_directories "C:/Program Files*/MATLAB/*/bin")
set(mex_program_name "mex.bat")
else()
file(GLOB matlab_bin_directories "/usr/local/MATLAB/*/bin")
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.
list(REVERSE matlab_bin_directories) # Reverse list so the highest version (sorted alphabetically) is preferred
find_program(MEX_COMMAND ${mex_program_name}
PATHS ${matlab_bin_directories} ENV PATH
DOC "Command to use for executing mex (if on path, 'mex' will work)"
NO_SYSTEM_ENVIRONMENT_PATH)
# Function to setup codegen and building of the wrap toolbox
#
# params:
# moduleName : the name of the module, interface file must be called moduleName.h
# mexFlags : Compilation flags to be passed to the mex compiler
# modulePath : relative path to module markup header file (called moduleName.h)
# otherLibraries : list of library targets this should depend on
# toolboxPath : the directory in which to generate/build wrappers
# wrap_header_path : path to the installed wrap header
function(wrap_library_generic moduleName mexFlags modulePath otherLibraries toolbox_path wrap_header_path)
set(module_markup_header_file "${moduleName}.h")
set(module_markup_header_path "${CMAKE_CURRENT_SOURCE_DIR}/${modulePath}/${module_markup_header_file}")
# Add wrap cpp file first
set(mexSources "")
list(APPEND mexSources ${moduleName}_wrapper.cpp)
# Add boost libraries
string(TOUPPER ${CMAKE_BUILD_TYPE} build_type_toupper)
if("${build_type_toupper}" STREQUAL "DEBUG")
list(APPEND otherLibraries ${Boost_SERIALIZATION_LIBRARY_DEBUG} ${Boost_FILESYSTEM_LIBRARY_DEBUG}
${Boost_SYSTEM_LIBRARY_DEBUG} ${Boost_THREAD_LIBRARY_DEBUG} ${Boost_DATE_TIME_LIBRARY_DEBUG}
${Boost_REGEX_LIBRARY_DEBUG})
if(Boost_TIMER_LIBRARY_DEBUG AND NOT GTSAM_DISABLE_NEW_TIMERS) # Only present in Boost >= 1.48.0
list(APPEND otherLibraries ${Boost_TIMER_LIBRARY_DEBUG} ${Boost_CHRONO_LIBRARY_DEBUG})
if(GTSAM_MEX_BUILD_STATIC_MODULE)
list(APPEND otherLibraries -Wl,--no-as-needed -lrt)
endif()
endif()
else()
list(APPEND otherLibraries ${Boost_SERIALIZATION_LIBRARY_RELEASE} ${Boost_FILESYSTEM_LIBRARY_RELEASE}
${Boost_SYSTEM_LIBRARY_RELEASE} ${Boost_THREAD_LIBRARY_RELEASE} ${Boost_DATE_TIME_LIBRARY_RELEASE}
${Boost_REGEX_LIBRARY_RELEASE})
if(Boost_TIMER_LIBRARY_RELEASE AND NOT GTSAM_DISABLE_NEW_TIMERS) # Only present in Boost >= 1.48.0
list(APPEND otherLibraries ${Boost_TIMER_LIBRARY_RELEASE} ${Boost_CHRONO_LIBRARY_RELEASE})
if(GTSAM_MEX_BUILD_STATIC_MODULE)
list(APPEND otherLibraries -Wl,--no-as-needed -lrt)
endif()
endif()
endif()
# Sort otherLibraries into files and targets, add -shared or -static to other dependency library names
set(otherLibraryTargets "")
set(otherLibraryLinks "")
set(otherLibraryArchives "")
foreach(lib ${moduleName} ${otherLibraries})
if(TARGET ${lib})
list(APPEND otherLibraryTargets ${lib})
elseif(TARGET ${lib}-shared) # Prefer the shared library if we have both shared and static
list(APPEND otherLibraryTargets ${lib}-shared)
elseif(TARGET ${lib}-static)
list(APPEND otherLibraryTargets ${lib}-static)
else()
if(${lib} MATCHES "^.+\\.a$")
list(APPEND otherLibraryArchives ${lib})
else()
list(APPEND otherLibraryLinks ${lib})
endif()
endif()
endforeach()
# Get path to libraries with current build type
foreach(target ${otherLibraryTargets})
if(GTSAM_MEX_BUILD_STATIC_MODULE)
# If building the wrapper statically, add the target's sources to the mex sources
get_target_property(librarySources ${target} SOURCES)
list(APPEND mexSources ${librarySources})
else()
# If not building wrapper staticically, link to the target library
get_target_property(libraryFile ${target} LOCATION_${build_type_toupper})
get_target_property(targetType ${target} TYPE)
if(WIN32 AND NOT targetType STREQUAL STATIC_LIBRARY)
# On Windows with a shared library, we link to the import library file
get_filename_component(libraryDir ${libraryFile} PATH)
get_filename_component(libraryName ${libraryFile} NAME_WE)
get_filename_component(oneUpDir ${libraryDir} NAME)
string(TOUPPER "${oneUpDir}" oneUpDir)
if("${oneUpDir}" STREQUAL BIN OR "${oneUpDir}" STREQUAL LIB)
# One level up is 'bin' or 'lib' so we do not have an extra
# subdirectory for the build configuration.
set(libraryFile "${libraryDir}/../lib/${libraryName}.lib")
else()
# One level up was neither 'bin' nor 'lib' so assume we also
# have a subdirectory for the build configuration.
set(libraryFile "${libraryDir}/../../lib/${build_type_toupper}/${libraryName}.lib")
endif()
endif()
list(APPEND otherLibraryLinks ${libraryFile})
endif()
endforeach()
# Bad hack - if static wrapper and this is GTSAM, add colamd to the include path
if(GTSAM_MEX_BUILD_STATIC_MODULE AND (${moduleName} STREQUAL "gtsam" OR ${moduleName} STREQUAL "gtsam_unstable"))
list(APPEND mexFlags
"-I${PROJECT_SOURCE_DIR}/gtsam/3rdparty/CCOLAMD/Include" "-I${PROJECT_SOURCE_DIR}/gtsam/3rdparty/UFconfig")
endif()
# If using Boost shared libs, set up auto linking for shared libs
if(NOT Boost_USE_STATIC_LIBS)
list(APPEND mexFlags "-DBOOST_ALL_DYN_LINK")
endif()
# Add the CXXFLAGS from building the library
if(MSVC)
set(compile_flag_var "COMPFLAGS")
set(link_flag_var "LINKFLAGS")
set(cxx_flags_extra "/bigobj")
else()
set(compile_flag_var "CXXFLAGS")
set(link_flag_var "LDFLAGS")
set(cxx_flags_extra "")
endif()
list(APPEND mexFlags "${compile_flag_var}=$${compile_flag_var} ${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${build_type_toupper}} ${cxx_flags_extra}")
# If building a static module on UNIX, extract dependent .a libraries and add their .o files
if(UNIX AND GTSAM_MEX_BUILD_STATIC_MODULE)
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/wrap/external_archives)
foreach(archive ${otherLibraryArchives})
get_filename_component(archive_name ${archive} NAME_WE)
set(extraction_dir ${CMAKE_BINARY_DIR}/wrap/external_archives/${archive_name})
file(MAKE_DIRECTORY ${extraction_dir})
# Extract archive
execute_process(
COMMAND ar x ${archive}
WORKING_DIRECTORY ${extraction_dir})
# Get list of .o's in archive
execute_process(
COMMAND ar t ${archive}
OUTPUT_VARIABLE object_files OUTPUT_STRIP_TRAILING_WHITESPACE)
string(REPLACE "\n" ";" object_list ${object_files})
set(object_list_absolute "")
foreach(object ${object_list})
list(APPEND object_list_absolute ${extraction_dir}/${object})
endforeach()
list(APPEND mexSources ${object_list_absolute})
endforeach()
set(otherLibraryArchives "")
endif()
# Add libraries to command line
string(REPLACE ";" " " otherLibraryArchivesSpaced "${otherLibraryArchives}")
list(APPEND mexFlags "${link_flag_var}=$${link_flag_var} ${otherLibraryArchivesSpaced}")
list(APPEND mexFlags ${otherLibraryLinks})
# Add -g if debug mode
if(${build_type_toupper} STREQUAL DEBUG OR ${build_type_toupper} STREQUAL RELWITHDEBINFO)
list(APPEND mexFlags -g)
endif()
# Verbose mex - print mex compilation flags to help notice flag problems
list(APPEND mexFlags -v)
# Add sources
set(mexSourcesFiltered "")
foreach(src ${mexSources})
if(src MATCHES "^.+\\.cpp$" OR src MATCHES "^.+\\.c$" OR src MATCHES "^.+\\.o$")
list(APPEND mexSourcesFiltered ${src})
endif()
endforeach()
set(mexFlags ${mexSourcesFiltered} ${mexFlags})
string(REPLACE ";" "' '" mexFlagsForDisplay "${mexFlags}")
message(STATUS "Building Matlab Wrapper, mexFlags: [ '${mexFlagsForDisplay}' ]")
# 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
# Code generation command
# FIXME: doesn't get cleaned properly because cmake doesn't track the dependency
# TODO: convert to a custom_command with output specified
get_target_property(wrap_LOCATION wrap LOCATION_${CMAKE_BUILD_TYPE})
add_custom_target(wrap_${moduleName}
ALL # Forces wrap to always be run, but no files are touched if they don't change
COMMAND
${wrap_LOCATION}
${CMAKE_CURRENT_SOURCE_DIR}/${modulePath}
${moduleName}
${toolbox_path}
${wrap_header_path}
VERBATIM)
add_dependencies(wrap_${moduleName} wrap ${module_markup_header_path})
# Hacking around output issue with custom command
# Deletes generated build folder
add_custom_target(wrap_${moduleName}_distclean
COMMAND
cmake -E remove_directory ${toolbox_path})
# Make building mex binaries a part of the all (and install) target
set(build_all "ALL")
# If building wrapper statically, we compile all the library sources
# with mex, so need no extra dependencies, but if not building statically,
# we depend on the libraries since we will link to them.
if(GTSAM_MEX_BUILD_STATIC_MODULE)
set(wrap_build_dependencies "")
else()
set(wrap_build_dependencies ${moduleTarget} ${otherLibraryTargets})
endif()
# Actual build target for building mex files
add_custom_target(wrap_${moduleName}_build ${build_all}
COMMAND "${MEX_COMMAND}" ${mexFlags}
VERBATIM
WORKING_DIRECTORY ${toolbox_path})
add_dependencies(wrap_${moduleName}_build wrap_${moduleName} ${wrap_build_dependencies})
endfunction(wrap_library_generic)
# Function to setup codegen, building and installation of the wrap toolbox
# This wrap setup function assumes that the toolbox will be installed directly,
# with predictable matlab.h sourcing. Use this version when the toolbox will be used
# from the installed version, rather than in place.
# Assumes variable GTSAM_WRAP_HEADER_PATH has been set
# params:
# moduleName : the name of the module, interface file must be called moduleName.h
# mexFlags : Compilation flags to be passed to the mex compiler
# modulePath : relative path to module markup header file (called moduleName.h)
# otherLibraries : list of library targets this should depend on
function(wrap_library moduleName mexFlags modulePath otherLibraries)
# Toolbox generation path goes in build folder
set(toolbox_base_path ${CMAKE_BINARY_DIR}/wrap)
set(toolbox_path ${toolbox_base_path}/${moduleName})
# Call generic version of function
wrap_library_generic("${moduleName}" "${mexFlags}" "${modulePath}" "${otherLibraries}" "${toolbox_path}" "${GTSAM_WRAP_HEADER_PATH}")
# 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 ${GTSAM_TOOLBOX_INSTALL_PATH}")
install(DIRECTORY ${toolbox_path}/ DESTINATION ${GTSAM_TOOLBOX_INSTALL_PATH} FILES_MATCHING PATTERN "*.m")
install(DIRECTORY ${toolbox_path}/ DESTINATION ${GTSAM_TOOLBOX_INSTALL_PATH} FILES_MATCHING PATTERN "*.mex*")
endfunction(wrap_library)