QMSetupAPI.cmake 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005
  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...>)
  171. #]]
  172. macro(qm_find_qt)
  173. foreach(_module ${ARGN})
  174. if(NOT QT_VERSION_MAJOR)
  175. find_package(QT NAMES ${QMSETUP_FIND_QT_ORDER} COMPONENTS ${_module} REQUIRED)
  176. endif()
  177. if(NOT TARGET Qt${QT_VERSION_MAJOR}::${_module})
  178. find_package(Qt${QT_VERSION_MAJOR} COMPONENTS ${_module} REQUIRED)
  179. endif()
  180. endforeach()
  181. endmacro()
  182. #[[
  183. Link Qt libraries. Don't wrap it in any functions.
  184. qm_link_qt(<target> <scope> <modules...>)
  185. #]]
  186. macro(qm_link_qt _target _scope)
  187. foreach(_module ${ARGN})
  188. qm_find_qt(${_module})
  189. target_link_libraries(${_target} ${_scope} Qt${QT_VERSION_MAJOR}::${_module})
  190. endforeach()
  191. endmacro()
  192. #[[
  193. Include Qt private header directories. Don't wrap it in any functions.
  194. qm_include_qt_private(<target> <scope> <modules...>)
  195. #]]
  196. macro(qm_include_qt_private _target _scope)
  197. foreach(_module ${ARGN})
  198. qm_find_qt(${_module})
  199. target_include_directories(${_target} ${_scope} ${Qt${QT_VERSION_MAJOR}${_module}_PRIVATE_INCLUDE_DIRS})
  200. endforeach()
  201. endmacro()
  202. #[[
  203. Helper to set or append all kinds of attributes to a target. Don't wrap it in any functions.
  204. qm_configure_target(<target>
  205. [SOURCES <files>]
  206. [LINKS <libs>]
  207. [LINKS_PRIVATE <libs>]
  208. [INCLUDE <dirs>]
  209. [INCLUDE_PRIVATE <dirs>]
  210. [LINKDIR <dirs>]
  211. [LINKDIR_PRIVATE <dirs>]
  212. [DEFINES <defs>]
  213. [DEFINES_PRIVATE <defs>]
  214. [FEATURES <features>]
  215. [FEATURES_PRIVATE <features>]
  216. [CCFLAGS <flags>]
  217. [CCFLAGS_PUBLIC <flags>]
  218. [LDFLAGS <flags>]
  219. [LDFLAGS_PUBLIC <flags>]
  220. [QT_LINKS <modules>]
  221. [QT_LINKS_PRIVATE <modules>]
  222. [QT_INCLUDE_PRIVATE <modules>]
  223. [SKIP_AUTOMOC <dir/file...>]
  224. )
  225. INCLUDE/LINKDIR: `dir/*` will be expanded to all subdirectories
  226. `dir/**` will be expanded to all descendent directories recursively
  227. ]] #
  228. macro(qm_configure_target _target)
  229. set(options)
  230. set(oneValueArgs)
  231. set(multiValueArgs
  232. SOURCES
  233. LINKS LINKS_PRIVATE
  234. INCLUDE INCLUDE_PRIVATE
  235. LINKDIR LINKDIR_PRIVATE
  236. DEFINES DEFINES_PRIVATE
  237. FEATURES FEATURES_PRIVATE
  238. CCFLAGS CCFLAGS_PUBLIC
  239. LDFLAGS LDFLAGS_PUBLIC
  240. QT_LINKS QT_LINKS_PRIVATE QT_INCLUDE_PRIVATE
  241. SKIP_AUTOMOC
  242. )
  243. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  244. macro(_resolve_dir_helper _dirs _out)
  245. set(${_out})
  246. foreach(_item IN LISTS ${_dirs})
  247. if(_item STREQUAL "*")
  248. set(_cur_dir ".")
  249. file(GLOB _subdirs LIST_DIRECTORIES true "*")
  250. elseif(_item STREQUAL "**")
  251. set(_cur_dir ".")
  252. file(GLOB_RECURSE _subdirs LIST_DIRECTORIES true "*")
  253. elseif(_item MATCHES "(.+)/\\*$")
  254. set(_cur_dir ${CMAKE_MATCH_1})
  255. file(GLOB _subdirs LIST_DIRECTORIES true "${_cur_dir}/*")
  256. elseif(_item MATCHES "(.+)/\\*\\*$")
  257. set(_cur_dir ${CMAKE_MATCH_1})
  258. file(GLOB_RECURSE _subdirs LIST_DIRECTORIES true "${_cur_dir}/*")
  259. else()
  260. list(APPEND ${_out} ${_item})
  261. continue()
  262. endif()
  263. list(APPEND ${_out} ${_cur_dir})
  264. foreach(_subdir IN LISTS _subdirs)
  265. if(IS_DIRECTORY ${_subdir})
  266. get_filename_component(_subdir ${_subdir} ABSOLUTE)
  267. list(APPEND ${_out} ${_subdir})
  268. endif()
  269. endforeach()
  270. endforeach()
  271. endmacro()
  272. target_sources(${_target} PRIVATE ${FUNC_SOURCES})
  273. target_link_libraries(${_target} PUBLIC ${FUNC_LINKS})
  274. target_link_libraries(${_target} PRIVATE ${FUNC_LINKS_PRIVATE})
  275. if(FUNC_INCLUDE)
  276. _resolve_dir_helper(FUNC_INCLUDE _temp_dirs)
  277. target_include_directories(${_target} PUBLIC ${_temp_dirs})
  278. unset(_temp_dirs)
  279. endif()
  280. if(FUNC_INCLUDE_PRIVATE)
  281. _resolve_dir_helper(FUNC_INCLUDE_PRIVATE _temp_dirs)
  282. target_include_directories(${_target} PRIVATE ${_temp_dirs})
  283. unset(_temp_dirs)
  284. endif()
  285. if(FUNC_LINKDIR)
  286. _resolve_dir_helper(FUNC_LINKDIR _temp_dirs)
  287. target_link_directories(${_target} PUBLIC ${_temp_dirs})
  288. unset(_temp_dirs)
  289. endif()
  290. if(FUNC_LINKDIR_PRIVATE)
  291. _resolve_dir_helper(FUNC_LINKDIR_PRIVATE _temp_dirs)
  292. target_link_directories(${_target} PRIVATE ${_temp_dirs})
  293. unset(_temp_dirs)
  294. endif()
  295. target_compile_definitions(${_target} PUBLIC ${FUNC_DEFINES})
  296. target_compile_definitions(${_target} PRIVATE ${FUNC_DEFINES_PRIVATE})
  297. target_compile_features(${_target} PUBLIC ${FUNC_FEATURES})
  298. target_compile_features(${_target} PRIVATE ${FUNC_FEATURES_PRIVATE})
  299. target_compile_options(${_target} PUBLIC ${FUNC_CCFLAGS_PUBLIC})
  300. target_compile_options(${_target} PRIVATE ${FUNC_CCFLAGS})
  301. target_link_options(${_target} PUBLIC ${FUNC_LDFLAGS_PUBLIC})
  302. target_link_options(${_target} PRIVATE ${FUNC_LDFLAGS})
  303. qm_link_qt(${_target} PUBLIC ${FUNC_QT_LINKS})
  304. qm_link_qt(${_target} PRIVATE ${FUNC_QT_LINKS_PRIVATE})
  305. qm_include_qt_private(${_target} PRIVATE ${FUNC_QT_INCLUDE_PRIVATE})
  306. if(FUNC_SKIP_AUTOMOC)
  307. _resolve_dir_helper(FUNC_SKIP_AUTOMOC _temp_files)
  308. qm_skip_automoc(${_temp_files})
  309. unset(_temp_files)
  310. endif()
  311. endmacro()
  312. #[[
  313. Helper to define export macros.
  314. qm_export_defines(<target>
  315. [PREFIX <prefix>]
  316. [STATIC <token>]
  317. [LIBRARY <token>]
  318. )
  319. ]] #
  320. function(qm_export_defines _target)
  321. set(options)
  322. set(oneValueArgs PREFIX STATIC LIBRARY)
  323. set(multiValueArgs)
  324. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  325. if(NOT FUNC_PREFIX)
  326. string(TOUPPER ${_target} _prefix)
  327. else()
  328. set(_prefix ${FUNC_PREFIX})
  329. endif()
  330. qm_set_value(_static_macro FUNC_STATIC ${_prefix}_STATIC)
  331. qm_set_value(_library_macro FUNC_LIBRARY ${_prefix}_LIBRARY)
  332. get_target_property(_type ${_target} TYPE)
  333. if(_type STREQUAL "INTERFACE_LIBRARY")
  334. return()
  335. endif()
  336. if(_type STREQUAL "STATIC_LIBRARY")
  337. target_compile_definitions(${_target} PUBLIC ${_static_macro})
  338. endif()
  339. target_compile_definitions(${_target} PRIVATE ${_library_macro})
  340. endfunction()
  341. #[[
  342. Attach windows RC file to a target.
  343. qm_add_win_rc(<target>
  344. [NAME name]
  345. [VERSION version]
  346. [DESCRIPTION desc]
  347. [COPYRIGHT copyright]
  348. [ICON ico]
  349. [OUTPUT_DIR dir]
  350. )
  351. ]] #
  352. function(qm_add_win_rc _target)
  353. if(NOT WIN32)
  354. return()
  355. endif()
  356. _qm_check_target_type_helper(${_target} _ "EXECUTABLE" "SHARED_LIBRARY")
  357. set(options)
  358. set(oneValueArgs NAME VERSION DESCRIPTION COPYRIGHT ICON OUTPUT_DIR)
  359. set(multiValueArgs)
  360. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  361. qm_set_value(_name FUNC_NAME ${_target})
  362. qm_set_value(_version FUNC_VERSION PROJECT_VERSION "0.0.0.0")
  363. qm_set_value(_desc FUNC_DESCRIPTION PROJECT_DESCRIPTION ${_name})
  364. qm_set_value(_copyright FUNC_COPYRIGHT ${_name})
  365. qm_parse_version(_ver ${_version})
  366. set(RC_VERSION ${_ver_1},${_ver_2},${_ver_3},${_ver_4})
  367. set(RC_APPLICATION_NAME ${_name})
  368. set(RC_VERSION_STRING ${_version})
  369. set(RC_DESCRIPTION ${_desc})
  370. set(RC_COPYRIGHT ${_copyright})
  371. if(NOT FUNC_ICON)
  372. set(RC_ICON_COMMENT "//")
  373. set(RC_ICON_PATH)
  374. else()
  375. get_filename_component(RC_ICON_PATH ${FUNC_ICON} ABSOLUTE)
  376. endif()
  377. qm_set_value(_out_dir OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR})
  378. set(_out_path "${_out_dir}/${_target}_res.rc")
  379. configure_file("${QMSETUP_MODULES_DIR}/windows/WinResource.rc.in" ${_out_path} @ONLY)
  380. target_sources(${_target} PRIVATE ${_out_path})
  381. if(FUNC_ICON)
  382. if(${FUNC_ICON} IS_NEWER_THAN ${_out_path})
  383. file(TOUCH_NOCREATE ${_out_path})
  384. endif()
  385. endif()
  386. endfunction()
  387. #[[
  388. Attach windows RC file to a target, enhanced edition.
  389. qm_add_win_rc_enhanced(<target>
  390. [NAME name]
  391. [VERSION version]
  392. [DESCRIPTION description]
  393. [COPYRIGHT copyright]
  394. [COMMENTS comments]
  395. [COMPANY company]
  396. [INTERNAL_NAME internal name]
  397. [TRADEMARK trademark]
  398. [ORIGINAL_FILENAME original filename]
  399. [ICONS icon file paths]
  400. [OUTPUT_DIR dir]
  401. )
  402. ]] #
  403. function(qm_add_win_rc_enhanced _target)
  404. if(NOT WIN32)
  405. return()
  406. endif()
  407. _qm_check_target_type_helper(${_target} _type "EXECUTABLE" "SHARED_LIBRARY")
  408. set(options)
  409. set(oneValueArgs
  410. NAME VERSION DESCRIPTION COPYRIGHT COMMENTS COMPANY
  411. INTERNAL_NAME TRADEMARK ORIGINAL_FILENAME OUTPUT_DIR
  412. )
  413. set(multiValueArgs ICONS)
  414. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  415. qm_set_value(_name FUNC_NAME ${_target})
  416. qm_set_value(_version FUNC_VERSION PROJECT_VERSION "0.0.0.0")
  417. qm_set_value(_desc FUNC_DESCRIPTION PROJECT_DESCRIPTION ${_name})
  418. qm_set_value(_copyright FUNC_COPYRIGHT ${_name})
  419. qm_set_value(_comments FUNC_COMMENTS "")
  420. qm_set_value(_company FUNC_COMPANY "")
  421. qm_set_value(_internal_name FUNC_INTERNAL_NAME "")
  422. qm_set_value(_trademark FUNC_TRADEMARK "")
  423. qm_set_value(_original_filename FUNC_ORIGINAL_FILENAME "")
  424. qm_parse_version(_ver ${_version})
  425. set(RC_VERSION ${_ver_1},${_ver_2},${_ver_3},${_ver_4})
  426. set(RC_APPLICATION_NAME ${_name})
  427. set(RC_VERSION_STRING ${_version})
  428. set(RC_DESCRIPTION ${_desc})
  429. set(RC_COPYRIGHT ${_copyright})
  430. set(RC_COMMENTS ${_comments})
  431. set(RC_COMPANY ${_company})
  432. set(RC_INTERNAL_NAME ${_internal_name})
  433. set(RC_TRADEMARK ${_trademark})
  434. set(RC_ORIGINAL_FILENAME ${_original_filename})
  435. set(_file_type)
  436. if(_type STREQUAL "EXECUTABLE")
  437. set(_file_type "VFT_APP")
  438. else()
  439. set(_file_type "VFT_DLL")
  440. endif()
  441. set(RC_FILE_TYPE ${_file_type})
  442. set(_icons)
  443. if(FUNC_ICONS)
  444. set(_index 1)
  445. foreach(_icon IN LISTS FUNC_ICONS)
  446. get_filename_component(_icon_path ${_icon} ABSOLUTE)
  447. string(APPEND _icons "IDI_ICON${_index} ICON \"${_icon_path}\"\n")
  448. math(EXPR _index "${_index} +1")
  449. endforeach()
  450. endif()
  451. set(RC_ICONS ${_icons})
  452. qm_set_value(_out_dir OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR})
  453. set(_out_path "${_out_dir}/${_target}_res.rc")
  454. configure_file("${QMSETUP_MODULES_DIR}/windows/WinResource2.rc.in" ${_out_path} @ONLY)
  455. target_sources(${_target} PRIVATE ${_out_path})
  456. foreach(_icon IN LISTS FUNC_ICONS)
  457. if(${_icon} IS_NEWER_THAN ${_out_path})
  458. file(TOUCH_NOCREATE ${_out_path})
  459. break()
  460. endif()
  461. endforeach()
  462. endfunction()
  463. #[[
  464. Attach windows manifest file to a target.
  465. qm_add_win_manifest(<target>
  466. [NAME name]
  467. [VERSION version]
  468. [DESCRIPTION desc]
  469. [OUTPUT_DIR dir]
  470. [UTF8]
  471. [ADMIN]
  472. )
  473. ]] #
  474. function(qm_add_win_manifest _target)
  475. if(NOT WIN32)
  476. return()
  477. endif()
  478. _qm_check_target_type_helper(${_target} _ "EXECUTABLE")
  479. set(options UTF8 ADMIN)
  480. set(oneValueArgs NAME VERSION DESCRIPTION OUTPUT_DIR)
  481. set(multiValueArgs)
  482. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  483. qm_set_value(_name FUNC_NAME ${_target})
  484. qm_set_value(_version FUNC_VERSION PROJECT_VERSION "0.0.0.0")
  485. qm_set_value(_desc FUNC_DESCRIPTION PROJECT_DESCRIPTION ${_name})
  486. qm_crop_version(_version ${_version} 4)
  487. set(MANIFEST_IDENTIFIER ${_name})
  488. set(MANIFEST_VERSION ${_version})
  489. set(MANIFEST_DESCRIPTION ${_desc})
  490. set(MANIFEST_UTF8)
  491. if(FUNC_UTF8)
  492. set(MANIFEST_UTF8 "<activeCodePage xmlns=\"http://schemas.microsoft.com/SMI/2019/WindowsSettings\">UTF-8</activeCodePage>")
  493. endif()
  494. if(FUNC_ADMIN)
  495. set(MANIFEST_PRIVILEGES "<requestedExecutionLevel level=\"requireAdministrator\" uiAccess=\"false\" />")
  496. else()
  497. set(MANIFEST_PRIVILEGES "<requestedExecutionLevel level=\"asInvoker\" uiAccess=\"false\" />")
  498. endif()
  499. qm_set_value(_out_dir OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR})
  500. set(_out_path "${_out_dir}/${_target}_manifest.exe.manifest")
  501. configure_file("${QMSETUP_MODULES_DIR}/windows/WinManifest.manifest.in" ${_out_path} @ONLY)
  502. # https://cmake.org/cmake/help/latest/release/3.4.html#other
  503. # CMake learned to honor *.manifest source files with MSVC tools. Manifest files named as sources
  504. # of .exe and .dll targets will be merged with linker-generated manifests and embedded in the binary.
  505. # NOTE: CMake will automatically generate a default manifest file and embed it into the binary file
  506. # when we are using MSVC toolchain, so it will conflict with the one we embed in the RC file. So in
  507. # this case, we just follow the CMake documentation, add the manifest file into the sources and let
  508. # CMake handle it. What's more, CMake can automatically merge all manifest files so we can actually
  509. # add multiple manifest files, eg. each manifest file contains a separate section.
  510. if(MSVC)
  511. target_sources(${_target} PRIVATE ${_out_path})
  512. # The manifest file contains a UAC field, we should prevent MSVC from embedding the
  513. # automatically generated UAC field
  514. target_link_options(${_target} PRIVATE "/manifestuac:no")
  515. else()
  516. # For non-MSVC toolchains we can only embed the manifest file into the RC file, sadly we have
  517. # to merge all manifest files into one by ourself if we have multiple of them, but luckily
  518. # CMake won't embed a default one so we don't need to worry about resource conflicts.
  519. set(_manifest_rc ${_out_path}.rc)
  520. file(WRITE ${_manifest_rc} "#include <windows.h>\n\n1 RT_MANIFEST \"${_out_path}\"")
  521. target_sources(${_target} PRIVATE ${_manifest_rc})
  522. endif()
  523. endfunction()
  524. #[[
  525. Add Mac bundle info.
  526. qm_add_mac_bundle(<target>
  527. [NAME <name>]
  528. [VERSION <version>]
  529. [DESCRIPTION <desc>]
  530. [COPYRIGHT <copyright>]
  531. [ICON <file>]
  532. )
  533. ]] #
  534. function(qm_add_mac_bundle _target)
  535. if(NOT APPLE)
  536. return()
  537. endif()
  538. _qm_check_target_type_helper(${_target} _ "EXECUTABLE")
  539. set(options)
  540. set(oneValueArgs NAME VERSION DESCRIPTION COPYRIGHT ICON)
  541. set(multiValueArgs)
  542. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  543. qm_set_value(_app_name FUNC_NAME ${_target})
  544. qm_set_value(_app_version FUNC_VERSION PROJECT_VERSION "0.0.0.0")
  545. qm_set_value(_app_desc FUNC_DESCRIPTION PROJECT_DESCRIPTION ${_app_name})
  546. qm_set_value(_app_copyright FUNC_COPYRIGHT ${_app_name})
  547. qm_parse_version(_app_version ${_app_version})
  548. # configure mac plist
  549. set_target_properties(${_target} PROPERTIES
  550. MACOSX_BUNDLE TRUE
  551. MACOSX_BUNDLE_BUNDLE_NAME ${_app_name}
  552. MACOSX_BUNDLE_EXECUTABLE_NAME ${_app_name}
  553. MACOSX_BUNDLE_INFO_STRING ${_app_desc}
  554. MACOSX_BUNDLE_GUI_IDENTIFIER ${_app_name}
  555. MACOSX_BUNDLE_BUNDLE_VERSION ${_app_version}
  556. MACOSX_BUNDLE_SHORT_VERSION_STRING ${_app_version_1}.${_app_version_2}
  557. MACOSX_BUNDLE_COPYRIGHT ${_app_copyright}
  558. )
  559. if(FUNC_ICON)
  560. # And this part tells CMake where to find and install the file itself
  561. set_source_files_properties(${FUNC_ICON} PROPERTIES
  562. MACOSX_PACKAGE_LOCATION "Resources"
  563. )
  564. # NOTE: Don't include the path in MACOSX_BUNDLE_ICON_FILE -- this is
  565. # the property added to Info.plist
  566. get_filename_component(_icns_name ${FUNC_ICON} NAME)
  567. # configure mac plist
  568. set_target_properties(${_target} PROPERTIES
  569. MACOSX_BUNDLE_ICON_FILE ${_icns_name}
  570. )
  571. # ICNS icon MUST be added to executable's sources list, for some reason
  572. # Only apple can do
  573. target_sources(${_target} PRIVATE ${FUNC_ICON})
  574. endif()
  575. endfunction()
  576. #[[
  577. Generate Windows shortcut after building target.
  578. qm_create_win_shortcut(<target> <dir>
  579. [OUTPUT_NAME <name]
  580. )
  581. ]] #
  582. function(qm_create_win_shortcut _target _dir)
  583. if(NOT WIN32)
  584. return()
  585. endif()
  586. set(options)
  587. set(oneValueArgs OUTPUT_NAME)
  588. set(multiValueArgs)
  589. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  590. qm_set_value(_output_name FUNC_OUTPUT_NAME $<TARGET_FILE_BASE_NAME:${_target}>)
  591. set(_vbs_name ${CMAKE_CURRENT_BINARY_DIR}/${_target}_shortcut_$<CONFIG>.vbs)
  592. set(_vbs_temp ${_vbs_name}.in)
  593. set(_lnk_path "${_dir}/${_output_name}.lnk")
  594. set(SHORTCUT_PATH ${_lnk_path})
  595. set(SHORTCUT_TARGET_PATH $<TARGET_FILE:${_target}>)
  596. set(SHORTCUT_WORKING_DIRECOTRY $<TARGET_FILE_DIR:${_target}>)
  597. set(SHORTCUT_DESCRIPTION $<TARGET_FILE_BASE_NAME:${_target}>)
  598. set(SHORTCUT_ICON_LOCATION $<TARGET_FILE:${_target}>)
  599. configure_file(
  600. "${QMSETUP_MODULES_DIR}/windows/WinCreateShortcut.vbs.in"
  601. ${_vbs_temp}
  602. @ONLY
  603. )
  604. file(GENERATE OUTPUT ${_vbs_name} INPUT ${_vbs_temp})
  605. add_custom_command(
  606. TARGET ${_target} POST_BUILD
  607. COMMAND cscript ${_vbs_name} ${QMSETUP_IGNORE_STDOUT}
  608. BYPRODUCTS ${_lnk_path}
  609. VERBATIM
  610. )
  611. endfunction()
  612. #[[
  613. Collect targets of given types recursively in a directory.
  614. qm_collect_targets(<list> [DIRECTORY directory]
  615. [EXECUTABLE] [SHARED] [STATIC] [INTERFACE] [UTILITY])
  616. If one or more types are specified, return targets matching the types.
  617. If no type is specified, return all targets.
  618. ]] #
  619. function(qm_collect_targets _var)
  620. set(options EXECUTABLE SHARED STATIC INTERFACE UTILITY)
  621. set(oneValueArgs DIR)
  622. set(multiValueArgs)
  623. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  624. if(FUNC_DIRECTORY)
  625. set(_dir ${FUNC_DIRECTORY})
  626. else()
  627. set(_dir ${CMAKE_CURRENT_SOURCE_DIR})
  628. endif()
  629. set(_tmp_targets)
  630. macro(_get_targets_recursive _targets _dir)
  631. get_property(_subdirs DIRECTORY ${_dir} PROPERTY SUBDIRECTORIES)
  632. foreach(_subdir IN LISTS _subdirs)
  633. _get_targets_recursive(${_targets} ${_subdir})
  634. endforeach()
  635. get_property(_current_targets DIRECTORY ${_dir} PROPERTY BUILDSYSTEM_TARGETS)
  636. list(APPEND ${_targets} ${_current_targets})
  637. endmacro()
  638. # Get targets
  639. _get_targets_recursive(_tmp_targets ${_dir})
  640. set(_targets)
  641. if(NOT FUNC_EXECUTABLE AND NOT FUNC_SHARED AND NOT FUNC_STATIC AND NOT FUNC_INTERFACE AND NOT FUNC_UTILITY)
  642. set(_targets ${_tmp_targets})
  643. else()
  644. # Filter targets
  645. foreach(_item IN LISTS _tmp_targets)
  646. get_target_property(_type ${_item} TYPE)
  647. if(_type STREQUAL "EXECUTABLE")
  648. if(FUNC_EXECUTABLE)
  649. list(APPEND _targets ${_item})
  650. endif()
  651. elseif(_type STREQUAL "SHARED_LIBRARY")
  652. if(FUNC_SHARED)
  653. list(APPEND _targets ${_item})
  654. endif()
  655. elseif(_type STREQUAL "STATIC_LIBRARY")
  656. if(FUNC_STATIC)
  657. list(APPEND _targets ${_item})
  658. endif()
  659. elseif(_type STREQUAL "INTERFACE_LIBRARY")
  660. if(FUNC_INTERFACE)
  661. list(APPEND _targets ${_item})
  662. endif()
  663. elseif(_type STREQUAL "UTILITY")
  664. if(FUNC_UTILITY)
  665. list(APPEND _targets ${_item})
  666. endif()
  667. endif()
  668. endforeach()
  669. endif()
  670. set(${_var} ${_targets} PARENT_SCOPE)
  671. endfunction()
  672. #[[
  673. Get subdirectories' names or paths.
  674. qm_get_subdirs(<list>
  675. [DIRECTORY dir]
  676. [EXCLUDE names...]
  677. [REGEX_INCLUDE exps...]
  678. [REGEX_EXLCUDE exps...]
  679. [RELATIVE path]
  680. [ABSOLUTE]
  681. )
  682. If `DIRECTORY` is not specified, consider `CMAKE_CURRENT_SOURCE_DIR`.
  683. If `RELATIVE` is specified, return paths evaluated as a relative path to it.
  684. If `ABSOLUTE` is specified, return absolute paths.
  685. If neither of them is specified, return names.
  686. ]] #
  687. function(qm_get_subdirs _var)
  688. set(options ABSOLUTE)
  689. set(oneValueArgs DIRECTORY RELATIVE)
  690. set(multiValueArgs EXCLUDE REGEX_EXLCUDE)
  691. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  692. if(FUNC_DIRECTORY)
  693. get_filename_component(_dir ${FUNC_DIRECTORY} ABSOLUTE)
  694. else()
  695. set(_dir ${CMAKE_CURRENT_SOURCE_DIR})
  696. endif()
  697. file(GLOB _subdirs LIST_DIRECTORIES true RELATIVE ${_dir} "${_dir}/*")
  698. if(FUNC_EXCLUDE)
  699. foreach(_exclude_dir IN LISTS FUNC_EXCLUDE)
  700. list(REMOVE_ITEM _subdirs ${_exclude_dir})
  701. endforeach()
  702. endif()
  703. if(FUNC_REGEX_INCLUDE)
  704. foreach(_exp IN LISTS FUNC_REGEX_INCLUDE)
  705. list(FILTER _subdirs INCLUDE REGEX ${_exp})
  706. endforeach()
  707. endif()
  708. if(FUNC_REGEX_EXCLUDE)
  709. foreach(_exp IN LISTS FUNC_REGEX_EXCLUDE)
  710. list(FILTER _subdirs EXCLUDE REGEX ${_exp})
  711. endforeach()
  712. endif()
  713. set(_res)
  714. if(FUNC_RELATIVE)
  715. get_filename_component(_relative ${FUNC_RELATIVE} ABSOLUTE)
  716. else()
  717. set(_relative)
  718. endif()
  719. foreach(_sub IN LISTS _subdirs)
  720. if(IS_DIRECTORY ${_dir}/${_sub})
  721. if(FUNC_ABSOLUTE)
  722. list(APPEND _res ${_dir}/${_sub})
  723. elseif(_relative)
  724. file(RELATIVE_PATH _rel_path ${_relative} ${_dir}/${_sub})
  725. list(APPEND _res ${_rel_path})
  726. else()
  727. list(APPEND _res ${_sub})
  728. endif()
  729. endif()
  730. endforeach()
  731. set(${_var} ${_res} PARENT_SCOPE)
  732. endfunction()
  733. #[[
  734. Basic template to install a CMake project.
  735. qm_basic_install(
  736. [NAME <name>]
  737. [VERSION <version>]
  738. [COMPATIBILITY <compatibility>]
  739. [INSTALL_DIR <dir>]
  740. [CONFIG_TEMPLATE <file>]
  741. [NAMESPACE <namespace>]
  742. [EXPORT <sets...>]
  743. [WRITE_VERSION_OPTIONS <options...>]
  744. [WRITE_CONFIG_OPTIONS <options...>]
  745. )
  746. Include `GNUInstallDirs`, `CMakePackageConfigHelpers` before calling this function.
  747. ]] #
  748. function(qm_basic_install)
  749. set(options)
  750. set(oneValueArgs NAME VERSION COMPATIBILITY INSTALL_DIR CONFIG_TEMPLATE NAMESPACE)
  751. set(multiValueArgs WRITE_VERSION_OPTIONS WRITE_CONFIG_OPTIONS EXPORT)
  752. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  753. qm_set_value(_lib_dir CMAKE_INSTALL_LIBDIR lib)
  754. qm_set_value(_name FUNC_NAME PROJECT_NAME "unknown")
  755. qm_set_value(_version FUNC_VERSION PROJECT_VERSION "0.0.0.0")
  756. qm_set_value(_compatibility FUNC_COMPATIBILITY AnyNewerVersion)
  757. qm_set_value(_install_dir FUNC_INSTALL_DIR "${_lib_dir}/cmake/${_name}")
  758. qm_set_value(_config_template FUNC_CONFIG_TEMPLATE "${CMAKE_CURRENT_LIST_DIR}/${_name}Config.cmake.in")
  759. set(_namespace)
  760. if(FUNC_NAMESPACE)
  761. set(_namespace NAMESPACE ${FUNC_NAMESPACE})
  762. endif()
  763. set(_export_files)
  764. # Install cmake targets files
  765. foreach(_item IN LISTS FUNC_EXPORT)
  766. install(EXPORT ${_item}
  767. FILE "${_item}.cmake"
  768. DESTINATION ${_install_dir}
  769. ${_namespace}
  770. )
  771. list(APPEND _export_files "${_item}.cmake")
  772. endforeach()
  773. # Prepare config template file
  774. get_filename_component(_config_template ${_config_template} ABSOLUTE)
  775. if(NOT EXISTS ${_config_template})
  776. set(_config_content "@PACKAGE_INIT@\n\n")
  777. foreach(_item IN LISTS _export_files)
  778. string(APPEND _config_content "include(\"\${CMAKE_CURRENT_LIST_DIR}/${_item}\")\n")
  779. endforeach()
  780. set(_config_template "${CMAKE_CURRENT_BINARY_DIR}/${_name}Config.cmake.in")
  781. file(WRITE ${_config_template} ${_config_content})
  782. endif()
  783. # Add version file
  784. write_basic_package_version_file(
  785. "${CMAKE_CURRENT_BINARY_DIR}/${_name}ConfigVersion.cmake"
  786. VERSION ${_version}
  787. COMPATIBILITY ${_compatibility}
  788. ${FUNC_WRITE_VERSION_OPTIONS}
  789. )
  790. # Add configuration file
  791. configure_package_config_file(
  792. ${_config_template}
  793. "${CMAKE_CURRENT_BINARY_DIR}/${_name}Config.cmake"
  794. INSTALL_DESTINATION ${_install_dir}
  795. ${FUNC_WRITE_CONFIG_OPTIONS}
  796. )
  797. # Install cmake files
  798. install(FILES
  799. "${CMAKE_CURRENT_BINARY_DIR}/${_name}Config.cmake"
  800. "${CMAKE_CURRENT_BINARY_DIR}/${_name}ConfigVersion.cmake"
  801. DESTINATION ${_install_dir}
  802. )
  803. endfunction()
  804. # ----------------------------------
  805. # Private functions
  806. # ----------------------------------
  807. macro(_qm_check_target_type_helper _target _type)
  808. set(_tmp_target_type_list ${ARGN})
  809. get_target_property(_tmp_target_type ${_target} TYPE)
  810. if(NOT "${_tmp_target_type}" IN_LIST _tmp_target_type_list)
  811. return()
  812. endif()
  813. set(${_type} ${_tmp_target_type})
  814. unset(_tmp_target_type)
  815. unset(_tmp_target_type_list)
  816. endmacro()