# Check Cython version, need to be >=0.25.2 # Unset these cached variables to avoid surprises when the python/cython # in the current environment are different from the cached! unset(PYTHON_EXECUTABLE CACHE) unset(CYTHON_EXECUTABLE CACHE) unset(PYTHON_INCLUDE_DIR CACHE) unset(PYTHON_MAJOR_VERSION CACHE) unset(PYTHON_LIBRARY CACHE) # Allow override from command line if(NOT DEFINED GTSAM_USE_CUSTOM_PYTHON_LIBRARY) if(GTSAM_PYTHON_VERSION STREQUAL "Default") find_package(PythonInterp REQUIRED) find_package(PythonLibs REQUIRED) else() find_package(PythonInterp ${GTSAM_PYTHON_VERSION} EXACT REQUIRED) find_package(PythonLibs ${GTSAM_PYTHON_VERSION} EXACT REQUIRED) endif() endif() find_package(Cython 0.25.2 REQUIRED) execute_process(COMMAND "${PYTHON_EXECUTABLE}" "-c" "from __future__ import print_function;import sys;print(sys.version[0], end='')" OUTPUT_VARIABLE PYTHON_MAJOR_VERSION ) # User-friendly Cython wrapping and installing function. # Builds a Cython module from the provided interface_header. # For example, for the interface header gtsam.h, # this will build the wrap module 'gtsam'. # # Arguments: # # interface_header: The relative path to the wrapper interface definition file. # extra_imports: extra header to import in the Cython pxd file. # For example, to use Cython gtsam.pxd in your own module, # use "from gtsam cimport *" # install_path: destination to install the library # libs: libraries to link with # dependencies: Dependencies which need to be built before the wrapper function(wrap_and_install_library_cython interface_header extra_imports install_path libs dependencies) # Paths for generated files get_filename_component(module_name "${interface_header}" NAME_WE) set(generated_files_path "${GTSAM_CYTHON_INSTALL_PATH}/${module_name}") wrap_library_cython("${interface_header}" "${generated_files_path}" "${extra_imports}" "${libs}" "${dependencies}") endfunction() function(set_up_required_cython_packages) # Set up building of cython module include_directories(${PYTHON_INCLUDE_DIRS}) find_package(NumPy REQUIRED) include_directories(${NUMPY_INCLUDE_DIRS}) endfunction() # Convert pyx to cpp by executing cython # This is the first step to compile cython from the command line # as described at: http://cython.readthedocs.io/en/latest/src/reference/compilation.html # # Arguments: # - target: The specified target for this step # - pyx_file: The input pyx_file in full *absolute* path # - generated_cpp: The output cpp file in full absolute path # - include_dirs: Directories to include when executing cython function(pyx_to_cpp target pyx_file generated_cpp include_dirs) foreach(dir ${include_dirs}) set(includes_for_cython ${includes_for_cython} -I ${dir}) endforeach() add_custom_command( OUTPUT ${generated_cpp} COMMAND ${CYTHON_EXECUTABLE} -X boundscheck=False -v --fast-fail --cplus -${PYTHON_MAJOR_VERSION} ${includes_for_cython} ${pyx_file} -o ${generated_cpp} VERBATIM) add_custom_target(${target} ALL DEPENDS ${generated_cpp}) endfunction() # Build the cpp file generated by converting pyx using cython # This is the second step to compile cython from the command line # as described at: http://cython.readthedocs.io/en/latest/src/reference/compilation.html # # Arguments: # - target: The specified target for this step # - cpp_file: The input cpp_file in full *absolute* path # - output_lib_we: The output lib filename only (without extension) # - output_dir: The output directory function(build_cythonized_cpp target cpp_file output_lib_we output_dir) add_library(${target} MODULE ${cpp_file}) if(WIN32) # Use .pyd extension instead of .dll on Windows set_target_properties(${target} PROPERTIES SUFFIX ".pyd") # Add full path to the Python library target_link_libraries(${target} ${PYTHON_LIBRARIES}) endif() if(APPLE) set(link_flags "-undefined dynamic_lookup") endif() set_target_properties(${target} PROPERTIES COMPILE_FLAGS "-w" LINK_FLAGS "${link_flags}" OUTPUT_NAME ${output_lib_we} PREFIX "" ${CMAKE_BUILD_TYPE_UPPER}_POSTFIX "" LIBRARY_OUTPUT_DIRECTORY ${output_dir}) endfunction() # Cythonize a pyx from the command line as described at # http://cython.readthedocs.io/en/latest/src/reference/compilation.html # Arguments: # - target: The specified target # - pyx_file: The input pyx_file in full *absolute* path # - output_lib_we: The output lib filename only (without extension) # - output_dir: The output directory # - include_dirs: Directories to include when executing cython # - libs: Libraries to link with # - interface_header: For dependency. Any update in interface header will re-trigger cythonize function(cythonize target pyx_file output_lib_we output_dir include_dirs libs interface_header dependencies) get_filename_component(pyx_path "${pyx_file}" DIRECTORY) get_filename_component(pyx_name "${pyx_file}" NAME_WE) set(generated_cpp "${output_dir}/${pyx_name}.cpp") set_up_required_cython_packages() pyx_to_cpp(${target}_pyx2cpp ${pyx_file} ${generated_cpp} "${include_dirs}") # Late dependency injection, to make sure this gets called whenever the interface header is updated # See: https://stackoverflow.com/questions/40032593/cmake-does-not-rebuild-dependent-after-prerequisite-changes add_custom_command(OUTPUT ${generated_cpp} DEPENDS ${interface_header} ${pyx_file} APPEND) if (NOT "${dependencies}" STREQUAL "") add_dependencies(${target}_pyx2cpp "${dependencies}") endif() build_cythonized_cpp(${target} ${generated_cpp} ${output_lib_we} ${output_dir}) if (NOT "${libs}" STREQUAL "") target_link_libraries(${target} "${libs}") endif() add_dependencies(${target} ${target}_pyx2cpp) endfunction() # Internal function that wraps a library and compiles the wrapper function(wrap_library_cython interface_header generated_files_path extra_imports libs dependencies) # Wrap codegen interface # Extract module path and name from interface header file name # wrap requires interfacePath to be *absolute* get_filename_component(interface_header "${interface_header}" ABSOLUTE) get_filename_component(module_path "${interface_header}" PATH) get_filename_component(module_name "${interface_header}" NAME_WE) # Wrap module to Cython pyx message(STATUS "Cython wrapper generating ${module_name}.pyx") set(generated_pyx "${generated_files_path}/${module_name}.pyx") file(MAKE_DIRECTORY "${generated_files_path}") add_custom_command( OUTPUT ${generated_pyx} DEPENDS ${interface_header} wrap COMMAND wrap --cython ${module_path} ${module_name} ${generated_files_path} "${extra_imports}" VERBATIM WORKING_DIRECTORY ${generated_files_path}/../) add_custom_target(cython_wrap_${module_name}_pyx ALL DEPENDS ${generated_pyx}) if(NOT "${dependencies}" STREQUAL "") add_dependencies(cython_wrap_${module_name}_pyx ${dependencies}) endif() message(STATUS "Cythonize and build ${module_name}.pyx") get_property(include_dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES) cythonize(cythonize_${module_name} ${generated_pyx} ${module_name} ${generated_files_path} "${include_dirs}" "${libs}" ${interface_header} cython_wrap_${module_name}_pyx) add_dependencies(${python_install_target} cython_wrap_${module_name}_pyx) # distclean add_custom_target(wrap_${module_name}_cython_distclean COMMAND cmake -E remove_directory ${generated_files_path}) endfunction() # Helper function to install Cython scripts and handle multiple build types where the scripts # should be installed to all build type toolboxes # # Arguments: # source_directory: The source directory to be installed. "The last component of each directory # name is appended to the destination directory but a trailing slash may be # used to avoid this because it leaves the last component empty." # (https://cmake.org/cmake/help/v3.3/command/install.html?highlight=install#installing-directories) # dest_directory: The destination directory to install to. # patterns: list of file patterns to install function(install_cython_scripts source_directory dest_directory patterns) set(patterns_args "") set(exclude_patterns "") foreach(pattern ${patterns}) list(APPEND patterns_args PATTERN "${pattern}") endforeach() file(COPY "${source_directory}" DESTINATION "${dest_directory}" FILES_MATCHING ${patterns_args} PATTERN "${exclude_patterns}" EXCLUDE) endfunction()