Browse Source

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
Craig Scott 5 years ago
parent
commit
17e5516e60

+ 6 - 0
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.

+ 162 - 64
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 <tree-ish> --` 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_<keyword>. 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_<keyword>. 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_<keyword>.
@@ -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_<keyword>.
@@ -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

+ 55 - 0
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()

+ 8 - 0
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)

+ 8 - 0
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)

+ 32 - 14
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}
   "

+ 21 - 11
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")

+ 51 - 26
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@'"
+)

+ 65 - 27
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()

+ 30 - 17
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@'"
+)

+ 10 - 2
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)

+ 16 - 6
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()

+ 17 - 114
Modules/FetchContent.cmake

@@ -849,8 +849,6 @@ function(__FetchContent_directPopulate contentName)
       SUBBUILD_DIR
       SOURCE_DIR
       BINARY_DIR
-      # We need special processing if DOWNLOAD_NO_EXTRACT is true
-      DOWNLOAD_NO_EXTRACT
       # Prevent the following from being passed through
       CONFIGURE_COMMAND
       BUILD_COMMAND
@@ -894,123 +892,28 @@ function(__FetchContent_directPopulate contentName)
   set(${contentName}_SOURCE_DIR "${ARG_SOURCE_DIR}" PARENT_SCOPE)
   set(${contentName}_BINARY_DIR "${ARG_BINARY_DIR}" PARENT_SCOPE)
 
-  # The unparsed arguments may contain spaces, so build up ARG_EXTRA
-  # in such a way that it correctly substitutes into the generated
-  # CMakeLists.txt file with each argument quoted.
-  unset(ARG_EXTRA)
-  foreach(arg IN LISTS ARG_UNPARSED_ARGUMENTS)
-    set(ARG_EXTRA "${ARG_EXTRA} \"${arg}\"")
-  endforeach()
-
-  if(ARG_DOWNLOAD_NO_EXTRACT)
-    set(ARG_EXTRA "${ARG_EXTRA} DOWNLOAD_NO_EXTRACT YES")
-    set(__FETCHCONTENT_COPY_FILE
-"
-ExternalProject_Get_Property(${contentName}-populate DOWNLOADED_FILE)
-get_filename_component(dlFileName \"\${DOWNLOADED_FILE}\" NAME)
-
-ExternalProject_Add_Step(${contentName}-populate copyfile
-  COMMAND    \"${CMAKE_COMMAND}\" -E copy_if_different
-             \"<DOWNLOADED_FILE>\" \"${ARG_SOURCE_DIR}\"
-  DEPENDEES  patch
-  DEPENDERS  configure
-  BYPRODUCTS \"${ARG_SOURCE_DIR}/\${dlFileName}\"
-  COMMENT    \"Copying file to SOURCE_DIR\"
-)
-")
+  if(ARG_QUIET)
+    set(quiet TRUE)
   else()
-    unset(__FETCHCONTENT_COPY_FILE)
-  endif()
-
-  # Hide output if requested, but save it to a variable in case there's an
-  # error so we can show the output upon failure. When not quiet, don't
-  # capture the output to a variable because the user may want to see the
-  # output as it happens (e.g. progress during long downloads). Combine both
-  # stdout and stderr in the one capture variable so the output stays in order.
-  if (ARG_QUIET)
-    set(outputOptions
-        OUTPUT_VARIABLE capturedOutput
-        ERROR_VARIABLE  capturedOutput
-    )
-  else()
-    set(capturedOutput)
-    set(outputOptions)
+    set(quiet FALSE)
     message(STATUS "Populating ${contentName}")
   endif()
 
-  if(CMAKE_GENERATOR)
-    set(subCMakeOpts "-G${CMAKE_GENERATOR}")
-    if(CMAKE_GENERATOR_PLATFORM)
-      list(APPEND subCMakeOpts "-A${CMAKE_GENERATOR_PLATFORM}")
-    endif()
-    if(CMAKE_GENERATOR_TOOLSET)
-      list(APPEND subCMakeOpts "-T${CMAKE_GENERATOR_TOOLSET}")
-    endif()
-
-    if(CMAKE_MAKE_PROGRAM)
-      list(APPEND subCMakeOpts "-DCMAKE_MAKE_PROGRAM:FILEPATH=${CMAKE_MAKE_PROGRAM}")
-    endif()
-
-  else()
-    # Likely we've been invoked via CMake's script mode where no
-    # generator is set (and hence CMAKE_MAKE_PROGRAM could not be
-    # trusted even if provided). We will have to rely on being
-    # able to find the default generator and build tool.
-    unset(subCMakeOpts)
-  endif()
-
-  if(DEFINED CMAKE_EP_GIT_REMOTE_UPDATE_STRATEGY)
-    list(APPEND subCMakeOpts
-      "-DCMAKE_EP_GIT_REMOTE_UPDATE_STRATEGY=${CMAKE_EP_GIT_REMOTE_UPDATE_STRATEGY}")
-  endif()
-
-  # Avoid using if(... IN_LIST ...) so we don't have to alter policy settings
-  set(__FETCHCONTENT_CACHED_INFO "")
-  list(FIND ARG_UNPARSED_ARGUMENTS GIT_REPOSITORY indexResult)
-  if(indexResult GREATER_EQUAL 0)
-    find_package(Git QUIET)
-    set(__FETCHCONTENT_CACHED_INFO
-"# Pass through things we've already detected in the main project to avoid
-# paying the cost of redetecting them again in ExternalProject_Add()
-set(GIT_EXECUTABLE [==[${GIT_EXECUTABLE}]==])
-set(GIT_VERSION_STRING [==[${GIT_VERSION_STRING}]==])
-set_property(GLOBAL PROPERTY _CMAKE_FindGit_GIT_EXECUTABLE_VERSION
-  [==[${GIT_EXECUTABLE};${GIT_VERSION_STRING}]==]
-)
-")
-  endif()
-
-  # Create and build a separate CMake project to carry out the population.
-  # If we've already previously done these steps, they will not cause
-  # anything to be updated, so extra rebuilds of the project won't occur.
-  # Make sure to pass through CMAKE_MAKE_PROGRAM in case the main project
-  # has this set to something not findable on the PATH.
-  configure_file("${CMAKE_CURRENT_FUNCTION_LIST_DIR}/FetchContent/CMakeLists.cmake.in"
-                 "${ARG_SUBBUILD_DIR}/CMakeLists.txt")
-  execute_process(
-    COMMAND ${CMAKE_COMMAND} ${subCMakeOpts} .
-    RESULT_VARIABLE result
-    ${outputOptions}
-    WORKING_DIRECTORY "${ARG_SUBBUILD_DIR}"
-  )
-  if(result)
-    if(capturedOutput)
-      message("${capturedOutput}")
-    endif()
-    message(FATAL_ERROR "CMake step for ${contentName} failed: ${result}")
-  endif()
-  execute_process(
-    COMMAND ${CMAKE_COMMAND} --build .
-    RESULT_VARIABLE result
-    ${outputOptions}
-    WORKING_DIRECTORY "${ARG_SUBBUILD_DIR}"
+  include(ExternalProject)
+  set(argsQuoted)
+  foreach(__item IN LISTS ARG_UNPARSED_ARGUMENTS)
+    string(APPEND argsQuoted " [==[${__item}]==]")
+  endforeach()
+  cmake_language(EVAL CODE "
+    _ep_do_preconfigure_steps_now(${contentName}
+      ${argsQuoted}
+      QUIET                   ${quiet}
+      SOURCE_DIR              [==[${ARG_SOURCE_DIR}]==]
+      BINARY_DIR              [==[${ARG_BINARY_DIR}]==]
+      USES_TERMINAL_DOWNLOAD  YES
+      USES_TERMINAL_UPDATE    YES
+    )"
   )
-  if(result)
-    if(capturedOutput)
-      message("${capturedOutput}")
-    endif()
-    message(FATAL_ERROR "Build step for ${contentName} failed: ${result}")
-  endif()
 
 endfunction()
 

+ 0 - 27
Modules/FetchContent/CMakeLists.cmake.in

@@ -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@

+ 0 - 1
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)

+ 0 - 17
Tests/RunCMake/FetchContent/SameGenerator.cmake

@@ -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()