1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403
|
#.rst
# 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)
#
# The following functions are defined:
#
# add_cython_target(<Name> [<CythonInput>]
# [EMBED_MAIN]
# [C | CXX]
# [PY2 | PY3]
# [OUTPUT_VAR <OutputVar>])
#
# Create a custom rule to generate the source code for a Python extension module
# using cython. ``<Name>`` is the name of the new target, and ``<CythonInput>``
# is the path to a cython source file. Note that, despite the name, no new
# targets are created by this function. Instead, see ``OUTPUT_VAR`` for
# retrieving the path to the generated source for subsequent targets.
#
# If only ``<Name>`` is provided, and it ends in the ".pyx" extension, then it
# is assumed to be the ``<CythonInput>``. The name of the input without the
# extension is used as the target name. If only ``<Name>`` is provided, and it
# does not end in the ".pyx" extension, then the ``<CythonInput>`` is assumed to
# be ``<Name>.pyx``.
#
# The Cython include search path is amended with any entries found in the
# ``INCLUDE_DIRECTORIES`` property of the directory containing the
# ``<CythonInput>`` file. Use ``iunclude_directories`` to add to the Cython
# include search path.
#
# Options:
#
# ``EMBED_MAIN``
# Embed a main() function in the generated output (for stand-alone
# applications that initialize their own Python runtime).
#
# ``C | CXX``
# Force the generation of either a C or C++ file. By default, a C file is
# generated, unless the C language is not enabled for the project; in this
# case, a C++ file is generated by default.
#
# ``PY2 | PY3``
# Force compilation using either Python-2 or Python-3 syntax and code
# semantics. By default, Python-2 syntax and semantics are used if the major
# version of Python found is 2. Otherwise, Python-3 syntax and sematics are
# used.
#
# ``OUTPUT_VAR <OutputVar>``
# Set the variable ``<OutputVar>`` in the parent scope to the path to the
# generated source file. By default, ``<Name>`` is used as the output
# variable name.
#
# Defined variables:
#
# ``<OutputVar>``
# The path of the generated source file.
#
#
# Example usage:
#
# .. code-block:: cmake
#
# find_package(Cython)
#
# # Note: In this case, either one of these arguments may be omitted; their
# # value would have been inferred from that of the other.
# add_cython_target(cy_code cy_code.pyx)
#
# add_library(cy_code MODULE ${cy_code})
# target_link_libraries(cy_code ...)
#
# Cache variables that effect the behavior include:
#
# ``CYTHON_ANNOTATE``
# whether to create an annotated .html file when compiling
#
# ``CYTHON_FLAGS``
# additional flags to pass to the Cython compiler
#
# 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_FLAGS "" CACHE STRING
"Extra flags to the cython compiler.")
mark_as_advanced(CYTHON_ANNOTATE CYTHON_FLAGS)
find_package(PythonLibs REQUIRED)
set(CYTHON_CXX_EXTENSION "cxx")
set(CYTHON_C_EXTENSION "c")
get_property(languages GLOBAL PROPERTY ENABLED_LANGUAGES)
function(add_cython_target _name)
set(options EMBED_MAIN C CXX PY2 PY3)
set(options1 OUTPUT_VAR)
cmake_parse_arguments(_args "${options}" "${options1}" "" ${ARGN})
list(GET _args_UNPARSED_ARGUMENTS 0 _arg0)
# if provided, use _arg0 as the input file path
if(_arg0)
set(_source_file ${_arg0})
# otherwise, must determine source file from name, or vice versa
else()
get_filename_component(_name_ext "${_name}" EXT)
# if extension provided, _name is the source file
if(_name_ext)
set(_source_file ${_name})
get_filename_component(_name "${_source_file}" NAME_WE)
# otherwise, assume the source file is ${_name}.pyx
else()
set(_source_file ${_name}.pyx)
endif()
endif()
set(_embed_main FALSE)
if("C" IN_LIST languages)
set(_output_syntax "C")
elseif("CXX" IN_LIST languages)
set(_output_syntax "CXX")
else()
message(FATAL_ERROR "Either C or CXX must be enabled to use Cython")
endif()
if("${PYTHONLIBS_VERSION_STRING}" MATCHES "^2.")
set(_input_syntax "PY2")
else()
set(_input_syntax "PY3")
endif()
if(_args_EMBED_MAIN)
set(_embed_main TRUE)
endif()
if(_args_C)
set(_output_syntax "C")
endif()
if(_args_CXX)
set(_output_syntax "CXX")
endif()
if(_args_PY2)
set(_input_syntax "PY2")
endif()
if(_args_PY3)
set(_input_syntax "PY3")
endif()
set(embed_arg "")
if(_embed_main)
set(embed_arg "--embed")
endif()
set(cxx_arg "")
set(extension "c")
if(_output_syntax STREQUAL "CXX")
set(cxx_arg "--cplus")
set(extension "cxx")
endif()
set(py_version_arg "")
if(_input_syntax STREQUAL "PY2")
set(py_version_arg "-2")
elseif(_input_syntax STREQUAL "PY3")
set(py_version_arg "-3")
endif()
set(generated_file "${CMAKE_CURRENT_BINARY_DIR}/${_name}.${extension}")
set_source_files_properties(${generated_file} PROPERTIES GENERATED TRUE)
set(_output_var ${_name})
if(_args_OUTPUT_VAR)
set(_output_var ${_args_OUTPUT_VAR})
endif()
set(${_output_var} ${generated_file} PARENT_SCOPE)
file(RELATIVE_PATH generated_file_relative
${CMAKE_BINARY_DIR} ${generated_file})
set(comment "Generating ${_output_syntax} source ${generated_file_relative}")
set(cython_include_directories "")
set(pxd_dependencies "")
set(c_header_dependencies "")
# Get the include directories.
get_source_file_property(pyx_location ${_source_file} LOCATION)
get_filename_component(pyx_path ${pyx_location} PATH)
get_directory_property(cmake_include_directories
DIRECTORY ${pyx_path}
INCLUDE_DIRECTORIES)
list(APPEND cython_include_directories ${cmake_include_directories})
# Determine dependencies.
# Add the pxd file with the same basename as the given pyx file.
get_filename_component(pyx_file_basename ${_source_file} NAME_WE)
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 "${_source_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}")
# look for C headers
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()
# check for pxi dependencies
# Look for include statements.
set(include_dependencies "")
file(STRINGS "${pxd}" include_statements REGEX include)
foreach(statement ${include_statements})
string(REGEX REPLACE
"include[ ]+[\"]([^\"]+)[\"].*" "\\1"
module "${statement}")
list(APPEND include_dependencies ${module})
endforeach()
list(REMOVE_DUPLICATES module_dependencies)
list(REMOVE_DUPLICATES include_dependencies)
# Add modules 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
# Add includes to the files to check, if appropriate.
foreach(_include ${include_dependencies})
unset(pxi_location CACHE)
find_file(pxi_location ${_include}
PATHS "${pyx_path}" ${cmake_include_directories}
NO_DEFAULT_PATH)
if(pxi_location)
list(FIND pxds_checked ${pxi_location} pxd_idx)
if(${pxd_idx} LESS 0)
list(FIND pxds_to_check ${pxi_location} pxd_idx)
if(${pxd_idx} LESS 0)
list(APPEND pxds_to_check ${pxi_location})
list(APPEND pxd_dependencies ${pxi_location})
endif() # if it is not already going to be checked
endif() # if it has not already been checked
endif() # if include file can be found
endforeach() # for each include dependency discovered
endforeach() # for each include file to check
list(LENGTH pxds_to_check number_pxds_to_check)
endwhile()
# Set additional flags.
set(annotate_arg "")
if(CYTHON_ANNOTATE)
set(annotate_arg "--annotate")
endif()
set(no_docstrings_arg "")
if(CMAKE_BUILD_TYPE STREQUAL "Release" OR
CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
set(no_docstrings_arg "--no-docstrings")
endif()
set(cython_debug_arg "")
set(embed_pos_arg "")
set(line_directives_arg "")
if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR
CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
set(cython_debug_arg "--gdb")
set(embed_pos_arg "--embed-positions")
set(line_directives_arg "--line-directives")
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} "--include-dir" "${_include_dir}")
endforeach()
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} ${py_version_arg}
${embed_arg} ${annotate_arg} ${no_docstrings_arg}
${cython_debug_arg} ${embed_pos_arg}
${line_directives_arg} ${CYTHON_FLAGS} ${pyx_location}
--output-file ${generated_file}
DEPENDS ${_source_file}
${pxd_dependencies}
IMPLICIT_DEPENDS ${_output_syntax}
${c_header_dependencies}
COMMENT ${comment})
# NOTE(opadron): I thought about making a proper target, but after trying it
# out, I decided that it would be far too convenient to use the same name as
# the target for the extension module (e.g.: for single-file modules):
#
# ...
# add_cython_target(_module.pyx)
# add_library(_module ${_module})
# ...
#
# The above example would not be possible since the "_module" target name
# would already be taken by the cython target. Since I can't think of a
# reason why someone would need the custom target instead of just using the
# generated file directly, I decided to leave this commented out.
#
# add_custom_target(${_name} DEPENDS ${generated_file})
# Remove their visibility to the user.
set(corresponding_pxd_file "" CACHE INTERNAL "")
set(header_location "" CACHE INTERNAL "")
set(pxd_location "" CACHE INTERNAL "")
endfunction()
|