helpers_common.cmake 18 KB

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