summaryrefslogtreecommitdiff
path: root/cmake/UseCython.cmake
blob: 7ff4a276bd802a4be691e04ecab44f4d6e132256 (plain)
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()

generated by cgit on debian on lair
contact matthew@masot.net with questions or feedback