Merge branch 'main/rendor-staging' into main/atys-live
[ryzomcore.git] / CMakeModules / HunterGate.cmake
blob64ccde563b43ccf5d699d516cb38adb401596deb
1 # Copyright (c) 2013-2019, Ruslan Baratov
2 # All rights reserved.
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")
31 #     HunterGate(
32 #         URL "https://github.com/path/to/hunter/archive.tar.gz"
33 #         SHA1 "798501e983f14b28b10cda16afa4de69eee1da1d"
34 #     )
36 #     project(MyProject)
38 #     hunter_add_package(Foo)
39 #     hunter_add_package(Boo COMPONENTS Bar Baz)
41 # Projects:
42 #     * https://github.com/hunter-packages/gate/
43 #     * https://github.com/ruslo/hunter
45 option(HUNTER_ENABLED "Enable Hunter package manager support" ON)
47 if(HUNTER_ENABLED)
48   if(CMAKE_VERSION VERSION_LESS "3.2")
49     message(
50         FATAL_ERROR
51         "At least CMake version 3.2 required for Hunter dependency management."
52         " Update CMake or set HUNTER_ENABLED to OFF."
53     )
54   endif()
55 endif()
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}")
70     endforeach()
71   endif()
72 endfunction()
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}")
79     endforeach()
80   endif()
81 endfunction()
83 function(hunter_gate_error_page error_page)
84   message("------------------------------ ERROR ------------------------------")
85   message("    ${HUNTER_ERROR_PAGE}/${error_page}.html")
86   message("-------------------------------------------------------------------")
87   message("")
88   message(FATAL_ERROR "")
89 endfunction()
91 function(hunter_gate_internal_error)
92   message("")
93   foreach(print_message ${ARGV})
94     message("[hunter ** INTERNAL **] ${print_message}")
95   endforeach()
96   message("[hunter ** INTERNAL **] [Directory:${CMAKE_CURRENT_LIST_DIR}]")
97   message("")
98   hunter_gate_error_page("error.internal")
99 endfunction()
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")
105   endif()
106   message("")
107   foreach(x ${hunter_UNPARSED_ARGUMENTS})
108     message("[hunter ** FATAL ERROR **] ${x}")
109   endforeach()
110   message("[hunter ** FATAL ERROR **] [Directory:${CMAKE_CURRENT_LIST_DIR}]")
111   message("")
112   hunter_gate_error_page("${hunter_ERROR_PAGE}")
113 endfunction()
115 function(hunter_gate_user_error)
116   hunter_gate_fatal_error(${ARGV} ERROR_PAGE "error.incorrect.input.data")
117 endfunction()
119 function(hunter_gate_self root version sha1 result)
120   string(COMPARE EQUAL "${root}" "" is_bad)
121   if(is_bad)
122     hunter_gate_internal_error("root is empty")
123   endif()
125   string(COMPARE EQUAL "${version}" "" is_bad)
126   if(is_bad)
127     hunter_gate_internal_error("version is empty")
128   endif()
130   string(COMPARE EQUAL "${sha1}" "" is_bad)
131   if(is_bad)
132     hunter_gate_internal_error("sha1 is empty")
133   endif()
135   string(SUBSTRING "${sha1}" 0 7 archive_id)
137   if(EXISTS "${root}/cmake/Hunter")
138     set(hunter_self "${root}")
139   else()
140     set(
141         hunter_self
142         "${root}/_Base/Download/Hunter/${version}/${archive_id}/Unpacked"
143     )
144   endif()
146   set("${result}" "${hunter_self}" PARENT_SCOPE)
147 endfunction()
149 # Set HUNTER_GATE_ROOT cmake variable to suitable value.
150 function(hunter_gate_detect_root)
151   # Check CMake variable
152   if(HUNTER_ROOT)
153     set(HUNTER_GATE_ROOT "${HUNTER_ROOT}" PARENT_SCOPE)
154     hunter_gate_status_debug("HUNTER_ROOT detected by cmake variable")
155     return()
156   endif()
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")
162     return()
163   endif()
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")
169     return()
170   endif()
172   # Check SYSTEMDRIVE and USERPROFILE environment variable (windows only)
173   if(WIN32)
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"
178       )
179       return()
180     endif()
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"
186       )
187       return()
188     endif()
189   endif()
191   hunter_gate_fatal_error(
192       "Can't detect HUNTER_ROOT"
193       ERROR_PAGE "error.detect.hunter.root"
194   )
195 endfunction()
197 function(hunter_gate_download dir)
198   string(
199       COMPARE
200       NOTEQUAL
201       "$ENV{HUNTER_DISABLE_AUTOINSTALL}"
202       ""
203       disable_autoinstall
204   )
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}'"
209         "Settings:"
210         "  HUNTER_ROOT: ${HUNTER_GATE_ROOT}"
211         "  HUNTER_SHA1: ${HUNTER_GATE_SHA1}"
212         ERROR_PAGE "error.run.install"
213     )
214   endif()
215   string(COMPARE EQUAL "${dir}" "" is_bad)
216   if(is_bad)
217     hunter_gate_internal_error("Empty 'dir' argument")
218   endif()
220   string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" is_bad)
221   if(is_bad)
222     hunter_gate_internal_error("HUNTER_GATE_SHA1 empty")
223   endif()
225   string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" is_bad)
226   if(is_bad)
227     hunter_gate_internal_error("HUNTER_GATE_URL empty")
228   endif()
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")
243     return()
244   endif()
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
253   file(
254       WRITE
255       "${cmakelists}"
256       "cmake_minimum_required(VERSION 3.2)\n"
257       "project(HunterDownload LANGUAGES NONE)\n"
258       "include(ExternalProject)\n"
259       "ExternalProject_Add(\n"
260       "    Hunter\n"
261       "    URL\n"
262       "    \"${HUNTER_GATE_URL}\"\n"
263       "    URL_HASH\n"
264       "    SHA1=${HUNTER_GATE_SHA1}\n"
265       "    DOWNLOAD_DIR\n"
266       "    \"${dir}\"\n"
267       "    TLS_VERIFY\n"
268       "    ${HUNTER_TLS_VERIFY}\n"
269       "    SOURCE_DIR\n"
270       "    \"${dir}/Unpacked\"\n"
271       "    CONFIGURE_COMMAND\n"
272       "    \"\"\n"
273       "    BUILD_COMMAND\n"
274       "    \"\"\n"
275       "    INSTALL_COMMAND\n"
276       "    \"\"\n"
277       ")\n"
278   )
280   if(HUNTER_STATUS_DEBUG)
281     set(logging_params "")
282   else()
283     set(logging_params OUTPUT_QUIET)
284   endif()
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}")
294   else()
295     # 'toolchain_arg' can't be empty
296     set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=")
297   endif()
299   string(COMPARE EQUAL "${CMAKE_MAKE_PROGRAM}" "" no_make)
300   if(no_make)
301     set(make_arg "")
302   else()
303     # Test case: remove Ninja from PATH but set it via CMAKE_MAKE_PROGRAM
304     set(make_arg "-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}")
305   endif()
307   execute_process(
308       COMMAND
309       "${CMAKE_COMMAND}"
310       "-H${dir}"
311       "-B${build_dir}"
312       "-G${CMAKE_GENERATOR}"
313       "${toolchain_arg}"
314       ${make_arg}
315       WORKING_DIRECTORY "${dir}"
316       RESULT_VARIABLE download_result
317       ${logging_params}
318   )
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}"
325     )
326   endif()
328   hunter_gate_status_print(
329       "Initializing Hunter workspace (${HUNTER_GATE_SHA1})"
330       "  ${HUNTER_GATE_URL}"
331       "  -> ${dir}"
332   )
333   execute_process(
334       COMMAND "${CMAKE_COMMAND}" --build "${build_dir}"
335       WORKING_DIRECTORY "${dir}"
336       RESULT_VARIABLE download_result
337       ${logging_params}
338   )
340   if(NOT download_result EQUAL 0)
341     hunter_gate_internal_error("Build project failed")
342   endif()
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")
351 endfunction()
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)
356 macro(HunterGate)
357   if(HUNTER_GATE_DONE)
358     # variable HUNTER_GATE_DONE set explicitly for external project
359     # (see `hunter_download`)
360     set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES)
361   endif()
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)
369     endfunction()
371     set(
372         _hunter_gate_disabled_mode_dir
373         "${CMAKE_CURRENT_LIST_DIR}/cmake/Hunter/disabled-mode"
374     )
375     if(EXISTS "${_hunter_gate_disabled_mode_dir}")
376       hunter_gate_status_debug(
377           "Adding \"disabled-mode\" modules: ${_hunter_gate_disabled_mode_dir}"
378       )
379       list(APPEND CMAKE_PREFIX_PATH "${_hunter_gate_disabled_mode_dir}")
380     endif()
381   elseif(_hunter_gate_done)
382     hunter_gate_status_debug("Secondary HunterGate (use old settings)")
383     hunter_gate_self(
384         "${HUNTER_CACHED_ROOT}"
385         "${HUNTER_VERSION}"
386         "${HUNTER_SHA1}"
387         _hunter_self
388     )
389     include("${_hunter_self}/cmake/Hunter")
390   else()
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"
399       )
400     endif()
402     cmake_parse_arguments(
403         HUNTER_GATE "LOCAL" "URL;SHA1;GLOBAL;FILEPATH" "" ${ARGV}
404     )
406     string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" _empty_sha1)
407     string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" _empty_url)
408     string(
409         COMPARE
410         NOTEQUAL
411         "${HUNTER_GATE_UNPARSED_ARGUMENTS}"
412         ""
413         _have_unparsed
414     )
415     string(COMPARE NOTEQUAL "${HUNTER_GATE_GLOBAL}" "" _have_global)
416     string(COMPARE NOTEQUAL "${HUNTER_GATE_FILEPATH}" "" _have_filepath)
418     if(_have_unparsed)
419       hunter_gate_user_error(
420           "HunterGate unparsed arguments: ${HUNTER_GATE_UNPARSED_ARGUMENTS}"
421       )
422     endif()
423     if(_empty_sha1)
424       hunter_gate_user_error("SHA1 suboption of HunterGate is mandatory")
425     endif()
426     if(_empty_url)
427       hunter_gate_user_error("URL suboption of HunterGate is mandatory")
428     endif()
429     if(_have_global)
430       if(HUNTER_GATE_LOCAL)
431         hunter_gate_user_error("Unexpected LOCAL (already has GLOBAL)")
432       endif()
433       if(_have_filepath)
434         hunter_gate_user_error("Unexpected FILEPATH (already has GLOBAL)")
435       endif()
436     endif()
437     if(HUNTER_GATE_LOCAL)
438       if(_have_global)
439         hunter_gate_user_error("Unexpected GLOBAL (already has LOCAL)")
440       endif()
441       if(_have_filepath)
442         hunter_gate_user_error("Unexpected FILEPATH (already has LOCAL)")
443       endif()
444     endif()
445     if(_have_filepath)
446       if(_have_global)
447         hunter_gate_user_error("Unexpected GLOBAL (already has FILEPATH)")
448       endif()
449       if(HUNTER_GATE_LOCAL)
450         hunter_gate_user_error("Unexpected LOCAL (already has FILEPATH)")
451       endif()
452     endif()
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
459     )
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"
469         )
470       endif()
471     endif()
473     string(
474         REGEX
475         MATCH
476         "[0-9]+\\.[0-9]+\\.[0-9]+[-_a-z0-9]*"
477         HUNTER_GATE_VERSION
478         "${HUNTER_GATE_URL}"
479     )
480     string(COMPARE EQUAL "${HUNTER_GATE_VERSION}" "" _is_empty)
481     if(_is_empty)
482       set(HUNTER_GATE_VERSION "unknown")
483     endif()
485     hunter_gate_self(
486         "${HUNTER_GATE_ROOT}"
487         "${HUNTER_GATE_VERSION}"
488         "${HUNTER_GATE_SHA1}"
489         _hunter_self
490     )
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}")
498     else()
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}")
506       endif()
508       if(NOT EXISTS "${_done_location}")
509         hunter_gate_internal_error("hunter_gate_download failed")
510       endif()
512       if(NOT EXISTS "${_sha1_location}")
513         hunter_gate_internal_error("${_sha1_location} not found")
514       endif()
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)
519       if(NOT _is_equal)
520         hunter_gate_internal_error(
521             "Short SHA1 collision:"
522             "  ${_sha1_value} (from ${_sha1_location})"
523             "  ${HUNTER_GATE_SHA1} (HunterGate)"
524         )
525       endif()
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"
531         )
532       endif()
533     endif()
534     include("${_master_location}")
535     set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES)
536   endif()
537 endmacro()