Publishing 2019 R1 content
[platform/upstream/dldt.git] / inference-engine / ie_bridges / python / cmake / UseCython.cmake
1 # Define a function to create Cython modules.
2 #
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."
6 #
7 # This file defines a CMake function to build a Cython Python module.
8 # To use it, first include this file.
9 #
10 #   include( UseCython )
11 #
12 # Then call cython_add_module to create a module.
13 #
14 #   cython_add_module( <module_name> <src1> <src2> ... <srcN> )
15 #
16 # To create a standalone executable, the function
17 #
18 #   cython_add_standalone_executable( <executable_name> [MAIN_MODULE src1] <src1> <src2> ... <srcN> )
19 #
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.
25 #
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.
30 #
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.
33 #
34 # Cache variables that effect the behavior include:
35 #
36 #  CYTHON_ANNOTATE
37 #  CYTHON_NO_DOCSTRINGS
38 #  CYTHON_FLAGS
39 #
40 # Source file properties that effect the build process are
41 #
42 #  CYTHON_IS_CXX
43 #
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.
46 #
47 # See also FindCython.cmake
48
49 # Copyright (C) 2018-2019 Intel Corporation
50 #
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
54 #
55 #      http://www.apache.org/licenses/LICENSE-2.0
56 #
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.
62 #
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
65
66 #=============================================================================
67 # Copyright 2011 Kitware, Inc.
68 #
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
72 #
73 #     http://www.apache.org/licenses/LICENSE-2.0
74 #
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 #=============================================================================
81
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 )
90
91 find_package( Cython REQUIRED )
92 find_package( PythonLibs REQUIRED )
93
94 set( CYTHON_CXX_EXTENSION "cxx" )
95 set( CYTHON_C_EXTENSION "c" )
96
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.
102   set( cxx_arg "" )
103   set( extension ${CYTHON_C_EXTENSION} )
104   set( pyx_lang "C" )
105   set( comment "Compiling Cython C source for ${_name}..." )
106
107   set( cython_include_directories "" )
108   set( pxd_dependencies "" )
109   set( pxi_dependencies "" )
110   set( c_header_dependencies "" )
111   set( pyx_locations "" )
112
113   foreach( pyx_file ${ARGN} )
114     get_filename_component( pyx_file_basename "${pyx_file}" NAME_WE )
115
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}..." )
123     endif()
124
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}" )
131
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}
137       NO_DEFAULT_PATH )
138     if( corresponding_pxd_file )
139       list( APPEND pxd_dependencies "${corresponding_pxd_file}" )
140     endif()
141
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)
149       if (pxi_location)
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}" )
158         endif()
159       endif()
160     endforeach() # for each include statement found
161
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}" )
170
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}" )
183             endif()
184           endif()
185         endforeach()
186
187         # check for pxd dependencies
188
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}" )
195           else()
196             string( REGEX REPLACE "cimport[ ]+([^ ]+).*" "\\1" module "${statement}" )
197           endif()
198           list( APPEND module_dependencies ${module} )
199         endforeach()
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 )
206           if( pxd_location )
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 )
219     endwhile()
220
221
222
223   endforeach() # pyx_file
224
225   # Set additional flags.
226   if( CYTHON_ANNOTATE )
227     set( annotate_arg "--annotate" )
228   endif()
229
230   if( CYTHON_NO_DOCSTRINGS )
231     set( no_docstrings_arg "--no-docstrings" )
232   endif()
233
234   if( "${CMAKE_BUILD_TYPE}" STREQUAL "Debug" OR
235         "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo" )
236       set( cython_debug_arg "--gdb" )
237   endif()
238
239   if( "${PYTHONLIBS_VERSION_STRING}" MATCHES "^2." )
240     set( version_arg "-2" )
241   elseif( "${PYTHONLIBS_VERSION_STRING}" MATCHES "^3." )
242     set( version_arg "-3" )
243   else()
244     set( version_arg )
245   endif()
246
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}" )
252   endforeach()
253
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 )
258
259   list( REMOVE_DUPLICATES pxd_dependencies )
260   list( REMOVE_DUPLICATES c_header_dependencies )
261
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}
270     COMMENT ${comment}
271     )
272
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 "" )
277 endfunction()
278
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} )
287     else()
288       list( APPEND other_module_sources ${_file} )
289     endif()
290   endforeach()
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} )
294   if( APPLE )
295     set_target_properties( ${_name} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup" )
296   else()
297     target_link_libraries( ${_name} PRIVATE ${PYTHON_LIBRARIES} )
298   endif()
299 endfunction()
300
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}" )
319       endif()
320     else()
321       list( APPEND other_module_sources ${_file} )
322     endif()
323   endforeach()
324
325   if( cython_arguments_MAIN_MODULE )
326     set( main_module ${cython_arguments_MAIN_MODULE} )
327   endif()
328   if( NOT main_module )
329     message( FATAL_ERROR "main module not found." )
330   endif()
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} )
336 endfunction()