helpers_common.cmake 17 KB

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