helpers_common.cmake 18 KB

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