helpers_common.cmake 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. # OBS CMake common helper functions module
  2. # cmake-format: off
  3. # cmake-lint: disable=C0103
  4. # cmake-lint: disable=E1121
  5. # cmake-format: on
  6. include_guard(GLOBAL)
  7. # message_configuration: Function to print configuration outcome
  8. function(message_configuration)
  9. include(FeatureSummary)
  10. feature_summary(WHAT ALL VAR _feature_summary)
  11. message(DEBUG "${_feature_summary}")
  12. message(
  13. NOTICE
  14. " _ _ _ _ \n"
  15. " ___ | |__ ___ ___| |_ _ _ __| (_) ___ \n"
  16. " / _ \\| '_ \\/ __|_____/ __| __| | | |/ _` | |/ _ \\ \n"
  17. " | (_) | |_) \\__ \\_____\\__ \\ |_| |_| | (_| | | (_) |\n"
  18. " \\___/|_.__/|___/ |___/\\__|\\__,_|\\__,_|_|\\___/ \n"
  19. "\nOBS: Application Version: ${OBS_VERSION} - Build Number: ${OBS_BUILD_NUMBER}\n"
  20. "==================================================================================\n\n")
  21. get_property(OBS_FEATURES_ENABLED GLOBAL PROPERTY OBS_FEATURES_ENABLED)
  22. list(
  23. SORT OBS_FEATURES_ENABLED
  24. COMPARE NATURAL
  25. CASE SENSITIVE
  26. ORDER ASCENDING)
  27. if(OBS_FEATURES_ENABLED)
  28. message(NOTICE "------------------------ Enabled Features ------------------------")
  29. foreach(feature IN LISTS OBS_FEATURES_ENABLED)
  30. message(NOTICE " - ${feature}")
  31. endforeach()
  32. endif()
  33. get_property(OBS_FEATURES_DISABLED GLOBAL PROPERTY OBS_FEATURES_DISABLED)
  34. list(
  35. SORT OBS_FEATURES_DISABLED
  36. COMPARE NATURAL
  37. CASE SENSITIVE
  38. ORDER ASCENDING)
  39. if(OBS_FEATURES_DISABLED)
  40. message(NOTICE "------------------------ Disabled Features ------------------------")
  41. foreach(feature IN LISTS OBS_FEATURES_DISABLED)
  42. message(NOTICE " - ${feature}")
  43. endforeach()
  44. endif()
  45. if(ENABLE_PLUGINS)
  46. get_property(OBS_MODULES_ENABLED GLOBAL PROPERTY OBS_MODULES_ENABLED)
  47. list(
  48. SORT OBS_MODULES_ENABLED
  49. COMPARE NATURAL
  50. CASE SENSITIVE
  51. ORDER ASCENDING)
  52. if(OBS_MODULES_ENABLED)
  53. message(NOTICE "------------------------ Enabled Modules ------------------------")
  54. foreach(feature IN LISTS OBS_MODULES_ENABLED)
  55. message(NOTICE " - ${feature}")
  56. endforeach()
  57. endif()
  58. get_property(OBS_MODULES_DISABLED GLOBAL PROPERTY OBS_MODULES_DISABLED)
  59. list(
  60. SORT OBS_MODULES_DISABLED
  61. COMPARE NATURAL
  62. CASE SENSITIVE
  63. ORDER ASCENDING)
  64. if(OBS_MODULES_DISABLED)
  65. message(NOTICE "------------------------ Disabled Modules ------------------------")
  66. foreach(feature IN LISTS OBS_MODULES_DISABLED)
  67. message(NOTICE " - ${feature}")
  68. endforeach()
  69. endif()
  70. endif()
  71. message(NOTICE "----------------------------------------------------------------------------------")
  72. endfunction()
  73. # target_enable_feature: Adds feature to list of enabled application features and sets optional compile definitions
  74. function(target_enable_feature target feature_description)
  75. set_property(GLOBAL APPEND PROPERTY OBS_FEATURES_ENABLED "${feature_description}")
  76. if(ARGN)
  77. target_compile_definitions(${target} PRIVATE ${ARGN})
  78. endif()
  79. endfunction()
  80. # target_disable_feature: Adds feature to list of disabled application features and sets optional compile definitions
  81. function(target_disable_feature target feature_description)
  82. set_property(GLOBAL APPEND PROPERTY OBS_FEATURES_DISABLED "${feature_description}")
  83. if(ARGN)
  84. target_compile_definitions(${target} PRIVATE ${ARGN})
  85. endif()
  86. endfunction()
  87. # target_disable: Adds target to list of disabled modules
  88. function(target_disable target)
  89. set_property(GLOBAL APPEND PROPERTY OBS_MODULES_DISABLED ${target})
  90. endfunction()
  91. # _handle_generator_expression_dependency: Helper function to yield dependency from a generator expression
  92. function(_handle_generator_expression_dependency library)
  93. set(oneValueArgs FOUND_VAR)
  94. set(multiValueArgs)
  95. cmake_parse_arguments(var "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  96. set(${var_FOUND_VAR} "${var_FOUND_VAR}-NOTFOUND")
  97. message(DEBUG "Checking ${library}...")
  98. if(library MATCHES "\\$<\\$<PLATFORM_ID:[^>]+>:.+>" OR library MATCHES "\\$<\\$<NOT:\\$<PLATFORM_ID:[^>]+>>:.+>")
  99. # Platform-dependent generator expression found. Platforms are a comma-separated list of CMake host OS identifiers.
  100. # Convert to CMake list and check if current host OS is contained in list.
  101. string(REGEX REPLACE "\\$<.*\\$<PLATFORM_ID:([^>]+)>>?:([^>]+)>" "\\1;\\2" gen_expression "${library}")
  102. list(GET gen_expression 0 gen_platform)
  103. list(GET gen_expression 1 gen_library)
  104. string(REPLACE "," ";" gen_platform "${gen_platform}")
  105. set(${var_FOUND_VAR} "${var_FOUND_VAR}-SKIP")
  106. if(library MATCHES "\\$<\\$<NOT:.+>.+>")
  107. if(NOT CMAKE_SYSTEM_NAME IN_LIST gen_platform)
  108. set(${var_FOUND_VAR} "${gen_library}")
  109. endif()
  110. else()
  111. if(CMAKE_SYSTEM_NAME IN_LIST gen_platform)
  112. set(${var_FOUND_VAR} "${gen_library}")
  113. endif()
  114. endif()
  115. elseif(library MATCHES "\\$<\\$<BOOL:[^>]+>:.+>")
  116. # Boolean generator expression found. Consider parameter a CMake variable that resolves into a CMake-like boolean
  117. # value for a simple conditional check.
  118. string(REGEX REPLACE "\\$<\\$<BOOL:([^>]+)>:([^>]+)>" "\\1;\\2" gen_expression "${library}")
  119. list(GET gen_expression 0 gen_boolean)
  120. list(GET gen_expression 1 gen_library)
  121. set(${var_FOUND_VAR} "${var_FOUND_VAR}-SKIP")
  122. if(${gen_boolean})
  123. set(${var_FOUND_VAR} "${gen_library}")
  124. endif()
  125. elseif(library MATCHES "\\$<TARGET_NAME_IF_EXISTS:[^>]+>")
  126. # Target-dependent generator expression found. Consider parameter to be a CMake target identifier and check for
  127. # target existence.
  128. string(REGEX REPLACE "\\$<TARGET_NAME_IF_EXISTS:([^>]+)>" "\\1" gen_target "${library}")
  129. set(${var_FOUND_VAR} "${var_FOUND_VAR}-SKIP")
  130. if(TARGET ${gen_target})
  131. set(${var_FOUND_VAR} "${gen_target}")
  132. endif()
  133. elseif(library MATCHES "\\$<.*Qt6::EntryPointPrivate>" OR library MATCHES "\\$<.*Qt6::QDarwin.+PermissionPlugin>")
  134. set(${var_FOUND_VAR} "${var_FOUND_VAR}-SKIP")
  135. else()
  136. # Unknown or unimplemented generator expression found. Abort script run to either add to ignore list or implement
  137. # detection.
  138. message(FATAL_ERROR "${library} is an unsupported generator expression for linked libraries.")
  139. endif()
  140. if(CMAKE_VERSION VERSION_LESS 3.25)
  141. set(${var_FOUND_VAR}
  142. ${var_FOUND_VAR}
  143. PARENT_SCOPE)
  144. else()
  145. return(PROPAGATE ${var_FOUND_VAR})
  146. endif()
  147. endfunction()
  148. # find_dependencies: Check linked interface and direct dependencies of target
  149. function(find_dependencies)
  150. set(oneValueArgs TARGET FOUND_VAR)
  151. set(multiValueArgs)
  152. cmake_parse_arguments(var "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  153. if(NOT DEFINED is_root)
  154. # Root of recursive dependency resolution
  155. set(is_root TRUE)
  156. set(nested_depth 0)
  157. else()
  158. # Branch of recursive dependency resolution
  159. set(is_root FALSE)
  160. math(EXPR nested_depth "${nested_depth}+1")
  161. endif()
  162. # * LINK_LIBRARIES are direct dependencies
  163. # * INTERFACE_LINK_LIBRARIES are transitive dependencies
  164. get_target_property(linked_libraries ${var_TARGET} LINK_LIBRARIES)
  165. get_target_property(interface_libraries ${var_TARGET} INTERFACE_LINK_LIBRARIES)
  166. message(DEBUG "[${nested_depth}] Linked libraries in target ${var_TARGET}: ${linked_libraries}")
  167. message(DEBUG "[${nested_depth}] Linked interface libraries in target ${var_TARGET}: ${interface_libraries}")
  168. # Consider CMake targets only
  169. list(FILTER linked_libraries INCLUDE REGEX ".+::.+")
  170. list(FILTER interface_libraries INCLUDE REGEX ".+::.+")
  171. foreach(library IN LISTS linked_libraries interface_libraries)
  172. if(NOT library)
  173. continue()
  174. elseif(library MATCHES "\\$<.*:[^>]+>")
  175. # Generator expression found
  176. _handle_generator_expression_dependency(${library} FOUND_VAR found_library)
  177. if(found_library STREQUAL found_library-SKIP)
  178. continue()
  179. elseif(found_library)
  180. set(library ${found_library})
  181. endif()
  182. endif()
  183. message(DEBUG "[${nested_depth}] Found ${library}...")
  184. if(NOT library IN_LIST ${var_FOUND_VAR})
  185. list(APPEND found_libraries ${library})
  186. # Enter recursive branch
  187. find_dependencies(TARGET ${library} FOUND_VAR ${var_FOUND_VAR})
  188. endif()
  189. endforeach()
  190. if(NOT is_root)
  191. # cmake-format: off
  192. set(found_libraries ${found_libraries} PARENT_SCOPE)
  193. # cmake-format: on
  194. # Exit recursive branch
  195. return()
  196. endif()
  197. list(REMOVE_DUPLICATES found_libraries)
  198. list(APPEND ${var_FOUND_VAR} ${found_libraries})
  199. # cmake-format: off
  200. set(${var_FOUND_VAR} ${${var_FOUND_VAR}} PARENT_SCOPE)
  201. # cmake-format: on
  202. endfunction()
  203. # find_qt_plugins: Find and add Qt plugin libraries associated with Qt component to target
  204. function(find_qt_plugins)
  205. set(oneValueArgs COMPONENT TARGET FOUND_VAR)
  206. cmake_parse_arguments(var "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  207. string(REPLACE "::" ";" library_tuple "${var_COMPONENT}")
  208. list(GET library_tuple 0 library_namespace)
  209. list(GET library_tuple 1 library_name)
  210. if(NOT ${library_namespace} MATCHES "Qt6?")
  211. message(FATAL_ERROR "'find_qt_plugins' has to be called with a valid target from the Qt or Qt6 namespace.")
  212. endif()
  213. # cmake-format: off
  214. list(APPEND qt_plugins_Core platforms printsupport styles imageformats iconengines)
  215. # cmake-format: on
  216. list(APPEND qt_plugins_Gui platforminputcontexts)
  217. list(APPEND qt_plugins_Sql sqldrivers)
  218. list(APPEND qt_plugins_3dRender sceneparsers geometryloaders)
  219. list(APPEND qt_plugins_3dQuickRender renderplugins)
  220. list(APPEND qt_plugins_Positioning position)
  221. list(APPEND qt_plugins_Location geoservices)
  222. list(APPEND qt_plugins_TextToSpeech texttospeech)
  223. list(APPEND qt_plugins_WebView webview)
  224. if(qt_plugins_${library_name})
  225. get_target_property(library_location ${var_COMPONENT} IMPORTED_LOCATION)
  226. get_target_property(is_framework ${var_COMPONENT} FRAMEWORK)
  227. if(is_framework)
  228. # Resolve Qt plugin location relative to framework binary location on macOS
  229. set(plugins_location "../../../../../plugins")
  230. cmake_path(ABSOLUTE_PATH plugins_location BASE_DIRECTORY "${library_location}" NORMALIZE)
  231. else()
  232. # Resolve Qt plugin location relative to dynamic library location
  233. set(plugins_location "../../plugins")
  234. cmake_path(ABSOLUTE_PATH plugins_location BASE_DIRECTORY "${library_location}" NORMALIZE)
  235. endif()
  236. foreach(plugin IN ITEMS ${qt_plugins_${library_name}})
  237. if(NOT plugin IN_LIST plugins_list)
  238. if(EXISTS "${plugins_location}/${plugin}")
  239. # Gather all .dll or .dylib files in given plugin subdirectory
  240. file(
  241. GLOB plugin_libraries
  242. RELATIVE "${plugins_location}/${plugin}"
  243. "${plugins_location}/${plugin}/*.dylib" "${plugins_location}/${plugin}/*.dll")
  244. message(DEBUG "Found Qt plugin ${plugin} libraries: ${plugin_libraries}")
  245. foreach(plugin_library IN ITEMS ${plugin_libraries})
  246. set(plugin_full_path "${plugins_location}/${plugin}/${plugin_library}")
  247. list(APPEND plugins_list ${plugin_full_path})
  248. endforeach()
  249. endif()
  250. endif()
  251. endforeach()
  252. endif()
  253. # cmake-format: off
  254. set(${var_FOUND_VAR} ${plugins_list} PARENT_SCOPE)
  255. # cmake-format: on
  256. endfunction()
  257. # target_export: Helper function to export target as CMake package
  258. function(target_export target)
  259. if(NOT DEFINED exclude_variant)
  260. set(exclude_variant EXCLUDE_FROM_ALL)
  261. endif()
  262. get_target_property(is_framework ${target} FRAMEWORK)
  263. if(is_framework)
  264. set(package_destination "Frameworks/${target}.framework/Resources/cmake")
  265. set(include_destination "Frameworks/${target}.framework/Headers")
  266. else()
  267. set(package_destination "${OBS_CMAKE_DESTINATION}/${target}")
  268. set(include_destination "${OBS_INCLUDE_DESTINATION}")
  269. endif()
  270. install(
  271. TARGETS ${target}
  272. EXPORT ${target}Targets
  273. RUNTIME DESTINATION "${OBS_EXECUTABLE_DESTINATION}"
  274. COMPONENT Development
  275. ${exclude_variant}
  276. LIBRARY DESTINATION "${OBS_LIBRARY_DESTINATION}"
  277. COMPONENT Development
  278. ${exclude_variant}
  279. ARCHIVE DESTINATION "${OBS_LIBRARY_DESTINATION}"
  280. COMPONENT Development
  281. ${exclude_variant}
  282. FRAMEWORK DESTINATION Frameworks
  283. COMPONENT Development
  284. ${exclude_variant}
  285. INCLUDES
  286. DESTINATION "${include_destination}"
  287. PUBLIC_HEADER
  288. DESTINATION "${include_destination}"
  289. COMPONENT Development
  290. ${exclude_variant})
  291. get_target_property(obs_public_headers ${target} OBS_PUBLIC_HEADERS)
  292. if(obs_public_headers)
  293. foreach(header IN LISTS obs_public_headers)
  294. cmake_path(GET header PARENT_PATH header_dir)
  295. if(header_dir)
  296. if(NOT ${header_dir} IN_LIST header_dirs)
  297. list(APPEND header_dirs ${header_dir})
  298. endif()
  299. list(APPEND headers_${header_dir} ${header})
  300. else()
  301. list(APPEND headers ${header})
  302. endif()
  303. endforeach()
  304. foreach(header_dir IN LISTS header_dirs)
  305. install(
  306. FILES ${headers_${header_dir}}
  307. DESTINATION "${include_destination}/${header_dir}"
  308. COMPONENT Development
  309. ${exclude_variant})
  310. endforeach()
  311. if(headers)
  312. install(
  313. FILES ${headers}
  314. DESTINATION "${include_destination}"
  315. COMPONENT Development
  316. ${exclude_variant})
  317. endif()
  318. endif()
  319. if(target STREQUAL libobs AND NOT EXISTS "${include_destination}/obsconfig.h")
  320. install(
  321. FILES "${CMAKE_BINARY_DIR}/config/obsconfig.h"
  322. DESTINATION "${include_destination}"
  323. COMPONENT Development
  324. ${exclude_variant})
  325. endif()
  326. get_target_property(target_type ${target} TYPE)
  327. if(NOT target_type STREQUAL INTERFACE_LIBRARY)
  328. message(DEBUG "Generating export header for target ${target} as ${target}_EXPORT.h...")
  329. include(GenerateExportHeader)
  330. generate_export_header(${target} EXPORT_FILE_NAME "${target}_EXPORT.h")
  331. target_sources(${target} PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/${target}_EXPORT.h>)
  332. set_property(
  333. TARGET ${target}
  334. APPEND
  335. PROPERTY PUBLIC_HEADER "${target}_EXPORT.h")
  336. endif()
  337. set(TARGETS_EXPORT_NAME ${target}Targets)
  338. message(
  339. DEBUG
  340. "Generating CMake package configuration file ${target}Config.cmake with targets file ${TARGETS_EXPORT_NAME}...")
  341. include(CMakePackageConfigHelpers)
  342. configure_package_config_file(cmake/${target}Config.cmake.in ${target}Config.cmake
  343. INSTALL_DESTINATION "${package_destination}")
  344. message(DEBUG "Generating CMake package version configuration file ${target}ConfigVersion.cmake...")
  345. write_basic_package_version_file(
  346. "${target}ConfigVersion.cmake"
  347. VERSION ${OBS_VERSION_CANONICAL}
  348. COMPATIBILITY SameMajorVersion)
  349. export(
  350. EXPORT ${target}Targets
  351. FILE "${TARGETS_EXPORT_NAME}.cmake"
  352. NAMESPACE OBS::)
  353. export(PACKAGE ${target})
  354. install(
  355. EXPORT ${TARGETS_EXPORT_NAME}
  356. FILE ${TARGETS_EXPORT_NAME}.cmake
  357. NAMESPACE OBS::
  358. DESTINATION "${package_destination}"
  359. COMPONENT Development
  360. ${exclude_variant})
  361. install(
  362. FILES "${CMAKE_CURRENT_BINARY_DIR}/${target}Config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/${target}ConfigVersion.cmake"
  363. DESTINATION "${package_destination}"
  364. COMPONENT Development
  365. ${exclude_variant})
  366. endfunction()
  367. # check_uuid: Helper function to check for valid UUID
  368. function(check_uuid uuid_string return_value)
  369. set(valid_uuid TRUE)
  370. set(uuid_token_lengths 8 4 4 4 12)
  371. set(token_num 0)
  372. string(REPLACE "-" ";" uuid_tokens ${uuid_string})
  373. list(LENGTH uuid_tokens uuid_num_tokens)
  374. if(uuid_num_tokens EQUAL 5)
  375. message(DEBUG "UUID ${uuid_string} is valid with 5 tokens.")
  376. foreach(uuid_token IN LISTS uuid_tokens)
  377. list(GET uuid_token_lengths ${token_num} uuid_target_length)
  378. string(LENGTH "${uuid_token}" uuid_actual_length)
  379. if(uuid_actual_length EQUAL uuid_target_length)
  380. string(REGEX MATCH "[0-9a-fA-F]+" uuid_hex_match ${uuid_token})
  381. if(NOT uuid_hex_match STREQUAL uuid_token)
  382. set(valid_uuid FALSE)
  383. break()
  384. endif()
  385. else()
  386. set(valid_uuid FALSE)
  387. break()
  388. endif()
  389. math(EXPR token_num "${token_num}+1")
  390. endforeach()
  391. else()
  392. set(valid_uuid FALSE)
  393. endif()
  394. message(DEBUG "UUID ${uuid_string} valid: ${valid_uuid}")
  395. # cmake-format: off
  396. set(${return_value} ${valid_uuid} PARENT_SCOPE)
  397. # cmake-format: on
  398. endfunction()
  399. # legacy_check: Check if new CMake framework was not enabled and load legacy rules instead
  400. macro(legacy_check)
  401. if(OBS_CMAKE_VERSION VERSION_LESS 3.0.0)
  402. message(FATAL_ERROR "CMake version changed between CMakeLists.txt.")
  403. endif()
  404. endmacro()
  405. # add_obs_plugin: Add plugin subdirectory if host platform is in specified list of supported platforms
  406. function(add_obs_plugin target)
  407. set(options WITH_MESSAGE)
  408. set(oneValueArgs "")
  409. set(multiValueArgs PLATFORMS)
  410. cmake_parse_arguments(PARSE_ARGV 0 _AOP "${options}" "${oneValueArgs}" "${multiValueArgs}")
  411. set(found_platform FALSE)
  412. list(LENGTH _AOP_PLATFORMS _AOP_NUM_PLATFORMS)
  413. if(_AOP_NUM_PLATFORMS EQUAL 0)
  414. set(found_platform TRUE)
  415. else()
  416. foreach(platform IN LISTS _AOP_PLATFORMS)
  417. set(check_var_name "OS_${platform}")
  418. if(${${check_var_name}})
  419. set(found_platform TRUE)
  420. break()
  421. endif()
  422. endforeach()
  423. endif()
  424. if(found_platform)
  425. add_subdirectory(${target})
  426. elseif(_AOP_WITH_MESSAGE)
  427. add_custom_target(${target} COMMENT "Dummy target for unavailable module ${target}")
  428. target_disable(${target})
  429. endif()
  430. endfunction()