#.rst: # # The following functions are defined: # # .. cmake:command:: add_python_library # # Add a library that contains a mix of C, C++, Fortran, Cython, F2PY, Template, # and Tempita sources. The required targets are automatically generated to # "lower" source files from their high-level representation to a file that the # compiler can accept. # # # add_python_library( # SOURCES [source1 [source2 ...]] # [INCLUDE_DIRECTORIES [dir1 [dir2 ...]] # [LINK_LIBRARIES [lib1 [lib2 ...]] # [DEPENDS [source1 [source2 ...]]]) # # # Example usage # ^^^^^^^^^^^^^ # # .. code-block:: cmake # # find_package(PythonExtensions) # # file(GLOB arpack_sources ARPACK/SRC/*.f ARPACK/UTIL/*.f) # # add_python_library(arpack_scipy # SOURCES ${arpack_sources} # ${g77_wrapper_sources} # INCLUDE_DIRECTORIES ARPACK/SRC # ) # # .. cmake:command:: add_python_extension # # Add a extension that contains a mix of C, C++, Fortran, Cython, F2PY, Template, # and Tempita sources. The required targets are automatically generated to # "lower" source files from their high-level representation to a file that the # compiler can accept. # # # add_python_extension( # SOURCES [source1 [source2 ...]] # [INCLUDE_DIRECTORIES [dir1 [dir2 ...]] # [LINK_LIBRARIES [lib1 [lib2 ...]] # [DEPENDS [source1 [source2 ...]]]) # # # Example usage # ^^^^^^^^^^^^^ # # .. code-block:: cmake # # find_package(PythonExtensions) # # file(GLOB arpack_sources ARPACK/SRC/*.f ARPACK/UTIL/*.f) # # add_python_extension(arpack_scipy # SOURCES ${arpack_sources} # ${g77_wrapper_sources} # INCLUDE_DIRECTORIES ARPACK/SRC # ) # # #============================================================================= # Copyright 2011 Kitware, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. #============================================================================= macro(_remove_whitespace _output) string(REGEX REPLACE "[ \r\n\t]+" " " ${_output} "${${_output}}") string(STRIP "${${_output}}" ${_output}) endmacro() function(add_python_library _name) set(options STATIC SHARED MODULE) set(multiValueArgs SOURCES INCLUDE_DIRECTORIES LINK_LIBRARIES COMPILE_DEFINITIONS DEPENDS) cmake_parse_arguments(_args "${options}" "" "${multiValueArgs}" ${ARGN} ) # Validate arguments to allow simpler debugging if(NOT _args_SOURCES) message( FATAL_ERROR "You have called add_python_library for library ${_name} without " "any source files. This typically indicates a problem with " "your CMakeLists.txt file" ) endif() # Initialize the list of sources set(_sources ${_args_SOURCES}) # Generate targets for all *.src files set(_processed ) foreach(_source IN LISTS _sources) if(${_source} MATCHES ".pyf.src$" OR ${_source} MATCHES "\\.f\\.src$") if(NOT NumPy_FOUND) message( FATAL_ERROR "NumPy is required to process *.src Template files" ) endif() string(REGEX REPLACE "\\.[^.]*$" "" _source_we ${_source}) add_custom_command( OUTPUT ${_source_we} COMMAND ${NumPy_FROM_TEMPLATE_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/${_source} ${CMAKE_CURRENT_BINARY_DIR}/${_source_we} DEPENDS ${_source} ${_args_DEPENDS} COMMENT "Generating ${_source_we} from template ${_source}" ) list(APPEND _processed ${_source_we}) elseif(${_source} MATCHES "\\.c\\.src$") if(NOT NumPy_FOUND) message( FATAL_ERROR "NumPy is required to process *.src Template files" ) endif() string(REGEX REPLACE "\\.[^.]*$" "" _source_we ${_source}) add_custom_command( OUTPUT ${_source_we} COMMAND ${NumPy_CONV_TEMPLATE_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/${_source} ${CMAKE_CURRENT_BINARY_DIR}/${_source_we} DEPENDS ${_source} ${_args_DEPENDS} COMMENT "Generating ${_source_we} from template ${_source}" ) list(APPEND _processed ${_source_we}) elseif(${_source} MATCHES "\\.pyx\\.in$") if(NOT Cython_FOUND) message( FATAL_ERROR "Cython is required to process *.in Tempita files" ) endif() string(REGEX REPLACE "\\.[^.]*$" "" _source_we ${_source}) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/${_source} ${CMAKE_CURRENT_BINARY_DIR}/${_source} COPYONLY ) set(_tempita_command " import os; import sys; from Cython.Tempita import Template; cwd = os.getcwd(); open(os.path.join(cwd, '${_source_we}'), 'w+') .write( Template.from_filename(os.path.join(cwd, '${_source}'), encoding=sys.getdefaultencoding()).substitute() ) " ) _remove_whitespace(_tempita_command) add_custom_command( OUTPUT ${_source_we} COMMAND ${PYTHON_EXECUTABLE} -c "${_tempita_command}" DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${_source}" ${_args_DEPENDS} ) list(APPEND _processed ${_source_we}) else() list(APPEND _processed ${_source}) endif() endforeach() set(_sources ${_processed}) # If we're building a Python extension and we're given only Fortran sources, # We can conclude that we need to generate a Fortran interface file list(FILTER _processed EXCLUDE REGEX "(\\.f|\\.f90)$") if(NOT _processed AND _args_MODULE) if(NOT NumPy_FOUND) message( FATAL_ERROR "NumPy is required to process *.pyf F2PY files" ) endif() set(_sources_abs ) foreach(_source IN LISTS _sources) if(NOT IS_ABSOLUTE ${_source}) set(_source ${CMAKE_CURRENT_SOURCE_DIR}/${_source}) endif() list(APPEND _sources_abs ${_source}) endforeach() add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${_name}.pyf WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMAND ${F2PY_EXECUTABLE} ARGS -h ${_name}.pyf -m ${_name} --overwrite-signature ${_sources_abs} DEPENDS ${_sources} ${_args_DEPENDS} COMMENT "Generating ${_name} Fortran interface file" ) list(APPEND _sources ${_name}.pyf) endif() # Are there F2PY targets? set(_has_f2py_targets OFF) set(_has_cython_targets OFF) # Generate targets for all *.pyx and *.pyf files set(_processed ) foreach(_source IN LISTS _sources) if(${_source} MATCHES \\.pyx$) if(NOT Cython_FOUND) message( FATAL_ERROR "Cython is required to process *.pyx Cython files" ) endif() string(REGEX REPLACE "\\.[^.]*$" "" _pyx_target_name ${_source}) set(_has_cython_targets ON) add_cython_target(${_pyx_target_name} ${_source} OUTPUT_VAR _pyx_target_output DEPENDS ${_args_DEPENDS} ) list(APPEND _processed ${_pyx_target_output}) elseif(${_source} MATCHES \\.pyf$) if(NOT NumPy_FOUND) message( FATAL_ERROR "NumPy is required to process *.pyf F2PY files" ) endif() string(REGEX REPLACE "\\.[^.]*$" "" _pyf_target_name ${_source}) set(_has_f2py_targets ON) add_f2py_target(${_pyf_target_name} ${_source} OUTPUT_VAR _pyf_target_output DEPENDS ${_args_DEPENDS} ) list(APPEND _processed ${_pyf_target_output}) else() list(APPEND _processed ${_source}) endif() endforeach() set(_sources ${_processed}) if(_args_SHARED) add_library(${_name} SHARED ${_sources}) elseif(_args_MODULE) add_library(${_name} MODULE ${_sources}) else() # Assume static add_library(${_name} STATIC ${_sources}) endif() target_include_directories(${_name} PRIVATE ${_args_INCLUDE_DIRECTORIES}) target_link_libraries(${_name} ${SKBUILD_LINK_LIBRARIES_KEYWORD} ${_args_LINK_LIBRARIES}) if(_has_f2py_targets) target_include_directories(${_name} PRIVATE ${F2PY_INCLUDE_DIRS}) target_link_libraries(${_name} ${SKBUILD_LINK_LIBRARIES_KEYWORD} ${F2PY_LIBRARIES}) endif() if(_args_COMPILE_DEFINITIONS) target_compile_definitions(${_name} PRIVATE ${_args_COMPILE_DEFINITIONS}) endif() if(_args_DEPENDS) add_custom_target( "${_name}_depends" DEPENDS ${_args_DEPENDS} ) add_dependencies(${_name} "${_name}_depends") endif() endfunction() function(add_python_extension _name) # FIXME: make sure that extensions with the same name can happen # in multiple directories set(multiValueArgs SOURCES INCLUDE_DIRECTORIES LINK_LIBRARIES COMPILE_DEFINITIONS DEPENDS) cmake_parse_arguments(_args "" "" "${multiValueArgs}" ${ARGN} ) # Validate arguments to allow simpler debugging if(NOT _args_SOURCES) message( FATAL_ERROR "You have called add_python_extension for library ${_name} without " "any source files. This typically indicates a problem with " "your CMakeLists.txt file" ) endif() add_python_library(${_name} MODULE SOURCES ${_args_SOURCES} INCLUDE_DIRECTORIES ${_args_INCLUDE_DIRECTORIES} LINK_LIBRARIES ${_args_LINK_LIBRARIES} COMPILE_DEFINITIONS ${_args_COMPILE_DEFINITIONS} DEPENDS ${_args_DEPENDS} ) python_extension_module(${_name}) file(RELATIVE_PATH _relative "${CMAKE_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}") if(_relative STREQUAL "") set(_relative ".") endif() install( TARGETS ${_name} LIBRARY DESTINATION "${_relative}" RUNTIME DESTINATION "${_relative}" ) endfunction()