HunterGate.cmake 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. # Copyright (c) 2013-2019, Ruslan Baratov
  2. # All rights reserved.
  3. #
  4. # Redistribution and use in source and binary forms, with or without
  5. # modification, are permitted provided that the following conditions are met:
  6. #
  7. # * Redistributions of source code must retain the above copyright notice, this
  8. # list of conditions and the following disclaimer.
  9. #
  10. # * Redistributions in binary form must reproduce the above copyright notice,
  11. # this list of conditions and the following disclaimer in the documentation
  12. # and/or other materials provided with the distribution.
  13. #
  14. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  15. # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  16. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  17. # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  18. # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  19. # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  20. # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  21. # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  22. # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  23. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  24. option(HUNTER_ENABLED "Enable Hunter package manager support" ON)
  25. include(CMakeParseArguments) # cmake_parse_arguments
  26. option(HUNTER_STATUS_PRINT "Print working status" ON)
  27. option(HUNTER_STATUS_DEBUG "Print a lot info" OFF)
  28. option(HUNTER_TLS_VERIFY "Enable/disable TLS certificate checking on downloads" ON)
  29. set(HUNTER_ERROR_PAGE "https://hunter.readthedocs.io/en/latest/reference/errors")
  30. function(hunter_gate_status_print)
  31. if(HUNTER_STATUS_PRINT OR HUNTER_STATUS_DEBUG)
  32. foreach(print_message ${ARGV})
  33. message(STATUS "[hunter] ${print_message}")
  34. endforeach()
  35. endif()
  36. endfunction()
  37. function(hunter_gate_status_debug)
  38. if(HUNTER_STATUS_DEBUG)
  39. foreach(print_message ${ARGV})
  40. string(TIMESTAMP timestamp)
  41. message(STATUS "[hunter *** DEBUG *** ${timestamp}] ${print_message}")
  42. endforeach()
  43. endif()
  44. endfunction()
  45. function(hunter_gate_error_page error_page)
  46. message("------------------------------ ERROR ------------------------------")
  47. message(" ${HUNTER_ERROR_PAGE}/${error_page}.html")
  48. message("-------------------------------------------------------------------")
  49. message("")
  50. message(FATAL_ERROR "")
  51. endfunction()
  52. function(hunter_gate_internal_error)
  53. message("")
  54. foreach(print_message ${ARGV})
  55. message("[hunter ** INTERNAL **] ${print_message}")
  56. endforeach()
  57. message("[hunter ** INTERNAL **] [Directory:${CMAKE_CURRENT_LIST_DIR}]")
  58. message("")
  59. hunter_gate_error_page("error.internal")
  60. endfunction()
  61. function(hunter_gate_fatal_error)
  62. cmake_parse_arguments(hunter "" "ERROR_PAGE" "" "${ARGV}")
  63. if("${hunter_ERROR_PAGE}" STREQUAL "")
  64. hunter_gate_internal_error("Expected ERROR_PAGE")
  65. endif()
  66. message("")
  67. foreach(x ${hunter_UNPARSED_ARGUMENTS})
  68. message("[hunter ** FATAL ERROR **] ${x}")
  69. endforeach()
  70. message("[hunter ** FATAL ERROR **] [Directory:${CMAKE_CURRENT_LIST_DIR}]")
  71. message("")
  72. hunter_gate_error_page("${hunter_ERROR_PAGE}")
  73. endfunction()
  74. function(hunter_gate_user_error)
  75. hunter_gate_fatal_error(${ARGV} ERROR_PAGE "error.incorrect.input.data")
  76. endfunction()
  77. function(hunter_gate_self root version sha1 result)
  78. string(COMPARE EQUAL "${root}" "" is_bad)
  79. if(is_bad)
  80. hunter_gate_internal_error("root is empty")
  81. endif()
  82. string(COMPARE EQUAL "${version}" "" is_bad)
  83. if(is_bad)
  84. hunter_gate_internal_error("version is empty")
  85. endif()
  86. string(COMPARE EQUAL "${sha1}" "" is_bad)
  87. if(is_bad)
  88. hunter_gate_internal_error("sha1 is empty")
  89. endif()
  90. string(SUBSTRING "${sha1}" 0 7 archive_id)
  91. if(EXISTS "${root}/cmake/Hunter")
  92. set(hunter_self "${root}")
  93. else()
  94. set(
  95. hunter_self
  96. "${root}/_Base/Download/Hunter/${version}/${archive_id}/Unpacked"
  97. )
  98. endif()
  99. set("${result}" "${hunter_self}" PARENT_SCOPE)
  100. endfunction()
  101. # Set HUNTER_GATE_ROOT cmake variable to suitable value.
  102. function(hunter_gate_detect_root)
  103. # Check CMake variable
  104. string(COMPARE NOTEQUAL "${HUNTER_ROOT}" "" not_empty)
  105. if(not_empty)
  106. set(HUNTER_GATE_ROOT "${HUNTER_ROOT}" PARENT_SCOPE)
  107. hunter_gate_status_debug("HUNTER_ROOT detected by cmake variable")
  108. return()
  109. endif()
  110. # Check environment variable
  111. string(COMPARE NOTEQUAL "$ENV{HUNTER_ROOT}" "" not_empty)
  112. if(not_empty)
  113. set(HUNTER_GATE_ROOT "$ENV{HUNTER_ROOT}" PARENT_SCOPE)
  114. hunter_gate_status_debug("HUNTER_ROOT detected by environment variable")
  115. return()
  116. endif()
  117. # Check HOME environment variable
  118. string(COMPARE NOTEQUAL "$ENV{HOME}" "" result)
  119. if(result)
  120. set(HUNTER_GATE_ROOT "$ENV{HOME}/.hunter" PARENT_SCOPE)
  121. hunter_gate_status_debug("HUNTER_ROOT set using HOME environment variable")
  122. return()
  123. endif()
  124. # Check SYSTEMDRIVE and USERPROFILE environment variable (windows only)
  125. if(WIN32)
  126. string(COMPARE NOTEQUAL "$ENV{SYSTEMDRIVE}" "" result)
  127. if(result)
  128. set(HUNTER_GATE_ROOT "$ENV{SYSTEMDRIVE}/.hunter" PARENT_SCOPE)
  129. hunter_gate_status_debug(
  130. "HUNTER_ROOT set using SYSTEMDRIVE environment variable"
  131. )
  132. return()
  133. endif()
  134. string(COMPARE NOTEQUAL "$ENV{USERPROFILE}" "" result)
  135. if(result)
  136. set(HUNTER_GATE_ROOT "$ENV{USERPROFILE}/.hunter" PARENT_SCOPE)
  137. hunter_gate_status_debug(
  138. "HUNTER_ROOT set using USERPROFILE environment variable"
  139. )
  140. return()
  141. endif()
  142. endif()
  143. hunter_gate_fatal_error(
  144. "Can't detect HUNTER_ROOT"
  145. ERROR_PAGE "error.detect.hunter.root"
  146. )
  147. endfunction()
  148. function(hunter_gate_download dir)
  149. string(
  150. COMPARE
  151. NOTEQUAL
  152. "$ENV{HUNTER_DISABLE_AUTOINSTALL}"
  153. ""
  154. disable_autoinstall
  155. )
  156. if(disable_autoinstall AND NOT HUNTER_RUN_INSTALL)
  157. hunter_gate_fatal_error(
  158. "Hunter not found in '${dir}'"
  159. "Set HUNTER_RUN_INSTALL=ON to auto-install it from '${HUNTER_GATE_URL}'"
  160. "Settings:"
  161. " HUNTER_ROOT: ${HUNTER_GATE_ROOT}"
  162. " HUNTER_SHA1: ${HUNTER_GATE_SHA1}"
  163. ERROR_PAGE "error.run.install"
  164. )
  165. endif()
  166. string(COMPARE EQUAL "${dir}" "" is_bad)
  167. if(is_bad)
  168. hunter_gate_internal_error("Empty 'dir' argument")
  169. endif()
  170. string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" is_bad)
  171. if(is_bad)
  172. hunter_gate_internal_error("HUNTER_GATE_SHA1 empty")
  173. endif()
  174. string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" is_bad)
  175. if(is_bad)
  176. hunter_gate_internal_error("HUNTER_GATE_URL empty")
  177. endif()
  178. set(done_location "${dir}/DONE")
  179. set(sha1_location "${dir}/SHA1")
  180. set(build_dir "${dir}/Build")
  181. set(cmakelists "${dir}/CMakeLists.txt")
  182. hunter_gate_status_debug("Locking directory: ${dir}")
  183. file(LOCK "${dir}" DIRECTORY GUARD FUNCTION)
  184. hunter_gate_status_debug("Lock done")
  185. if(EXISTS "${done_location}")
  186. # while waiting for lock other instance can do all the job
  187. hunter_gate_status_debug("File '${done_location}' found, skip install")
  188. return()
  189. endif()
  190. file(REMOVE_RECURSE "${build_dir}")
  191. file(REMOVE_RECURSE "${cmakelists}")
  192. file(MAKE_DIRECTORY "${build_dir}") # check directory permissions
  193. # Disabling languages speeds up a little bit, reduces noise in the output
  194. # and avoids path too long windows error
  195. file(
  196. WRITE
  197. "${cmakelists}"
  198. "cmake_minimum_required(VERSION 3.2)\n"
  199. "project(HunterDownload LANGUAGES NONE)\n"
  200. "include(ExternalProject)\n"
  201. "ExternalProject_Add(\n"
  202. " Hunter\n"
  203. " URL\n"
  204. " \"${HUNTER_GATE_URL}\"\n"
  205. " URL_HASH\n"
  206. " SHA1=${HUNTER_GATE_SHA1}\n"
  207. " DOWNLOAD_DIR\n"
  208. " \"${dir}\"\n"
  209. " TLS_VERIFY\n"
  210. " ${HUNTER_TLS_VERIFY}\n"
  211. " SOURCE_DIR\n"
  212. " \"${dir}/Unpacked\"\n"
  213. " CONFIGURE_COMMAND\n"
  214. " \"\"\n"
  215. " BUILD_COMMAND\n"
  216. " \"\"\n"
  217. " INSTALL_COMMAND\n"
  218. " \"\"\n"
  219. ")\n"
  220. )
  221. if(HUNTER_STATUS_DEBUG)
  222. set(logging_params "")
  223. else()
  224. set(logging_params OUTPUT_QUIET)
  225. endif()
  226. hunter_gate_status_debug("Run generate")
  227. # Need to add toolchain file too.
  228. # Otherwise on Visual Studio + MDD this will fail with error:
  229. # "Could not find an appropriate version of the Windows 10 SDK installed on this machine"
  230. if(EXISTS "${CMAKE_TOOLCHAIN_FILE}")
  231. get_filename_component(absolute_CMAKE_TOOLCHAIN_FILE "${CMAKE_TOOLCHAIN_FILE}" ABSOLUTE)
  232. set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=${absolute_CMAKE_TOOLCHAIN_FILE}")
  233. else()
  234. # 'toolchain_arg' can't be empty
  235. set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=")
  236. endif()
  237. string(COMPARE EQUAL "${CMAKE_MAKE_PROGRAM}" "" no_make)
  238. if(no_make)
  239. set(make_arg "")
  240. else()
  241. # Test case: remove Ninja from PATH but set it via CMAKE_MAKE_PROGRAM
  242. set(make_arg "-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}")
  243. endif()
  244. execute_process(
  245. COMMAND
  246. "${CMAKE_COMMAND}"
  247. "-H${dir}"
  248. "-B${build_dir}"
  249. "-G${CMAKE_GENERATOR}"
  250. "${toolchain_arg}"
  251. ${make_arg}
  252. WORKING_DIRECTORY "${dir}"
  253. RESULT_VARIABLE download_result
  254. ${logging_params}
  255. )
  256. if(NOT download_result EQUAL 0)
  257. hunter_gate_internal_error(
  258. "Configure project failed."
  259. "To reproduce the error run: ${CMAKE_COMMAND} -H${dir} -B${build_dir} -G${CMAKE_GENERATOR} ${toolchain_arg} ${make_arg}"
  260. "In directory ${dir}"
  261. )
  262. endif()
  263. hunter_gate_status_print(
  264. "Initializing Hunter workspace (${HUNTER_GATE_SHA1})"
  265. " ${HUNTER_GATE_URL}"
  266. " -> ${dir}"
  267. )
  268. execute_process(
  269. COMMAND "${CMAKE_COMMAND}" --build "${build_dir}"
  270. WORKING_DIRECTORY "${dir}"
  271. RESULT_VARIABLE download_result
  272. ${logging_params}
  273. )
  274. if(NOT download_result EQUAL 0)
  275. hunter_gate_internal_error("Build project failed")
  276. endif()
  277. file(REMOVE_RECURSE "${build_dir}")
  278. file(REMOVE_RECURSE "${cmakelists}")
  279. file(WRITE "${sha1_location}" "${HUNTER_GATE_SHA1}")
  280. file(WRITE "${done_location}" "DONE")
  281. hunter_gate_status_debug("Finished")
  282. endfunction()
  283. # Must be a macro so master file 'cmake/Hunter' can
  284. # apply all variables easily just by 'include' command
  285. # (otherwise PARENT_SCOPE magic needed)
  286. macro(HunterGate)
  287. if(HUNTER_GATE_DONE)
  288. # variable HUNTER_GATE_DONE set explicitly for external project
  289. # (see `hunter_download`)
  290. set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES)
  291. endif()
  292. # First HunterGate command will init Hunter, others will be ignored
  293. get_property(_hunter_gate_done GLOBAL PROPERTY HUNTER_GATE_DONE SET)
  294. if(NOT HUNTER_ENABLED)
  295. # Empty function to avoid error "unknown function"
  296. function(hunter_add_package)
  297. endfunction()
  298. set(
  299. _hunter_gate_disabled_mode_dir
  300. "${CMAKE_CURRENT_LIST_DIR}/cmake/Hunter/disabled-mode"
  301. )
  302. if(EXISTS "${_hunter_gate_disabled_mode_dir}")
  303. hunter_gate_status_debug(
  304. "Adding \"disabled-mode\" modules: ${_hunter_gate_disabled_mode_dir}"
  305. )
  306. list(APPEND CMAKE_PREFIX_PATH "${_hunter_gate_disabled_mode_dir}")
  307. endif()
  308. elseif(_hunter_gate_done)
  309. hunter_gate_status_debug("Secondary HunterGate (use old settings)")
  310. hunter_gate_self(
  311. "${HUNTER_CACHED_ROOT}"
  312. "${HUNTER_VERSION}"
  313. "${HUNTER_SHA1}"
  314. _hunter_self
  315. )
  316. include("${_hunter_self}/cmake/Hunter")
  317. else()
  318. set(HUNTER_GATE_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}")
  319. string(COMPARE NOTEQUAL "${PROJECT_NAME}" "" _have_project_name)
  320. if(_have_project_name)
  321. hunter_gate_fatal_error(
  322. "Please set HunterGate *before* 'project' command. "
  323. "Detected project: ${PROJECT_NAME}"
  324. ERROR_PAGE "error.huntergate.before.project"
  325. )
  326. endif()
  327. cmake_parse_arguments(
  328. HUNTER_GATE "LOCAL" "URL;SHA1;GLOBAL;FILEPATH" "" ${ARGV}
  329. )
  330. string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" _empty_sha1)
  331. string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" _empty_url)
  332. string(
  333. COMPARE
  334. NOTEQUAL
  335. "${HUNTER_GATE_UNPARSED_ARGUMENTS}"
  336. ""
  337. _have_unparsed
  338. )
  339. string(COMPARE NOTEQUAL "${HUNTER_GATE_GLOBAL}" "" _have_global)
  340. string(COMPARE NOTEQUAL "${HUNTER_GATE_FILEPATH}" "" _have_filepath)
  341. if(_have_unparsed)
  342. hunter_gate_user_error(
  343. "HunterGate unparsed arguments: ${HUNTER_GATE_UNPARSED_ARGUMENTS}"
  344. )
  345. endif()
  346. if(_empty_sha1)
  347. hunter_gate_user_error("SHA1 suboption of HunterGate is mandatory")
  348. endif()
  349. if(_empty_url)
  350. hunter_gate_user_error("URL suboption of HunterGate is mandatory")
  351. endif()
  352. if(_have_global)
  353. if(HUNTER_GATE_LOCAL)
  354. hunter_gate_user_error("Unexpected LOCAL (already has GLOBAL)")
  355. endif()
  356. if(_have_filepath)
  357. hunter_gate_user_error("Unexpected FILEPATH (already has GLOBAL)")
  358. endif()
  359. endif()
  360. if(HUNTER_GATE_LOCAL)
  361. if(_have_global)
  362. hunter_gate_user_error("Unexpected GLOBAL (already has LOCAL)")
  363. endif()
  364. if(_have_filepath)
  365. hunter_gate_user_error("Unexpected FILEPATH (already has LOCAL)")
  366. endif()
  367. endif()
  368. if(_have_filepath)
  369. if(_have_global)
  370. hunter_gate_user_error("Unexpected GLOBAL (already has FILEPATH)")
  371. endif()
  372. if(HUNTER_GATE_LOCAL)
  373. hunter_gate_user_error("Unexpected LOCAL (already has FILEPATH)")
  374. endif()
  375. endif()
  376. hunter_gate_detect_root() # set HUNTER_GATE_ROOT
  377. # Beautify path, fix probable problems with windows path slashes
  378. get_filename_component(
  379. HUNTER_GATE_ROOT "${HUNTER_GATE_ROOT}" ABSOLUTE
  380. )
  381. hunter_gate_status_debug("HUNTER_ROOT: ${HUNTER_GATE_ROOT}")
  382. if(NOT HUNTER_ALLOW_SPACES_IN_PATH)
  383. string(FIND "${HUNTER_GATE_ROOT}" " " _contain_spaces)
  384. if(NOT _contain_spaces EQUAL -1)
  385. hunter_gate_fatal_error(
  386. "HUNTER_ROOT (${HUNTER_GATE_ROOT}) contains spaces."
  387. "Set HUNTER_ALLOW_SPACES_IN_PATH=ON to skip this error"
  388. "(Use at your own risk!)"
  389. ERROR_PAGE "error.spaces.in.hunter.root"
  390. )
  391. endif()
  392. endif()
  393. string(
  394. REGEX
  395. MATCH
  396. "[0-9]+\\.[0-9]+\\.[0-9]+[-_a-z0-9]*"
  397. HUNTER_GATE_VERSION
  398. "${HUNTER_GATE_URL}"
  399. )
  400. string(COMPARE EQUAL "${HUNTER_GATE_VERSION}" "" _is_empty)
  401. if(_is_empty)
  402. set(HUNTER_GATE_VERSION "unknown")
  403. endif()
  404. hunter_gate_self(
  405. "${HUNTER_GATE_ROOT}"
  406. "${HUNTER_GATE_VERSION}"
  407. "${HUNTER_GATE_SHA1}"
  408. _hunter_self
  409. )
  410. set(_master_location "${_hunter_self}/cmake/Hunter")
  411. if(EXISTS "${HUNTER_GATE_ROOT}/cmake/Hunter")
  412. # Hunter downloaded manually (e.g. by 'git clone')
  413. set(_unused "xxxxxxxxxx")
  414. set(HUNTER_GATE_SHA1 "${_unused}")
  415. set(HUNTER_GATE_VERSION "${_unused}")
  416. else()
  417. get_filename_component(_archive_id_location "${_hunter_self}/.." ABSOLUTE)
  418. set(_done_location "${_archive_id_location}/DONE")
  419. set(_sha1_location "${_archive_id_location}/SHA1")
  420. # Check Hunter already downloaded by HunterGate
  421. if(NOT EXISTS "${_done_location}")
  422. hunter_gate_download("${_archive_id_location}")
  423. endif()
  424. if(NOT EXISTS "${_done_location}")
  425. hunter_gate_internal_error("hunter_gate_download failed")
  426. endif()
  427. if(NOT EXISTS "${_sha1_location}")
  428. hunter_gate_internal_error("${_sha1_location} not found")
  429. endif()
  430. file(READ "${_sha1_location}" _sha1_value)
  431. string(COMPARE EQUAL "${_sha1_value}" "${HUNTER_GATE_SHA1}" _is_equal)
  432. if(NOT _is_equal)
  433. hunter_gate_internal_error(
  434. "Short SHA1 collision:"
  435. " ${_sha1_value} (from ${_sha1_location})"
  436. " ${HUNTER_GATE_SHA1} (HunterGate)"
  437. )
  438. endif()
  439. if(NOT EXISTS "${_master_location}")
  440. hunter_gate_user_error(
  441. "Master file not found:"
  442. " ${_master_location}"
  443. "try to update Hunter/HunterGate"
  444. )
  445. endif()
  446. endif()
  447. include("${_master_location}")
  448. set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES)
  449. endif()
  450. endmacro()