Interrogate.cmake 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. # Filename: Interrogate.cmake
  2. #
  3. # Description: This file contains macros and functions that are used to invoke
  4. # interrogate, to generate wrappers for Python and/or other languages.
  5. #
  6. # Functions:
  7. # target_interrogate(target [ALL] [source1 [source2 ...]])
  8. # add_python_module(module [lib1 [lib2 ...]])
  9. # add_python_target(target [source1 [source2 ...]])
  10. #
  11. set(IGATE_FLAGS -DCPPPARSER -D__cplusplus -Dvolatile -Dmutable)
  12. # In addition, Interrogate needs to know if this is a 64-bit build:
  13. include(CheckTypeSize)
  14. check_type_size(long CMAKE_SIZEOF_LONG)
  15. if(CMAKE_SIZEOF_LONG EQUAL 8)
  16. list(APPEND IGATE_FLAGS "-D_LP64")
  17. endif()
  18. # This is a list of regexes that are applied to every filename. If one of the
  19. # regexes matches, that file will not be passed to Interrogate.
  20. set(INTERROGATE_EXCLUDE_REGEXES
  21. ".*\\.I$"
  22. ".*\\.N$"
  23. ".*\\.c$"
  24. ".*\\.lxx$"
  25. ".*\\.yxx$"
  26. ".*_src\\..*"
  27. )
  28. if(WIN32)
  29. list(APPEND IGATE_FLAGS -D_X86_ -D__STDC__=1 -DWIN32_VC -D "_declspec(param)=" -D "__declspec(param)=" -D_near -D_far -D__near -D__far -D_WIN32 -D__stdcall -DWIN32)
  30. endif()
  31. if(INTERROGATE_VERBOSE)
  32. list(APPEND IGATE_FLAGS "-v")
  33. endif()
  34. set(IMOD_FLAGS -python-native)
  35. # This stores the names of every module added to the Interrogate system:
  36. set(ALL_INTERROGATE_MODULES CACHE INTERNAL "Internal variable")
  37. #
  38. # Function: target_interrogate(target [ALL] [source1 [source2 ...]])
  39. # NB. This doesn't actually invoke interrogate, but merely adds the
  40. # sources to the list of scan sources associated with the target.
  41. # Interrogate will be invoked when add_python_module is called.
  42. # If ALL is specified, all of the sources from the associated
  43. # target are added.
  44. #
  45. function(target_interrogate target)
  46. set(sources)
  47. set(extensions)
  48. set(want_all OFF)
  49. set(extensions_keyword OFF)
  50. foreach(arg ${ARGN})
  51. if(arg STREQUAL "ALL")
  52. set(want_all ON)
  53. elseif(arg STREQUAL "EXTENSIONS")
  54. set(extensions_keyword ON)
  55. elseif(extensions_keyword)
  56. list(APPEND extensions "${arg}")
  57. else()
  58. list(APPEND sources "${arg}")
  59. endif()
  60. endforeach()
  61. # If ALL was specified, pull in all sources from the target.
  62. if(want_all)
  63. get_target_property(target_sources "${target}" SOURCES)
  64. list(APPEND sources ${target_sources})
  65. endif()
  66. list(REMOVE_DUPLICATES sources)
  67. # Now let's get everything's absolute path, so that it can be passed
  68. # through a property while still preserving the reference.
  69. set(absolute_sources)
  70. foreach(source ${sources})
  71. get_source_file_property(exclude "${source}" WRAP_EXCLUDE)
  72. if(NOT exclude)
  73. get_source_file_property(location "${source}" LOCATION)
  74. list(APPEND absolute_sources ${location})
  75. endif()
  76. endforeach(source)
  77. set(absolute_extensions)
  78. foreach(extension ${extensions})
  79. get_source_file_property(location "${extension}" LOCATION)
  80. list(APPEND absolute_extensions ${location})
  81. endforeach(extension)
  82. set_target_properties("${target}" PROPERTIES
  83. IGATE_SOURCES "${absolute_sources}")
  84. set_target_properties("${target}" PROPERTIES
  85. IGATE_EXTENSIONS "${absolute_extensions}")
  86. # CMake has no property for determining the source directory where the
  87. # target was originally added. interrogate_sources makes use of this
  88. # property (if it is set) in order to make all paths on the command-line
  89. # relative to it, thereby shortening the command-line even more.
  90. # Since this is not an Interrogate-specific property, it is not named with
  91. # an IGATE_ prefix.
  92. set_target_properties("${target}" PROPERTIES
  93. TARGET_SRCDIR "${CMAKE_CURRENT_SOURCE_DIR}")
  94. # Also store where the build files are kept, so the Interrogate output can go
  95. # there as well.
  96. set_target_properties("${target}" PROPERTIES
  97. TARGET_BINDIR "${CMAKE_CURRENT_BINARY_DIR}")
  98. endfunction(target_interrogate)
  99. #
  100. # Function: interrogate_sources(target output database language_flags module)
  101. #
  102. # This function actually runs a component-level interrogation against 'target'.
  103. # It generates the outfile.cxx (output) and dbfile.in (database) files, which
  104. # can then be used during the interrogate_module step to produce language
  105. # bindings.
  106. #
  107. # The target must first have had sources selected with target_interrogate.
  108. # Failure to do so will result in an error.
  109. #
  110. function(interrogate_sources target output database language_flags)
  111. get_target_property(sources "${target}" IGATE_SOURCES)
  112. get_target_property(extensions "${target}" IGATE_EXTENSIONS)
  113. if(NOT sources)
  114. message(FATAL_ERROR
  115. "Cannot interrogate ${target} unless it's run through target_interrogate first!")
  116. endif()
  117. get_target_property(srcdir "${target}" TARGET_SRCDIR)
  118. if(NOT srcdir)
  119. # No TARGET_SRCDIR was set, so we'll do everything relative to our
  120. # current binary dir instead:
  121. set(srcdir "${CMAKE_CURRENT_BINARY_DIR}")
  122. endif()
  123. set(scan_sources)
  124. set(nfiles)
  125. foreach(source ${sources})
  126. get_filename_component(source_basename "${source}" NAME)
  127. # Only certain sources should actually be scanned by Interrogate. The
  128. # rest are merely dependencies. This uses the exclusion regex above in
  129. # order to determine what files are okay:
  130. set(exclude OFF)
  131. foreach(regex ${INTERROGATE_EXCLUDE_REGEXES})
  132. if("${source_basename}" MATCHES "${regex}")
  133. set(exclude ON)
  134. endif()
  135. endforeach(regex)
  136. if(NOT exclude)
  137. # This file is to be scanned by Interrogate. In order to avoid
  138. # cluttering up the command line, we should first make it relative:
  139. file(RELATIVE_PATH rel_source "${srcdir}" "${source}")
  140. list(APPEND scan_sources "${rel_source}")
  141. # Also see if this file has a .N counterpart, which has directives
  142. # specific for Interrogate. If there is a .N file, we add it as a dep,
  143. # so that CMake will rerun Interrogate if the .N files are modified:
  144. get_filename_component(source_path "${source}" PATH)
  145. get_filename_component(source_name_we "${source}" NAME_WE)
  146. set(nfile "${source_path}/${source_name_we}.N")
  147. if(EXISTS "${nfile}")
  148. list(APPEND nfiles "${nfile}")
  149. endif()
  150. endif()
  151. endforeach(source)
  152. # Also add extensions, in relative-path form
  153. foreach(extension ${extensions})
  154. file(RELATIVE_PATH rel_extension "${srcdir}" "${extension}")
  155. list(APPEND scan_sources "${rel_extension}")
  156. endforeach(extension)
  157. # Interrogate also needs the include paths, so we'll extract them from the
  158. # target. These are available via a generator expression.
  159. # When we read the INTERFACE_INCLUDE_DIRECTORIES property, we need to read it
  160. # from a target that has the IS_INTERROGATE=1 property.
  161. # (See PackageConfig.cmake for an explanation why.)
  162. # The problem is, custom commands are not targets, so we can't put target
  163. # properties on them. And if you try to use the $<TARGET_PROPERTY:prop>
  164. # generator expression from the context of a custom command, it'll instead
  165. # read the property from the most recent actual target. As a workaround for
  166. # this, we create a fake target with the IS_INTERROGATE property set and pull
  167. # the INTERFACE_INCLUDE_DIRECTORIES property out through that.
  168. # I hate it, but such is CMake.
  169. add_custom_target(${target}_igate_internal)
  170. set_target_properties(${target}_igate_internal PROPERTIES
  171. IS_INTERROGATE 1
  172. INTERFACE_INCLUDE_DIRECTORIES "$<TARGET_PROPERTY:${target},INTERFACE_INCLUDE_DIRECTORIES>")
  173. # Note, the \t is a workaround for a CMake bug where using a plain space in
  174. # a JOIN will cause it to be escaped. Tabs are not escaped and will
  175. # separate correctly.
  176. set(include_flags "-I$<JOIN:$<TARGET_PROPERTY:${target}_igate_internal,INTERFACE_INCLUDE_DIRECTORIES>,\t-I>")
  177. # Get the compiler definition flags. These must be passed to Interrogate
  178. # in the same way that they are passed to the compiler so that Interrogate
  179. # will preprocess each file in the same way.
  180. set(define_flags "$<JOIN:\t$<SEMICOLON>$<TARGET_PROPERTY:${target},COMPILE_DEFINITIONS>,\t-D>")
  181. # If this is a release build that has NDEBUG defined, we need that too:
  182. string(TOUPPER "${CMAKE_BUILD_TYPE}" build_type)
  183. if("${CMAKE_CXX_FLAGS_${build_type}}" MATCHES ".*NDEBUG.*")
  184. list(APPEND define_flags "-DNDEBUG")
  185. endif()
  186. add_custom_command(
  187. OUTPUT "${output}" "${database}"
  188. COMMAND host_interrogate
  189. -oc "${output}"
  190. -od "${database}"
  191. -srcdir "${srcdir}"
  192. -library ${target}
  193. ${INTERROGATE_OPTIONS}
  194. ${IGATE_FLAGS}
  195. ${language_flags}
  196. ${define_flags}
  197. -S "${PROJECT_SOURCE_DIR}/dtool/src/interrogatedb"
  198. -S "${PROJECT_SOURCE_DIR}/dtool/src/parser-inc"
  199. -S "${PYTHON_INCLUDE_DIRS}"
  200. ${include_flags}
  201. ${scan_sources}
  202. DEPENDS host_interrogate ${sources} ${extensions} ${nfiles}
  203. COMMENT "Interrogating ${target}")
  204. # Propagate the target's compile definitions to the output file
  205. set_source_files_properties("${output}" PROPERTIES
  206. COMPILE_DEFINITIONS "$<TARGET_PROPERTY:${target},INTERFACE_COMPILE_DEFINITIONS>")
  207. endfunction(interrogate_sources)
  208. #
  209. # Function: add_python_module(module [lib1 [lib2 ...]] [LINK lib1 ...]
  210. # [IMPORT mod1 ...])
  211. # Uses interrogate to create a Python module. If the LINK keyword is specified,
  212. # the Python module is linked against the specified libraries instead of those
  213. # listed before. The IMPORT keyword makes the output module import another
  214. # Python module when it's initialized.
  215. #
  216. function(add_python_module module)
  217. if(NOT INTERROGATE_PYTHON_INTERFACE)
  218. return()
  219. endif()
  220. set(targets)
  221. set(component "Python")
  222. set(link_targets)
  223. set(import_flags)
  224. set(infiles_rel)
  225. set(infiles_abs)
  226. set(sources_abs)
  227. set(extensions)
  228. set(keyword)
  229. foreach(arg ${ARGN})
  230. if(arg STREQUAL "LINK" OR arg STREQUAL "IMPORT" OR arg STREQUAL "COMPONENT")
  231. set(keyword "${arg}")
  232. elseif(keyword STREQUAL "LINK")
  233. list(APPEND link_targets "${arg}")
  234. set(keyword)
  235. elseif(keyword STREQUAL "IMPORT")
  236. list(APPEND import_flags "-import" "${arg}")
  237. set(keyword)
  238. elseif(keyword STREQUAL "COMPONENT")
  239. set(component "${arg}")
  240. set(keyword)
  241. else()
  242. list(APPEND targets "${arg}")
  243. endif()
  244. endforeach(arg)
  245. if(NOT link_targets)
  246. set(link_targets ${targets})
  247. endif()
  248. string(REGEX REPLACE "^.*\\." "" modname "${module}")
  249. foreach(target ${targets})
  250. get_target_property(workdir_abs "${target}" TARGET_BINDIR)
  251. if(NOT workdir_abs)
  252. # No TARGET_BINDIR was set, so we'll just use our current directory:
  253. set(workdir_abs "${CMAKE_CURRENT_BINARY_DIR}")
  254. endif()
  255. # Keep command lines short
  256. file(RELATIVE_PATH workdir_rel "${CMAKE_CURRENT_BINARY_DIR}" "${workdir_abs}")
  257. interrogate_sources(${target}
  258. "${workdir_abs}/${target}_igate.cxx" "${workdir_abs}/${target}.in"
  259. "-python-native;-module;${module}")
  260. get_target_property(target_extensions "${target}" IGATE_EXTENSIONS)
  261. list(APPEND infiles_rel "${workdir_rel}/${target}.in")
  262. list(APPEND infiles_abs "${workdir_abs}/${target}.in")
  263. list(APPEND sources_abs "${workdir_abs}/${target}_igate.cxx")
  264. list(APPEND extensions ${target_extensions})
  265. endforeach(target)
  266. add_custom_command(
  267. OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${module}_module.cxx"
  268. COMMAND host_interrogate_module
  269. -oc "${CMAKE_CURRENT_BINARY_DIR}/${module}_module.cxx"
  270. -module ${modname} -library ${modname}
  271. ${import_flags}
  272. ${INTERROGATE_MODULE_OPTIONS}
  273. ${IMOD_FLAGS} ${infiles_rel}
  274. DEPENDS host_interrogate_module ${infiles_abs}
  275. COMMENT "Generating module ${module}")
  276. add_python_target(${module} COMPONENT "${component}" EXPORT "${component}"
  277. "${module}_module.cxx" ${sources_abs} ${extensions})
  278. target_link_libraries(${module} ${link_targets})
  279. if(CMAKE_VERSION VERSION_LESS "3.11")
  280. # CMake <3.11 doesn't allow generator expressions on source files, so we
  281. # need to copy them to our target, which does allow them.
  282. foreach(source ${sources_abs})
  283. get_source_file_property(compile_definitions "${source}" COMPILE_DEFINITIONS)
  284. if(compile_definitions)
  285. set_property(TARGET ${module} APPEND PROPERTY
  286. COMPILE_DEFINITIONS ${compile_definitions})
  287. set_source_files_properties("${source}" PROPERTIES COMPILE_DEFINITIONS "")
  288. endif()
  289. endforeach(source)
  290. endif()
  291. list(APPEND ALL_INTERROGATE_MODULES "${module}")
  292. set(ALL_INTERROGATE_MODULES "${ALL_INTERROGATE_MODULES}" CACHE INTERNAL "Internal variable")
  293. endfunction(add_python_module)