2
0

PackageConfig.cmake 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. # Filename: PackageConfig.cmake
  2. #
  3. # This module defines functions which find and configure libraries
  4. # and packages for Panda3D.
  5. #
  6. # Assumes an attempt to find the package has already been made with
  7. # find_package(). (i.e. relies on packagename_FOUND variable)
  8. #
  9. # The packages are added as imported/interface libraries in the PKG::
  10. # namespace. If the package is not found (or disabled by the user),
  11. # a dummy package will be created instead. Therefore, it is safe
  12. # to link against the PKG::PACKAGENAME target unconditionally.
  13. #
  14. # Function: package_option
  15. # Usage:
  16. # package_option(package_name package_doc_string
  17. # [DEFAULT ON | OFF]
  18. # [IMPORTED_AS CMake::Imported::Target [...]]
  19. # [FOUND_AS find_name]
  20. # [LICENSE license])
  21. #
  22. # Examples:
  23. # package_option(LIBNAME "Enables LIBNAME support." DEFAULT OFF)
  24. #
  25. # If no default is given, the default in normal
  26. # builds is to enable all found third-party packages.
  27. # In builds for redistribution, there is the additional requirement that
  28. # the package be suitably-licensed.
  29. #
  30. # FOUND_AS indicates the name of the CMake find_package() module, which
  31. # may differ from Panda3D's internal name for that package.
  32. #
  33. # IMPORTED_AS is used to indicate that the find_package() may have
  34. # provided one or more IMPORTED targets, and that if at least one is
  35. # found, the IMPORTED target(s) should be used instead of the
  36. # variables provided by find_package()
  37. #
  38. #
  39. # Function: package_status
  40. # Usage:
  41. # package_status(package_name "Package description" ["Config summary"])
  42. #
  43. # Examples:
  44. # package_status(OpenAL "OpenAL Audio Output")
  45. # package_status(ROCKET "Rocket" "without Python bindings")
  46. #
  47. #
  48. # Function: show_packages
  49. # Usage:
  50. # show_packages()
  51. #
  52. # This prints the package usage report using the information provided in
  53. # calls to package_status above.
  54. #
  55. set(_ALL_PACKAGE_OPTIONS CACHE INTERNAL "Internal variable")
  56. #
  57. # package_option
  58. #
  59. # In order to make sure no third-party licenses are inadvertently violated,
  60. # this imposes a few rules regarding license:
  61. # 1) If there is no license, no special restrictions.
  62. # 2) If there is a license, but the build is not flagged for redistribution,
  63. # no special restrictions.
  64. # 3) If there is a license, and this is for redistribution, the package is
  65. # forcibly defaulted off and must be explicitly enabled, unless the license
  66. # matches a list of licenses suitable for redistribution.
  67. #
  68. function(package_option name)
  69. # Parse the arguments.
  70. set(command)
  71. set(default)
  72. set(found_as "${name}")
  73. set(imported_as)
  74. set(license "")
  75. set(cache_string)
  76. string(TOUPPER "${name}" name)
  77. foreach(arg ${ARGN})
  78. if(command STREQUAL "DEFAULT")
  79. set(default "${arg}")
  80. set(command)
  81. elseif(command STREQUAL "FOUND_AS")
  82. set(found_as "${arg}")
  83. set(command)
  84. elseif(command STREQUAL "LICENSE")
  85. set(license "${arg}")
  86. set(command)
  87. elseif(arg STREQUAL "DEFAULT")
  88. set(command "DEFAULT")
  89. elseif(arg STREQUAL "FOUND_AS")
  90. set(command "FOUND_AS")
  91. elseif(arg STREQUAL "LICENSE")
  92. set(command "LICENSE")
  93. elseif(arg STREQUAL "IMPORTED_AS")
  94. set(command "IMPORTED_AS")
  95. elseif(command STREQUAL "IMPORTED_AS")
  96. list(APPEND imported_as "${arg}")
  97. else()
  98. # Yes, a list, because semicolons can be in there, and
  99. # that gets split up into multiple args, so we have to
  100. # join it back together here.
  101. list(APPEND cache_string "${arg}")
  102. endif()
  103. endforeach()
  104. if(command AND NOT command STREQUAL "IMPORTED_AS")
  105. message(SEND_ERROR "${command} in package_option takes an argument")
  106. endif()
  107. # If the default is not set, we set it.
  108. if(NOT DEFINED default)
  109. if(IS_DIST_BUILD)
  110. # Accept things that don't have a configured license
  111. if(license STREQUAL "")
  112. set(default "${${found_as}_FOUND}")
  113. else()
  114. list(FIND PANDA_DIST_USE_LICENSES ${license} license_index)
  115. # If the license isn't in the accept listed, don't use the package
  116. if(${license_index} EQUAL "-1")
  117. set(default OFF)
  118. else()
  119. set(default "${${found_as}_FOUND}")
  120. endif()
  121. endif()
  122. else()
  123. set(default "${${found_as}_FOUND}")
  124. endif()
  125. endif()
  126. option("HAVE_${name}" "${cache_string}" "${default}")
  127. # If it was set by the user but not found, display an error.
  128. string(TOUPPER "${found_as}" FOUND_AS)
  129. if(HAVE_${name} AND NOT ${found_as}_FOUND AND NOT ${FOUND_AS}_FOUND)
  130. message(SEND_ERROR "NOT FOUND: ${name}. Disable HAVE_${name} to continue.")
  131. endif()
  132. # Prevent the function from being called twice.
  133. # This would indicate a cmake error.
  134. if(";${_ALL_PACKAGE_OPTIONS};" MATCHES ";${name};")
  135. message(SEND_ERROR "package_option(${name}) was called twice.
  136. This is a bug in the cmake build scripts.")
  137. else()
  138. list(APPEND _ALL_PACKAGE_OPTIONS "${name}")
  139. set(_ALL_PACKAGE_OPTIONS "${_ALL_PACKAGE_OPTIONS}" CACHE INTERNAL "Internal variable")
  140. endif()
  141. set(PANDA_PACKAGE_DEFAULT_${name} "${default}" PARENT_SCOPE)
  142. if(${found_as}_FOUND OR ${FOUND_AS}_FOUND)
  143. set(PANDA_PACKAGE_FOUND_${name} ON PARENT_SCOPE)
  144. else()
  145. set(PANDA_PACKAGE_FOUND_${name} OFF PARENT_SCOPE)
  146. endif()
  147. # Create the INTERFACE library used to depend on this package.
  148. add_library(PKG::${name} INTERFACE IMPORTED GLOBAL)
  149. # If the option actually is enabled, populate the INTERFACE library created above
  150. if(HAVE_${name})
  151. set(use_variables ON)
  152. # This is gross, but we actually want to hide package include directories
  153. # from Interrogate to make sure it relies on parser-inc instead, so we'll
  154. # use some generator expressions to do that.
  155. set(_is_not_interface_lib
  156. "$<NOT:$<STREQUAL:$<TARGET_PROPERTY:TYPE>,INTERFACE_LIBRARY>>")
  157. set(_is_not_interrogate
  158. "$<NOT:$<BOOL:$<${_is_not_interface_lib}:$<TARGET_PROPERTY:IS_INTERROGATE>>>>")
  159. foreach(implib ${imported_as})
  160. if(TARGET ${implib})
  161. # We found one of the implibs, so we don't need to use variables
  162. # (below) anymore
  163. set(use_variables OFF)
  164. # Hide it from Interrogate
  165. target_link_libraries(PKG::${name} INTERFACE
  166. "$<${_is_not_interrogate}:$<TARGET_NAME:${implib}>>")
  167. endif()
  168. endforeach(implib)
  169. if(use_variables)
  170. if(DEFINED ${found_as}_INCLUDE_DIRS)
  171. set(includes ${${found_as}_INCLUDE_DIRS})
  172. elseif(DEFINED ${found_as}_INCLUDE_DIR)
  173. set(includes "${${found_as}_INCLUDE_DIR}")
  174. elseif(DEFINED ${FOUND_AS}_INCLUDE_DIRS)
  175. set(includes ${${FOUND_AS}_INCLUDE_DIRS})
  176. else()
  177. set(includes "${${FOUND_AS}_INCLUDE_DIR}")
  178. endif()
  179. if(DEFINED ${found_as}_LIBRARIES)
  180. set(libs ${${found_as}_LIBRARIES})
  181. elseif(DEFINED ${found_as}_LIBRARY)
  182. set(libs "${${found_as}_LIBRARY}")
  183. elseif(DEFINED ${FOUND_AS}_LIBRARIES)
  184. set(libs ${${FOUND_AS}_LIBRARIES})
  185. else()
  186. set(libs "${${FOUND_AS}_LIBRARY}")
  187. endif()
  188. target_link_libraries(PKG::${name} INTERFACE ${libs})
  189. # Hide it from Interrogate
  190. set_target_properties(PKG::${name} PROPERTIES
  191. INTERFACE_INCLUDE_DIRECTORIES "$<${_is_not_interrogate}:${includes}>")
  192. endif()
  193. endif()
  194. endfunction(package_option)
  195. set(_ALL_CONFIG_PACKAGES CACHE INTERNAL "Internal variable")
  196. #
  197. # package_status
  198. #
  199. function(package_status name desc)
  200. set(note "")
  201. foreach(arg ${ARGN})
  202. set(note "${arg}")
  203. endforeach()
  204. string(TOUPPER "${name}" name)
  205. if(NOT ";${_ALL_PACKAGE_OPTIONS};" MATCHES ";${name};")
  206. message(SEND_ERROR "package_status(${name}) was called before package_option(${name}).
  207. This is a bug in the cmake build scripts.")
  208. return()
  209. endif()
  210. if(";${_ALL_CONFIG_PACKAGES};" MATCHES ";${name};")
  211. message(SEND_ERROR "package_status(${name}) was called twice.
  212. This is a bug in the cmake build scripts.")
  213. else()
  214. list(APPEND _ALL_CONFIG_PACKAGES "${name}")
  215. set(_ALL_CONFIG_PACKAGES "${_ALL_CONFIG_PACKAGES}" CACHE INTERNAL "Internal variable")
  216. endif()
  217. set(PANDA_PACKAGE_DESC_${name} "${desc}" PARENT_SCOPE)
  218. set(PANDA_PACKAGE_NOTE_${name} "${note}" PARENT_SCOPE)
  219. endfunction()
  220. #
  221. # show_packages
  222. #
  223. function(show_packages)
  224. message("")
  225. message("Configuring support for the following optional third-party packages:")
  226. foreach(package ${_ALL_CONFIG_PACKAGES})
  227. set(desc "${PANDA_PACKAGE_DESC_${package}}")
  228. set(note "${PANDA_PACKAGE_NOTE_${package}}")
  229. if(HAVE_${package} AND PANDA_PACKAGE_FOUND_${package})
  230. if(NOT note STREQUAL "")
  231. message("+ ${desc} (${note})")
  232. else()
  233. message("+ ${desc}")
  234. endif()
  235. elseif(HAVE_${package})
  236. message("! ${desc} (enabled but not found)")
  237. else()
  238. if(NOT PANDA_PACKAGE_FOUND_${package})
  239. set(reason "not found")
  240. elseif(NOT PANDA_PACKAGE_DEFAULT_${package})
  241. set(reason "not requested")
  242. else()
  243. set(reason "disabled")
  244. endif()
  245. message("- ${desc} (${reason})")
  246. endif()
  247. endforeach()
  248. endfunction()
  249. #
  250. # export_packages(filename)
  251. #
  252. # Generates an includable CMake file that contains definitions for every PKG::
  253. # package defined.
  254. #
  255. function(export_packages filename)
  256. set(exports "# Exports for Panda3D PKG:: packages\n")
  257. foreach(pkg ${_ALL_PACKAGE_OPTIONS})
  258. set(exports "${exports}\n# Create imported target PKG::${pkg}\n")
  259. set(exports "${exports}add_library(PKG::${pkg} INTERFACE IMPORTED)\n\n")
  260. set(exports "${exports}set_target_properties(PKG::${pkg} PROPERTIES\n")
  261. foreach(prop
  262. INTERFACE_COMPILE_DEFINITIONS
  263. INTERFACE_COMPILE_FEATURES
  264. INTERFACE_COMPILE_OPTIONS
  265. INTERFACE_INCLUDE_DIRECTORIES
  266. INTERFACE_LINK_DEPENDS
  267. INTERFACE_LINK_DIRECTORIES
  268. INTERFACE_LINK_OPTIONS
  269. INTERFACE_POSITION_INDEPENDENT_CODE
  270. #INTERFACE_SYSTEM_INCLUDE_DIRECTORIES # Let the consumer dictate this
  271. INTERFACE_SOURCES)
  272. set(prop_ex "$<TARGET_PROPERTY:PKG::${pkg},${prop}>")
  273. set(exports "${exports}$<$<BOOL:${prop_ex}>: ${prop} \"${prop_ex}\"\n>")
  274. endforeach(prop)
  275. # Ugh, INTERFACE_LINK_LIBRARIES isn't transitive. Fine. Take care of it
  276. # by hand:
  277. set(libraries)
  278. set(stack "PKG::${pkg}")
  279. set(history)
  280. while(stack)
  281. # Remove head item from stack
  282. unset(head)
  283. while(NOT DEFINED head)
  284. if(NOT stack)
  285. break()
  286. endif()
  287. list(GET stack 0 head)
  288. list(REMOVE_AT stack 0)
  289. # Don't visit anything twice
  290. list(FIND history "${head}" _index)
  291. if(_index GREATER -1)
  292. unset(head)
  293. endif()
  294. endwhile()
  295. if(head)
  296. list(APPEND history "${head}")
  297. else()
  298. break()
  299. endif()
  300. # If head isn't a target, add it to `libraries`, else recurse
  301. if(TARGET "${head}")
  302. get_target_property(link_libs "${head}" INTERFACE_LINK_LIBRARIES)
  303. if(link_libs)
  304. list(APPEND stack ${link_libs})
  305. endif()
  306. get_target_property(type "${head}" TYPE)
  307. if(NOT type STREQUAL "INTERFACE_LIBRARY")
  308. get_target_property(imported_location "${head}" IMPORTED_LOCATION)
  309. get_target_property(imported_implib "${head}" IMPORTED_IMPLIB)
  310. if(imported_implib)
  311. list(APPEND libraries ${imported_implib})
  312. elseif(imported_location)
  313. list(APPEND libraries ${imported_location})
  314. endif()
  315. get_target_property(configs "${head}" IMPORTED_CONFIGURATIONS)
  316. if(configs AND NOT imported_location)
  317. foreach(config ${configs})
  318. get_target_property(imported_location "${head}" IMPORTED_LOCATION_${config})
  319. # Prefer IMPORTED_IMPLIB where present
  320. get_target_property(imported_implib "${head}" IMPORTED_IMPLIB_${config})
  321. if(imported_implib)
  322. set(imported_location "${imported_implib}")
  323. endif()
  324. if(imported_location)
  325. if(configs MATCHES ".*;.*")
  326. set(_bling "$<1:$>") # genex-escaped $
  327. list(APPEND libraries "${_bling}<${_bling}<CONFIG:${config}>:${imported_location}>")
  328. else()
  329. list(APPEND libraries ${imported_location})
  330. endif()
  331. endif()
  332. endforeach(config)
  333. endif()
  334. else()
  335. # This is an INTERFACE_LIBRARY.
  336. get_target_property(imported_libname "${head}" IMPORTED_LIBNAME)
  337. if(imported_libname)
  338. list(APPEND libraries ${imported_libname})
  339. endif()
  340. endif()
  341. elseif("${head}" MATCHES "\\$<TARGET_NAME:\([^>]+\)>")
  342. string(REGEX REPLACE ".*\\$<TARGET_NAME:\([^>]+\)>.*" "\\1" match "${head}")
  343. list(APPEND stack "${match}")
  344. else()
  345. list(APPEND libraries "${head}")
  346. endif()
  347. endwhile(stack)
  348. set(exports "${exports} INTERFACE_LINK_LIBRARIES \"${libraries}\"\n")
  349. set(exports "${exports})\n")
  350. endforeach(pkg)
  351. # file(GENERATE) does not like $<LINK_ONLY:...> (and it's meant to be
  352. # consumed by our importer) so we escape it
  353. set(_bling "$<1:$>") # genex-escaped $
  354. string(REPLACE "$<LINK_ONLY:" "${_bling}<LINK_ONLY:" exports "${exports}")
  355. file(GENERATE OUTPUT "${filename}" CONTENT "${exports}")
  356. endfunction(export_packages)
  357. #
  358. # export_targets(set [NAMESPACE namespace] [COMPONENT component])
  359. #
  360. # Export targets in the export set named by "set"
  361. #
  362. # NAMESPACE overrides the namespace prefixed to the exported targets; it
  363. # defaults to "Panda3D::[set]::" if no explicit override is given.
  364. #
  365. # COMPONENT overrides the install component for the generated .cmake file; it
  366. # defaults to "[set]" if no explicit override is given.
  367. #
  368. function(export_targets set)
  369. set(namespace "Panda3D::${set}::")
  370. set(component "${set}")
  371. set(keyword)
  372. foreach(arg ${ARGN})
  373. if(arg STREQUAL "NAMESPACE" OR
  374. arg STREQUAL "COMPONENT")
  375. set(keyword "${arg}")
  376. elseif(keyword STREQUAL "NAMESPACE")
  377. set(namespace "${arg}")
  378. elseif(keyword STREQUAL "COMPONENT")
  379. set(component "${arg}")
  380. else()
  381. message(FATAL_ERROR "export_targets() given unexpected arg: ${arg}")
  382. endif()
  383. endforeach(arg)
  384. export(EXPORT "${set}" NAMESPACE "${namespace}"
  385. FILE "${PROJECT_BINARY_DIR}/Panda3D${set}Targets.cmake")
  386. install(EXPORT "${set}" NAMESPACE "${namespace}"
  387. FILE "Panda3D${set}Targets.cmake"
  388. COMPONENT "${component}" DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Panda3D)
  389. endfunction(export_targets)
  390. #
  391. # find_package
  392. #
  393. # This override implements CMAKE_FIND_PACKAGE_PREFER_CONFIG on versions of
  394. # CMake too old to include it.
  395. #
  396. if(CMAKE_VERSION VERSION_LESS "3.15")
  397. macro(find_package name)
  398. if(";${ARGN};" MATCHES ";(CONFIG|MODULE|NO_MODULE);")
  399. # Caller explicitly asking for a certain mode; so be it.
  400. _find_package(${ARGV})
  401. elseif(CMAKE_FIND_PACKAGE_PREFER_CONFIG)
  402. # Try CONFIG
  403. _find_package("${name}" CONFIG ${ARGN})
  404. if(NOT ${name}_FOUND)
  405. # CONFIG didn't work, fall back to MODULE
  406. _find_package("${name}" MODULE ${ARGN})
  407. endif()
  408. else()
  409. # Default behavior
  410. _find_package(${ARGV})
  411. endif()
  412. endmacro(find_package)
  413. endif()