Interrogate.cmake 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  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 -D "_declspec(param)=" -D "__declspec(param)=" -D_near -D_far -D__near -D__far -D_WIN32 -D__stdcall)
  30. endif()
  31. if(MSVC_VERSION)
  32. list(APPEND IGATE_FLAGS "-D_MSC_VER=${MSVC_VERSION}")
  33. endif()
  34. if(INTERROGATE_VERBOSE)
  35. list(APPEND IGATE_FLAGS "-v")
  36. endif()
  37. set(IMOD_FLAGS -python-native)
  38. # This stores the names of every module added to the Interrogate system:
  39. set(ALL_INTERROGATE_MODULES CACHE INTERNAL "Internal variable")
  40. #
  41. # Function: target_interrogate(target [ALL] [source1 [source2 ...]])
  42. # NB. This doesn't actually invoke interrogate, but merely adds the
  43. # sources to the list of scan sources associated with the target.
  44. # Interrogate will be invoked when add_python_module is called.
  45. # If ALL is specified, all of the sources from the associated
  46. # target are added.
  47. #
  48. function(target_interrogate target)
  49. set(sources)
  50. set(extensions)
  51. set(want_all OFF)
  52. set(extensions_keyword OFF)
  53. foreach(arg ${ARGN})
  54. if(arg STREQUAL "ALL")
  55. set(want_all ON)
  56. elseif(arg STREQUAL "EXTENSIONS")
  57. set(extensions_keyword ON)
  58. elseif(extensions_keyword)
  59. list(APPEND extensions "${arg}")
  60. else()
  61. list(APPEND sources "${arg}")
  62. endif()
  63. endforeach()
  64. # If ALL was specified, pull in all sources from the target.
  65. if(want_all)
  66. get_target_property(target_sources "${target}" SOURCES)
  67. list(APPEND sources ${target_sources})
  68. endif()
  69. list(REMOVE_DUPLICATES sources)
  70. # Now let's get everything's absolute path, so that it can be passed
  71. # through a property while still preserving the reference.
  72. set(absolute_sources)
  73. foreach(source ${sources})
  74. get_source_file_property(exclude "${source}" WRAP_EXCLUDE)
  75. if(NOT exclude)
  76. get_source_file_property(location "${source}" LOCATION)
  77. list(APPEND absolute_sources ${location})
  78. endif()
  79. endforeach(source)
  80. set(absolute_extensions)
  81. foreach(extension ${extensions})
  82. get_source_file_property(location "${extension}" LOCATION)
  83. list(APPEND absolute_extensions ${location})
  84. endforeach(extension)
  85. set_target_properties("${target}" PROPERTIES
  86. IGATE_SOURCES "${absolute_sources}")
  87. set_target_properties("${target}" PROPERTIES
  88. IGATE_EXTENSIONS "${absolute_extensions}")
  89. # CMake has no property for determining the source directory where the
  90. # target was originally added. interrogate_sources makes use of this
  91. # property (if it is set) in order to make all paths on the command-line
  92. # relative to it, thereby shortening the command-line even more.
  93. # Since this is not an Interrogate-specific property, it is not named with
  94. # an IGATE_ prefix.
  95. set_target_properties("${target}" PROPERTIES
  96. TARGET_SRCDIR "${CMAKE_CURRENT_SOURCE_DIR}")
  97. # Also store where the build files are kept, so the Interrogate output can go
  98. # there as well.
  99. set_target_properties("${target}" PROPERTIES
  100. TARGET_BINDIR "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}")
  101. endfunction(target_interrogate)
  102. #
  103. # Function: interrogate_sources(target output database language_flags module)
  104. #
  105. # This function actually runs a component-level interrogation against 'target'.
  106. # It generates the outfile.cxx (output) and dbfile.in (database) files, which
  107. # can then be used during the interrogate_module step to produce language
  108. # bindings.
  109. #
  110. # The target must first have had sources selected with target_interrogate.
  111. # Failure to do so will result in an error.
  112. #
  113. function(interrogate_sources target output database language_flags)
  114. get_target_property(sources "${target}" IGATE_SOURCES)
  115. get_target_property(extensions "${target}" IGATE_EXTENSIONS)
  116. if(NOT sources)
  117. message(FATAL_ERROR
  118. "Cannot interrogate ${target} unless it's run through target_interrogate first!")
  119. endif()
  120. get_target_property(srcdir "${target}" TARGET_SRCDIR)
  121. if(NOT srcdir)
  122. # No TARGET_SRCDIR was set, so we'll do everything relative to our
  123. # current binary dir instead:
  124. set(srcdir "${CMAKE_CURRENT_BINARY_DIR}")
  125. endif()
  126. set(scan_sources)
  127. set(nfiles)
  128. foreach(source ${sources})
  129. get_filename_component(source_basename "${source}" NAME)
  130. # Only certain sources should actually be scanned by Interrogate. The
  131. # rest are merely dependencies. This uses the exclusion regex above in
  132. # order to determine what files are okay:
  133. set(exclude OFF)
  134. foreach(regex ${INTERROGATE_EXCLUDE_REGEXES})
  135. if("${source_basename}" MATCHES "${regex}")
  136. set(exclude ON)
  137. endif()
  138. endforeach(regex)
  139. if(NOT exclude)
  140. # This file is to be scanned by Interrogate. In order to avoid
  141. # cluttering up the command line, we should first make it relative:
  142. file(RELATIVE_PATH rel_source "${srcdir}" "${source}")
  143. list(APPEND scan_sources "${rel_source}")
  144. # Also see if this file has a .N counterpart, which has directives
  145. # specific for Interrogate. If there is a .N file, we add it as a dep,
  146. # so that CMake will rerun Interrogate if the .N files are modified:
  147. get_filename_component(source_path "${source}" PATH)
  148. get_filename_component(source_name_we "${source}" NAME_WE)
  149. set(nfile "${source_path}/${source_name_we}.N")
  150. if(EXISTS "${nfile}")
  151. list(APPEND nfiles "${nfile}")
  152. endif()
  153. endif()
  154. endforeach(source)
  155. # Also add extensions, in relative-path form
  156. foreach(extension ${extensions})
  157. file(RELATIVE_PATH rel_extension "${srcdir}" "${extension}")
  158. list(APPEND scan_sources "${rel_extension}")
  159. endforeach(extension)
  160. # Interrogate also needs the include paths, so we'll extract them from the
  161. # target. These are available via a generator expression.
  162. # When we read the INTERFACE_INCLUDE_DIRECTORIES property, we need to read it
  163. # from a target that has the IS_INTERROGATE=1 property.
  164. # (See PackageConfig.cmake for an explanation why.)
  165. # The problem is, custom commands are not targets, so we can't put target
  166. # properties on them. And if you try to use the $<TARGET_PROPERTY:prop>
  167. # generator expression from the context of a custom command, it'll instead
  168. # read the property from the most recent actual target. As a workaround for
  169. # this, we create a fake target with the IS_INTERROGATE property set and pull
  170. # the INTERFACE_INCLUDE_DIRECTORIES property out through that.
  171. # I hate it, but such is CMake.
  172. add_custom_target(${target}_igate_internal)
  173. set_target_properties(${target}_igate_internal PROPERTIES
  174. IS_INTERROGATE 1
  175. INTERFACE_INCLUDE_DIRECTORIES "$<TARGET_PROPERTY:${target},INTERFACE_INCLUDE_DIRECTORIES>")
  176. # Note, the \t is a workaround for a CMake bug where using a plain space in
  177. # a JOIN will cause it to be escaped. Tabs are not escaped and will
  178. # separate correctly.
  179. set(include_flags "-I$<JOIN:$<TARGET_PROPERTY:${target}_igate_internal,INTERFACE_INCLUDE_DIRECTORIES>,\t-I>")
  180. # Get the compiler definition flags. These must be passed to Interrogate
  181. # in the same way that they are passed to the compiler so that Interrogate
  182. # will preprocess each file in the same way.
  183. set(_compile_defs "$<TARGET_PROPERTY:${target},COMPILE_DEFINITIONS>")
  184. if(NOT CMAKE_HOST_WIN32)
  185. # Win32's command-line parser doesn't understand "'"
  186. # that's fine, it also ignores '"'
  187. set(_q "'")
  188. endif()
  189. set(_compile_defs_flags "-D${_q}$<JOIN:${_compile_defs},${_q}\t-D${_q}>${_q}")
  190. # We may have just ended up with -D'' if there are no flags; filter that
  191. set(define_flags
  192. "$<$<NOT:$<STREQUAL:${_compile_defs_flags},-D${_q}${_q}>>:${_compile_defs_flags}>")
  193. # Some of the definitions may be specified using -D flags in the global
  194. # CXX_FLAGS variables; parse those out (this also picks up NDEBUG)
  195. set(_configs ${CMAKE_CONFIGURATION_TYPES} ${CMAKE_BUILD_TYPE} "<ALL>")
  196. list(REMOVE_DUPLICATES _configs)
  197. foreach(_config ${_configs})
  198. if(_config STREQUAL "<ALL>")
  199. set(flags "${CMAKE_CXX_FLAGS}")
  200. else()
  201. string(TOUPPER "${_config}" _CONFIG)
  202. set(flags "${CMAKE_CXX_FLAGS_${_CONFIG}}")
  203. endif()
  204. # Convert "/D define1" and "-Ddefine2" flags, interspersed with other
  205. # compiler nonsense, into a basic "-Ddefine1 -Ddefine2" string
  206. string(REGEX MATCHALL "[/-]D[ \t]*[A-Za-z0-9_]+" igate_flags "${flags}")
  207. string(REPLACE ";" " " igate_flags "${igate_flags}")
  208. string(REPLACE "/D" "-D" igate_flags "${igate_flags}")
  209. if(_config STREQUAL "<ALL>")
  210. list(APPEND define_flags "${igate_flags}")
  211. else()
  212. list(APPEND define_flags "$<$<CONFIG:${_config}>:${igate_flags}>")
  213. endif()
  214. endforeach(_config)
  215. get_filename_component(output_directory "${output}" DIRECTORY)
  216. get_filename_component(database_directory "${database}" DIRECTORY)
  217. add_custom_command(
  218. OUTPUT "${output}" "${database}"
  219. COMMAND ${CMAKE_COMMAND} -E
  220. make_directory "${output_directory}"
  221. COMMAND ${CMAKE_COMMAND} -E
  222. make_directory "${database_directory}"
  223. COMMAND interrogate
  224. -oc "${output}"
  225. -od "${database}"
  226. -srcdir "${srcdir}"
  227. -library ${target}
  228. ${INTERROGATE_OPTIONS}
  229. ${IGATE_FLAGS}
  230. ${language_flags}
  231. ${define_flags}
  232. -S "${PROJECT_SOURCE_DIR}/dtool/src/interrogatedb"
  233. -S "${PROJECT_SOURCE_DIR}/dtool/src/parser-inc"
  234. -S "${PYTHON_INCLUDE_DIRS}"
  235. ${include_flags}
  236. ${scan_sources}
  237. DEPENDS interrogate ${sources} ${extensions} ${nfiles}
  238. COMMENT "Interrogating ${target}")
  239. # Propagate the target's compile definitions to the output file
  240. set_source_files_properties("${output}" PROPERTIES
  241. COMPILE_DEFINITIONS "$<TARGET_PROPERTY:${target},INTERFACE_COMPILE_DEFINITIONS>")
  242. endfunction(interrogate_sources)
  243. #
  244. # Function: add_python_module(module [lib1 [lib2 ...]] [LINK lib1 ...]
  245. # [IMPORT mod1 ...] [INIT func1 ...])
  246. # Uses interrogate to create a Python module. If the LINK keyword is specified,
  247. # the Python module is linked against the specified libraries instead of those
  248. # listed before. The IMPORT keyword makes the output module import another
  249. # Python module when it's initialized.
  250. #
  251. function(add_python_module module)
  252. if(NOT INTERROGATE_PYTHON_INTERFACE)
  253. return()
  254. endif()
  255. set(targets)
  256. set(component "Python")
  257. set(link_targets)
  258. set(import_flags)
  259. set(infiles_rel)
  260. set(infiles_abs)
  261. set(sources_abs)
  262. set(extensions)
  263. set(keyword)
  264. foreach(arg ${ARGN})
  265. if(arg STREQUAL "LINK" OR arg STREQUAL "IMPORT" OR arg STREQUAL "INIT" OR arg STREQUAL "COMPONENT")
  266. set(keyword "${arg}")
  267. elseif(keyword STREQUAL "LINK")
  268. list(APPEND link_targets "${arg}")
  269. set(keyword)
  270. elseif(keyword STREQUAL "IMPORT")
  271. list(APPEND import_flags "-import" "${arg}")
  272. set(keyword)
  273. elseif(keyword STREQUAL "INIT")
  274. list(APPEND import_flags "-init" "${arg}")
  275. set(keyword)
  276. elseif(keyword STREQUAL "COMPONENT")
  277. set(component "${arg}")
  278. set(keyword)
  279. else()
  280. list(APPEND targets "${arg}")
  281. endif()
  282. endforeach(arg)
  283. if(NOT link_targets)
  284. set(link_targets ${targets})
  285. endif()
  286. string(REGEX REPLACE "^.*\\." "" modname "${module}")
  287. foreach(target ${targets})
  288. get_target_property(workdir_abs "${target}" TARGET_BINDIR)
  289. if(NOT workdir_abs)
  290. # No TARGET_BINDIR was set, so we'll just use our current directory:
  291. set(workdir_abs "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}")
  292. endif()
  293. # Keep command lines short
  294. file(RELATIVE_PATH workdir_rel "${CMAKE_CURRENT_BINARY_DIR}" "${workdir_abs}")
  295. get_target_property(target_module "${target}" IGATE_MODULE)
  296. if(NOT target_module)
  297. set(target_module "${module}")
  298. endif()
  299. interrogate_sources(${target}
  300. "${workdir_abs}/${target}_igate.cxx"
  301. "${workdir_abs}/${target}.in"
  302. "-python-native;-module;${target_module}")
  303. get_target_property(target_extensions "${target}" IGATE_EXTENSIONS)
  304. list(APPEND infiles_rel "${workdir_rel}/${target}.in")
  305. list(APPEND infiles_abs "${workdir_abs}/${target}.in")
  306. list(APPEND sources_abs "${workdir_abs}/${target}_igate.cxx")
  307. list(APPEND extensions ${target_extensions})
  308. endforeach(target)
  309. add_custom_command(
  310. OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}/${module}_module.cxx"
  311. COMMAND ${CMAKE_COMMAND} -E
  312. make_directory "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}"
  313. COMMAND interrogate_module
  314. -oc "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}/${module}_module.cxx"
  315. -module ${module} -library ${modname}
  316. ${import_flags}
  317. ${INTERROGATE_MODULE_OPTIONS}
  318. ${IMOD_FLAGS} ${infiles_rel}
  319. DEPENDS interrogate_module ${infiles_abs}
  320. COMMENT "Generating module ${module}")
  321. # CMake chokes on ${CMAKE_CFG_INTDIR} in source paths when unity builds are
  322. # enabled. The easiest way out of this is to skip unity for those paths.
  323. # Since generated Interrogate .cxx files are pretty big already, this doesn't
  324. # really inconvenience us at all.
  325. set_source_files_properties(
  326. "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}/${module}_module.cxx"
  327. ${sources_abs} PROPERTIES
  328. SKIP_UNITY_BUILD_INCLUSION YES)
  329. add_python_target(${module} COMPONENT "${component}" EXPORT "${component}"
  330. "${CMAKE_CURRENT_BINARY_DIR}/${PANDA_CFG_INTDIR}/${module}_module.cxx"
  331. ${sources_abs} ${extensions})
  332. target_link_libraries(${module} ${link_targets})
  333. if(CMAKE_VERSION VERSION_LESS "3.11")
  334. # CMake <3.11 doesn't allow generator expressions on source files, so we
  335. # need to copy them to our target, which does allow them.
  336. foreach(source ${sources_abs})
  337. get_source_file_property(compile_definitions "${source}" COMPILE_DEFINITIONS)
  338. if(compile_definitions)
  339. set_property(TARGET ${module} APPEND PROPERTY
  340. COMPILE_DEFINITIONS ${compile_definitions})
  341. set_source_files_properties("${source}" PROPERTIES COMPILE_DEFINITIONS "")
  342. endif()
  343. endforeach(source)
  344. endif()
  345. list(APPEND ALL_INTERROGATE_MODULES "${module}")
  346. set(ALL_INTERROGATE_MODULES "${ALL_INTERROGATE_MODULES}" CACHE INTERNAL "Internal variable")
  347. endfunction(add_python_module)