summaryrefslogtreecommitdiffstats
path: root/CMakeScripts/UseCython.cmake
blob: 38c31551d4dfe50e297f4e2806747f28c1c42357 (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
# 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 )
#
# Then call cython_add_module to create a module.
#
#   cython_add_module( <module_name> <src1> <src2> ... <srcN> )
#
# To create a standalone executable, the function
#
#   cython_add_standalone_executable( <executable_name> [MAIN_MODULE src1] <src1> <src2> ... <srcN> )
#
# To avoid dependence on Python, set the PYTHON_LIBRARY cache variable to point
# to a static library.  If a MAIN_MODULE source is specified, 
# the "if __name__ == '__main__':" from that module is used as the C main() method
# for the executable.  If MAIN_MODULE, the source with the same basename as
# <executable_name> is assumed to be the MAIN_MODULE.
#
# Where <module_name> is the name of the resulting Python module and
# <src1> <src2> ... are source files to be compiled into the module, e.g. *.pyx,
# *.py, *.c, *.cxx, etc.  A CMake target is created with name <module_name>.  This can
# be used for target_link_libraries(), etc.
#
# The sample paths set with the CMake include_directories() command will be used
# for include directories to search for *.pxd when running the Cython complire.
#
# Cache variables that effect the behavior include:
#
#  CYTHON_ANNOTATE
#  CYTHON_NO_DOCSTRINGS
#  CYTHON_FLAGS
#
# Source file properties that effect the build process are
#
#  CYTHON_IS_CXX
#
# If this is set of a *.pyx file with CMake set_source_files_properties()
# command, the file will be compiled as a C++ file.
#
# 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.

#TODO - rewrite to use ALLCAPS?
set( CYTHON_ANNOTATE OFF
  CACHE BOOL "Create an annotated .html file when compiling *.pyx." )
set( CYTHON_NO_DOCSTRINGS OFF
  CACHE BOOL "Strip docstrings from the compiled module." )
set( CYTHON_FLAGS "" CACHE STRING
  "Extra flags to the cython compiler." )
mark_as_advanced( CYTHON_ANNOTATE CYTHON_NO_DOCSTRINGS CYTHON_FLAGS )

find_package( Cython REQUIRED )
find_package( PythonLibs REQUIRED )

set( CYTHON_CXX_EXTENSION "cxx" )
set( CYTHON_C_EXTENSION "c" )

# Create a *.c or *.cxx file from a *.pyx file.
# Input the generated file basename.  The generate file will put into the variable
# placed in the "generated_file" argument. Finally all the *.py and *.pyx files.
function( compile_pyx _name generated_file )
  # Default to assuming all files are C.
  set( cxx_arg "" )
  set( extension ${CYTHON_C_EXTENSION} )
  set( pyx_lang "C" )
  set( comment "Compiling Cython C source for ${_name}..." )

  set( cython_include_directories "" )
  set( pxd_dependencies "" )
  set( c_header_dependencies "" )
  set( pyx_locations "" )

  foreach( pyx_file ${ARGN} )
    get_filename_component( pyx_file_basename "${pyx_file}" NAME_WE )

    # Determine if it is a C or C++ file.
    get_source_file_property( property_is_cxx ${pyx_file} CYTHON_IS_CXX )
    if( ${property_is_cxx} )
      set( cxx_arg "--cplus" )
      set( extension ${CYTHON_CXX_EXTENSION} )
      set( pyx_lang "CXX" )
      set( comment "Compiling Cython CXX source for ${_name}..." )
    endif()

    # Get the include directories.
    get_source_file_property( pyx_location ${pyx_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} )
    list( APPEND pyx_locations "${pyx_location}" )

    # Determine dependencies.
    # Add the pxd file will the same name as the given pyx file.
    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 "${pyx_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}" )

        # check for C header dependencies
        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()

        set( module_dependencies "" )

        # Look for cimport and include statements.
        file( STRINGS "${pxd}" cimport_statements REGEX "(cimport|include)" )
        foreach( statement ${cimport_statements} )
          if( ${statement} MATCHES from )
            string( REGEX REPLACE "from[ ]+([^ ]+).*" "\\1.pxd" module "${statement}" )
          elseif( ${statement} MATCHES include )
            string( REGEX REPLACE "include[ ]+[\"]([^\"]+)[\"].*" "\\1" module "${statement}" )
          else()
            string( REGEX REPLACE "cimport[ ]+([^ ]+).*" "\\1.pxd" module "${statement}" )
          endif()
          list( APPEND module_dependencies ${module} )
        endforeach()
        list( REMOVE_DUPLICATES module_dependencies )
        # Add the module to the files to check, if appropriate.
        foreach( module ${module_dependencies} )
          unset( pxd_location CACHE )
          find_file( pxd_location ${module}
            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
          else()
            message("${module} ignored")
          endif() # if pxd file can be found
        endforeach() # for each module dependency discovered
      endforeach() # for each pxd file to check
      list( LENGTH pxds_to_check number_pxds_to_check )
    endwhile()
  endforeach() # pyx_file

  # Set additional flags.
  if( CYTHON_ANNOTATE )
    set( annotate_arg "--annotate" )
  endif()

  if( CYTHON_NO_DOCSTRINGS )
    set( no_docstrings_arg "--no-docstrings" )
  endif()

  if( "${CMAKE_BUILD_TYPE}" STREQUAL "Debug" OR
        "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo" )
      set( cython_debug_arg "--gdb" )
  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} "-I" "${_include_dir}" )
  endforeach()

  # Determining generated file name.
  set( _generated_file "${_name}.${extension}" )
  set_source_files_properties( ${_generated_file} PROPERTIES GENERATED TRUE )
  set( ${generated_file} ${_generated_file} PARENT_SCOPE )

  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}
    ${annotate_arg} ${no_docstrings_arg} ${cython_debug_arg} ${CYTHON_FLAGS} 
    --output-file  ${_generated_file} ${pyx_locations}
    DEPENDS ${pyx_locations} ${pxd_dependencies}
    IMPLICIT_DEPENDS ${pyx_lang} ${c_header_dependencies}
    COMMENT ${comment}
    )

  # Remove their visibility to the user.
  set( corresponding_pxd_file "" CACHE INTERNAL "" )
  set( header_location "" CACHE INTERNAL "" )
  set( pxd_location "" CACHE INTERNAL "" )
endfunction()

# cython_add_module( <name> src1 src2 ... srcN )
# Build the Cython Python module.
function( cython_add_module _name )
  set( pyx_module_sources "" )
  set( other_module_sources "" )
  foreach( _file ${ARGN} )
    if( ${_file} MATCHES ".*\\.py[x]?$" )
      list( APPEND pyx_module_sources ${_file} )
    else()
      list( APPEND other_module_sources ${_file} )
    endif()
  endforeach()
  compile_pyx( ${_name} generated_file ${pyx_module_sources} )
  include_directories( ${PYTHON_INCLUDE_DIRS} )
  python_add_module( ${_name} ${generated_file} ${other_module_sources} )
  target_link_libraries( ${_name} ${PYTHON_LIBRARIES} )
endfunction()

include( CMakeParseArguments )
# cython_add_standalone_executable( _name [MAIN_MODULE src3.py] src1 src2 ... srcN )
# Creates a standalone executable the given sources.
function( cython_add_standalone_executable _name )
  set( pyx_module_sources "" )
  set( other_module_sources "" )
  set( main_module "" )
  cmake_parse_arguments( cython_arguments "" "MAIN_MODULE" "" ${ARGN} )
  include_directories( ${PYTHON_INCLUDE_DIRS} )
  foreach( _file ${cython_arguments_UNPARSED_ARGUMENTS} )
    if( ${_file} MATCHES ".*\\.py[x]?$" )
      get_filename_component( _file_we ${_file} NAME_WE )
      if( "${_file_we}" STREQUAL "${_name}" )
        set( main_module "${_file}" )
      elseif( NOT "${_file}" STREQUAL "${cython_arguments_MAIN_MODULE}" )
        set( PYTHON_MODULE_${_file_we}_static_BUILD_SHARED OFF )
        compile_pyx( "${_file_we}_static" generated_file "${_file}" )
        list( APPEND pyx_module_sources "${generated_file}" )
      endif()
    else()
      list( APPEND other_module_sources ${_file} )
    endif()
  endforeach()

  if( cython_arguments_MAIN_MODULE )
    set( main_module ${cython_arguments_MAIN_MODULE} )
  endif()
  if( NOT main_module )
    message( FATAL_ERROR "main module not found." )
  endif()
  get_filename_component( main_module_we "${main_module}" NAME_WE )
  set( CYTHON_FLAGS ${CYTHON_FLAGS} --embed )
  compile_pyx( "${main_module_we}_static" generated_file ${main_module} )
  add_executable( ${_name} ${generated_file} ${pyx_module_sources} ${other_module_sources} )
  target_link_libraries( ${_name} ${PYTHON_LIBRARIES} ${pyx_module_libs} )
endfunction()