Preprocess.cmake 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. #[[
  2. Warning: This module depends on `qmcorecmd` after installation.
  3. ]] #
  4. if(NOT QMSETUP_CORECMD_EXECUTABLE)
  5. message(FATAL_ERROR "QMSETUP_CORECMD_EXECUTABLE not defined. Add find_package(qmsetup) to CMake first.")
  6. endif()
  7. if(NOT DEFINED QMSETUP_DEFINITION_NUMERICAL)
  8. set(QMSETUP_DEFINITION_NUMERICAL off)
  9. endif()
  10. if(NOT DEFINED QMSETUP_DEFINITION_SCOPE)
  11. set(QMSETUP_DEFINITION_SCOPE)
  12. endif()
  13. if(NOT DEFINED QMSETUP_DEFINITION_PROPERTY)
  14. set(QMSETUP_DEFINITION_PROPERTY)
  15. endif()
  16. if(NOT DEFINED QMSETUP_SYNC_INCLUDE_STANDARD)
  17. set(QMSETUP_SYNC_INCLUDE_STANDARD on)
  18. endif()
  19. include_guard(DIRECTORY)
  20. #[[
  21. Generate indirect reference files for header files to make the include statements more orderly.
  22. The generated file has the same timestamp as the source file.
  23. qm_sync_include(<src> <dest>
  24. [STANDARD] [NO_STANDARD] [NO_ALL]
  25. [INCLUDE <expr> <sub> ...]
  26. [EXCLUDE <expr...>]
  27. [INSTALL_DIR <dir>]
  28. [FORCE] [VERBOSE]
  29. )
  30. STANDARD: Enable standard public-private pattern, can be forced to enable by enabling `QMSETUP_SYNC_INCLUDE_STANDARD`
  31. NO_STANDARD: Disable standard public-private pattern, enable it to override `QMSETUP_SYNC_INCLUDE_STANDARD`
  32. #]]
  33. function(qm_sync_include _src_dir _dest_dir)
  34. set(options FORCE VERBOSE NO_STANDARD NO_ALL)
  35. set(oneValueArgs INSTALL_DIR)
  36. set(multiValueArgs INCLUDE EXCLUDE)
  37. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  38. if(NOT IS_ABSOLUTE ${_src_dir})
  39. get_filename_component(_src_dir ${_src_dir} ABSOLUTE)
  40. else()
  41. string(REPLACE "\\" "/" _src_dir ${_src_dir})
  42. endif()
  43. if(NOT IS_ABSOLUTE ${_dest_dir})
  44. get_filename_component(_dest_dir ${_dest_dir} ABSOLUTE)
  45. else()
  46. string(REPLACE "\\" "/" _dest_dir ${_dest_dir})
  47. endif()
  48. if(IS_DIRECTORY ${_src_dir})
  49. file(GLOB_RECURSE header_files ${_src_dir}/*.h ${_src_dir}/*.hpp)
  50. set(_args)
  51. if(FUNC_STANDARD OR(QMSETUP_SYNC_INCLUDE_STANDARD AND NOT FUNC_NO_STANDARD))
  52. list(APPEND _args -s)
  53. endif()
  54. if(FUNC_NO_ALL)
  55. list(APPEND _args -n)
  56. endif()
  57. set(_even off)
  58. foreach(_item IN LISTS FUNC_INCLUDE)
  59. if(_even)
  60. set(_even off)
  61. list(APPEND _args ${_item})
  62. else()
  63. set(_even on)
  64. list(APPEND _args -i ${_item})
  65. endif()
  66. endforeach()
  67. foreach(_item IN LISTS FUNC_EXCLUDE)
  68. list(APPEND _args -e ${_item})
  69. endforeach()
  70. if(FUNC_VERBOSE)
  71. list(APPEND _args -V)
  72. endif()
  73. if(FUNC_FORCE OR NOT EXISTS ${_dest_dir})
  74. if(EXISTS ${_dest_dir})
  75. file(REMOVE_RECURSE ${_dest_dir})
  76. endif()
  77. execute_process(
  78. COMMAND ${QMSETUP_CORECMD_EXECUTABLE} incsync ${_args} ${_src_dir} ${_dest_dir}
  79. WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
  80. COMMAND_ERROR_IS_FATAL ANY
  81. )
  82. endif()
  83. if(FUNC_INSTALL_DIR)
  84. set(_install_dir ${FUNC_INSTALL_DIR})
  85. set(_args_quoted)
  86. foreach(_item IN LISTS _args)
  87. set(_args_quoted "${_args_quoted}\"${_item}\" ")
  88. endforeach()
  89. # Get command output only and use file(INSTALL) to install files
  90. install(CODE "
  91. get_filename_component(_install_dir \"${_install_dir}\" ABSOLUTE BASE_DIR \${CMAKE_INSTALL_PREFIX})
  92. execute_process(
  93. COMMAND \"${QMSETUP_CORECMD_EXECUTABLE}\" incsync -d
  94. ${_args_quoted} \"${_src_dir}\" \${_install_dir}
  95. WORKING_DIRECTORY \"${CMAKE_CURRENT_SOURCE_DIR}\"
  96. OUTPUT_VARIABLE _output_contents
  97. OUTPUT_STRIP_TRAILING_WHITESPACE
  98. COMMAND_ERROR_IS_FATAL ANY
  99. )
  100. string(REPLACE \"\\n\" \";\" _lines \"\${_output_contents}\")
  101. foreach(_line IN LISTS _lines)
  102. string(REGEX MATCH \"from \\\"([^\\\"]*)\\\" to \\\"([^\\\"]*)\\\"\" _ \${_line})
  103. get_filename_component(_target_path \${CMAKE_MATCH_2} DIRECTORY)
  104. file(INSTALL \${CMAKE_MATCH_1} DESTINATION \${_target_path})
  105. endforeach()
  106. ")
  107. endif()
  108. else()
  109. message(FATAL_ERROR "qm_sync_include: source directory doesn't exist.")
  110. endif()
  111. endfunction()
  112. #[[
  113. Add a definition to a property scope.
  114. qm_add_definition( <key | key=value> | <key> <value>
  115. [GLOBAL | TARGET <target> | SOURCE <file> | DIRECTORY <dir>]
  116. [PROPERTY <prop>]
  117. [CONDITION <cond...>]
  118. [STRING_LITERAL] [NO_KEYWORD]
  119. [NUMERICAL] [CLASSICAL]
  120. )
  121. STRING_LITERAL: Force quotes on values
  122. NO_KEYWORD: Treat any keyword as string
  123. NUMERICAL: Use 1/-1 as defined/undefined, can be forced to enable by enabling `QMSETUP_DEFINITION_NUMERICAL`
  124. CLASSICAL: Use classical definition, enable it to override `QMSETUP_DEFINITION_NUMERICAL`
  125. ]] #
  126. function(qm_add_definition _first)
  127. set(options STRING_LITERAL NO_KEYWORD NUMERICAL CLASSICAL)
  128. set(oneValueArgs TARGET SOURCE DIRECTORY PROPERTY)
  129. set(multiValueArgs CONDITION)
  130. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  131. set(_result)
  132. set(_is_pair off)
  133. set(_defined off)
  134. set(_list ${_first} ${FUNC_UNPARSED_ARGUMENTS})
  135. list(LENGTH _list _len)
  136. set(_cond on)
  137. set(_numerical off)
  138. if(NOT FUNC_CLASSICAL AND(QMSETUP_DEFINITION_NUMERICAL OR FUNC_NUMERICAL))
  139. set(_numerical on)
  140. endif()
  141. if(FUNC_CONDITION)
  142. if(NOT(${FUNC_CONDITION}))
  143. set(_cond off)
  144. endif()
  145. elseif(DEFINED FUNC_CONDITION)
  146. set(_cond off)
  147. endif()
  148. if(_len EQUAL 1)
  149. set(_result ${_list})
  150. set(_defined on)
  151. if(NOT _cond)
  152. set(_defined off)
  153. endif()
  154. elseif(_len EQUAL 2)
  155. # Get key
  156. list(POP_FRONT _list _key)
  157. list(POP_FRONT _list _val)
  158. if(FUNC_STRING_LITERAL AND NOT ${_val} MATCHES "\".+\"")
  159. set(_val "\"${_val}\"")
  160. endif()
  161. # Boolean
  162. string(TOLOWER ${_val} _val_lower)
  163. if(NOT FUNC_NO_KEYWORD AND(${_val_lower} STREQUAL "off" OR ${_val_lower} STREQUAL "false"))
  164. set(_result ${_key})
  165. set(_defined off)
  166. if(NOT _cond)
  167. set(_defined on)
  168. endif()
  169. elseif(NOT FUNC_NO_KEYWORD AND(${_val_lower} STREQUAL "on" OR ${_val_lower} STREQUAL "true"))
  170. set(_result ${_key})
  171. set(_defined on)
  172. if(NOT _cond)
  173. set(_defined off)
  174. endif()
  175. else()
  176. set(_result "${_key}=${_val}")
  177. set(_is_pair on)
  178. set(_defined on)
  179. if(NOT _cond)
  180. set(_defined off)
  181. endif()
  182. endif()
  183. else()
  184. message(FATAL_ERROR "qm_add_definition: called with incorrect number of arguments")
  185. endif()
  186. if(_numerical AND NOT _is_pair)
  187. if(_defined)
  188. set(_result "${_result}=1")
  189. else()
  190. set(_result "${_result}=-1")
  191. endif()
  192. elseif(NOT _defined)
  193. return()
  194. endif()
  195. _qm_calc_property_scope_helper(_scope _prop)
  196. set_property(${_scope} APPEND PROPERTY ${_prop} "${_result}")
  197. endfunction()
  198. #[[
  199. Remove a definition from a property scope.
  200. qm_remove_definition(<key>
  201. [GLOBAL | TARGET <target> | SOURCE <file> | DIRECTORY <dir>]
  202. [PROPERTY <prop>]
  203. )
  204. ]] #
  205. function(qm_remove_definition _key)
  206. set(options)
  207. set(oneValueArgs TARGET PROPERTY)
  208. set(multiValueArgs)
  209. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  210. if(FUNC_TARGET)
  211. get_target_property(_definitions ${FUNC_TARGET} ${_prop})
  212. else()
  213. get_property(_definitions GLOBAL PROPERTY ${_prop})
  214. endif()
  215. # Filter
  216. list(FILTER _definitions EXCLUDE REGEX "^${_key}(=.*)?$")
  217. _qm_calc_property_scope_helper(_scope _prop)
  218. set_property(${_scope} PROPERTY ${_prop} "${_definitions}")
  219. endfunction()
  220. #[[
  221. Generate a configuration header of a property scope. If the configuration has not changed,
  222. the generated file's timestemp will not be updated when you reconfigure it.
  223. qm_generate_config(<file>
  224. [GLOBAL | TARGET <target> | SOURCE <file> | DIRECTORY <dir>]
  225. [PROPERTY <prop>]
  226. [PROJECT_NAME <name>]
  227. [WARNING_FILE <file>]
  228. [NO_WARNING]
  229. [NO_HASH]
  230. )
  231. ]] #
  232. function(qm_generate_config _file)
  233. set(options NO_WARNING NO_HASH)
  234. set(oneValueArgs TARGET PROPERTY PROJECT_NAME WARNING_FILE)
  235. set(multiValueArgs)
  236. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  237. _qm_calc_property_scope_helper(_scope _prop)
  238. get_property(_definitions ${_scope} PROPERTY ${_prop})
  239. if(NOT _definitions)
  240. set(_definitions) # May be _-NOTFOUND
  241. endif()
  242. _qm_generate_config_helper()
  243. endfunction()
  244. #[[
  245. Generate build info information header.
  246. qm_generate_build_info(<file>
  247. [ROOT_DIRECTORY <dir>]
  248. [PREFIX <prefix>]
  249. [YEAR] [TIME]
  250. [WARNING_FILE <file>]
  251. [NO_WARNING]
  252. [NO_HASH]
  253. [REQUIRED]
  254. )
  255. file: Output file
  256. ROOT_DIRECTORY: Repository root directory (CMake will try to run `git` at this directory)
  257. PREFIX: Macros prefix, default to the upper case of `PROJECT_NAME`
  258. REQUIRED: Abort if there's any error with git
  259. ]] #
  260. function(qm_generate_build_info _file)
  261. set(options NO_WARNING NO_HASH YEAR TIME REQUIRED)
  262. set(oneValueArgs ROOT_DIRECTORY PREFIX WARNING_FILE)
  263. set(multiValueArgs)
  264. cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  265. if(FUNC_PREFIX)
  266. set(_prefix ${FUNC_PREFIX})
  267. else()
  268. string(TOUPPER "${PROJECT_NAME}" _prefix)
  269. endif()
  270. set(_dir)
  271. qm_set_value(_dir FUNC_ROOT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
  272. set(_git_branch "unknown")
  273. set(_git_hash "unknown")
  274. set(_git_commit_time "unknown")
  275. set(_git_commit_author "unknown")
  276. set(_git_commit_email "unknown")
  277. # Check `git` command
  278. if(NOT GIT_EXECUTABLE)
  279. find_package(Git)
  280. if(NOT Git_FOUND)
  281. if(FUNC_REQUIRED)
  282. message(FATAL_ERROR "Git not found")
  283. endif()
  284. endif()
  285. endif()
  286. if(GIT_EXECUTABLE)
  287. # Branch
  288. execute_process(
  289. COMMAND ${GIT_EXECUTABLE} symbolic-ref --short -q HEAD
  290. OUTPUT_VARIABLE _temp
  291. ERROR_VARIABLE _err
  292. OUTPUT_STRIP_TRAILING_WHITESPACE
  293. ERROR_STRIP_TRAILING_WHITESPACE
  294. ERROR_QUIET
  295. WORKING_DIRECTORY ${_dir}
  296. RESULT_VARIABLE _code
  297. )
  298. if(_code EQUAL 0)
  299. set(_git_branch ${_temp})
  300. elseif(FUNC_REQUIRED)
  301. message(FATAL_ERROR "Error running git symbolic-ref: ${_err}")
  302. endif()
  303. # Hash
  304. execute_process(
  305. COMMAND ${GIT_EXECUTABLE} log -1 "--pretty=format:%H;%aI;%aN;%aE" # Use `;` as separator
  306. OUTPUT_VARIABLE _temp
  307. ERROR_VARIABLE _err
  308. OUTPUT_STRIP_TRAILING_WHITESPACE
  309. ERROR_STRIP_TRAILING_WHITESPACE
  310. ERROR_QUIET
  311. WORKING_DIRECTORY ${_dir}
  312. RESULT_VARIABLE _code
  313. )
  314. if(_code EQUAL 0)
  315. list(GET _temp 0 _git_hash)
  316. list(GET _temp 1 _git_commit_time)
  317. list(GET _temp 2 _git_commit_author)
  318. list(GET _temp 3 _git_commit_email)
  319. elseif(FUNC_REQUIRED)
  320. message(FATAL_ERROR "Error running git log: ${_err}")
  321. endif()
  322. endif()
  323. qm_set_value(_system_name CMAKE_SYSTEM_NAME unknown)
  324. qm_set_value(_system_version CMAKE_SYSTEM_VERSION unknown)
  325. qm_set_value(_system_processor CMAKE_SYSTEM_PROCESSOR unknown)
  326. qm_set_value(_host_system_name CMAKE_HOST_SYSTEM_NAME unknown)
  327. qm_set_value(_host_system_version CMAKE_HOST_SYSTEM_VERSION unknown)
  328. qm_set_value(_host_system_processor CMAKE_HOST_SYSTEM_PROCESSOR unknown)
  329. qm_set_value(_compiler_name CMAKE_CXX_COMPILER_ID unknown)
  330. qm_set_value(_compiler_version CMAKE_CXX_COMPILER_VERSION unknown)
  331. qm_set_value(_compiler_arch CMAKE_CXX_COMPILER_ARCHITECTURE_ID unknown)
  332. qm_set_value(_compiler_abi CMAKE_CXX_COMPILER_ABI unknown)
  333. set(_definitions)
  334. set(_has_time off)
  335. # year
  336. if(FUNC_YEAR)
  337. string(TIMESTAMP _build_year "%Y")
  338. list(APPEND _definitions ${_prefix}_BUILD_YEAR=\"${_build_year}\")
  339. set(_has_time on)
  340. endif()
  341. # time
  342. if(FUNC_TIME)
  343. string(TIMESTAMP _build_time "%Y/%m/%d %H:%M:%S")
  344. list(APPEND _definitions ${_prefix}_BUILD_TIME=\"${_build_time}\")
  345. set(_has_time on)
  346. endif()
  347. if(_has_time)
  348. list(APPEND _definitions "%")
  349. endif()
  350. # system
  351. list(APPEND _definitions ${_prefix}_SYSTEM_NAME=\"${_system_name}\")
  352. list(APPEND _definitions ${_prefix}_SYSTEM_VERSION=\"${_system_version}\")
  353. list(APPEND _definitions ${_prefix}_SYSTEM_PROCESSOR=\"${_system_processor}\")
  354. list(APPEND _definitions ${_prefix}_HOST_SYSTEM_NAME=\"${_host_system_name}\")
  355. list(APPEND _definitions ${_prefix}_HOST_SYSTEM_VERSION=\"${_host_system_version}\")
  356. list(APPEND _definitions ${_prefix}_HOST_SYSTEM_PROCESSOR=\"${_host_system_processor}\")
  357. list(APPEND _definitions "%")
  358. # compiler
  359. list(APPEND _definitions ${_prefix}_COMPILER_ID=\"${_compiler_name}\")
  360. list(APPEND _definitions ${_prefix}_COMPILER_VERSION=\"${_compiler_version}\")
  361. list(APPEND _definitions ${_prefix}_COMPILER_ARCH=\"${_compiler_arch}\")
  362. list(APPEND _definitions ${_prefix}_COMPILER_ABI=\"${_compiler_abi}\")
  363. list(APPEND _definitions "%")
  364. # build time (deprecated)
  365. # list(APPEND _definitions ${_prefix}_BUILD_DATE_TIME=\"${_build_time}\")
  366. # list(APPEND _definitions ${_prefix}_BUILD_YEAR=\"${_build_year}\")
  367. # list(APPEND _definitions "%")
  368. # git info
  369. list(APPEND _definitions ${_prefix}_GIT_BRANCH=\"${_git_branch}\")
  370. list(APPEND _definitions ${_prefix}_GIT_LAST_COMMIT_HASH=\"${_git_hash}\")
  371. list(APPEND _definitions ${_prefix}_GIT_LAST_COMMIT_TIME=\"${_git_commit_time}\")
  372. list(APPEND _definitions ${_prefix}_GIT_LAST_COMMIT_AUTHOR=\"${_git_commit_author}\")
  373. list(APPEND _definitions ${_prefix}_GIT_LAST_COMMIT_EMAIL=\"${_git_commit_email}\")
  374. _qm_generate_config_helper()
  375. endfunction()
  376. # ----------------------------------
  377. # Private functions
  378. # ----------------------------------
  379. function(_qm_calc_property_scope_helper _scope _prop)
  380. if(FUNC_TARGET)
  381. set(_scope TARGET ${FUNC_TARGET})
  382. elseif(FUNC_SOURCE)
  383. set(_scope SOURCE ${FUNC__SOURCE})
  384. elseif(FUNC_DIRECTORY)
  385. set(_scope DIRECTORY ${FUNC_DIRECTORY})
  386. elseif(FUNC_GLOBAL)
  387. set(_scope GLOBAL)
  388. elseif(QMSETUP_DEFINITION_SCOPE)
  389. set(_scope ${QMSETUP_DEFINITION_SCOPE})
  390. else()
  391. set(_scope GLOBAL)
  392. endif()
  393. qm_set_value(_prop FUNC_PROPERTY QMSETUP_DEFINITION_PROPERTY "CONFIG_DEFINITIONS")
  394. set(_scope ${_scope} PARENT_SCOPE)
  395. set(_prop ${_prop} PARENT_SCOPE)
  396. endfunction()
  397. function(_qm_generate_config_helper)
  398. set(_args)
  399. foreach(_item IN LISTS _definitions)
  400. list(APPEND _args "-D${_item}")
  401. endforeach()
  402. list(APPEND _args ${_file})
  403. if(NOT FUNC_NO_WARNING)
  404. list(APPEND _args "-w" ${FUNC_WARNING_FILE})
  405. endif()
  406. if(FUNC_PROJECT_NAME)
  407. list(APPEND _args "-p" ${FUNC_PROJECT_NAME})
  408. endif()
  409. if(FUNC_NO_HASH)
  410. list(APPEND _args "-f")
  411. endif()
  412. execute_process(COMMAND ${QMSETUP_CORECMD_EXECUTABLE} configure ${_args}
  413. WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
  414. COMMAND_ERROR_IS_FATAL ANY
  415. )
  416. endfunction()