|
| 1 | +# Define a function to create Cython modules. |
| 2 | +# |
| 3 | +# For more information on the Cython project, see http://cython.org/. |
| 4 | +# "Cython is a language that makes writing C extensions for the Python language |
| 5 | +# as easy as Python itself." |
| 6 | +# |
| 7 | +# This file defines a CMake function to build a Cython Python module. |
| 8 | +# To use it, first include this file. |
| 9 | +# |
| 10 | +# include( UseCython ) |
| 11 | +# |
| 12 | +# Then call cython_add_module to create a module. |
| 13 | +# |
| 14 | +# cython_add_module( <module_name> <src1> <src2> ... <srcN> ) |
| 15 | +# |
| 16 | +# To create a standalone executable, the function |
| 17 | +# |
| 18 | +# cython_add_standalone_executable( <executable_name> [MAIN_MODULE src1] <src1> <src2> ... <srcN> ) |
| 19 | +# |
| 20 | +# To avoid dependence on Python, set the PYTHON_LIBRARY cache variable to point |
| 21 | +# to a static library. If a MAIN_MODULE source is specified, |
| 22 | +# the "if __name__ == '__main__':" from that module is used as the C main() method |
| 23 | +# for the executable. If MAIN_MODULE, the source with the same basename as |
| 24 | +# <executable_name> is assumed to be the MAIN_MODULE. |
| 25 | +# |
| 26 | +# Where <module_name> is the name of the resulting Python module and |
| 27 | +# <src1> <src2> ... are source files to be compiled into the module, e.g. *.pyx, |
| 28 | +# *.py, *.c, *.cxx, etc. A CMake target is created with name <module_name>. This can |
| 29 | +# be used for target_link_libraries(), etc. |
| 30 | +# |
| 31 | +# The sample paths set with the CMake include_directories() command will be used |
| 32 | +# for include directories to search for *.pxd when running the Cython complire. |
| 33 | +# |
| 34 | +# Cache variables that effect the behavior include: |
| 35 | +# |
| 36 | +# CYTHON_ANNOTATE |
| 37 | +# CYTHON_NO_DOCSTRINGS |
| 38 | +# CYTHON_FLAGS |
| 39 | +# |
| 40 | +# Source file properties that effect the build process are |
| 41 | +# |
| 42 | +# CYTHON_IS_CXX |
| 43 | +# |
| 44 | +# If this is set of a *.pyx file with CMake set_source_files_properties() |
| 45 | +# command, the file will be compiled as a C++ file. |
| 46 | +# |
| 47 | +# See also FindCython.cmake |
| 48 | + |
| 49 | +#============================================================================= |
| 50 | +# Copyright 2011 Kitware, Inc. |
| 51 | +# |
| 52 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 53 | +# you may not use this file except in compliance with the License. |
| 54 | +# You may obtain a copy of the License at |
| 55 | +# |
| 56 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 57 | +# |
| 58 | +# Unless required by applicable law or agreed to in writing, software |
| 59 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 60 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 61 | +# See the License for the specific language governing permissions and |
| 62 | +# limitations under the License. |
| 63 | +#============================================================================= |
| 64 | + |
| 65 | +# Configuration options. |
| 66 | +set( CYTHON_ANNOTATE OFF |
| 67 | + CACHE BOOL "Create an annotated .html file when compiling *.pyx." ) |
| 68 | +set( CYTHON_NO_DOCSTRINGS OFF |
| 69 | + CACHE BOOL "Strip docstrings from the compiled module." ) |
| 70 | +set( CYTHON_FLAGS "" CACHE STRING |
| 71 | + "Extra flags to the cython compiler." ) |
| 72 | +mark_as_advanced( CYTHON_ANNOTATE CYTHON_NO_DOCSTRINGS CYTHON_FLAGS ) |
| 73 | + |
| 74 | +find_package( Cython REQUIRED ) |
| 75 | +find_package( PythonLibs REQUIRED ) |
| 76 | + |
| 77 | +set( CYTHON_CXX_EXTENSION "cxx" ) |
| 78 | +set( CYTHON_C_EXTENSION "c" ) |
| 79 | + |
| 80 | +# Create a *.c or *.cxx file from a *.pyx file. |
| 81 | +# Input the generated file basename. The generate file will put into the variable |
| 82 | +# placed in the "generated_file" argument. Finally all the *.py and *.pyx files. |
| 83 | +function( compile_pyx _name generated_file ) |
| 84 | + # Default to assuming all files are C. |
| 85 | + set( cxx_arg "" ) |
| 86 | + set( extension ${CYTHON_C_EXTENSION} ) |
| 87 | + set( pyx_lang "C" ) |
| 88 | + set( comment "Compiling Cython C source for ${_name}..." ) |
| 89 | + |
| 90 | + set( cython_include_directories "" ) |
| 91 | + set( pxd_dependencies "" ) |
| 92 | + set( c_header_dependencies "" ) |
| 93 | + set( pyx_locations "" ) |
| 94 | + |
| 95 | + foreach( pyx_file ${ARGN} ) |
| 96 | + get_filename_component( pyx_file_basename "${pyx_file}" NAME_WE ) |
| 97 | + |
| 98 | + # Determine if it is a C or C++ file. |
| 99 | + get_source_file_property( property_is_cxx ${pyx_file} CYTHON_IS_CXX ) |
| 100 | + if( ${property_is_cxx} ) |
| 101 | + set( cxx_arg "--cplus" ) |
| 102 | + set( extension ${CYTHON_CXX_EXTENSION} ) |
| 103 | + set( pyx_lang "CXX" ) |
| 104 | + set( comment "Compiling Cython CXX source for ${_name}..." ) |
| 105 | + endif() |
| 106 | + |
| 107 | + # Get the include directories. |
| 108 | + get_source_file_property( pyx_location ${pyx_file} LOCATION ) |
| 109 | + get_filename_component( pyx_path ${pyx_location} PATH ) |
| 110 | + get_directory_property( cmake_include_directories DIRECTORY ${pyx_path} INCLUDE_DIRECTORIES ) |
| 111 | + list( APPEND cython_include_directories ${cmake_include_directories} ) |
| 112 | + list( APPEND pyx_locations "${pyx_location}" ) |
| 113 | + |
| 114 | + # Determine dependencies. |
| 115 | + # Add the pxd file will the same name as the given pyx file. |
| 116 | + unset( corresponding_pxd_file CACHE ) |
| 117 | + find_file( corresponding_pxd_file ${pyx_file_basename}.pxd |
| 118 | + PATHS "${pyx_path}" ${cmake_include_directories} |
| 119 | + NO_DEFAULT_PATH ) |
| 120 | + if( corresponding_pxd_file ) |
| 121 | + list( APPEND pxd_dependencies "${corresponding_pxd_file}" ) |
| 122 | + endif() |
| 123 | + |
| 124 | + # pxd files to check for additional dependencies. |
| 125 | + set( pxds_to_check "${pyx_file}" "${pxd_dependencies}" ) |
| 126 | + set( pxds_checked "" ) |
| 127 | + set( number_pxds_to_check 1 ) |
| 128 | + while( ${number_pxds_to_check} GREATER 0 ) |
| 129 | + foreach( pxd ${pxds_to_check} ) |
| 130 | + list( APPEND pxds_checked "${pxd}" ) |
| 131 | + list( REMOVE_ITEM pxds_to_check "${pxd}" ) |
| 132 | + |
| 133 | + # check for C header dependencies |
| 134 | + file( STRINGS "${pxd}" extern_from_statements |
| 135 | + REGEX "cdef[ ]+extern[ ]+from.*$" ) |
| 136 | + foreach( statement ${extern_from_statements} ) |
| 137 | + # Had trouble getting the quote in the regex |
| 138 | + string( REGEX REPLACE "cdef[ ]+extern[ ]+from[ ]+[\"]([^\"]+)[\"].*" "\\1" header "${statement}" ) |
| 139 | + unset( header_location CACHE ) |
| 140 | + find_file( header_location ${header} PATHS ${cmake_include_directories} ) |
| 141 | + if( header_location ) |
| 142 | + list( FIND c_header_dependencies "${header_location}" header_idx ) |
| 143 | + if( ${header_idx} LESS 0 ) |
| 144 | + list( APPEND c_header_dependencies "${header_location}" ) |
| 145 | + endif() |
| 146 | + endif() |
| 147 | + endforeach() |
| 148 | + |
| 149 | + # check for pxd dependencies |
| 150 | + |
| 151 | + # Look for cimport statements. |
| 152 | + set( module_dependencies "" ) |
| 153 | + file( STRINGS "${pxd}" cimport_statements REGEX cimport ) |
| 154 | + foreach( statement ${cimport_statements} ) |
| 155 | + if( ${statement} MATCHES from ) |
| 156 | + string( REGEX REPLACE "from[ ]+([^ ]+).*" "\\1" module "${statement}" ) |
| 157 | + else() |
| 158 | + string( REGEX REPLACE "cimport[ ]+([^ ]+).*" "\\1" module "${statement}" ) |
| 159 | + endif() |
| 160 | + list( APPEND module_dependencies ${module} ) |
| 161 | + endforeach() |
| 162 | + list( REMOVE_DUPLICATES module_dependencies ) |
| 163 | + # Add the module to the files to check, if appropriate. |
| 164 | + foreach( module ${module_dependencies} ) |
| 165 | + unset( pxd_location CACHE ) |
| 166 | + find_file( pxd_location ${module}.pxd |
| 167 | + PATHS "${pyx_path}" ${cmake_include_directories} NO_DEFAULT_PATH ) |
| 168 | + if( pxd_location ) |
| 169 | + list( FIND pxds_checked ${pxd_location} pxd_idx ) |
| 170 | + if( ${pxd_idx} LESS 0 ) |
| 171 | + list( FIND pxds_to_check ${pxd_location} pxd_idx ) |
| 172 | + if( ${pxd_idx} LESS 0 ) |
| 173 | + list( APPEND pxds_to_check ${pxd_location} ) |
| 174 | + list( APPEND pxd_dependencies ${pxd_location} ) |
| 175 | + endif() # if it is not already going to be checked |
| 176 | + endif() # if it has not already been checked |
| 177 | + endif() # if pxd file can be found |
| 178 | + endforeach() # for each module dependency discovered |
| 179 | + endforeach() # for each pxd file to check |
| 180 | + list( LENGTH pxds_to_check number_pxds_to_check ) |
| 181 | + endwhile() |
| 182 | + endforeach() # pyx_file |
| 183 | + |
| 184 | + # Set additional flags. |
| 185 | + if( CYTHON_ANNOTATE ) |
| 186 | + set( annotate_arg "--annotate" ) |
| 187 | + endif() |
| 188 | + |
| 189 | + if( CYTHON_NO_DOCSTRINGS ) |
| 190 | + set( no_docstrings_arg "--no-docstrings" ) |
| 191 | + endif() |
| 192 | + |
| 193 | + if( "${CMAKE_BUILD_TYPE}" STREQUAL "Debug" OR |
| 194 | + "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo" ) |
| 195 | + set( cython_debug_arg "--gdb" ) |
| 196 | + endif() |
| 197 | + |
| 198 | + # Include directory arguments. |
| 199 | + list( REMOVE_DUPLICATES cython_include_directories ) |
| 200 | + set( include_directory_arg "" ) |
| 201 | + foreach( _include_dir ${cython_include_directories} ) |
| 202 | + set( include_directory_arg ${include_directory_arg} "-I" "${_include_dir}" ) |
| 203 | + endforeach() |
| 204 | + |
| 205 | + # Determining generated file name. |
| 206 | + set( _generated_file "${CMAKE_CURRENT_BINARY_DIR}/${_name}.${extension}" ) |
| 207 | + set_source_files_properties( ${_generated_file} PROPERTIES GENERATED TRUE ) |
| 208 | + set( ${generated_file} ${_generated_file} PARENT_SCOPE ) |
| 209 | + |
| 210 | + list( REMOVE_DUPLICATES pxd_dependencies ) |
| 211 | + list( REMOVE_DUPLICATES c_header_dependencies ) |
| 212 | + |
| 213 | + # Add the command to run the compiler. |
| 214 | + add_custom_command( OUTPUT ${_generated_file} |
| 215 | + COMMAND ${CYTHON_EXECUTABLE} |
| 216 | + ARGS ${cxx_arg} ${include_directory_arg} |
| 217 | + ${annotate_arg} ${no_docstrings_arg} ${cython_debug_arg} ${CYTHON_FLAGS} |
| 218 | + --output-file ${_generated_file} ${pyx_locations} |
| 219 | + DEPENDS ${pyx_locations} ${pxd_dependencies} |
| 220 | + IMPLICIT_DEPENDS ${pyx_lang} ${c_header_dependencies} |
| 221 | + COMMENT ${comment} |
| 222 | + ) |
| 223 | + |
| 224 | + # Remove their visibility to the user. |
| 225 | + set( corresponding_pxd_file "" CACHE INTERNAL "" ) |
| 226 | + set( header_location "" CACHE INTERNAL "" ) |
| 227 | + set( pxd_location "" CACHE INTERNAL "" ) |
| 228 | +endfunction() |
| 229 | + |
| 230 | +# cython_add_module( <name> src1 src2 ... srcN ) |
| 231 | +# Build the Cython Python module. |
| 232 | +function( cython_add_module _name ) |
| 233 | + set( pyx_module_sources "" ) |
| 234 | + set( other_module_sources "" ) |
| 235 | + foreach( _file ${ARGN} ) |
| 236 | + if( ${_file} MATCHES ".*\\.py[x]?$" ) |
| 237 | + list( APPEND pyx_module_sources ${_file} ) |
| 238 | + else() |
| 239 | + list( APPEND other_module_sources ${_file} ) |
| 240 | + endif() |
| 241 | + endforeach() |
| 242 | + compile_pyx( ${_name} generated_file ${pyx_module_sources} ) |
| 243 | + include_directories( ${PYTHON_INCLUDE_DIRS} ) |
| 244 | + python_add_module( ${_name} ${generated_file} ${other_module_sources} ) |
| 245 | + if( APPLE ) |
| 246 | + set_target_properties( ${_name} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup" ) |
| 247 | + else() |
| 248 | + target_link_libraries( ${_name} ${PYTHON_LIBRARIES} ) |
| 249 | + endif() |
| 250 | +endfunction() |
| 251 | + |
| 252 | +include( CMakeParseArguments ) |
| 253 | +# cython_add_standalone_executable( _name [MAIN_MODULE src3.py] src1 src2 ... srcN ) |
| 254 | +# Creates a standalone executable the given sources. |
| 255 | +function( cython_add_standalone_executable _name ) |
| 256 | + set( pyx_module_sources "" ) |
| 257 | + set( other_module_sources "" ) |
| 258 | + set( main_module "" ) |
| 259 | + cmake_parse_arguments( cython_arguments "" "MAIN_MODULE" "" ${ARGN} ) |
| 260 | + include_directories( ${PYTHON_INCLUDE_DIRS} ) |
| 261 | + foreach( _file ${cython_arguments_UNPARSED_ARGUMENTS} ) |
| 262 | + if( ${_file} MATCHES ".*\\.py[x]?$" ) |
| 263 | + get_filename_component( _file_we ${_file} NAME_WE ) |
| 264 | + if( "${_file_we}" STREQUAL "${_name}" ) |
| 265 | + set( main_module "${_file}" ) |
| 266 | + elseif( NOT "${_file}" STREQUAL "${cython_arguments_MAIN_MODULE}" ) |
| 267 | + set( PYTHON_MODULE_${_file_we}_static_BUILD_SHARED OFF ) |
| 268 | + compile_pyx( "${_file_we}_static" generated_file "${_file}" ) |
| 269 | + list( APPEND pyx_module_sources "${generated_file}" ) |
| 270 | + endif() |
| 271 | + else() |
| 272 | + list( APPEND other_module_sources ${_file} ) |
| 273 | + endif() |
| 274 | + endforeach() |
| 275 | + |
| 276 | + if( cython_arguments_MAIN_MODULE ) |
| 277 | + set( main_module ${cython_arguments_MAIN_MODULE} ) |
| 278 | + endif() |
| 279 | + if( NOT main_module ) |
| 280 | + message( FATAL_ERROR "main module not found." ) |
| 281 | + endif() |
| 282 | + get_filename_component( main_module_we "${main_module}" NAME_WE ) |
| 283 | + set( CYTHON_FLAGS ${CYTHON_FLAGS} --embed ) |
| 284 | + compile_pyx( "${main_module_we}_static" generated_file ${main_module} ) |
| 285 | + add_executable( ${_name} ${generated_file} ${pyx_module_sources} ${other_module_sources} ) |
| 286 | + target_link_libraries( ${_name} ${PYTHON_LIBRARIES} ${pyx_module_libs} ) |
| 287 | +endfunction() |
0 commit comments