311 lines
12 KiB
CMake
311 lines
12 KiB
CMake
# Define a function to create Cython modules.
|
|
#
|
|
# For more information on the Cython project, see http://cython.org/.
|
|
# "Cython is a language that makes writing C extensions for the Python language
|
|
# as easy as Python itself."
|
|
#
|
|
# This file defines a CMake function to build a Cython Python module.
|
|
# To use it, first include this file.
|
|
#
|
|
# include( UseCython )
|
|
#
|
|
# Then call cython_add_module to create a module.
|
|
#
|
|
# cython_add_module( <module_name> <src1> <src2> ... <srcN> )
|
|
#
|
|
# To create a standalone executable, the function
|
|
#
|
|
# cython_add_standalone_executable( <executable_name> [MAIN_MODULE src1] <src1> <src2> ... <srcN> )
|
|
#
|
|
# To avoid dependence on Python, set the PYTHON_LIBRARY cache variable to point
|
|
# to a static library. If a MAIN_MODULE source is specified,
|
|
# the "if __name__ == '__main__':" from that module is used as the C main() method
|
|
# for the executable. If MAIN_MODULE, the source with the same basename as
|
|
# <executable_name> is assumed to be the MAIN_MODULE.
|
|
#
|
|
# Where <module_name> is the name of the resulting Python module and
|
|
# <src1> <src2> ... are source files to be compiled into the module, e.g. *.pyx,
|
|
# *.py, *.c, *.cxx, etc. A CMake target is created with name <module_name>. This can
|
|
# be used for target_link_libraries(), etc.
|
|
#
|
|
# The sample paths set with the CMake include_directories() command will be used
|
|
# for include directories to search for *.pxd when running the Cython complire.
|
|
#
|
|
# Cache variables that effect the behavior include:
|
|
#
|
|
# CYTHON_ANNOTATE
|
|
# CYTHON_NO_DOCSTRINGS
|
|
# CYTHON_FLAGS
|
|
#
|
|
# Source file properties that effect the build process are
|
|
#
|
|
# CYTHON_IS_CXX
|
|
#
|
|
# If this is set of a *.pyx file with CMake set_source_files_properties()
|
|
# command, the file will be compiled as a C++ file.
|
|
#
|
|
# See also FindCython.cmake
|
|
|
|
#=============================================================================
|
|
# 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.
|
|
#=============================================================================
|
|
|
|
# Configuration options.
|
|
set( CYTHON_ANNOTATE OFF
|
|
CACHE BOOL "Create an annotated .html file when compiling *.pyx." )
|
|
set( CYTHON_NO_DOCSTRINGS OFF
|
|
CACHE BOOL "Strip docstrings from the compiled module." )
|
|
set( CYTHON_FLAGS "" CACHE STRING
|
|
"Extra flags to the cython compiler." )
|
|
mark_as_advanced( CYTHON_ANNOTATE CYTHON_NO_DOCSTRINGS CYTHON_FLAGS )
|
|
|
|
find_package( Cython REQUIRED )
|
|
|
|
|
|
set( CYTHON_CXX_EXTENSION "cxx" )
|
|
set( CYTHON_C_EXTENSION "c" )
|
|
|
|
# Create a *.c or *.cxx file from a *.pyx file.
|
|
# Input the generated file basename. The generate file will put into the variable
|
|
# placed in the "generated_file" argument. Finally all the *.py and *.pyx files.
|
|
function( compile_pyx _name generated_file )
|
|
# Default to assuming all files are C.
|
|
set( cxx_arg "" )
|
|
set( extension ${CYTHON_C_EXTENSION} )
|
|
set( pyx_lang "C" )
|
|
set( comment "Compiling Cython C source for ${_name}..." )
|
|
|
|
set( cython_include_directories "" )
|
|
set( pxd_dependencies "" )
|
|
set( pxi_dependencies "" )
|
|
set( c_header_dependencies "" )
|
|
set( pyx_locations "" )
|
|
|
|
foreach( pyx_file ${ARGN} )
|
|
get_filename_component( pyx_file_basename "${pyx_file}" NAME_WE )
|
|
|
|
# Determine if it is a C or C++ file.
|
|
get_source_file_property( property_is_cxx ${pyx_file} CYTHON_IS_CXX )
|
|
if( ${property_is_cxx} )
|
|
set( cxx_arg "--cplus" )
|
|
set( extension ${CYTHON_CXX_EXTENSION} )
|
|
set( pyx_lang "CXX" )
|
|
set( comment "Compiling Cython CXX source for ${_name}..." )
|
|
endif()
|
|
|
|
# Get the include directories.
|
|
get_source_file_property( pyx_location ${pyx_file} LOCATION )
|
|
get_filename_component( pyx_path ${pyx_location} PATH )
|
|
message(${pyx_path})
|
|
get_directory_property( cmake_include_directories DIRECTORY ${pyx_path} INCLUDE_DIRECTORIES )
|
|
list( APPEND cython_include_directories ${cmake_include_directories} )
|
|
list( APPEND pyx_locations "${pyx_location}" )
|
|
|
|
# Determine dependencies.
|
|
# Add the pxd file will the same name as the given pyx file.
|
|
unset( corresponding_pxd_file CACHE )
|
|
find_file( corresponding_pxd_file ${pyx_file_basename}.pxd
|
|
PATHS "${pyx_path}" ${cmake_include_directories}
|
|
NO_DEFAULT_PATH )
|
|
if( corresponding_pxd_file )
|
|
list( APPEND pxd_dependencies "${corresponding_pxd_file}" )
|
|
endif()
|
|
|
|
# pxd files to check for additional dependencies.
|
|
set( pxds_to_check "${pyx_file}" "${pxd_dependencies}" )
|
|
set( pxds_checked "" )
|
|
set( number_pxds_to_check 1 )
|
|
while( ${number_pxds_to_check} GREATER 0 )
|
|
foreach( pxd ${pxds_to_check} )
|
|
list( APPEND pxds_checked "${pxd}" )
|
|
list( REMOVE_ITEM pxds_to_check "${pxd}" )
|
|
|
|
# check for C header dependencies
|
|
file( STRINGS "${pxd}" extern_from_statements
|
|
REGEX "cdef[ ]+extern[ ]+from.*$" )
|
|
foreach( statement ${extern_from_statements} )
|
|
# Had trouble getting the quote in the regex
|
|
string( REGEX REPLACE "cdef[ ]+extern[ ]+from[ ]+[\"]([^\"]+)[\"].*" "\\1" header "${statement}" )
|
|
unset( header_location CACHE )
|
|
find_file( header_location ${header} PATHS ${cmake_include_directories} )
|
|
if( header_location )
|
|
list( FIND c_header_dependencies "${header_location}" header_idx )
|
|
if( ${header_idx} LESS 0 )
|
|
list( APPEND c_header_dependencies "${header_location}" )
|
|
endif()
|
|
endif()
|
|
endforeach()
|
|
|
|
# check for pxd dependencies
|
|
|
|
# Look for cimport statements.
|
|
set( module_dependencies "" )
|
|
file( STRINGS "${pxd}" cimport_statements REGEX cimport )
|
|
foreach( statement ${cimport_statements} )
|
|
if( ${statement} MATCHES from )
|
|
string( REGEX REPLACE "from[ ]+([^ ]+).*" "\\1" module "${statement}" )
|
|
else()
|
|
string( REGEX REPLACE "cimport[ ]+([^ ]+).*" "\\1" module "${statement}" )
|
|
endif()
|
|
list( APPEND module_dependencies ${module} )
|
|
endforeach()
|
|
list( REMOVE_DUPLICATES module_dependencies )
|
|
# Add the module to the files to check, if appropriate.
|
|
foreach( module ${module_dependencies} )
|
|
unset( pxd_location CACHE )
|
|
find_file( pxd_location ${module}.pxd
|
|
PATHS "${pyx_path}" ${cmake_include_directories} NO_DEFAULT_PATH )
|
|
if( pxd_location )
|
|
list( FIND pxds_checked ${pxd_location} pxd_idx )
|
|
if( ${pxd_idx} LESS 0 )
|
|
list( FIND pxds_to_check ${pxd_location} pxd_idx )
|
|
if( ${pxd_idx} LESS 0 )
|
|
list( APPEND pxds_to_check ${pxd_location} )
|
|
list( APPEND pxd_dependencies ${pxd_location} )
|
|
endif() # if it is not already going to be checked
|
|
endif() # if it has not already been checked
|
|
endif() # if pxd file can be found
|
|
endforeach() # for each module dependency discovered
|
|
endforeach() # for each pxd file to check
|
|
list( LENGTH pxds_to_check number_pxds_to_check )
|
|
endwhile()
|
|
|
|
# Look for included pxi files
|
|
file(STRINGS "${pyx_file}" include_statements REGEX "include +['\"]([^'\"]+).*")
|
|
foreach(statement ${include_statements})
|
|
string(REGEX REPLACE "include +['\"]([^'\"]+).*" "\\1" pxi_file "${statement}")
|
|
unset(pxi_location CACHE)
|
|
find_file(pxi_location ${pxi_file}
|
|
PATHS "${pyx_path}" ${cmake_include_directories} NO_DEFAULT_PATH)
|
|
if (pxi_location)
|
|
list(APPEND pxi_dependencies ${pxi_location})
|
|
endif()
|
|
endforeach() # for each include statement found
|
|
|
|
endforeach() # pyx_file
|
|
|
|
# Set additional flags.
|
|
if( CYTHON_ANNOTATE )
|
|
set( annotate_arg "--annotate" )
|
|
endif()
|
|
|
|
if( CYTHON_NO_DOCSTRINGS )
|
|
set( no_docstrings_arg "--no-docstrings" )
|
|
endif()
|
|
|
|
if( "${CMAKE_BUILD_TYPE}" STREQUAL "Debug" OR
|
|
"${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo" )
|
|
set( cython_debug_arg "--gdb" )
|
|
endif()
|
|
|
|
if( "${PYTHONLIBS_VERSION_STRING}" MATCHES "^2." )
|
|
set( version_arg "-2" )
|
|
elseif( "${PYTHONLIBS_VERSION_STRING}" MATCHES "^3." )
|
|
set( version_arg "-3" )
|
|
else()
|
|
set( version_arg )
|
|
endif()
|
|
|
|
# Include directory arguments.
|
|
list( REMOVE_DUPLICATES cython_include_directories )
|
|
set( include_directory_arg "" )
|
|
foreach( _include_dir ${cython_include_directories} )
|
|
set( include_directory_arg ${include_directory_arg} "-I" "${_include_dir}" )
|
|
endforeach()
|
|
|
|
# Determining generated file name.
|
|
set( _generated_file "${CMAKE_CURRENT_BINARY_DIR}/${_name}.${extension}" )
|
|
set_source_files_properties( ${_generated_file} PROPERTIES GENERATED TRUE )
|
|
set( ${generated_file} ${_generated_file} PARENT_SCOPE )
|
|
|
|
list( REMOVE_DUPLICATES pxd_dependencies )
|
|
list( REMOVE_DUPLICATES c_header_dependencies )
|
|
|
|
# Add the command to run the compiler.
|
|
add_custom_command( OUTPUT ${_generated_file}
|
|
COMMAND ${CYTHON_EXECUTABLE}
|
|
ARGS ${cxx_arg} ${include_directory_arg} ${version_arg}
|
|
${annotate_arg} ${no_docstrings_arg} ${cython_debug_arg} ${CYTHON_FLAGS}
|
|
--output-file ${_generated_file} ${pyx_locations}
|
|
DEPENDS ${pyx_locations} ${pxd_dependencies} ${pxi_dependencies}
|
|
IMPLICIT_DEPENDS ${pyx_lang} ${c_header_dependencies}
|
|
COMMENT ${comment}
|
|
)
|
|
|
|
# Remove their visibility to the user.
|
|
set( corresponding_pxd_file "" CACHE INTERNAL "" )
|
|
set( header_location "" CACHE INTERNAL "" )
|
|
set( pxd_location "" CACHE INTERNAL "" )
|
|
endfunction()
|
|
|
|
# cython_add_module( <name> src1 src2 ... srcN )
|
|
# Build the Cython Python module.
|
|
function( cython_add_module _name )
|
|
set( pyx_module_sources "" )
|
|
set( other_module_sources "" )
|
|
foreach( _file ${ARGN} )
|
|
if( ${_file} MATCHES ".*\\.py[x]?$" )
|
|
list( APPEND pyx_module_sources ${_file} )
|
|
else()
|
|
list( APPEND other_module_sources ${_file} )
|
|
endif()
|
|
endforeach()
|
|
compile_pyx( ${_name} generated_file ${pyx_module_sources} )
|
|
include_directories( ${PYTHON_INCLUDE_DIRS} )
|
|
python_add_module( ${_name} ${generated_file} ${other_module_sources} )
|
|
if( APPLE )
|
|
set_target_properties( ${_name} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup" )
|
|
else()
|
|
target_link_libraries( ${_name} ${PYTHON_LIBRARIES} )
|
|
endif()
|
|
endfunction()
|
|
|
|
include( CMakeParseArguments )
|
|
# cython_add_standalone_executable( _name [MAIN_MODULE src3.py] src1 src2 ... srcN )
|
|
# Creates a standalone executable the given sources.
|
|
function( cython_add_standalone_executable _name )
|
|
set( pyx_module_sources "" )
|
|
set( other_module_sources "" )
|
|
set( main_module "" )
|
|
cmake_parse_arguments( cython_arguments "" "MAIN_MODULE" "" ${ARGN} )
|
|
include_directories( ${PYTHON_INCLUDE_DIRS} )
|
|
foreach( _file ${cython_arguments_UNPARSED_ARGUMENTS} )
|
|
if( ${_file} MATCHES ".*\\.py[x]?$" )
|
|
get_filename_component( _file_we ${_file} NAME_WE )
|
|
if( "${_file_we}" STREQUAL "${_name}" )
|
|
set( main_module "${_file}" )
|
|
elseif( NOT "${_file}" STREQUAL "${cython_arguments_MAIN_MODULE}" )
|
|
set( PYTHON_MODULE_${_file_we}_static_BUILD_SHARED OFF )
|
|
compile_pyx( "${_file_we}_static" generated_file "${_file}" )
|
|
list( APPEND pyx_module_sources "${generated_file}" )
|
|
endif()
|
|
else()
|
|
list( APPEND other_module_sources ${_file} )
|
|
endif()
|
|
endforeach()
|
|
|
|
if( cython_arguments_MAIN_MODULE )
|
|
set( main_module ${cython_arguments_MAIN_MODULE} )
|
|
endif()
|
|
if( NOT main_module )
|
|
message( FATAL_ERROR "main module not found." )
|
|
endif()
|
|
get_filename_component( main_module_we "${main_module}" NAME_WE )
|
|
set( CYTHON_FLAGS ${CYTHON_FLAGS} --embed )
|
|
compile_pyx( "${main_module_we}_static" generated_file ${main_module} )
|
|
add_executable( ${_name} ${generated_file} ${pyx_module_sources} ${other_module_sources} )
|
|
target_link_libraries( ${_name} ${PYTHON_LIBRARIES} ${pyx_module_libs} )
|
|
endfunction()
|