PackageConfig.cmake 15 KB

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