2 # Define a function to create Cython modules.
4 # For more information on the Cython project, see http://cython.org/.
5 # "Cython is a language that makes writing C extensions for the Python language
6 # as easy as Python itself."
8 # This file defines a CMake function to build a Cython Python module.
9 # To use it, first include this file.
13 # The following functions are defined:
15 # add_cython_target(<Name> [<CythonInput>]
19 # [OUTPUT_VAR <OutputVar>])
21 # Create a custom rule to generate the source code for a Python extension module
22 # using cython. ``<Name>`` is the name of the new target, and ``<CythonInput>``
23 # is the path to a cython source file. Note that, despite the name, no new
24 # targets are created by this function. Instead, see ``OUTPUT_VAR`` for
25 # retrieving the path to the generated source for subsequent targets.
27 # If only ``<Name>`` is provided, and it ends in the ".pyx" extension, then it
28 # is assumed to be the ``<CythonInput>``. The name of the input without the
29 # extension is used as the target name. If only ``<Name>`` is provided, and it
30 # does not end in the ".pyx" extension, then the ``<CythonInput>`` is assumed to
33 # The Cython include search path is amended with any entries found in the
34 # ``INCLUDE_DIRECTORIES`` property of the directory containing the
35 # ``<CythonInput>`` file. Use ``iunclude_directories`` to add to the Cython
36 # include search path.
41 # Embed a main() function in the generated output (for stand-alone
42 # applications that initialize their own Python runtime).
45 # Force the generation of either a C or C++ file. By default, a C file is
46 # generated, unless the C language is not enabled for the project; in this
47 # case, a C++ file is generated by default.
50 # Force compilation using either Python-2 or Python-3 syntax and code
51 # semantics. By default, Python-2 syntax and semantics are used if the major
52 # version of Python found is 2. Otherwise, Python-3 syntax and sematics are
55 # ``OUTPUT_VAR <OutputVar>``
56 # Set the variable ``<OutputVar>`` in the parent scope to the path to the
57 # generated source file. By default, ``<Name>`` is used as the output
63 # The path of the generated source file.
68 # .. code-block:: cmake
70 # find_package(Cython)
72 # # Note: In this case, either one of these arguments may be omitted; their
73 # # value would have been inferred from that of the other.
74 # add_cython_target(cy_code cy_code.pyx)
76 # add_library(cy_code MODULE ${cy_code})
77 # target_link_libraries(cy_code ...)
79 # Cache variables that effect the behavior include:
82 # whether to create an annotated .html file when compiling
85 # additional flags to pass to the Cython compiler
87 # See also FindCython.cmake
89 #=============================================================================
90 # Copyright 2011 Kitware, Inc.
92 # Licensed under the Apache License, Version 2.0 (the "License");
93 # you may not use this file except in compliance with the License.
94 # You may obtain a copy of the License at
96 # http://www.apache.org/licenses/LICENSE-2.0
98 # Unless required by applicable law or agreed to in writing, software
99 # distributed under the License is distributed on an "AS IS" BASIS,
100 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
101 # See the License for the specific language governing permissions and
102 # limitations under the License.
103 #=============================================================================
105 # Configuration options.
106 set(CYTHON_ANNOTATE OFF
107 CACHE BOOL "Create an annotated .html file when compiling *.pyx.")
109 set(CYTHON_FLAGS "" CACHE STRING
110 "Extra flags to the cython compiler.")
111 mark_as_advanced(CYTHON_ANNOTATE CYTHON_FLAGS)
113 find_package(PythonLibs REQUIRED)
115 set(CYTHON_CXX_EXTENSION "cxx")
116 set(CYTHON_C_EXTENSION "c")
118 get_property(languages GLOBAL PROPERTY ENABLED_LANGUAGES)
120 function(add_cython_target _name)
121 set(options EMBED_MAIN C CXX PY2 PY3)
122 set(options1 OUTPUT_VAR)
123 cmake_parse_arguments(_args "${options}" "${options1}" "" ${ARGN})
125 list(GET _args_UNPARSED_ARGUMENTS 0 _arg0)
127 # if provided, use _arg0 as the input file path
129 set(_source_file ${_arg0})
131 # otherwise, must determine source file from name, or vice versa
133 get_filename_component(_name_ext "${_name}" EXT)
135 # if extension provided, _name is the source file
137 set(_source_file ${_name})
138 get_filename_component(_name "${_source_file}" NAME_WE)
140 # otherwise, assume the source file is ${_name}.pyx
142 set(_source_file ${_name}.pyx)
146 set(_embed_main FALSE)
148 if("C" IN_LIST languages)
149 set(_output_syntax "C")
150 elseif("CXX" IN_LIST languages)
151 set(_output_syntax "CXX")
153 message(FATAL_ERROR "Either C or CXX must be enabled to use Cython")
156 if("${PYTHONLIBS_VERSION_STRING}" MATCHES "^2.")
157 set(_input_syntax "PY2")
159 set(_input_syntax "PY3")
163 set(_embed_main TRUE)
167 set(_output_syntax "C")
171 set(_output_syntax "CXX")
175 set(_input_syntax "PY2")
179 set(_input_syntax "PY3")
184 set(embed_arg "--embed")
189 if(_output_syntax STREQUAL "CXX")
190 set(cxx_arg "--cplus")
194 set(py_version_arg "")
195 if(_input_syntax STREQUAL "PY2")
196 set(py_version_arg "-2")
197 elseif(_input_syntax STREQUAL "PY3")
198 set(py_version_arg "-3")
201 set(generated_file "${CMAKE_CURRENT_BINARY_DIR}/${_name}.${extension}")
202 set_source_files_properties(${generated_file} PROPERTIES GENERATED TRUE)
204 set(_output_var ${_name})
206 set(_output_var ${_args_OUTPUT_VAR})
208 set(${_output_var} ${generated_file} PARENT_SCOPE)
210 file(RELATIVE_PATH generated_file_relative
211 ${CMAKE_BINARY_DIR} ${generated_file})
213 set(comment "Generating ${_output_syntax} source ${generated_file_relative}")
214 set(cython_include_directories "")
215 set(pxd_dependencies "")
216 set(c_header_dependencies "")
218 # Get the include directories.
219 get_source_file_property(pyx_location ${_source_file} LOCATION)
220 get_filename_component(pyx_path ${pyx_location} PATH)
221 get_directory_property(cmake_include_directories
222 DIRECTORY ${pyx_path}
224 list(APPEND cython_include_directories ${cmake_include_directories})
226 # Determine dependencies.
227 # Add the pxd file with the same basename as the given pyx file.
228 get_filename_component(pyx_file_basename ${_source_file} NAME_WE)
229 unset(corresponding_pxd_file CACHE)
230 find_file(corresponding_pxd_file ${pyx_file_basename}.pxd
231 PATHS "${pyx_path}" ${cmake_include_directories}
233 if(corresponding_pxd_file)
234 list(APPEND pxd_dependencies "${corresponding_pxd_file}")
237 # pxd files to check for additional dependencies
238 set(pxds_to_check "${_source_file}" "${pxd_dependencies}")
240 set(number_pxds_to_check 1)
241 while(number_pxds_to_check GREATER 0)
242 foreach(pxd ${pxds_to_check})
243 list(APPEND pxds_checked "${pxd}")
244 list(REMOVE_ITEM pxds_to_check "${pxd}")
247 file(STRINGS "${pxd}" extern_from_statements
248 REGEX "cdef[ ]+extern[ ]+from.*$")
249 foreach(statement ${extern_from_statements})
250 # Had trouble getting the quote in the regex
252 "cdef[ ]+extern[ ]+from[ ]+[\"]([^\"]+)[\"].*" "\\1"
253 header "${statement}")
254 unset(header_location CACHE)
255 find_file(header_location ${header} PATHS ${cmake_include_directories})
257 list(FIND c_header_dependencies "${header_location}" header_idx)
258 if(${header_idx} LESS 0)
259 list(APPEND c_header_dependencies "${header_location}")
264 # check for pxd dependencies
265 # Look for cimport statements.
266 set(module_dependencies "")
267 file(STRINGS "${pxd}" cimport_statements REGEX cimport)
268 foreach(statement ${cimport_statements})
269 if(${statement} MATCHES from)
271 "from[ ]+([^ ]+).*" "\\1"
272 module "${statement}")
275 "cimport[ ]+([^ ]+).*" "\\1"
276 module "${statement}")
278 list(APPEND module_dependencies ${module})
281 # check for pxi dependencies
282 # Look for include statements.
283 set(include_dependencies "")
284 file(STRINGS "${pxd}" include_statements REGEX include)
285 foreach(statement ${include_statements})
287 "include[ ]+[\"]([^\"]+)[\"].*" "\\1"
288 module "${statement}")
289 list(APPEND include_dependencies ${module})
292 list(REMOVE_DUPLICATES module_dependencies)
293 list(REMOVE_DUPLICATES include_dependencies)
295 # Add modules to the files to check, if appropriate.
296 foreach(module ${module_dependencies})
297 unset(pxd_location CACHE)
298 find_file(pxd_location ${module}.pxd
299 PATHS "${pyx_path}" ${cmake_include_directories}
302 list(FIND pxds_checked ${pxd_location} pxd_idx)
303 if(${pxd_idx} LESS 0)
304 list(FIND pxds_to_check ${pxd_location} pxd_idx)
305 if(${pxd_idx} LESS 0)
306 list(APPEND pxds_to_check ${pxd_location})
307 list(APPEND pxd_dependencies ${pxd_location})
308 endif() # if it is not already going to be checked
309 endif() # if it has not already been checked
310 endif() # if pxd file can be found
311 endforeach() # for each module dependency discovered
313 # Add includes to the files to check, if appropriate.
314 foreach(_include ${include_dependencies})
315 unset(pxi_location CACHE)
316 find_file(pxi_location ${_include}
317 PATHS "${pyx_path}" ${cmake_include_directories}
320 list(FIND pxds_checked ${pxi_location} pxd_idx)
321 if(${pxd_idx} LESS 0)
322 list(FIND pxds_to_check ${pxi_location} pxd_idx)
323 if(${pxd_idx} LESS 0)
324 list(APPEND pxds_to_check ${pxi_location})
325 list(APPEND pxd_dependencies ${pxi_location})
326 endif() # if it is not already going to be checked
327 endif() # if it has not already been checked
328 endif() # if include file can be found
329 endforeach() # for each include dependency discovered
330 endforeach() # for each include file to check
332 list(LENGTH pxds_to_check number_pxds_to_check)
335 # Set additional flags.
338 set(annotate_arg "--annotate")
341 set(no_docstrings_arg "")
342 if(CMAKE_BUILD_TYPE STREQUAL "Release" OR
343 CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
344 set(no_docstrings_arg "--no-docstrings")
347 set(cython_debug_arg "")
348 set(embed_pos_arg "")
349 set(line_directives_arg "")
350 if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR
351 CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
352 set(cython_debug_arg "--gdb")
353 set(embed_pos_arg "--embed-positions")
354 set(line_directives_arg "--line-directives")
357 # Include directory arguments.
358 list(REMOVE_DUPLICATES cython_include_directories)
359 set(include_directory_arg "")
360 foreach(_include_dir ${cython_include_directories})
361 set(include_directory_arg
362 ${include_directory_arg} "--include-dir" "${_include_dir}")
365 list(REMOVE_DUPLICATES pxd_dependencies)
366 list(REMOVE_DUPLICATES c_header_dependencies)
368 # Add the command to run the compiler.
369 add_custom_command(OUTPUT ${generated_file}
370 COMMAND ${CYTHON_EXECUTABLE}
371 ARGS ${cxx_arg} ${include_directory_arg} ${py_version_arg}
372 ${embed_arg} ${annotate_arg} ${no_docstrings_arg}
373 ${cython_debug_arg} ${embed_pos_arg}
374 ${line_directives_arg} ${CYTHON_FLAGS} ${pyx_location}
375 --output-file ${generated_file}
376 DEPENDS ${_source_file}
378 IMPLICIT_DEPENDS ${_output_syntax}
379 ${c_header_dependencies}
382 # NOTE(opadron): I thought about making a proper target, but after trying it
383 # out, I decided that it would be far too convenient to use the same name as
384 # the target for the extension module (e.g.: for single-file modules):
387 # add_cython_target(_module.pyx)
388 # add_library(_module ${_module})
391 # The above example would not be possible since the "_module" target name
392 # would already be taken by the cython target. Since I can't think of a
393 # reason why someone would need the custom target instead of just using the
394 # generated file directly, I decided to leave this commented out.
396 # add_custom_target(${_name} DEPENDS ${generated_file})
398 # Remove their visibility to the user.
399 set(corresponding_pxd_file "" CACHE INTERNAL "")
400 set(header_location "" CACHE INTERNAL "")
401 set(pxd_location "" CACHE INTERNAL "")