Interrogate.cmake 12 KB

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