From 23aab9ecce264b7ea198fc9d36f382a4bb4fbe28 Mon Sep 17 00:00:00 2001 From: Craig Scott Date: Sun, 24 Jan 2021 23:33:51 +1100 Subject: ExternalProject: Avoid scanning docs for keywords, use include_guard() The previous implementation was scanning the documentation in the file at runtime to determine the set of supported keywords for each public function. This was fragile, made it difficult to restructure the documentation and was sometimes observable in runtime performance measurements. Change to a more conventional approach where supported keywords are explicitly listed in the code. The internal _ExternalProject_SELF variable is no longer needed. CMake now provides CMAKE_CURRENT_FUNCTION_LIST_DIR which can be used for the same purpose and avoids having to set a variable when the module is read. This also removes the requirement that the module must be included by the current or a parent scope. It is now enough that the module has been included once somewhere before calling any of its functions. The above changes combined mean that the module can now use include_guard() and avoid having to re-parse the very long file every time. --- Modules/ExternalProject.cmake | 232 ++++++++++++++++----- .../ExternalProject/IncludeScope-Add-result.txt | 1 - .../ExternalProject/IncludeScope-Add-stderr.txt | 7 - .../ExternalProject/IncludeScope-Add.cmake | 12 -- .../IncludeScope-Add_Step-result.txt | 1 - .../IncludeScope-Add_Step-stderr.txt | 7 - .../ExternalProject/IncludeScope-Add_Step.cmake | 13 -- Tests/RunCMake/ExternalProject/RunCMakeTest.cmake | 2 - 8 files changed, 178 insertions(+), 97 deletions(-) delete mode 100644 Tests/RunCMake/ExternalProject/IncludeScope-Add-result.txt delete mode 100644 Tests/RunCMake/ExternalProject/IncludeScope-Add-stderr.txt delete mode 100644 Tests/RunCMake/ExternalProject/IncludeScope-Add.cmake delete mode 100644 Tests/RunCMake/ExternalProject/IncludeScope-Add_Step-result.txt delete mode 100644 Tests/RunCMake/ExternalProject/IncludeScope-Add_Step-stderr.txt delete mode 100644 Tests/RunCMake/ExternalProject/IncludeScope-Add_Step.cmake diff --git a/Modules/ExternalProject.cmake b/Modules/ExternalProject.cmake index 903dd57..56525080 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 --------------- @@ -1111,60 +1113,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) +function(_ep_parse_arguments f keywords 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 + # Because some keywords can be repeated, we can't use cmake_parse_arguments(). + # Instead, we loop through ARGN 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() - set(key) foreach(arg IN LISTS args) @@ -1173,7 +1152,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() @@ -1198,9 +1177,6 @@ function(_ep_parse_arguments f name ns args) endif() else() set(key "${arg}") - if(key MATCHES GIT) - get_property(have_key TARGET ${name} PROPERTY ${ns}${key} SET) - endif() endif() endforeach() endfunction() @@ -1432,7 +1408,7 @@ function(_ep_write_gitupdate_script script_filename git_EXECUTABLE git_tag git_r endif() configure_file( - "${_ExternalProject_SELF_DIR}/ExternalProject-gitupdate.cmake.in" + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject-gitupdate.cmake.in" "${script_filename}" @ONLY ) @@ -1461,6 +1437,7 @@ function(_ep_write_downloadfile_script script_filename REMOTE LOCAL timeout inac 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,14 @@ 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" + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject-download.cmake.in" "${script_filename}" @ONLY ) endfunction() function(_ep_write_verifyfile_script script_filename LOCAL hash) + _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) @@ -1561,7 +1539,7 @@ function(_ep_write_verifyfile_script script_filename LOCAL hash) # * EXPECT_VALUE # * LOCAL configure_file( - "${_ExternalProject_SELF_DIR}/ExternalProject-verify.cmake.in" + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject-verify.cmake.in" "${script_filename}" @ONLY ) @@ -2250,7 +2228,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 + set(keywords + COMMAND + COMMENT + DEPENDEES + DEPENDERS + DEPENDS + INDEPENDENT + BYPRODUCTS + ALWAYS + EXCLUDE_FROM_MAIN + WORKING_DIRECTORY + LOG + USES_TERMINAL + ) + _ep_parse_arguments(ExternalProject_Add_Step "${keywords}" ${name} _EP_${step}_ "${ARGN}") get_property(independent TARGET ${name} PROPERTY _EP_${step}_INDEPENDENT) @@ -2747,7 +2739,10 @@ function(_ep_add_download_command name) elseif(url) get_filename_component(work_dir "${source_dir}" PATH) get_property(hash TARGET ${name} PROPERTY _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.") @@ -3505,7 +3500,136 @@ function(ExternalProject_Add name) set_property(TARGET ${name} PROPERTY _EP_CMP0114 "${cmp0114}") - _ep_parse_arguments(ExternalProject_Add ${name} _EP_ "${ARGN}") + set(keywords + # + # 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 + ) + _ep_parse_arguments(ExternalProject_Add "${keywords}" ${name} _EP_ "${ARGN}") _ep_set_directories(${name}) _ep_get_step_stampfile(${name} "done" done_stamp_file) _ep_get_step_stampfile(${name} "install" install_stamp_file) diff --git a/Tests/RunCMake/ExternalProject/IncludeScope-Add-result.txt b/Tests/RunCMake/ExternalProject/IncludeScope-Add-result.txt deleted file mode 100644 index d00491f..0000000 --- a/Tests/RunCMake/ExternalProject/IncludeScope-Add-result.txt +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/Tests/RunCMake/ExternalProject/IncludeScope-Add-stderr.txt b/Tests/RunCMake/ExternalProject/IncludeScope-Add-stderr.txt deleted file mode 100644 index ff3e5c1..0000000 --- a/Tests/RunCMake/ExternalProject/IncludeScope-Add-stderr.txt +++ /dev/null @@ -1,7 +0,0 @@ -^CMake Error at .*/Modules/ExternalProject.cmake:[0-9]+ \(message\): - error: ExternalProject module must be explicitly included before using - ExternalProject_Add function -Call Stack \(most recent call first\): - .*/Modules/ExternalProject.cmake:[0-9]+ \(_ep_parse_arguments\) - IncludeScope-Add.cmake:[0-9]+ \(ExternalProject_Add\) - CMakeLists.txt:[0-9]+ \(include\)$ diff --git a/Tests/RunCMake/ExternalProject/IncludeScope-Add.cmake b/Tests/RunCMake/ExternalProject/IncludeScope-Add.cmake deleted file mode 100644 index 1061ffd..0000000 --- a/Tests/RunCMake/ExternalProject/IncludeScope-Add.cmake +++ /dev/null @@ -1,12 +0,0 @@ -function(IncludeScope_IncludeOnly) - include(ExternalProject) -endfunction() - -IncludeScope_IncludeOnly() - -ExternalProject_Add(MyProj - SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR} - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" -) diff --git a/Tests/RunCMake/ExternalProject/IncludeScope-Add_Step-result.txt b/Tests/RunCMake/ExternalProject/IncludeScope-Add_Step-result.txt deleted file mode 100644 index d00491f..0000000 --- a/Tests/RunCMake/ExternalProject/IncludeScope-Add_Step-result.txt +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/Tests/RunCMake/ExternalProject/IncludeScope-Add_Step-stderr.txt b/Tests/RunCMake/ExternalProject/IncludeScope-Add_Step-stderr.txt deleted file mode 100644 index cbad4be..0000000 --- a/Tests/RunCMake/ExternalProject/IncludeScope-Add_Step-stderr.txt +++ /dev/null @@ -1,7 +0,0 @@ -^CMake Error at .*/Modules/ExternalProject.cmake:[0-9]+ \(message\): - error: ExternalProject module must be explicitly included before using - ExternalProject_Add_Step function -Call Stack \(most recent call first\): - .*/Modules/ExternalProject.cmake:[0-9]+ \(_ep_parse_arguments\) - IncludeScope-Add_Step.cmake:[0-9]+ \(ExternalProject_Add_Step\) - CMakeLists.txt:[0-9]+ \(include\)$ diff --git a/Tests/RunCMake/ExternalProject/IncludeScope-Add_Step.cmake b/Tests/RunCMake/ExternalProject/IncludeScope-Add_Step.cmake deleted file mode 100644 index 2a820f8..0000000 --- a/Tests/RunCMake/ExternalProject/IncludeScope-Add_Step.cmake +++ /dev/null @@ -1,13 +0,0 @@ -function(IncludeScope_DefineProj) - include(ExternalProject) - ExternalProject_Add(MyProj - SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR} - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - ) -endfunction() - -IncludeScope_DefineProj() - -ExternalProject_Add_Step(MyProj extraStep COMMENT "Foo") diff --git a/Tests/RunCMake/ExternalProject/RunCMakeTest.cmake b/Tests/RunCMake/ExternalProject/RunCMakeTest.cmake index 976655a..3205dd5 100644 --- a/Tests/RunCMake/ExternalProject/RunCMakeTest.cmake +++ b/Tests/RunCMake/ExternalProject/RunCMakeTest.cmake @@ -8,8 +8,6 @@ unset(ENV{https_proxy}) run_cmake(BadIndependentStep1) run_cmake(BadIndependentStep2) -run_cmake(IncludeScope-Add) -run_cmake(IncludeScope-Add_Step) run_cmake(NoOptions) run_cmake(SourceEmpty) run_cmake(SourceMissing) -- cgit v0.12 From 4f3d1abbb4dca3d1e6b019471fa5d8be296492e3 Mon Sep 17 00:00:00 2001 From: Craig Scott Date: Fri, 5 Feb 2021 07:32:58 +1100 Subject: ExternalProject: Refactor pre-configure steps to support no-target uses The mkdir, download, update and patch steps are used by FetchContent during the configure phase of the main build. Because these steps need a target, this has so far required a sub-build to be set up. The changes here factor out the preparation of the scripts from the creation of the targets, allowing future work to leverage these steps without a sub-build (see #21703). As part of the refactoring, some rationalisation of the stamp files, repository info files and script names was done to make things more consistent between download methods and step implementations. Every download method now records its own specific repository info in a file and that file is a dependency of the download step. The source directory is also written to that file, so if the SOURCE_DIR changes, the download will be retriggered (the existing implementation fails in this scenario). Each download method now also has just one driver script that implements the whole step (it may pull in other scripts to do its task though). The patch step gained support for USES_TERMINAL as a result of generalising the implementation for custom commands. Fixes: #21748 --- Help/release/dev/fetchcontent-performance.rst | 7 + Modules/ExternalProject-download.cmake.in | 173 --- Modules/ExternalProject-gitupdate.cmake.in | 277 ---- Modules/ExternalProject-verify.cmake.in | 37 - Modules/ExternalProject.cmake | 1606 ++++++++++++-------- Modules/ExternalProject/RepositoryInfo.txt.in | 1 + Modules/ExternalProject/cfgcmd.txt.in | 1 + Modules/ExternalProject/copydir.cmake.in | 10 + Modules/ExternalProject/download.cmake.in | 187 +++ Modules/ExternalProject/extractfile.cmake.in | 63 + Modules/ExternalProject/gitclone.cmake.in | 67 + Modules/ExternalProject/gitupdate.cmake.in | 277 ++++ Modules/ExternalProject/hgclone.cmake.in | 45 + Modules/ExternalProject/hgupdate.cmake.in | 16 + Modules/ExternalProject/mkdirs.cmake.in | 19 + Modules/ExternalProject/verify.cmake.in | 48 + Modules/RepositoryInfo.txt.in | 3 - .../NO_DEPENDS-CMP0114-NEW-stderr.txt | 2 +- .../NO_DEPENDS-CMP0114-WARN-stderr.txt | 4 +- .../RunCMake/ExternalProject/NoOptions-stderr.txt | 2 +- .../ExternalProject/SourceEmpty-stderr.txt | 2 +- .../ExternalProject/SourceMissing-stderr.txt | 2 +- .../ExternalProject/UsesTerminal-check.cmake | 2 +- 23 files changed, 1683 insertions(+), 1168 deletions(-) create mode 100644 Help/release/dev/fetchcontent-performance.rst delete mode 100644 Modules/ExternalProject-download.cmake.in delete mode 100644 Modules/ExternalProject-gitupdate.cmake.in delete mode 100644 Modules/ExternalProject-verify.cmake.in create mode 100644 Modules/ExternalProject/RepositoryInfo.txt.in create mode 100644 Modules/ExternalProject/cfgcmd.txt.in create mode 100644 Modules/ExternalProject/copydir.cmake.in create mode 100644 Modules/ExternalProject/download.cmake.in create mode 100644 Modules/ExternalProject/extractfile.cmake.in create mode 100644 Modules/ExternalProject/gitclone.cmake.in create mode 100644 Modules/ExternalProject/gitupdate.cmake.in create mode 100644 Modules/ExternalProject/hgclone.cmake.in create mode 100644 Modules/ExternalProject/hgupdate.cmake.in create mode 100644 Modules/ExternalProject/mkdirs.cmake.in create mode 100644 Modules/ExternalProject/verify.cmake.in delete mode 100644 Modules/RepositoryInfo.txt.in diff --git a/Help/release/dev/fetchcontent-performance.rst b/Help/release/dev/fetchcontent-performance.rst new file mode 100644 index 0000000..361c2b4 --- /dev/null +++ b/Help/release/dev/fetchcontent-performance.rst @@ -0,0 +1,7 @@ +fetchcontent-performance +------------------------ + +* The implementation of the :module:`ExternalProject` module was + significantly refactored. The patch step gained support for + using the terminal with a new ``USES_TERMINAL_PATCH`` keyword + as a by-product of that work. 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-gitupdate.cmake.in b/Modules/ExternalProject-gitupdate.cmake.in deleted file mode 100644 index 7033918..0000000 --- a/Modules/ExternalProject-gitupdate.cmake.in +++ /dev/null @@ -1,277 +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(get_hash_for_ref ref out_var err_var) - execute_process( - COMMAND "@git_EXECUTABLE@" rev-parse "${ref}" - WORKING_DIRECTORY "@work_dir@" - RESULT_VARIABLE error_code - OUTPUT_VARIABLE ref_hash - ERROR_VARIABLE error_msg - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - if(error_code) - set(${out_var} "" PARENT_SCOPE) - else() - set(${out_var} "${ref_hash}" PARENT_SCOPE) - endif() - set(${err_var} "${error_msg}" PARENT_SCOPE) -endfunction() - -get_hash_for_ref(HEAD head_sha error_msg) -if(head_sha STREQUAL "") - message(FATAL_ERROR "Failed to get the hash for HEAD:\n${error_msg}") -endif() - - -execute_process( - COMMAND "@git_EXECUTABLE@" show-ref "@git_tag@" - WORKING_DIRECTORY "@work_dir@" - OUTPUT_VARIABLE show_ref_output -) -if(show_ref_output MATCHES "^[a-z0-9]+[ \\t]+refs/remotes/") - # Given a full remote/branch-name and we know about it already. Since - # branches can move around, we always have to fetch. - set(fetch_required YES) - set(checkout_name "@git_tag@") - -elseif(show_ref_output MATCHES "^[a-z0-9]+[ \\t]+refs/tags/") - # Given a tag name that we already know about. We don't know if the tag we - # have matches the remote though (tags can move), so we should fetch. - set(fetch_required YES) - set(checkout_name "@git_tag@") - - # Special case to preserve backward compatibility: if we are already at the - # same commit as the tag we hold locally, don't do a fetch and assume the tag - # hasn't moved on the remote. - # 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}") - return() - endif() - -elseif(show_ref_output MATCHES "^[a-z0-9]+[ \\t]+refs/heads/") - # Given a branch name without any remote and we already have a branch by that - # name. We might already have that branch checked out or it might be a - # different branch. It isn't safe to use a bare branch name without the - # remote, so do a fetch and replace the ref with one that includes the remote. - set(fetch_required YES) - set(checkout_name "@git_remote_name@/@git_tag@") - -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}") - return() - - elseif(tag_sha STREQUAL "") - # We don't know about this ref yet, so we have no choice but to fetch. - # We deliberately swallow any error message at the default log level - # because it can be confusing for users to see a failed git command. - # That failure is being handled here, so it isn't an error. - set(fetch_required YES) - set(checkout_name "@git_tag@") - if(NOT error_msg STREQUAL "") - message(VERBOSE "${error_msg}") - endif() - - else() - # We have the commit, so we know we were asked to find a commit hash - # (otherwise it would have been handled further above), but we don't - # have that commit checked out yet - set(fetch_required NO) - set(checkout_name "@git_tag@") - if(NOT error_msg STREQUAL "") - message(WARNING "${error_msg}") - endif() - - endif() -endif() - -if(fetch_required) - message(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 - ) -endif() - -set(git_update_strategy "@git_update_strategy@") -if(git_update_strategy STREQUAL "") - # Backward compatibility requires REBASE as the default behavior - set(git_update_strategy REBASE) -endif() - -if(git_update_strategy MATCHES "^REBASE(_CHECKOUT)?$") - # Asked to potentially try to rebase first, maybe with fallback to checkout. - # We can't if we aren't already on a branch and we shouldn't if that local - # branch isn't tracking the one we want to checkout. - execute_process( - COMMAND "@git_EXECUTABLE@" symbolic-ref -q HEAD - WORKING_DIRECTORY "@work_dir@" - OUTPUT_VARIABLE current_branch - OUTPUT_STRIP_TRAILING_WHITESPACE - # Don't test for an error. If this isn't a branch, we get a non-zero error - # code but empty output. - ) - - if(current_branch STREQUAL "") - # Not on a branch, checkout is the only sensible option since any rebase - # would always fail (and backward compatibility requires us to checkout in - # this situation) - set(git_update_strategy CHECKOUT) - - else() - execute_process( - COMMAND "@git_EXECUTABLE@" for-each-ref "--format='%(upstream:short)'" "${current_branch}" - WORKING_DIRECTORY "@work_dir@" - OUTPUT_VARIABLE upstream_branch - OUTPUT_STRIP_TRAILING_WHITESPACE - COMMAND_ERROR_IS_FATAL ANY # There is no error if no upstream is set - ) - 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 - # commits added to the ref we were asked to checkout if the current local - # branch happens to be able to rebase onto the target branch. There would - # be no error message and the user wouldn't know this was occurring. - set(git_update_strategy CHECKOUT) - endif() - - endif() -elseif(NOT git_update_strategy STREQUAL "CHECKOUT") - message(FATAL_ERROR "Unsupported git update strategy: ${git_update_strategy}") -endif() - - -# Check if stash is needed -execute_process( - COMMAND "@git_EXECUTABLE@" status --porcelain - WORKING_DIRECTORY "@work_dir@" - RESULT_VARIABLE error_code - OUTPUT_VARIABLE repo_status -) -if(error_code) - message(FATAL_ERROR "Failed to get the status") -endif() -string(LENGTH "${repo_status}" need_stash) - -# If not in clean state, stash changes in order to be able to perform a -# rebase or checkout without losing those changes permanently -if(need_stash) - execute_process( - COMMAND "@git_EXECUTABLE@" stash save @git_stash_save_options@ - WORKING_DIRECTORY "@work_dir@" - COMMAND_ERROR_IS_FATAL ANY - ) -endif() - -if(git_update_strategy STREQUAL "CHECKOUT") - execute_process( - COMMAND "@git_EXECUTABLE@" checkout "${checkout_name}" - WORKING_DIRECTORY "@work_dir@" - COMMAND_ERROR_IS_FATAL ANY - ) -else() - execute_process( - COMMAND "@git_EXECUTABLE@" rebase "${checkout_name}" - WORKING_DIRECTORY "@work_dir@" - RESULT_VARIABLE error_code - OUTPUT_VARIABLE rebase_output - ERROR_VARIABLE rebase_output - ) - if(error_code) - # Rebase failed, undo the rebase attempt before continuing - execute_process( - COMMAND "@git_EXECUTABLE@" rebase --abort - WORKING_DIRECTORY "@work_dir@" - ) - - if(NOT git_update_strategy STREQUAL "REBASE_CHECKOUT") - # Not allowed to do a checkout as a fallback, so cannot proceed - if(need_stash) - 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") - endif() - - # Fall back to checkout. We create an annotated tag so that the user - # can manually inspect the situation and revert if required. - # We can't log the failed rebase output because MSVC sees it and - # intervenes, causing the build to fail even though it completes. - # Write it to a file instead. - string(TIMESTAMP tag_timestamp "%Y%m%dT%H%M%S" UTC) - 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}") - 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 - ) - - execute_process( - COMMAND "@git_EXECUTABLE@" checkout "${checkout_name}" - WORKING_DIRECTORY "@work_dir@" - COMMAND_ERROR_IS_FATAL ANY - ) - endif() -endif() - -if(need_stash) - # Put back the stashed changes - execute_process( - COMMAND "@git_EXECUTABLE@" stash pop --index --quiet - WORKING_DIRECTORY "@work_dir@" - RESULT_VARIABLE error_code - ) - if(error_code) - # Stash pop --index failed: Try again dropping the index - execute_process( - COMMAND "@git_EXECUTABLE@" reset --hard --quiet - WORKING_DIRECTORY "@work_dir@" - ) - execute_process( - COMMAND "@git_EXECUTABLE@" stash pop --quiet - WORKING_DIRECTORY "@work_dir@" - RESULT_VARIABLE error_code - ) - if(error_code) - # Stash pop failed: Restore previous state. - execute_process( - COMMAND "@git_EXECUTABLE@" reset --hard --quiet ${head_sha} - WORKING_DIRECTORY "@work_dir@" - ) - execute_process( - COMMAND "@git_EXECUTABLE@" stash pop --index --quiet - WORKING_DIRECTORY "@work_dir@" - ) - message(FATAL_ERROR "\nFailed to unstash changes in: '@work_dir@'." - "\nYou will have to resolve the conflicts manually") - endif() - endif() -endif() - -set(init_submodules "@init_submodules@") -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 - ) -endif() 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 56525080..5f00c87 100644 --- a/Modules/ExternalProject.cmake +++ b/Modules/ExternalProject.cmake @@ -407,7 +407,7 @@ External Project Definition ``CVS_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). @@ -442,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 ...`` 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 @@ -717,6 +718,11 @@ External Project Definition ``USES_TERMINAL_UPDATE `` Give the update step access to the terminal. + ``USES_TERMINAL_PATCH `` + .. versionadded:: 3.20 + + Give the patch step access to the terminal. + ``USES_TERMINAL_CONFIGURE `` Give the configure step access to the terminal. @@ -1134,16 +1140,17 @@ macro(_ep_get_hash_regex out_var) set(${out_var} "^(${${out_var}})=([0-9A-Fa-f]+)$") endmacro() -function(_ep_parse_arguments f keywords 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. - # +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 ARGN and consider the namespace starting with an - # upper-case letter followed by at least two more upper-case letters, + # 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. + foreach(key IN LISTS keywords) + unset(${ns}${key}) + endforeach() + set(key) foreach(arg IN LISTS args) @@ -1160,25 +1167,37 @@ function(_ep_parse_arguments f keywords 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}") 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}") + + # 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() + endfunction() @@ -1221,7 +1240,26 @@ define_property(DIRECTORY PROPERTY "EP_UPDATE_DISCONNECTED" INHERITED "ExternalProject module." ) -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) + +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 --` to avoid ambiguity with a local path. set(git_checkout_explicit-- "--") @@ -1267,134 +1305,50 @@ 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) + 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) + if("${git_tag}" STREQUAL "") message(FATAL_ERROR "Tag for git checkout should not be empty.") endif() @@ -1408,13 +1362,54 @@ function(_ep_write_gitupdate_script script_filename git_EXECUTABLE git_tag git_r endif() configure_file( - "${CMAKE_CURRENT_FUNCTION_LIST_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) + + 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) + if(timeout) set(TIMEOUT_ARGS TIMEOUT ${timeout}) set(TIMEOUT_MSG "${timeout} seconds") @@ -1518,13 +1513,18 @@ function(_ep_write_downloadfile_script script_filename REMOTE LOCAL timeout inac # * USERPWD_ARGS # * HTTP_HEADERS_ARGS configure_file( - "${CMAKE_CURRENT_FUNCTION_LIST_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) + _ep_get_hash_regex(_ep_hash_regex) if("${hash}" MATCHES "${_ep_hash_regex}") set(ALGO "${CMAKE_MATCH_1}") @@ -1538,15 +1538,21 @@ function(_ep_write_verifyfile_script script_filename LOCAL hash) # * ALGO # * EXPECT_VALUE # * LOCAL + # * extract_script_filename configure_file( - "${CMAKE_CURRENT_FUNCTION_LIST_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) + set(args "") if(filename MATCHES "(\\.|=)(7z|tar\\.bz2|tar\\.gz|tar\\.xz|tbz2|tgz|txz|zip)$") @@ -1558,77 +1564,33 @@ 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(). # -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_. It will create the various directories +# before returning and it will populate variables of the form +# _EP__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) @@ -1639,6 +1601,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}") @@ -1646,6 +1609,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}") @@ -1653,10 +1617,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() @@ -1667,64 +1631,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} @@ -1741,6 +1718,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 "") @@ -1923,17 +1911,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}-$) + 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) @@ -1955,8 +1950,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() @@ -1990,15 +1985,22 @@ endif() endif() endforeach() string(APPEND code "set(command \"${cmd}\")${code_execute_process}") - file(GENERATE OUTPUT "${stamp_dir}/${name}-${step}-$-impl.cmake" CONTENT "${code}") - set(command ${CMAKE_COMMAND} "-Dmake=\${make}" "-Dconfig=\${config}" -P ${stamp_dir}/${name}-${step}-$-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}-$.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") @@ -2063,8 +2065,13 @@ else() 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() @@ -2242,8 +2249,7 @@ function(ExternalProject_Add_Step name step) LOG USES_TERMINAL ) - _ep_parse_arguments(ExternalProject_Add_Step "${keywords}" - ${name} _EP_${step}_ "${ARGN}") + _ep_parse_arguments("${keywords}" ${name} _EP_${step}_ "${ARGN}") get_property(independent TARGET ${name} PROPERTY _EP_${step}_INDEPENDENT) if(independent STREQUAL "") @@ -2354,7 +2360,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 "") @@ -2486,27 +2493,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 "") @@ -2517,114 +2503,258 @@ 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 + 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) + + 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(APPEND script_content " +execute_process( + COMMAND ${this_command} + COMMAND_ERROR_IS_FATAL LAST + WORKING_DIRECTORY [==[${work_dir}]==] +) +") + 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(APPEND script_content " +execute_process( + COMMAND ${this_command} + COMMAND_ERROR_IS_FATAL LAST + WORKING_DIRECTORY [==[${work_dir}]==] +) +") + endif() + + if(script_content STREQUAL "") + set(${have_commands_var} FALSE PARENT_SCOPE) + else() + set(${have_commands_var} TRUE PARENT_SCOPE) + string(PREPEND script_content "cmake_minimum_required(VERSION 3.19)\n") 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(). +# +# The function expects keyword arguments to have already been parsed into +# variables of the form _EP_. 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(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) + 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}" + 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}" + 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}" + 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 @@ -2634,112 +2764,132 @@ 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}" + ) 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}" + ) 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) - _ep_get_hash_regex(_ep_hash_regex) + + 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) @@ -2747,22 +2897,27 @@ function(_ep_add_download_command name) "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") @@ -2777,12 +2932,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") @@ -2796,55 +2960,67 @@ 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}" + ) + 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}" + ) + 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}" + ) 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" @@ -2857,105 +3033,143 @@ 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(). +# +# The function expects keyword arguments to have already been parsed into +# variables of the form _EP_. +# +# 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(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) + 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}" + 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}" + 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}" + 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 @@ -2964,27 +3178,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() @@ -2996,23 +3210,27 @@ 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}" + ) + 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 @@ -3020,87 +3238,112 @@ 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}" + ) + 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(). +# +# The function expects keyword arguments to have already been parsed into +# variables of the form _EP_. +# +# 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}") - 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) 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}" + 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) @@ -3210,7 +3453,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 @@ -3242,10 +3489,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}) @@ -3456,51 +3704,8 @@ function(_ep_add_test_command name) endif() endfunction() - -function(ExternalProject_Add name) - cmake_policy(GET CMP0097 _EP_CMP0097 - PARENT_SCOPE # undocumented, do not use outside of CMake - ) - cmake_policy(GET CMP0114 cmp0114 - PARENT_SCOPE # undocumented, do not use outside of CMake - ) - if(CMAKE_XCODE_BUILD_SYSTEM VERSION_GREATER_EQUAL 12 AND NOT cmp0114 STREQUAL "NEW") - message(AUTHOR_WARNING - "Policy CMP0114 is not set to NEW. " - "In order to support the Xcode \"new build system\", " - "this project must be updated to set policy CMP0114 to NEW." - "\n" - "Since CMake is generating for the Xcode \"new build system\", " - "ExternalProject_Add will use policy CMP0114's NEW behavior anyway, " - "but the generated build system may not match what the project intends." - ) - set(cmp0114 "NEW") - endif() - - _ep_get_configuration_subdir_suffix(cfgdir) - - # Add a custom target for the external project. - set(cmf_dir ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles) - _ep_get_complete_stampfile(${name} complete_stamp_file) - - cmake_policy(PUSH) - if(cmp0114 STREQUAL "NEW") - # To implement CMP0114 NEW behavior with Makefile generators, - # we need CMP0113 NEW behavior. - cmake_policy(SET CMP0113 NEW) - endif() - # The "ALL" option to add_custom_target just tells it to not set the - # EXCLUDE_FROM_ALL target property. Later, if the EXCLUDE_FROM_ALL - # argument was passed, we explicitly set it for the target. - add_custom_target(${name} ALL DEPENDS ${complete_stamp_file}) - cmake_policy(POP) - set_property(TARGET ${name} PROPERTY _EP_IS_EXTERNAL_PROJECT 1) - set_property(TARGET ${name} PROPERTY LABELS ${name}) - set_property(TARGET ${name} PROPERTY FOLDER "ExternalProjectTargets/${name}") - - set_property(TARGET ${name} PROPERTY _EP_CMP0114 "${cmp0114}") - - set(keywords +macro(_ep_get_add_keywords out_var) + set(${out_var} # # Directory options # @@ -3629,14 +3834,73 @@ function(ExternalProject_Add name) # LIST_SEPARATOR ) - _ep_parse_arguments(ExternalProject_Add "${keywords}" ${name} _EP_ "${ARGN}") - _ep_set_directories(${name}) +endmacro() + + +function(ExternalProject_Add name) + cmake_policy(GET CMP0097 _EP_CMP0097 + PARENT_SCOPE # undocumented, do not use outside of CMake + ) + cmake_policy(GET CMP0114 cmp0114 + PARENT_SCOPE # undocumented, do not use outside of CMake + ) + if(CMAKE_XCODE_BUILD_SYSTEM VERSION_GREATER_EQUAL 12 AND NOT cmp0114 STREQUAL "NEW") + message(AUTHOR_WARNING + "Policy CMP0114 is not set to NEW. " + "In order to support the Xcode \"new build system\", " + "this project must be updated to set policy CMP0114 to NEW." + "\n" + "Since CMake is generating for the Xcode \"new build system\", " + "ExternalProject_Add will use policy CMP0114's NEW behavior anyway, " + "but the generated build system may not match what the project intends." + ) + 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. + set(cmf_dir ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles) + _ep_get_complete_stampfile(${name} complete_stamp_file) + + cmake_policy(PUSH) + if(cmp0114 STREQUAL "NEW") + # To implement CMP0114 NEW behavior with Makefile generators, + # we need CMP0113 NEW behavior. + cmake_policy(SET CMP0113 NEW) + endif() + # The "ALL" option to add_custom_target just tells it to not set the + # EXCLUDE_FROM_ALL target property. Later, if the EXCLUDE_FROM_ALL + # argument was passed, we explicitly set it for the target. + add_custom_target(${name} ALL DEPENDS ${complete_stamp_file}) + cmake_policy(POP) + set_property(TARGET ${name} PROPERTY _EP_IS_EXTERNAL_PROJECT 1) + set_property(TARGET ${name} PROPERTY LABELS ${name}) + set_property(TARGET ${name} PROPERTY FOLDER "ExternalProjectTargets/${name}") + + set_property(TARGET ${name} PROPERTY _EP_CMP0114 "${cmp0114}") + + # 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() @@ -3677,10 +3941,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/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/download.cmake.in b/Modules/ExternalProject/download.cmake.in new file mode 100644 index 0000000..6ef4eb1 --- /dev/null +++ b/Modules/ExternalProject/download.cmake.in @@ -0,0 +1,187 @@ +# 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() + +function(download_and_verify) + 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} + " + ) + +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..d9e07f1 --- /dev/null +++ b/Modules/ExternalProject/extractfile.cmake.in @@ -0,0 +1,63 @@ +# 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) + +# Make file names absolute: +# +get_filename_component(filename "@filename@" ABSOLUTE) +get_filename_component(directory "@directory@" ABSOLUTE) + +message(STATUS "extracting... + src='${filename}' + dst='${directory}'") + +if(NOT EXISTS "${filename}") + message(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: +# +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 "Extract of '${filename}' failed") +endif() + +# Analyze what came out of the tar file: +# +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: +# +message(STATUS "extracting... [rename]") +file(REMOVE_RECURSE ${directory}) +get_filename_component(contents ${contents} ABSOLUTE) +file(RENAME ${contents} ${directory}) + +# Clean up: +# +message(STATUS "extracting... [clean up]") +file(REMOVE_RECURSE "${ut_dir}") + +message(STATUS "extracting... done") diff --git a/Modules/ExternalProject/gitclone.cmake.in b/Modules/ExternalProject/gitclone.cmake.in new file mode 100644 index 0000000..5e5c415 --- /dev/null +++ b/Modules/ExternalProject/gitclone.cmake.in @@ -0,0 +1,67 @@ +# 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(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 + ) +if(error_code) + message(FATAL_ERROR "Failed to copy script-last-run stamp file: '@gitclone_stampfile@'") +endif() diff --git a/Modules/ExternalProject/gitupdate.cmake.in b/Modules/ExternalProject/gitupdate.cmake.in new file mode 100644 index 0000000..7033918 --- /dev/null +++ b/Modules/ExternalProject/gitupdate.cmake.in @@ -0,0 +1,277 @@ +# 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(get_hash_for_ref ref out_var err_var) + execute_process( + COMMAND "@git_EXECUTABLE@" rev-parse "${ref}" + WORKING_DIRECTORY "@work_dir@" + RESULT_VARIABLE error_code + OUTPUT_VARIABLE ref_hash + ERROR_VARIABLE error_msg + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(error_code) + set(${out_var} "" PARENT_SCOPE) + else() + set(${out_var} "${ref_hash}" PARENT_SCOPE) + endif() + set(${err_var} "${error_msg}" PARENT_SCOPE) +endfunction() + +get_hash_for_ref(HEAD head_sha error_msg) +if(head_sha STREQUAL "") + message(FATAL_ERROR "Failed to get the hash for HEAD:\n${error_msg}") +endif() + + +execute_process( + COMMAND "@git_EXECUTABLE@" show-ref "@git_tag@" + WORKING_DIRECTORY "@work_dir@" + OUTPUT_VARIABLE show_ref_output +) +if(show_ref_output MATCHES "^[a-z0-9]+[ \\t]+refs/remotes/") + # Given a full remote/branch-name and we know about it already. Since + # branches can move around, we always have to fetch. + set(fetch_required YES) + set(checkout_name "@git_tag@") + +elseif(show_ref_output MATCHES "^[a-z0-9]+[ \\t]+refs/tags/") + # Given a tag name that we already know about. We don't know if the tag we + # have matches the remote though (tags can move), so we should fetch. + set(fetch_required YES) + set(checkout_name "@git_tag@") + + # Special case to preserve backward compatibility: if we are already at the + # same commit as the tag we hold locally, don't do a fetch and assume the tag + # hasn't moved on the remote. + # 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}") + return() + endif() + +elseif(show_ref_output MATCHES "^[a-z0-9]+[ \\t]+refs/heads/") + # Given a branch name without any remote and we already have a branch by that + # name. We might already have that branch checked out or it might be a + # different branch. It isn't safe to use a bare branch name without the + # remote, so do a fetch and replace the ref with one that includes the remote. + set(fetch_required YES) + set(checkout_name "@git_remote_name@/@git_tag@") + +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}") + return() + + elseif(tag_sha STREQUAL "") + # We don't know about this ref yet, so we have no choice but to fetch. + # We deliberately swallow any error message at the default log level + # because it can be confusing for users to see a failed git command. + # That failure is being handled here, so it isn't an error. + set(fetch_required YES) + set(checkout_name "@git_tag@") + if(NOT error_msg STREQUAL "") + message(VERBOSE "${error_msg}") + endif() + + else() + # We have the commit, so we know we were asked to find a commit hash + # (otherwise it would have been handled further above), but we don't + # have that commit checked out yet + set(fetch_required NO) + set(checkout_name "@git_tag@") + if(NOT error_msg STREQUAL "") + message(WARNING "${error_msg}") + endif() + + endif() +endif() + +if(fetch_required) + message(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 + ) +endif() + +set(git_update_strategy "@git_update_strategy@") +if(git_update_strategy STREQUAL "") + # Backward compatibility requires REBASE as the default behavior + set(git_update_strategy REBASE) +endif() + +if(git_update_strategy MATCHES "^REBASE(_CHECKOUT)?$") + # Asked to potentially try to rebase first, maybe with fallback to checkout. + # We can't if we aren't already on a branch and we shouldn't if that local + # branch isn't tracking the one we want to checkout. + execute_process( + COMMAND "@git_EXECUTABLE@" symbolic-ref -q HEAD + WORKING_DIRECTORY "@work_dir@" + OUTPUT_VARIABLE current_branch + OUTPUT_STRIP_TRAILING_WHITESPACE + # Don't test for an error. If this isn't a branch, we get a non-zero error + # code but empty output. + ) + + if(current_branch STREQUAL "") + # Not on a branch, checkout is the only sensible option since any rebase + # would always fail (and backward compatibility requires us to checkout in + # this situation) + set(git_update_strategy CHECKOUT) + + else() + execute_process( + COMMAND "@git_EXECUTABLE@" for-each-ref "--format='%(upstream:short)'" "${current_branch}" + WORKING_DIRECTORY "@work_dir@" + OUTPUT_VARIABLE upstream_branch + OUTPUT_STRIP_TRAILING_WHITESPACE + COMMAND_ERROR_IS_FATAL ANY # There is no error if no upstream is set + ) + 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 + # commits added to the ref we were asked to checkout if the current local + # branch happens to be able to rebase onto the target branch. There would + # be no error message and the user wouldn't know this was occurring. + set(git_update_strategy CHECKOUT) + endif() + + endif() +elseif(NOT git_update_strategy STREQUAL "CHECKOUT") + message(FATAL_ERROR "Unsupported git update strategy: ${git_update_strategy}") +endif() + + +# Check if stash is needed +execute_process( + COMMAND "@git_EXECUTABLE@" status --porcelain + WORKING_DIRECTORY "@work_dir@" + RESULT_VARIABLE error_code + OUTPUT_VARIABLE repo_status +) +if(error_code) + message(FATAL_ERROR "Failed to get the status") +endif() +string(LENGTH "${repo_status}" need_stash) + +# If not in clean state, stash changes in order to be able to perform a +# rebase or checkout without losing those changes permanently +if(need_stash) + execute_process( + COMMAND "@git_EXECUTABLE@" stash save @git_stash_save_options@ + WORKING_DIRECTORY "@work_dir@" + COMMAND_ERROR_IS_FATAL ANY + ) +endif() + +if(git_update_strategy STREQUAL "CHECKOUT") + execute_process( + COMMAND "@git_EXECUTABLE@" checkout "${checkout_name}" + WORKING_DIRECTORY "@work_dir@" + COMMAND_ERROR_IS_FATAL ANY + ) +else() + execute_process( + COMMAND "@git_EXECUTABLE@" rebase "${checkout_name}" + WORKING_DIRECTORY "@work_dir@" + RESULT_VARIABLE error_code + OUTPUT_VARIABLE rebase_output + ERROR_VARIABLE rebase_output + ) + if(error_code) + # Rebase failed, undo the rebase attempt before continuing + execute_process( + COMMAND "@git_EXECUTABLE@" rebase --abort + WORKING_DIRECTORY "@work_dir@" + ) + + if(NOT git_update_strategy STREQUAL "REBASE_CHECKOUT") + # Not allowed to do a checkout as a fallback, so cannot proceed + if(need_stash) + 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") + endif() + + # Fall back to checkout. We create an annotated tag so that the user + # can manually inspect the situation and revert if required. + # We can't log the failed rebase output because MSVC sees it and + # intervenes, causing the build to fail even though it completes. + # Write it to a file instead. + string(TIMESTAMP tag_timestamp "%Y%m%dT%H%M%S" UTC) + 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}") + 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 + ) + + execute_process( + COMMAND "@git_EXECUTABLE@" checkout "${checkout_name}" + WORKING_DIRECTORY "@work_dir@" + COMMAND_ERROR_IS_FATAL ANY + ) + endif() +endif() + +if(need_stash) + # Put back the stashed changes + execute_process( + COMMAND "@git_EXECUTABLE@" stash pop --index --quiet + WORKING_DIRECTORY "@work_dir@" + RESULT_VARIABLE error_code + ) + if(error_code) + # Stash pop --index failed: Try again dropping the index + execute_process( + COMMAND "@git_EXECUTABLE@" reset --hard --quiet + WORKING_DIRECTORY "@work_dir@" + ) + execute_process( + COMMAND "@git_EXECUTABLE@" stash pop --quiet + WORKING_DIRECTORY "@work_dir@" + RESULT_VARIABLE error_code + ) + if(error_code) + # Stash pop failed: Restore previous state. + execute_process( + COMMAND "@git_EXECUTABLE@" reset --hard --quiet ${head_sha} + WORKING_DIRECTORY "@work_dir@" + ) + execute_process( + COMMAND "@git_EXECUTABLE@" stash pop --index --quiet + WORKING_DIRECTORY "@work_dir@" + ) + message(FATAL_ERROR "\nFailed to unstash changes in: '@work_dir@'." + "\nYou will have to resolve the conflicts manually") + endif() + endif() +endif() + +set(init_submodules "@init_submodules@") +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 + ) +endif() diff --git a/Modules/ExternalProject/hgclone.cmake.in b/Modules/ExternalProject/hgclone.cmake.in new file mode 100644 index 0000000..09395cc --- /dev/null +++ b/Modules/ExternalProject/hgclone.cmake.in @@ -0,0 +1,45 @@ +# 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(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 + ) +if(error_code) + message(FATAL_ERROR "Failed to copy script-last-run stamp file: '@hgclone_stampfile@'") +endif() diff --git a/Modules/ExternalProject/hgupdate.cmake.in b/Modules/ExternalProject/hgupdate.cmake.in new file mode 100644 index 0000000..f88e1ee --- /dev/null +++ b/Modules/ExternalProject/hgupdate.cmake.in @@ -0,0 +1,16 @@ +# 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) + +execute_process( + COMMAND "@hg_EXECUTABLE@" pull + COMMAND_ERROR_IS_FATAL ANY + WORKING_DIRECTORY "@work_dir@" +) + +execute_process( + COMMAND "@hg_EXECUTABLE@" update @hg_tag@ + COMMAND_ERROR_IS_FATAL ANY + WORKING_DIRECTORY "@work_dir@" +) 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..f37059b --- /dev/null +++ b/Modules/ExternalProject/verify.cmake.in @@ -0,0 +1,48 @@ +# 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() + +function(do_verify) + 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") +endfunction() + +do_verify() + +set(extract_script "@extract_script_filename@") +if(NOT "${extract_script}" STREQUAL "") + include("${extract_script}") +endif() 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@' diff --git a/Tests/RunCMake/ExternalProject/NO_DEPENDS-CMP0114-NEW-stderr.txt b/Tests/RunCMake/ExternalProject/NO_DEPENDS-CMP0114-NEW-stderr.txt index 5a5ba89..22d7ac0 100644 --- a/Tests/RunCMake/ExternalProject/NO_DEPENDS-CMP0114-NEW-stderr.txt +++ b/Tests/RunCMake/ExternalProject/NO_DEPENDS-CMP0114-NEW-stderr.txt @@ -10,7 +10,7 @@ Call Stack \(most recent call first\): [^ ]*/Modules/ExternalProject.cmake:[0-9]+ \(ExternalProject_Add_Step\) [^ -]*/Modules/ExternalProject.cmake:[0-9]+ \(_ep_add_mkdir_command\) +]*/Modules/ExternalProject.cmake:[0-9]+ \(_ep_add_preconfigure_command\) NO_DEPENDS-CMP0114-Common.cmake:[0-9]+ \(ExternalProject_Add\) NO_DEPENDS-CMP0114-NEW.cmake:[0-9]+ \(include\) CMakeLists.txt:[0-9]+ \(include\)$ diff --git a/Tests/RunCMake/ExternalProject/NO_DEPENDS-CMP0114-WARN-stderr.txt b/Tests/RunCMake/ExternalProject/NO_DEPENDS-CMP0114-WARN-stderr.txt index bbf7178..0172e3f 100644 --- a/Tests/RunCMake/ExternalProject/NO_DEPENDS-CMP0114-WARN-stderr.txt +++ b/Tests/RunCMake/ExternalProject/NO_DEPENDS-CMP0114-WARN-stderr.txt @@ -13,7 +13,7 @@ Call Stack \(most recent call first\): [^ ]*/Modules/ExternalProject.cmake:[0-9]+ \(ExternalProject_Add_Step\) [^ -]*/Modules/ExternalProject.cmake:[0-9]+ \(_ep_add_mkdir_command\) +]*/Modules/ExternalProject.cmake:[0-9]+ \(_ep_add_preconfigure_command\) NO_DEPENDS-CMP0114-Common.cmake:[0-9]+ \(ExternalProject_Add\) NO_DEPENDS-CMP0114-WARN.cmake:[0-9]+ \(include\) CMakeLists.txt:[0-9]+ \(include\) @@ -68,7 +68,7 @@ Call Stack \(most recent call first\): [^ ]*/Modules/ExternalProject.cmake:[0-9]+ \(ExternalProject_Add_Step\) [^ -]*/Modules/ExternalProject.cmake:[0-9]+ \(_ep_add_mkdir_command\) +]*/Modules/ExternalProject.cmake:[0-9]+ \(_ep_add_preconfigure_command\) NO_DEPENDS-CMP0114-Common.cmake:[0-9]+ \(ExternalProject_Add\) NO_DEPENDS-CMP0114-WARN.cmake:[0-9]+ \(include\) CMakeLists.txt:[0-9]+ \(include\) diff --git a/Tests/RunCMake/ExternalProject/NoOptions-stderr.txt b/Tests/RunCMake/ExternalProject/NoOptions-stderr.txt index 2fc7d29..9576ae1 100644 --- a/Tests/RunCMake/ExternalProject/NoOptions-stderr.txt +++ b/Tests/RunCMake/ExternalProject/NoOptions-stderr.txt @@ -13,6 +13,6 @@ \* HG_REPOSITORY \* CVS_REPOSITORY and CVS_MODULE Call Stack \(most recent call first\): - .*/Modules/ExternalProject.cmake:[0-9]+ \(_ep_add_download_command\) + .*/Modules/ExternalProject.cmake:[0-9]+ \(_ep_prepare_download\) NoOptions.cmake:[0-9]+ \(ExternalProject_Add\) CMakeLists.txt:[0-9]+ \(include\)$ diff --git a/Tests/RunCMake/ExternalProject/SourceEmpty-stderr.txt b/Tests/RunCMake/ExternalProject/SourceEmpty-stderr.txt index 07c6e87..648f28b 100644 --- a/Tests/RunCMake/ExternalProject/SourceEmpty-stderr.txt +++ b/Tests/RunCMake/ExternalProject/SourceEmpty-stderr.txt @@ -13,6 +13,6 @@ \* HG_REPOSITORY \* CVS_REPOSITORY and CVS_MODULE Call Stack \(most recent call first\): - .*/Modules/ExternalProject.cmake:[0-9]+ \(_ep_add_download_command\) + .*/Modules/ExternalProject.cmake:[0-9]+ \(_ep_prepare_download\) SourceEmpty.cmake:[0-9]+ \(ExternalProject_Add\) CMakeLists.txt:[0-9]+ \(include\)$ diff --git a/Tests/RunCMake/ExternalProject/SourceMissing-stderr.txt b/Tests/RunCMake/ExternalProject/SourceMissing-stderr.txt index 373f6e3..e061cf6 100644 --- a/Tests/RunCMake/ExternalProject/SourceMissing-stderr.txt +++ b/Tests/RunCMake/ExternalProject/SourceMissing-stderr.txt @@ -13,6 +13,6 @@ \* HG_REPOSITORY \* CVS_REPOSITORY and CVS_MODULE Call Stack \(most recent call first\): - .*/Modules/ExternalProject.cmake:[0-9]+ \(_ep_add_download_command\) + .*/Modules/ExternalProject.cmake:[0-9]+ \(_ep_prepare_download\) SourceMissing.cmake:[0-9]+ \(ExternalProject_Add\) CMakeLists.txt:[0-9]+ \(include\)$ diff --git a/Tests/RunCMake/ExternalProject/UsesTerminal-check.cmake b/Tests/RunCMake/ExternalProject/UsesTerminal-check.cmake index 201d822..2850bed 100644 --- a/Tests/RunCMake/ExternalProject/UsesTerminal-check.cmake +++ b/Tests/RunCMake/ExternalProject/UsesTerminal-check.cmake @@ -19,7 +19,7 @@ cmake_minimum_required(VERSION 3.3) # console pool. macro(CheckNinjaStep _target _step _require) if("${_build}" MATCHES -" DESC = Performing ${_step} step for '${_target}' +" DESC = Performing ${_step} step (\\([a-zA-Z0-9 ]*\\) )?for '${_target}' pool = console" ) if(NOT ${_require}) -- cgit v0.12 From 17e5516e608ba5c9c1f2dfad3d64f8f90874f108 Mon Sep 17 00:00:00 2001 From: Craig Scott Date: Fri, 29 Jan 2021 23:22:45 +1100 Subject: FetchContent: Invoke steps directly and avoid a separate sub-build The cost of setting up and executing a separate sub-build to do the download, update and patch steps required for FetchContent population can be significant with some platforms and CMake generators. Avoid the sub-build altogether by invoking the step scripts directly. Previously, if no generator was set (e.g. population was being done in script mode), a generator needed to be available on the default PATH. Since we no longer use a sub-build, this restriction is also now gone. Fixes: #21703 --- Help/release/dev/fetchcontent-performance.rst | 6 + Modules/ExternalProject.cmake | 226 +++++++++++++++------ .../ExternalProject/captured_process_setup.cmake | 55 +++++ Modules/ExternalProject/customcommand.cmake.in | 8 + .../customcommand_preamble.cmake.in | 8 + Modules/ExternalProject/download.cmake.in | 46 +++-- Modules/ExternalProject/extractfile.cmake.in | 32 ++- Modules/ExternalProject/gitclone.cmake.in | 77 ++++--- Modules/ExternalProject/gitupdate.cmake.in | 92 ++++++--- Modules/ExternalProject/hgclone.cmake.in | 47 +++-- Modules/ExternalProject/hgupdate.cmake.in | 12 +- Modules/ExternalProject/verify.cmake.in | 22 +- Modules/FetchContent.cmake | 131 ++---------- Modules/FetchContent/CMakeLists.cmake.in | 27 --- Tests/RunCMake/FetchContent/RunCMakeTest.cmake | 1 - Tests/RunCMake/FetchContent/SameGenerator.cmake | 17 -- 16 files changed, 481 insertions(+), 326 deletions(-) create mode 100644 Modules/ExternalProject/captured_process_setup.cmake create mode 100644 Modules/ExternalProject/customcommand.cmake.in create mode 100644 Modules/ExternalProject/customcommand_preamble.cmake.in delete mode 100644 Modules/FetchContent/CMakeLists.cmake.in delete mode 100644 Tests/RunCMake/FetchContent/SameGenerator.cmake diff --git a/Help/release/dev/fetchcontent-performance.rst b/Help/release/dev/fetchcontent-performance.rst index 361c2b4..fcb68a1 100644 --- a/Help/release/dev/fetchcontent-performance.rst +++ b/Help/release/dev/fetchcontent-performance.rst @@ -5,3 +5,9 @@ fetchcontent-performance significantly refactored. The patch step gained support for using the terminal with a new ``USES_TERMINAL_PATCH`` keyword as a by-product of that work. +* The :module:`FetchContent` module no longer creates a separate + sub-build to implement the content population. It now invokes + the step scripts directly from within the main project's + configure stage. This significantly speeds up the configure + phase when the required content is already populated and + up-to-date. diff --git a/Modules/ExternalProject.cmake b/Modules/ExternalProject.cmake index 5f00c87..2b413c2 100644 --- a/Modules/ExternalProject.cmake +++ b/Modules/ExternalProject.cmake @@ -1200,46 +1200,46 @@ function(_ep_parse_arguments keywords name ns args) endfunction() +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_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_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_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_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_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_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_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_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 @@ -1258,7 +1258,8 @@ function(_ep_write_gitclone_script work_dir gitclone_infofile gitclone_stampfile - tls_verify) + tls_verify + quiet) if(NOT GIT_VERSION_STRING VERSION_LESS 1.8.5) # Use `git checkout --` to avoid ambiguity with a local path. @@ -1322,7 +1323,8 @@ function(_ep_write_hgclone_script src_name work_dir hgclone_infofile - hgclone_stampfile) + hgclone_stampfile + quiet) if("${hg_tag}" STREQUAL "") message(FATAL_ERROR "Tag for hg checkout should not be empty.") @@ -1347,7 +1349,8 @@ function(_ep_write_gitupdate_script git_submodules git_repository work_dir - git_update_strategy) + git_update_strategy + quiet) if("${git_tag}" STREQUAL "") message(FATAL_ERROR "Tag for git checkout should not be empty.") @@ -1372,7 +1375,8 @@ function(_ep_write_hgupdate_script script_filename hg_EXECUTABLE hg_tag - work_dir) + work_dir + quiet) configure_file( ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ExternalProject/hgupdate.cmake.in @@ -1408,7 +1412,8 @@ function(_ep_write_downloadfile_script http_headers netrc netrc_file - extract_script_filename) + extract_script_filename + quiet) if(timeout) set(TIMEOUT_ARGS TIMEOUT ${timeout}) @@ -1426,7 +1431,7 @@ function(_ep_write_downloadfile_script endif() - if(no_progress) + if(no_progress OR quiet) set(SHOW_PROGRESS "") else() set(SHOW_PROGRESS "SHOW_PROGRESS") @@ -1523,7 +1528,8 @@ function(_ep_write_verifyfile_script script_filename LOCAL hash - extract_script_filename) + extract_script_filename + quiet) _ep_get_hash_regex(_ep_hash_regex) if("${hash}" MATCHES "${_ep_hash_regex}") @@ -1551,7 +1557,8 @@ function(_ep_write_extractfile_script script_filename name filename - directory) + directory + quiet) set(args "") @@ -1578,7 +1585,8 @@ function(_ep_write_extractfile_script endfunction() -# This function is an implementation detail of ExternalProject_Add(). +# 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_. It will create the various directories @@ -2059,7 +2067,7 @@ 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() @@ -2523,6 +2531,7 @@ function(_ep_write_command_script commands work_dir genex_supported + quiet have_commands_var) set(sep "${_EP_LIST_SEPARATOR}") @@ -2531,6 +2540,10 @@ function(_ep_write_command_script 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) @@ -2539,13 +2552,8 @@ function(_ep_write_command_script # Silently skip empty commands continue() endif() - string(APPEND script_content " -execute_process( - COMMAND ${this_command} - COMMAND_ERROR_IS_FATAL LAST - WORKING_DIRECTORY [==[${work_dir}]==] -) -") + string(CONFIGURE "${exec_command_template}" content @ONLY) + string(APPEND script_content "${content}") set(this_command) else() # Ensure we quote every token so we preserve empty items, quotes, etc @@ -2554,20 +2562,20 @@ execute_process( endforeach() if(NOT "${this_command}" STREQUAL "") - string(APPEND script_content " -execute_process( - COMMAND ${this_command} - COMMAND_ERROR_IS_FATAL LAST - WORKING_DIRECTORY [==[${work_dir}]==] -) -") + 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) - string(PREPEND script_content "cmake_minimum_required(VERSION 3.19)\n") + 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() if(genex_supported) @@ -2603,7 +2611,8 @@ function(_ep_add_preconfigure_command name step) ) endfunction() -# This function is an implementation detail of ExternalProject_Add(). +# 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_. It will populate the variable @@ -2619,6 +2628,7 @@ function(_ep_prepare_download name genex_supported) set(tmp_dir "${_EP_TMP_DIR}") set(source_dir "${_EP_SOURCE_DIR}") set(download_dir "${_EP_DOWNLOAD_DIR}") + set(quiet "${_EP_QUIET}") set(comment) @@ -2628,6 +2638,7 @@ function(_ep_prepare_download name genex_supported) 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) @@ -2660,6 +2671,7 @@ work_dir=${work_dir} "${_EP_DOWNLOAD_COMMAND}" "${work_dir}" "${genex_supported}" + "${quiet}" script_does_something ) set(comment "Performing download step (custom command) for '${name}'") @@ -2698,6 +2710,7 @@ source_dir=${source_dir} "${cmd}" "${work_dir}" "${genex_supported}" + "${quiet}" script_does_something ) set(comment "Performing download step (CVS checkout) for '${name}'") @@ -2750,6 +2763,7 @@ source_dir=${source_dir} "${cmd}" "${work_dir}" "${genex_supported}" + "${quiet}" script_does_something ) set(comment "Performing download step (SVN checkout) for '${name}'") @@ -2835,6 +2849,7 @@ source_dir=${source_dir} "${repo_info_file}" "${last_run_file}" "${tls_verify}" + "${quiet}" ) set(comment "Performing download step (git clone) for '${name}'") @@ -2880,6 +2895,7 @@ source_dir=${source_dir} "${work_dir}" "${repo_info_file}" "${last_run_file}" + "${quiet}" ) set(comment "Performing download step (hg clone) for '${name}'") @@ -2982,6 +2998,7 @@ source_dir=${source_dir} "${_EP_NETRC}" "${_EP_NETRC_FILE}" "${extract_script}" + "${quiet}" ) if(no_extract) set(steps "download and verify") @@ -2995,6 +3012,7 @@ source_dir=${source_dir} "${file}" "${hash}" "${extract_script}" + "${quiet}" ) if(no_extract) set(steps "verify") @@ -3012,6 +3030,7 @@ source_dir=${source_dir} "${name}" "${file}" "${source_dir}" + "${quiet}" ) endif() endif() @@ -3079,7 +3098,8 @@ function(_ep_get_update_disconnected var) set(${var} "${update_disconnected}" PARENT_SCOPE) endfunction() -# This function is an implementation detail of ExternalProject_Add(). +# 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_. @@ -3091,6 +3111,7 @@ function(_ep_prepare_update name genex_supported) set(tmp_dir "${_EP_TMP_DIR}") set(source_dir "${_EP_SOURCE_DIR}") + set(quiet "${_EP_QUIET}") set(comment) @@ -3102,6 +3123,7 @@ function(_ep_prepare_update name genex_supported) 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) @@ -3114,6 +3136,7 @@ function(_ep_prepare_update name genex_supported) "${_EP_UPDATE_COMMAND}" "${work_dir}" "${genex_supported}" + "${quiet}" script_does_something ) set(comment "Performing update step (custom command) for '${name}'") @@ -3132,6 +3155,7 @@ function(_ep_prepare_update name genex_supported) "${cmd}" "${work_dir}" "${genex_supported}" + "${quiet}" script_does_something ) set(comment "Performing update step (CVS update) for '${name}'") @@ -3165,6 +3189,7 @@ function(_ep_prepare_update name genex_supported) "${cmd}" "${work_dir}" "${genex_supported}" + "${quiet}" script_does_something ) set(comment "Performing update step (SVN update) for '${name}'") @@ -3222,6 +3247,7 @@ function(_ep_prepare_update name genex_supported) "${_EP_GIT_REPOSITORY}" "${work_dir}" "${git_update_strategy}" + "${quiet}" ) set(script_does_something TRUE) set(comment "Performing update step (git update) for '${name}'") @@ -3250,6 +3276,7 @@ Update to Mercurial >= 2.1.1. "${HG_EXECUTABLE}" "${hg_tag}" "${work_dir}" + "${quiet}" ) set(script_does_something TRUE) set(comment "Performing update step (hg pull) for '${name}'") @@ -3280,7 +3307,8 @@ Update to Mercurial >= 2.1.1. endfunction() -# This function is an implementation detail of ExternalProject_Add(). +# 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_. @@ -3292,6 +3320,7 @@ function(_ep_prepare_patch name genex_supported) set(tmp_dir "${_EP_TMP_DIR}") set(source_dir "${_EP_SOURCE_DIR}") + set(quiet "${_EP_QUIET}") _ep_get_update_disconnected(update_disconnected) if(update_disconnected) @@ -3306,6 +3335,7 @@ function(_ep_prepare_patch name genex_supported) if(log) 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(script_filename ${tmp_dir}/${name}-patch.cmake) set(log FALSE) @@ -3318,6 +3348,7 @@ function(_ep_prepare_patch name genex_supported) "${_EP_PATCH_COMMAND}" "${work_dir}" "${genex_supported}" + "${quiet}" script_does_something ) if(script_does_something) @@ -3837,6 +3868,73 @@ macro(_ep_get_add_keywords out_var) 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 PARENT_SCOPE # undocumented, do not use outside of CMake 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/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 index 6ef4eb1..c8d2f28 100644 --- a/Modules/ExternalProject/download.cmake.in +++ b/Modules/ExternalProject/download.cmake.in @@ -3,13 +3,17 @@ 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 "") - message(FATAL_ERROR "has_hash Can't be empty") + _ep_message_quiet_capture(FATAL_ERROR "has_hash Can't be empty") endif() if("${hash_is_good}" STREQUAL "") - message(FATAL_ERROR "hash_is_good Can't be empty") + _ep_message_quiet_capture(FATAL_ERROR "hash_is_good Can't be empty") endif() if("@ALGO@" STREQUAL "") @@ -21,18 +25,20 @@ function(check_file_hash has_hash hash_is_good) set("${has_hash}" TRUE PARENT_SCOPE) - message(STATUS "verifying file... + _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) - message(STATUS "@ALGO@ hash of + _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() @@ -44,7 +50,8 @@ function(sleep_before_download attempt) endif() if(attempt EQUAL 1) - message(STATUS "Retrying...") + _ep_message_quiet_capture(STATUS "Retrying...") + set(accumulated_output "${accumulated_output}" PARENT_SCOPE) return() endif() @@ -66,7 +73,10 @@ function(sleep_before_download attempt) set(sleep_seconds 1200) endif() - message(STATUS "Retry after ${sleep_seconds} seconds (attempt #${attempt}) ...") + _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() @@ -84,18 +94,22 @@ function(download_and_verify) check_file_hash(has_hash hash_is_good) if(has_hash) if(hash_is_good) - message(STATUS + _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() - message(STATUS "File already exists but hash mismatch. Removing...") + _ep_message_quiet_capture(STATUS + "File already exists but hash mismatch. Removing..." + ) + set(accumulated_output "${accumulated_output}" PARENT_SCOPE) file(REMOVE "@LOCAL@") endif() else() - message(STATUS + _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." @@ -106,11 +120,12 @@ Old file will be removed and new file downloaded from URL." set(retry_number 5) - message(STATUS "Downloading... + _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) @@ -120,7 +135,8 @@ Old file will be removed and new file downloaded from URL." endif() foreach(url @REMOTE@) if(NOT url IN_LIST skip_url_list) - message(STATUS "Using src='${url}'") + _ep_message_quiet_capture(STATUS "Using src='${url}'") + set(accumulated_output "${accumulated_output}" PARENT_SCOPE) @TLS_VERIFY_CODE@ @TLS_CAINFO_CODE@ @@ -145,10 +161,12 @@ Old file will be removed and new file downloaded from URL." 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...") + _ep_message_quiet_capture(STATUS "Hash mismatch, removing...") + set(accumulated_output "${accumulated_output}" PARENT_SCOPE) file(REMOVE "@LOCAL@") else() - message(STATUS "Downloading... done") + _ep_message_quiet_capture(STATUS "Downloading... done") + set(accumulated_output "${accumulated_output}" PARENT_SCOPE) return() endif() else() @@ -171,7 +189,7 @@ Old file will be removed and new file downloaded from URL." endforeach() endforeach() - message(FATAL_ERROR + _ep_message_quiet_capture(FATAL_ERROR "Each download failed! ${logFailedURLs} " diff --git a/Modules/ExternalProject/extractfile.cmake.in b/Modules/ExternalProject/extractfile.cmake.in index d9e07f1..d46de73 100644 --- a/Modules/ExternalProject/extractfile.cmake.in +++ b/Modules/ExternalProject/extractfile.cmake.in @@ -3,17 +3,24 @@ 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) -message(STATUS "extracting... +_ep_message_quiet_capture(STATUS "extracting... src='${filename}' - dst='${directory}'") + dst='${directory}'" +) if(NOT EXISTS "${filename}") - message(FATAL_ERROR "File to extract does not exist: '${filename}'") + _ep_message_quiet_capture(FATAL_ERROR + "File to extract does not exist: '${filename}'" + ) endif() # Prepare a space for extracting: @@ -27,20 +34,23 @@ file(MAKE_DIRECTORY "${ut_dir}") # Extract it: # -message(STATUS "extracting... [tar @args@]") +_ep_message_quiet_capture(STATUS "extracting... [tar @args@]") execute_process(COMMAND ${CMAKE_COMMAND} -E tar @args@ ${filename} WORKING_DIRECTORY ${ut_dir} - RESULT_VARIABLE rv) + RESULT_VARIABLE rv + ${capture_output} +) +_ep_accumulate_captured_output() if(NOT rv EQUAL 0) - message(STATUS "extracting... [error clean up]") + _ep_message_quiet_capture(STATUS "extracting... [error clean up]") file(REMOVE_RECURSE "${ut_dir}") - message(FATAL_ERROR "Extract of '${filename}' failed") + _ep_message_quiet_capture(FATAL_ERROR "Extract of '${filename}' failed") endif() # Analyze what came out of the tar file: # -message(STATUS "extracting... [analysis]") +_ep_message_quiet_capture(STATUS "extracting... [analysis]") file(GLOB contents "${ut_dir}/*") list(REMOVE_ITEM contents "${ut_dir}/.DS_Store") list(LENGTH contents n) @@ -50,14 +60,14 @@ endif() # Move "the one" directory to the final directory: # -message(STATUS "extracting... [rename]") +_ep_message_quiet_capture(STATUS "extracting... [rename]") file(REMOVE_RECURSE ${directory}) get_filename_component(contents ${contents} ABSOLUTE) file(RENAME ${contents} ${directory}) # Clean up: # -message(STATUS "extracting... [clean up]") +_ep_message_quiet_capture(STATUS "extracting... [clean up]") file(REMOVE_RECURSE "${ut_dir}") -message(STATUS "extracting... done") +_ep_message_quiet_capture(STATUS "extracting... done") diff --git a/Modules/ExternalProject/gitclone.cmake.in b/Modules/ExternalProject/gitclone.cmake.in index 5e5c415..a2e900c 100644 --- a/Modules/ExternalProject/gitclone.cmake.in +++ b/Modules/ExternalProject/gitclone.cmake.in @@ -3,57 +3,81 @@ 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@") - message(STATUS "Avoiding repeated git clone, stamp file is up to date: '@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 - ) -if(error_code) - message(FATAL_ERROR "Failed to remove directory: '@source_dir@'") -endif() + ${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@" + 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) - 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@'") + 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--@ + 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() + ${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@ + 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@'") + ${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: @@ -61,7 +85,8 @@ endif() execute_process( COMMAND ${CMAKE_COMMAND} -E copy "@gitclone_infofile@" "@gitclone_stampfile@" RESULT_VARIABLE error_code - ) -if(error_code) - message(FATAL_ERROR "Failed to copy script-last-run stamp file: '@gitclone_stampfile@'") -endif() + ${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 index 09395cc..5561955 100644 --- a/Modules/ExternalProject/hgclone.cmake.in +++ b/Modules/ExternalProject/hgclone.cmake.in @@ -3,43 +3,56 @@ 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@") - message(STATUS "Avoiding repeated hg clone, stamp file is up to date: '@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 - ) -if(error_code) - message(FATAL_ERROR "Failed to remove directory: '@source_dir@'") -endif() + ${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 - ) -if(error_code) - message(FATAL_ERROR "Failed to clone repository: '@hg_repository@'") -endif() + ${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 - ) -if(error_code) - message(FATAL_ERROR "Failed to checkout tag: '@hg_tag@'") -endif() + ${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 - ) -if(error_code) - message(FATAL_ERROR "Failed to copy script-last-run stamp file: '@hgclone_stampfile@'") -endif() + ${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 index f88e1ee..a82a819 100644 --- a/Modules/ExternalProject/hgupdate.cmake.in +++ b/Modules/ExternalProject/hgupdate.cmake.in @@ -3,14 +3,22 @@ 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 - COMMAND_ERROR_IS_FATAL ANY WORKING_DIRECTORY "@work_dir@" + RESULT_VARIABLE error_code + ${capture_output} ) +_ep_command_check_result(error_code) execute_process( COMMAND "@hg_EXECUTABLE@" update @hg_tag@ - COMMAND_ERROR_IS_FATAL ANY WORKING_DIRECTORY "@work_dir@" + RESULT_VARIABLE error_code + ${capture_output} ) +_ep_command_check_result(error_code) diff --git a/Modules/ExternalProject/verify.cmake.in b/Modules/ExternalProject/verify.cmake.in index f37059b..cd34ba9 100644 --- a/Modules/ExternalProject/verify.cmake.in +++ b/Modules/ExternalProject/verify.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) + if("@LOCAL@" STREQUAL "") message(FATAL_ERROR "LOCAL can't be empty") endif() @@ -13,22 +17,27 @@ endif() function(do_verify) if("@ALGO@" STREQUAL "") - message(WARNING "File will not be verified since no URL_HASH specified") + _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 "") - message(FATAL_ERROR "EXPECT_VALUE can't be empty") + _ep_message_quiet_capture(FATAL_ERROR "EXPECT_VALUE can't be empty") endif() - message(STATUS + _ep_message_quiet_capture(STATUS "verifying file... - file='@LOCAL@'") + file='@LOCAL@'" + ) + set(accumulated_output "${accumulated_output}" PARENT_SCOPE) file("@ALGO@" "@LOCAL@" actual_value) if(NOT "${actual_value}" STREQUAL "@EXPECT_VALUE@") - message(FATAL_ERROR + _ep_message_quiet_capture(FATAL_ERROR "error: @ALGO@ hash of @LOCAL@ does not match expected value @@ -37,7 +46,8 @@ does not match expected value ") endif() - message(STATUS "verifying file... done") + _ep_message_quiet_capture(STATUS "verifying file... done") + set(accumulated_output "${accumulated_output}" PARENT_SCOPE) endfunction() do_verify() 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 - \"\" \"${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/Tests/RunCMake/FetchContent/RunCMakeTest.cmake b/Tests/RunCMake/FetchContent/RunCMakeTest.cmake index 9baeab7..b497382 100644 --- a/Tests/RunCMake/FetchContent/RunCMakeTest.cmake +++ b/Tests/RunCMake/FetchContent/RunCMakeTest.cmake @@ -7,7 +7,6 @@ run_cmake(DirectIgnoresDetails) run_cmake(FirstDetailsWin) run_cmake(DownloadTwice) run_cmake(DownloadFile) -run_cmake(SameGenerator) run_cmake(VarDefinitions) run_cmake(GetProperties) run_cmake(UsesTerminalOverride) diff --git a/Tests/RunCMake/FetchContent/SameGenerator.cmake b/Tests/RunCMake/FetchContent/SameGenerator.cmake deleted file mode 100644 index 58204ef..0000000 --- a/Tests/RunCMake/FetchContent/SameGenerator.cmake +++ /dev/null @@ -1,17 +0,0 @@ -include(FetchContent) - -FetchContent_Declare( - t1 - DOWNLOAD_COMMAND ${CMAKE_COMMAND} -E echo "Download command executed" -) - -FetchContent_Populate(t1) - -file(STRINGS "${FETCHCONTENT_BASE_DIR}/t1-subbuild/CMakeCache.txt" - matchLine REGEX "^CMAKE_GENERATOR:.*=" - LIMIT_COUNT 1 -) -if(NOT matchLine MATCHES "${CMAKE_GENERATOR}") - message(FATAL_ERROR "Generator line mismatch: ${matchLine}\n" - " Expected type: ${CMAKE_GENERATOR}") -endif() -- cgit v0.12