QMSetupAPI.cmake 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092
  1. cmake_minimum_required(VERSION 3.19)
  2. #[[
  3. NOTICE
  4. --------
  5. Since Qt official CMake modules sets private header directory variables when you call `find_package(Qt)`
  6. only if the Qt targets hasn't been defined, if we place `find_package(Qt)` in a function, the variable
  7. will be cleared while the target remains after the function returns, as a result, we can never get the
  8. private header directory variables again.
  9. Therefore, never wrap `find_package(Qt)` in a function, use macro instead, any macros that wraps it also
  10. shouldn't be wrapped in any function.
  11. ]] #
  12. set(QMSETUP_MODULES_DIR ${CMAKE_CURRENT_LIST_DIR})
  13. if(WIN32)
  14. set(QMSETUP_SHARED_LIBRARY_CATEGORY bin)
  15. set(QMSETUP_NULL_FILE "NUL")
  16. set(QMSETUP_REGEX_ABSOLUTE_PATH "^[a-zA-Z]:|\\\\")
  17. else()
  18. set(QMSETUP_SHARED_LIBRARY_CATEGORY lib)
  19. set(QMSETUP_NULL_FILE "/dev/null")
  20. set(QMSETUP_REGEX_ABSOLUTE_PATH "^/")
  21. endif()
  22. set(QMSETUP_IGNORE_STDOUT > ${QMSETUP_NULL_FILE})
  23. set(QMSETUP_IGNORE_STDERR 2> ${QMSETUP_NULL_FILE})
  24. set(QMSETUP_IGNORE_STDOUT_STDERR > ${QMSETUP_NULL_FILE} 2>&1)
  25. if(TARGET qmsetup::corecmd)
  26. get_target_property(QMSETUP_CORECMD_EXECUTABLE qmsetup::corecmd LOCATION)
  27. else()
  28. set(QMSETUP_CORECMD_EXECUTABLE)
  29. endif()
  30. if(NOT DEFINED QMSETUP_FIND_QT_ORDER)
  31. set(QMSETUP_FIND_QT_ORDER Qt6 Qt5)
  32. endif()
  33. include_guard(DIRECTORY)
  34. #[[
  35. Include modules of this library.
  36. qm_import(<module...>)
  37. ]] #
  38. macro(qm_import)
  39. foreach(_module ${ARGN})
  40. if(NOT _module MATCHES "(.+)\\.cmake")
  41. set(_module "${_module}.cmake")
  42. endif()
  43. set(_module_path "${QMSETUP_MODULES_DIR}/modules/${_module}")
  44. if(NOT EXISTS "${_module_path}")
  45. message(FATAL_ERROR "qm_import: module \"${_module}\" not found.")
  46. endif()
  47. include("${_module_path}")
  48. endforeach()
  49. endmacro()
  50. #[[
  51. Include all modules of this library.
  52. qm_import_all()
  53. ]] #
  54. macro(qm_import_all)
  55. file(GLOB _tmp_modules "${QMSETUP_MODULES_DIR}/modules/*.cmake")
  56. foreach(_module IN LISTS _tmp_modules)
  57. include("${_module}")
  58. endforeach()
  59. unset(_tmp_modules)
  60. endmacro()
  61. #[[
  62. Find third-party packages by including scripts in `find-modules`.
  63. qm_find_package(<module...>)
  64. ]] #
  65. macro(qm_find_package)
  66. foreach(_module ${ARGN})
  67. if(NOT _module MATCHES "(.+)\\.cmake")
  68. set(_module "${_module}.cmake")
  69. endif()
  70. set(_module_path "${QMSETUP_MODULES_DIR}/find-modules/${_module}")
  71. # Prepend "Find" to the file name
  72. if(NOT EXISTS "${_module_path}")
  73. set(_module_path "${QMSETUP_MODULES_DIR}/find-modules/Find${_module}")
  74. endif()
  75. if(NOT EXISTS "${_module_path}")
  76. message(FATAL_ERROR "qm_find_package: find script \"${_module}\" not found.")
  77. endif()
  78. include("${_module_path}")
  79. endforeach()
  80. endmacro()
  81. #[[
  82. Parse version and create seq vars with specified prefix.
  83. qm_parse_version(<prefix> <version>)
  84. ]] #
  85. function(qm_parse_version _prefix _version)
  86. string(REPLACE "." ";" _version_list ${_version})
  87. list(LENGTH _version_list _version_count)
  88. list(PREPEND _version_list 0) # Add placeholder
  89. foreach(_i RANGE 1 4)
  90. if(_i LESS_EQUAL _version_count)
  91. list(GET _version_list ${_i} _item)
  92. else()
  93. set(_item 0)
  94. endif()
  95. set(${_prefix}_${_i} ${_item} PARENT_SCOPE)
  96. endforeach()
  97. endfunction()
  98. #[[
  99. Get shorter version number.
  100. qm_crop_version(<VAR> <version> <count>)
  101. ]] #
  102. function(qm_crop_version _var _version _count)
  103. qm_parse_version(FUNC ${_version})
  104. set(_list)
  105. foreach(_i RANGE 1 ${_count})
  106. list(APPEND _list ${FUNC_${_i}})
  107. endforeach()
  108. string(JOIN "." _short_version ${_list})
  109. set(${_var} ${_short_version} PARENT_SCOPE)
  110. endfunction()
  111. #[[
  112. Tell if the given paths are same in canonical form.
  113. qm_paths_equal(<VAR> <path1> <path2>)
  114. ]] #
  115. function(qm_paths_equal _out _path1 _path2)
  116. # cmake_path(NORMAL_PATH) is introduced in CMake 3.20, we don't use it
  117. # We call `get_filename_component` twice to normalize the paths
  118. get_filename_component(_path1 ${_path1} ABSOLUTE)
  119. get_filename_component(_path1 ${_path1} REALPATH)
  120. get_filename_component(_path2 ${_path2} ABSOLUTE)
  121. get_filename_component(_path2 ${_path2} REALPATH)
  122. if(_path1 STREQUAL _path2)
  123. set(${_out} on PARENT_SCOPE)
  124. else()
  125. set(${_out} off PARENT_SCOPE)
  126. endif()
  127. endfunction()
  128. #[[
  129. Set value if valid, otherwise use default.
  130. qm_set_value(<key> <maybe_value...> <default>)
  131. ]] #
  132. function(qm_set_value _key)
  133. set(_args "${ARGN}")
  134. list(POP_BACK _args _default)
  135. foreach(_item IN LISTS _args)
  136. if(${_item})
  137. set(${_key} ${${_item}} PARENT_SCOPE)
  138. return()
  139. endif()
  140. endforeach()
  141. set(${_key} ${_default} PARENT_SCOPE)
  142. endfunction()
  143. #[[
  144. Skip CMAKE_AUTOMOC for sources files or ones in directories.
  145. qm_skip_automoc(<file/dir...>)
  146. ]] #
  147. function(qm_skip_automoc)
  148. foreach(_item ${ARGN})
  149. get_filename_component(_item ${_item} ABSOLUTE)
  150. if(IS_DIRECTORY ${_item})
  151. file(GLOB _src
  152. ${_item}/*.h ${_item}/*.hpp
  153. ${_item}/*.hh ${_item}/*.hxx
  154. ${_item}/*.cpp ${_item}/*.cxx
  155. ${_item}/*.c ${_item}/*.cc
  156. ${_item}/*.m ${_item}/*.mm
  157. )
  158. set_source_files_properties(
  159. ${_src} PROPERTIES SKIP_AUTOMOC ON
  160. )
  161. elseif(EXISTS ${_item})
  162. set_source_files_properties(
  163. ${_item} PROPERTIES SKIP_AUTOMOC ON
  164. )
  165. endif()
  166. endforeach()
  167. endfunction()
  168. #[[
  169. Find Qt libraries. Don't wrap it in any functions.
  170. qm_find_qt(<modules...> [QUIET | REQUIRED | EXACT])
  171. #]]
  172. macro(qm_find_qt)
  173. set(options QUIET REQUIRED EXACT)
  174. set(oneValueArgs)
  175. set(multiValueArgs)
  176. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  177. set(_qm_find_qt_options)
  178. if(FUNC_QUIET)
  179. list(APPEND _qm_find_qt_options QUIET)
  180. elseif(FUNC_REQUIRED)
  181. list(APPEND _qm_find_qt_options REQUIRED)
  182. elseif(FUNC_EXACT)
  183. list(APPEND _qm_find_qt_options EXACT)
  184. endif()
  185. if(NOT _qm_find_qt_options)
  186. set(_qm_find_qt_options REQUIRED)
  187. endif()
  188. foreach(_module ${FUNC_UNPARSED_ARGUMENTS})
  189. if(NOT QT_VERSION_MAJOR)
  190. find_package(QT NAMES ${QMSETUP_FIND_QT_ORDER} COMPONENTS ${_module} ${_qm_find_qt_options})
  191. endif()
  192. if(NOT TARGET Qt${QT_VERSION_MAJOR}::${_module})
  193. find_package(Qt${QT_VERSION_MAJOR} COMPONENTS ${_module} ${_qm_find_qt_options})
  194. endif()
  195. endforeach()
  196. unset(_qm_find_qt_options)
  197. endmacro()
  198. #[[
  199. Link Qt libraries. Don't wrap it in any functions.
  200. qm_link_qt(<target> <scope> <modules...>)
  201. #]]
  202. macro(qm_link_qt _target _scope)
  203. foreach(_module ${ARGN})
  204. qm_find_qt(${_module})
  205. target_link_libraries(${_target} ${_scope} Qt${QT_VERSION_MAJOR}::${_module})
  206. endforeach()
  207. endmacro()
  208. #[[
  209. Include Qt private header directories. Don't wrap it in any functions.
  210. qm_include_qt_private(<target> <scope> <modules...>)
  211. #]]
  212. macro(qm_include_qt_private _target _scope)
  213. foreach(_module ${ARGN})
  214. qm_find_qt(${_module}Private QUIET)
  215. if (TARGET Qt${QT_VERSION_MAJOR}::${_module}Private)
  216. target_link_libraries(${_target} ${_scope} Qt${QT_VERSION_MAJOR}::${_module}Private)
  217. else()
  218. qm_find_qt(${_module})
  219. target_include_directories(${_target} ${_scope} ${Qt${QT_VERSION_MAJOR}${_module}_PRIVATE_INCLUDE_DIRS})
  220. endif()
  221. endforeach()
  222. endmacro()
  223. #[[
  224. Helper to set or append all kinds of attributes to a target. Don't wrap it in any functions.
  225. qm_configure_target(<target>
  226. [SOURCES <files>]
  227. [LINKS <libs>]
  228. [LINKS_INTERFACE <libs>]
  229. [LINKS_PRIVATE <libs>]
  230. [INCLUDE <dirs>]
  231. [INCLUDE_INTERFACE <dirs>]
  232. [INCLUDE_PRIVATE <dirs>]
  233. [LINKDIR <dirs>]
  234. [LINKDIR_INTERFACE <dirs>]
  235. [LINKDIR_PRIVATE <dirs>]
  236. [DEFINES <defs>]
  237. [DEFINES_INTERFACE <defs>]
  238. [DEFINES_PRIVATE <defs>]
  239. [FEATURES <features>]
  240. [FEATURES_INTERFACE <features>]
  241. [FEATURES_PRIVATE <features>]
  242. [CCFLAGS <flags>]
  243. [CCFLAGS_INTERFACE <flags>]
  244. [CCFLAGS_PUBLIC <flags>]
  245. [LDFLAGS <flags>]
  246. [LDFLAGS_INTERFACE <flags>]
  247. [LDFLAGS_PUBLIC <flags>]
  248. [QT_LINKS <modules>]
  249. [QT_LINKS_INTERFACE <modules>]
  250. [QT_LINKS_PRIVATE <modules>]
  251. [QT_INCLUDE_PRIVATE <modules>]
  252. [SKIP_AUTOMOC <dir/file...>]
  253. )
  254. INCLUDE/LINKDIR: `dir/*` will be expanded to all subdirectories
  255. `dir/**` will be expanded to all descendent directories recursively
  256. ]] #
  257. macro(qm_configure_target _target)
  258. set(options)
  259. set(oneValueArgs)
  260. set(multiValueArgs
  261. SOURCES
  262. LINKS LINKS_INTERFACE LINKS_PRIVATE
  263. INCLUDE INCLUDE_INTERFACE INCLUDE_PRIVATE
  264. LINKDIR LINKDIR_INTERFACE LINKDIR_PRIVATE
  265. DEFINES DEFINES_INTERFACE DEFINES_PRIVATE
  266. FEATURES FEATURES_INTERFACE FEATURES_PRIVATE
  267. CCFLAGS CCFLAGS_INTERFACE CCFLAGS_PUBLIC
  268. LDFLAGS LDFLAGS_INTERFACE LDFLAGS_PUBLIC
  269. QT_LINKS QT_LINKS_PRIVATE QT_LINKS_INTERFACE
  270. QT_INCLUDE_PRIVATE
  271. SKIP_AUTOMOC
  272. )
  273. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  274. target_sources(${_target} PRIVATE ${FUNC_SOURCES})
  275. target_link_libraries(${_target} PUBLIC ${FUNC_LINKS})
  276. target_link_libraries(${_target} PRIVATE ${FUNC_LINKS_PRIVATE})
  277. if(FUNC_INCLUDE)
  278. _qm_resolve_dir_helper("${FUNC_INCLUDE}" _temp_dirs)
  279. target_include_directories(${_target} PUBLIC ${_temp_dirs})
  280. unset(_temp_dirs)
  281. endif()
  282. if(FUNC_INCLUDE_INTERFACE)
  283. _qm_resolve_dir_helper("${FUNC_INCLUDE_INTERFACE}" _temp_dirs)
  284. target_include_directories(${_target} INTERFACE ${_temp_dirs})
  285. unset(_temp_dirs)
  286. endif()
  287. if(FUNC_INCLUDE_PRIVATE)
  288. _qm_resolve_dir_helper("${FUNC_INCLUDE_PRIVATE}" _temp_dirs)
  289. target_include_directories(${_target} PRIVATE ${_temp_dirs})
  290. unset(_temp_dirs)
  291. endif()
  292. if(FUNC_LINKDIR)
  293. _qm_resolve_dir_helper("${FUNC_LINKDIR}" _temp_dirs)
  294. target_link_directories(${_target} PUBLIC ${_temp_dirs})
  295. unset(_temp_dirs)
  296. endif()
  297. if(FUNC_LINKDIR_INTERFACE)
  298. _qm_resolve_dir_helper("${FUNC_LINKDIR}" _temp_dirs)
  299. target_link_directories(${_target} INTERFACE ${_temp_dirs})
  300. unset(_temp_dirs)
  301. endif()
  302. if(FUNC_LINKDIR_PRIVATE)
  303. _qm_resolve_dir_helper("${FUNC_LINKDIR_PRIVATE}" _temp_dirs)
  304. target_link_directories(${_target} PRIVATE ${_temp_dirs})
  305. unset(_temp_dirs)
  306. endif()
  307. target_compile_definitions(${_target} PUBLIC ${FUNC_DEFINES})
  308. target_compile_definitions(${_target} INTERFACE ${FUNC_DEFINES_INTERFACE})
  309. target_compile_definitions(${_target} PRIVATE ${FUNC_DEFINES_PRIVATE})
  310. target_compile_features(${_target} PUBLIC ${FUNC_FEATURES})
  311. target_compile_features(${_target} INTERFACE ${FUNC_FEATURES_INTERFACE})
  312. target_compile_features(${_target} PRIVATE ${FUNC_FEATURES_PRIVATE})
  313. # CMake won't add language standard flag if the compiler default mode supports the standard,
  314. # however, if the -std argument is not explicitly specified, the clang language server will
  315. # not work properly.
  316. # https://discourse.cmake.org/t/cmake-does-not-set-the-compiler-option-std-to-gnu17-or-c-17-although-i-set-the-target-compile-features-to-cxx-std-17/3299/8
  317. foreach(_item IN LISTS FUNC_FEATURES FUNC_FEATURES_PRIVATE)
  318. if(_item MATCHES "cxx_std_(.+)")
  319. set_property(TARGET APPEND PROPERTY CXX_STANDARD ${CMAKE_MATCH_1})
  320. elseif(_item MATCHES "c_std_(.+)")
  321. set_property(TARGET APPEND PROPERTY C_STANDARD ${CMAKE_MATCH_1})
  322. endif()
  323. endforeach()
  324. target_compile_options(${_target} PUBLIC ${FUNC_CCFLAGS_PUBLIC})
  325. target_compile_options(${_target} INTERFACE ${FUNC_CCFLAGS_INTERFACE})
  326. target_compile_options(${_target} PRIVATE ${FUNC_CCFLAGS})
  327. target_link_options(${_target} PUBLIC ${FUNC_LDFLAGS_PUBLIC})
  328. target_link_options(${_target} INTERFACE ${FUNC_LDFLAGS_INTERFACE})
  329. target_link_options(${_target} PRIVATE ${FUNC_LDFLAGS})
  330. qm_link_qt(${_target} PUBLIC ${FUNC_QT_LINKS})
  331. qm_link_qt(${_target} INTERFACE ${FUNC_QT_LINKS_INTERFACE})
  332. qm_link_qt(${_target} PRIVATE ${FUNC_QT_LINKS_PRIVATE})
  333. qm_include_qt_private(${_target} PRIVATE ${FUNC_QT_INCLUDE_PRIVATE})
  334. if(FUNC_SKIP_AUTOMOC)
  335. _qm_resolve_file_helper("${FUNC_SKIP_AUTOMOC}" _temp_files)
  336. qm_skip_automoc(${_temp_files})
  337. unset(_temp_files)
  338. endif()
  339. endmacro()
  340. #[[
  341. Helper to define export macros.
  342. qm_export_defines(<target>
  343. [PREFIX <prefix>]
  344. [STATIC <token>]
  345. [LIBRARY <token>]
  346. )
  347. ]] #
  348. function(qm_export_defines _target)
  349. set(options)
  350. set(oneValueArgs PREFIX STATIC LIBRARY)
  351. set(multiValueArgs)
  352. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  353. if(NOT FUNC_PREFIX)
  354. string(TOUPPER ${_target} _prefix)
  355. else()
  356. set(_prefix ${FUNC_PREFIX})
  357. endif()
  358. qm_set_value(_static_macro FUNC_STATIC ${_prefix}_STATIC)
  359. qm_set_value(_library_macro FUNC_LIBRARY ${_prefix}_LIBRARY)
  360. get_target_property(_type ${_target} TYPE)
  361. if(_type STREQUAL "INTERFACE_LIBRARY")
  362. return()
  363. endif()
  364. if(_type STREQUAL "STATIC_LIBRARY")
  365. target_compile_definitions(${_target} PUBLIC ${_static_macro})
  366. endif()
  367. target_compile_definitions(${_target} PRIVATE ${_library_macro})
  368. endfunction()
  369. #[[
  370. Attach windows RC file to a target.
  371. qm_add_win_rc(<target>
  372. [NAME name]
  373. [VERSION version]
  374. [DESCRIPTION desc]
  375. [COPYRIGHT copyright]
  376. [ICON ico]
  377. [OUTPUT_DIR dir]
  378. )
  379. ]] #
  380. function(qm_add_win_rc _target)
  381. if(NOT WIN32)
  382. return()
  383. endif()
  384. _qm_check_target_type_helper(${_target} _ "EXECUTABLE" "SHARED_LIBRARY")
  385. set(options)
  386. set(oneValueArgs NAME VERSION DESCRIPTION COPYRIGHT ICON OUTPUT_DIR)
  387. set(multiValueArgs)
  388. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  389. qm_set_value(_name FUNC_NAME ${_target})
  390. qm_set_value(_version FUNC_VERSION PROJECT_VERSION "0.0.0.0")
  391. qm_set_value(_desc FUNC_DESCRIPTION PROJECT_DESCRIPTION ${_name})
  392. qm_set_value(_copyright FUNC_COPYRIGHT ${_name})
  393. qm_parse_version(_ver ${_version})
  394. set(RC_VERSION ${_ver_1},${_ver_2},${_ver_3},${_ver_4})
  395. set(RC_APPLICATION_NAME ${_name})
  396. set(RC_VERSION_STRING ${_version})
  397. set(RC_DESCRIPTION ${_desc})
  398. set(RC_COPYRIGHT ${_copyright})
  399. if(NOT FUNC_ICON)
  400. set(RC_ICON_COMMENT "//")
  401. set(RC_ICON_PATH)
  402. else()
  403. get_filename_component(RC_ICON_PATH ${FUNC_ICON} ABSOLUTE)
  404. endif()
  405. qm_set_value(_out_dir OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR})
  406. set(_out_path "${_out_dir}/${_target}_res.rc")
  407. configure_file("${QMSETUP_MODULES_DIR}/windows/WinResource.rc.in" ${_out_path} @ONLY)
  408. target_sources(${_target} PRIVATE ${_out_path})
  409. if(FUNC_ICON)
  410. if(${FUNC_ICON} IS_NEWER_THAN ${_out_path})
  411. file(TOUCH_NOCREATE ${_out_path})
  412. endif()
  413. endif()
  414. endfunction()
  415. #[[
  416. Attach windows RC file to a target, enhanced edition.
  417. qm_add_win_rc_enhanced(<target>
  418. [NAME name]
  419. [VERSION version]
  420. [DESCRIPTION description]
  421. [COPYRIGHT copyright]
  422. [COMMENTS comments]
  423. [COMPANY company]
  424. [INTERNAL_NAME internal name]
  425. [TRADEMARK trademark]
  426. [ORIGINAL_FILENAME original filename]
  427. [ICONS icon file paths]
  428. [OUTPUT_DIR dir]
  429. )
  430. ]] #
  431. function(qm_add_win_rc_enhanced _target)
  432. if(NOT WIN32)
  433. return()
  434. endif()
  435. _qm_check_target_type_helper(${_target} _type "EXECUTABLE" "SHARED_LIBRARY")
  436. set(options)
  437. set(oneValueArgs
  438. NAME VERSION DESCRIPTION COPYRIGHT COMMENTS COMPANY
  439. INTERNAL_NAME TRADEMARK ORIGINAL_FILENAME OUTPUT_DIR
  440. )
  441. set(multiValueArgs ICONS)
  442. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  443. qm_set_value(_name FUNC_NAME ${_target})
  444. qm_set_value(_version FUNC_VERSION PROJECT_VERSION "0.0.0.0")
  445. qm_set_value(_desc FUNC_DESCRIPTION PROJECT_DESCRIPTION ${_name})
  446. qm_set_value(_copyright FUNC_COPYRIGHT ${_name})
  447. qm_set_value(_comments FUNC_COMMENTS "")
  448. qm_set_value(_company FUNC_COMPANY "")
  449. qm_set_value(_internal_name FUNC_INTERNAL_NAME "")
  450. qm_set_value(_trademark FUNC_TRADEMARK "")
  451. qm_set_value(_original_filename FUNC_ORIGINAL_FILENAME "")
  452. qm_parse_version(_ver ${_version})
  453. set(RC_VERSION ${_ver_1},${_ver_2},${_ver_3},${_ver_4})
  454. set(RC_APPLICATION_NAME ${_name})
  455. set(RC_VERSION_STRING ${_version})
  456. set(RC_DESCRIPTION ${_desc})
  457. set(RC_COPYRIGHT ${_copyright})
  458. set(RC_COMMENTS ${_comments})
  459. set(RC_COMPANY ${_company})
  460. set(RC_INTERNAL_NAME ${_internal_name})
  461. set(RC_TRADEMARK ${_trademark})
  462. set(RC_ORIGINAL_FILENAME ${_original_filename})
  463. set(_file_type)
  464. if(_type STREQUAL "EXECUTABLE")
  465. set(_file_type "VFT_APP")
  466. else()
  467. set(_file_type "VFT_DLL")
  468. endif()
  469. set(RC_FILE_TYPE ${_file_type})
  470. set(_icons)
  471. if(FUNC_ICONS)
  472. set(_index 1)
  473. foreach(_icon IN LISTS FUNC_ICONS)
  474. get_filename_component(_icon_path ${_icon} ABSOLUTE)
  475. string(APPEND _icons "IDI_ICON${_index} ICON \"${_icon_path}\"\n")
  476. math(EXPR _index "${_index} +1")
  477. endforeach()
  478. endif()
  479. set(RC_ICONS ${_icons})
  480. qm_set_value(_out_dir OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR})
  481. set(_out_path "${_out_dir}/${_target}_res.rc")
  482. configure_file("${QMSETUP_MODULES_DIR}/windows/WinResource2.rc.in" ${_out_path} @ONLY)
  483. target_sources(${_target} PRIVATE ${_out_path})
  484. foreach(_icon IN LISTS FUNC_ICONS)
  485. if(${_icon} IS_NEWER_THAN ${_out_path})
  486. file(TOUCH_NOCREATE ${_out_path})
  487. break()
  488. endif()
  489. endforeach()
  490. endfunction()
  491. #[[
  492. Attach windows manifest file to a target.
  493. qm_add_win_manifest(<target>
  494. [NAME name]
  495. [VERSION version]
  496. [DESCRIPTION desc]
  497. [OUTPUT_DIR dir]
  498. [UTF8]
  499. [ADMIN]
  500. )
  501. ]] #
  502. function(qm_add_win_manifest _target)
  503. if(NOT WIN32)
  504. return()
  505. endif()
  506. _qm_check_target_type_helper(${_target} _ "EXECUTABLE")
  507. set(options UTF8 ADMIN)
  508. set(oneValueArgs NAME VERSION DESCRIPTION OUTPUT_DIR)
  509. set(multiValueArgs)
  510. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  511. qm_set_value(_name FUNC_NAME ${_target})
  512. qm_set_value(_version FUNC_VERSION PROJECT_VERSION "0.0.0.0")
  513. qm_set_value(_desc FUNC_DESCRIPTION PROJECT_DESCRIPTION ${_name})
  514. qm_crop_version(_version ${_version} 4)
  515. set(MANIFEST_IDENTIFIER ${_name})
  516. set(MANIFEST_VERSION ${_version})
  517. set(MANIFEST_DESCRIPTION ${_desc})
  518. set(MANIFEST_UTF8)
  519. if(FUNC_UTF8)
  520. set(MANIFEST_UTF8 "<activeCodePage xmlns=\"http://schemas.microsoft.com/SMI/2019/WindowsSettings\">UTF-8</activeCodePage>")
  521. endif()
  522. if(FUNC_ADMIN)
  523. set(MANIFEST_PRIVILEGES "<requestedExecutionLevel level=\"requireAdministrator\" uiAccess=\"false\" />")
  524. else()
  525. set(MANIFEST_PRIVILEGES "<requestedExecutionLevel level=\"asInvoker\" uiAccess=\"false\" />")
  526. endif()
  527. qm_set_value(_out_dir OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR})
  528. set(_out_path "${_out_dir}/${_target}_manifest.exe.manifest")
  529. configure_file("${QMSETUP_MODULES_DIR}/windows/WinManifest.manifest.in" ${_out_path} @ONLY)
  530. if(MSVC)
  531. if(CMAKE_GENERATOR MATCHES "Visual Studio")
  532. # Visual Studio
  533. target_link_options(${_target} PRIVATE "/manifest" "/manifestinput:${_out_path}")
  534. # The manifest file contains a UAC field, we should prevent Ninja from embedding the
  535. # automatically generated UAC field
  536. target_link_options(${_target} PRIVATE "/manifest" "/manifestuac:no")
  537. else() # Ninja
  538. # https://cmake.org/cmake/help/latest/release/3.4.html#other
  539. # CMake learned to honor *.manifest source files with MSVC tools. Manifest files named as sources
  540. # of .exe and .dll targets will be merged with linker-generated manifests and embedded in the binary.
  541. # NOTE: CMake will automatically generate a default manifest file and embed it into the binary file
  542. # when we are using MSVC toolchain, so it will conflict with the one we embed in the RC file. So in
  543. # this case, we just follow the CMake documentation, add the manifest file into the sources and let
  544. # CMake handle it. What's more, CMake can automatically merge all manifest files so we can actually
  545. # add multiple manifest files, eg. each manifest file contains a separate section.
  546. target_sources(${_target} PRIVATE ${_out_path})
  547. target_link_options(${_target} PRIVATE "/manifestuac:no")
  548. endif()
  549. else()
  550. # For non-MSVC toolchains we can only embed the manifest file into the RC file, sadly we have
  551. # to merge all manifest files into one by ourself if we have multiple of them, but luckily
  552. # CMake won't embed a default one so we don't need to worry about resource conflicts.
  553. set(_manifest_rc ${_out_path}.rc)
  554. file(WRITE ${_manifest_rc} "#include <windows.h>\n\n1 RT_MANIFEST \"${_out_path}\"")
  555. target_sources(${_target} PRIVATE ${_manifest_rc})
  556. endif()
  557. endfunction()
  558. #[[
  559. Add Mac bundle info.
  560. qm_add_mac_bundle(<target>
  561. [NAME <name>]
  562. [VERSION <version>]
  563. [DESCRIPTION <desc>]
  564. [COPYRIGHT <copyright>]
  565. [ICON <file>]
  566. )
  567. ]] #
  568. function(qm_add_mac_bundle _target)
  569. if(NOT APPLE)
  570. return()
  571. endif()
  572. _qm_check_target_type_helper(${_target} _ "EXECUTABLE")
  573. set(options)
  574. set(oneValueArgs NAME VERSION DESCRIPTION COPYRIGHT ICON)
  575. set(multiValueArgs)
  576. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  577. qm_set_value(_app_name FUNC_NAME ${_target})
  578. qm_set_value(_app_version FUNC_VERSION PROJECT_VERSION "0.0.0.0")
  579. qm_set_value(_app_desc FUNC_DESCRIPTION PROJECT_DESCRIPTION ${_app_name})
  580. qm_set_value(_app_copyright FUNC_COPYRIGHT ${_app_name})
  581. qm_parse_version(_app_version ${_app_version})
  582. # configure mac plist
  583. set_target_properties(${_target} PROPERTIES
  584. MACOSX_BUNDLE TRUE
  585. MACOSX_BUNDLE_BUNDLE_NAME ${_app_name}
  586. MACOSX_BUNDLE_EXECUTABLE_NAME ${_app_name}
  587. MACOSX_BUNDLE_INFO_STRING ${_app_desc}
  588. MACOSX_BUNDLE_GUI_IDENTIFIER ${_app_name}
  589. MACOSX_BUNDLE_BUNDLE_VERSION ${_app_version}
  590. MACOSX_BUNDLE_SHORT_VERSION_STRING ${_app_version_1}.${_app_version_2}
  591. MACOSX_BUNDLE_COPYRIGHT ${_app_copyright}
  592. )
  593. if(FUNC_ICON)
  594. # And this part tells CMake where to find and install the file itself
  595. set_source_files_properties(${FUNC_ICON} PROPERTIES
  596. MACOSX_PACKAGE_LOCATION "Resources"
  597. )
  598. # NOTE: Don't include the path in MACOSX_BUNDLE_ICON_FILE -- this is
  599. # the property added to Info.plist
  600. get_filename_component(_icns_name ${FUNC_ICON} NAME)
  601. # configure mac plist
  602. set_target_properties(${_target} PROPERTIES
  603. MACOSX_BUNDLE_ICON_FILE ${_icns_name}
  604. )
  605. # ICNS icon MUST be added to executable's sources list, for some reason
  606. # Only apple can do
  607. target_sources(${_target} PRIVATE ${FUNC_ICON})
  608. endif()
  609. endfunction()
  610. #[[
  611. Generate Windows shortcut after building target.
  612. qm_create_win_shortcut(<target> <dir>
  613. [OUTPUT_NAME <name]
  614. )
  615. ]] #
  616. function(qm_create_win_shortcut _target _dir)
  617. if(NOT WIN32)
  618. return()
  619. endif()
  620. set(options)
  621. set(oneValueArgs OUTPUT_NAME)
  622. set(multiValueArgs)
  623. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  624. qm_set_value(_output_name FUNC_OUTPUT_NAME $<TARGET_FILE_BASE_NAME:${_target}>)
  625. set(_vbs_name ${CMAKE_CURRENT_BINARY_DIR}/${_target}_shortcut_$<CONFIG>.vbs)
  626. set(_vbs_temp ${_vbs_name}.in)
  627. set(_lnk_path "${_dir}/${_output_name}.lnk")
  628. set(SHORTCUT_PATH ${_lnk_path})
  629. set(SHORTCUT_TARGET_PATH $<TARGET_FILE:${_target}>)
  630. set(SHORTCUT_WORKING_DIRECOTRY $<TARGET_FILE_DIR:${_target}>)
  631. set(SHORTCUT_DESCRIPTION $<TARGET_FILE_BASE_NAME:${_target}>)
  632. set(SHORTCUT_ICON_LOCATION $<TARGET_FILE:${_target}>)
  633. configure_file(
  634. "${QMSETUP_MODULES_DIR}/windows/WinCreateShortcut.vbs.in"
  635. ${_vbs_temp}
  636. @ONLY
  637. )
  638. file(GENERATE OUTPUT ${_vbs_name} INPUT ${_vbs_temp})
  639. add_custom_command(
  640. TARGET ${_target} POST_BUILD
  641. COMMAND cscript ${_vbs_name} ${QMSETUP_IGNORE_STDOUT}
  642. BYPRODUCTS ${_lnk_path}
  643. VERBATIM
  644. )
  645. endfunction()
  646. #[[
  647. Collect targets of given types recursively in a directory.
  648. qm_collect_targets(<list> [DIRECTORY directory]
  649. [EXECUTABLE] [SHARED] [STATIC] [INTERFACE] [UTILITY])
  650. If one or more types are specified, return targets matching the types.
  651. If no type is specified, return all targets.
  652. ]] #
  653. function(qm_collect_targets _var)
  654. set(options EXECUTABLE SHARED STATIC INTERFACE UTILITY)
  655. set(oneValueArgs DIR)
  656. set(multiValueArgs)
  657. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  658. if(FUNC_DIRECTORY)
  659. set(_dir ${FUNC_DIRECTORY})
  660. else()
  661. set(_dir ${CMAKE_CURRENT_SOURCE_DIR})
  662. endif()
  663. set(_tmp_targets)
  664. macro(_get_targets_recursive _targets _dir)
  665. get_property(_subdirs DIRECTORY ${_dir} PROPERTY SUBDIRECTORIES)
  666. foreach(_subdir IN LISTS _subdirs)
  667. _get_targets_recursive(${_targets} ${_subdir})
  668. endforeach()
  669. get_property(_current_targets DIRECTORY ${_dir} PROPERTY BUILDSYSTEM_TARGETS)
  670. list(APPEND ${_targets} ${_current_targets})
  671. endmacro()
  672. # Get targets
  673. _get_targets_recursive(_tmp_targets ${_dir})
  674. set(_targets)
  675. if(NOT FUNC_EXECUTABLE AND NOT FUNC_SHARED AND NOT FUNC_STATIC AND NOT FUNC_INTERFACE AND NOT FUNC_UTILITY)
  676. set(_targets ${_tmp_targets})
  677. else()
  678. # Filter targets
  679. foreach(_item IN LISTS _tmp_targets)
  680. get_target_property(_type ${_item} TYPE)
  681. if(_type STREQUAL "EXECUTABLE")
  682. if(FUNC_EXECUTABLE)
  683. list(APPEND _targets ${_item})
  684. endif()
  685. elseif(_type STREQUAL "SHARED_LIBRARY")
  686. if(FUNC_SHARED)
  687. list(APPEND _targets ${_item})
  688. endif()
  689. elseif(_type STREQUAL "STATIC_LIBRARY")
  690. if(FUNC_STATIC)
  691. list(APPEND _targets ${_item})
  692. endif()
  693. elseif(_type STREQUAL "INTERFACE_LIBRARY")
  694. if(FUNC_INTERFACE)
  695. list(APPEND _targets ${_item})
  696. endif()
  697. elseif(_type STREQUAL "UTILITY")
  698. if(FUNC_UTILITY)
  699. list(APPEND _targets ${_item})
  700. endif()
  701. endif()
  702. endforeach()
  703. endif()
  704. set(${_var} ${_targets} PARENT_SCOPE)
  705. endfunction()
  706. #[[
  707. Get subdirectories' names or paths.
  708. qm_get_subdirs(<list>
  709. [DIRECTORY dir]
  710. [EXCLUDE names...]
  711. [REGEX_INCLUDE exps...]
  712. [REGEX_EXLCUDE exps...]
  713. [RELATIVE path]
  714. [ABSOLUTE]
  715. )
  716. If `DIRECTORY` is not specified, consider `CMAKE_CURRENT_SOURCE_DIR`.
  717. If `RELATIVE` is specified, return paths evaluated as a relative path to it.
  718. If `ABSOLUTE` is specified, return absolute paths.
  719. If neither of them is specified, return names.
  720. ]] #
  721. function(qm_get_subdirs _var)
  722. set(options ABSOLUTE)
  723. set(oneValueArgs DIRECTORY RELATIVE)
  724. set(multiValueArgs EXCLUDE REGEX_EXLCUDE)
  725. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  726. if(FUNC_DIRECTORY)
  727. get_filename_component(_dir ${FUNC_DIRECTORY} ABSOLUTE)
  728. else()
  729. set(_dir ${CMAKE_CURRENT_SOURCE_DIR})
  730. endif()
  731. file(GLOB _entries LIST_DIRECTORIES true RELATIVE ${_dir} "${_dir}/*")
  732. if(FUNC_EXCLUDE)
  733. foreach(_exclude_dir IN LISTS FUNC_EXCLUDE)
  734. list(REMOVE_ITEM _entries ${_exclude_dir})
  735. endforeach()
  736. endif()
  737. if(FUNC_REGEX_INCLUDE)
  738. foreach(_exp IN LISTS FUNC_REGEX_INCLUDE)
  739. list(FILTER _entries INCLUDE REGEX ${_exp})
  740. endforeach()
  741. endif()
  742. if(FUNC_REGEX_EXCLUDE)
  743. foreach(_exp IN LISTS FUNC_REGEX_EXCLUDE)
  744. list(FILTER _entries EXCLUDE REGEX ${_exp})
  745. endforeach()
  746. endif()
  747. set(_res)
  748. if(FUNC_RELATIVE)
  749. get_filename_component(_relative ${FUNC_RELATIVE} ABSOLUTE)
  750. else()
  751. set(_relative)
  752. endif()
  753. foreach(_sub IN LISTS _entries)
  754. if(IS_DIRECTORY ${_dir}/${_sub})
  755. if(FUNC_ABSOLUTE)
  756. list(APPEND _res ${_dir}/${_sub})
  757. elseif(_relative)
  758. file(RELATIVE_PATH _rel_path ${_relative} ${_dir}/${_sub})
  759. list(APPEND _res ${_rel_path})
  760. else()
  761. list(APPEND _res ${_sub})
  762. endif()
  763. endif()
  764. endforeach()
  765. set(${_var} ${_res} PARENT_SCOPE)
  766. endfunction()
  767. #[[
  768. Basic template to install a CMake project.
  769. qm_basic_install(
  770. [NAME <name>]
  771. [VERSION <version>]
  772. [COMPATIBILITY <compatibility>]
  773. [INSTALL_DIR <dir>]
  774. [CONFIG_TEMPLATE <file>]
  775. [NAMESPACE <namespace>]
  776. [EXPORT <sets...>]
  777. [WRITE_VERSION_OPTIONS <options...>]
  778. [WRITE_CONFIG_OPTIONS <options...>]
  779. )
  780. Include `GNUInstallDirs`, `CMakePackageConfigHelpers` before calling this function.
  781. ]] #
  782. function(qm_basic_install)
  783. set(options)
  784. set(oneValueArgs NAME VERSION COMPATIBILITY INSTALL_DIR CONFIG_TEMPLATE NAMESPACE)
  785. set(multiValueArgs WRITE_VERSION_OPTIONS WRITE_CONFIG_OPTIONS EXPORT)
  786. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  787. qm_set_value(_lib_dir CMAKE_INSTALL_LIBDIR lib)
  788. qm_set_value(_name FUNC_NAME PROJECT_NAME "unknown")
  789. qm_set_value(_version FUNC_VERSION PROJECT_VERSION "0.0.0.0")
  790. qm_set_value(_compatibility FUNC_COMPATIBILITY AnyNewerVersion)
  791. qm_set_value(_install_dir FUNC_INSTALL_DIR "${_lib_dir}/cmake/${_name}")
  792. qm_set_value(_config_template FUNC_CONFIG_TEMPLATE "${CMAKE_CURRENT_LIST_DIR}/${_name}Config.cmake.in")
  793. set(_namespace)
  794. if(FUNC_NAMESPACE)
  795. set(_namespace NAMESPACE ${FUNC_NAMESPACE})
  796. endif()
  797. set(_export_files)
  798. # Install cmake targets files
  799. foreach(_item IN LISTS FUNC_EXPORT)
  800. install(EXPORT ${_item}
  801. FILE "${_item}.cmake"
  802. DESTINATION ${_install_dir}
  803. ${_namespace}
  804. )
  805. list(APPEND _export_files "${_item}.cmake")
  806. endforeach()
  807. # Prepare config template file
  808. get_filename_component(_config_template ${_config_template} ABSOLUTE)
  809. if(NOT EXISTS ${_config_template})
  810. set(_config_content "@PACKAGE_INIT@\n\n")
  811. foreach(_item IN LISTS _export_files)
  812. string(APPEND _config_content "include(\"\${CMAKE_CURRENT_LIST_DIR}/${_item}\")\n")
  813. endforeach()
  814. set(_config_template "${CMAKE_CURRENT_BINARY_DIR}/${_name}Config.cmake.in")
  815. file(WRITE ${_config_template} ${_config_content})
  816. endif()
  817. # Add version file
  818. write_basic_package_version_file(
  819. "${CMAKE_CURRENT_BINARY_DIR}/${_name}ConfigVersion.cmake"
  820. VERSION ${_version}
  821. COMPATIBILITY ${_compatibility}
  822. ${FUNC_WRITE_VERSION_OPTIONS}
  823. )
  824. # Add configuration file
  825. configure_package_config_file(
  826. ${_config_template}
  827. "${CMAKE_CURRENT_BINARY_DIR}/${_name}Config.cmake"
  828. INSTALL_DESTINATION ${_install_dir}
  829. ${FUNC_WRITE_CONFIG_OPTIONS}
  830. )
  831. # Install cmake files
  832. install(FILES
  833. "${CMAKE_CURRENT_BINARY_DIR}/${_name}Config.cmake"
  834. "${CMAKE_CURRENT_BINARY_DIR}/${_name}ConfigVersion.cmake"
  835. DESTINATION ${_install_dir}
  836. )
  837. endfunction()
  838. # ----------------------------------
  839. # Private functions
  840. # ----------------------------------
  841. macro(_qm_check_target_type_helper _target _type)
  842. set(_tmp_target_type_list ${ARGN})
  843. get_target_property(_tmp_target_type ${_target} TYPE)
  844. if(NOT "${_tmp_target_type}" IN_LIST _tmp_target_type_list)
  845. return()
  846. endif()
  847. set(${_type} ${_tmp_target_type})
  848. unset(_tmp_target_type)
  849. unset(_tmp_target_type_list)
  850. endmacro()
  851. function(_qm_resolve_file_helper _dirs _out)
  852. set(_res)
  853. foreach(_item ${_dirs})
  854. if(_item STREQUAL "*")
  855. set(_cur_dir ".")
  856. file(GLOB _files LIST_DIRECTORIES true "*")
  857. elseif(_item STREQUAL "**")
  858. set(_cur_dir ".")
  859. file(GLOB_RECURSE _files LIST_DIRECTORIES true "*")
  860. elseif(_item MATCHES "(.+)[/\\]\\*$")
  861. set(_cur_dir ${CMAKE_MATCH_1})
  862. file(GLOB _files LIST_DIRECTORIES true "${_cur_dir}/*")
  863. elseif(_item MATCHES "(.+)[/\\]\\*\\*$")
  864. set(_cur_dir ${CMAKE_MATCH_1})
  865. file(GLOB_RECURSE _files LIST_DIRECTORIES true "${_cur_dir}/*")
  866. else()
  867. get_filename_component(_item ${_item} ABSOLUTE)
  868. list(APPEND _res ${_item})
  869. continue()
  870. endif()
  871. get_filename_component(_cur_dir ${_cur_dir} ABSOLUTE)
  872. list(APPEND _res ${_cur_dir})
  873. foreach(_item IN LISTS _files)
  874. get_filename_component(_item ${_item} ABSOLUTE)
  875. list(APPEND _res ${_item})
  876. endforeach()
  877. endforeach()
  878. set(${_out} ${_res} PARENT_SCOPE)
  879. endfunction()
  880. function(_qm_resolve_dir_helper _dirs _out)
  881. set(_files)
  882. _qm_resolve_file_helper("${_dirs}" _files)
  883. foreach(_item IN LISTS _files)
  884. if(IS_DIRECTORY ${_item})
  885. list(APPEND _res ${_item})
  886. endif()
  887. endforeach()
  888. set(${_out} ${_res} PARENT_SCOPE)
  889. endfunction()