1 # Copyright (c) 2013-2019, Ruslan Baratov
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are met:
7 # * Redistributions of source code must retain the above copyright notice, this
8 # list of conditions and the following disclaimer.
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.
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.
25 # This is a gate file to Hunter package manager.
26 # Include this file using `include` command and add package you need, example:
28 # cmake_minimum_required(VERSION 3.2)
30 # include("cmake/HunterGate.cmake")
32 # URL "https://github.com/path/to/hunter/archive.tar.gz"
33 # SHA1 "798501e983f14b28b10cda16afa4de69eee1da1d"
38 # hunter_add_package(Foo)
39 # hunter_add_package(Boo COMPONENTS Bar Baz)
42 # * https://github.com/hunter-packages/gate/
43 # * https://github.com/ruslo/hunter
45 option(HUNTER_ENABLED "Enable Hunter package manager support" ON)
48 if(CMAKE_VERSION VERSION_LESS "3.2")
51 "At least CMake version 3.2 required for Hunter dependency management."
52 " Update CMake or set HUNTER_ENABLED to OFF."
57 include(CMakeParseArguments) # cmake_parse_arguments
59 option(HUNTER_STATUS_PRINT "Print working status" ON)
60 option(HUNTER_STATUS_DEBUG "Print a lot info" OFF)
61 option(HUNTER_TLS_VERIFY "Enable/disable TLS certificate checking on downloads" ON)
62 set(HUNTER_ROOT "" CACHE FILEPATH "Override the HUNTER_ROOT.")
64 set(HUNTER_ERROR_PAGE "https://hunter.readthedocs.io/en/latest/reference/errors")
66 function(hunter_gate_status_print)
67 if(HUNTER_STATUS_PRINT OR HUNTER_STATUS_DEBUG)
68 foreach(print_message ${ARGV})
69 message(STATUS "[hunter] ${print_message}")
74 function(hunter_gate_status_debug)
75 if(HUNTER_STATUS_DEBUG)
76 foreach(print_message ${ARGV})
77 string(TIMESTAMP timestamp)
78 message(STATUS "[hunter *** DEBUG *** ${timestamp}] ${print_message}")
83 function(hunter_gate_error_page error_page)
84 message("------------------------------ ERROR ------------------------------")
85 message(" ${HUNTER_ERROR_PAGE}/${error_page}.html")
86 message("-------------------------------------------------------------------")
88 message(FATAL_ERROR "")
91 function(hunter_gate_internal_error)
93 foreach(print_message ${ARGV})
94 message("[hunter ** INTERNAL **] ${print_message}")
96 message("[hunter ** INTERNAL **] [Directory:${CMAKE_CURRENT_LIST_DIR}]")
98 hunter_gate_error_page("error.internal")
101 function(hunter_gate_fatal_error)
102 cmake_parse_arguments(hunter "" "ERROR_PAGE" "" "${ARGV}")
103 if("${hunter_ERROR_PAGE}" STREQUAL "")
104 hunter_gate_internal_error("Expected ERROR_PAGE")
107 foreach(x ${hunter_UNPARSED_ARGUMENTS})
108 message("[hunter ** FATAL ERROR **] ${x}")
110 message("[hunter ** FATAL ERROR **] [Directory:${CMAKE_CURRENT_LIST_DIR}]")
112 hunter_gate_error_page("${hunter_ERROR_PAGE}")
115 function(hunter_gate_user_error)
116 hunter_gate_fatal_error(${ARGV} ERROR_PAGE "error.incorrect.input.data")
119 function(hunter_gate_self root version sha1 result)
120 string(COMPARE EQUAL "${root}" "" is_bad)
122 hunter_gate_internal_error("root is empty")
125 string(COMPARE EQUAL "${version}" "" is_bad)
127 hunter_gate_internal_error("version is empty")
130 string(COMPARE EQUAL "${sha1}" "" is_bad)
132 hunter_gate_internal_error("sha1 is empty")
135 string(SUBSTRING "${sha1}" 0 7 archive_id)
137 if(EXISTS "${root}/cmake/Hunter")
138 set(hunter_self "${root}")
142 "${root}/_Base/Download/Hunter/${version}/${archive_id}/Unpacked"
146 set("${result}" "${hunter_self}" PARENT_SCOPE)
149 # Set HUNTER_GATE_ROOT cmake variable to suitable value.
150 function(hunter_gate_detect_root)
151 # Check CMake variable
153 set(HUNTER_GATE_ROOT "${HUNTER_ROOT}" PARENT_SCOPE)
154 hunter_gate_status_debug("HUNTER_ROOT detected by cmake variable")
158 # Check environment variable
159 if(DEFINED ENV{HUNTER_ROOT})
160 set(HUNTER_GATE_ROOT "$ENV{HUNTER_ROOT}" PARENT_SCOPE)
161 hunter_gate_status_debug("HUNTER_ROOT detected by environment variable")
165 # Check HOME environment variable
166 if(DEFINED ENV{HOME})
167 set(HUNTER_GATE_ROOT "$ENV{HOME}/.hunter" PARENT_SCOPE)
168 hunter_gate_status_debug("HUNTER_ROOT set using HOME environment variable")
172 # Check SYSTEMDRIVE and USERPROFILE environment variable (windows only)
174 if(DEFINED ENV{SYSTEMDRIVE})
175 set(HUNTER_GATE_ROOT "$ENV{SYSTEMDRIVE}/.hunter" PARENT_SCOPE)
176 hunter_gate_status_debug(
177 "HUNTER_ROOT set using SYSTEMDRIVE environment variable"
182 if(DEFINED ENV{USERPROFILE})
183 set(HUNTER_GATE_ROOT "$ENV{USERPROFILE}/.hunter" PARENT_SCOPE)
184 hunter_gate_status_debug(
185 "HUNTER_ROOT set using USERPROFILE environment variable"
191 hunter_gate_fatal_error(
192 "Can't detect HUNTER_ROOT"
193 ERROR_PAGE "error.detect.hunter.root"
197 function(hunter_gate_download dir)
201 "$ENV{HUNTER_DISABLE_AUTOINSTALL}"
205 if(disable_autoinstall AND NOT HUNTER_RUN_INSTALL)
206 hunter_gate_fatal_error(
207 "Hunter not found in '${dir}'"
208 "Set HUNTER_RUN_INSTALL=ON to auto-install it from '${HUNTER_GATE_URL}'"
210 " HUNTER_ROOT: ${HUNTER_GATE_ROOT}"
211 " HUNTER_SHA1: ${HUNTER_GATE_SHA1}"
212 ERROR_PAGE "error.run.install"
215 string(COMPARE EQUAL "${dir}" "" is_bad)
217 hunter_gate_internal_error("Empty 'dir' argument")
220 string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" is_bad)
222 hunter_gate_internal_error("HUNTER_GATE_SHA1 empty")
225 string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" is_bad)
227 hunter_gate_internal_error("HUNTER_GATE_URL empty")
230 set(done_location "${dir}/DONE")
231 set(sha1_location "${dir}/SHA1")
233 set(build_dir "${dir}/Build")
234 set(cmakelists "${dir}/CMakeLists.txt")
236 hunter_gate_status_debug("Locking directory: ${dir}")
237 file(LOCK "${dir}" DIRECTORY GUARD FUNCTION)
238 hunter_gate_status_debug("Lock done")
240 if(EXISTS "${done_location}")
241 # while waiting for lock other instance can do all the job
242 hunter_gate_status_debug("File '${done_location}' found, skip install")
246 file(REMOVE_RECURSE "${build_dir}")
247 file(REMOVE_RECURSE "${cmakelists}")
249 file(MAKE_DIRECTORY "${build_dir}") # check directory permissions
251 # Disabling languages speeds up a little bit, reduces noise in the output
252 # and avoids path too long windows error
256 "cmake_minimum_required(VERSION 3.2)\n"
257 "project(HunterDownload LANGUAGES NONE)\n"
258 "include(ExternalProject)\n"
259 "ExternalProject_Add(\n"
262 " \"${HUNTER_GATE_URL}\"\n"
264 " SHA1=${HUNTER_GATE_SHA1}\n"
268 " ${HUNTER_TLS_VERIFY}\n"
270 " \"${dir}/Unpacked\"\n"
271 " CONFIGURE_COMMAND\n"
280 if(HUNTER_STATUS_DEBUG)
281 set(logging_params "")
283 set(logging_params OUTPUT_QUIET)
286 hunter_gate_status_debug("Run generate")
288 # Need to add toolchain file too.
289 # Otherwise on Visual Studio + MDD this will fail with error:
290 # "Could not find an appropriate version of the Windows 10 SDK installed on this machine"
291 if(EXISTS "${CMAKE_TOOLCHAIN_FILE}")
292 get_filename_component(absolute_CMAKE_TOOLCHAIN_FILE "${CMAKE_TOOLCHAIN_FILE}" ABSOLUTE)
293 set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=${absolute_CMAKE_TOOLCHAIN_FILE}")
295 # 'toolchain_arg' can't be empty
296 set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=")
299 string(COMPARE EQUAL "${CMAKE_MAKE_PROGRAM}" "" no_make)
303 # Test case: remove Ninja from PATH but set it via CMAKE_MAKE_PROGRAM
304 set(make_arg "-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}")
312 "-G${CMAKE_GENERATOR}"
315 WORKING_DIRECTORY "${dir}"
316 RESULT_VARIABLE download_result
320 if(NOT download_result EQUAL 0)
321 hunter_gate_internal_error(
322 "Configure project failed."
323 "To reproduce the error run: ${CMAKE_COMMAND} -H${dir} -B${build_dir} -G${CMAKE_GENERATOR} ${toolchain_arg} ${make_arg}"
324 "In directory ${dir}"
328 hunter_gate_status_print(
329 "Initializing Hunter workspace (${HUNTER_GATE_SHA1})"
330 " ${HUNTER_GATE_URL}"
334 COMMAND "${CMAKE_COMMAND}" --build "${build_dir}"
335 WORKING_DIRECTORY "${dir}"
336 RESULT_VARIABLE download_result
340 if(NOT download_result EQUAL 0)
341 hunter_gate_internal_error("Build project failed")
344 file(REMOVE_RECURSE "${build_dir}")
345 file(REMOVE_RECURSE "${cmakelists}")
347 file(WRITE "${sha1_location}" "${HUNTER_GATE_SHA1}")
348 file(WRITE "${done_location}" "DONE")
350 hunter_gate_status_debug("Finished")
353 # Must be a macro so master file 'cmake/Hunter' can
354 # apply all variables easily just by 'include' command
355 # (otherwise PARENT_SCOPE magic needed)
358 # variable HUNTER_GATE_DONE set explicitly for external project
359 # (see `hunter_download`)
360 set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES)
363 # First HunterGate command will init Hunter, others will be ignored
364 get_property(_hunter_gate_done GLOBAL PROPERTY HUNTER_GATE_DONE SET)
366 if(NOT HUNTER_ENABLED)
367 # Empty function to avoid error "unknown function"
368 function(hunter_add_package)
372 _hunter_gate_disabled_mode_dir
373 "${CMAKE_CURRENT_LIST_DIR}/cmake/Hunter/disabled-mode"
375 if(EXISTS "${_hunter_gate_disabled_mode_dir}")
376 hunter_gate_status_debug(
377 "Adding \"disabled-mode\" modules: ${_hunter_gate_disabled_mode_dir}"
379 list(APPEND CMAKE_PREFIX_PATH "${_hunter_gate_disabled_mode_dir}")
381 elseif(_hunter_gate_done)
382 hunter_gate_status_debug("Secondary HunterGate (use old settings)")
384 "${HUNTER_CACHED_ROOT}"
389 include("${_hunter_self}/cmake/Hunter")
391 set(HUNTER_GATE_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}")
393 string(COMPARE NOTEQUAL "${PROJECT_NAME}" "" _have_project_name)
394 if(_have_project_name)
395 hunter_gate_fatal_error(
396 "Please set HunterGate *before* 'project' command. "
397 "Detected project: ${PROJECT_NAME}"
398 ERROR_PAGE "error.huntergate.before.project"
402 cmake_parse_arguments(
403 HUNTER_GATE "LOCAL" "URL;SHA1;GLOBAL;FILEPATH" "" ${ARGV}
406 string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" _empty_sha1)
407 string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" _empty_url)
411 "${HUNTER_GATE_UNPARSED_ARGUMENTS}"
415 string(COMPARE NOTEQUAL "${HUNTER_GATE_GLOBAL}" "" _have_global)
416 string(COMPARE NOTEQUAL "${HUNTER_GATE_FILEPATH}" "" _have_filepath)
419 hunter_gate_user_error(
420 "HunterGate unparsed arguments: ${HUNTER_GATE_UNPARSED_ARGUMENTS}"
424 hunter_gate_user_error("SHA1 suboption of HunterGate is mandatory")
427 hunter_gate_user_error("URL suboption of HunterGate is mandatory")
430 if(HUNTER_GATE_LOCAL)
431 hunter_gate_user_error("Unexpected LOCAL (already has GLOBAL)")
434 hunter_gate_user_error("Unexpected FILEPATH (already has GLOBAL)")
437 if(HUNTER_GATE_LOCAL)
439 hunter_gate_user_error("Unexpected GLOBAL (already has LOCAL)")
442 hunter_gate_user_error("Unexpected FILEPATH (already has LOCAL)")
447 hunter_gate_user_error("Unexpected GLOBAL (already has FILEPATH)")
449 if(HUNTER_GATE_LOCAL)
450 hunter_gate_user_error("Unexpected LOCAL (already has FILEPATH)")
454 hunter_gate_detect_root() # set HUNTER_GATE_ROOT
456 # Beautify path, fix probable problems with windows path slashes
457 get_filename_component(
458 HUNTER_GATE_ROOT "${HUNTER_GATE_ROOT}" ABSOLUTE
460 hunter_gate_status_debug("HUNTER_ROOT: ${HUNTER_GATE_ROOT}")
461 if(NOT HUNTER_ALLOW_SPACES_IN_PATH)
462 string(FIND "${HUNTER_GATE_ROOT}" " " _contain_spaces)
463 if(NOT _contain_spaces EQUAL -1)
464 hunter_gate_fatal_error(
465 "HUNTER_ROOT (${HUNTER_GATE_ROOT}) contains spaces."
466 "Set HUNTER_ALLOW_SPACES_IN_PATH=ON to skip this error"
467 "(Use at your own risk!)"
468 ERROR_PAGE "error.spaces.in.hunter.root"
476 "[0-9]+\\.[0-9]+\\.[0-9]+[-_a-z0-9]*"
480 string(COMPARE EQUAL "${HUNTER_GATE_VERSION}" "" _is_empty)
482 set(HUNTER_GATE_VERSION "unknown")
486 "${HUNTER_GATE_ROOT}"
487 "${HUNTER_GATE_VERSION}"
488 "${HUNTER_GATE_SHA1}"
492 set(_master_location "${_hunter_self}/cmake/Hunter")
493 if(EXISTS "${HUNTER_GATE_ROOT}/cmake/Hunter")
494 # Hunter downloaded manually (e.g. by 'git clone')
495 set(_unused "xxxxxxxxxx")
496 set(HUNTER_GATE_SHA1 "${_unused}")
497 set(HUNTER_GATE_VERSION "${_unused}")
499 get_filename_component(_archive_id_location "${_hunter_self}/.." ABSOLUTE)
500 set(_done_location "${_archive_id_location}/DONE")
501 set(_sha1_location "${_archive_id_location}/SHA1")
503 # Check Hunter already downloaded by HunterGate
504 if(NOT EXISTS "${_done_location}")
505 hunter_gate_download("${_archive_id_location}")
508 if(NOT EXISTS "${_done_location}")
509 hunter_gate_internal_error("hunter_gate_download failed")
512 if(NOT EXISTS "${_sha1_location}")
513 hunter_gate_internal_error("${_sha1_location} not found")
515 file(READ "${_sha1_location}" _sha1_value)
516 string(TOLOWER "${_sha1_value}" _sha1_value_lower)
517 string(TOLOWER "${HUNTER_GATE_SHA1}" _HUNTER_GATE_SHA1_lower)
518 string(COMPARE EQUAL "${_sha1_value_lower}" "${_HUNTER_GATE_SHA1_lower}" _is_equal)
520 hunter_gate_internal_error(
521 "Short SHA1 collision:"
522 " ${_sha1_value} (from ${_sha1_location})"
523 " ${HUNTER_GATE_SHA1} (HunterGate)"
526 if(NOT EXISTS "${_master_location}")
527 hunter_gate_user_error(
528 "Master file not found:"
529 " ${_master_location}"
530 "try to update Hunter/HunterGate"
534 include("${_master_location}")
535 set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES)