diff options
Diffstat (limited to 'Modules')
20 files changed, 1887 insertions, 1088 deletions
diff --git a/Modules/ExternalProject-download.cmake.in b/Modules/ExternalProject-download.cmake.in deleted file mode 100644 index ff8c659..0000000 --- a/Modules/ExternalProject-download.cmake.in +++ /dev/null @@ -1,173 +0,0 @@ -# Distributed under the OSI-approved BSD 3-Clause License. See accompanying -# file Copyright.txt or https://cmake.org/licensing for details. - -cmake_minimum_required(VERSION 3.5) - -function(check_file_hash has_hash hash_is_good) - if("${has_hash}" STREQUAL "") - message(FATAL_ERROR "has_hash Can't be empty") - endif() - - if("${hash_is_good}" STREQUAL "") - message(FATAL_ERROR "hash_is_good Can't be empty") - endif() - - if("@ALGO@" STREQUAL "") - # No check - set("${has_hash}" FALSE PARENT_SCOPE) - set("${hash_is_good}" FALSE PARENT_SCOPE) - return() - endif() - - set("${has_hash}" TRUE PARENT_SCOPE) - - message(STATUS "verifying file... - file='@LOCAL@'") - - file("@ALGO@" "@LOCAL@" actual_value) - - if(NOT "${actual_value}" STREQUAL "@EXPECT_VALUE@") - set("${hash_is_good}" FALSE PARENT_SCOPE) - message(STATUS "@ALGO@ hash of - @LOCAL@ - does not match expected value - expected: '@EXPECT_VALUE@' - actual: '${actual_value}'") - else() - set("${hash_is_good}" TRUE PARENT_SCOPE) - endif() -endfunction() - -function(sleep_before_download attempt) - if(attempt EQUAL 0) - return() - endif() - - if(attempt EQUAL 1) - message(STATUS "Retrying...") - return() - endif() - - set(sleep_seconds 0) - - if(attempt EQUAL 2) - set(sleep_seconds 5) - elseif(attempt EQUAL 3) - set(sleep_seconds 5) - elseif(attempt EQUAL 4) - set(sleep_seconds 15) - elseif(attempt EQUAL 5) - set(sleep_seconds 60) - elseif(attempt EQUAL 6) - set(sleep_seconds 90) - elseif(attempt EQUAL 7) - set(sleep_seconds 300) - else() - set(sleep_seconds 1200) - endif() - - message(STATUS "Retry after ${sleep_seconds} seconds (attempt #${attempt}) ...") - - execute_process(COMMAND "${CMAKE_COMMAND}" -E sleep "${sleep_seconds}") -endfunction() - -if("@LOCAL@" STREQUAL "") - message(FATAL_ERROR "LOCAL can't be empty") -endif() - -if("@REMOTE@" STREQUAL "") - message(FATAL_ERROR "REMOTE can't be empty") -endif() - -if(EXISTS "@LOCAL@") - check_file_hash(has_hash hash_is_good) - if(has_hash) - if(hash_is_good) - message(STATUS "File already exists and hash match (skip download): - file='@LOCAL@' - @ALGO@='@EXPECT_VALUE@'" - ) - return() - else() - message(STATUS "File already exists but hash mismatch. Removing...") - file(REMOVE "@LOCAL@") - endif() - else() - message(STATUS "File already exists but no hash specified (use URL_HASH): - file='@LOCAL@' -Old file will be removed and new file downloaded from URL." - ) - file(REMOVE "@LOCAL@") - endif() -endif() - -set(retry_number 5) - -message(STATUS "Downloading... - dst='@LOCAL@' - timeout='@TIMEOUT_MSG@' - inactivity timeout='@INACTIVITY_TIMEOUT_MSG@'" -) -set(download_retry_codes 7 6 8 15) -set(skip_url_list) -set(status_code) -foreach(i RANGE ${retry_number}) - if(status_code IN_LIST download_retry_codes) - sleep_before_download(${i}) - endif() - foreach(url @REMOTE@) - if(NOT url IN_LIST skip_url_list) - message(STATUS "Using src='${url}'") - - @TLS_VERIFY_CODE@ - @TLS_CAINFO_CODE@ - @NETRC_CODE@ - @NETRC_FILE_CODE@ - - file( - DOWNLOAD - "${url}" "@LOCAL@" - @SHOW_PROGRESS@ - @TIMEOUT_ARGS@ - @INACTIVITY_TIMEOUT_ARGS@ - STATUS status - LOG log - @USERPWD_ARGS@ - @HTTP_HEADERS_ARGS@ - ) - - list(GET status 0 status_code) - list(GET status 1 status_string) - - if(status_code EQUAL 0) - check_file_hash(has_hash hash_is_good) - if(has_hash AND NOT hash_is_good) - message(STATUS "Hash mismatch, removing...") - file(REMOVE "@LOCAL@") - else() - message(STATUS "Downloading... done") - return() - endif() - else() - string(APPEND logFailedURLs "error: downloading '${url}' failed - status_code: ${status_code} - status_string: ${status_string} - log: - --- LOG BEGIN --- - ${log} - --- LOG END --- - " - ) - if(NOT status_code IN_LIST download_retry_codes) - list(APPEND skip_url_list "${url}") - break() - endif() - endif() - endif() - endforeach() -endforeach() - -message(FATAL_ERROR "Each download failed! - ${logFailedURLs} - " -) diff --git a/Modules/ExternalProject-verify.cmake.in b/Modules/ExternalProject-verify.cmake.in deleted file mode 100644 index c06da4e..0000000 --- a/Modules/ExternalProject-verify.cmake.in +++ /dev/null @@ -1,37 +0,0 @@ -# Distributed under the OSI-approved BSD 3-Clause License. See accompanying -# file Copyright.txt or https://cmake.org/licensing for details. - -cmake_minimum_required(VERSION 3.5) - -if("@LOCAL@" STREQUAL "") - message(FATAL_ERROR "LOCAL can't be empty") -endif() - -if(NOT EXISTS "@LOCAL@") - message(FATAL_ERROR "File not found: @LOCAL@") -endif() - -if("@ALGO@" STREQUAL "") - message(WARNING "File will not be verified since no URL_HASH specified") - return() -endif() - -if("@EXPECT_VALUE@" STREQUAL "") - message(FATAL_ERROR "EXPECT_VALUE can't be empty") -endif() - -message(STATUS "verifying file... - file='@LOCAL@'") - -file("@ALGO@" "@LOCAL@" actual_value) - -if(NOT "${actual_value}" STREQUAL "@EXPECT_VALUE@") - message(FATAL_ERROR "error: @ALGO@ hash of - @LOCAL@ -does not match expected value - expected: '@EXPECT_VALUE@' - actual: '${actual_value}' -") -endif() - -message(STATUS "verifying file... done") diff --git a/Modules/ExternalProject.cmake b/Modules/ExternalProject.cmake index 903dd57..2b413c2 100644 --- a/Modules/ExternalProject.cmake +++ b/Modules/ExternalProject.cmake @@ -1,6 +1,8 @@ # Distributed under the OSI-approved BSD 3-Clause License. See accompanying # file Copyright.txt or https://cmake.org/licensing for details. +include_guard(GLOBAL) + #[=======================================================================[.rst: ExternalProject --------------- @@ -405,7 +407,7 @@ External Project Definition ``CVS_TAG <tag>`` Tag to checkout from the CVS repository. - **Update/Patch Step Options:** + **Update Step Options:** Whenever CMake is re-run, by default the external project's sources will be updated if the download method supports updates (e.g. a git repository would be checked if the ``GIT_TAG`` does not refer to a specific commit). @@ -440,6 +442,7 @@ External Project Definition This may cause a step target to be created automatically for the ``download`` step. See policy :policy:`CMP0114`. + **Patch Step Options:** ``PATCH_COMMAND <cmd>...`` Specifies a custom command to patch the sources after an update. By default, no patch command is defined. Note that it can be quite difficult @@ -715,6 +718,11 @@ External Project Definition ``USES_TERMINAL_UPDATE <bool>`` Give the update step access to the terminal. + ``USES_TERMINAL_PATCH <bool>`` + .. versionadded:: 3.20 + + Give the patch step access to the terminal. + ``USES_TERMINAL_CONFIGURE <bool>`` Give the configure step access to the terminal. @@ -1111,59 +1119,37 @@ cmake_policy(PUSH) cmake_policy(SET CMP0054 NEW) # if() quoted variables not dereferenced cmake_policy(SET CMP0057 NEW) # if() supports IN_LIST -# Pre-compute a regex to match documented keywords for each command. -math(EXPR _ep_documentation_line_count "${CMAKE_CURRENT_LIST_LINE} - 4") -file(STRINGS "${CMAKE_CURRENT_LIST_FILE}" lines - LIMIT_COUNT ${_ep_documentation_line_count} - REGEX "^\\.\\. command:: [A-Za-z0-9_]+|^ +``[A-Z0-9_]+ [^`]*``$") -foreach(line IN LISTS lines) - if("${line}" MATCHES "^\\.\\. command:: ([A-Za-z0-9_]+)") - if(_ep_func) - string(APPEND _ep_keywords_${_ep_func} ")$") - endif() - set(_ep_func "${CMAKE_MATCH_1}") - #message("function [${_ep_func}]") - set(_ep_keywords_${_ep_func} "^(") - set(_ep_keyword_sep) - elseif("${line}" MATCHES "^ +``([A-Z0-9_]+) [^`]*``$") - set(_ep_key "${CMAKE_MATCH_1}") - # COMMAND should never be included as a keyword, - # for ExternalProject_Add(), as it is treated as a - # special case by argument parsing as an extension - # of a previous ..._COMMAND - if("x${_ep_key}x" STREQUAL "xCOMMANDx" AND - "x${_ep_func}x" STREQUAL "xExternalProject_Addx") - continue() - endif() - #message(" keyword [${_ep_key}]") - string(APPEND _ep_keywords_${_ep_func} - "${_ep_keyword_sep}${_ep_key}") - set(_ep_keyword_sep "|") - endif() -endforeach() -if(_ep_func) - string(APPEND _ep_keywords_${_ep_func} ")$") -endif() - -# Save regex matching supported hash algorithm names. -set(_ep_hash_algos "MD5|SHA1|SHA224|SHA256|SHA384|SHA512|SHA3_224|SHA3_256|SHA3_384|SHA3_512") -set(_ep_hash_regex "^(${_ep_hash_algos})=([0-9A-Fa-f]+)$") +macro(_ep_get_hash_algos out_var) + set(${out_var} + MD5 + SHA1 + SHA224 + SHA256 + SHA384 + SHA512 + SHA3_224 + SHA3_256 + SHA3_384 + SHA3_512 + ) +endmacro() -set(_ExternalProject_SELF "${CMAKE_CURRENT_LIST_FILE}") -get_filename_component(_ExternalProject_SELF_DIR "${_ExternalProject_SELF}" PATH) +macro(_ep_get_hash_regex out_var) + _ep_get_hash_algos(${out_var}) + list(JOIN ${out_var} "|" ${out_var}) + set(${out_var} "^(${${out_var}})=([0-9A-Fa-f]+)$") +endmacro() -function(_ep_parse_arguments f name ns args) - # Transfer the arguments to this function into target properties for the - # new custom target we just added so that we can set up all the build steps - # correctly based on target properties. - # - # We loop through ARGN and consider the namespace starting with an - # upper-case letter followed by at least two more upper-case letters, +function(_ep_parse_arguments_to_vars keywords name ns args) + # Transfer the arguments into variables in the calling scope. + # Because some keywords can be repeated, we can't use cmake_parse_arguments(). + # Instead, we loop through the args and consider the namespace starting with + # an upper-case letter followed by at least two more upper-case letters, # numbers or underscores to be keywords. - if(NOT DEFINED _ExternalProject_SELF) - message(FATAL_ERROR "error: ExternalProject module must be explicitly included before using ${f} function") - endif() + foreach(key IN LISTS keywords) + unset(${ns}${key}) + endforeach() set(key) @@ -1173,7 +1159,7 @@ function(_ep_parse_arguments f name ns args) if(arg MATCHES "^[A-Z][A-Z0-9_][A-Z0-9_]+$" AND NOT (("x${arg}x" STREQUAL "x${key}x") AND ("x${key}x" STREQUAL "xCOMMANDx")) AND NOT arg MATCHES "^(TRUE|FALSE)$") - if(_ep_keywords_${f} AND arg MATCHES "${_ep_keywords_${f}}") + if(arg IN_LIST keywords) set(is_value 0) endif() endif() @@ -1181,71 +1167,100 @@ function(_ep_parse_arguments f name ns args) if(is_value) if(key) # Value - if(NOT arg STREQUAL "") - set_property(TARGET ${name} APPEND PROPERTY ${ns}${key} "${arg}") - else() - get_property(have_key TARGET ${name} PROPERTY ${ns}${key} SET) - if(have_key) - get_property(value TARGET ${name} PROPERTY ${ns}${key}) - set_property(TARGET ${name} PROPERTY ${ns}${key} "${value};${arg}") - else() - set_property(TARGET ${name} PROPERTY ${ns}${key} "${arg}") - endif() - endif() + list(APPEND ${ns}${key} "${arg}") else() # Missing Keyword - message(AUTHOR_WARNING "value '${arg}' with no previous keyword in ${f}") + message(AUTHOR_WARNING "value '${arg}' with no previous keyword") endif() else() set(key "${arg}") - if(key MATCHES GIT) - get_property(have_key TARGET ${name} PROPERTY ${ns}${key} SET) - endif() endif() endforeach() + + foreach(key IN LISTS keywords) + if(DEFINED ${ns}${key}) + set(${ns}${key} "${${ns}${key}}" PARENT_SCOPE) + else() + unset(${ns}${key} PARENT_SCOPE) + endif() + endforeach() + endfunction() +function(_ep_parse_arguments keywords name ns args) + _ep_parse_arguments_to_vars("${keywords}" ${name} ${ns} "${args}") -define_property(DIRECTORY PROPERTY "EP_BASE" INHERITED - BRIEF_DOCS "Base directory for External Project storage." - FULL_DOCS - "See documentation of the ExternalProject_Add() function in the " - "ExternalProject module." - ) + # Transfer the arguments to the target as target properties. These are + # read by the various steps, potentially from different scopes. + foreach(key IN LISTS keywords) + if(DEFINED ${ns}${key}) + set_property(TARGET ${name} PROPERTY ${ns}${key} "${${ns}${key}}") + endif() + endforeach() -define_property(DIRECTORY PROPERTY "EP_PREFIX" INHERITED - BRIEF_DOCS "Top prefix for External Project storage." - FULL_DOCS - "See documentation of the ExternalProject_Add() function in the " - "ExternalProject module." - ) +endfunction() -define_property(DIRECTORY PROPERTY "EP_STEP_TARGETS" INHERITED - BRIEF_DOCS - "List of ExternalProject steps that automatically get corresponding targets" - FULL_DOCS - "These targets will be dependent on the main target dependencies. " - "See documentation of the ExternalProject_Add_StepTargets() function in the " - "ExternalProject module." - ) +if(NOT DEFINED CMAKE_SCRIPT_MODE_FILE) + define_property(DIRECTORY PROPERTY "EP_BASE" INHERITED + BRIEF_DOCS "Base directory for External Project storage." + FULL_DOCS + "See documentation of the ExternalProject_Add() function in the " + "ExternalProject module." + ) -define_property(DIRECTORY PROPERTY "EP_INDEPENDENT_STEP_TARGETS" INHERITED - BRIEF_DOCS - "List of ExternalProject steps that automatically get corresponding targets" - FULL_DOCS - "These targets will not be dependent on the main target dependencies. " - "See documentation of the ExternalProject_Add_StepTargets() function in the " - "ExternalProject module." - ) + define_property(DIRECTORY PROPERTY "EP_PREFIX" INHERITED + BRIEF_DOCS "Top prefix for External Project storage." + FULL_DOCS + "See documentation of the ExternalProject_Add() function in the " + "ExternalProject module." + ) -define_property(DIRECTORY PROPERTY "EP_UPDATE_DISCONNECTED" INHERITED - BRIEF_DOCS "Never update automatically from the remote repo." - FULL_DOCS - "See documentation of the ExternalProject_Add() function in the " - "ExternalProject module." - ) + define_property(DIRECTORY PROPERTY "EP_STEP_TARGETS" INHERITED + BRIEF_DOCS + "List of ExternalProject steps that automatically get corresponding targets" + FULL_DOCS + "These targets will be dependent on the main target dependencies. " + "See documentation of the ExternalProject_Add_StepTargets() function in the " + "ExternalProject module." + ) + + define_property(DIRECTORY PROPERTY "EP_INDEPENDENT_STEP_TARGETS" INHERITED + BRIEF_DOCS + "List of ExternalProject steps that automatically get corresponding targets" + FULL_DOCS + "These targets will not be dependent on the main target dependencies. " + "See documentation of the ExternalProject_Add_StepTargets() function in the " + "ExternalProject module." + ) + + define_property(DIRECTORY PROPERTY "EP_UPDATE_DISCONNECTED" INHERITED + BRIEF_DOCS "Never update automatically from the remote repo." + FULL_DOCS + "See documentation of the ExternalProject_Add() function in the " + "ExternalProject module." + ) +endif() + +function(_ep_write_gitclone_script + script_filename + source_dir + git_EXECUTABLE + git_repository + git_tag + git_remote_name + init_submodules + git_submodules_recurse + git_submodules + git_shallow + git_progress + git_config + src_name + work_dir + gitclone_infofile + gitclone_stampfile + tls_verify + quiet) -function(_ep_write_gitclone_script script_filename source_dir git_EXECUTABLE git_repository git_tag git_remote_name init_submodules git_submodules_recurse git_submodules git_shallow git_progress git_config src_name work_dir gitclone_infofile gitclone_stampfile tls_verify) if(NOT GIT_VERSION_STRING VERSION_LESS 1.8.5) # Use `git checkout <tree-ish> --` to avoid ambiguity with a local path. set(git_checkout_explicit-- "--") @@ -1291,134 +1306,52 @@ function(_ep_write_gitclone_script script_filename source_dir git_EXECUTABLE git endif() string (REPLACE ";" " " git_options "${git_options}") - file(WRITE ${script_filename} -" -if(NOT \"${gitclone_infofile}\" IS_NEWER_THAN \"${gitclone_stampfile}\") - message(STATUS \"Avoiding repeated git clone, stamp file is up to date: '${gitclone_stampfile}'\") - return() -endif() - -execute_process( - COMMAND \${CMAKE_COMMAND} -E rm -rf \"${source_dir}\" - RESULT_VARIABLE error_code - ) -if(error_code) - message(FATAL_ERROR \"Failed to remove directory: '${source_dir}'\") -endif() - -# try the clone 3 times in case there is an odd git clone issue -set(error_code 1) -set(number_of_tries 0) -while(error_code AND number_of_tries LESS 3) - execute_process( - COMMAND \"${git_EXECUTABLE}\" ${git_options} clone ${git_clone_options} \"${git_repository}\" \"${src_name}\" - WORKING_DIRECTORY \"${work_dir}\" - RESULT_VARIABLE error_code - ) - math(EXPR number_of_tries \"\${number_of_tries} + 1\") -endwhile() -if(number_of_tries GREATER 1) - message(STATUS \"Had to git clone more than once: - \${number_of_tries} times.\") -endif() -if(error_code) - message(FATAL_ERROR \"Failed to clone repository: '${git_repository}'\") -endif() - -execute_process( - COMMAND \"${git_EXECUTABLE}\" ${git_options} checkout ${git_tag} ${git_checkout_explicit--} - WORKING_DIRECTORY \"${work_dir}/${src_name}\" - RESULT_VARIABLE error_code - ) -if(error_code) - message(FATAL_ERROR \"Failed to checkout tag: '${git_tag}'\") -endif() - -set(init_submodules ${init_submodules}) -if(init_submodules) - execute_process( - COMMAND \"${git_EXECUTABLE}\" ${git_options} submodule update ${git_submodules_recurse} --init ${git_submodules} - WORKING_DIRECTORY \"${work_dir}/${src_name}\" - RESULT_VARIABLE error_code - ) -endif() -if(error_code) - message(FATAL_ERROR \"Failed to update submodules in: '${work_dir}/${src_name}'\") -endif() - -# Complete success, update the script-last-run stamp file: -# -execute_process( - COMMAND \${CMAKE_COMMAND} -E copy - \"${gitclone_infofile}\" - \"${gitclone_stampfile}\" - RESULT_VARIABLE error_code + configure_file( + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject/gitclone.cmake.in + ${script_filename} + @ONLY ) -if(error_code) - message(FATAL_ERROR \"Failed to copy script-last-run stamp file: '${gitclone_stampfile}'\") -endif() - -" -) endfunction() -function(_ep_write_hgclone_script script_filename source_dir hg_EXECUTABLE hg_repository hg_tag src_name work_dir hgclone_infofile hgclone_stampfile) +function(_ep_write_hgclone_script + script_filename + source_dir + hg_EXECUTABLE + hg_repository + hg_tag + src_name + work_dir + hgclone_infofile + hgclone_stampfile + quiet) + if("${hg_tag}" STREQUAL "") message(FATAL_ERROR "Tag for hg checkout should not be empty.") endif() - file(WRITE ${script_filename} -" -if(NOT \"${hgclone_infofile}\" IS_NEWER_THAN \"${hgclone_stampfile}\") - message(STATUS \"Avoiding repeated hg clone, stamp file is up to date: '${hgclone_stampfile}'\") - return() -endif() - -execute_process( - COMMAND \${CMAKE_COMMAND} -E rm -rf \"${source_dir}\" - RESULT_VARIABLE error_code - ) -if(error_code) - message(FATAL_ERROR \"Failed to remove directory: '${source_dir}'\") -endif() - -execute_process( - COMMAND \"${hg_EXECUTABLE}\" clone -U \"${hg_repository}\" \"${src_name}\" - WORKING_DIRECTORY \"${work_dir}\" - RESULT_VARIABLE error_code - ) -if(error_code) - message(FATAL_ERROR \"Failed to clone repository: '${hg_repository}'\") -endif() - -execute_process( - COMMAND \"${hg_EXECUTABLE}\" update ${hg_tag} - WORKING_DIRECTORY \"${work_dir}/${src_name}\" - RESULT_VARIABLE error_code - ) -if(error_code) - message(FATAL_ERROR \"Failed to checkout tag: '${hg_tag}'\") -endif() -# Complete success, update the script-last-run stamp file: -# -execute_process( - COMMAND \${CMAKE_COMMAND} -E copy - \"${hgclone_infofile}\" - \"${hgclone_stampfile}\" - RESULT_VARIABLE error_code + configure_file( + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject/hgclone.cmake.in + ${script_filename} + @ONLY ) -if(error_code) - message(FATAL_ERROR \"Failed to copy script-last-run stamp file: '${hgclone_stampfile}'\") -endif() - -" -) endfunction() -function(_ep_write_gitupdate_script script_filename git_EXECUTABLE git_tag git_remote_name init_submodules git_submodules_recurse git_submodules git_repository work_dir git_update_strategy) +function(_ep_write_gitupdate_script + script_filename + git_EXECUTABLE + git_tag + git_remote_name + init_submodules + git_submodules_recurse + git_submodules + git_repository + work_dir + git_update_strategy + quiet) + if("${git_tag}" STREQUAL "") message(FATAL_ERROR "Tag for git checkout should not be empty.") endif() @@ -1432,13 +1365,56 @@ function(_ep_write_gitupdate_script script_filename git_EXECUTABLE git_tag git_r endif() configure_file( - "${_ExternalProject_SELF_DIR}/ExternalProject-gitupdate.cmake.in" - "${script_filename}" - @ONLY + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject/gitupdate.cmake.in" + "${script_filename}" + @ONLY + ) +endfunction() + +function(_ep_write_hgupdate_script + script_filename + hg_EXECUTABLE + hg_tag + work_dir + quiet) + + configure_file( + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject/hgupdate.cmake.in + ${script_filename} + @ONLY + ) + +endfunction() + +function(_ep_write_copydir_script + script_filename + from_dir + to_dir) + + configure_file( + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject/copydir.cmake.in" + "${script_filename}" + @ONLY ) endfunction() -function(_ep_write_downloadfile_script script_filename REMOTE LOCAL timeout inactivity_timeout no_progress hash tls_verify tls_cainfo userpwd http_headers netrc netrc_file) +function(_ep_write_downloadfile_script + script_filename + REMOTE + LOCAL + timeout + inactivity_timeout + no_progress + hash + tls_verify + tls_cainfo + userpwd + http_headers + netrc + netrc_file + extract_script_filename + quiet) + if(timeout) set(TIMEOUT_ARGS TIMEOUT ${timeout}) set(TIMEOUT_MSG "${timeout} seconds") @@ -1455,12 +1431,13 @@ function(_ep_write_downloadfile_script script_filename REMOTE LOCAL timeout inac endif() - if(no_progress) + if(no_progress OR quiet) set(SHOW_PROGRESS "") else() set(SHOW_PROGRESS "SHOW_PROGRESS") endif() + _ep_get_hash_regex(_ep_hash_regex) if("${hash}" MATCHES "${_ep_hash_regex}") set(ALGO "${CMAKE_MATCH_1}") string(TOLOWER "${CMAKE_MATCH_2}" EXPECT_VALUE) @@ -1541,13 +1518,20 @@ function(_ep_write_downloadfile_script script_filename REMOTE LOCAL timeout inac # * USERPWD_ARGS # * HTTP_HEADERS_ARGS configure_file( - "${_ExternalProject_SELF_DIR}/ExternalProject-download.cmake.in" - "${script_filename}" - @ONLY + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject/download.cmake.in" + "${script_filename}" + @ONLY ) endfunction() -function(_ep_write_verifyfile_script script_filename LOCAL hash) +function(_ep_write_verifyfile_script + script_filename + LOCAL + hash + extract_script_filename + quiet) + + _ep_get_hash_regex(_ep_hash_regex) if("${hash}" MATCHES "${_ep_hash_regex}") set(ALGO "${CMAKE_MATCH_1}") string(TOLOWER "${CMAKE_MATCH_2}" EXPECT_VALUE) @@ -1560,15 +1544,22 @@ function(_ep_write_verifyfile_script script_filename LOCAL hash) # * ALGO # * EXPECT_VALUE # * LOCAL + # * extract_script_filename configure_file( - "${_ExternalProject_SELF_DIR}/ExternalProject-verify.cmake.in" - "${script_filename}" - @ONLY + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject/verify.cmake.in" + "${script_filename}" + @ONLY ) endfunction() -function(_ep_write_extractfile_script script_filename name filename directory) +function(_ep_write_extractfile_script + script_filename + name + filename + directory + quiet) + set(args "") if(filename MATCHES "(\\.|=)(7z|tar\\.bz2|tar\\.gz|tar\\.xz|tbz2|tgz|txz|zip)$") @@ -1580,77 +1571,34 @@ function(_ep_write_extractfile_script script_filename name filename directory) endif() if(args STREQUAL "") - message(SEND_ERROR "error: do not know how to extract '${filename}' -- known types are .7z, .tar, .tar.bz2, .tar.gz, .tar.xz, .tbz2, .tgz, .txz and .zip") - return() + message(FATAL_ERROR + "Do not know how to extract '${filename}' -- known types are: " + ".7z, .tar, .tar.bz2, .tar.gz, .tar.xz, .tbz2, .tgz, .txz and .zip") endif() - file(WRITE ${script_filename} -"# Make file names absolute: -# -get_filename_component(filename \"${filename}\" ABSOLUTE) -get_filename_component(directory \"${directory}\" ABSOLUTE) - -message(STATUS \"extracting... - src='\${filename}' - dst='\${directory}'\") + configure_file( + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject/extractfile.cmake.in" + "${script_filename}" + @ONLY + ) -if(NOT EXISTS \"\${filename}\") - message(FATAL_ERROR \"error: file to extract does not exist: '\${filename}'\") -endif() +endfunction() -# Prepare a space for extracting: -# -set(i 1234) -while(EXISTS \"\${directory}/../ex-${name}\${i}\") - math(EXPR i \"\${i} + 1\") -endwhile() -set(ut_dir \"\${directory}/../ex-${name}\${i}\") -file(MAKE_DIRECTORY \"\${ut_dir}\") - -# Extract it: -# -message(STATUS \"extracting... [tar ${args}]\") -execute_process(COMMAND \${CMAKE_COMMAND} -E tar ${args} \${filename} - WORKING_DIRECTORY \${ut_dir} - RESULT_VARIABLE rv) - -if(NOT rv EQUAL 0) - message(STATUS \"extracting... [error clean up]\") - file(REMOVE_RECURSE \"\${ut_dir}\") - message(FATAL_ERROR \"error: extract of '\${filename}' failed\") -endif() -# Analyze what came out of the tar file: +# This function is an implementation detail of ExternalProject_Add() and +# _ep_do_preconfigure_steps_now(). # -message(STATUS \"extracting... [analysis]\") -file(GLOB contents \"\${ut_dir}/*\") -list(REMOVE_ITEM contents \"\${ut_dir}/.DS_Store\") -list(LENGTH contents n) -if(NOT n EQUAL 1 OR NOT IS_DIRECTORY \"\${contents}\") - set(contents \"\${ut_dir}\") -endif() - -# Move \"the one\" directory to the final directory: +# The function expects keyword arguments to have already been parsed into +# variables of the form _EP_<keyword>. It will create the various directories +# before returning and it will populate variables of the form +# _EP_<location>_DIR in the calling scope. # -message(STATUS \"extracting... [rename]\") -file(REMOVE_RECURSE \${directory}) -get_filename_component(contents \${contents} ABSOLUTE) -file(RENAME \${contents} \${directory}) - -# Clean up: +# Variables will also be set in the calling scope to enable subsequently +# calling _ep_add_preconfigure_command() for the mkdir step. # -message(STATUS \"extracting... [clean up]\") -file(REMOVE_RECURSE \"\${ut_dir}\") - -message(STATUS \"extracting... done\") -" -) +function(_ep_prepare_directories name) -endfunction() - - -function(_ep_set_directories name) - get_property(prefix TARGET ${name} PROPERTY _EP_PREFIX) + set(prefix ${_EP_PREFIX}) if(NOT prefix) get_property(prefix DIRECTORY PROPERTY EP_PREFIX) if(NOT prefix) @@ -1661,6 +1609,7 @@ function(_ep_set_directories name) endif() endif() if(prefix) + file(TO_CMAKE_PATH "${prefix}" prefix) set(tmp_default "${prefix}/tmp") set(download_default "${prefix}/src") set(source_default "${prefix}/src/${name}") @@ -1668,6 +1617,7 @@ function(_ep_set_directories name) set(stamp_default "${prefix}/src/${name}-stamp") set(install_default "${prefix}") else() + file(TO_CMAKE_PATH "${base}" base) set(tmp_default "${base}/tmp/${name}") set(download_default "${base}/Download/${name}") set(source_default "${base}/Source/${name}") @@ -1675,10 +1625,10 @@ function(_ep_set_directories name) set(stamp_default "${base}/Stamp/${name}") set(install_default "${base}/Install/${name}") endif() - get_property(build_in_source TARGET ${name} PROPERTY _EP_BUILD_IN_SOURCE) + + set(build_in_source "${_EP_BUILD_IN_SOURCE}") if(build_in_source) - get_property(have_binary_dir TARGET ${name} PROPERTY _EP_BINARY_DIR SET) - if(have_binary_dir) + if(DEFINED _EP_BINARY_DIR) message(FATAL_ERROR "External project ${name} has both BINARY_DIR and BUILD_IN_SOURCE!") endif() @@ -1689,64 +1639,77 @@ function(_ep_set_directories name) set(places stamp download source binary install tmp) foreach(var ${places}) string(TOUPPER "${var}" VAR) - get_property(${var}_dir TARGET ${name} PROPERTY _EP_${VAR}_DIR) + set(${var}_dir "${_EP_${VAR}_DIR}") if(NOT ${var}_dir) set(${var}_dir "${${var}_default}") endif() if(NOT IS_ABSOLUTE "${${var}_dir}") get_filename_component(${var}_dir "${top}/${${var}_dir}" ABSOLUTE) endif() - set_property(TARGET ${name} PROPERTY _EP_${VAR}_DIR "${${var}_dir}") + file(TO_CMAKE_PATH "${${var}_dir}" ${var}_dir) endforeach() # Special case for default log directory based on stamp directory. - get_property(log_dir TARGET ${name} PROPERTY _EP_LOG_DIR) + set(log_dir "${_EP_LOG_DIR}") if(NOT log_dir) - get_property(log_dir TARGET ${name} PROPERTY _EP_STAMP_DIR) - endif() - if(NOT IS_ABSOLUTE "${log_dir}") - get_filename_component(log_dir "${top}/${log_dir}" ABSOLUTE) + set(log_dir "${stamp_dir}") + else() + if(NOT IS_ABSOLUTE "${log_dir}") + get_filename_component(log_dir "${top}/${log_dir}" ABSOLUTE) + endif() endif() - set_property(TARGET ${name} PROPERTY _EP_LOG_DIR "${log_dir}") + file(TO_CMAKE_PATH "${log_dir}" log_dir) + list(APPEND places log) - get_property(source_subdir TARGET ${name} PROPERTY _EP_SOURCE_SUBDIR) - if(NOT source_subdir) - set_property(TARGET ${name} PROPERTY _EP_SOURCE_SUBDIR "") - elseif(IS_ABSOLUTE "${source_subdir}") - message(FATAL_ERROR - "External project ${name} has non-relative SOURCE_SUBDIR!") - else() + set(source_subdir "${_EP_SOURCE_SUBDIR}") + if(source_subdir) + if(IS_ABSOLUTE "${source_subdir}") + message(FATAL_ERROR + "External project ${name} has non-relative SOURCE_SUBDIR!") + endif() + string(REPLACE "\\" "/" source_subdir "${source_subdir}") # Prefix with a slash so that when appended to the source directory, it # behaves as expected. - set_property(TARGET ${name} PROPERTY _EP_SOURCE_SUBDIR "/${source_subdir}") + string(PREPEND source_subdir "/") endif() + if(build_in_source) - get_property(source_dir TARGET ${name} PROPERTY _EP_SOURCE_DIR) - if(source_subdir) - set_property(TARGET ${name} PROPERTY _EP_BINARY_DIR "${source_dir}/${source_subdir}") - else() - set_property(TARGET ${name} PROPERTY _EP_BINARY_DIR "${source_dir}") - endif() + set(binary_dir "${source_dir}${source_subdir}") endif() - # Make the directories at CMake configure time *and* add a custom command - # to make them at build time. They need to exist at makefile generation - # time for Borland make and wmake so that CMake may generate makefiles - # with "cd C:\short\paths\with\no\spaces" commands in them. - # - # Additionally, the add_custom_command is still used in case somebody - # removes one of the necessary directories and tries to rebuild without - # re-running cmake. + # This script will be used both here and by the mkdir step. We create the + # directories now at configure time and ensure they exists again at build + # time (since somebody might remove one of the required directories and try + # to rebuild without re-running cmake). They need to exist now at makefile + # generation time for Borland make and wmake so that CMake may generate + # makefiles with "cd C:\short\paths\with\no\spaces" commands in them. + set(script_filename "${tmp_dir}/${name}-mkdirs.cmake") + configure_file( + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject/mkdirs.cmake.in + ${script_filename} + @ONLY + ) + include(${script_filename}) + + set(comment "Creating directories for '${name}'") + set(cmd ${CMAKE_COMMAND} -P ${script_filename}) + + # Provide variables that can be used later to create a custom command or + # invoke the step directly + set(_EPcomment_MKDIR "${comment}" PARENT_SCOPE) + set(_EPcommand_MKDIR "${cmd}" PARENT_SCOPE) + set(_EPalways_MKDIR FALSE PARENT_SCOPE) + set(_EPexcludefrommain_MKDIR FALSE PARENT_SCOPE) + set(_EPdepends_MKDIR "" PARENT_SCOPE) + set(_EPdependees_MKDIR "" PARENT_SCOPE) + foreach(var ${places}) string(TOUPPER "${var}" VAR) - get_property(dir TARGET ${name} PROPERTY _EP_${VAR}_DIR) - file(MAKE_DIRECTORY "${dir}") - if(NOT EXISTS "${dir}") - message(FATAL_ERROR "dir '${dir}' does not exist after file(MAKE_DIRECTORY)") - endif() + set(_EP_${VAR}_DIR "${${var}_dir}" PARENT_SCOPE) endforeach() -endfunction() + set(_EP_SOURCE_SUBDIR "${source_subdir}" PARENT_SCOPE) +endfunction() # IMPORTANT: this MUST be a macro and not a function because of the # in-place replacements that occur in each ${var} @@ -1763,6 +1726,17 @@ macro(_ep_replace_location_tags target_name) endforeach() endmacro() +macro(_ep_replace_location_tags_from_vars) + set(vars ${ARGN}) + foreach(var ${vars}) + if(${var}) + foreach(dir SOURCE_DIR SOURCE_SUBDIR BINARY_DIR INSTALL_DIR TMP_DIR DOWNLOAD_DIR DOWNLOADED_FILE LOG_DIR) + string(REPLACE "<${dir}>" "${_EP_${dir}}" ${var} "${${var}}") + endforeach() + endif() + endforeach() +endmacro() + function(_ep_command_line_to_initial_cache var args force) set(script_initial_cache "") @@ -1945,17 +1919,24 @@ function(_ep_get_build_command name step cmd_var) set(${cmd_var} "${cmd}" PARENT_SCOPE) endfunction() -function(_ep_write_log_script name step cmd_var) - ExternalProject_Get_Property(${name} log_dir) - ExternalProject_Get_Property(${name} stamp_dir) +function(_ep_write_log_script name step genex_supported cmd_var) + + set(log_dir "${_EP_LOG_DIR}") + set(tmp_dir "${_EP_TMP_DIR}") + + if(genex_supported) + set(script_base ${tmp_dir}/${name}-${step}-$<CONFIG>) + else() + set(script_base ${tmp_dir}/${name}-${step}) + endif() set(command "${${cmd_var}}") set(make "") set(code_cygpath_make "") - if(command MATCHES "^\\$\\(MAKE\\)") + if(command MATCHES [[^\$\(MAKE\)]]) # GNU make recognizes the string "$(MAKE)" as recursive make, so # ensure that it appears directly in the makefile. - string(REGEX REPLACE "^\\$\\(MAKE\\)" "\${make}" command "${command}") + string(REGEX REPLACE [[^\$\(MAKE\)]] [[${make}]] command "${command}") set(make "-Dmake=$(MAKE)") if(WIN32 AND NOT CYGWIN) @@ -1977,8 +1958,8 @@ endif() endif() set(config "") - if("${CMAKE_CFG_INTDIR}" MATCHES "^\\$") - string(REPLACE "${CMAKE_CFG_INTDIR}" "\${config}" command "${command}") + if("${CMAKE_CFG_INTDIR}" MATCHES [[^\$]]) + string(REPLACE "${CMAKE_CFG_INTDIR}" [[${config}]] command "${command}") set(config "-Dconfig=${CMAKE_CFG_INTDIR}") endif() @@ -2012,15 +1993,22 @@ endif() endif() endforeach() string(APPEND code "set(command \"${cmd}\")${code_execute_process}") - file(GENERATE OUTPUT "${stamp_dir}/${name}-${step}-$<CONFIG>-impl.cmake" CONTENT "${code}") - set(command ${CMAKE_COMMAND} "-Dmake=\${make}" "-Dconfig=\${config}" -P ${stamp_dir}/${name}-${step}-$<CONFIG>-impl.cmake) + if(genex_supported) + file(GENERATE OUTPUT "${script_base}-impl.cmake" CONTENT "${code}") + else() + file(WRITE "${script_base}-impl.cmake" "${code}") + endif() + set(command ${CMAKE_COMMAND} + -D "make=\${make}" + -D "config=\${config}" + -P ${script_base}-impl.cmake + ) endif() # Wrap the command in a script to log output to files. - set(script ${stamp_dir}/${name}-${step}-$<CONFIG>.cmake) set(logbase ${log_dir}/${name}-${step}) - get_property(log_merged TARGET ${name} PROPERTY _EP_LOG_MERGED_STDOUTERR) - get_property(log_output_on_failure TARGET ${name} PROPERTY _EP_LOG_OUTPUT_ON_FAILURE) + set(log_merged "${_EP_LOG_MERGED_STDOUTERR}") + set(log_output_on_failure "${_EP_LOG_OUTPUT_ON_FAILURE}") if (log_merged) set(stdout_log "${logbase}.log") set(stderr_log "${logbase}.log") @@ -2079,14 +2067,19 @@ if(result) message(FATAL_ERROR \"\${msg}\") endif() else() - if(NOT \"${CMAKE_GENERATOR}\" MATCHES \"Ninja\") + if(NOT \"${CMAKE_GENERATOR}\" MATCHES \"Ninja\" AND NOT \"${_EP_QUIET}\") set(msg \"${name} ${step} command succeeded. See also ${logbase}-*.log\") message(STATUS \"\${msg}\") endif() endif() ") - file(GENERATE OUTPUT "${script}" CONTENT "${code}") - set(command ${CMAKE_COMMAND} ${make} ${config} -P ${script}) + set(script_filename ${script_base}.cmake) + if(genex_supported) + file(GENERATE OUTPUT ${script_filename} CONTENT "${code}") + else() + file(WRITE ${script_filename} "${code}") + endif() + set(command ${CMAKE_COMMAND} ${make} ${config} -P ${script_filename}) set(${cmd_var} "${command}" PARENT_SCOPE) endfunction() @@ -2250,8 +2243,21 @@ function(ExternalProject_Add_Step name step) _ep_get_complete_stampfile(${name} complete_stamp_file) _ep_get_step_stampfile(${name} ${step} stamp_file) - _ep_parse_arguments(ExternalProject_Add_Step - ${name} _EP_${step}_ "${ARGN}") + set(keywords + COMMAND + COMMENT + DEPENDEES + DEPENDERS + DEPENDS + INDEPENDENT + BYPRODUCTS + ALWAYS + EXCLUDE_FROM_MAIN + WORKING_DIRECTORY + LOG + USES_TERMINAL + ) + _ep_parse_arguments("${keywords}" ${name} _EP_${step}_ "${ARGN}") get_property(independent TARGET ${name} PROPERTY _EP_${step}_INDEPENDENT) if(independent STREQUAL "") @@ -2362,7 +2368,8 @@ function(ExternalProject_Add_Step name step) # Wrap with log script? get_property(log TARGET ${name} PROPERTY _EP_${step}_LOG) if(command AND log) - _ep_write_log_script(${name} ${step} command) + set(genex_supported TRUE) + _ep_write_log_script(${name} ${step} ${genex_supported} command) endif() if("${command}" STREQUAL "") @@ -2494,27 +2501,6 @@ function(ExternalProject_Add_StepDependencies name step) endfunction() - -function(_ep_add_mkdir_command name) - ExternalProject_Get_Property(${name} - source_dir binary_dir install_dir stamp_dir download_dir tmp_dir log_dir) - - _ep_get_configuration_subdir_suffix(cfgdir) - - ExternalProject_Add_Step(${name} mkdir - INDEPENDENT TRUE - COMMENT "Creating directories for '${name}'" - COMMAND ${CMAKE_COMMAND} -E make_directory ${source_dir} - COMMAND ${CMAKE_COMMAND} -E make_directory ${binary_dir} - COMMAND ${CMAKE_COMMAND} -E make_directory ${install_dir} - COMMAND ${CMAKE_COMMAND} -E make_directory ${tmp_dir} - COMMAND ${CMAKE_COMMAND} -E make_directory ${stamp_dir}${cfgdir} - COMMAND ${CMAKE_COMMAND} -E make_directory ${download_dir} - COMMAND ${CMAKE_COMMAND} -E make_directory ${log_dir} - ) -endfunction() - - function(_ep_is_dir_empty dir empty_var) file(GLOB gr "${dir}/*") if("${gr}" STREQUAL "") @@ -2525,114 +2511,264 @@ function(_ep_is_dir_empty dir empty_var) endfunction() function(_ep_get_git_submodules_recurse git_submodules_recurse) - # Checks for GIT_SUBMODULES_RECURSE property - # Default is ON, which sets git_submodules_recurse output variable to "--recursive" - # Otherwise, the output variable is set to an empty value "" - get_property(git_submodules_recurse_set TARGET ${name} PROPERTY _EP_GIT_SUBMODULES_RECURSE SET) - if(NOT git_submodules_recurse_set) - set(recurseFlag "--recursive") + + if(NOT DEFINED _EP_GIT_SUBMODULES_RECURSE OR _EP_GIT_SUBMODULES_RECURSE) + # The git submodule update '--recursive' flag requires git >= v1.6.5 + if(recurseFlag AND GIT_VERSION_STRING VERSION_LESS 1.6.5) + message(FATAL_ERROR + "git version 1.6.5 or later required for --recursive flag with " + "'git submodule ...': GIT_VERSION_STRING='${GIT_VERSION_STRING}'") + endif() + set(${git_submodules_recurse} "--recursive" PARENT_SCOPE) else() - get_property(git_submodules_recurse_value TARGET ${name} PROPERTY _EP_GIT_SUBMODULES_RECURSE) - if(git_submodules_recurse_value) - set(recurseFlag "--recursive") + set(${git_submodules_recurse} "" PARENT_SCOPE) + endif() + +endfunction() + +function(_ep_write_command_script + script_filename + commands + work_dir + genex_supported + quiet + have_commands_var) + + set(sep "${_EP_LIST_SEPARATOR}") + if(sep AND commands) + string(REPLACE "${sep}" "\\;" commands "${commands}") + endif() + _ep_replace_location_tags_from_vars(commands) + + file(READ + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject/customcommand.cmake.in + exec_command_template + ) + set(script_content) + set(this_command) + foreach(token IN LISTS commands) + if(token STREQUAL "COMMAND") + if("${this_command}" STREQUAL "") + # Silently skip empty commands + continue() + endif() + string(CONFIGURE "${exec_command_template}" content @ONLY) + string(APPEND script_content "${content}") + set(this_command) else() - set(recurseFlag "") + # Ensure we quote every token so we preserve empty items, quotes, etc + string(APPEND this_command " [==[${token}]==]") endif() + endforeach() + + if(NOT "${this_command}" STREQUAL "") + string(CONFIGURE "${exec_command_template}" content @ONLY) + string(APPEND script_content "${content}") + endif() + + if(script_content STREQUAL "") + set(${have_commands_var} FALSE PARENT_SCOPE) + else() + set(${have_commands_var} TRUE PARENT_SCOPE) + file(READ + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject/customcommand_preamble.cmake.in + exec_command_preamble + ) + string(CONFIGURE "${exec_command_preamble}" exec_command_preamble @ONLY) + string(PREPEND script_content "${exec_command_preamble}") endif() - set(${git_submodules_recurse} "${recurseFlag}" PARENT_SCOPE) - # The git submodule update '--recursive' flag requires git >= v1.6.5 - if(recurseFlag AND GIT_VERSION_STRING VERSION_LESS 1.6.5) - message(FATAL_ERROR "error: git version 1.6.5 or later required for --recursive flag with 'git submodule ...': GIT_VERSION_STRING='${GIT_VERSION_STRING}'") + if(genex_supported) + # Only written at generation phase + file(GENERATE OUTPUT "${script_filename}" CONTENT "${script_content}") + else() + # Written immediately, needed if script has to be invoked in configure phase + file(WRITE "${script_filename}" "${script_content}") endif() + endfunction() +function(_ep_add_preconfigure_command name step) -function(_ep_add_download_command name) - ExternalProject_Get_Property(${name} source_dir stamp_dir download_dir tmp_dir) + string(TOUPPER "${step}" STEP) + set(uses_terminal "${_EP_USES_TERMINAL_${STEP}}") + if(uses_terminal) + set(uses_terminal TRUE) + else() + set(uses_terminal FALSE) + endif() + + # Pre-configure steps are expected to set their own work_dir + ExternalProject_Add_Step(${name} ${step} + INDEPENDENT TRUE + COMMENT "${_EPcomment_${STEP}}" + COMMAND ${_EPcommand_${STEP}} + ALWAYS ${_EPalways_${STEP}} + EXCLUDE_FROM_MAIN ${_EPexcludefrommain_${STEP}} + DEPENDS ${_EPdepends_${STEP}} + DEPENDEES ${_EPdependees_${STEP}} + USES_TERMINAL ${uses_terminal} + ) +endfunction() - get_property(cmd_set TARGET ${name} PROPERTY _EP_DOWNLOAD_COMMAND SET) - get_property(cmd TARGET ${name} PROPERTY _EP_DOWNLOAD_COMMAND) - get_property(cvs_repository TARGET ${name} PROPERTY _EP_CVS_REPOSITORY) - get_property(svn_repository TARGET ${name} PROPERTY _EP_SVN_REPOSITORY) - get_property(git_repository TARGET ${name} PROPERTY _EP_GIT_REPOSITORY) - get_property(hg_repository TARGET ${name} PROPERTY _EP_HG_REPOSITORY ) - get_property(url TARGET ${name} PROPERTY _EP_URL) - get_property(fname TARGET ${name} PROPERTY _EP_DOWNLOAD_NAME) +# This function is an implementation detail of ExternalProject_Add() and +# _ep_do_preconfigure_steps_now(). +# +# The function expects keyword arguments to have already been parsed into +# variables of the form _EP_<keyword>. It will populate the variable +# _EP_DOWNLOADED_FILE in the calling scope only if the download method is +# URL-based and extraction has been turned off. +# +# Variables will also be set in the calling scope to enable subsequently +# calling _ep_add_preconfigure_command() for the download step. +# +function(_ep_prepare_download name genex_supported) - # TODO: Perhaps file:// should be copied to download dir before extraction. - string(REGEX REPLACE "file://" "" url "${url}") + set(stamp_dir "${_EP_STAMP_DIR}") + set(tmp_dir "${_EP_TMP_DIR}") + set(source_dir "${_EP_SOURCE_DIR}") + set(download_dir "${_EP_DOWNLOAD_DIR}") + set(quiet "${_EP_QUIET}") - set(depends) set(comment) - set(work_dir) - if(cmd_set) + # We handle the log setting directly here rather than deferring it to + # be handled by ExternalProject_Add_Step() + set(log "${_EP_LOG_DOWNLOAD}") + if(log) + set(script_filename ${tmp_dir}/${name}-download-impl.cmake) + set(log TRUE) + set(quiet FALSE) # Already quiet as a result of log being enabled + else() + set(script_filename ${tmp_dir}/${name}-download.cmake) + set(log FALSE) + endif() + + set(repo_info_file ${tmp_dir}/${name}-download-repoinfo.txt) + set(last_run_file ${stamp_dir}/${name}-download-lastrun.txt) + set(script_does_something TRUE) + + # We use configure_file() to write the repo_info_file below so that the + # file's timestamp is not updated if we don't change the contents of an + # existing file. + + if(DEFINED _EP_DOWNLOAD_COMMAND) set(work_dir ${download_dir}) - elseif(cvs_repository) + set(repo_info_content +"method=custom +command=${_EP_DOWNLOAD_COMMAND} +source_dir=${source_dir} +work_dir=${work_dir} +") + configure_file( + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject/RepositoryInfo.txt.in + ${repo_info_file} + @ONLY + ) + + _ep_write_command_script( + "${script_filename}" + "${_EP_DOWNLOAD_COMMAND}" + "${work_dir}" + "${genex_supported}" + "${quiet}" + script_does_something + ) + set(comment "Performing download step (custom command) for '${name}'") + + elseif(DEFINED _EP_CVS_REPOSITORY) find_package(CVS QUIET) if(NOT CVS_EXECUTABLE) message(FATAL_ERROR "error: could not find cvs for checkout of ${name}") endif() - get_target_property(cvs_module ${name} _EP_CVS_MODULE) - if(NOT cvs_module) + if("${_EP_CVS_MODULE}" STREQUAL "") message(FATAL_ERROR "error: no CVS_MODULE") endif() - get_property(cvs_tag TARGET ${name} PROPERTY _EP_CVS_TAG) - - set(repository ${cvs_repository}) - set(module ${cvs_module}) - set(tag ${cvs_tag}) + set(repo_info_content +"method=cvs +repository=${_EP_CVS_REPOSITORY} +module=${_EP_CVS_MODULE} +tag=${_EP_CVS_TAG} +source_dir=${source_dir} +") configure_file( - "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in" - "${stamp_dir}/${name}-cvsinfo.txt" + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject/RepositoryInfo.txt.in + ${repo_info_file} @ONLY - ) + ) get_filename_component(src_name "${source_dir}" NAME) get_filename_component(work_dir "${source_dir}" PATH) + + set(cmd "${CVS_EXECUTABLE}" -d "${_EP_CVS_REPOSITORY}" -q + co ${_EP_CVS_TAG} -d "${src_name}" "${_EP_CVS_MODULE}" + ) + _ep_write_command_script( + "${script_filename}" + "${cmd}" + "${work_dir}" + "${genex_supported}" + "${quiet}" + script_does_something + ) set(comment "Performing download step (CVS checkout) for '${name}'") - set(cmd ${CVS_EXECUTABLE} -d ${cvs_repository} -q co ${cvs_tag} -d ${src_name} ${cvs_module}) - list(APPEND depends ${stamp_dir}/${name}-cvsinfo.txt) - elseif(svn_repository) + + elseif(DEFINED _EP_SVN_REPOSITORY) find_package(Subversion QUIET) if(NOT Subversion_SVN_EXECUTABLE) message(FATAL_ERROR "error: could not find svn for checkout of ${name}") endif() - get_property(svn_revision TARGET ${name} PROPERTY _EP_SVN_REVISION) - get_property(svn_username TARGET ${name} PROPERTY _EP_SVN_USERNAME) - get_property(svn_password TARGET ${name} PROPERTY _EP_SVN_PASSWORD) - get_property(svn_trust_cert TARGET ${name} PROPERTY _EP_SVN_TRUST_CERT) + set(svn_repository "${_EP_SVN_REPOSITORY}") + set(svn_revision "${_EP_SVN_REVISION}") + set(svn_username "${_EP_SVN_USERNAME}") + set(svn_password "${_EP_SVN_PASSWORD}") + set(svn_trust_cert "${_EP_SVN_TRUST_CERT}") + + set(svn_options --non-interactive) + if(DEFINED _EP_SVN_USERNAME) + list(APPEND svn_options "--username=${svn_username}") + endif() + if(DEFINED _EP_SVN_PASSWORD) + list(APPEND svn_options "--password=${svn_password}") + endif() + if(svn_trust_cert) + list(APPEND svn_options --trust-server-cert) + endif() - set(repository "${svn_repository} user=${svn_username} password=${svn_password}") - set(module) - set(tag ${svn_revision}) + set(repo_info_content +"method=svn +repository=${svn_repository} +user=${svn_username} +password=${svn_password} +revision=${svn_revision} +source_dir=${source_dir} +") configure_file( - "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in" - "${stamp_dir}/${name}-svninfo.txt" + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject/RepositoryInfo.txt.in + ${repo_info_file} @ONLY - ) + ) get_filename_component(src_name "${source_dir}" NAME) get_filename_component(work_dir "${source_dir}" PATH) + + set(cmd "${Subversion_SVN_EXECUTABLE}" co "${svn_repository}" + ${svn_revision} ${svn_options} "${src_name}" + ) + _ep_write_command_script( + "${script_filename}" + "${cmd}" + "${work_dir}" + "${genex_supported}" + "${quiet}" + script_does_something + ) set(comment "Performing download step (SVN checkout) for '${name}'") - set(svn_user_pw_args "") - if(DEFINED svn_username) - set(svn_user_pw_args ${svn_user_pw_args} "--username=${svn_username}") - endif() - if(DEFINED svn_password) - set(svn_user_pw_args ${svn_user_pw_args} "--password=${svn_password}") - endif() - if(svn_trust_cert) - set(svn_trust_cert_args --trust-server-cert) - endif() - set(cmd ${Subversion_SVN_EXECUTABLE} co ${svn_repository} ${svn_revision} - --non-interactive ${svn_trust_cert_args} ${svn_user_pw_args} ${src_name}) - list(APPEND depends ${stamp_dir}/${name}-svninfo.txt) - elseif(git_repository) + + elseif(DEFINED _EP_GIT_REPOSITORY) # FetchContent gives us these directly, so don't try to recompute them if(NOT GIT_EXECUTABLE OR NOT GIT_VERSION_STRING) unset(CMAKE_MODULE_PATH) # Use CMake builtin find module @@ -2642,132 +2778,162 @@ function(_ep_add_download_command name) endif() endif() - _ep_get_git_submodules_recurse(git_submodules_recurse) - - get_property(git_tag TARGET ${name} PROPERTY _EP_GIT_TAG) + set(git_tag "${_EP_GIT_TAG}") if(NOT git_tag) set(git_tag "master") endif() + set(git_remote_name "${_EP_GIT_REMOTE_NAME}") + if(NOT git_remote_name) + set(git_remote_name "origin") + endif() + set(git_init_submodules TRUE) - get_property(git_submodules_set TARGET ${name} PROPERTY _EP_GIT_SUBMODULES SET) - if(git_submodules_set) - get_property(git_submodules TARGET ${name} PROPERTY _EP_GIT_SUBMODULES) - if(git_submodules STREQUAL "" AND _EP_CMP0097 STREQUAL "NEW") + if(DEFINED _EP_GIT_SUBMODULES) + set(git_submodules "${_EP_GIT_SUBMODULES}") + if(git_submodules STREQUAL "" AND _EP_CMP0097 STREQUAL "NEW") set(git_init_submodules FALSE) endif() endif() + _ep_get_git_submodules_recurse(git_submodules_recurse) - get_property(git_remote_name TARGET ${name} PROPERTY _EP_GIT_REMOTE_NAME) - if(NOT git_remote_name) - set(git_remote_name "origin") - endif() - - get_property(tls_verify TARGET ${name} PROPERTY _EP_TLS_VERIFY) + set(tls_verify "${_EP_TLS_VERIFY}") if("x${tls_verify}" STREQUAL "x" AND DEFINED CMAKE_TLS_VERIFY) set(tls_verify "${CMAKE_TLS_VERIFY}") endif() - get_property(git_shallow TARGET ${name} PROPERTY _EP_GIT_SHALLOW) - get_property(git_progress TARGET ${name} PROPERTY _EP_GIT_PROGRESS) - get_property(git_config TARGET ${name} PROPERTY _EP_GIT_CONFIG) + set(git_shallow "${_EP_GIT_SHALLOW}") + set(git_progress "${_EP_GIT_PROGRESS}") + set(git_config "${_EP_GIT_CONFIG}") # Make checkouts quiet when checking out a git hash (this avoids the # very noisy detached head message) list(PREPEND git_config advice.detachedHead=false) - # For the download step, and the git clone operation, only the repository - # should be recorded in a configured RepositoryInfo file. If the repo - # changes, the clone script should be run again. But if only the tag + # For the git clone operation, only the repository and remote should be + # recorded in a configured repository info file. If the repo or remote + # name changes, the clone script should be run again. But if only the tag # changes, avoid running the clone script again. Let the 'always' running # update step checkout the new tag. - # - set(repository ${git_repository}) - set(module) - set(tag ${git_remote_name}) + set(repo_info_content +"method=git +repository=${_EP_GIT_REPOSITORY} +remote=${git_remote_name} +source_dir=${source_dir} +") configure_file( - "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in" - "${stamp_dir}/${name}-gitinfo.txt" + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject/RepositoryInfo.txt.in + ${repo_info_file} @ONLY - ) + ) get_filename_component(src_name "${source_dir}" NAME) get_filename_component(work_dir "${source_dir}" PATH) # Since git clone doesn't succeed if the non-empty source_dir exists, - # create a cmake script to invoke as download command. - # The script will delete the source directory and then call git clone. - # - _ep_write_gitclone_script(${tmp_dir}/${name}-gitclone.cmake ${source_dir} - ${GIT_EXECUTABLE} ${git_repository} ${git_tag} ${git_remote_name} ${git_init_submodules} "${git_submodules_recurse}" "${git_submodules}" "${git_shallow}" "${git_progress}" "${git_config}" ${src_name} ${work_dir} - ${stamp_dir}/${name}-gitinfo.txt ${stamp_dir}/${name}-gitclone-lastrun.txt "${tls_verify}" - ) + # the script will delete the source directory and then call git clone. + _ep_write_gitclone_script( + "${script_filename}" + "${source_dir}" + "${GIT_EXECUTABLE}" + "${_EP_GIT_REPOSITORY}" + "${git_tag}" + "${git_remote_name}" + "${git_init_submodules}" + "${git_submodules_recurse}" + "${git_submodules}" + "${git_shallow}" + "${git_progress}" + "${git_config}" + "${src_name}" + "${work_dir}" + "${repo_info_file}" + "${last_run_file}" + "${tls_verify}" + "${quiet}" + ) set(comment "Performing download step (git clone) for '${name}'") - set(cmd ${CMAKE_COMMAND} -P ${tmp_dir}/${name}-gitclone.cmake) - list(APPEND depends ${stamp_dir}/${name}-gitinfo.txt) - elseif(hg_repository) + + elseif(DEFINED _EP_HG_REPOSITORY) find_package(Hg QUIET) if(NOT HG_EXECUTABLE) message(FATAL_ERROR "error: could not find hg for clone of ${name}") endif() - get_property(hg_tag TARGET ${name} PROPERTY _EP_HG_TAG) + set(hg_tag "${_EP_HG_TAG}") if(NOT hg_tag) set(hg_tag "tip") endif() - # For the download step, and the hg clone operation, only the repository - # should be recorded in a configured RepositoryInfo file. If the repo - # changes, the clone script should be run again. But if only the tag - # changes, avoid running the clone script again. Let the 'always' running - # update step checkout the new tag. - # - set(repository ${hg_repository}) - set(module) - set(tag) + # For the hg clone operation, only the repository should be recorded in a + # configured repository info file. If the repo changes, the clone script + # should be run again. But if only the tag changes, avoid running the + # clone script again. Let the 'always' running update step checkout the + # new tag. + set(repo_info_content +"method=hg +repository=${_EP_HG_REPOSITORY} +source_dir=${source_dir} +") configure_file( - "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in" - "${stamp_dir}/${name}-hginfo.txt" + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject/RepositoryInfo.txt.in + ${repo_info_file} @ONLY - ) + ) get_filename_component(src_name "${source_dir}" NAME) get_filename_component(work_dir "${source_dir}" PATH) # Since hg clone doesn't succeed if the non-empty source_dir exists, - # create a cmake script to invoke as download command. - # The script will delete the source directory and then call hg clone. - # - _ep_write_hgclone_script(${tmp_dir}/${name}-hgclone.cmake ${source_dir} - ${HG_EXECUTABLE} ${hg_repository} ${hg_tag} ${src_name} ${work_dir} - ${stamp_dir}/${name}-hginfo.txt ${stamp_dir}/${name}-hgclone-lastrun.txt - ) + # the script will delete the source directory and then call hg clone. + _ep_write_hgclone_script( + "${script_filename}" + "${source_dir}" + "${HG_EXECUTABLE}" + "${_EP_HG_REPOSITORY}" + "${hg_tag}" + "${src_name}" + "${work_dir}" + "${repo_info_file}" + "${last_run_file}" + "${quiet}" + ) set(comment "Performing download step (hg clone) for '${name}'") - set(cmd ${CMAKE_COMMAND} -P ${tmp_dir}/${name}-hgclone.cmake) - list(APPEND depends ${stamp_dir}/${name}-hginfo.txt) - elseif(url) - get_filename_component(work_dir "${source_dir}" PATH) - get_property(hash TARGET ${name} PROPERTY _EP_URL_HASH) + + elseif(DEFINED _EP_URL) + set(url "${_EP_URL}") + # TODO: Perhaps file:// should be copied to download dir before extraction. + string(REGEX REPLACE "file://" "" url "${url}") + + set(hash "${_EP_URL_HASH}") + _ep_get_hash_regex(_ep_hash_regex) if(hash AND NOT "${hash}" MATCHES "${_ep_hash_regex}") + _ep_get_hash_algos(_ep_hash_algos) + list(JOIN _ep_hash_algos "|" _ep_hash_algos) message(FATAL_ERROR "URL_HASH is set to\n ${hash}\n" "but must be ALGO=value where ALGO is\n ${_ep_hash_algos}\n" "and value is a hex string.") endif() - get_property(md5 TARGET ${name} PROPERTY _EP_URL_MD5) + set(md5 "${_EP_URL_MD5}") if(md5 AND NOT "MD5=${md5}" MATCHES "${_ep_hash_regex}") message(FATAL_ERROR "URL_MD5 is set to\n ${md5}\nbut must be a hex string.") endif() if(md5 AND NOT hash) set(hash "MD5=${md5}") endif() - set(repository "external project URL") - set(module "${url}") - set(tag "${hash}") + + set(repo_info_content +"method=url +url=${url} +hash=${hash} +source_dir=${source_dir} +") configure_file( - "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in" - "${stamp_dir}/${name}-urlinfo.txt" + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject/RepositoryInfo.txt.in + ${repo_info_file} @ONLY - ) - list(APPEND depends ${stamp_dir}/${name}-urlinfo.txt) + ) + + set(fname "${_EP_DOWNLOAD_NAME}") list(LENGTH url url_list_length) if(NOT "${url_list_length}" STREQUAL "1") @@ -2782,12 +2948,21 @@ function(_ep_add_download_command name) endif() if(IS_DIRECTORY "${url}") - get_filename_component(abs_dir "${url}" ABSOLUTE) - set(comment "Performing download step (DIR copy) for '${name}'") - set(cmd ${CMAKE_COMMAND} -E rm -rf ${source_dir} - COMMAND ${CMAKE_COMMAND} -E copy_directory ${abs_dir} ${source_dir}) + get_filename_component(from_dir "${url}" ABSOLUTE) + _ep_write_copydir_script( + ${script_filename} + ${from_dir} + ${source_dir} + ) + set(steps "DIR copy") else() - get_property(no_extract TARGET "${name}" PROPERTY _EP_DOWNLOAD_NO_EXTRACT) + set(no_extract "${_EP_DOWNLOAD_NO_EXTRACT}") + if(no_extract) + set(extract_script) + else() + set(extract_script "${tmp_dir}/extract-${name}.cmake") + endif() + if("${url}" MATCHES "^[a-z]+://") # TODO: Should download and extraction be different steps? if("x${fname}" STREQUAL "x") @@ -2801,55 +2976,70 @@ function(_ep_add_download_command name) # Fall back to a default file name. The actual file name does not # matter because it is used only internally and our extraction tool # inspects the file content directly. If it turns out the wrong URL - # was given that will be revealed during the build which is an easier - # place for users to diagnose than an error here anyway. + # was given, that will be revealed when the download is attempted + # (during the build unless we are being invoked by FetchContent) + # which is an easier place for users to diagnose than an error here. set(fname "archive.tar") endif() string(REPLACE ";" "-" fname "${fname}") set(file ${download_dir}/${fname}) - get_property(timeout TARGET ${name} PROPERTY _EP_TIMEOUT) - get_property(inactivity_timeout TARGET ${name} PROPERTY _EP_INACTIVITY_TIMEOUT) - get_property(no_progress TARGET ${name} PROPERTY _EP_DOWNLOAD_NO_PROGRESS) - get_property(tls_verify TARGET ${name} PROPERTY _EP_TLS_VERIFY) - get_property(tls_cainfo TARGET ${name} PROPERTY _EP_TLS_CAINFO) - get_property(netrc TARGET ${name} PROPERTY _EP_NETRC) - get_property(netrc_file TARGET ${name} PROPERTY _EP_NETRC_FILE) - get_property(http_username TARGET ${name} PROPERTY _EP_HTTP_USERNAME) - get_property(http_password TARGET ${name} PROPERTY _EP_HTTP_PASSWORD) - get_property(http_headers TARGET ${name} PROPERTY _EP_HTTP_HEADER) - set(download_script "${stamp_dir}/download-${name}.cmake") - _ep_write_downloadfile_script("${download_script}" "${url}" "${file}" "${timeout}" "${inactivity_timeout}" "${no_progress}" "${hash}" "${tls_verify}" "${tls_cainfo}" "${http_username}:${http_password}" "${http_headers}" "${netrc}" "${netrc_file}") - set(cmd ${CMAKE_COMMAND} -P "${download_script}" - COMMAND) - if (no_extract) + _ep_write_downloadfile_script( + "${script_filename}" + "${url}" + "${file}" + "${_EP_TIMEOUT}" + "${_EP_INACTIVITY_TIMEOUT}" + "${_EP_DOWNLOAD_NO_PROGRESS}" + "${hash}" + "${_EP_TLS_VERIFY}" + "${_EP_TLS_CAINFO}" + "${_EP_HTTP_USERNAME}:${_EP_HTTP_PASSWORD}" + "${_EP_HTTP_HEADER}" + "${_EP_NETRC}" + "${_EP_NETRC_FILE}" + "${extract_script}" + "${quiet}" + ) + if(no_extract) set(steps "download and verify") - else () + else() set(steps "download, verify and extract") - endif () - set(comment "Performing download step (${steps}) for '${name}'") - file(WRITE "${stamp_dir}/verify-${name}.cmake" "") # already verified by 'download_script' + endif() else() set(file "${url}") - if (no_extract) + _ep_write_verifyfile_script( + "${script_filename}" + "${file}" + "${hash}" + "${extract_script}" + "${quiet}" + ) + if(no_extract) set(steps "verify") - else () + else() set(steps "verify and extract") - endif () - set(comment "Performing download step (${steps}) for '${name}'") - _ep_write_verifyfile_script("${stamp_dir}/verify-${name}.cmake" "${file}" "${hash}") + endif() + endif() + + if(no_extract) + set(_EP_DOWNLOADED_FILE ${file} PARENT_SCOPE) + else() + # This will be pulled in by the download/verify script written above + _ep_write_extractfile_script( + "${extract_script}" + "${name}" + "${file}" + "${source_dir}" + "${quiet}" + ) endif() - list(APPEND cmd ${CMAKE_COMMAND} -P ${stamp_dir}/verify-${name}.cmake) - if (NOT no_extract) - _ep_write_extractfile_script("${stamp_dir}/extract-${name}.cmake" "${name}" "${file}" "${source_dir}") - list(APPEND cmd COMMAND ${CMAKE_COMMAND} -P ${stamp_dir}/extract-${name}.cmake) - else () - set_property(TARGET ${name} PROPERTY _EP_DOWNLOADED_FILE ${file}) - endif () endif() + set(comment "Performing download step (${steps}) for '${name}'") + else() _ep_is_dir_empty("${source_dir}" empty) if(${empty}) - message(SEND_ERROR + message(FATAL_ERROR "No download info given for '${name}' and its source directory:\n" " ${source_dir}\n" "is not an existing non-empty directory. Please specify one of:\n" @@ -2862,105 +3052,149 @@ function(_ep_add_download_command name) " * CVS_REPOSITORY and CVS_MODULE" ) endif() - endif() + set(repo_info_content +"method=source_dir +source_dir=${source_dir} +") + configure_file( + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject/RepositoryInfo.txt.in + ${repo_info_file} + @ONLY + ) - get_property(log TARGET ${name} PROPERTY _EP_LOG_DOWNLOAD) - if(log) - set(log LOG 1) - else() - set(log "") + set(comment "Skipping download step (SOURCE_DIR given) for '${name}'") + set(script_does_something FALSE) endif() - get_property(uses_terminal TARGET ${name} PROPERTY - _EP_USES_TERMINAL_DOWNLOAD) - if(uses_terminal) - set(uses_terminal USES_TERMINAL 1) + # Provide variables that can be used later to create a custom command or + # invoke the step directly + if(script_does_something) + set(cmd ${CMAKE_COMMAND} -P ${script_filename}) + if(log) + _ep_write_log_script(${name} download "${genex_supported}" cmd) + endif() + set(depends ${repo_info_file}) else() - set(uses_terminal "") + set(cmd) + set(depends) + string(REPLACE "Performing" "Skipping" comment "${comment}") endif() - set(__cmdQuoted) - foreach(__item IN LISTS cmd) - string(APPEND __cmdQuoted " [==[${__item}]==]") - endforeach() - cmake_language(EVAL CODE " - ExternalProject_Add_Step(\${name} download - INDEPENDENT TRUE - COMMENT \${comment} - COMMAND ${__cmdQuoted} - WORKING_DIRECTORY \${work_dir} - DEPENDS \${depends} - DEPENDEES mkdir - ${log} - ${uses_terminal} - )" - ) + set(_EPcomment_DOWNLOAD "${comment}" PARENT_SCOPE) + set(_EPcommand_DOWNLOAD "${cmd}" PARENT_SCOPE) + set(_EPalways_DOWNLOAD FALSE PARENT_SCOPE) + set(_EPexcludefrommain_DOWNLOAD FALSE PARENT_SCOPE) + set(_EPdepends_DOWNLOAD "${depends}" PARENT_SCOPE) + set(_EPdependees_DOWNLOAD mkdir PARENT_SCOPE) + endfunction() -function(_ep_get_update_disconnected var name) - get_property(update_disconnected_set TARGET ${name} PROPERTY _EP_UPDATE_DISCONNECTED SET) - if(update_disconnected_set) - get_property(update_disconnected TARGET ${name} PROPERTY _EP_UPDATE_DISCONNECTED) +function(_ep_get_update_disconnected var) + if(DEFINED _EP_UPDATE_DISCONNECTED) + set(update_disconnected "${_EP_UPDATE_DISCONNECTED}") else() get_property(update_disconnected DIRECTORY PROPERTY EP_UPDATE_DISCONNECTED) endif() set(${var} "${update_disconnected}" PARENT_SCOPE) endfunction() -function(_ep_add_update_command name) - ExternalProject_Get_Property(${name} source_dir tmp_dir) - - get_property(cmd_set TARGET ${name} PROPERTY _EP_UPDATE_COMMAND SET) - get_property(cmd TARGET ${name} PROPERTY _EP_UPDATE_COMMAND) - get_property(cvs_repository TARGET ${name} PROPERTY _EP_CVS_REPOSITORY) - get_property(svn_repository TARGET ${name} PROPERTY _EP_SVN_REPOSITORY) - get_property(git_repository TARGET ${name} PROPERTY _EP_GIT_REPOSITORY) - get_property(hg_repository TARGET ${name} PROPERTY _EP_HG_REPOSITORY ) +# This function is an implementation detail of ExternalProject_Add() and +# _ep_do_preconfigure_steps_now(). +# +# The function expects keyword arguments to have already been parsed into +# variables of the form _EP_<keyword>. +# +# Variables will also be set in the calling scope to enable subsequently +# calling _ep_add_preconfigure_command() for the updated step. +# +function(_ep_prepare_update name genex_supported) - _ep_get_update_disconnected(update_disconnected ${name}) + set(tmp_dir "${_EP_TMP_DIR}") + set(source_dir "${_EP_SOURCE_DIR}") + set(quiet "${_EP_QUIET}") - set(work_dir) set(comment) - set(always) - if(cmd_set) + _ep_get_update_disconnected(update_disconnected) + + # We handle the log setting directly here rather than deferring it to + # be handled by ExternalProject_Add_Step() + set(log "${_EP_LOG_UPDATE}") + if(log) + set(script_filename ${tmp_dir}/${name}-update-impl.cmake) + set(log TRUE) + set(quiet FALSE) # Already quiet as a result of log being enabled + else() + set(script_filename ${tmp_dir}/${name}-update.cmake) + set(log FALSE) + endif() + + if(DEFINED _EP_UPDATE_COMMAND) set(work_dir ${source_dir}) - if(NOT "x${cmd}" STREQUAL "x") - set(always 1) - endif() - elseif(cvs_repository) + _ep_write_command_script( + "${script_filename}" + "${_EP_UPDATE_COMMAND}" + "${work_dir}" + "${genex_supported}" + "${quiet}" + script_does_something + ) + set(comment "Performing update step (custom command) for '${name}'") + + elseif(DEFINED _EP_CVS_REPOSITORY) if(NOT CVS_EXECUTABLE) message(FATAL_ERROR "error: could not find cvs for update of ${name}") endif() + set(work_dir ${source_dir}) + set(cmd "${CVS_EXECUTABLE}" -d "${_EP_CVS_REPOSITORY}" -q + up -dP ${_EP_CVS_TAG} + ) + _ep_write_command_script( + "${script_filename}" + "${cmd}" + "${work_dir}" + "${genex_supported}" + "${quiet}" + script_does_something + ) set(comment "Performing update step (CVS update) for '${name}'") - get_property(cvs_tag TARGET ${name} PROPERTY _EP_CVS_TAG) - set(cmd ${CVS_EXECUTABLE} -d ${cvs_repository} -q up -dP ${cvs_tag}) - set(always 1) - elseif(svn_repository) + + elseif(DEFINED _EP_SVN_REPOSITORY) if(NOT Subversion_SVN_EXECUTABLE) message(FATAL_ERROR "error: could not find svn for update of ${name}") endif() - set(work_dir ${source_dir}) - set(comment "Performing update step (SVN update) for '${name}'") - get_property(svn_revision TARGET ${name} PROPERTY _EP_SVN_REVISION) - get_property(svn_username TARGET ${name} PROPERTY _EP_SVN_USERNAME) - get_property(svn_password TARGET ${name} PROPERTY _EP_SVN_PASSWORD) - get_property(svn_trust_cert TARGET ${name} PROPERTY _EP_SVN_TRUST_CERT) - set(svn_user_pw_args "") - if(DEFINED svn_username) - set(svn_user_pw_args ${svn_user_pw_args} "--username=${svn_username}") + + set(svn_revision "${_EP_SVN_REVISION}") + set(svn_username "${_EP_SVN_USERNAME}") + set(svn_password "${_EP_SVN_PASSWORD}") + set(svn_trust_cert "${_EP_SVN_TRUST_CERT}") + + set(svn_options --non-interactive) + if(DEFINED _EP_SVN_USERNAME) + list(APPEND svn_options "--username=${svn_username}") endif() - if(DEFINED svn_password) - set(svn_user_pw_args ${svn_user_pw_args} "--password=${svn_password}") + if(DEFINED _EP_SVN_PASSWORD) + list(APPEND svn_options "--password=${svn_password}") endif() if(svn_trust_cert) - set(svn_trust_cert_args --trust-server-cert) + list(APPEND svn_options --trust-server-cert) endif() - set(cmd ${Subversion_SVN_EXECUTABLE} up ${svn_revision} - --non-interactive ${svn_trust_cert_args} ${svn_user_pw_args}) - set(always 1) - elseif(git_repository) + + set(work_dir ${source_dir}) + set(cmd "${Subversion_SVN_EXECUTABLE}" up ${svn_revision} ${svn_options}) + + _ep_write_command_script( + "${script_filename}" + "${cmd}" + "${work_dir}" + "${genex_supported}" + "${quiet}" + script_does_something + ) + set(comment "Performing update step (SVN update) for '${name}'") + + elseif(DEFINED _EP_GIT_REPOSITORY) # FetchContent gives us these directly, so don't try to recompute them if(NOT GIT_EXECUTABLE OR NOT GIT_VERSION_STRING) unset(CMAKE_MODULE_PATH) # Use CMake builtin find module @@ -2969,27 +3203,27 @@ function(_ep_add_update_command name) message(FATAL_ERROR "error: could not find git for fetch of ${name}") endif() endif() - set(work_dir ${source_dir}) - set(comment "Performing update step for '${name}'") - get_property(git_tag TARGET ${name} PROPERTY _EP_GIT_TAG) + + set(git_tag "${_EP_GIT_TAG}") if(NOT git_tag) set(git_tag "master") endif() - get_property(git_remote_name TARGET ${name} PROPERTY _EP_GIT_REMOTE_NAME) + + set(git_remote_name "${_EP_GIT_REMOTE_NAME}") if(NOT git_remote_name) set(git_remote_name "origin") endif() set(git_init_submodules TRUE) - get_property(git_submodules_set TARGET ${name} PROPERTY _EP_GIT_SUBMODULES SET) - if(git_submodules_set) - get_property(git_submodules TARGET ${name} PROPERTY _EP_GIT_SUBMODULES) + if(DEFINED _EP_GIT_SUBMODULES) + set(git_submodules "${_EP_GIT_SUBMODULES}") if(git_submodules STREQUAL "" AND _EP_CMP0097 STREQUAL "NEW") set(git_init_submodules FALSE) endif() endif() + _ep_get_git_submodules_recurse(git_submodules_recurse) - get_property(git_update_strategy TARGET ${name} PROPERTY _EP_GIT_REMOTE_UPDATE_STRATEGY) + set(git_update_strategy "${_EP_GIT_REMOTE_UPDATE_STRATEGY}") if(NOT git_update_strategy) set(git_update_strategy "${CMAKE_EP_GIT_REMOTE_UPDATE_STRATEGY}") endif() @@ -3001,23 +3235,28 @@ function(_ep_add_update_command name) message(FATAL_ERROR "'${git_update_strategy}' is not one of the supported strategies: ${strategies}") endif() - _ep_get_git_submodules_recurse(git_submodules_recurse) + set(work_dir ${source_dir}) + _ep_write_gitupdate_script( + "${script_filename}" + "${GIT_EXECUTABLE}" + "${git_tag}" + "${git_remote_name}" + "${git_init_submodules}" + "${git_submodules_recurse}" + "${git_submodules}" + "${_EP_GIT_REPOSITORY}" + "${work_dir}" + "${git_update_strategy}" + "${quiet}" + ) + set(script_does_something TRUE) + set(comment "Performing update step (git update) for '${name}'") - _ep_write_gitupdate_script(${tmp_dir}/${name}-gitupdate.cmake - ${GIT_EXECUTABLE} ${git_tag} ${git_remote_name} ${git_init_submodules} "${git_submodules_recurse}" "${git_submodules}" ${git_repository} ${work_dir} ${git_update_strategy} - ) - set(cmd ${CMAKE_COMMAND} -P ${tmp_dir}/${name}-gitupdate.cmake) - set(always 1) - elseif(hg_repository) + elseif(DEFINED _EP_HG_REPOSITORY) if(NOT HG_EXECUTABLE) message(FATAL_ERROR "error: could not find hg for pull of ${name}") endif() - set(work_dir ${source_dir}) - set(comment "Performing update step (hg pull) for '${name}'") - get_property(hg_tag TARGET ${name} PROPERTY _EP_HG_TAG) - if(NOT hg_tag) - set(hg_tag "tip") - endif() + if("${HG_VERSION_STRING}" STREQUAL "2.1") message(WARNING "Mercurial 2.1 does not distinguish an empty pull from a failed pull: http://mercurial.selenic.com/wiki/UpgradeNotes#A2.1.1:_revert_pull_return_code_change.2C_compile_issue_on_OS_X @@ -3025,87 +3264,117 @@ function(_ep_add_update_command name) Update to Mercurial >= 2.1.1. ") endif() - set(cmd ${HG_EXECUTABLE} pull - COMMAND ${HG_EXECUTABLE} update ${hg_tag} - ) - set(always 1) - endif() - get_property(log TARGET ${name} PROPERTY _EP_LOG_UPDATE) - if(log) - set(log LOG 1) + set(hg_tag "${_EP_HG_TAG}") + if(NOT hg_tag) + set(hg_tag "tip") + endif() + + set(work_dir ${source_dir}) + _ep_write_hgupdate_script( + "${script_filename}" + "${HG_EXECUTABLE}" + "${hg_tag}" + "${work_dir}" + "${quiet}" + ) + set(script_does_something TRUE) + set(comment "Performing update step (hg pull) for '${name}'") + else() - set(log "") + set(script_does_something FALSE) endif() - get_property(uses_terminal TARGET ${name} PROPERTY - _EP_USES_TERMINAL_UPDATE) - if(uses_terminal) - set(uses_terminal USES_TERMINAL 1) + # Provide variables that can be used later to create a custom command or + # invoke the step directly + if(script_does_something) + set(always TRUE) + set(cmd ${CMAKE_COMMAND} -P ${script_filename}) + if(log) + _ep_write_log_script(${name} update "${genex_supported}" cmd) + endif() else() - set(uses_terminal "") + set(always FALSE) + set(cmd) endif() - set(__cmdQuoted) - foreach(__item IN LISTS cmd) - string(APPEND __cmdQuoted " [==[${__item}]==]") - endforeach() - cmake_language(EVAL CODE " - ExternalProject_Add_Step(${name} update - INDEPENDENT TRUE - COMMENT \${comment} - COMMAND ${__cmdQuoted} - ALWAYS \${always} - EXCLUDE_FROM_MAIN \${update_disconnected} - WORKING_DIRECTORY \${work_dir} - DEPENDEES download - ${log} - ${uses_terminal} - )" - ) + set(_EPcomment_UPDATE "${comment}" PARENT_SCOPE) + set(_EPcommand_UPDATE "${cmd}" PARENT_SCOPE) + set(_EPalways_UPDATE "${always}" PARENT_SCOPE) + set(_EPexcludefrommain_UPDATE "${update_disconnected}" PARENT_SCOPE) + set(_EPdepends_UPDATE "" PARENT_SCOPE) + set(_EPdependees_UPDATE download PARENT_SCOPE) endfunction() +# This function is an implementation detail of ExternalProject_Add() and +# _ep_do_preconfigure_steps_now(). +# +# The function expects keyword arguments to have already been parsed into +# variables of the form _EP_<keyword>. +# +# Variables will also be set in the calling scope to enable subsequently +# calling _ep_add_preconfigure_command() for the patch step. +# +function(_ep_prepare_patch name genex_supported) -function(_ep_add_patch_command name) - ExternalProject_Get_Property(${name} source_dir) - - get_property(cmd_set TARGET ${name} PROPERTY _EP_PATCH_COMMAND SET) - get_property(cmd TARGET ${name} PROPERTY _EP_PATCH_COMMAND) - - set(work_dir) + set(tmp_dir "${_EP_TMP_DIR}") + set(source_dir "${_EP_SOURCE_DIR}") + set(quiet "${_EP_QUIET}") - if(cmd_set) - set(work_dir ${source_dir}) + _ep_get_update_disconnected(update_disconnected) + if(update_disconnected) + set(patch_dep download) + else() + set(patch_dep update) endif() - get_property(log TARGET ${name} PROPERTY _EP_LOG_PATCH) + # We handle the log setting directly here rather than deferring it to + # be handled by ExternalProject_Add_Step() + set(log "${_EP_LOG_PATCH}") if(log) - set(log LOG 1) + set(script_filename ${tmp_dir}/${name}-patch-impl.cmake) + set(log TRUE) + set(quiet FALSE) # Already quiet as a result of log being enabled else() - set(log "") + set(script_filename ${tmp_dir}/${name}-patch.cmake) + set(log FALSE) endif() - _ep_get_update_disconnected(update_disconnected ${name}) - if(update_disconnected) - set(patch_dep download) + if(DEFINED _EP_PATCH_COMMAND) + set(work_dir "${source_dir}") + _ep_write_command_script( + "${script_filename}" + "${_EP_PATCH_COMMAND}" + "${work_dir}" + "${genex_supported}" + "${quiet}" + script_does_something + ) + if(script_does_something) + set(cmd ${CMAKE_COMMAND} -P ${script_filename}) + if(log) + _ep_write_log_script(${name} patch "${genex_supported}" cmd) + endif() + set(comment "Performing patch step (custom command) for '${name}'") + else() + set(cmd) + set(comment "Skipping patch step (empty custom command) for '${name}'") + endif() else() - set(patch_dep update) + set(cmd) + set(comment "Skipping patch step (no custom command) for '${name}'") endif() - set(__cmdQuoted) - foreach(__item IN LISTS cmd) - string(APPEND __cmdQuoted " [==[${__item}]==]") - endforeach() - cmake_language(EVAL CODE " - ExternalProject_Add_Step(${name} patch - INDEPENDENT TRUE - COMMAND ${__cmdQuoted} - WORKING_DIRECTORY \${work_dir} - DEPENDEES \${patch_dep} - ${log} - )" - ) + # Provide variables that can be used later to create a custom command or + # invoke the step directly + set(_EPcomment_PATCH "${comment}" PARENT_SCOPE) + set(_EPcommand_PATCH "${cmd}" PARENT_SCOPE) + set(_EPalways_PATCH FALSE PARENT_SCOPE) + set(_EPexcludefrommain_PATCH FALSE PARENT_SCOPE) + set(_EPdepends_PATCH "" PARENT_SCOPE) + set(_EPdependees_PATCH "${patch_dep}" PARENT_SCOPE) + endfunction() function(_ep_get_file_deps var name) @@ -3215,7 +3484,11 @@ function(_ep_extract_configure_command var name) if(has_cmake_cache_default_args) _ep_command_line_to_initial_cache(script_initial_cache_default "${cmake_cache_default_args}" 0) endif() - _ep_write_initial_cache(${name} "${_ep_cache_args_script}" "${script_initial_cache_force}${script_initial_cache_default}") + _ep_write_initial_cache( + ${name} + "${_ep_cache_args_script}" + "${script_initial_cache_force}${script_initial_cache_default}" + ) list(APPEND cmd "-C${_ep_cache_args_script}") _ep_replace_location_tags(${name} _ep_cache_args_script) set(_ep_cache_args_script @@ -3247,10 +3520,11 @@ function(_ep_add_configure_command name) # used, cmake args or cmake generator) then re-run the configure step. # Fixes issue https://gitlab.kitware.com/cmake/cmake/-/issues/10258 # - if(NOT EXISTS ${tmp_dir}/${name}-cfgcmd.txt.in) - file(WRITE ${tmp_dir}/${name}-cfgcmd.txt.in "cmd='\@cmd\@'\n") - endif() - configure_file(${tmp_dir}/${name}-cfgcmd.txt.in ${tmp_dir}/${name}-cfgcmd.txt) + configure_file( + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject/cfgcmd.txt.in + ${tmp_dir}/${name}-cfgcmd.txt + @ONLY + ) list(APPEND file_deps ${tmp_dir}/${name}-cfgcmd.txt) list(APPEND file_deps ${_ep_cache_args_script}) @@ -3461,6 +3735,205 @@ function(_ep_add_test_command name) endif() endfunction() +macro(_ep_get_add_keywords out_var) + set(${out_var} + # + # Directory options + # + PREFIX + TMP_DIR + STAMP_DIR + LOG_DIR + DOWNLOAD_DIR + SOURCE_DIR + BINARY_DIR + INSTALL_DIR + # + # Download step options + # + DOWNLOAD_COMMAND + # + URL + URL_HASH + URL_MD5 + DOWNLOAD_NAME + DOWNLOAD_NO_EXTRACT + DOWNLOAD_NO_PROGRESS + TIMEOUT + INACTIVITY_TIMEOUT + HTTP_USERNAME + HTTP_PASSWORD + HTTP_HEADER + TLS_VERIFY # Also used for git clone operations + TLS_CAINFO + NETRC + NETRC_FILE + # + GIT_REPOSITORY + GIT_TAG + GIT_REMOTE_NAME + GIT_SUBMODULES + GIT_SUBMODULES_RECURSE + GIT_SHALLOW + GIT_PROGRESS + GIT_CONFIG + GIT_REMOTE_UPDATE_STRATEGY + # + SVN_REPOSITORY + SVN_REVISION + SVN_USERNAME + SVN_PASSWORD + SVN_TRUST_CERT + # + HG_REPOSITORY + HG_TAG + # + CVS_REPOSITORY + CVS_MODULE + CVS_TAG + # + # Update step options + # + UPDATE_COMMAND + UPDATE_DISCONNECTED + # + # Patch step options + # + PATCH_COMMAND + # + # Configure step options + # + CONFIGURE_COMMAND + CMAKE_COMMAND + CMAKE_GENERATOR + CMAKE_GENERATOR_PLATFORM + CMAKE_GENERATOR_TOOLSET + CMAKE_GENERATOR_INSTANCE + CMAKE_ARGS + CMAKE_CACHE_ARGS + CMAKE_CACHE_DEFAULT_ARGS + SOURCE_SUBDIR + CONFIGURE_HANDLED_BY_BUILD + # + # Build step options + # + BUILD_COMMAND + BUILD_IN_SOURCE + BUILD_ALWAYS + BUILD_BYPRODUCTS + # + # Install step options + # + INSTALL_COMMAND + # + # Test step options + # + TEST_COMMAND + TEST_BEFORE_INSTALL + TEST_AFTER_INSTALL + TEST_EXCLUDE_FROM_MAIN + # + # Logging options + # + LOG_DOWNLOAD + LOG_UPDATE + LOG_PATCH + LOG_CONFIGURE + LOG_BUILD + LOG_INSTALL + LOG_TEST + LOG_MERGED_STDOUTERR + LOG_OUTPUT_ON_FAILURE + # + # Terminal access options + # + USES_TERMINAL_DOWNLOAD + USES_TERMINAL_UPDATE + USES_TERMINAL_CONFIGURE + USES_TERMINAL_BUILD + USES_TERMINAL_INSTALL + USES_TERMINAL_TEST + # + # Target options + # + DEPENDS + EXCLUDE_FROM_ALL + STEP_TARGETS + INDEPENDENT_STEP_TARGETS + # + # Miscellaneous options + # + LIST_SEPARATOR + ) +endmacro() + + +# Internal function called by FetchContent to populate immediately. +# It only executes steps up to and including "patch". It takes the same +# arguments as ExternalProject_Add() plus one additional argument: QUIET. +# +# Not to be used outside of CMake. +# +function(_ep_do_preconfigure_steps_now name) + + cmake_policy(GET CMP0097 _EP_CMP0097 + PARENT_SCOPE # undocumented, do not use outside of CMake + ) + + set(genex_supported FALSE) + + _ep_get_add_keywords(keywords) + _ep_parse_arguments_to_vars("${keywords};QUIET" ${name} _EP_ "${ARGN}") + + _ep_get_update_disconnected(update_disconnected) + + _ep_prepare_directories(${name}) + _ep_prepare_download(${name} ${genex_supported}) + _ep_prepare_update(${name} ${genex_supported}) + _ep_prepare_patch(${name} ${genex_supported}) + + set(stamp_dir "${_EP_STAMP_DIR}") + set(tmp_dir "${_EP_TMP_DIR}") + + # Once any step has to run, all later steps have to be run too + set(need_to_run FALSE) + foreach(step IN ITEMS download update parse) + if(update_disconnected AND "${step}" STREQUAL "update") + continue() + endif() + + string(TOUPPER "${step}" STEP) + if("${_EPcommand_${STEP}}" STREQUAL "") + continue() + endif() + + set(stamp_file "${stamp_dir}/${name}-${step}") + set(script_file ${tmp_dir}/${name}-${step}.cmake) + + if(NOT EXISTS ${stamp_file}) + set(need_to_run TRUE) + endif() + + if(NOT need_to_run) + foreach(dep_file ${script_file} ${_EPdepends_${STEP}}) + if(NOT EXISTS ${dep_file} OR ${dep_file} IS_NEWER_THAN ${stamp_file}) + set(need_to_run TRUE) + break() + endif() + endforeach() + endif() + + if(need_to_run) + include(${script_file}) + file(TOUCH ${stamp_file}) + endif() + endforeach() + + if("${_EP_DOWNLOAD_NO_EXTRACT}") + file(COPY "${_EP_DOWNLOADED_FILE}" DESTINATION "${_EP_SOURCE_DIR}") + endif() + +endfunction() function(ExternalProject_Add name) cmake_policy(GET CMP0097 _EP_CMP0097 @@ -3482,6 +3955,15 @@ function(ExternalProject_Add name) set(cmp0114 "NEW") endif() + set(genex_supported TRUE) + + _ep_get_add_keywords(keywords) + _ep_parse_arguments_to_vars("${keywords}" ${name} _EP_ "${ARGN}") + _ep_prepare_directories(${name}) + _ep_prepare_download(${name} ${genex_supported}) + _ep_prepare_update(${name} ${genex_supported}) + _ep_prepare_patch(${name} ${genex_supported}) + _ep_get_configuration_subdir_suffix(cfgdir) # Add a custom target for the external project. @@ -3505,14 +3987,18 @@ function(ExternalProject_Add name) set_property(TARGET ${name} PROPERTY _EP_CMP0114 "${cmp0114}") - _ep_parse_arguments(ExternalProject_Add ${name} _EP_ "${ARGN}") - _ep_set_directories(${name}) + # Transfer the arguments to the target as target properties. These are + # read by the various steps, potentially from different scopes. + foreach(key IN LISTS keywords ITEMS DOWNLOADED_FILE) + if(DEFINED _EP_${key}) + set_property(TARGET ${name} PROPERTY _EP_${key} "${_EP_${key}}") + endif() + endforeach() + _ep_get_step_stampfile(${name} "done" done_stamp_file) _ep_get_step_stampfile(${name} "install" install_stamp_file) - # Set the EXCLUDE_FROM_ALL target property if required. - get_property(exclude_from_all TARGET ${name} PROPERTY _EP_EXCLUDE_FROM_ALL) - if(exclude_from_all) + if(arg_EXCLUDE_FROM_ALL) set_property(TARGET ${name} PROPERTY EXCLUDE_FROM_ALL TRUE) endif() @@ -3553,10 +4039,10 @@ function(ExternalProject_Add name) # The target depends on the output of the final step. # (Already set up above in the DEPENDS of the add_custom_target command.) # - _ep_add_mkdir_command(${name}) - _ep_add_download_command(${name}) - _ep_add_update_command(${name}) - _ep_add_patch_command(${name}) + _ep_add_preconfigure_command(${name} mkdir) + _ep_add_preconfigure_command(${name} download) + _ep_add_preconfigure_command(${name} update) + _ep_add_preconfigure_command(${name} patch) _ep_add_configure_command(${name}) _ep_add_build_command(${name}) _ep_add_install_command(${name}) diff --git a/Modules/ExternalProject/RepositoryInfo.txt.in b/Modules/ExternalProject/RepositoryInfo.txt.in new file mode 100644 index 0000000..d82f04c --- /dev/null +++ b/Modules/ExternalProject/RepositoryInfo.txt.in @@ -0,0 +1 @@ +@repo_info_content@ diff --git a/Modules/ExternalProject/captured_process_setup.cmake b/Modules/ExternalProject/captured_process_setup.cmake new file mode 100644 index 0000000..9c8abb1 --- /dev/null +++ b/Modules/ExternalProject/captured_process_setup.cmake @@ -0,0 +1,55 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +if(quiet) + set(capture_output + OUTPUT_VARIABLE out_var + ERROR_VARIABLE out_var + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_STRIP_TRAILING_WHITESPACE + ) + set(capture_error_only + ERROR_VARIABLE out_var + ERROR_STRIP_TRAILING_WHITESPACE + ) +else() + unset(capture_output) + unset(capture_error_only) +endif() + +set(out_var "") +set(accumulated_output "") + +macro(_ep_message_quiet_capture mode) + if("${mode}" STREQUAL "FATAL_ERROR") + string(JOIN "" detail "${ARGN}") + if(NOT detail STREQUAL "" AND NOT accumulated_output STREQUAL "") + string(PREPEND detail "\n") + endif() + message(FATAL_ERROR "${accumulated_output}${detail}") + endif() + + if(quiet) + if("${mode}" MATCHES "WARNING") + # We can't provide the full CMake backtrace, but we can at least record + # the warning message with a sensible prefix + string(APPEND accumulated_output "${mode}: ") + endif() + string(APPEND accumulated_output "${ARGN}\n") + else() + message(${mode} ${ARGN}) + endif() +endmacro() + +macro(_ep_accumulate_captured_output) + if(NOT "${out_var}" STREQUAL "") + string(APPEND accumulated_output "${out_var}\n") + endif() +endmacro() + +macro(_ep_command_check_result result) + _ep_accumulate_captured_output() + if(result) + _ep_message_quiet_capture(FATAL_ERROR ${ARGN}) + endif() +endmacro() diff --git a/Modules/ExternalProject/cfgcmd.txt.in b/Modules/ExternalProject/cfgcmd.txt.in new file mode 100644 index 0000000..b3f09ef --- /dev/null +++ b/Modules/ExternalProject/cfgcmd.txt.in @@ -0,0 +1 @@ +cmd='@cmd@' diff --git a/Modules/ExternalProject/copydir.cmake.in b/Modules/ExternalProject/copydir.cmake.in new file mode 100644 index 0000000..5dd3891 --- /dev/null +++ b/Modules/ExternalProject/copydir.cmake.in @@ -0,0 +1,10 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +cmake_minimum_required(VERSION 3.5) + +file(REMOVE_RECURSE "@to_dir@") + +# Copy the _contents_ of the source dir into the destination dir, hence the +# trailing slash on the from_dir +file(COPY "@from_dir@/" DESTINATION "@to_dir@") diff --git a/Modules/ExternalProject/customcommand.cmake.in b/Modules/ExternalProject/customcommand.cmake.in new file mode 100644 index 0000000..d41f31b --- /dev/null +++ b/Modules/ExternalProject/customcommand.cmake.in @@ -0,0 +1,8 @@ + +execute_process( + COMMAND @this_command@ + WORKING_DIRECTORY "@work_dir@" + RESULT_VARIABLE result + ${capture_output} +) +_ep_command_check_result(result) diff --git a/Modules/ExternalProject/customcommand_preamble.cmake.in b/Modules/ExternalProject/customcommand_preamble.cmake.in new file mode 100644 index 0000000..ae4fec6 --- /dev/null +++ b/Modules/ExternalProject/customcommand_preamble.cmake.in @@ -0,0 +1,8 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +cmake_minimum_required(VERSION 3.5) + +set(quiet "@quiet@") +set(script_dir "@CMAKE_CURRENT_FUNCTION_LIST_DIR@/ExternalProject") +include(${script_dir}/captured_process_setup.cmake) diff --git a/Modules/ExternalProject/download.cmake.in b/Modules/ExternalProject/download.cmake.in new file mode 100644 index 0000000..c8d2f28 --- /dev/null +++ b/Modules/ExternalProject/download.cmake.in @@ -0,0 +1,205 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +cmake_minimum_required(VERSION 3.5) + +set(quiet "@quiet@") +set(script_dir "@CMAKE_CURRENT_FUNCTION_LIST_DIR@/ExternalProject") +include(${script_dir}/captured_process_setup.cmake) + +function(check_file_hash has_hash hash_is_good) + if("${has_hash}" STREQUAL "") + _ep_message_quiet_capture(FATAL_ERROR "has_hash Can't be empty") + endif() + + if("${hash_is_good}" STREQUAL "") + _ep_message_quiet_capture(FATAL_ERROR "hash_is_good Can't be empty") + endif() + + if("@ALGO@" STREQUAL "") + # No check + set("${has_hash}" FALSE PARENT_SCOPE) + set("${hash_is_good}" FALSE PARENT_SCOPE) + return() + endif() + + set("${has_hash}" TRUE PARENT_SCOPE) + + _ep_message_quiet_capture(STATUS "verifying file... + file='@LOCAL@'") + set(accumulated_output "${accumulated_output}" PARENT_SCOPE) + + file("@ALGO@" "@LOCAL@" actual_value) + + if(NOT "${actual_value}" STREQUAL "@EXPECT_VALUE@") + set("${hash_is_good}" FALSE PARENT_SCOPE) + _ep_message_quiet_capture(STATUS "@ALGO@ hash of + @LOCAL@ + does not match expected value + expected: '@EXPECT_VALUE@' + actual: '${actual_value}'") + set(accumulated_output "${accumulated_output}" PARENT_SCOPE) + else() + set("${hash_is_good}" TRUE PARENT_SCOPE) + endif() +endfunction() + +function(sleep_before_download attempt) + if(attempt EQUAL 0) + return() + endif() + + if(attempt EQUAL 1) + _ep_message_quiet_capture(STATUS "Retrying...") + set(accumulated_output "${accumulated_output}" PARENT_SCOPE) + return() + endif() + + set(sleep_seconds 0) + + if(attempt EQUAL 2) + set(sleep_seconds 5) + elseif(attempt EQUAL 3) + set(sleep_seconds 5) + elseif(attempt EQUAL 4) + set(sleep_seconds 15) + elseif(attempt EQUAL 5) + set(sleep_seconds 60) + elseif(attempt EQUAL 6) + set(sleep_seconds 90) + elseif(attempt EQUAL 7) + set(sleep_seconds 300) + else() + set(sleep_seconds 1200) + endif() + + _ep_message_quiet_capture(STATUS + "Retry after ${sleep_seconds} seconds (attempt #${attempt}) ..." + ) + set(accumulated_output "${accumulated_output}" PARENT_SCOPE) + + execute_process(COMMAND "${CMAKE_COMMAND}" -E sleep "${sleep_seconds}") +endfunction() + +if("@LOCAL@" STREQUAL "") + message(FATAL_ERROR "LOCAL can't be empty") +endif() + +if("@REMOTE@" STREQUAL "") + message(FATAL_ERROR "REMOTE can't be empty") +endif() + +function(download_and_verify) + if(EXISTS "@LOCAL@") + check_file_hash(has_hash hash_is_good) + if(has_hash) + if(hash_is_good) + _ep_message_quiet_capture(STATUS +"File already exists and hash match (skip download): + file='@LOCAL@' + @ALGO@='@EXPECT_VALUE@'" + ) + set(accumulated_output "${accumulated_output}" PARENT_SCOPE) + return() + else() + _ep_message_quiet_capture(STATUS + "File already exists but hash mismatch. Removing..." + ) + set(accumulated_output "${accumulated_output}" PARENT_SCOPE) + file(REMOVE "@LOCAL@") + endif() + else() + _ep_message_quiet_capture(STATUS +"File already exists but no hash specified (use URL_HASH): + file='@LOCAL@' +Old file will be removed and new file downloaded from URL." + ) + file(REMOVE "@LOCAL@") + endif() + endif() + + set(retry_number 5) + + _ep_message_quiet_capture(STATUS "Downloading... + dst='@LOCAL@' + timeout='@TIMEOUT_MSG@' + inactivity timeout='@INACTIVITY_TIMEOUT_MSG@'" + ) + set(accumulated_output "${accumulated_output}" PARENT_SCOPE) + set(download_retry_codes 7 6 8 15) + set(skip_url_list) + set(status_code) + foreach(i RANGE ${retry_number}) + if(status_code IN_LIST download_retry_codes) + sleep_before_download(${i}) + endif() + foreach(url @REMOTE@) + if(NOT url IN_LIST skip_url_list) + _ep_message_quiet_capture(STATUS "Using src='${url}'") + set(accumulated_output "${accumulated_output}" PARENT_SCOPE) + + @TLS_VERIFY_CODE@ + @TLS_CAINFO_CODE@ + @NETRC_CODE@ + @NETRC_FILE_CODE@ + + file( + DOWNLOAD + "${url}" "@LOCAL@" + @SHOW_PROGRESS@ + @TIMEOUT_ARGS@ + @INACTIVITY_TIMEOUT_ARGS@ + STATUS status + LOG log + @USERPWD_ARGS@ + @HTTP_HEADERS_ARGS@ + ) + + list(GET status 0 status_code) + list(GET status 1 status_string) + + if(status_code EQUAL 0) + check_file_hash(has_hash hash_is_good) + if(has_hash AND NOT hash_is_good) + _ep_message_quiet_capture(STATUS "Hash mismatch, removing...") + set(accumulated_output "${accumulated_output}" PARENT_SCOPE) + file(REMOVE "@LOCAL@") + else() + _ep_message_quiet_capture(STATUS "Downloading... done") + set(accumulated_output "${accumulated_output}" PARENT_SCOPE) + return() + endif() + else() + string(APPEND logFailedURLs +"error: downloading '${url}' failed + status_code: ${status_code} + status_string: ${status_string} + log: + --- LOG BEGIN --- + ${log} + --- LOG END --- + " + ) + if(NOT status_code IN_LIST download_retry_codes) + list(APPEND skip_url_list "${url}") + break() + endif() + endif() + endif() + endforeach() + endforeach() + + _ep_message_quiet_capture(FATAL_ERROR +"Each download failed! + ${logFailedURLs} + " + ) + +endfunction() + +download_and_verify() + +set(extract_script @extract_script_filename@) +if(NOT "${extract_script}" STREQUAL "") + include(${extract_script}) +endif() diff --git a/Modules/ExternalProject/extractfile.cmake.in b/Modules/ExternalProject/extractfile.cmake.in new file mode 100644 index 0000000..d46de73 --- /dev/null +++ b/Modules/ExternalProject/extractfile.cmake.in @@ -0,0 +1,73 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +cmake_minimum_required(VERSION 3.5) + +set(quiet "@quiet@") +set(script_dir "@CMAKE_CURRENT_FUNCTION_LIST_DIR@/ExternalProject") +include(${script_dir}/captured_process_setup.cmake) + +# Make file names absolute: +# +get_filename_component(filename "@filename@" ABSOLUTE) +get_filename_component(directory "@directory@" ABSOLUTE) + +_ep_message_quiet_capture(STATUS "extracting... + src='${filename}' + dst='${directory}'" +) + +if(NOT EXISTS "${filename}") + _ep_message_quiet_capture(FATAL_ERROR + "File to extract does not exist: '${filename}'" + ) +endif() + +# Prepare a space for extracting: +# +set(i 1234) +while(EXISTS "${directory}/../ex-@name@${i}") + math(EXPR i "${i} + 1") +endwhile() +set(ut_dir "${directory}/../ex-@name@${i}") +file(MAKE_DIRECTORY "${ut_dir}") + +# Extract it: +# +_ep_message_quiet_capture(STATUS "extracting... [tar @args@]") +execute_process(COMMAND ${CMAKE_COMMAND} -E tar @args@ ${filename} + WORKING_DIRECTORY ${ut_dir} + RESULT_VARIABLE rv + ${capture_output} +) +_ep_accumulate_captured_output() + +if(NOT rv EQUAL 0) + _ep_message_quiet_capture(STATUS "extracting... [error clean up]") + file(REMOVE_RECURSE "${ut_dir}") + _ep_message_quiet_capture(FATAL_ERROR "Extract of '${filename}' failed") +endif() + +# Analyze what came out of the tar file: +# +_ep_message_quiet_capture(STATUS "extracting... [analysis]") +file(GLOB contents "${ut_dir}/*") +list(REMOVE_ITEM contents "${ut_dir}/.DS_Store") +list(LENGTH contents n) +if(NOT n EQUAL 1 OR NOT IS_DIRECTORY "${contents}") + set(contents "${ut_dir}") +endif() + +# Move "the one" directory to the final directory: +# +_ep_message_quiet_capture(STATUS "extracting... [rename]") +file(REMOVE_RECURSE ${directory}) +get_filename_component(contents ${contents} ABSOLUTE) +file(RENAME ${contents} ${directory}) + +# Clean up: +# +_ep_message_quiet_capture(STATUS "extracting... [clean up]") +file(REMOVE_RECURSE "${ut_dir}") + +_ep_message_quiet_capture(STATUS "extracting... done") diff --git a/Modules/ExternalProject/gitclone.cmake.in b/Modules/ExternalProject/gitclone.cmake.in new file mode 100644 index 0000000..a2e900c --- /dev/null +++ b/Modules/ExternalProject/gitclone.cmake.in @@ -0,0 +1,92 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +cmake_minimum_required(VERSION 3.5) + +set(quiet "@quiet@") +set(script_dir "@CMAKE_CURRENT_FUNCTION_LIST_DIR@/ExternalProject") +include(${script_dir}/captured_process_setup.cmake) + +if(NOT "@gitclone_infofile@" IS_NEWER_THAN "@gitclone_stampfile@") + if(NOT quiet) + message(STATUS + "Avoiding repeated git clone, stamp file is up to date: " + "'@gitclone_stampfile@'" + ) + endif() + return() +endif() + +execute_process( + COMMAND ${CMAKE_COMMAND} -E rm -rf "@source_dir@" + RESULT_VARIABLE error_code + ${capture_output} +) +_ep_command_check_result( + error_code "Failed to remove directory: '@source_dir@'" +) + +# try the clone 3 times in case there is an odd git clone issue +set(error_code 1) +set(number_of_tries 0) +while(error_code AND number_of_tries LESS 3) + # If you are seeing the following call hang and you have QUIET enabled, try + # turning QUIET off to show any output immediately. The command may be + # blocking while waiting for user input (e.g. a password to a SSH key). + execute_process( + COMMAND "@git_EXECUTABLE@" @git_options@ + clone @git_clone_options@ "@git_repository@" "@src_name@" + WORKING_DIRECTORY "@work_dir@" + RESULT_VARIABLE error_code + ${capture_output} + ) + if(NOT "${out_var}" STREQUAL "") + string(APPEND accumulated_output "${out_var}\n") + endif() + math(EXPR number_of_tries "${number_of_tries} + 1") +endwhile() +if(number_of_tries GREATER 1) + set(msg "Had to git clone more than once: ${number_of_tries} times.") + if(quiet) + string(APPEND accumulated_output "${msg}\n") + else() + message(STATUS "${msg}") + endif() +endif() +_ep_command_check_result( + error_code "Failed to clone repository: '@git_repository@'" +) + +execute_process( + COMMAND "@git_EXECUTABLE@" @git_options@ + checkout "@git_tag@" @git_checkout_explicit--@ + WORKING_DIRECTORY "@work_dir@/@src_name@" + RESULT_VARIABLE error_code + ${capture_output} +) +_ep_command_check_result(error_code "Failed to checkout tag: '@git_tag@'") + +set(init_submodules @init_submodules@) +if(init_submodules) + execute_process( + COMMAND "@git_EXECUTABLE@" @git_options@ + submodule update @git_submodules_recurse@ --init @git_submodules@ + WORKING_DIRECTORY "@work_dir@/@src_name@" + RESULT_VARIABLE error_code + ${capture_output} + ) + _ep_command_check_result( + error_code "Failed to update submodules in: '@work_dir@/@src_name@'" + ) +endif() + +# Complete success, update the script-last-run stamp file: +# +execute_process( + COMMAND ${CMAKE_COMMAND} -E copy "@gitclone_infofile@" "@gitclone_stampfile@" + RESULT_VARIABLE error_code + ${capture_output} +) +_ep_command_check_result( + error_code "Failed to copy script-last-run stamp file: '@gitclone_stampfile@'" +) diff --git a/Modules/ExternalProject-gitupdate.cmake.in b/Modules/ExternalProject/gitupdate.cmake.in index 7033918..fc2a6ab 100644 --- a/Modules/ExternalProject-gitupdate.cmake.in +++ b/Modules/ExternalProject/gitupdate.cmake.in @@ -3,6 +3,10 @@ cmake_minimum_required(VERSION 3.5) +set(quiet "@quiet@") +set(script_dir "@CMAKE_CURRENT_FUNCTION_LIST_DIR@/ExternalProject") +include(${script_dir}/captured_process_setup.cmake) + function(get_hash_for_ref ref out_var err_var) execute_process( COMMAND "@git_EXECUTABLE@" rev-parse "${ref}" @@ -49,7 +53,7 @@ elseif(show_ref_output MATCHES "^[a-z0-9]+[ \\t]+refs/tags/") # FIXME: We should provide an option to always fetch for this case get_hash_for_ref("@git_tag@" tag_sha error_msg) if(tag_sha STREQUAL head_sha) - message(VERBOSE "Already at requested tag: ${tag_sha}") + _ep_message_quiet_capture(VERBOSE "Already at requested tag: ${tag_sha}") return() endif() @@ -65,7 +69,7 @@ else() get_hash_for_ref("@git_tag@" tag_sha error_msg) if(tag_sha STREQUAL head_sha) # Have the right commit checked out already - message(VERBOSE "Already at requested ref: ${tag_sha}") + _ep_message_quiet_capture(VERBOSE "Already at requested ref: ${tag_sha}") return() elseif(tag_sha STREQUAL "") @@ -76,7 +80,7 @@ else() set(fetch_required YES) set(checkout_name "@git_tag@") if(NOT error_msg STREQUAL "") - message(VERBOSE "${error_msg}") + _ep_message_quiet_capture(VERBOSE "${error_msg}") endif() else() @@ -86,18 +90,22 @@ else() set(fetch_required NO) set(checkout_name "@git_tag@") if(NOT error_msg STREQUAL "") - message(WARNING "${error_msg}") + _ep_message_quiet_capture(WARNING "${error_msg}") endif() endif() endif() if(fetch_required) - message(VERBOSE "Fetching latest from the remote @git_remote_name@") + _ep_message_quiet_capture(VERBOSE "Fetching latest from the remote @git_remote_name@") execute_process( COMMAND "@git_EXECUTABLE@" fetch --tags --force "@git_remote_name@" WORKING_DIRECTORY "@work_dir@" - COMMAND_ERROR_IS_FATAL ANY + RESULT_VARIABLE error_code + ${capture_output} + ) + _ep_command_check_result( + error_code "Failed to fetch from the remote @git_remote_name@'" ) endif() @@ -128,12 +136,15 @@ if(git_update_strategy MATCHES "^REBASE(_CHECKOUT)?$") else() execute_process( - COMMAND "@git_EXECUTABLE@" for-each-ref "--format='%(upstream:short)'" "${current_branch}" + COMMAND "@git_EXECUTABLE@" for-each-ref + "--format='%(upstream:short)'" "${current_branch}" WORKING_DIRECTORY "@work_dir@" + RESULT_VARIABLE error_code # There is no error if no upstream is set OUTPUT_VARIABLE upstream_branch OUTPUT_STRIP_TRAILING_WHITESPACE - COMMAND_ERROR_IS_FATAL ANY # There is no error if no upstream is set + ${capture_error_only} ) + _ep_command_check_result(error_code) if(NOT upstream_branch STREQUAL checkout_name) # Not safe to rebase when asked to checkout a different branch to the one # we are tracking. If we did rebase, we could end up with arbitrary @@ -145,7 +156,9 @@ if(git_update_strategy MATCHES "^REBASE(_CHECKOUT)?$") endif() elseif(NOT git_update_strategy STREQUAL "CHECKOUT") - message(FATAL_ERROR "Unsupported git update strategy: ${git_update_strategy}") + _ep_message_quiet_capture(FATAL_ERROR + "Unsupported git update strategy: ${git_update_strategy}" + ) endif() @@ -155,10 +168,9 @@ execute_process( WORKING_DIRECTORY "@work_dir@" RESULT_VARIABLE error_code OUTPUT_VARIABLE repo_status + ${capture_error_only} ) -if(error_code) - message(FATAL_ERROR "Failed to get the status") -endif() +_ep_command_check_result(error_code "Failed to get the status") string(LENGTH "${repo_status}" need_stash) # If not in clean state, stash changes in order to be able to perform a @@ -167,16 +179,20 @@ if(need_stash) execute_process( COMMAND "@git_EXECUTABLE@" stash save @git_stash_save_options@ WORKING_DIRECTORY "@work_dir@" - COMMAND_ERROR_IS_FATAL ANY + RESULT_VARIABLE error_code + ${capture_output} ) + _ep_command_check_result(error_code) endif() if(git_update_strategy STREQUAL "CHECKOUT") execute_process( COMMAND "@git_EXECUTABLE@" checkout "${checkout_name}" WORKING_DIRECTORY "@work_dir@" - COMMAND_ERROR_IS_FATAL ANY + RESULT_VARIABLE error_code + ${capture_output} ) + _ep_command_check_result(error_code) else() execute_process( COMMAND "@git_EXECUTABLE@" rebase "${checkout_name}" @@ -198,12 +214,14 @@ else() execute_process( COMMAND "@git_EXECUTABLE@" stash pop --index --quiet WORKING_DIRECTORY "@work_dir@" - ) + ) endif() - message(FATAL_ERROR "\nFailed to rebase in: '@work_dir@'." - "\nOutput from the attempted rebase follows:" - "\n${rebase_output}" - "\n\nYou will have to resolve the conflicts manually") + _ep_message_quiet_capture(FATAL_ERROR + "\nFailed to rebase in: '@work_dir@'." + "\nOutput from the attempted rebase follows:" + "\n${rebase_output}" + "\n\nYou will have to resolve the conflicts manually" + ) endif() # Fall back to checkout. We create an annotated tag so that the user @@ -215,21 +233,27 @@ else() set(tag_name _cmake_ExternalProject_moved_from_here_${tag_timestamp}Z) set(error_log_file ${CMAKE_CURRENT_LIST_DIR}/rebase_error_${tag_timestamp}Z.log) file(WRITE ${error_log_file} "${rebase_output}") - message(WARNING "Rebase failed, output has been saved to ${error_log_file}" - "\nFalling back to checkout, previous commit tagged as ${tag_name}") + _ep_message_quiet_capture(WARNING + "Rebase failed, output has been saved to ${error_log_file}" + "\nFalling back to checkout, previous commit tagged as ${tag_name}" + ) execute_process( COMMAND "@git_EXECUTABLE@" tag -a -m "ExternalProject attempting to move from here to ${checkout_name}" ${tag_name} WORKING_DIRECTORY "@work_dir@" - COMMAND_ERROR_IS_FATAL ANY + RESULT_VARIABLE error_code + ${capture_output} ) + _ep_command_check_result(error_code) execute_process( COMMAND "@git_EXECUTABLE@" checkout "${checkout_name}" WORKING_DIRECTORY "@work_dir@" - COMMAND_ERROR_IS_FATAL ANY + RESULT_VARIABLE error_code + ${capture_output} ) + _ep_command_check_result(error_code) endif() endif() @@ -239,30 +263,42 @@ if(need_stash) COMMAND "@git_EXECUTABLE@" stash pop --index --quiet WORKING_DIRECTORY "@work_dir@" RESULT_VARIABLE error_code - ) + ${capture_output} + ) + _ep_accumulate_captured_output() if(error_code) # Stash pop --index failed: Try again dropping the index execute_process( COMMAND "@git_EXECUTABLE@" reset --hard --quiet WORKING_DIRECTORY "@work_dir@" + ${capture_output} ) + _ep_accumulate_captured_output() execute_process( COMMAND "@git_EXECUTABLE@" stash pop --quiet WORKING_DIRECTORY "@work_dir@" RESULT_VARIABLE error_code + ${capture_output} ) + _ep_accumulate_captured_output() if(error_code) # Stash pop failed: Restore previous state. execute_process( COMMAND "@git_EXECUTABLE@" reset --hard --quiet ${head_sha} WORKING_DIRECTORY "@work_dir@" + ${capture_output} ) + _ep_accumulate_captured_output() execute_process( COMMAND "@git_EXECUTABLE@" stash pop --index --quiet WORKING_DIRECTORY "@work_dir@" + ${capture_output} + ) + _ep_accumulate_captured_output() + _ep_message_quiet_capture(FATAL_ERROR + "Failed to unstash changes in: '@work_dir@'.\n" + "You will have to resolve the conflicts manually" ) - message(FATAL_ERROR "\nFailed to unstash changes in: '@work_dir@'." - "\nYou will have to resolve the conflicts manually") endif() endif() endif() @@ -272,6 +308,8 @@ if(init_submodules) execute_process( COMMAND "@git_EXECUTABLE@" submodule update @git_submodules_recurse@ --init @git_submodules@ WORKING_DIRECTORY "@work_dir@" - COMMAND_ERROR_IS_FATAL ANY + RESULT_VARIABLE error_code + ${capture_output} ) + _ep_command_check_result(error_code) endif() diff --git a/Modules/ExternalProject/hgclone.cmake.in b/Modules/ExternalProject/hgclone.cmake.in new file mode 100644 index 0000000..5561955 --- /dev/null +++ b/Modules/ExternalProject/hgclone.cmake.in @@ -0,0 +1,58 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +cmake_minimum_required(VERSION 3.5) + +set(quiet "@quiet@") +set(script_dir "@CMAKE_CURRENT_FUNCTION_LIST_DIR@/ExternalProject") +include(${script_dir}/captured_process_setup.cmake) + +if(NOT "@hgclone_infofile@" IS_NEWER_THAN "@hgclone_stampfile@") + if(NOT quiet) + message(STATUS + "Avoiding repeated hg clone, stamp file is up to date: " + "'@hgclone_stampfile@'" + ) + endif() + return() +endif() + +execute_process( + COMMAND ${CMAKE_COMMAND} -E rm -rf "@source_dir@" + RESULT_VARIABLE error_code + ${capture_output} +) +_ep_command_check_result( + error_code "Failed to remove directory: '@source_dir@'" +) + +execute_process( + COMMAND "@hg_EXECUTABLE@" clone -U "@hg_repository@" "@src_name@" + WORKING_DIRECTORY "@work_dir@" + RESULT_VARIABLE error_code + ${capture_output} +) +_ep_command_check_result( + error_code "Failed to clone repository: '@hg_repository@'" +) + +execute_process( + COMMAND "@hg_EXECUTABLE@" update @hg_tag@ + WORKING_DIRECTORY "@work_dir@/@src_name@" + RESULT_VARIABLE error_code + ${capture_output} +) +_ep_command_check_result( + error_code "Failed to checkout tag: '@hg_tag@'" +) + +# Complete success, update the script-last-run stamp file: +# +execute_process( + COMMAND ${CMAKE_COMMAND} -E copy "@hgclone_infofile@" "@hgclone_stampfile@" + RESULT_VARIABLE error_code + ${capture_output} +) +_ep_command_check_result( + error_code "Failed to copy script-last-run stamp file: '@hgclone_stampfile@'" +) diff --git a/Modules/ExternalProject/hgupdate.cmake.in b/Modules/ExternalProject/hgupdate.cmake.in new file mode 100644 index 0000000..a82a819 --- /dev/null +++ b/Modules/ExternalProject/hgupdate.cmake.in @@ -0,0 +1,24 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +cmake_minimum_required(VERSION 3.19) + +set(quiet "@quiet@") +set(script_dir "@CMAKE_CURRENT_FUNCTION_LIST_DIR@/ExternalProject") +include(${script_dir}/captured_process_setup.cmake) + +execute_process( + COMMAND "@hg_EXECUTABLE@" pull + WORKING_DIRECTORY "@work_dir@" + RESULT_VARIABLE error_code + ${capture_output} +) +_ep_command_check_result(error_code) + +execute_process( + COMMAND "@hg_EXECUTABLE@" update @hg_tag@ + WORKING_DIRECTORY "@work_dir@" + RESULT_VARIABLE error_code + ${capture_output} +) +_ep_command_check_result(error_code) diff --git a/Modules/ExternalProject/mkdirs.cmake.in b/Modules/ExternalProject/mkdirs.cmake.in new file mode 100644 index 0000000..73e80fa --- /dev/null +++ b/Modules/ExternalProject/mkdirs.cmake.in @@ -0,0 +1,19 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +cmake_minimum_required(VERSION 3.5) + +file(MAKE_DIRECTORY + "@source_dir@" + "@binary_dir@" + "@install_dir@" + "@tmp_dir@" + "@stamp_dir@" + "@download_dir@" + "@log_dir@" +) + +set(configSubDirs @CMAKE_CONFIGURATION_TYPES@) +foreach(subDir IN LISTS configSubDirs) + file(MAKE_DIRECTORY "@stamp_dir@/${subDir}") +endforeach() diff --git a/Modules/ExternalProject/verify.cmake.in b/Modules/ExternalProject/verify.cmake.in new file mode 100644 index 0000000..cd34ba9 --- /dev/null +++ b/Modules/ExternalProject/verify.cmake.in @@ -0,0 +1,58 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +cmake_minimum_required(VERSION 3.5) + +set(quiet "@quiet@") +set(script_dir "@CMAKE_CURRENT_FUNCTION_LIST_DIR@/ExternalProject") +include(${script_dir}/captured_process_setup.cmake) + +if("@LOCAL@" STREQUAL "") + message(FATAL_ERROR "LOCAL can't be empty") +endif() + +if(NOT EXISTS "@LOCAL@") + message(FATAL_ERROR "File not found: @LOCAL@") +endif() + +function(do_verify) + if("@ALGO@" STREQUAL "") + _ep_message_quiet_capture(WARNING + "File will not be verified since no URL_HASH specified" + ) + set(accumulated_output "${accumulated_output}" PARENT_SCOPE) + return() + endif() + + if("@EXPECT_VALUE@" STREQUAL "") + _ep_message_quiet_capture(FATAL_ERROR "EXPECT_VALUE can't be empty") + endif() + + _ep_message_quiet_capture(STATUS +"verifying file... + file='@LOCAL@'" + ) + set(accumulated_output "${accumulated_output}" PARENT_SCOPE) + + file("@ALGO@" "@LOCAL@" actual_value) + + if(NOT "${actual_value}" STREQUAL "@EXPECT_VALUE@") + _ep_message_quiet_capture(FATAL_ERROR +"error: @ALGO@ hash of + @LOCAL@ +does not match expected value + expected: '@EXPECT_VALUE@' + actual: '${actual_value}' +") + endif() + + _ep_message_quiet_capture(STATUS "verifying file... done") + set(accumulated_output "${accumulated_output}" PARENT_SCOPE) +endfunction() + +do_verify() + +set(extract_script "@extract_script_filename@") +if(NOT "${extract_script}" STREQUAL "") + include("${extract_script}") +endif() diff --git a/Modules/FetchContent.cmake b/Modules/FetchContent.cmake index 8adef47..6a4cf38 100644 --- a/Modules/FetchContent.cmake +++ b/Modules/FetchContent.cmake @@ -849,8 +849,6 @@ function(__FetchContent_directPopulate contentName) SUBBUILD_DIR SOURCE_DIR BINARY_DIR - # We need special processing if DOWNLOAD_NO_EXTRACT is true - DOWNLOAD_NO_EXTRACT # Prevent the following from being passed through CONFIGURE_COMMAND BUILD_COMMAND @@ -894,123 +892,28 @@ function(__FetchContent_directPopulate contentName) set(${contentName}_SOURCE_DIR "${ARG_SOURCE_DIR}" PARENT_SCOPE) set(${contentName}_BINARY_DIR "${ARG_BINARY_DIR}" PARENT_SCOPE) - # The unparsed arguments may contain spaces, so build up ARG_EXTRA - # in such a way that it correctly substitutes into the generated - # CMakeLists.txt file with each argument quoted. - unset(ARG_EXTRA) - foreach(arg IN LISTS ARG_UNPARSED_ARGUMENTS) - set(ARG_EXTRA "${ARG_EXTRA} \"${arg}\"") - endforeach() - - if(ARG_DOWNLOAD_NO_EXTRACT) - set(ARG_EXTRA "${ARG_EXTRA} DOWNLOAD_NO_EXTRACT YES") - set(__FETCHCONTENT_COPY_FILE -" -ExternalProject_Get_Property(${contentName}-populate DOWNLOADED_FILE) -get_filename_component(dlFileName \"\${DOWNLOADED_FILE}\" NAME) - -ExternalProject_Add_Step(${contentName}-populate copyfile - COMMAND \"${CMAKE_COMMAND}\" -E copy_if_different - \"<DOWNLOADED_FILE>\" \"${ARG_SOURCE_DIR}\" - DEPENDEES patch - DEPENDERS configure - BYPRODUCTS \"${ARG_SOURCE_DIR}/\${dlFileName}\" - COMMENT \"Copying file to SOURCE_DIR\" -) -") + if(ARG_QUIET) + set(quiet TRUE) else() - unset(__FETCHCONTENT_COPY_FILE) - endif() - - # Hide output if requested, but save it to a variable in case there's an - # error so we can show the output upon failure. When not quiet, don't - # capture the output to a variable because the user may want to see the - # output as it happens (e.g. progress during long downloads). Combine both - # stdout and stderr in the one capture variable so the output stays in order. - if (ARG_QUIET) - set(outputOptions - OUTPUT_VARIABLE capturedOutput - ERROR_VARIABLE capturedOutput - ) - else() - set(capturedOutput) - set(outputOptions) + set(quiet FALSE) message(STATUS "Populating ${contentName}") endif() - if(CMAKE_GENERATOR) - set(subCMakeOpts "-G${CMAKE_GENERATOR}") - if(CMAKE_GENERATOR_PLATFORM) - list(APPEND subCMakeOpts "-A${CMAKE_GENERATOR_PLATFORM}") - endif() - if(CMAKE_GENERATOR_TOOLSET) - list(APPEND subCMakeOpts "-T${CMAKE_GENERATOR_TOOLSET}") - endif() - - if(CMAKE_MAKE_PROGRAM) - list(APPEND subCMakeOpts "-DCMAKE_MAKE_PROGRAM:FILEPATH=${CMAKE_MAKE_PROGRAM}") - endif() - - else() - # Likely we've been invoked via CMake's script mode where no - # generator is set (and hence CMAKE_MAKE_PROGRAM could not be - # trusted even if provided). We will have to rely on being - # able to find the default generator and build tool. - unset(subCMakeOpts) - endif() - - if(DEFINED CMAKE_EP_GIT_REMOTE_UPDATE_STRATEGY) - list(APPEND subCMakeOpts - "-DCMAKE_EP_GIT_REMOTE_UPDATE_STRATEGY=${CMAKE_EP_GIT_REMOTE_UPDATE_STRATEGY}") - endif() - - # Avoid using if(... IN_LIST ...) so we don't have to alter policy settings - set(__FETCHCONTENT_CACHED_INFO "") - list(FIND ARG_UNPARSED_ARGUMENTS GIT_REPOSITORY indexResult) - if(indexResult GREATER_EQUAL 0) - find_package(Git QUIET) - set(__FETCHCONTENT_CACHED_INFO -"# Pass through things we've already detected in the main project to avoid -# paying the cost of redetecting them again in ExternalProject_Add() -set(GIT_EXECUTABLE [==[${GIT_EXECUTABLE}]==]) -set(GIT_VERSION_STRING [==[${GIT_VERSION_STRING}]==]) -set_property(GLOBAL PROPERTY _CMAKE_FindGit_GIT_EXECUTABLE_VERSION - [==[${GIT_EXECUTABLE};${GIT_VERSION_STRING}]==] -) -") - endif() - - # Create and build a separate CMake project to carry out the population. - # If we've already previously done these steps, they will not cause - # anything to be updated, so extra rebuilds of the project won't occur. - # Make sure to pass through CMAKE_MAKE_PROGRAM in case the main project - # has this set to something not findable on the PATH. - configure_file("${CMAKE_CURRENT_FUNCTION_LIST_DIR}/FetchContent/CMakeLists.cmake.in" - "${ARG_SUBBUILD_DIR}/CMakeLists.txt") - execute_process( - COMMAND ${CMAKE_COMMAND} ${subCMakeOpts} . - RESULT_VARIABLE result - ${outputOptions} - WORKING_DIRECTORY "${ARG_SUBBUILD_DIR}" - ) - if(result) - if(capturedOutput) - message("${capturedOutput}") - endif() - message(FATAL_ERROR "CMake step for ${contentName} failed: ${result}") - endif() - execute_process( - COMMAND ${CMAKE_COMMAND} --build . - RESULT_VARIABLE result - ${outputOptions} - WORKING_DIRECTORY "${ARG_SUBBUILD_DIR}" + include(ExternalProject) + set(argsQuoted) + foreach(__item IN LISTS ARG_UNPARSED_ARGUMENTS) + string(APPEND argsQuoted " [==[${__item}]==]") + endforeach() + cmake_language(EVAL CODE " + _ep_do_preconfigure_steps_now(${contentName} + ${argsQuoted} + QUIET ${quiet} + SOURCE_DIR [==[${ARG_SOURCE_DIR}]==] + BINARY_DIR [==[${ARG_BINARY_DIR}]==] + USES_TERMINAL_DOWNLOAD YES + USES_TERMINAL_UPDATE YES + )" ) - if(result) - if(capturedOutput) - message("${capturedOutput}") - endif() - message(FATAL_ERROR "Build step for ${contentName} failed: ${result}") - endif() endfunction() diff --git a/Modules/FetchContent/CMakeLists.cmake.in b/Modules/FetchContent/CMakeLists.cmake.in deleted file mode 100644 index 5ebb12f..0000000 --- a/Modules/FetchContent/CMakeLists.cmake.in +++ /dev/null @@ -1,27 +0,0 @@ -# Distributed under the OSI-approved BSD 3-Clause License. See accompanying -# file Copyright.txt or https://cmake.org/licensing for details. - -cmake_minimum_required(VERSION ${CMAKE_VERSION}) - -# We name the project and the target for the ExternalProject_Add() call -# to something that will highlight to the user what we are working on if -# something goes wrong and an error message is produced. - -project(${contentName}-populate NONE) - -@__FETCHCONTENT_CACHED_INFO@ - -include(ExternalProject) -ExternalProject_Add(${contentName}-populate - ${ARG_EXTRA} - SOURCE_DIR "${ARG_SOURCE_DIR}" - BINARY_DIR "${ARG_BINARY_DIR}" - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - TEST_COMMAND "" - USES_TERMINAL_DOWNLOAD YES - USES_TERMINAL_UPDATE YES -) - -@__FETCHCONTENT_COPY_FILE@ diff --git a/Modules/RepositoryInfo.txt.in b/Modules/RepositoryInfo.txt.in deleted file mode 100644 index df8e322..0000000 --- a/Modules/RepositoryInfo.txt.in +++ /dev/null @@ -1,3 +0,0 @@ -repository='@repository@' -module='@module@' -tag='@tag@' |