Add Python bindings using Cython -- see below for more details (#2879)
[cvc5.git] / cmake / UseCython.cmake
1 #.rst
2 # Define a function to create Cython modules.
3 #
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."
7 #
8 # This file defines a CMake function to build a Cython Python module.
9 # To use it, first include this file.
10 #
11 # include(UseCython)
12 #
13 # The following functions are defined:
14 #
15 # add_cython_target(<Name> [<CythonInput>]
16 # [EMBED_MAIN]
17 # [C | CXX]
18 # [PY2 | PY3]
19 # [OUTPUT_VAR <OutputVar>])
20 #
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.
26 #
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
31 # be ``<Name>.pyx``.
32 #
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.
37 #
38 # Options:
39 #
40 # ``EMBED_MAIN``
41 # Embed a main() function in the generated output (for stand-alone
42 # applications that initialize their own Python runtime).
43 #
44 # ``C | CXX``
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.
48 #
49 # ``PY2 | PY3``
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
53 # used.
54 #
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
58 # variable name.
59 #
60 # Defined variables:
61 #
62 # ``<OutputVar>``
63 # The path of the generated source file.
64 #
65 #
66 # Example usage:
67 #
68 # .. code-block:: cmake
69 #
70 # find_package(Cython)
71 #
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)
75 #
76 # add_library(cy_code MODULE ${cy_code})
77 # target_link_libraries(cy_code ...)
78 #
79 # Cache variables that effect the behavior include:
80 #
81 # ``CYTHON_ANNOTATE``
82 # whether to create an annotated .html file when compiling
83 #
84 # ``CYTHON_FLAGS``
85 # additional flags to pass to the Cython compiler
86 #
87 # See also FindCython.cmake
88 #
89 #=============================================================================
90 # Copyright 2011 Kitware, Inc.
91 #
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
95 #
96 # http://www.apache.org/licenses/LICENSE-2.0
97 #
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 #=============================================================================
104
105 # Configuration options.
106 set(CYTHON_ANNOTATE OFF
107 CACHE BOOL "Create an annotated .html file when compiling *.pyx.")
108
109 set(CYTHON_FLAGS "" CACHE STRING
110 "Extra flags to the cython compiler.")
111 mark_as_advanced(CYTHON_ANNOTATE CYTHON_FLAGS)
112
113 find_package(PythonLibs REQUIRED)
114
115 set(CYTHON_CXX_EXTENSION "cxx")
116 set(CYTHON_C_EXTENSION "c")
117
118 get_property(languages GLOBAL PROPERTY ENABLED_LANGUAGES)
119
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})
124
125 list(GET _args_UNPARSED_ARGUMENTS 0 _arg0)
126
127 # if provided, use _arg0 as the input file path
128 if(_arg0)
129 set(_source_file ${_arg0})
130
131 # otherwise, must determine source file from name, or vice versa
132 else()
133 get_filename_component(_name_ext "${_name}" EXT)
134
135 # if extension provided, _name is the source file
136 if(_name_ext)
137 set(_source_file ${_name})
138 get_filename_component(_name "${_source_file}" NAME_WE)
139
140 # otherwise, assume the source file is ${_name}.pyx
141 else()
142 set(_source_file ${_name}.pyx)
143 endif()
144 endif()
145
146 set(_embed_main FALSE)
147
148 if("C" IN_LIST languages)
149 set(_output_syntax "C")
150 elseif("CXX" IN_LIST languages)
151 set(_output_syntax "CXX")
152 else()
153 message(FATAL_ERROR "Either C or CXX must be enabled to use Cython")
154 endif()
155
156 if("${PYTHONLIBS_VERSION_STRING}" MATCHES "^2.")
157 set(_input_syntax "PY2")
158 else()
159 set(_input_syntax "PY3")
160 endif()
161
162 if(_args_EMBED_MAIN)
163 set(_embed_main TRUE)
164 endif()
165
166 if(_args_C)
167 set(_output_syntax "C")
168 endif()
169
170 if(_args_CXX)
171 set(_output_syntax "CXX")
172 endif()
173
174 if(_args_PY2)
175 set(_input_syntax "PY2")
176 endif()
177
178 if(_args_PY3)
179 set(_input_syntax "PY3")
180 endif()
181
182 set(embed_arg "")
183 if(_embed_main)
184 set(embed_arg "--embed")
185 endif()
186
187 set(cxx_arg "")
188 set(extension "c")
189 if(_output_syntax STREQUAL "CXX")
190 set(cxx_arg "--cplus")
191 set(extension "cxx")
192 endif()
193
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")
199 endif()
200
201 set(generated_file "${CMAKE_CURRENT_BINARY_DIR}/${_name}.${extension}")
202 set_source_files_properties(${generated_file} PROPERTIES GENERATED TRUE)
203
204 set(_output_var ${_name})
205 if(_args_OUTPUT_VAR)
206 set(_output_var ${_args_OUTPUT_VAR})
207 endif()
208 set(${_output_var} ${generated_file} PARENT_SCOPE)
209
210 file(RELATIVE_PATH generated_file_relative
211 ${CMAKE_BINARY_DIR} ${generated_file})
212
213 set(comment "Generating ${_output_syntax} source ${generated_file_relative}")
214 set(cython_include_directories "")
215 set(pxd_dependencies "")
216 set(c_header_dependencies "")
217
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}
223 INCLUDE_DIRECTORIES)
224 list(APPEND cython_include_directories ${cmake_include_directories})
225
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}
232 NO_DEFAULT_PATH)
233 if(corresponding_pxd_file)
234 list(APPEND pxd_dependencies "${corresponding_pxd_file}")
235 endif()
236
237 # pxd files to check for additional dependencies
238 set(pxds_to_check "${_source_file}" "${pxd_dependencies}")
239 set(pxds_checked "")
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}")
245
246 # look for C headers
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
251 string(REGEX REPLACE
252 "cdef[ ]+extern[ ]+from[ ]+[\"]([^\"]+)[\"].*" "\\1"
253 header "${statement}")
254 unset(header_location CACHE)
255 find_file(header_location ${header} PATHS ${cmake_include_directories})
256 if(header_location)
257 list(FIND c_header_dependencies "${header_location}" header_idx)
258 if(${header_idx} LESS 0)
259 list(APPEND c_header_dependencies "${header_location}")
260 endif()
261 endif()
262 endforeach()
263
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)
270 string(REGEX REPLACE
271 "from[ ]+([^ ]+).*" "\\1"
272 module "${statement}")
273 else()
274 string(REGEX REPLACE
275 "cimport[ ]+([^ ]+).*" "\\1"
276 module "${statement}")
277 endif()
278 list(APPEND module_dependencies ${module})
279 endforeach()
280
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})
286 string(REGEX REPLACE
287 "include[ ]+[\"]([^\"]+)[\"].*" "\\1"
288 module "${statement}")
289 list(APPEND include_dependencies ${module})
290 endforeach()
291
292 list(REMOVE_DUPLICATES module_dependencies)
293 list(REMOVE_DUPLICATES include_dependencies)
294
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}
300 NO_DEFAULT_PATH)
301 if(pxd_location)
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
312
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}
318 NO_DEFAULT_PATH)
319 if(pxi_location)
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
331
332 list(LENGTH pxds_to_check number_pxds_to_check)
333 endwhile()
334
335 # Set additional flags.
336 set(annotate_arg "")
337 if(CYTHON_ANNOTATE)
338 set(annotate_arg "--annotate")
339 endif()
340
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")
345 endif()
346
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")
355 endif()
356
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}")
363 endforeach()
364
365 list(REMOVE_DUPLICATES pxd_dependencies)
366 list(REMOVE_DUPLICATES c_header_dependencies)
367
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}
377 ${pxd_dependencies}
378 IMPLICIT_DEPENDS ${_output_syntax}
379 ${c_header_dependencies}
380 COMMENT ${comment})
381
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):
385 #
386 # ...
387 # add_cython_target(_module.pyx)
388 # add_library(_module ${_module})
389 # ...
390 #
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.
395 #
396 # add_custom_target(${_name} DEPENDS ${generated_file})
397
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 "")
402 endfunction()
403