1 # Define a function to create Cython modules.
3 # For more information on the Cython project, see http://cython.org/.
4 # "Cython is a language that makes writing C extensions for the Python language
5 # as easy as Python itself."
7 # This file defines a CMake function to build a Cython Python module.
8 # To use it, first include this file.
10 # include( UseCython )
12 # Then call cython_add_module to create a module.
14 # cython_add_module( <module_name> <src1> <src2> ... <srcN> )
16 # To create a standalone executable, the function
18 # cython_add_standalone_executable( <executable_name> [MAIN_MODULE src1] <src1> <src2> ... <srcN> )
20 # To avoid dependence on Python, set the PYTHON_LIBRARY cache variable to point
21 # to a static library. If a MAIN_MODULE source is specified,
22 # the "if __name__ == '__main__':" from that module is used as the C main() method
23 # for the executable. If MAIN_MODULE, the source with the same basename as
24 # <executable_name> is assumed to be the MAIN_MODULE.
26 # Where <module_name> is the name of the resulting Python module and
27 # <src1> <src2> ... are source files to be compiled into the module, e.g. *.pyx,
28 # *.py, *.c, *.cxx, etc. A CMake target is created with name <module_name>. This can
29 # be used for target_link_libraries(), etc.
31 # The sample paths set with the CMake include_directories() command will be used
32 # for include directories to search for *.pxd when running the Cython complire.
34 # Cache variables that effect the behavior include:
37 # CYTHON_NO_DOCSTRINGS
40 # Source file properties that effect the build process are
44 # If this is set of a *.pyx file with CMake set_source_files_properties()
45 # command, the file will be compiled as a C++ file.
47 # See also FindCython.cmake
49 # Copyright (C) 2018-2019 Intel Corporation
51 # Licensed under the Apache License, Version 2.0 (the "License");
52 # you may not use this file except in compliance with the License.
53 # You may obtain a copy of the License at
55 # http://www.apache.org/licenses/LICENSE-2.0
57 # Unless required by applicable law or agreed to in writing, software
58 # distributed under the License is distributed on an "AS IS" BASIS,
59 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
60 # See the License for the specific language governing permissions and
61 # limitations under the License.
63 # Following changes were done on top of the original file:
64 # added PRIVATE linking mode for target_link_libraries call at lines 298 and 336
66 #=============================================================================
67 # Copyright 2011 Kitware, Inc.
69 # Licensed under the Apache License, Version 2.0 (the "License");
70 # you may not use this file except in compliance with the License.
71 # You may obtain a copy of the License at
73 # http://www.apache.org/licenses/LICENSE-2.0
75 # Unless required by applicable law or agreed to in writing, software
76 # distributed under the License is distributed on an "AS IS" BASIS,
77 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
78 # See the License for the specific language governing permissions and
79 # limitations under the License.
80 #=============================================================================
82 # Configuration options.
83 set( CYTHON_ANNOTATE OFF
84 CACHE BOOL "Create an annotated .html file when compiling *.pyx." )
85 set( CYTHON_NO_DOCSTRINGS OFF
86 CACHE BOOL "Strip docstrings from the compiled module." )
87 set( CYTHON_FLAGS "" CACHE STRING
88 "Extra flags to the cython compiler." )
89 mark_as_advanced( CYTHON_ANNOTATE CYTHON_NO_DOCSTRINGS CYTHON_FLAGS )
91 find_package( Cython REQUIRED )
92 find_package( PythonLibs REQUIRED )
94 set( CYTHON_CXX_EXTENSION "cxx" )
95 set( CYTHON_C_EXTENSION "c" )
97 # Create a *.c or *.cxx file from a *.pyx file.
98 # Input the generated file basename. The generate file will put into the variable
99 # placed in the "generated_file" argument. Finally all the *.py and *.pyx files.
100 function( compile_pyx _name generated_file )
101 # Default to assuming all files are C.
103 set( extension ${CYTHON_C_EXTENSION} )
105 set( comment "Compiling Cython C source for ${_name}..." )
107 set( cython_include_directories "" )
108 set( pxd_dependencies "" )
109 set( pxi_dependencies "" )
110 set( c_header_dependencies "" )
111 set( pyx_locations "" )
113 foreach( pyx_file ${ARGN} )
114 get_filename_component( pyx_file_basename "${pyx_file}" NAME_WE )
116 # Determine if it is a C or C++ file.
117 get_source_file_property( property_is_cxx ${pyx_file} CYTHON_IS_CXX )
118 if( ${property_is_cxx} )
119 set( cxx_arg "--cplus" )
120 set( extension ${CYTHON_CXX_EXTENSION} )
121 set( pyx_lang "CXX" )
122 set( comment "Compiling Cython CXX source for ${_name}..." )
125 # Get the include directories.
126 get_source_file_property( pyx_location ${pyx_file} LOCATION )
127 get_filename_component( pyx_path ${pyx_location} PATH )
128 get_directory_property( cmake_include_directories DIRECTORY ${pyx_path} INCLUDE_DIRECTORIES )
129 list( APPEND cython_include_directories ${cmake_include_directories} )
130 list( APPEND pyx_locations "${pyx_location}" )
132 # Determine dependencies.
133 # Add the pxd file will the same name as the given pyx file.
134 unset( corresponding_pxd_file CACHE )
135 find_file( corresponding_pxd_file ${pyx_file_basename}.pxd
136 PATHS "${pyx_path}" ${cmake_include_directories}
138 if( corresponding_pxd_file )
139 list( APPEND pxd_dependencies "${corresponding_pxd_file}" )
142 # Look for included pxi files
143 file(STRINGS "${pyx_file}" include_statements REGEX "include +['\"]([^'\"]+).*")
144 foreach(statement ${include_statements})
145 string(REGEX REPLACE "include +['\"]([^'\"]+).*" "\\1" pxi_file "${statement}")
146 unset(pxi_location CACHE)
147 find_file(pxi_location ${pxi_file}
148 PATHS "${pyx_path}" ${cmake_include_directories} NO_DEFAULT_PATH)
150 list(APPEND pxi_dependencies ${pxi_location})
151 get_filename_component( found_pyi_file_basename "${pxi_file}" NAME_WE )
152 get_filename_component( found_pyi_path ${pxi_location} PATH )
153 unset( found_pyi_pxd_file CACHE )
154 find_file( found_pyi_pxd_file ${found_pyi_file_basename}.pxd
155 PATHS "${found_pyi_path}" ${cmake_include_directories} NO_DEFAULT_PATH )
156 if (found_pyi_pxd_file)
157 list( APPEND pxd_dependencies "${found_pyi_pxd_file}" )
160 endforeach() # for each include statement found
162 # pxd files to check for additional dependencies.
163 set( pxds_to_check "${pyx_file}" "${pxd_dependencies}" )
164 set( pxds_checked "" )
165 set( number_pxds_to_check 1 )
166 while( ${number_pxds_to_check} GREATER 0 )
167 foreach( pxd ${pxds_to_check} )
168 list( APPEND pxds_checked "${pxd}" )
169 list( REMOVE_ITEM pxds_to_check "${pxd}" )
171 # check for C header dependencies
172 file( STRINGS "${pxd}" extern_from_statements
173 REGEX "cdef[ ]+extern[ ]+from.*$" )
174 foreach( statement ${extern_from_statements} )
175 # Had trouble getting the quote in the regex
176 string( REGEX REPLACE "cdef[ ]+extern[ ]+from[ ]+[\"]([^\"]+)[\"].*" "\\1" header "${statement}" )
177 unset( header_location CACHE )
178 find_file( header_location ${header} PATHS ${cmake_include_directories} )
179 if( header_location )
180 list( FIND c_header_dependencies "${header_location}" header_idx )
181 if( ${header_idx} LESS 0 )
182 list( APPEND c_header_dependencies "${header_location}" )
187 # check for pxd dependencies
189 # Look for cimport statements.
190 set( module_dependencies "" )
191 file( STRINGS "${pxd}" cimport_statements REGEX cimport )
192 foreach( statement ${cimport_statements} )
193 if( ${statement} MATCHES from )
194 string( REGEX REPLACE "from[ ]+([^ ]+).*" "\\1" module "${statement}" )
196 string( REGEX REPLACE "cimport[ ]+([^ ]+).*" "\\1" module "${statement}" )
198 list( APPEND module_dependencies ${module} )
200 list( REMOVE_DUPLICATES module_dependencies )
201 # Add the module to the files to check, if appropriate.
202 foreach( module ${module_dependencies} )
203 unset( pxd_location CACHE )
204 find_file( pxd_location ${module}.pxd
205 PATHS "${pyx_path}" ${cmake_include_directories} NO_DEFAULT_PATH )
207 list( FIND pxds_checked ${pxd_location} pxd_idx )
208 if( ${pxd_idx} LESS 0 )
209 list( FIND pxds_to_check ${pxd_location} pxd_idx )
210 if( ${pxd_idx} LESS 0 )
211 list( APPEND pxds_to_check ${pxd_location} )
212 list( APPEND pxd_dependencies ${pxd_location} )
213 endif() # if it is not already going to be checked
214 endif() # if it has not already been checked
215 endif() # if pxd file can be found
216 endforeach() # for each module dependency discovered
217 endforeach() # for each pxd file to check
218 list( LENGTH pxds_to_check number_pxds_to_check )
223 endforeach() # pyx_file
225 # Set additional flags.
226 if( CYTHON_ANNOTATE )
227 set( annotate_arg "--annotate" )
230 if( CYTHON_NO_DOCSTRINGS )
231 set( no_docstrings_arg "--no-docstrings" )
234 if( "${CMAKE_BUILD_TYPE}" STREQUAL "Debug" OR
235 "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo" )
236 set( cython_debug_arg "--gdb" )
239 if( "${PYTHONLIBS_VERSION_STRING}" MATCHES "^2." )
240 set( version_arg "-2" )
241 elseif( "${PYTHONLIBS_VERSION_STRING}" MATCHES "^3." )
242 set( version_arg "-3" )
247 # Include directory arguments.
248 list( REMOVE_DUPLICATES cython_include_directories )
249 set( include_directory_arg "" )
250 foreach( _include_dir ${cython_include_directories} )
251 set( include_directory_arg ${include_directory_arg} "-I" "${_include_dir}" )
254 # Determining generated file name.
255 set( _generated_file "${CMAKE_CURRENT_BINARY_DIR}/${_name}.${extension}" )
256 set_source_files_properties( ${_generated_file} PROPERTIES GENERATED TRUE )
257 set( ${generated_file} ${_generated_file} PARENT_SCOPE )
259 list( REMOVE_DUPLICATES pxd_dependencies )
260 list( REMOVE_DUPLICATES c_header_dependencies )
262 # Add the command to run the compiler.
263 add_custom_command( OUTPUT ${_generated_file}
264 COMMAND ${CYTHON_EXECUTABLE}
265 ARGS ${cxx_arg} ${include_directory_arg} ${version_arg}
266 ${annotate_arg} ${no_docstrings_arg} ${cython_debug_arg} ${CYTHON_FLAGS}
267 --output-file ${_generated_file} ${pyx_locations}
268 DEPENDS ${pyx_locations} ${pxd_dependencies} ${pxi_dependencies}
269 IMPLICIT_DEPENDS ${pyx_lang} ${c_header_dependencies}
273 # Remove their visibility to the user.
274 set( corresponding_pxd_file "" CACHE INTERNAL "" )
275 set( header_location "" CACHE INTERNAL "" )
276 set( pxd_location "" CACHE INTERNAL "" )
279 # cython_add_module( <name> src1 src2 ... srcN )
280 # Build the Cython Python module.
281 function( cython_add_module _name )
282 set( pyx_module_sources "" )
283 set( other_module_sources "" )
284 foreach( _file ${ARGN} )
285 if( ${_file} MATCHES ".*\\.py[x]?$" )
286 list( APPEND pyx_module_sources ${_file} )
288 list( APPEND other_module_sources ${_file} )
291 compile_pyx( ${_name} generated_file ${pyx_module_sources} )
292 include_directories( ${PYTHON_INCLUDE_DIRS} )
293 python_add_module( ${_name} ${generated_file} ${other_module_sources} )
295 set_target_properties( ${_name} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup" )
297 target_link_libraries( ${_name} PRIVATE ${PYTHON_LIBRARIES} )
301 include( CMakeParseArguments )
302 # cython_add_standalone_executable( _name [MAIN_MODULE src3.py] src1 src2 ... srcN )
303 # Creates a standalone executable the given sources.
304 function( cython_add_standalone_executable _name )
305 set( pyx_module_sources "" )
306 set( other_module_sources "" )
307 set( main_module "" )
308 cmake_parse_arguments( cython_arguments "" "MAIN_MODULE" "" ${ARGN} )
309 include_directories( ${PYTHON_INCLUDE_DIRS} )
310 foreach( _file ${cython_arguments_UNPARSED_ARGUMENTS} )
311 if( ${_file} MATCHES ".*\\.py[x]?$" )
312 get_filename_component( _file_we ${_file} NAME_WE )
313 if( "${_file_we}" STREQUAL "${_name}" )
314 set( main_module "${_file}" )
315 elseif( NOT "${_file}" STREQUAL "${cython_arguments_MAIN_MODULE}" )
316 set( PYTHON_MODULE_${_file_we}_static_BUILD_SHARED OFF )
317 compile_pyx( "${_file_we}_static" generated_file "${_file}" )
318 list( APPEND pyx_module_sources "${generated_file}" )
321 list( APPEND other_module_sources ${_file} )
325 if( cython_arguments_MAIN_MODULE )
326 set( main_module ${cython_arguments_MAIN_MODULE} )
328 if( NOT main_module )
329 message( FATAL_ERROR "main module not found." )
331 get_filename_component( main_module_we "${main_module}" NAME_WE )
332 set( CYTHON_FLAGS ${CYTHON_FLAGS} --embed )
333 compile_pyx( "${main_module_we}_static" generated_file ${main_module} )
334 add_executable( ${_name} ${generated_file} ${pyx_module_sources} ${other_module_sources} )
335 target_link_libraries( ${_name} PRIVATE ${PYTHON_LIBRARIES} ${pyx_module_libs} )