Merge topic 'windows-kernel-mode-driver'

54bbd00448 WindowsKernelModeDriver: Switch to appending to variables

Acked-by: Kitware Robot <kwrobot@kitware.com>
Acked-by: buildbot <buildbot@kitware.com>
Merge-request: !9941
diff --git a/.clang-tidy b/.clang-tidy
index 6d2edd4..68d0a8f 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -7,7 +7,6 @@
 -bugprone-implicit-widening-of-multiplication-result,\
 -bugprone-inc-dec-in-conditions,\
 -bugprone-macro-parentheses,\
--bugprone-misplaced-widening-cast,\
 -bugprone-multi-level-implicit-pointer-conversion,\
 -bugprone-narrowing-conversions,\
 -bugprone-switch-missing-default-case,\
@@ -36,11 +35,8 @@
 performance-*,\
 -performance-avoid-endl,\
 -performance-enum-size,\
--performance-inefficient-vector-operation,\
--performance-noexcept-swap,\
 readability-*,\
 -readability-avoid-nested-conditional-operator,\
--readability-avoid-return-with-void-value,\
 -readability-avoid-unconditional-preprocessor-if,\
 -readability-convert-member-functions-to-static,\
 -readability-function-cognitive-complexity,\
@@ -52,11 +48,6 @@
 -readability-magic-numbers,\
 -readability-make-member-function-const,\
 -readability-named-parameter,\
--readability-redundant-casting,\
--readability-redundant-declaration,\
--readability-redundant-inline-specifier,\
--readability-redundant-member-init,\
--readability-reference-to-constructed-temporary,\
 -readability-simplify-boolean-expr,\
 -readability-static-accessed-through-instance,\
 -readability-suspicious-call-argument,\
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b352b06..43bb7cc 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -747,6 +747,13 @@
         CMAKE_CI_BUILD_NAME: oneapi2024.1.0_makefiles
         CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2024.1.0-el8
 
+t:oneapi2024.2.0-makefiles:
+    extends:
+        - .cmake_test_linux_inteloneapi_makefiles
+    variables:
+        CMAKE_CI_BUILD_NAME: oneapi2024.2.0_makefiles
+        CMAKE_CI_INTELCOMPILER_IMAGE_TAG: 2024.2.0-rocky9
+
 b:linux-x86_64-package:
     extends:
         - .linux_package
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 79a562c..ae1359e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,7 +1,7 @@
 # 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.13...3.29 FATAL_ERROR)
+cmake_minimum_required(VERSION 3.13...3.30 FATAL_ERROR)
 set(CMAKE_USER_MAKE_RULES_OVERRIDE_C ${CMAKE_CURRENT_SOURCE_DIR}/Source/Modules/OverrideC.cmake)
 set(CMAKE_USER_MAKE_RULES_OVERRIDE_CXX ${CMAKE_CURRENT_SOURCE_DIR}/Source/Modules/OverrideCXX.cmake)
 
diff --git a/Help/command/LINK_LIBRARIES_LINKER.txt b/Help/command/LINK_LIBRARIES_LINKER.txt
new file mode 100644
index 0000000..7dc6b84
--- /dev/null
+++ b/Help/command/LINK_LIBRARIES_LINKER.txt
@@ -0,0 +1,24 @@
+Handling Compiler Driver Differences
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. versionadded:: 3.32
+
+To pass options to the linker tool, each compiler driver has its own syntax.
+The ``LINKER:`` prefix and ``,`` separator can be used to specify, in a portable
+way, options to pass to the linker tool. ``LINKER:`` is replaced by the
+appropriate driver option and ``,`` by the appropriate driver separator.
+The driver prefix and driver separator are given by the values of the
+:variable:`CMAKE_<LANG>_LINKER_WRAPPER_FLAG` and
+:variable:`CMAKE_<LANG>_LINKER_WRAPPER_FLAG_SEP` variables.
+
+For example, ``"LINKER:-z,defs"`` becomes ``-Xlinker -z -Xlinker defs`` for
+``Clang`` and ``-Wl,-z,defs`` for ``GNU GCC``.
+
+The ``LINKER:`` prefix supports, as an alternative syntax, specification of
+arguments using the ``SHELL:`` prefix and space as separator. The previous
+example then becomes ``"LINKER:SHELL:-z defs"``.
+
+.. note::
+
+  Specifying the ``SHELL:`` prefix anywhere other than at the beginning of the
+  ``LINKER:`` prefix is not supported.
diff --git a/Help/command/ctest_run_script.rst b/Help/command/ctest_run_script.rst
index 145bd90..0d94eb0 100644
--- a/Help/command/ctest_run_script.rst
+++ b/Help/command/ctest_run_script.rst
@@ -9,7 +9,6 @@
               script_file_name2 ... [RETURN_VALUE var])
 
 Runs a script or scripts much like if it was run from :option:`ctest -S`.
-If no argument is provided then the current script is run using the current
-settings of the variables.  If ``NEW_PROCESS`` is specified then each
-script will be run in a separate process.If ``RETURN_VALUE`` is specified
-the return value of the last script run will be put into ``var``.
+If ``NEW_PROCESS`` is specified then each script will be run in a separate
+process.  If ``RETURN_VALUE`` is specified the return value of the last script
+run will be put into ``var``.
diff --git a/Help/command/target_link_libraries.rst b/Help/command/target_link_libraries.rst
index 94a2429..caa6441 100644
--- a/Help/command/target_link_libraries.rst
+++ b/Help/command/target_link_libraries.rst
@@ -148,6 +148,8 @@
 See the :manual:`cmake-buildsystem(7)` manual for more on defining
 buildsystem properties.
 
+.. include:: ../command/LINK_LIBRARIES_LINKER.txt
+
 Libraries for a Target and/or its Dependents
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
diff --git a/Help/dev/maint.rst b/Help/dev/maint.rst
index 10b3f7d..8098bb0 100644
--- a/Help/dev/maint.rst
+++ b/Help/dev/maint.rst
@@ -350,7 +350,7 @@
   away from setting policies to OLD.
 
 Update the ``cmake_policy`` version range generated by ``install(EXPORT)``
-in ``cmExportFileGenerator::GeneratePolicyHeaderCode`` and
+in ``cmExportCMakeConfigGenerator::GeneratePolicyHeaderCode`` and
 ``install_jar_exports`` in ``javaTargets.cmake.in`` to end at the
 previous release.  We use one release back since we now know all the
 policies added for that version.  Commit with a message such as::
diff --git a/Help/manual/cmake-properties.7.rst b/Help/manual/cmake-properties.7.rst
index 10dbe10..b310e89 100644
--- a/Help/manual/cmake-properties.7.rst
+++ b/Help/manual/cmake-properties.7.rst
@@ -415,6 +415,7 @@
    /prop_tgt/UNITY_BUILD_CODE_AFTER_INCLUDE
    /prop_tgt/UNITY_BUILD_CODE_BEFORE_INCLUDE
    /prop_tgt/UNITY_BUILD_MODE
+   /prop_tgt/UNITY_BUILD_RELOCATABLE
    /prop_tgt/UNITY_BUILD_UNIQUE_ID
    /prop_tgt/VERIFY_INTERFACE_HEADER_SETS
    /prop_tgt/VERSION
diff --git a/Help/manual/ctest.1.rst b/Help/manual/ctest.1.rst
index 9281339..683ac2f 100644
--- a/Help/manual/ctest.1.rst
+++ b/Help/manual/ctest.1.rst
@@ -421,11 +421,8 @@
 
 .. option:: --force-new-ctest-process
 
- Run child CTest instances as new processes.
-
- By default CTest will run child CTest instances within the same
- process.  If this behavior is not desired, this argument will
- enforce new processes for child CTest processes.
+ Ignored.  This option once disabled a now-removed optimization
+ for tests running ``ctest`` itself.
 
 .. option:: --schedule-random
 
diff --git a/Help/prop_tgt/INTERFACE_LINK_LIBRARIES.rst b/Help/prop_tgt/INTERFACE_LINK_LIBRARIES.rst
index 53f5838..73d273b 100644
--- a/Help/prop_tgt/INTERFACE_LINK_LIBRARIES.rst
+++ b/Help/prop_tgt/INTERFACE_LINK_LIBRARIES.rst
@@ -32,6 +32,8 @@
 :prop_tgt:`INTERFACE_LINK_LIBRARIES_DIRECT` and
 :prop_tgt:`INTERFACE_LINK_LIBRARIES_DIRECT_EXCLUDE` target properties.
 
+.. include:: ../command/LINK_LIBRARIES_LINKER.txt
+
 Creating Relocatable Packages
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
diff --git a/Help/prop_tgt/INTERFACE_LINK_OPTIONS.rst b/Help/prop_tgt/INTERFACE_LINK_OPTIONS.rst
index 785b17c..886adf2 100644
--- a/Help/prop_tgt/INTERFACE_LINK_OPTIONS.rst
+++ b/Help/prop_tgt/INTERFACE_LINK_OPTIONS.rst
@@ -8,3 +8,9 @@
 .. |PROPERTY_INTERFACE_NAME| replace:: ``INTERFACE_LINK_OPTIONS``
 .. |PROPERTY_LINK| replace:: :prop_tgt:`LINK_OPTIONS`
 .. include:: INTERFACE_BUILD_PROPERTY.txt
+
+.. include:: ../command/DEVICE_LINK_OPTIONS.txt
+
+.. include:: ../command/OPTIONS_SHELL.txt
+
+.. include:: ../command/LINK_OPTIONS_LINKER.txt
diff --git a/Help/prop_tgt/LINK_LIBRARIES.rst b/Help/prop_tgt/LINK_LIBRARIES.rst
index b449aa1..8814980 100644
--- a/Help/prop_tgt/LINK_LIBRARIES.rst
+++ b/Help/prop_tgt/LINK_LIBRARIES.rst
@@ -33,3 +33,5 @@
 corresponding :prop_tgt:`LINK_LIBRARIES_STRATEGY` target property
 for details on how CMake orders direct link dependencies on linker
 command lines.
+
+.. include:: ../command/LINK_LIBRARIES_LINKER.txt
diff --git a/Help/prop_tgt/UNITY_BUILD_RELOCATABLE.rst b/Help/prop_tgt/UNITY_BUILD_RELOCATABLE.rst
new file mode 100644
index 0000000..daac7fd
--- /dev/null
+++ b/Help/prop_tgt/UNITY_BUILD_RELOCATABLE.rst
@@ -0,0 +1,40 @@
+UNITY_BUILD_RELOCATABLE
+-----------------------
+
+.. versionadded:: 3.32
+
+By default, the unity file generated when :prop_tgt:`UNITY_BUILD` is enabled
+uses absolute paths to reference the original source files. This causes the
+unity file to result in a different output depending on the location of the
+source files.
+
+When this property is set to true, the ``#include`` lines inside the generated
+unity source files will attempt to use relative paths to the original source
+files if possible in order to standardize the output of the unity file.
+
+The unity file's path to the original source file will use the following
+priority:
+
+* relative path to the generated unity file if the source file exists
+  directly or in subfolder under the :variable:`CMAKE_BINARY_DIR`
+
+* relative path to :variable:`CMAKE_SOURCE_DIR` if the source file exists
+  directly or in subfolder under the :variable:`CMAKE_SOURCE_DIR`
+
+* absolute path to the source file.
+
+This target property *does not* guarantee a consistent unity file across
+different environments as the final priority is an absolute path.
+
+Example usage:
+
+.. code-block:: cmake
+
+  add_library(example_library
+              source1.cxx
+              source2.cxx
+              source3.cxx)
+
+  set_target_properties(example_library PROPERTIES
+                        UNITY_BUILD True
+                        UNITY_BUILD_RELOCATABLE TRUE)
diff --git a/Help/release/dev/0-sample-topic.rst b/Help/release/dev/0-sample-topic.rst
new file mode 100644
index 0000000..e4cc01e
--- /dev/null
+++ b/Help/release/dev/0-sample-topic.rst
@@ -0,0 +1,7 @@
+0-sample-topic
+--------------
+
+* This is a sample release note for the change in a topic.
+  Developers should add similar notes for each topic branch
+  making a noteworthy change.  Each document should be named
+  and titled to match the topic name to avoid merge conflicts.
diff --git a/Help/release/dev/FindProtobuf-protoc-exe-option.rst b/Help/release/dev/FindProtobuf-protoc-exe-option.rst
new file mode 100644
index 0000000..93d60cb
--- /dev/null
+++ b/Help/release/dev/FindProtobuf-protoc-exe-option.rst
@@ -0,0 +1,5 @@
+FindProtobuf-protoc-exe-option
+------------------------------
+
+* The :module:`FindProtobuf` module :command:`protobuf_generate` command
+  gained a ``PROTOC_EXE`` option to specify a custom ``protoc`` executable.
diff --git a/Help/release/dev/ctest-remove-declarative-script-mode.rst b/Help/release/dev/ctest-remove-declarative-script-mode.rst
new file mode 100644
index 0000000..c346abe
--- /dev/null
+++ b/Help/release/dev/ctest-remove-declarative-script-mode.rst
@@ -0,0 +1,14 @@
+ctest-remove-declarative-script-mode
+------------------------------------
+
+* CTest's declarative scripting mode has been removed.  This mode used to be
+  triggered by a :option:`ctest -S` script which did not call any
+  :ref:`CTest Commands` unless :variable:`CTEST_RUN_CURRENT_SCRIPT` was
+  explicitly set to ``OFF``.  This feature was undocumented and was not covered
+  by any unit tests.
+
+* The :variable:`CTEST_RUN_CURRENT_SCRIPT` variable no longer has any special
+  meaning.
+
+* The :command:`ctest_run_script` command may no longer be called without any
+  arguments.
diff --git a/Help/release/dev/target_link_libraries-LINKER-prefix.rst b/Help/release/dev/target_link_libraries-LINKER-prefix.rst
new file mode 100644
index 0000000..08f36af
--- /dev/null
+++ b/Help/release/dev/target_link_libraries-LINKER-prefix.rst
@@ -0,0 +1,5 @@
+target_link_libraries-LINKER-prefix
+-----------------------------------
+
+* The :command:`target_link_libraries` command gains the support of the
+  ``LINKER:`` prefix.
diff --git a/Help/release/index.rst b/Help/release/index.rst
index 40767b7..101dd68 100644
--- a/Help/release/index.rst
+++ b/Help/release/index.rst
@@ -7,6 +7,8 @@
   This file should include the adjacent "dev.txt" file
   in development versions but not in release versions.
 
+.. include:: dev.txt
+
 Releases
 ========
 
diff --git a/Help/variable/CMAKE_LINK_DEPENDS_USE_LINKER.rst b/Help/variable/CMAKE_LINK_DEPENDS_USE_LINKER.rst
index 1867ad8..1c07a23 100644
--- a/Help/variable/CMAKE_LINK_DEPENDS_USE_LINKER.rst
+++ b/Help/variable/CMAKE_LINK_DEPENDS_USE_LINKER.rst
@@ -13,8 +13,9 @@
 
 .. note::
 
-  CMake version |release| defaults this variable to ``FALSE`` because
-  GNU binutils linkers (``ld``, ``ld.bfd``, ``ld.gold``) generate spurious
+  CMake version |release| defaults this variable to ``FALSE`` if the linker is
+  one from the GNU binutils linkers (``ld`` and ``ld.bfd`` for version less
+  than 2.41 or ``ld.gold`` for any version) because it generate spurious
   dependencies on temporary files when LTO is enabled.  See `GNU bug 30568`_.
 
 .. _`GNU bug 30568`: https://sourceware.org/bugzilla/show_bug.cgi?id=30568
diff --git a/Help/variable/CTEST_RUN_CURRENT_SCRIPT.rst b/Help/variable/CTEST_RUN_CURRENT_SCRIPT.rst
index 8cb6eaa..616324e 100644
--- a/Help/variable/CTEST_RUN_CURRENT_SCRIPT.rst
+++ b/Help/variable/CTEST_RUN_CURRENT_SCRIPT.rst
@@ -1,7 +1,5 @@
 CTEST_RUN_CURRENT_SCRIPT
 ------------------------
 
-.. versionadded:: 3.11
-
-Setting this to 0 prevents :manual:`ctest(1)` from being run again when it
-reaches the end of a script run by calling :option:`ctest -S`.
+Removed.  This variable once supported an undocumented feature that has since
+been removed.
diff --git a/Modules/CMakeASMInformation.cmake b/Modules/CMakeASMInformation.cmake
index 2dc1585..7030034 100644
--- a/Modules/CMakeASMInformation.cmake
+++ b/Modules/CMakeASMInformation.cmake
@@ -96,5 +96,6 @@
   set(CMAKE_EXECUTABLE_RPATH_LINK_ASM${ASM_DIALECT}_FLAG ${CMAKE_SHARED_LIBRARY_RPATH_LINK_ASM${ASM_DIALECT}_FLAG})
 endif()
 
+set(CMAKE_ASM${ASM_DIALECT}_USE_LINKER_INFORMATION TRUE)
 
 set(CMAKE_ASM${ASM_DIALECT}_INFOMATION_LOADED 1)
diff --git a/Modules/CMakeAddNewLanguage.txt b/Modules/CMakeAddNewLanguage.txt
index b0be590..011195c 100644
--- a/Modules/CMakeAddNewLanguage.txt
+++ b/Modules/CMakeAddNewLanguage.txt
@@ -20,12 +20,20 @@
   This file is used to store compiler information and is copied down into try
   compile directories so that try compiles do not need to re-determine and test the LANG
 
-CMakeTest(LANG)Compiler.cmake -> test the compiler and set:
-  SET(CMAKE_(LANG)_COMPILER_WORKS 1 CACHE INTERNAL "")
-
-CMake(LANG)Information.cmake  -> set up rule variables for LANG :
+CMake(LANG)Information.cmake => set compiler configuration:
   CMAKE_(LANG)_CREATE_SHARED_LIBRARY
   CMAKE_(LANG)_CREATE_SHARED_MODULE
   CMAKE_(LANG)_CREATE_STATIC_LIBRARY
   CMAKE_(LANG)_COMPILE_OBJECT
   CMAKE_(LANG)_LINK_EXECUTABLE
+
+  CMAKE_(LANG)_USE_LINKER_INFORMATION
+
+CMakeTest(LANG)Compiler.cmake -> test the compiler and set:
+  SET(CMAKE_(LANG)_COMPILER_WORKS 1 CACHE INTERNAL "")
+
+
+If the variable CMAKE_(LANG)_USE_LINKER_INFORMATION has value TRUE, the file CMake(LANG)LinkerInformation.cmake
+should be defined.
+
+CMake(LANG)LinkerInformation.cmake  -> set up linker configuration for LANG.
diff --git a/Modules/CMakeCInformation.cmake b/Modules/CMakeCInformation.cmake
index 72fb801..977e51a 100644
--- a/Modules/CMakeCInformation.cmake
+++ b/Modules/CMakeCInformation.cmake
@@ -175,4 +175,6 @@
     "<CMAKE_C_COMPILER> <FLAGS> <CMAKE_C_LINK_FLAGS> <LINK_FLAGS> <OBJECTS> -o <TARGET> <LINK_LIBRARIES>")
 endif()
 
+set(CMAKE_C_USE_LINKER_INFORMATION TRUE)
+
 set(CMAKE_C_INFORMATION_LOADED 1)
diff --git a/Modules/CMakeCSharpInformation.cmake b/Modules/CMakeCSharpInformation.cmake
index 41cd449..f698bdf 100644
--- a/Modules/CMakeCSharpInformation.cmake
+++ b/Modules/CMakeCSharpInformation.cmake
@@ -64,4 +64,7 @@
 set(CMAKE_CSharp_LINK_EXECUTABLE "CSharp_NO_LINK_EXECUTABLE")
 
 set(CMAKE_CSharp_USE_RESPONSE_FILE_FOR_OBJECTS 1)
+
+set(CMAKE_CSharp_USE_LINKER_INFORMATION FALSE)
+
 set(CMAKE_CSharp_INFORMATION_LOADED 1)
diff --git a/Modules/CMakeCUDAInformation.cmake b/Modules/CMakeCUDAInformation.cmake
index 66a5faa..fa197b2 100644
--- a/Modules/CMakeCUDAInformation.cmake
+++ b/Modules/CMakeCUDAInformation.cmake
@@ -150,4 +150,6 @@
 
 unset(__IMPLICIT_DLINK_FLAGS)
 
+set(CMAKE_CUDA_USE_LINKER_INFORMATION TRUE)
+
 set(CMAKE_CUDA_INFORMATION_LOADED 1)
diff --git a/Modules/CMakeCXXInformation.cmake b/Modules/CMakeCXXInformation.cmake
index e521fb8..cc130ae 100644
--- a/Modules/CMakeCXXInformation.cmake
+++ b/Modules/CMakeCXXInformation.cmake
@@ -183,4 +183,6 @@
 CMAKE_VERBOSE_MAKEFILE
 )
 
+set(CMAKE_CXX_USE_LINKER_INFORMATION TRUE)
+
 set(CMAKE_CXX_INFORMATION_LOADED 1)
diff --git a/Modules/CMakeFortranInformation.cmake b/Modules/CMakeFortranInformation.cmake
index 984a39d..a376e10 100644
--- a/Modules/CMakeFortranInformation.cmake
+++ b/Modules/CMakeFortranInformation.cmake
@@ -129,5 +129,7 @@
   mark_as_advanced(CMAKE_Fortran_STANDARD_LIBRARIES)
 endif()
 
+set(CMAKE_Fortran_USE_LINKER_INFORMATION TRUE)
+
 # set this variable so we can avoid loading this more than once.
 set(CMAKE_Fortran_INFORMATION_LOADED 1)
diff --git a/Modules/CMakeHIPInformation.cmake b/Modules/CMakeHIPInformation.cmake
index dc76c63..c013df8 100644
--- a/Modules/CMakeHIPInformation.cmake
+++ b/Modules/CMakeHIPInformation.cmake
@@ -98,6 +98,8 @@
     "<CMAKE_HIP_COMPILER> <FLAGS> <CMAKE_HIP_LINK_FLAGS> <LINK_FLAGS> <OBJECTS> -o <TARGET> <LINK_LIBRARIES>")
 endif()
 
+set(CMAKE_HIP_USE_LINKER_INFORMATION TRUE)
+
 set(CMAKE_HIP_INFORMATION_LOADED 1)
 
 # Load the file and find the relevant HIP runtime.
diff --git a/Modules/CMakeISPCInformation.cmake b/Modules/CMakeISPCInformation.cmake
index 5acb682..4a4b72a 100644
--- a/Modules/CMakeISPCInformation.cmake
+++ b/Modules/CMakeISPCInformation.cmake
@@ -62,4 +62,6 @@
     "<CMAKE_ISPC_COMPILER> <DEFINES> <INCLUDES> <FLAGS> -o <OBJECT> --emit-obj <SOURCE> -h <ISPC_HEADER>")
 endif()
 
+set(CMAKE_ISPC_USE_LINKER_INFORMATION FALSE)
+
 set(CMAKE_ISPC_INFORMATION_LOADED 1)
diff --git a/Modules/CMakeJavaInformation.cmake b/Modules/CMakeJavaInformation.cmake
index 989afc1..0ca8395 100644
--- a/Modules/CMakeJavaInformation.cmake
+++ b/Modules/CMakeJavaInformation.cmake
@@ -47,3 +47,5 @@
 else()
   set(CMAKE_INCLUDE_FLAG_SEP_Java ":")
 endif()
+
+set(CMAKE_Java_USE_LINKER_INFORMATION FALSE)
diff --git a/Modules/CMakeOBJCInformation.cmake b/Modules/CMakeOBJCInformation.cmake
index bcb8b11..56e8239 100644
--- a/Modules/CMakeOBJCInformation.cmake
+++ b/Modules/CMakeOBJCInformation.cmake
@@ -187,4 +187,6 @@
   set(CMAKE_EXECUTABLE_RPATH_LINK_OBJC_FLAG ${CMAKE_SHARED_LIBRARY_RPATH_LINK_OBJC_FLAG})
 endif()
 
+set(CMAKE_OBJC_USE_LINKER_INFORMATION TRUE)
+
 set(CMAKE_OBJC_INFORMATION_LOADED 1)
diff --git a/Modules/CMakeOBJCXXInformation.cmake b/Modules/CMakeOBJCXXInformation.cmake
index 4fcd469..a242dab 100644
--- a/Modules/CMakeOBJCXXInformation.cmake
+++ b/Modules/CMakeOBJCXXInformation.cmake
@@ -179,4 +179,6 @@
 CMAKE_VERBOSE_MAKEFILE
 )
 
+set(CMAKE_OBJCXX_USE_LINKER_INFORMATION TRUE)
+
 set(CMAKE_OBJCXX_INFORMATION_LOADED 1)
diff --git a/Modules/CMakeParseImplicitLinkInfo.cmake b/Modules/CMakeParseImplicitLinkInfo.cmake
index 87c9e8a..d9ecbd8 100644
--- a/Modules/CMakeParseImplicitLinkInfo.cmake
+++ b/Modules/CMakeParseImplicitLinkInfo.cmake
@@ -49,20 +49,21 @@
   set(multiValueArgs )
   cmake_parse_arguments(EXTRA_PARSE "${keywordArgs}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
 
-  set(is_lfortran 0)
+  set(is_lfortran_less_0_40 0)
   set(is_msvc 0)
   if(EXTRA_PARSE_LANGUAGE)
     if("x${CMAKE_${EXTRA_PARSE_LANGUAGE}_COMPILER_ID}" STREQUAL "xMSVC" OR
         "x${CMAKE_${EXTRA_PARSE_LANGUAGE}_SIMULATE_ID}" STREQUAL "xMSVC")
       set(is_msvc 1)
-    elseif("x${CMAKE_${EXTRA_PARSE_LANGUAGE}_COMPILER_ID}" STREQUAL "xLFortran")
-      set(is_lfortran 1)
+    elseif("x${CMAKE_${EXTRA_PARSE_LANGUAGE}_COMPILER_ID}" STREQUAL "xLFortran"
+        AND CMAKE_${EXTRA_PARSE_LANGUAGE}_COMPILER_VERSION VERSION_LESS "0.40")
+      set(is_lfortran_less_0_40 1)
     endif()
   endif()
   # Parse implicit linker arguments.
   set(linker "ld[0-9]*(\\.[a-z]+)?")
-  if(is_lfortran)
-    # FIXME(lfortran): No way to pass -v to clang/gcc driver.
+  if(is_lfortran_less_0_40)
+    # lfortran < 0.40 has no way to pass -v to clang/gcc driver.
     string(APPEND linker "|clang|gcc")
   endif()
   if(is_msvc)
@@ -85,8 +86,8 @@
   set(linker_exclude_regex "collect2 version |^[A-Za-z0-9_]+=|/ldfe ")
   set(linker_tool_regex "^[ \t]*(->|\")?[ \t]*(([^\"]*[/\\])?(${linker}))(\"|,| |$)")
   set(linker_tool_exclude_regex "cuda-fake-ld|-fuse-ld=|^ExecuteExternalTool ")
-  if(is_lfortran)
-    # FIXME(lfortran): No way to pass -v to clang/gcc driver.
+  if(is_lfortran_less_0_40)
+    # lfortran < 0.40 has no way to pass -v to clang/gcc driver.
     string(APPEND linker_tool_exclude_regex "|^clang |^gcc ")
   endif()
   set(linker_tool "NOTFOUND")
diff --git a/Modules/CMakeRCInformation.cmake b/Modules/CMakeRCInformation.cmake
index b634796..d716469 100644
--- a/Modules/CMakeRCInformation.cmake
+++ b/Modules/CMakeRCInformation.cmake
@@ -46,5 +46,7 @@
     "<CMAKE_RC_COMPILER> <DEFINES> <INCLUDES> <FLAGS> /fo <OBJECT> <SOURCE>")
 endif()
 
+set(CMAKE_RC_USE_LINKER_INFORMATION FALSE)
+
 # set this variable so we can avoid loading this more than once.
 set(CMAKE_RC_INFORMATION_LOADED 1)
diff --git a/Modules/CMakeSwiftInformation.cmake b/Modules/CMakeSwiftInformation.cmake
index a46d298..904a528 100644
--- a/Modules/CMakeSwiftInformation.cmake
+++ b/Modules/CMakeSwiftInformation.cmake
@@ -172,4 +172,6 @@
   endif()
 endif()
 
+set(CMAKE_Swift_USE_LINKER_INFORMATION TRUE)
+
 set(CMAKE_Swift_INFORMATION_LOADED 1)
diff --git a/Modules/Compiler/LFortran-Fortran.cmake b/Modules/Compiler/LFortran-Fortran.cmake
index 4180730..9090e1d 100644
--- a/Modules/Compiler/LFortran-Fortran.cmake
+++ b/Modules/Compiler/LFortran-Fortran.cmake
@@ -3,7 +3,7 @@
 string(APPEND CMAKE_Fortran_FLAGS_RELEASE_INIT " -O3")
 string(APPEND CMAKE_Fortran_FLAGS_RELWITHDEBINFO_INIT " -O2 -g")
 set(CMAKE_Fortran_MODDIR_FLAG "-J")
-set(CMAKE_Fortran_VERBOSE_FLAG "-v")
+set(CMAKE_Fortran_VERBOSE_FLAG "-v -Wl,-v")
 set(CMAKE_Fortran_FORMAT_FIXED_FLAG "--fixed-form")
 set(CMAKE_Fortran_LINKER_WRAPPER_FLAG "-Wl,")
 set(CMAKE_Fortran_COMPILE_OPTIONS_PREPROCESS_ON "--cpp")
diff --git a/Modules/FindBISON.cmake b/Modules/FindBISON.cmake
index 3515bf0..6d0ace7 100644
--- a/Modules/FindBISON.cmake
+++ b/Modules/FindBISON.cmake
@@ -281,7 +281,8 @@
         VERBATIM
         DEPENDS ${_BisonInput}
         COMMENT "[BISON][${Name}] Building parser with bison ${BISON_VERSION}"
-        WORKING_DIRECTORY ${_BISON_WORKING_DIRECTORY})
+        WORKING_DIRECTORY ${_BISON_WORKING_DIRECTORY}
+        COMMAND_EXPAND_LISTS)
 
       unset(_BISON_WORKING_DIRECTORY)
 
diff --git a/Modules/FindCURL.cmake b/Modules/FindCURL.cmake
index 5ce8a90..f736130 100644
--- a/Modules/FindCURL.cmake
+++ b/Modules/FindCURL.cmake
@@ -239,9 +239,24 @@
         IMPORTED_LOCATION_DEBUG "${CURL_LIBRARY_DEBUG}")
     endif()
 
-    if(CURL_USE_STATIC_LIBS AND MSVC)
-       set_target_properties(CURL::libcurl PROPERTIES
-           INTERFACE_LINK_LIBRARIES "normaliz.lib;ws2_32.lib;wldap32.lib")
+    if(PC_CURL_FOUND)
+      if(PC_CURL_LINK_LIBRARIES)
+        set_property(TARGET CURL::libcurl PROPERTY
+                     INTERFACE_LINK_LIBRARIES "${PC_CURL_LINK_LIBRARIES}")
+      endif()
+      if(PC_CURL_LDFLAGS_OTHER)
+        set_property(TARGET CURL::libcurl PROPERTY
+                     INTERFACE_LINK_OPTIONS "${PC_CURL_LDFLAGS_OTHER}")
+      endif()
+      if(PC_CURL_CFLAGS_OTHER)
+        set_property(TARGET CURL::libcurl PROPERTY
+                     INTERFACE_COMPILE_OPTIONS "${PC_CURL_CFLAGS_OTHER}")
+      endif()
+    else()
+      if(CURL_USE_STATIC_LIBS AND MSVC)
+         set_target_properties(CURL::libcurl PROPERTIES
+             INTERFACE_LINK_LIBRARIES "normaliz.lib;ws2_32.lib;wldap32.lib")
+      endif()
     endif()
 
   endif()
diff --git a/Modules/FindProtobuf.cmake b/Modules/FindProtobuf.cmake
index 197e71f..1d69c57 100644
--- a/Modules/FindProtobuf.cmake
+++ b/Modules/FindProtobuf.cmake
@@ -108,20 +108,27 @@
 
   Add custom commands to process ``.proto`` files to C++::
 
-    protobuf_generate_cpp (<SRCS> <HDRS>
-        [DESCRIPTORS <DESC>] [EXPORT_MACRO <MACRO>] [<ARGN>...])
+    protobuf_generate_cpp (
+      <srcs-var> <hdrs-var>
+      [DESCRIPTORS <var>]
+      [EXPORT_MACRO <macro>]
+      [<proto-file>...])
 
-  ``SRCS``
+  ``<srcs-var>``
     Variable to define with autogenerated source files
-  ``HDRS``
+
+  ``<hdrs-var>``
     Variable to define with autogenerated header files
-  ``DESCRIPTORS``
+
+  ``DESCRIPTORS <var>``
     .. versionadded:: 3.10
       Variable to define with autogenerated descriptor files, if requested.
-  ``EXPORT_MACRO``
+
+  ``EXPORT_MACRO <macro>``
     is a macro which should expand to ``__declspec(dllexport)`` or
     ``__declspec(dllimport)`` depending on what is being compiled.
-  ``ARGN``
+
+  ``<proto-file>...``
     ``.proto`` files
 
 .. command:: protobuf_generate_python
@@ -130,11 +137,12 @@
 
   Add custom commands to process ``.proto`` files to Python::
 
-    protobuf_generate_python (<PY> [<ARGN>...])
+    protobuf_generate_python (<py-srcs-var> [<proto-file>...])
 
-  ``PY``
+  ``<py-srcs-var>``
     Variable to define with autogenerated Python files
-  ``ARGN``
+
+  ``<proto-file>...``
     ``.proto`` files
 
 .. command:: protobuf_generate
@@ -146,63 +154,82 @@
     protobuf_generate (
         TARGET <target>
         [LANGUAGE <lang>]
-        [OUT_VAR <out_var>]
+        [OUT_VAR <var>]
         [EXPORT_MACRO <macro>]
         [PROTOC_OUT_DIR <dir>]
         [PLUGIN <plugin>]
-        [PLUGIN_OPTIONS <plugin_options>]
-        [DEPENDENCIES <depends]
-        [PROTOS <protobuf_files>]
-        [IMPORT_DIRS <dirs>]
-        [GENERATE_EXTENSIONS <extensions>]
-        [PROTOC_OPTIONS <protoc_options>]
+        [PLUGIN_OPTIONS <plugin-options>]
+        [DEPENDENCIES <dependencies>]
+        [PROTOS <proto-file>...]
+        [IMPORT_DIRS <dir>...]
+        [GENERATE_EXTENSIONS <extension>...]
+        [PROTOC_OPTIONS <option>...]
+        [PROTOC_EXE <executable>]
         [APPEND_PATH])
 
   ``APPEND_PATH``
     A flag that causes the base path of all proto schema files to be added to
     ``IMPORT_DIRS``.
-  ``LANGUAGE``
+
+  ``LANGUAGE <lang>``
     A single value: cpp or python. Determines what kind of source files are
     being generated. Defaults to cpp.
-  ``OUT_VAR``
+
+  ``OUT_VAR <var>``
     Name of a CMake variable that will be filled with the paths to the generated
     source files.
-  ``EXPORT_MACRO``
+
+  ``EXPORT_MACRO <macro>``
     Name of a macro that is applied to all generated Protobuf message classes
     and extern variables. It can, for example, be used to declare DLL exports.
-  ``PROTOC_OUT_DIR``
+
+  ``PROTOC_OUT_DIR <dir>``
     Output directory of generated source files. Defaults to ``CMAKE_CURRENT_BINARY_DIR``.
-  ``PLUGIN``
+
+  ``PLUGIN <plugin>``
     .. versionadded:: 3.21
 
     An optional plugin executable. This could, for example, be the path to
     ``grpc_cpp_plugin``.
-  ``PLUGIN_OPTIONS``
+
+  ``PLUGIN_OPTIONS <plugin-options>``
     .. versionadded:: 3.28
 
     Additional options provided to the plugin, such as ``generate_mock_code=true``
     for the gRPC cpp plugin.
-  ``DEPENDENCIES``
+
+  ``DEPENDENCIES <dependencies>``
     .. versionadded:: 3.28
 
     Arguments forwarded to the ``DEPENDS`` of the underlying ``add_custom_command``
     invocation.
-  ``TARGET``
+
+  ``TARGET <target>``
     CMake target that will have the generated files added as sources.
-  ``PROTOS``
+
+  ``PROTOS <proto-file>...``
     List of proto schema files. If omitted, then every source file ending in *proto* of ``TARGET`` will be used.
-  ``IMPORT_DIRS``
+
+  ``IMPORT_DIRS <dir>...``
     A common parent directory for the schema files. For example, if the schema file is
     ``proto/helloworld/helloworld.proto`` and the import directory ``proto/`` then the
     generated files are ``${PROTOC_OUT_DIR}/helloworld/helloworld.pb.h`` and
     ``${PROTOC_OUT_DIR}/helloworld/helloworld.pb.cc``.
-  ``GENERATE_EXTENSIONS``
+
+  ``GENERATE_EXTENSIONS <extension>...``
     If LANGUAGE is omitted then this must be set to the extensions that protoc generates.
-  ``PROTOC_OPTIONS``
+
+  ``PROTOC_OPTIONS <option>...``
     .. versionadded:: 3.28
 
     Additional arguments that are forwarded to protoc.
 
+  ``PROTOC_EXE <executable>``
+    .. versionadded:: 3.32
+
+    Command name, path, or CMake executable used to generate protobuf bindings.
+    If omitted, ``protobuf::protoc`` is used.
+
   Example::
 
     find_package(gRPC CONFIG REQUIRED)
@@ -223,7 +250,7 @@
 
 function(protobuf_generate)
   set(_options APPEND_PATH DESCRIPTORS)
-  set(_singleargs LANGUAGE OUT_VAR EXPORT_MACRO PROTOC_OUT_DIR PLUGIN PLUGIN_OPTIONS DEPENDENCIES)
+  set(_singleargs LANGUAGE OUT_VAR EXPORT_MACRO PROTOC_OUT_DIR PLUGIN PLUGIN_OPTIONS DEPENDENCIES PROTOC_EXE)
   if(COMMAND target_sources)
     list(APPEND _singleargs TARGET)
   endif()
@@ -292,10 +319,14 @@
     return()
   endif()
 
-  if(NOT TARGET protobuf::protoc)
-    message(SEND_ERROR "protoc executable not found. "
-            "Please define the Protobuf_PROTOC_EXECUTABLE variable or ensure that protoc is in CMake's search path.")
-    return()
+  if(NOT protobuf_generate_PROTOC_EXE)
+    if(NOT TARGET protobuf::protoc)
+      message(SEND_ERROR "protoc executable not found. "
+        "Please define the Protobuf_PROTOC_EXECUTABLE variable, or pass PROTOC_EXE to protobuf_generate, or ensure that protoc is in CMake's search path.")
+      return()
+    endif()
+    # Default to using the CMake executable
+    set(protobuf_generate_PROTOC_EXE protobuf::protoc)
   endif()
 
   if(protobuf_generate_APPEND_PATH)
@@ -368,7 +399,7 @@
 
     add_custom_command(
       OUTPUT ${_generated_srcs}
-      COMMAND protobuf::protoc
+      COMMAND ${protobuf_generate_PROTOC_EXE}
       ARGS ${protobuf_generate_PROTOC_OPTIONS} --${protobuf_generate_LANGUAGE}_out ${_plugin_options}:${protobuf_generate_PROTOC_OUT_DIR} ${_plugin} ${_dll_desc_out} ${_protobuf_include_path} ${_abs_file}
       DEPENDS ${_abs_file} protobuf::protoc ${protobuf_generate_DEPENDENCIES}
       COMMENT ${_comment}
diff --git a/Modules/Internal/CMakeCSharpLinkerInformation.cmake b/Modules/Internal/CMakeCSharpLinkerInformation.cmake
deleted file mode 100644
index ff73a37..0000000
--- a/Modules/Internal/CMakeCSharpLinkerInformation.cmake
+++ /dev/null
@@ -1,8 +0,0 @@
-# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-# file Copyright.txt or https://cmake.org/licensing for details.
-
-
-# This file sets the basic flags for the linker used by the CSharp compiler in CMake.
-# For now, nothing to define
-
-set(CMAKE_CSharp_LINKER_INFORMATION_LOADED 1)
diff --git a/Modules/Internal/CMakeISPCLinkerInformation.cmake b/Modules/Internal/CMakeISPCLinkerInformation.cmake
deleted file mode 100644
index 47950aa..0000000
--- a/Modules/Internal/CMakeISPCLinkerInformation.cmake
+++ /dev/null
@@ -1,8 +0,0 @@
-# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-# file Copyright.txt or https://cmake.org/licensing for details.
-
-
-# This file sets the basic flags for the linker used by the ISPC compiler in CMake.
-# For now, ISPC is not able to handle the link step
-
-set(CMAKE_ISPC_LINKER_INFORMATION_LOADED 1)
diff --git a/Modules/Internal/CMakeJavaLinkerInformation.cmake b/Modules/Internal/CMakeJavaLinkerInformation.cmake
deleted file mode 100644
index c0edadd..0000000
--- a/Modules/Internal/CMakeJavaLinkerInformation.cmake
+++ /dev/null
@@ -1,5 +0,0 @@
-# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-# file Copyright.txt or https://cmake.org/licensing for details.
-
-
-# Java language does not have a concept of linker, so nothing to configure.
diff --git a/Modules/Internal/CMakeRCLinkerInformation.cmake b/Modules/Internal/CMakeRCLinkerInformation.cmake
deleted file mode 100644
index 1b0563e..0000000
--- a/Modules/Internal/CMakeRCLinkerInformation.cmake
+++ /dev/null
@@ -1,17 +0,0 @@
-# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-# file Copyright.txt or https://cmake.org/licensing for details.
-
-
-# This file sets the basic flags for the linker used by the C compiler in CMake.
-# It also loads the available platform file for the system-linker
-# if it exists.
-# It also loads a system - linker - processor (or target hardware)
-# specific file, which is mainly useful for crosscompiling and embedded systems.
-
-include(Internal/CMakeCommonLinkerInformation)
-
-set(_INCLUDED_FILE 0)
-
-# Foe now, no linker associated with RC language
-
-set(CMAKE_RC_LINKER_INFORMATION_LOADED 1)
diff --git a/Modules/UseJava/javaTargets.cmake.in b/Modules/UseJava/javaTargets.cmake.in
index 6002d4e..11f1f06 100644
--- a/Modules/UseJava/javaTargets.cmake.in
+++ b/Modules/UseJava/javaTargets.cmake.in
@@ -1,5 +1,5 @@
 cmake_policy(PUSH)
-cmake_policy(VERSION 2.8.12...3.29)
+cmake_policy(VERSION 2.8.12...3.30)
 
 #----------------------------------------------------------------
 # Generated CMake Java target import file.
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index c4cd101..6c896c1 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -1071,7 +1071,7 @@
   cmCTest.cxx
   CTest/cmProcess.cxx
   CTest/cmCTestBinPacker.cxx
-  CTest/cmCTestBuildAndTestHandler.cxx
+  CTest/cmCTestBuildAndTest.cxx
   CTest/cmCTestBuildCommand.cxx
   CTest/cmCTestBuildHandler.cxx
   CTest/cmCTestConfigureCommand.cxx
diff --git a/Source/CMakeVersion.cmake b/Source/CMakeVersion.cmake
index ce004af..b1279bc 100644
--- a/Source/CMakeVersion.cmake
+++ b/Source/CMakeVersion.cmake
@@ -1,8 +1,8 @@
 # CMake version number components.
 set(CMake_VERSION_MAJOR 3)
 set(CMake_VERSION_MINOR 31)
-set(CMake_VERSION_PATCH 0)
-set(CMake_VERSION_RC 2)
+set(CMake_VERSION_PATCH 20241025)
+#set(CMake_VERSION_RC 0)
 set(CMake_VERSION_IS_DIRTY 0)
 
 # Start with the full version number used in tags.  It has no dev info.
diff --git a/Source/CPack/cmCPackArchiveGenerator.cxx b/Source/CPack/cmCPackArchiveGenerator.cxx
index c70a2f1..e5b9297 100644
--- a/Source/CPack/cmCPackArchiveGenerator.cxx
+++ b/Source/CPack/cmCPackArchiveGenerator.cxx
@@ -2,7 +2,6 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmCPackArchiveGenerator.h"
 
-#include <cstring>
 #include <map>
 #include <ostream>
 #include <unordered_map>
@@ -238,10 +237,7 @@
   // Change to local toplevel
   cmWorkingDirectory workdir(localToplevel);
   if (workdir.Failed()) {
-    cmCPackLogger(cmCPackLog::LOG_ERROR,
-                  "Failed to change working directory to "
-                    << localToplevel << " : "
-                    << std::strerror(workdir.GetLastResult()) << std::endl);
+    cmCPackLogger(cmCPackLog::LOG_ERROR, workdir.GetError() << std::endl);
     return 0;
   }
   std::string filePrefix;
@@ -448,10 +444,7 @@
   DECLARE_AND_OPEN_ARCHIVE(packageFileNames[0], archive);
   cmWorkingDirectory workdir(this->toplevel);
   if (workdir.Failed()) {
-    cmCPackLogger(cmCPackLog::LOG_ERROR,
-                  "Failed to change working directory to "
-                    << this->toplevel << " : "
-                    << std::strerror(workdir.GetLastResult()) << std::endl);
+    cmCPackLogger(cmCPackLog::LOG_ERROR, workdir.GetError() << std::endl);
     return 0;
   }
   for (std::string const& file : this->files) {
diff --git a/Source/CPack/cmCPackGenerator.cxx b/Source/CPack/cmCPackGenerator.cxx
index 44d0ece..d5c8cc8 100644
--- a/Source/CPack/cmCPackGenerator.cxx
+++ b/Source/CPack/cmCPackGenerator.cxx
@@ -3,7 +3,6 @@
 #include "cmCPackGenerator.h"
 
 #include <algorithm>
-#include <cstring>
 #include <memory>
 #include <utility>
 
@@ -52,10 +51,9 @@
 }
 
 void cmCPackGenerator::DisplayVerboseOutput(const std::string& msg,
-                                            float progress)
+                                            float /*unused*/)
 {
-  (void)progress;
-  cmCPackLogger(cmCPackLog::LOG_VERBOSE, "" << msg << std::endl);
+  cmCPackLogger(cmCPackLog::LOG_VERBOSE, msg << std::endl);
 }
 
 int cmCPackGenerator::PrepareNames()
@@ -434,10 +432,7 @@
         cmWorkingDirectory workdir(goToDir);
         if (workdir.Failed()) {
           cmCPackLogger(cmCPackLog::LOG_ERROR,
-                        "Failed to change working directory to "
-                          << goToDir << " : "
-                          << std::strerror(workdir.GetLastResult())
-                          << std::endl);
+                        workdir.GetError() << std::endl);
           return 0;
         }
         for (auto const& symlinked : symlinkedFiles) {
diff --git a/Source/CPack/cpack.cxx b/Source/CPack/cpack.cxx
index 305ac56..68e759d 100644
--- a/Source/CPack/cpack.cxx
+++ b/Source/CPack/cpack.cxx
@@ -208,6 +208,7 @@
     CommandArgument{ "--list-presets", CommandArgument::Values::Zero,
                      CommandArgument::setToTrue(listPresets) },
     CommandArgument{ "-D", CommandArgument::Values::One,
+                     CommandArgument::RequiresSeparator::No,
                      [&log, &definitions](const std::string& arg, cmake*,
                                           cmMakefile*) -> bool {
                        std::string value = arg;
@@ -405,7 +406,7 @@
 
   bool cpackConfigFileSpecified = true;
   if (!cpackConfigFile.empty()) {
-    cpackConfigFile = cmSystemTools::CollapseFullPath(cpackConfigFile);
+    cpackConfigFile = cmSystemTools::ToNormalizedPathOnDisk(cpackConfigFile);
   } else {
     cpackConfigFile = cmStrCat(cmSystemTools::GetCurrentWorkingDirectory(),
                                "/CPackConfig.cmake");
@@ -479,7 +480,7 @@
     if (!cpackProjectDirectory.empty()) {
       // The value has been set on the command line.  Ensure it is absolute.
       cpackProjectDirectory =
-        cmSystemTools::CollapseFullPath(cpackProjectDirectory);
+        cmSystemTools::ToNormalizedPathOnDisk(cpackProjectDirectory);
     } else {
       // The value has not been set on the command line.  Check config file.
       if (cmValue pd = globalMF.GetDefinition("CPACK_PACKAGE_DIRECTORY")) {
diff --git a/Source/CTest/cmCTestBuildAndTest.cxx b/Source/CTest/cmCTestBuildAndTest.cxx
new file mode 100644
index 0000000..a142445
--- /dev/null
+++ b/Source/CTest/cmCTestBuildAndTest.cxx
@@ -0,0 +1,341 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmCTestBuildAndTest.h"
+
+#include <chrono>
+#include <cstdint>
+#include <iostream>
+#include <ratio>
+#include <utility>
+
+#include <cm3p/uv.h>
+
+#include "cmBuildOptions.h"
+#include "cmCTest.h"
+#include "cmCTestTestHandler.h"
+#include "cmGlobalGenerator.h"
+#include "cmMakefile.h"
+#include "cmProcessOutput.h"
+#include "cmState.h"
+#include "cmStringAlgorithms.h"
+#include "cmSystemTools.h"
+#include "cmUVHandlePtr.h"
+#include "cmUVProcessChain.h"
+#include "cmUVStream.h"
+#include "cmWorkingDirectory.h"
+#include "cmake.h"
+
+struct cmMessageMetadata;
+
+cmCTestBuildAndTest::cmCTestBuildAndTest(cmCTest* ctest)
+  : CTest(ctest)
+{
+}
+
+bool cmCTestBuildAndTest::RunCMake(cmake* cm)
+{
+  std::vector<std::string> args;
+  args.push_back(cmSystemTools::GetCMakeCommand());
+  args.push_back(this->SourceDir);
+  if (!this->BuildGenerator.empty()) {
+    args.push_back("-G" + this->BuildGenerator);
+  }
+  if (!this->BuildGeneratorPlatform.empty()) {
+    args.push_back("-A" + this->BuildGeneratorPlatform);
+  }
+  if (!this->BuildGeneratorToolset.empty()) {
+    args.push_back("-T" + this->BuildGeneratorToolset);
+  }
+
+  const char* config = nullptr;
+  if (!this->CTest->GetConfigType().empty()) {
+    config = this->CTest->GetConfigType().c_str();
+  }
+
+  if (config) {
+    args.push_back("-DCMAKE_BUILD_TYPE:STRING=" + std::string(config));
+  }
+  if (!this->BuildMakeProgram.empty() &&
+      (this->BuildGenerator.find("Make") != std::string::npos ||
+       this->BuildGenerator.find("Ninja") != std::string::npos)) {
+    args.push_back("-DCMAKE_MAKE_PROGRAM:FILEPATH=" + this->BuildMakeProgram);
+  }
+
+  for (std::string const& opt : this->BuildOptions) {
+    args.push_back(opt);
+  }
+  std::cout << "======== CMake output     ======\n";
+  if (cm->Run(args) != 0) {
+    std::cout << "======== End CMake output ======\n"
+                 "Error: cmake execution failed\n";
+    return false;
+  }
+  // do another config?
+  if (this->BuildTwoConfig) {
+    if (cm->Run(args) != 0) {
+      std::cout << "======== End CMake output ======\n"
+                   "Error: cmake execution failed\n";
+      return false;
+    }
+  }
+  std::cout << "======== End CMake output ======\n";
+  return true;
+}
+
+bool cmCTestBuildAndTest::RunTest(std::vector<std::string> const& argv,
+                                  int* retVal, cmDuration timeout)
+{
+  cmUVProcessChainBuilder builder;
+  builder.AddCommand(argv).SetMergedBuiltinStreams();
+  auto chain = builder.Start();
+
+  cmProcessOutput processOutput(cmProcessOutput::Auto);
+  cm::uv_pipe_ptr outputStream;
+  outputStream.init(chain.GetLoop(), 0);
+  uv_pipe_open(outputStream, chain.OutputStream());
+  auto outputHandle = cmUVStreamRead(
+    outputStream,
+    [&processOutput](std::vector<char> data) {
+      std::string decoded;
+      processOutput.DecodeText(data.data(), data.size(), decoded);
+      std::cout << decoded << std::flush;
+    },
+    []() {});
+
+  bool complete = chain.Wait(static_cast<uint64_t>(timeout.count() * 1000.0));
+
+  bool result = false;
+
+  if (complete) {
+    auto const& status = chain.GetStatus(0);
+    auto exception = status.GetException();
+    switch (exception.first) {
+      case cmUVProcessChain::ExceptionCode::None:
+        *retVal = static_cast<int>(status.ExitStatus);
+        result = true;
+        break;
+      case cmUVProcessChain::ExceptionCode::Spawn: {
+        std::cout << "\n*** ERROR executing: " << exception.second;
+      } break;
+      default: {
+        *retVal = status.TermSignal;
+        std::cout << "\n*** Exception executing: " << exception.second;
+      } break;
+    }
+  }
+
+  return result;
+}
+
+class cmCTestBuildAndTestCaptureRAII
+{
+  cmake& CM;
+
+public:
+  cmCTestBuildAndTestCaptureRAII(cmake& cm)
+    : CM(cm)
+  {
+    cmSystemTools::SetMessageCallback(
+      [](const std::string& msg, const cmMessageMetadata& /* unused */) {
+        std::cout << msg << std::endl;
+      });
+
+    cmSystemTools::SetStdoutCallback(
+      [](std::string const& m) { std::cout << m << std::flush; });
+    cmSystemTools::SetStderrCallback(
+      [](std::string const& m) { std::cout << m << std::flush; });
+
+    this->CM.SetProgressCallback([](const std::string& msg, float prog) {
+      if (prog < 0) {
+        std::cout << msg << std::endl;
+      }
+    });
+  }
+
+  ~cmCTestBuildAndTestCaptureRAII()
+  {
+    this->CM.SetProgressCallback(nullptr);
+    cmSystemTools::SetStderrCallback(nullptr);
+    cmSystemTools::SetStdoutCallback(nullptr);
+    cmSystemTools::SetMessageCallback(nullptr);
+  }
+
+  cmCTestBuildAndTestCaptureRAII(const cmCTestBuildAndTestCaptureRAII&) =
+    delete;
+  cmCTestBuildAndTestCaptureRAII& operator=(
+    const cmCTestBuildAndTestCaptureRAII&) = delete;
+};
+
+int cmCTestBuildAndTest::Run()
+{
+  // if the generator and make program are not specified then it is an error
+  if (this->BuildGenerator.empty()) {
+    std::cout << "--build-and-test requires that the generator "
+                 "be provided using the --build-generator "
+                 "command line option.\n";
+    return 1;
+  }
+
+  cmake cm(cmake::RoleProject, cmState::Project);
+  cm.SetHomeDirectory("");
+  cm.SetHomeOutputDirectory("");
+  cmCTestBuildAndTestCaptureRAII captureRAII(cm);
+  static_cast<void>(captureRAII);
+
+  if (this->CTest->GetConfigType().empty() && !this->ConfigSample.empty()) {
+    // use the config sample to set the ConfigType
+    std::string fullPath;
+    std::string resultingConfig;
+    std::vector<std::string> extraPaths;
+    std::vector<std::string> failed;
+    fullPath = cmCTestTestHandler::FindExecutable(
+      this->CTest, this->ConfigSample, resultingConfig, extraPaths, failed);
+    if (!fullPath.empty() && !resultingConfig.empty()) {
+      this->CTest->SetConfigType(resultingConfig);
+    }
+    std::cout << "Using config sample with results: " << fullPath << " and "
+              << resultingConfig << std::endl;
+  }
+
+  // we need to honor the timeout specified, the timeout include cmake, build
+  // and test time
+  auto clock_start = std::chrono::steady_clock::now();
+
+  // make sure the binary dir is there
+  std::cout << "Internal cmake changing into directory: " << this->BinaryDir
+            << std::endl;
+  if (!cmSystemTools::FileIsDirectory(this->BinaryDir)) {
+    cmSystemTools::MakeDirectory(this->BinaryDir);
+  }
+  cmWorkingDirectory workdir(this->BinaryDir);
+  if (workdir.Failed()) {
+    std::cout << workdir.GetError() << '\n';
+    return 1;
+  }
+
+  if (this->BuildNoCMake) {
+    // Make the generator available for the Build call below.
+    cm.SetGlobalGenerator(cm.CreateGlobalGenerator(this->BuildGenerator));
+    if (!this->BuildGeneratorPlatform.empty()) {
+      cmMakefile mf(cm.GetGlobalGenerator(), cm.GetCurrentSnapshot());
+      if (!cm.GetGlobalGenerator()->SetGeneratorPlatform(
+            this->BuildGeneratorPlatform, &mf)) {
+        return 1;
+      }
+    }
+
+    // Load the cache to make CMAKE_MAKE_PROGRAM available.
+    cm.LoadCache(this->BinaryDir);
+  } else {
+    // do the cmake step, no timeout here since it is not a sub process
+    if (!this->RunCMake(&cm)) {
+      return 1;
+    }
+  }
+
+  // do the build
+  if (this->BuildTargets.empty()) {
+    this->BuildTargets.emplace_back();
+  }
+  for (std::string const& tar : this->BuildTargets) {
+    cmDuration remainingTime = std::chrono::seconds(0);
+    if (this->Timeout > cmDuration::zero()) {
+      remainingTime =
+        this->Timeout - (std::chrono::steady_clock::now() - clock_start);
+      if (remainingTime <= std::chrono::seconds(0)) {
+        std::cout << "--build-and-test timeout exceeded. ";
+        return 1;
+      }
+    }
+    const char* config = nullptr;
+    if (!this->CTest->GetConfigType().empty()) {
+      config = this->CTest->GetConfigType().c_str();
+    }
+    if (!config) {
+      config = "Debug";
+    }
+
+    cmBuildOptions buildOptions(!this->BuildNoClean, false,
+                                PackageResolveMode::Disable);
+    int retVal = cm.GetGlobalGenerator()->Build(
+      cmake::NO_BUILD_PARALLEL_LEVEL, this->SourceDir, this->BinaryDir,
+      this->BuildProject, { tar }, std::cout, this->BuildMakeProgram, config,
+      buildOptions, false, remainingTime, cmSystemTools::OUTPUT_PASSTHROUGH);
+    // if the build failed then return
+    if (retVal) {
+      return 1;
+    }
+  }
+
+  // if no test was specified then we are done
+  if (this->TestCommand.empty()) {
+    return 0;
+  }
+
+  // now run the compiled test if we can find it
+  // store the final location in fullPath
+  std::string fullPath;
+  std::string resultingConfig;
+  std::vector<std::string> extraPaths;
+  // if this->ExecutableDirectory is set try that as well
+  if (!this->ExecutableDirectory.empty()) {
+    std::string tempPath =
+      cmStrCat(this->ExecutableDirectory, '/', this->TestCommand);
+    extraPaths.push_back(tempPath);
+  }
+  std::vector<std::string> failed;
+  fullPath = cmCTestTestHandler::FindExecutable(
+    this->CTest, this->TestCommand, resultingConfig, extraPaths, failed);
+
+  if (!cmSystemTools::FileExists(fullPath)) {
+    std::cout
+      << "Could not find path to executable, perhaps it was not built: "
+      << this->TestCommand << "\n"
+      << "tried to find it in these places:\n"
+      << fullPath << '\n';
+    for (std::string const& fail : failed) {
+      std::cout << fail << '\n';
+    }
+    return 1;
+  }
+
+  std::vector<std::string> testCommand;
+  testCommand.push_back(fullPath);
+  for (std::string const& testCommandArg : this->TestCommandArgs) {
+    testCommand.push_back(testCommandArg);
+  }
+  int retval = 0;
+  // run the test from the this->BuildRunDir if set
+  if (!this->BuildRunDir.empty()) {
+    std::cout << "Run test in directory: " << this->BuildRunDir << '\n';
+    if (!workdir.SetDirectory(this->BuildRunDir)) {
+      std::cout << workdir.GetError() << '\n';
+      return 1;
+    }
+  }
+  std::cout << "Running test command: \"" << fullPath << '"';
+  for (std::string const& testCommandArg : this->TestCommandArgs) {
+    std::cout << " \"" << testCommandArg << '"';
+  }
+  std::cout << '\n';
+
+  // how much time is remaining
+  cmDuration remainingTime = std::chrono::seconds(0);
+  if (this->Timeout > cmDuration::zero()) {
+    remainingTime =
+      this->Timeout - (std::chrono::steady_clock::now() - clock_start);
+    if (remainingTime <= std::chrono::seconds(0)) {
+      std::cout << "--build-and-test timeout exceeded. ";
+      return 1;
+    }
+  }
+
+  bool runTestRes = this->RunTest(testCommand, &retval, remainingTime);
+
+  if (!runTestRes || retval != 0) {
+    std::cout << "\nTest command failed: " << testCommand[0] << '\n';
+    retval = 1;
+  }
+
+  return retval;
+}
diff --git a/Source/CTest/cmCTestBuildAndTest.h b/Source/CTest/cmCTestBuildAndTest.h
new file mode 100644
index 0000000..f680bc2
--- /dev/null
+++ b/Source/CTest/cmCTestBuildAndTest.h
@@ -0,0 +1,56 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <string>
+#include <vector>
+
+#include "cmDuration.h"
+
+class cmake;
+class cmCTest;
+
+/** \class cmCTestBuildAndTest
+ * \brief A class that handles ctest -S invocations
+ *
+ */
+class cmCTestBuildAndTest
+{
+public:
+  /*
+   * The main entry point for this class
+   */
+  int Run();
+
+  cmCTestBuildAndTest(cmCTest* ctest);
+
+private:
+  cmCTest* CTest;
+
+  bool RunCMake(cmake* cm);
+  bool RunTest(std::vector<std::string> const& args, int* retVal,
+               cmDuration timeout);
+
+  std::string BuildGenerator;
+  std::string BuildGeneratorPlatform;
+  std::string BuildGeneratorToolset;
+  std::vector<std::string> BuildOptions;
+  bool BuildTwoConfig = false;
+  std::string BuildMakeProgram;
+  std::string ConfigSample;
+  std::string SourceDir;
+  std::string BinaryDir;
+  std::string BuildProject;
+  std::string TestCommand;
+  bool BuildNoClean = false;
+  std::string BuildRunDir;
+  std::string ExecutableDirectory;
+  std::vector<std::string> TestCommandArgs;
+  std::vector<std::string> BuildTargets;
+  bool BuildNoCMake = false;
+  cmDuration Timeout = cmDuration::zero();
+
+  friend class cmCTest;
+};
diff --git a/Source/CTest/cmCTestBuildAndTestHandler.cxx b/Source/CTest/cmCTestBuildAndTestHandler.cxx
deleted file mode 100644
index 9faabf7..0000000
--- a/Source/CTest/cmCTestBuildAndTestHandler.cxx
+++ /dev/null
@@ -1,453 +0,0 @@
-/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-   file Copyright.txt or https://cmake.org/licensing for details.  */
-#include "cmCTestBuildAndTestHandler.h"
-
-#include <chrono>
-#include <cstdlib>
-#include <cstring>
-#include <ratio>
-
-#include "cmBuildOptions.h"
-#include "cmCTest.h"
-#include "cmCTestTestHandler.h"
-#include "cmGlobalGenerator.h"
-#include "cmMakefile.h"
-#include "cmState.h"
-#include "cmStringAlgorithms.h"
-#include "cmSystemTools.h"
-#include "cmWorkingDirectory.h"
-#include "cmake.h"
-
-struct cmMessageMetadata;
-
-cmCTestBuildAndTestHandler::cmCTestBuildAndTestHandler()
-{
-  this->BuildTwoConfig = false;
-  this->BuildNoClean = false;
-  this->BuildNoCMake = false;
-  this->Timeout = cmDuration::zero();
-}
-
-void cmCTestBuildAndTestHandler::Initialize()
-{
-  this->BuildTargets.clear();
-  this->Superclass::Initialize();
-}
-
-const char* cmCTestBuildAndTestHandler::GetOutput()
-{
-  return this->Output.c_str();
-}
-int cmCTestBuildAndTestHandler::ProcessHandler()
-{
-  this->Output.clear();
-  std::string output;
-  cmSystemTools::ResetErrorOccurredFlag();
-  int retv = this->RunCMakeAndTest(&this->Output);
-  cmSystemTools::ResetErrorOccurredFlag();
-  return retv;
-}
-
-int cmCTestBuildAndTestHandler::RunCMake(std::string* outstring,
-                                         std::ostringstream& out,
-                                         std::string& cmakeOutString,
-                                         cmake* cm)
-{
-  std::vector<std::string> args;
-  args.push_back(cmSystemTools::GetCMakeCommand());
-  args.push_back(this->SourceDir);
-  if (!this->BuildGenerator.empty()) {
-    args.push_back("-G" + this->BuildGenerator);
-  }
-  if (!this->BuildGeneratorPlatform.empty()) {
-    args.push_back("-A" + this->BuildGeneratorPlatform);
-  }
-  if (!this->BuildGeneratorToolset.empty()) {
-    args.push_back("-T" + this->BuildGeneratorToolset);
-  }
-
-  const char* config = nullptr;
-  if (!this->CTest->GetConfigType().empty()) {
-    config = this->CTest->GetConfigType().c_str();
-  }
-
-  if (config) {
-    args.push_back("-DCMAKE_BUILD_TYPE:STRING=" + std::string(config));
-  }
-  if (!this->BuildMakeProgram.empty() &&
-      (this->BuildGenerator.find("Make") != std::string::npos ||
-       this->BuildGenerator.find("Ninja") != std::string::npos)) {
-    args.push_back("-DCMAKE_MAKE_PROGRAM:FILEPATH=" + this->BuildMakeProgram);
-  }
-
-  for (std::string const& opt : this->BuildOptions) {
-    args.push_back(opt);
-  }
-  if (cm->Run(args) != 0) {
-    out << "Error: cmake execution failed\n";
-    out << cmakeOutString << "\n";
-    if (outstring) {
-      *outstring = out.str();
-    } else {
-      cmCTestLog(this->CTest, ERROR_MESSAGE, out.str() << std::endl);
-    }
-    return 1;
-  }
-  // do another config?
-  if (this->BuildTwoConfig) {
-    if (cm->Run(args) != 0) {
-      out << "Error: cmake execution failed\n";
-      out << cmakeOutString << "\n";
-      if (outstring) {
-        *outstring = out.str();
-      } else {
-        cmCTestLog(this->CTest, ERROR_MESSAGE, out.str() << std::endl);
-      }
-      return 1;
-    }
-  }
-  out << "======== CMake output     ======\n";
-  out << cmakeOutString;
-  out << "======== End CMake output ======\n";
-  return 0;
-}
-
-class cmCTestBuildAndTestCaptureRAII
-{
-  cmake& CM;
-
-public:
-  cmCTestBuildAndTestCaptureRAII(cmake& cm, std::string& s)
-    : CM(cm)
-  {
-    cmSystemTools::SetMessageCallback(
-      [&s](const std::string& msg, const cmMessageMetadata& /* unused */) {
-        s += msg;
-        s += "\n";
-      });
-
-    cmSystemTools::SetStdoutCallback([&s](std::string const& m) { s += m; });
-    cmSystemTools::SetStderrCallback([&s](std::string const& m) { s += m; });
-
-    this->CM.SetProgressCallback([&s](const std::string& msg, float prog) {
-      if (prog < 0) {
-        s += msg;
-        s += "\n";
-      }
-    });
-  }
-
-  ~cmCTestBuildAndTestCaptureRAII()
-  {
-    this->CM.SetProgressCallback(nullptr);
-    cmSystemTools::SetStderrCallback(nullptr);
-    cmSystemTools::SetStdoutCallback(nullptr);
-    cmSystemTools::SetMessageCallback(nullptr);
-  }
-
-  cmCTestBuildAndTestCaptureRAII(const cmCTestBuildAndTestCaptureRAII&) =
-    delete;
-  cmCTestBuildAndTestCaptureRAII& operator=(
-    const cmCTestBuildAndTestCaptureRAII&) = delete;
-};
-
-int cmCTestBuildAndTestHandler::RunCMakeAndTest(std::string* outstring)
-{
-  // if the generator and make program are not specified then it is an error
-  if (this->BuildGenerator.empty()) {
-    if (outstring) {
-      *outstring = "--build-and-test requires that the generator "
-                   "be provided using the --build-generator "
-                   "command line option.\n";
-    }
-    return 1;
-  }
-
-  cmake cm(cmake::RoleProject, cmState::Project);
-  cm.SetHomeDirectory("");
-  cm.SetHomeOutputDirectory("");
-  std::string cmakeOutString;
-  cmCTestBuildAndTestCaptureRAII captureRAII(cm, cmakeOutString);
-  static_cast<void>(captureRAII);
-  std::ostringstream out;
-
-  if (this->CTest->GetConfigType().empty() && !this->ConfigSample.empty()) {
-    // use the config sample to set the ConfigType
-    std::string fullPath;
-    std::string resultingConfig;
-    std::vector<std::string> extraPaths;
-    std::vector<std::string> failed;
-    fullPath = cmCTestTestHandler::FindExecutable(
-      this->CTest, this->ConfigSample, resultingConfig, extraPaths, failed);
-    if (!fullPath.empty() && !resultingConfig.empty()) {
-      this->CTest->SetConfigType(resultingConfig);
-    }
-    out << "Using config sample with results: " << fullPath << " and "
-        << resultingConfig << std::endl;
-  }
-
-  // we need to honor the timeout specified, the timeout include cmake, build
-  // and test time
-  auto clock_start = std::chrono::steady_clock::now();
-
-  // make sure the binary dir is there
-  out << "Internal cmake changing into directory: " << this->BinaryDir
-      << std::endl;
-  if (!cmSystemTools::FileIsDirectory(this->BinaryDir)) {
-    cmSystemTools::MakeDirectory(this->BinaryDir);
-  }
-  cmWorkingDirectory workdir(this->BinaryDir);
-  if (workdir.Failed()) {
-    auto msg = "Failed to change working directory to " + this->BinaryDir +
-      " : " + std::strerror(workdir.GetLastResult()) + "\n";
-    if (outstring) {
-      *outstring = msg;
-    } else {
-      cmCTestLog(this->CTest, ERROR_MESSAGE, msg);
-    }
-    return 1;
-  }
-
-  if (this->BuildNoCMake) {
-    // Make the generator available for the Build call below.
-    cm.SetGlobalGenerator(cm.CreateGlobalGenerator(this->BuildGenerator));
-    if (!this->BuildGeneratorPlatform.empty()) {
-      cmMakefile mf(cm.GetGlobalGenerator(), cm.GetCurrentSnapshot());
-      if (!cm.GetGlobalGenerator()->SetGeneratorPlatform(
-            this->BuildGeneratorPlatform, &mf)) {
-        return 1;
-      }
-    }
-
-    // Load the cache to make CMAKE_MAKE_PROGRAM available.
-    cm.LoadCache(this->BinaryDir);
-  } else {
-    // do the cmake step, no timeout here since it is not a sub process
-    if (this->RunCMake(outstring, out, cmakeOutString, &cm)) {
-      return 1;
-    }
-  }
-
-  // do the build
-  if (this->BuildTargets.empty()) {
-    this->BuildTargets.emplace_back();
-  }
-  for (std::string const& tar : this->BuildTargets) {
-    cmDuration remainingTime = std::chrono::seconds(0);
-    if (this->Timeout > cmDuration::zero()) {
-      remainingTime =
-        this->Timeout - (std::chrono::steady_clock::now() - clock_start);
-      if (remainingTime <= std::chrono::seconds(0)) {
-        if (outstring) {
-          *outstring = "--build-and-test timeout exceeded. ";
-        }
-        return 1;
-      }
-    }
-    const char* config = nullptr;
-    if (!this->CTest->GetConfigType().empty()) {
-      config = this->CTest->GetConfigType().c_str();
-    }
-    if (!config) {
-      config = "Debug";
-    }
-
-    cmBuildOptions buildOptions(!this->BuildNoClean, false,
-                                PackageResolveMode::Disable);
-    int retVal = cm.GetGlobalGenerator()->Build(
-      cmake::NO_BUILD_PARALLEL_LEVEL, this->SourceDir, this->BinaryDir,
-      this->BuildProject, { tar }, out, this->BuildMakeProgram, config,
-      buildOptions, false, remainingTime);
-    // if the build failed then return
-    if (retVal) {
-      if (outstring) {
-        *outstring = out.str();
-      }
-      return 1;
-    }
-  }
-  if (outstring) {
-    *outstring = out.str();
-  }
-
-  // if no test was specified then we are done
-  if (this->TestCommand.empty()) {
-    return 0;
-  }
-
-  // now run the compiled test if we can find it
-  // store the final location in fullPath
-  std::string fullPath;
-  std::string resultingConfig;
-  std::vector<std::string> extraPaths;
-  // if this->ExecutableDirectory is set try that as well
-  if (!this->ExecutableDirectory.empty()) {
-    std::string tempPath =
-      cmStrCat(this->ExecutableDirectory, '/', this->TestCommand);
-    extraPaths.push_back(tempPath);
-  }
-  std::vector<std::string> failed;
-  fullPath = cmCTestTestHandler::FindExecutable(
-    this->CTest, this->TestCommand, resultingConfig, extraPaths, failed);
-
-  if (!cmSystemTools::FileExists(fullPath)) {
-    out << "Could not find path to executable, perhaps it was not built: "
-        << this->TestCommand << "\n";
-    out << "tried to find it in these places:\n";
-    out << fullPath << "\n";
-    for (std::string const& fail : failed) {
-      out << fail << "\n";
-    }
-    if (outstring) {
-      *outstring = out.str();
-    } else {
-      cmCTestLog(this->CTest, ERROR_MESSAGE, out.str());
-    }
-    return 1;
-  }
-
-  std::vector<std::string> testCommand;
-  testCommand.push_back(fullPath);
-  for (std::string const& testCommandArg : this->TestCommandArgs) {
-    testCommand.push_back(testCommandArg);
-  }
-  std::string outs;
-  int retval = 0;
-  // run the test from the this->BuildRunDir if set
-  if (!this->BuildRunDir.empty()) {
-    out << "Run test in directory: " << this->BuildRunDir << "\n";
-    if (!workdir.SetDirectory(this->BuildRunDir)) {
-      out << "Failed to change working directory : "
-          << std::strerror(workdir.GetLastResult()) << "\n";
-      if (outstring) {
-        *outstring = out.str();
-      } else {
-        cmCTestLog(this->CTest, ERROR_MESSAGE, out.str());
-      }
-      return 1;
-    }
-  }
-  out << "Running test command: \"" << fullPath << "\"";
-  for (std::string const& testCommandArg : this->TestCommandArgs) {
-    out << " \"" << testCommandArg << "\"";
-  }
-  out << "\n";
-
-  // how much time is remaining
-  cmDuration remainingTime = std::chrono::seconds(0);
-  if (this->Timeout > cmDuration::zero()) {
-    remainingTime =
-      this->Timeout - (std::chrono::steady_clock::now() - clock_start);
-    if (remainingTime <= std::chrono::seconds(0)) {
-      if (outstring) {
-        *outstring = "--build-and-test timeout exceeded. ";
-      }
-      return 1;
-    }
-  }
-
-  bool runTestRes = this->CTest->RunTest(testCommand, &outs, &retval, nullptr,
-                                         remainingTime, nullptr);
-
-  if (!runTestRes || retval != 0) {
-    out << "Test command failed: " << testCommand[0] << "\n";
-    retval = 1;
-  }
-
-  out << outs << "\n";
-  if (outstring) {
-    *outstring = out.str();
-  } else {
-    cmCTestLog(this->CTest, OUTPUT, out.str() << std::endl);
-  }
-  return retval;
-}
-
-int cmCTestBuildAndTestHandler::ProcessCommandLineArguments(
-  const std::string& currentArg, size_t& idx,
-  const std::vector<std::string>& allArgs, bool& validArg)
-{
-  bool buildAndTestArg = true;
-  // --build-and-test options
-  if (cmHasLiteralPrefix(currentArg, "--build-and-test") &&
-      idx < allArgs.size() - 1) {
-    if (idx + 2 < allArgs.size()) {
-      idx++;
-      this->SourceDir = allArgs[idx];
-      idx++;
-      this->BinaryDir = allArgs[idx];
-      // dir must exist before CollapseFullPath is called
-      cmSystemTools::MakeDirectory(this->BinaryDir);
-      this->BinaryDir = cmSystemTools::CollapseFullPath(this->BinaryDir);
-      this->SourceDir = cmSystemTools::CollapseFullPath(this->SourceDir);
-    } else {
-      cmCTestLog(this->CTest, ERROR_MESSAGE,
-                 "--build-and-test must have source and binary dir"
-                   << std::endl);
-      return 0;
-    }
-  } else if (cmHasLiteralPrefix(currentArg, "--build-target") &&
-             idx < allArgs.size() - 1) {
-    idx++;
-    this->BuildTargets.push_back(allArgs[idx]);
-  } else if (cmHasLiteralPrefix(currentArg, "--build-nocmake")) {
-    this->BuildNoCMake = true;
-  } else if (cmHasLiteralPrefix(currentArg, "--build-run-dir") &&
-             idx < allArgs.size() - 1) {
-    idx++;
-    this->BuildRunDir = allArgs[idx];
-  } else if (cmHasLiteralPrefix(currentArg, "--build-two-config")) {
-    this->BuildTwoConfig = true;
-  } else if (cmHasLiteralPrefix(currentArg, "--build-exe-dir") &&
-             idx < allArgs.size() - 1) {
-    idx++;
-    this->ExecutableDirectory = allArgs[idx];
-  } else if (cmHasLiteralPrefix(currentArg, "--test-timeout") &&
-             idx < allArgs.size() - 1) {
-    idx++;
-    this->Timeout = cmDuration(atof(allArgs[idx].c_str()));
-  } else if (currentArg == "--build-generator" && idx < allArgs.size() - 1) {
-    idx++;
-    this->BuildGenerator = allArgs[idx];
-  } else if (currentArg == "--build-generator-platform" &&
-             idx < allArgs.size() - 1) {
-    idx++;
-    this->BuildGeneratorPlatform = allArgs[idx];
-  } else if (currentArg == "--build-generator-toolset" &&
-             idx < allArgs.size() - 1) {
-    idx++;
-    this->BuildGeneratorToolset = allArgs[idx];
-  } else if (cmHasLiteralPrefix(currentArg, "--build-project") &&
-             idx < allArgs.size() - 1) {
-    idx++;
-    this->BuildProject = allArgs[idx];
-  } else if (cmHasLiteralPrefix(currentArg, "--build-makeprogram") &&
-             idx < allArgs.size() - 1) {
-    idx++;
-    this->BuildMakeProgram = allArgs[idx];
-  } else if (cmHasLiteralPrefix(currentArg, "--build-config-sample") &&
-             idx < allArgs.size() - 1) {
-    idx++;
-    this->ConfigSample = allArgs[idx];
-  } else if (cmHasLiteralPrefix(currentArg, "--build-noclean")) {
-    this->BuildNoClean = true;
-  } else if (cmHasLiteralPrefix(currentArg, "--build-options")) {
-    while (idx + 1 < allArgs.size() && allArgs[idx + 1] != "--build-target" &&
-           allArgs[idx + 1] != "--test-command") {
-      ++idx;
-      this->BuildOptions.push_back(allArgs[idx]);
-    }
-  } else if (cmHasLiteralPrefix(currentArg, "--test-command") &&
-             idx < allArgs.size() - 1) {
-    ++idx;
-    this->TestCommand = allArgs[idx];
-    while (idx + 1 < allArgs.size()) {
-      ++idx;
-      this->TestCommandArgs.push_back(allArgs[idx]);
-    }
-  } else {
-    buildAndTestArg = false;
-  }
-  validArg = validArg || buildAndTestArg;
-  return 1;
-}
diff --git a/Source/CTest/cmCTestBuildAndTestHandler.h b/Source/CTest/cmCTestBuildAndTestHandler.h
deleted file mode 100644
index 60b3a11..0000000
--- a/Source/CTest/cmCTestBuildAndTestHandler.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-   file Copyright.txt or https://cmake.org/licensing for details.  */
-#pragma once
-
-#include "cmConfigure.h" // IWYU pragma: keep
-
-#include <cstddef>
-#include <sstream>
-#include <string>
-#include <vector>
-
-#include "cmCTestGenericHandler.h"
-#include "cmDuration.h"
-
-class cmake;
-
-/** \class cmCTestBuildAndTestHandler
- * \brief A class that handles ctest -S invocations
- *
- */
-class cmCTestBuildAndTestHandler : public cmCTestGenericHandler
-{
-public:
-  using Superclass = cmCTestGenericHandler;
-
-  /*
-   * The main entry point for this class
-   */
-  int ProcessHandler() override;
-
-  //! Set all the build and test arguments
-  int ProcessCommandLineArguments(const std::string& currentArg, size_t& idx,
-                                  const std::vector<std::string>& allArgs,
-                                  bool& validArg) override;
-
-  /*
-   * Get the output variable
-   */
-  const char* GetOutput();
-
-  cmCTestBuildAndTestHandler();
-
-  void Initialize() override;
-
-protected:
-  //! Run CMake and build a test and then run it as a single test.
-  int RunCMakeAndTest(std::string* output);
-  int RunCMake(std::string* outstring, std::ostringstream& out,
-               std::string& cmakeOutString, cmake* cm);
-
-  std::string Output;
-
-  std::string BuildGenerator;
-  std::string BuildGeneratorPlatform;
-  std::string BuildGeneratorToolset;
-  std::vector<std::string> BuildOptions;
-  bool BuildTwoConfig;
-  std::string BuildMakeProgram;
-  std::string ConfigSample;
-  std::string SourceDir;
-  std::string BinaryDir;
-  std::string BuildProject;
-  std::string TestCommand;
-  bool BuildNoClean;
-  std::string BuildRunDir;
-  std::string ExecutableDirectory;
-  std::vector<std::string> TestCommandArgs;
-  std::vector<std::string> BuildTargets;
-  bool BuildNoCMake;
-  cmDuration Timeout;
-};
diff --git a/Source/CTest/cmCTestBuildCommand.cxx b/Source/CTest/cmCTestBuildCommand.cxx
index d8ef195..410a35e 100644
--- a/Source/CTest/cmCTestBuildCommand.cxx
+++ b/Source/CTest/cmCTestBuildCommand.cxx
@@ -24,7 +24,6 @@
 {
   auto ni = cm::make_unique<cmCTestBuildCommand>();
   ni->CTest = this->CTest;
-  ni->CTestScriptHandler = this->CTestScriptHandler;
   return std::unique_ptr<cmCommand>(std::move(ni));
 }
 
diff --git a/Source/CTest/cmCTestBuildHandler.cxx b/Source/CTest/cmCTestBuildHandler.cxx
index e962cc7..a665993 100644
--- a/Source/CTest/cmCTestBuildHandler.cxx
+++ b/Source/CTest/cmCTestBuildHandler.cxx
@@ -580,9 +580,6 @@
   int numErrorsAllowed = this->MaxErrors;
   int numWarningsAllowed = this->MaxWarnings;
   std::string srcdir = this->CTest->GetCTestConfiguration("SourceDirectory");
-  // make sure the source dir is in the correct case on windows
-  // via a call to collapse full path.
-  srcdir = cmStrCat(cmSystemTools::CollapseFullPath(srcdir), '/');
   for (it = ew.begin();
        it != ew.end() && (numErrorsAllowed || numWarningsAllowed); it++) {
     cmCTestBuildErrorWarning* cm = &(*it);
@@ -613,8 +610,9 @@
             }
           } else {
             // make sure it is a full path with the correct case
-            cm->SourceFile = cmSystemTools::CollapseFullPath(cm->SourceFile);
-            cmSystemTools::ReplaceString(cm->SourceFile, srcdir.c_str(), "");
+            cm->SourceFile =
+              cmSystemTools::ToNormalizedPathOnDisk(cm->SourceFile);
+            cmSystemTools::ReplaceString(cm->SourceFile, srcdir, "");
           }
           cm->LineNumber = atoi(re->match(rit.LineIndex).c_str());
           break;
diff --git a/Source/CTest/cmCTestCommand.h b/Source/CTest/cmCTestCommand.h
index 007378d..ccdef7c 100644
--- a/Source/CTest/cmCTestCommand.h
+++ b/Source/CTest/cmCTestCommand.h
@@ -5,7 +5,6 @@
 #include "cmCommand.h"
 
 class cmCTest;
-class cmCTestScriptHandler;
 
 /** \class cmCTestCommand
  * \brief A superclass for all commands added to the CTestScriptHandler
@@ -17,12 +16,5 @@
 class cmCTestCommand : public cmCommand
 {
 public:
-  cmCTestCommand()
-  {
-    this->CTest = nullptr;
-    this->CTestScriptHandler = nullptr;
-  }
-
-  cmCTest* CTest;
-  cmCTestScriptHandler* CTestScriptHandler;
+  cmCTest* CTest = nullptr;
 };
diff --git a/Source/CTest/cmCTestConfigureCommand.h b/Source/CTest/cmCTestConfigureCommand.h
index f338637..59699df 100644
--- a/Source/CTest/cmCTestConfigureCommand.h
+++ b/Source/CTest/cmCTestConfigureCommand.h
@@ -29,7 +29,6 @@
   {
     auto ni = cm::make_unique<cmCTestConfigureCommand>();
     ni->CTest = this->CTest;
-    ni->CTestScriptHandler = this->CTestScriptHandler;
     return std::unique_ptr<cmCommand>(std::move(ni));
   }
 
diff --git a/Source/CTest/cmCTestCoverageCommand.h b/Source/CTest/cmCTestCoverageCommand.h
index 55c68b2..6eb5868 100644
--- a/Source/CTest/cmCTestCoverageCommand.h
+++ b/Source/CTest/cmCTestCoverageCommand.h
@@ -32,7 +32,6 @@
   {
     auto ni = cm::make_unique<cmCTestCoverageCommand>();
     ni->CTest = this->CTest;
-    ni->CTestScriptHandler = this->CTestScriptHandler;
     return std::unique_ptr<cmCommand>(std::move(ni));
   }
 
diff --git a/Source/CTest/cmCTestCoverageHandler.cxx b/Source/CTest/cmCTestCoverageHandler.cxx
index cec4a7e..edc9280 100644
--- a/Source/CTest/cmCTestCoverageHandler.cxx
+++ b/Source/CTest/cmCTestCoverageHandler.cxx
@@ -6,7 +6,6 @@
 #include <chrono>
 #include <cstdio>
 #include <cstdlib>
-#include <cstring>
 #include <iomanip>
 #include <iterator>
 #include <memory>
@@ -1325,10 +1324,7 @@
     std::string fileDir = cmSystemTools::GetFilenamePath(f);
     cmWorkingDirectory workdir(fileDir);
     if (workdir.Failed()) {
-      cmCTestLog(this->CTest, ERROR_MESSAGE,
-                 "Unable to change working directory to "
-                   << fileDir << " : "
-                   << std::strerror(workdir.GetLastResult()) << std::endl);
+      cmCTestLog(this->CTest, ERROR_MESSAGE, workdir.GetError() << std::endl);
       cont->Error++;
       continue;
     }
@@ -1550,9 +1546,7 @@
   std::string buildDir = this->CTest->GetCTestConfiguration("BuildDirectory");
   cmWorkingDirectory workdir(buildDir);
   if (workdir.Failed()) {
-    cmCTestLog(this->CTest, ERROR_MESSAGE,
-               "Unable to change working directory to " << buildDir
-                                                        << std::endl);
+    cmCTestLog(this->CTest, ERROR_MESSAGE, workdir.GetError() << std::endl);
     return false;
   }
 
diff --git a/Source/CTest/cmCTestEmptyBinaryDirectoryCommand.cxx b/Source/CTest/cmCTestEmptyBinaryDirectoryCommand.cxx
index 2c92d77..5ae89a1 100644
--- a/Source/CTest/cmCTestEmptyBinaryDirectoryCommand.cxx
+++ b/Source/CTest/cmCTestEmptyBinaryDirectoryCommand.cxx
@@ -2,22 +2,98 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmCTestEmptyBinaryDirectoryCommand.h"
 
-#include "cmCTestScriptHandler.h"
+#include "cmsys/Directory.hxx"
+
 #include "cmExecutionStatus.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
 #include "cmStringAlgorithms.h"
+#include "cmSystemTools.h"
 
-bool cmCTestEmptyBinaryDirectoryCommand::InitialPass(
-  std::vector<std::string> const& args, cmExecutionStatus& status)
+namespace {
+
+// Try to remove the binary directory once
+cmsys::Status TryToRemoveBinaryDirectoryOnce(const std::string& directoryPath)
+{
+  cmsys::Directory directory;
+  directory.Load(directoryPath);
+
+  // Make sure that CMakeCache.txt is deleted last.
+  for (unsigned long i = 0; i < directory.GetNumberOfFiles(); ++i) {
+    std::string path = directory.GetFile(i);
+
+    if (path == "." || path == ".." || path == "CMakeCache.txt") {
+      continue;
+    }
+
+    std::string fullPath = cmStrCat(directoryPath, "/", path);
+
+    bool isDirectory = cmSystemTools::FileIsDirectory(fullPath) &&
+      !cmSystemTools::FileIsSymlink(fullPath);
+
+    cmsys::Status status;
+    if (isDirectory) {
+      status = cmSystemTools::RemoveADirectory(fullPath);
+    } else {
+      status = cmSystemTools::RemoveFile(fullPath);
+    }
+    if (!status) {
+      return status;
+    }
+  }
+
+  return cmSystemTools::RemoveADirectory(directoryPath);
+}
+
+/*
+ * Empty Binary Directory
+ */
+bool EmptyBinaryDirectory(const std::string& sname, std::string& err)
+{
+  // try to avoid deleting root
+  if (sname.size() < 2) {
+    err = "path too short";
+    return false;
+  }
+
+  // consider non existing target directory a success
+  if (!cmSystemTools::FileExists(sname)) {
+    return true;
+  }
+
+  // try to avoid deleting directories that we shouldn't
+  std::string check = cmStrCat(sname, "/CMakeCache.txt");
+
+  if (!cmSystemTools::FileExists(check)) {
+    err = "path does not contain an existing CMakeCache.txt file";
+    return false;
+  }
+
+  cmsys::Status status;
+  for (int i = 0; i < 5; ++i) {
+    status = TryToRemoveBinaryDirectoryOnce(sname);
+    if (status) {
+      return true;
+    }
+    cmSystemTools::Delay(100);
+  }
+
+  err = status.GetString();
+  return false;
+}
+
+} // namespace
+
+bool cmCTestEmptyBinaryDirectoryCommand(std::vector<std::string> const& args,
+                                        cmExecutionStatus& status)
 {
   if (args.size() != 1) {
-    this->SetError("called with incorrect number of arguments");
+    status.SetError("called with incorrect number of arguments");
     return false;
   }
 
   std::string err;
-  if (!cmCTestScriptHandler::EmptyBinaryDirectory(args[0], err)) {
+  if (!EmptyBinaryDirectory(args[0], err)) {
     status.GetMakefile().IssueMessage(
       MessageType::FATAL_ERROR,
       cmStrCat("Did not remove the binary directory:\n ", args[0],
diff --git a/Source/CTest/cmCTestEmptyBinaryDirectoryCommand.h b/Source/CTest/cmCTestEmptyBinaryDirectoryCommand.h
index ba2b0eb..d19ae98 100644
--- a/Source/CTest/cmCTestEmptyBinaryDirectoryCommand.h
+++ b/Source/CTest/cmCTestEmptyBinaryDirectoryCommand.h
@@ -5,42 +5,9 @@
 #include "cmConfigure.h" // IWYU pragma: keep
 
 #include <string>
-#include <utility>
 #include <vector>
 
-#include <cm/memory>
-
-#include "cmCTestCommand.h"
-#include "cmCommand.h"
-
 class cmExecutionStatus;
 
-/** \class cmCTestEmptyBinaryDirectory
- * \brief Run a ctest script
- *
- * cmLibrarysCommand defines a list of executable (i.e., test)
- * programs to create.
- */
-class cmCTestEmptyBinaryDirectoryCommand : public cmCTestCommand
-{
-public:
-  cmCTestEmptyBinaryDirectoryCommand() {}
-
-  /**
-   * This is a virtual constructor for the command.
-   */
-  std::unique_ptr<cmCommand> Clone() override
-  {
-    auto ni = cm::make_unique<cmCTestEmptyBinaryDirectoryCommand>();
-    ni->CTest = this->CTest;
-    ni->CTestScriptHandler = this->CTestScriptHandler;
-    return std::unique_ptr<cmCommand>(std::move(ni));
-  }
-
-  /**
-   * This is called when the command is first encountered in
-   * the CMakeLists.txt file.
-   */
-  bool InitialPass(std::vector<std::string> const& args,
-                   cmExecutionStatus& status) override;
-};
+bool cmCTestEmptyBinaryDirectoryCommand(std::vector<std::string> const& args,
+                                        cmExecutionStatus& status);
diff --git a/Source/CTest/cmCTestGIT.cxx b/Source/CTest/cmCTestGIT.cxx
index 99c5a2b..3a5ec04 100644
--- a/Source/CTest/cmCTestGIT.cxx
+++ b/Source/CTest/cmCTestGIT.cxx
@@ -146,7 +146,7 @@
       !cdup.empty()) {
     top_dir += "/";
     top_dir += cdup;
-    top_dir = cmSystemTools::CollapseFullPath(top_dir);
+    top_dir = cmSystemTools::ToNormalizedPathOnDisk(top_dir);
   }
   return top_dir;
 }
diff --git a/Source/CTest/cmCTestGenericHandler.h b/Source/CTest/cmCTestGenericHandler.h
index a189d60..39fa6eb 100644
--- a/Source/CTest/cmCTestGenericHandler.h
+++ b/Source/CTest/cmCTestGenericHandler.h
@@ -4,7 +4,6 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
-#include <cstddef>
 #include <map>
 #include <string>
 #include <vector>
@@ -44,16 +43,6 @@
   virtual int ProcessHandler() = 0;
 
   /**
-   * Process command line arguments that are applicable for the handler
-   */
-  virtual int ProcessCommandLineArguments(
-    const std::string& /*currentArg*/, size_t& /*idx*/,
-    const std::vector<std::string>& /*allArgs*/, bool& /*valid*/)
-  {
-    return 1;
-  }
-
-  /**
    * Initialize handler
    */
   virtual void Initialize();
diff --git a/Source/CTest/cmCTestHandlerCommand.cxx b/Source/CTest/cmCTestHandlerCommand.cxx
index c377d68..753ec0f 100644
--- a/Source/CTest/cmCTestHandlerCommand.cxx
+++ b/Source/CTest/cmCTestHandlerCommand.cxx
@@ -4,7 +4,6 @@
 
 #include <algorithm>
 #include <cstdlib>
-#include <cstring>
 #include <sstream>
 
 #include <cm/string_view>
@@ -190,9 +189,7 @@
   cmWorkingDirectory workdir(
     this->CTest->GetCTestConfiguration("BuildDirectory"));
   if (workdir.Failed()) {
-    this->SetError("failed to change directory to " +
-                   this->CTest->GetCTestConfiguration("BuildDirectory") +
-                   " : " + std::strerror(workdir.GetLastResult()));
+    this->SetError(workdir.GetError());
     if (captureCMakeError) {
       this->Makefile->AddDefinition(this->CaptureCMakeError, "-1");
       cmCTestLog(this->CTest, ERROR_MESSAGE,
diff --git a/Source/CTest/cmCTestMemCheckCommand.h b/Source/CTest/cmCTestMemCheckCommand.h
index ee39e49..fcb4836 100644
--- a/Source/CTest/cmCTestMemCheckCommand.h
+++ b/Source/CTest/cmCTestMemCheckCommand.h
@@ -30,7 +30,6 @@
   {
     auto ni = cm::make_unique<cmCTestMemCheckCommand>();
     ni->CTest = this->CTest;
-    ni->CTestScriptHandler = this->CTestScriptHandler;
     return std::unique_ptr<cmCommand>(std::move(ni));
   }
 
diff --git a/Source/CTest/cmCTestMultiProcessHandler.cxx b/Source/CTest/cmCTestMultiProcessHandler.cxx
index 84ea32b..ac74697 100644
--- a/Source/CTest/cmCTestMultiProcessHandler.cxx
+++ b/Source/CTest/cmCTestMultiProcessHandler.cxx
@@ -8,7 +8,6 @@
 #include <cmath>
 #include <cstddef> // IWYU pragma: keep
 #include <cstdlib>
-#include <cstring>
 #include <iomanip>
 #include <iostream>
 #include <list>
@@ -279,9 +278,7 @@
   cmWorkingDirectory workdir(this->Properties[test]->Directory);
   if (workdir.Failed()) {
     cmCTestRunTest::StartFailure(std::move(testRun), this->Total,
-                                 "Failed to change working directory to " +
-                                   this->Properties[test]->Directory + " : " +
-                                   std::strerror(workdir.GetLastResult()),
+                                 workdir.GetError(),
                                  "Failed to change working directory");
     return;
   }
@@ -501,7 +498,7 @@
 
 inline size_t cmCTestMultiProcessHandler::GetProcessorsUsed(int test)
 {
-  size_t processors = static_cast<int>(this->Properties[test]->Processors);
+  size_t processors = this->Properties[test]->Processors;
   size_t const parallelLevel = this->GetParallelLevel();
   // If processors setting is set higher than the -j
   // setting, we default to using all of the process slots.
diff --git a/Source/CTest/cmCTestRunScriptCommand.cxx b/Source/CTest/cmCTestRunScriptCommand.cxx
index 7661d4d..38953ea 100644
--- a/Source/CTest/cmCTestRunScriptCommand.cxx
+++ b/Source/CTest/cmCTestRunScriptCommand.cxx
@@ -3,16 +3,16 @@
 #include "cmCTestRunScriptCommand.h"
 
 #include "cmCTestScriptHandler.h"
+#include "cmExecutionStatus.h"
 #include "cmMakefile.h"
-
-class cmExecutionStatus;
+#include "cmSystemTools.h"
 
 bool cmCTestRunScriptCommand::InitialPass(std::vector<std::string> const& args,
-                                          cmExecutionStatus& /*unused*/)
+                                          cmExecutionStatus& status)
 {
   if (args.empty()) {
-    this->CTestScriptHandler->RunCurrentScript();
-    return true;
+    status.SetError("called with incorrect number of arguments");
+    return false;
   }
 
   bool np = false;
@@ -37,7 +37,8 @@
       ++i;
     } else {
       int ret;
-      cmCTestScriptHandler::RunScript(this->CTest, this->Makefile, args[i],
+      cmCTestScriptHandler::RunScript(this->CTest, this->Makefile,
+                                      cmSystemTools::CollapseFullPath(args[i]),
                                       !np, &ret);
       this->Makefile->AddDefinition(returnVariable, std::to_string(ret));
     }
diff --git a/Source/CTest/cmCTestRunScriptCommand.h b/Source/CTest/cmCTestRunScriptCommand.h
index 510b748..c7b1c5a 100644
--- a/Source/CTest/cmCTestRunScriptCommand.h
+++ b/Source/CTest/cmCTestRunScriptCommand.h
@@ -33,7 +33,6 @@
   {
     auto ni = cm::make_unique<cmCTestRunScriptCommand>();
     ni->CTest = this->CTest;
-    ni->CTestScriptHandler = this->CTestScriptHandler;
     return std::unique_ptr<cmCommand>(std::move(ni));
   }
 
diff --git a/Source/CTest/cmCTestRunTest.cxx b/Source/CTest/cmCTestRunTest.cxx
index 483b3b4..326846e 100644
--- a/Source/CTest/cmCTestRunTest.cxx
+++ b/Source/CTest/cmCTestRunTest.cxx
@@ -7,7 +7,6 @@
 #include <cstddef> // IWYU pragma: keep
 #include <cstdint>
 #include <cstdio>
-#include <cstring>
 #include <iomanip>
 #include <ratio>
 #include <sstream>
@@ -398,10 +397,7 @@
   // change to tests directory
   cmWorkingDirectory workdir(testRun->TestProperties->Directory);
   if (workdir.Failed()) {
-    testRun->StartFailure(testRun->TotalNumberOfTests,
-                          "Failed to change working directory to " +
-                            testRun->TestProperties->Directory + " : " +
-                            std::strerror(workdir.GetLastResult()),
+    testRun->StartFailure(testRun->TotalNumberOfTests, workdir.GetError(),
                           "Failed to change working directory");
     return true;
   }
diff --git a/Source/CTest/cmCTestScriptHandler.cxx b/Source/CTest/cmCTestScriptHandler.cxx
index ed567d4..be298fa 100644
--- a/Source/CTest/cmCTestScriptHandler.cxx
+++ b/Source/CTest/cmCTestScriptHandler.cxx
@@ -2,7 +2,6 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmCTestScriptHandler.h"
 
-#include <cstdio>
 #include <cstdlib>
 #include <map>
 #include <ratio>
@@ -13,8 +12,6 @@
 
 #include <cm3p/uv.h>
 
-#include "cmsys/Directory.hxx"
-
 #include "cmCTest.h"
 #include "cmCTestBuildCommand.h"
 #include "cmCTestCommand.h"
@@ -32,51 +29,22 @@
 #include "cmCTestUploadCommand.h"
 #include "cmCommand.h"
 #include "cmDuration.h"
-#include "cmGeneratedFileStream.h"
 #include "cmGlobalGenerator.h"
-#include "cmList.h"
 #include "cmMakefile.h"
 #include "cmState.h"
 #include "cmStateDirectory.h"
 #include "cmStateSnapshot.h"
-#include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmUVHandlePtr.h"
 #include "cmUVProcessChain.h"
 #include "cmValue.h"
 #include "cmake.h"
 
-#ifdef _WIN32
-#  include <windows.h>
-#else
-#  include <unistd.h>
-#endif
-
 cmCTestScriptHandler::cmCTestScriptHandler() = default;
 
 void cmCTestScriptHandler::Initialize()
 {
   this->Superclass::Initialize();
-  this->Backup = false;
-  this->EmptyBinDir = false;
-  this->EmptyBinDirOnce = false;
-
-  this->SourceDir.clear();
-  this->BinaryDir.clear();
-  this->BackupSourceDir.clear();
-  this->BackupBinaryDir.clear();
-  this->CTestRoot.clear();
-  this->CVSCheckOut.clear();
-  this->CTestCmd.clear();
-  this->UpdateCmd.clear();
-  this->CTestEnv.clear();
-  this->InitialCache.clear();
-  this->CMakeCmd.clear();
-  this->CMOutFile.clear();
-  this->ExtraUpdates.clear();
-
-  this->MinimumInterval = 20 * 60;
-  this->ContinuousDuration = -1;
 
   // what time in seconds did this script start running
   this->ScriptStartTime = std::chrono::steady_clock::time_point();
@@ -106,9 +74,8 @@
   int res = 0;
   for (size_t i = 0; i < this->ConfigurationScripts.size(); ++i) {
     // for each script run it
-    res |= this->RunConfigurationScript(
-      cmSystemTools::CollapseFullPath(this->ConfigurationScripts[i]),
-      this->ScriptProcessScope[i]);
+    res |= this->RunConfigurationScript(this->ConfigurationScripts[i],
+                                        this->ScriptProcessScope[i]);
   }
   if (res) {
     return -1;
@@ -131,7 +98,6 @@
   std::string const& name, std::unique_ptr<cmCTestCommand> command)
 {
   command->CTest = this->CTest;
-  command->CTestScriptHandler = this;
   this->CMake->GetState()->AddBuiltinCommand(name, std::move(command));
 }
 
@@ -252,20 +218,21 @@
       }
     });
 
+  cmState* state = this->CMake->GetState();
   this->AddCTestCommand("ctest_build", cm::make_unique<cmCTestBuildCommand>());
   this->AddCTestCommand("ctest_configure",
                         cm::make_unique<cmCTestConfigureCommand>());
   this->AddCTestCommand("ctest_coverage",
                         cm::make_unique<cmCTestCoverageCommand>());
-  this->AddCTestCommand("ctest_empty_binary_directory",
-                        cm::make_unique<cmCTestEmptyBinaryDirectoryCommand>());
+  state->AddBuiltinCommand("ctest_empty_binary_directory",
+                           cmCTestEmptyBinaryDirectoryCommand);
   this->AddCTestCommand("ctest_memcheck",
                         cm::make_unique<cmCTestMemCheckCommand>());
   this->AddCTestCommand("ctest_read_custom_files",
                         cm::make_unique<cmCTestReadCustomFilesCommand>());
   this->AddCTestCommand("ctest_run_script",
                         cm::make_unique<cmCTestRunScriptCommand>());
-  this->AddCTestCommand("ctest_sleep", cm::make_unique<cmCTestSleepCommand>());
+  state->AddBuiltinCommand("ctest_sleep", cmCTestSleepCommand);
   this->AddCTestCommand("ctest_start", cm::make_unique<cmCTestStartCommand>());
   this->AddCTestCommand("ctest_submit",
                         cm::make_unique<cmCTestSubmitCommand>());
@@ -314,8 +281,6 @@
                                 cmSystemTools::GetCTestCommand());
   this->Makefile->AddDefinition("CMAKE_EXECUTABLE_NAME",
                                 cmSystemTools::GetCMakeCommand());
-  this->Makefile->AddDefinitionBool("CTEST_RUN_CURRENT_SCRIPT", true);
-  this->SetRunCurrentScript(true);
   this->UpdateElapsedTime();
 
   // set the CTEST_CONFIGURATION_TYPE variable to the current value of the
@@ -366,113 +331,6 @@
   return 0;
 }
 
-// extract variables from the script to set ivars
-int cmCTestScriptHandler::ExtractVariables()
-{
-  // Temporary variables
-  cmValue minInterval;
-  cmValue contDuration;
-
-  this->SourceDir =
-    this->Makefile->GetSafeDefinition("CTEST_SOURCE_DIRECTORY");
-  this->BinaryDir =
-    this->Makefile->GetSafeDefinition("CTEST_BINARY_DIRECTORY");
-
-  // add in translations for src and bin
-  cmSystemTools::AddKeepPath(this->SourceDir);
-  cmSystemTools::AddKeepPath(this->BinaryDir);
-
-  this->CTestCmd = this->Makefile->GetSafeDefinition("CTEST_COMMAND");
-  this->CVSCheckOut = this->Makefile->GetSafeDefinition("CTEST_CVS_CHECKOUT");
-  this->CTestRoot = this->Makefile->GetSafeDefinition("CTEST_DASHBOARD_ROOT");
-  this->UpdateCmd = this->Makefile->GetSafeDefinition("CTEST_UPDATE_COMMAND");
-  if (this->UpdateCmd.empty()) {
-    this->UpdateCmd = this->Makefile->GetSafeDefinition("CTEST_CVS_COMMAND");
-  }
-  this->CTestEnv = this->Makefile->GetSafeDefinition("CTEST_ENVIRONMENT");
-  this->InitialCache =
-    this->Makefile->GetSafeDefinition("CTEST_INITIAL_CACHE");
-  this->CMakeCmd = this->Makefile->GetSafeDefinition("CTEST_CMAKE_COMMAND");
-  this->CMOutFile =
-    this->Makefile->GetSafeDefinition("CTEST_CMAKE_OUTPUT_FILE_NAME");
-
-  this->Backup = this->Makefile->IsOn("CTEST_BACKUP_AND_RESTORE");
-  this->EmptyBinDir =
-    this->Makefile->IsOn("CTEST_START_WITH_EMPTY_BINARY_DIRECTORY");
-  this->EmptyBinDirOnce =
-    this->Makefile->IsOn("CTEST_START_WITH_EMPTY_BINARY_DIRECTORY_ONCE");
-
-  minInterval =
-    this->Makefile->GetDefinition("CTEST_CONTINUOUS_MINIMUM_INTERVAL");
-  contDuration = this->Makefile->GetDefinition("CTEST_CONTINUOUS_DURATION");
-
-  char updateVar[40];
-  int i;
-  for (i = 1; i < 10; ++i) {
-    snprintf(updateVar, sizeof(updateVar), "CTEST_EXTRA_UPDATES_%i", i);
-    cmValue updateVal = this->Makefile->GetDefinition(updateVar);
-    if (updateVal) {
-      if (this->UpdateCmd.empty()) {
-        cmSystemTools::Error(
-          std::string(updateVar) +
-          " specified without specifying CTEST_CVS_COMMAND.");
-        return 12;
-      }
-      this->ExtraUpdates.emplace_back(*updateVal);
-    }
-  }
-
-  // in order to backup and restore we also must have the cvs root
-  if (this->Backup && this->CVSCheckOut.empty()) {
-    cmSystemTools::Error(
-      "Backup was requested without specifying CTEST_CVS_CHECKOUT.");
-    return 3;
-  }
-
-  // make sure the required info is here
-  if (this->SourceDir.empty() || this->BinaryDir.empty() ||
-      this->CTestCmd.empty()) {
-    std::string msg =
-      cmStrCat("CTEST_SOURCE_DIRECTORY = ",
-               (!this->SourceDir.empty()) ? this->SourceDir.c_str() : "(Null)",
-               "\nCTEST_BINARY_DIRECTORY = ",
-               (!this->BinaryDir.empty()) ? this->BinaryDir.c_str() : "(Null)",
-               "\nCTEST_COMMAND = ",
-               (!this->CTestCmd.empty()) ? this->CTestCmd.c_str() : "(Null)");
-    cmSystemTools::Error(
-      "Some required settings in the configuration file were missing:\n" +
-      msg);
-    return 4;
-  }
-
-  // if the dashboard root isn't specified then we can compute it from the
-  // this->SourceDir
-  if (this->CTestRoot.empty()) {
-    this->CTestRoot = cmSystemTools::GetFilenamePath(this->SourceDir);
-  }
-
-  // the script may override the minimum continuous interval
-  if (minInterval) {
-    this->MinimumInterval = 60 * atof(minInterval->c_str());
-  }
-  if (contDuration) {
-    this->ContinuousDuration = 60.0 * atof(contDuration->c_str());
-  }
-
-  this->UpdateElapsedTime();
-
-  return 0;
-}
-
-void cmCTestScriptHandler::SleepInSeconds(unsigned int secondsToWait)
-{
-#if defined(_WIN32)
-  Sleep(1000 * secondsToWait);
-#else
-  sleep(secondsToWait);
-#endif
-}
-
 // run a specific script
 int cmCTestScriptHandler::RunConfigurationScript(
   const std::string& total_script_arg, bool pscope)
@@ -495,355 +353,10 @@
                "Executing Script: " << total_script_arg << std::endl);
     result = this->ExecuteScript(total_script_arg);
   }
-  if (result) {
-    return result;
-  }
-
-  // only run the current script if we should
-  if (this->Makefile && this->Makefile->IsOn("CTEST_RUN_CURRENT_SCRIPT") &&
-      this->ShouldRunCurrentScript) {
-    return this->RunCurrentScript();
-  }
-  return result;
-}
-
-int cmCTestScriptHandler::RunCurrentScript()
-{
-  int result;
-
-  // do not run twice
-  this->SetRunCurrentScript(false);
-
-  // no popup widows
-  cmSystemTools::SetRunCommandHideConsole(true);
-
-  // extract the vars from the cache and store in ivars
-  result = this->ExtractVariables();
-  if (result) {
-    return result;
-  }
-
-  // set any environment variables
-  if (!this->CTestEnv.empty()) {
-    cmList envArgs{ this->CTestEnv };
-    cmSystemTools::AppendEnv(envArgs);
-  }
-
-  // now that we have done most of the error checking finally run the
-  // dashboard, we may be asked to repeatedly run this dashboard, such as
-  // for a continuous, do we need to run it more than once?
-  if (this->ContinuousDuration >= 0) {
-    this->UpdateElapsedTime();
-    auto ending_time =
-      std::chrono::steady_clock::now() + cmDuration(this->ContinuousDuration);
-    if (this->EmptyBinDirOnce) {
-      this->EmptyBinDir = true;
-    }
-    do {
-      auto startOfInterval = std::chrono::steady_clock::now();
-      result = this->RunConfigurationDashboard();
-      auto interval = std::chrono::steady_clock::now() - startOfInterval;
-      auto minimumInterval = cmDuration(this->MinimumInterval);
-      if (interval < minimumInterval) {
-        auto sleepTime =
-          cmDurationTo<unsigned int>(minimumInterval - interval);
-        this->SleepInSeconds(sleepTime);
-      }
-      if (this->EmptyBinDirOnce) {
-        this->EmptyBinDir = false;
-      }
-    } while (std::chrono::steady_clock::now() < ending_time);
-  }
-  // otherwise just run it once
-  else {
-    result = this->RunConfigurationDashboard();
-  }
 
   return result;
 }
 
-int cmCTestScriptHandler::CheckOutSourceDir()
-{
-  std::string output;
-  int retVal;
-
-  if (!cmSystemTools::FileExists(this->SourceDir) &&
-      !this->CVSCheckOut.empty()) {
-    // we must now checkout the src dir
-    output.clear();
-    cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
-               "Run cvs: " << this->CVSCheckOut << std::endl);
-    bool res = cmSystemTools::RunSingleCommand(
-      this->CVSCheckOut, &output, &output, &retVal, this->CTestRoot.c_str(),
-      this->HandlerVerbose, cmDuration::zero() /*this->TimeOut*/);
-    if (!res || retVal != 0) {
-      cmSystemTools::Error("Unable to perform cvs checkout:\n" + output);
-      return 6;
-    }
-  }
-  return 0;
-}
-
-int cmCTestScriptHandler::BackupDirectories()
-{
-  // compute the backup names
-  this->BackupSourceDir = cmStrCat(this->SourceDir, "_CMakeBackup");
-  this->BackupBinaryDir = cmStrCat(this->BinaryDir, "_CMakeBackup");
-
-  // backup the binary and src directories if requested
-  if (this->Backup) {
-    // if for some reason those directories exist then first delete them
-    if (cmSystemTools::FileExists(this->BackupSourceDir)) {
-      cmSystemTools::RemoveADirectory(this->BackupSourceDir);
-    }
-    if (cmSystemTools::FileExists(this->BackupBinaryDir)) {
-      cmSystemTools::RemoveADirectory(this->BackupBinaryDir);
-    }
-
-    // first rename the src and binary directories
-    rename(this->SourceDir.c_str(), this->BackupSourceDir.c_str());
-    rename(this->BinaryDir.c_str(), this->BackupBinaryDir.c_str());
-
-    // we must now checkout the src dir
-    int retVal = this->CheckOutSourceDir();
-    if (retVal) {
-      this->RestoreBackupDirectories();
-      return retVal;
-    }
-  }
-
-  return 0;
-}
-
-int cmCTestScriptHandler::PerformExtraUpdates()
-{
-  std::string command;
-  std::string output;
-  int retVal;
-  bool res;
-
-  // do an initial cvs update as required
-  command = this->UpdateCmd;
-  for (std::string const& eu : this->ExtraUpdates) {
-    cmList cvsArgs{ eu };
-    if (cvsArgs.size() == 2) {
-      std::string fullCommand = cmStrCat(command, " update ", cvsArgs[1]);
-      output.clear();
-      retVal = 0;
-      cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
-                 "Run Update: " << fullCommand << std::endl);
-      res = cmSystemTools::RunSingleCommand(
-        fullCommand, &output, &output, &retVal, cvsArgs[0].c_str(),
-        this->HandlerVerbose, cmDuration::zero() /*this->TimeOut*/);
-      if (!res || retVal != 0) {
-        cmSystemTools::Error(cmStrCat("Unable to perform extra updates:\n", eu,
-                                      "\nWith output:\n", output));
-        return 0;
-      }
-    }
-  }
-  return 0;
-}
-
-// run a single dashboard entry
-int cmCTestScriptHandler::RunConfigurationDashboard()
-{
-  // local variables
-  std::string command;
-  std::string output;
-  int retVal;
-  bool res;
-
-  // make sure the src directory is there, if it isn't then we might be able
-  // to check it out from cvs
-  retVal = this->CheckOutSourceDir();
-  if (retVal) {
-    return retVal;
-  }
-
-  // backup the dirs if requested
-  retVal = this->BackupDirectories();
-  if (retVal) {
-    return retVal;
-  }
-
-  // clear the binary directory?
-  if (this->EmptyBinDir) {
-    std::string err;
-    if (!cmCTestScriptHandler::EmptyBinaryDirectory(this->BinaryDir, err)) {
-      cmCTestLog(this->CTest, ERROR_MESSAGE,
-                 "Problem removing the binary directory ("
-                   << err << "): " << this->BinaryDir << std::endl);
-    }
-  }
-
-  // make sure the binary directory exists if it isn't the srcdir
-  if (!cmSystemTools::FileExists(this->BinaryDir) &&
-      this->SourceDir != this->BinaryDir) {
-    if (!cmSystemTools::MakeDirectory(this->BinaryDir)) {
-      cmSystemTools::Error("Unable to create the binary directory:\n" +
-                           this->BinaryDir);
-      this->RestoreBackupDirectories();
-      return 7;
-    }
-  }
-
-  // if the binary directory and the source directory are the same,
-  // and we are starting with an empty binary directory, then that means
-  // we must check out the source tree
-  if (this->EmptyBinDir && this->SourceDir == this->BinaryDir) {
-    // make sure we have the required info
-    if (this->CVSCheckOut.empty()) {
-      cmSystemTools::Error(
-        "You have specified the source and binary "
-        "directories to be the same (an in source build). You have also "
-        "specified that the binary directory is to be erased. This means "
-        "that the source will have to be checked out from CVS. But you have "
-        "not specified CTEST_CVS_CHECKOUT");
-      return 8;
-    }
-
-    // we must now checkout the src dir
-    retVal = this->CheckOutSourceDir();
-    if (retVal) {
-      this->RestoreBackupDirectories();
-      return retVal;
-    }
-  }
-
-  // backup the dirs if requested
-  retVal = this->PerformExtraUpdates();
-  if (retVal) {
-    return retVal;
-  }
-
-  // put the initial cache into the bin dir
-  if (!this->InitialCache.empty()) {
-    if (!cmCTestScriptHandler::WriteInitialCache(this->BinaryDir,
-                                                 this->InitialCache)) {
-      this->RestoreBackupDirectories();
-      return 9;
-    }
-  }
-
-  // do an initial cmake to setup the DartConfig file
-  int cmakeFailed = 0;
-  std::string cmakeFailedOuput;
-  if (!this->CMakeCmd.empty()) {
-    command = cmStrCat(this->CMakeCmd, " \"", this->SourceDir);
-    output.clear();
-    command += "\"";
-    retVal = 0;
-    cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
-               "Run cmake command: " << command << std::endl);
-    res = cmSystemTools::RunSingleCommand(
-      command, &output, &output, &retVal, this->BinaryDir.c_str(),
-      this->HandlerVerbose, cmDuration::zero() /*this->TimeOut*/);
-
-    if (!this->CMOutFile.empty()) {
-      std::string cmakeOutputFile = this->CMOutFile;
-      if (!cmSystemTools::FileIsFullPath(cmakeOutputFile)) {
-        cmakeOutputFile = this->BinaryDir + "/" + cmakeOutputFile;
-      }
-
-      cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
-                 "Write CMake output to file: " << cmakeOutputFile
-                                                << std::endl);
-      cmGeneratedFileStream fout(cmakeOutputFile);
-      if (fout) {
-        fout << output.c_str();
-      } else {
-        cmCTestLog(this->CTest, ERROR_MESSAGE,
-                   "Cannot open CMake output file: "
-                     << cmakeOutputFile << " for writing" << std::endl);
-      }
-    }
-    if (!res || retVal != 0) {
-      // even if this fails continue to the next step
-      cmakeFailed = 1;
-      cmakeFailedOuput = output;
-    }
-  }
-
-  // run ctest, it may be more than one command in here
-  cmList ctestCommands{ this->CTestCmd };
-  // for each variable/argument do a putenv
-  for (std::string const& ctestCommand : ctestCommands) {
-    command = ctestCommand;
-    output.clear();
-    retVal = 0;
-    cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
-               "Run ctest command: " << command << std::endl);
-    res = cmSystemTools::RunSingleCommand(
-      command, &output, &output, &retVal, this->BinaryDir.c_str(),
-      this->HandlerVerbose, cmDuration::zero() /*this->TimeOut*/);
-
-    // did something critical fail in ctest
-    if (!res || cmakeFailed || retVal & cmCTest::BUILD_ERRORS) {
-      this->RestoreBackupDirectories();
-      if (cmakeFailed) {
-        cmCTestLog(this->CTest, ERROR_MESSAGE,
-                   "Unable to run cmake:" << std::endl
-                                          << cmakeFailedOuput << std::endl);
-        return 10;
-      }
-      cmCTestLog(this->CTest, ERROR_MESSAGE,
-                 "Unable to run ctest:" << std::endl
-                                        << "command: " << command << std::endl
-                                        << "output: " << output << std::endl);
-      if (!res) {
-        return 11;
-      }
-      return retVal * 100;
-    }
-  }
-
-  // if all was successful, delete the backup dirs to free up disk space
-  if (this->Backup) {
-    cmSystemTools::RemoveADirectory(this->BackupSourceDir);
-    cmSystemTools::RemoveADirectory(this->BackupBinaryDir);
-  }
-
-  return 0;
-}
-
-bool cmCTestScriptHandler::WriteInitialCache(const std::string& directory,
-                                             const std::string& text)
-{
-  std::string cacheFile = cmStrCat(directory, "/CMakeCache.txt");
-  cmGeneratedFileStream fout(cacheFile);
-  if (!fout) {
-    return false;
-  }
-
-  fout.write(text.data(), text.size());
-
-  // Make sure the operating system has finished writing the file
-  // before closing it.  This will ensure the file is finished before
-  // the check below.
-  fout.flush();
-  fout.close();
-  return true;
-}
-
-void cmCTestScriptHandler::RestoreBackupDirectories()
-{
-  // if we backed up the dirs and the build failed, then restore
-  // the backed up dirs
-  if (this->Backup) {
-    // if for some reason those directories exist then first delete them
-    if (cmSystemTools::FileExists(this->SourceDir)) {
-      cmSystemTools::RemoveADirectory(this->SourceDir);
-    }
-    if (cmSystemTools::FileExists(this->BinaryDir)) {
-      cmSystemTools::RemoveADirectory(this->BinaryDir);
-    }
-    // rename the src and binary directories
-    rename(this->BackupSourceDir.c_str(), this->SourceDir.c_str());
-    rename(this->BackupBinaryDir.c_str(), this->BinaryDir.c_str());
-  }
-}
-
 bool cmCTestScriptHandler::RunScript(cmCTest* ctest, cmMakefile* mf,
                                      const std::string& sname, bool InProcess,
                                      int* returnValue)
@@ -859,73 +372,6 @@
   return true;
 }
 
-bool cmCTestScriptHandler::EmptyBinaryDirectory(const std::string& sname,
-                                                std::string& err)
-{
-  // try to avoid deleting root
-  if (sname.size() < 2) {
-    err = "path too short";
-    return false;
-  }
-
-  // consider non existing target directory a success
-  if (!cmSystemTools::FileExists(sname)) {
-    return true;
-  }
-
-  // try to avoid deleting directories that we shouldn't
-  std::string check = cmStrCat(sname, "/CMakeCache.txt");
-
-  if (!cmSystemTools::FileExists(check)) {
-    err = "path does not contain an existing CMakeCache.txt file";
-    return false;
-  }
-
-  cmsys::Status status;
-  for (int i = 0; i < 5; ++i) {
-    status = TryToRemoveBinaryDirectoryOnce(sname);
-    if (status) {
-      return true;
-    }
-    cmSystemTools::Delay(100);
-  }
-
-  err = status.GetString();
-  return false;
-}
-
-cmsys::Status cmCTestScriptHandler::TryToRemoveBinaryDirectoryOnce(
-  const std::string& directoryPath)
-{
-  cmsys::Directory directory;
-  directory.Load(directoryPath);
-
-  for (unsigned long i = 0; i < directory.GetNumberOfFiles(); ++i) {
-    std::string path = directory.GetFile(i);
-
-    if (path == "." || path == ".." || path == "CMakeCache.txt") {
-      continue;
-    }
-
-    std::string fullPath = cmStrCat(directoryPath, "/", path);
-
-    bool isDirectory = cmSystemTools::FileIsDirectory(fullPath) &&
-      !cmSystemTools::FileIsSymlink(fullPath);
-
-    cmsys::Status status;
-    if (isDirectory) {
-      status = cmSystemTools::RemoveADirectory(fullPath);
-    } else {
-      status = cmSystemTools::RemoveFile(fullPath);
-    }
-    if (!status) {
-      return status;
-    }
-  }
-
-  return cmSystemTools::RemoveADirectory(directoryPath);
-}
-
 cmDuration cmCTestScriptHandler::GetRemainingTimeAllowed()
 {
   if (!this->Makefile) {
@@ -944,8 +390,3 @@
     std::chrono::steady_clock::now() - this->ScriptStartTime);
   return (timelimit - duration);
 }
-
-void cmCTestScriptHandler::SetRunCurrentScript(bool value)
-{
-  this->ShouldRunCurrentScript = value;
-}
diff --git a/Source/CTest/cmCTestScriptHandler.h b/Source/CTest/cmCTestScriptHandler.h
index 8aa07e7..2096ab7 100644
--- a/Source/CTest/cmCTestScriptHandler.h
+++ b/Source/CTest/cmCTestScriptHandler.h
@@ -9,8 +9,6 @@
 #include <string>
 #include <vector>
 
-#include "cmsys/Status.hxx"
-
 #include "cmCTestGenericHandler.h"
 #include "cmDuration.h"
 
@@ -22,39 +20,6 @@
 
 /** \class cmCTestScriptHandler
  * \brief A class that handles ctest -S invocations
- *
- * CTest script is controlled using several variables that script has to
- * specify and some optional ones. Required ones are:
- *   CTEST_SOURCE_DIRECTORY - Source directory of the project
- *   CTEST_BINARY_DIRECTORY - Binary directory of the project
- *   CTEST_COMMAND          - Testing commands
- *
- * Optional variables are:
- *   CTEST_BACKUP_AND_RESTORE
- *   CTEST_CMAKE_COMMAND
- *   CTEST_CMAKE_OUTPUT_FILE_NAME
- *   CTEST_CONTINUOUS_DURATION
- *   CTEST_CONTINUOUS_MINIMUM_INTERVAL
- *   CTEST_CVS_CHECKOUT
- *   CTEST_CVS_COMMAND
- *   CTEST_UPDATE_COMMAND
- *   CTEST_DASHBOARD_ROOT
- *   CTEST_ENVIRONMENT
- *   CTEST_INITIAL_CACHE
- *   CTEST_START_WITH_EMPTY_BINARY_DIRECTORY
- *   CTEST_START_WITH_EMPTY_BINARY_DIRECTORY_ONCE
- *
- * In addition the following variables can be used. The number can be 1-10.
- *   CTEST_EXTRA_UPDATES_1
- *   CTEST_EXTRA_UPDATES_2
- *   ...
- *   CTEST_EXTRA_UPDATES_10
- *
- * CTest script can use the following arguments CTest provides:
- *   CTEST_SCRIPT_ARG
- *   CTEST_SCRIPT_DIRECTORY
- *   CTEST_SCRIPT_NAME
- *
  */
 class cmCTestScriptHandler : public cmCTestGenericHandler
 {
@@ -77,23 +42,10 @@
   static bool RunScript(cmCTest* ctest, cmMakefile* mf,
                         const std::string& script, bool InProcess,
                         int* returnValue);
-  int RunCurrentScript();
-
-  /*
-   * Empty Binary Directory
-   */
-  static bool EmptyBinaryDirectory(const std::string& dir, std::string& err);
-
-  /*
-   * Write an initial CMakeCache.txt from the given contents.
-   */
-  static bool WriteInitialCache(const std::string& directory,
-                                const std::string& text);
 
   /*
    * Some elapsed time handling functions
    */
-  static void SleepInSeconds(unsigned int secondsToWait);
   void UpdateElapsedTime();
 
   /**
@@ -112,65 +64,22 @@
 
   void CreateCMake();
   cmake* GetCMake() { return this->CMake.get(); }
-
-  void SetRunCurrentScript(bool value);
+  cmMakefile* GetMakefile() { return this->Makefile.get(); }
 
 private:
   // reads in a script
   int ReadInScript(const std::string& total_script_arg);
   int ExecuteScript(const std::string& total_script_arg);
 
-  // extract vars from the script to set ivars
-  int ExtractVariables();
-
-  // perform a CVS checkout of the source dir
-  int CheckOutSourceDir();
-
-  // perform any extra cvs updates that were requested
-  int PerformExtraUpdates();
-
-  // backup and restore dirs
-  int BackupDirectories();
-  void RestoreBackupDirectories();
-
   int RunConfigurationScript(const std::string& script, bool pscope);
-  int RunConfigurationDashboard();
 
   // Add ctest command
   void AddCTestCommand(std::string const& name,
                        std::unique_ptr<cmCTestCommand> command);
 
-  // Try to remove the binary directory once
-  static cmsys::Status TryToRemoveBinaryDirectoryOnce(
-    const std::string& directoryPath);
-
   std::vector<std::string> ConfigurationScripts;
   std::vector<bool> ScriptProcessScope;
 
-  bool ShouldRunCurrentScript;
-
-  bool Backup = false;
-  bool EmptyBinDir = false;
-  bool EmptyBinDirOnce = false;
-
-  std::string SourceDir;
-  std::string BinaryDir;
-  std::string BackupSourceDir;
-  std::string BackupBinaryDir;
-  std::string CTestRoot;
-  std::string CVSCheckOut;
-  std::string CTestCmd;
-  std::string UpdateCmd;
-  std::string CTestEnv;
-  std::string InitialCache;
-  std::string CMakeCmd;
-  std::string CMOutFile;
-  std::vector<std::string> ExtraUpdates;
-
-  // the *60 is because the settings are in minutes but GetTime is seconds
-  double MinimumInterval = 30 * 60;
-  double ContinuousDuration = -1;
-
   // what time in seconds did this script start running
   std::chrono::steady_clock::time_point ScriptStartTime =
     std::chrono::steady_clock::time_point();
diff --git a/Source/CTest/cmCTestSleepCommand.cxx b/Source/CTest/cmCTestSleepCommand.cxx
index 623d3b6..f57d856 100644
--- a/Source/CTest/cmCTestSleepCommand.cxx
+++ b/Source/CTest/cmCTestSleepCommand.cxx
@@ -2,42 +2,34 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmCTestSleepCommand.h"
 
+#include <chrono>
 #include <cstdlib>
+#include <thread>
 
-#include "cmCTestScriptHandler.h"
+#include "cmExecutionStatus.h"
 
-class cmExecutionStatus;
-
-bool cmCTestSleepCommand::InitialPass(std::vector<std::string> const& args,
-                                      cmExecutionStatus& /*unused*/)
+bool cmCTestSleepCommand(std::vector<std::string> const& args,
+                         cmExecutionStatus& status)
 {
-  if (args.empty()) {
-    this->SetError("called with incorrect number of arguments");
-    return false;
-  }
-
   // sleep for specified seconds
-  unsigned int time1 = atoi(args[0].c_str());
   if (args.size() == 1) {
-    cmCTestScriptHandler::SleepInSeconds(time1);
-    // update the elapsed time since it could have slept for a while
-    this->CTestScriptHandler->UpdateElapsedTime();
+    unsigned int duration = atoi(args[0].c_str());
+    std::this_thread::sleep_for(std::chrono::seconds(duration));
     return true;
   }
 
   // sleep up to a duration
   if (args.size() == 3) {
+    unsigned int time1 = atoi(args[0].c_str());
     unsigned int duration = atoi(args[1].c_str());
     unsigned int time2 = atoi(args[2].c_str());
     if (time1 + duration > time2) {
       duration = (time1 + duration - time2);
-      cmCTestScriptHandler::SleepInSeconds(duration);
-      // update the elapsed time since it could have slept for a while
-      this->CTestScriptHandler->UpdateElapsedTime();
+      std::this_thread::sleep_for(std::chrono::seconds(duration));
     }
     return true;
   }
 
-  this->SetError("called with incorrect number of arguments");
+  status.SetError("called with incorrect number of arguments");
   return false;
 }
diff --git a/Source/CTest/cmCTestSleepCommand.h b/Source/CTest/cmCTestSleepCommand.h
index 9425576..3d4eb1e 100644
--- a/Source/CTest/cmCTestSleepCommand.h
+++ b/Source/CTest/cmCTestSleepCommand.h
@@ -5,42 +5,9 @@
 #include "cmConfigure.h" // IWYU pragma: keep
 
 #include <string>
-#include <utility>
 #include <vector>
 
-#include <cm/memory>
-
-#include "cmCTestCommand.h"
-#include "cmCommand.h"
-
 class cmExecutionStatus;
 
-/** \class cmCTestSleep
- * \brief Run a ctest script
- *
- * cmLibrarysCommand defines a list of executable (i.e., test)
- * programs to create.
- */
-class cmCTestSleepCommand : public cmCTestCommand
-{
-public:
-  cmCTestSleepCommand() {}
-
-  /**
-   * This is a virtual constructor for the command.
-   */
-  std::unique_ptr<cmCommand> Clone() override
-  {
-    auto ni = cm::make_unique<cmCTestSleepCommand>();
-    ni->CTest = this->CTest;
-    ni->CTestScriptHandler = this->CTestScriptHandler;
-    return std::unique_ptr<cmCommand>(std::move(ni));
-  }
-
-  /**
-   * This is called when the command is first encountered in
-   * the CMakeLists.txt file.
-   */
-  bool InitialPass(std::vector<std::string> const& args,
-                   cmExecutionStatus& status) override;
-};
+bool cmCTestSleepCommand(std::vector<std::string> const& args,
+                         cmExecutionStatus& status);
diff --git a/Source/CTest/cmCTestStartCommand.cxx b/Source/CTest/cmCTestStartCommand.cxx
index 84d12d7..14ff9df 100644
--- a/Source/CTest/cmCTestStartCommand.cxx
+++ b/Source/CTest/cmCTestStartCommand.cxx
@@ -9,17 +9,12 @@
 #include "cmCTestVC.h"
 #include "cmGeneratedFileStream.h"
 #include "cmMakefile.h"
+#include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmValue.h"
 
 class cmExecutionStatus;
 
-cmCTestStartCommand::cmCTestStartCommand()
-{
-  this->CreateNewTag = true;
-  this->Quiet = false;
-}
-
 bool cmCTestStartCommand::InitialPass(std::vector<std::string> const& args,
                                       cmExecutionStatus& /*unused*/)
 {
@@ -29,6 +24,8 @@
   }
 
   size_t cnt = 0;
+  bool append = false;
+  bool quiet = false;
   const char* smodel = nullptr;
   cmValue src_dir;
   cmValue bld_dir;
@@ -47,10 +44,10 @@
       cnt++;
     } else if (args[cnt] == "APPEND") {
       cnt++;
-      this->CreateNewTag = false;
+      append = true;
     } else if (args[cnt] == "QUIET") {
       cnt++;
-      this->Quiet = true;
+      quiet = true;
     } else if (!smodel) {
       smodel = args[cnt].c_str();
       cnt++;
@@ -82,7 +79,7 @@
                    "as an argument or set CTEST_BINARY_DIRECTORY");
     return false;
   }
-  if (!smodel && this->CreateNewTag) {
+  if (!smodel && !append) {
     this->SetError("no test model specified and APPEND not specified. Specify "
                    "either a test model or the APPEND argument");
     return false;
@@ -95,9 +92,8 @@
 
   std::string sourceDir = cmSystemTools::CollapseFullPath(*src_dir);
   std::string binaryDir = cmSystemTools::CollapseFullPath(*bld_dir);
-  this->CTest->SetCTestConfiguration("SourceDirectory", sourceDir,
-                                     this->Quiet);
-  this->CTest->SetCTestConfiguration("BuildDirectory", binaryDir, this->Quiet);
+  this->CTest->SetCTestConfiguration("SourceDirectory", sourceDir, quiet);
+  this->CTest->SetCTestConfiguration("BuildDirectory", binaryDir, quiet);
 
   if (smodel) {
     cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
@@ -105,7 +101,7 @@
                          << smodel << std::endl
                          << "   Source directory: " << *src_dir << std::endl
                          << "   Build directory: " << *bld_dir << std::endl,
-                       this->Quiet);
+                       quiet);
   } else {
     cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
                        "Run dashboard with "
@@ -113,12 +109,12 @@
                          << std::endl
                          << "   Source directory: " << *src_dir << std::endl
                          << "   Build directory: " << *bld_dir << std::endl,
-                       this->Quiet);
+                       quiet);
   }
   const char* group = this->CTest->GetSpecificGroup();
   if (group) {
     cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
-                       "   Group: " << group << std::endl, this->Quiet);
+                       "   Group: " << group << std::endl, quiet);
   }
 
   // Log startup actions.
@@ -144,7 +140,6 @@
     return false;
   }
 
-  this->CTest->SetRunCurrentScript(false);
   this->CTest->SetSuppressUpdatingCTestConfiguration(true);
   int model;
   if (smodel) {
@@ -155,7 +150,79 @@
   this->CTest->SetTestModel(model);
   this->CTest->SetProduceXML(true);
 
-  return this->CTest->InitializeFromCommand(this);
+  std::string fname;
+
+  std::string src_dir_fname = cmStrCat(sourceDir, "/CTestConfig.cmake");
+  cmSystemTools::ConvertToUnixSlashes(src_dir_fname);
+
+  std::string bld_dir_fname = cmStrCat(binaryDir, "/CTestConfig.cmake");
+  cmSystemTools::ConvertToUnixSlashes(bld_dir_fname);
+
+  if (cmSystemTools::FileExists(bld_dir_fname)) {
+    fname = bld_dir_fname;
+  } else if (cmSystemTools::FileExists(src_dir_fname)) {
+    fname = src_dir_fname;
+  }
+
+  if (!fname.empty()) {
+    cmCTestOptionalLog(
+      this->CTest, OUTPUT,
+      "   Reading ctest configuration file: " << fname << std::endl, quiet);
+    bool readit = this->Makefile->ReadDependentFile(fname);
+    if (!readit) {
+      std::string m = cmStrCat("Could not find include file: ", fname);
+      this->SetError(m);
+      return false;
+    }
+  }
+
+  this->CTest->SetCTestConfigurationFromCMakeVariable(
+    this->Makefile, "NightlyStartTime", "CTEST_NIGHTLY_START_TIME", quiet);
+  this->CTest->SetCTestConfigurationFromCMakeVariable(this->Makefile, "Site",
+                                                      "CTEST_SITE", quiet);
+  this->CTest->SetCTestConfigurationFromCMakeVariable(
+    this->Makefile, "BuildName", "CTEST_BUILD_NAME", quiet);
+
+  this->CTest->Initialize(bld_dir);
+  this->CTest->UpdateCTestConfiguration();
+
+  cmCTestOptionalLog(
+    this->CTest, OUTPUT,
+    "   Site: " << this->CTest->GetCTestConfiguration("Site") << std::endl
+                << "   Build name: "
+                << cmCTest::SafeBuildIdField(
+                     this->CTest->GetCTestConfiguration("BuildName"))
+                << std::endl,
+    quiet);
+
+  if (this->CTest->GetTestModel() == cmCTest::NIGHTLY &&
+      this->CTest->GetCTestConfiguration("NightlyStartTime").empty()) {
+    cmCTestOptionalLog(
+      this->CTest, WARNING,
+      "WARNING: No nightly start time found please set in CTestConfig.cmake"
+      " or DartConfig.cmake"
+        << std::endl,
+      quiet);
+    return false;
+  }
+
+  this->CTest->ReadCustomConfigurationFileTree(bld_dir, this->Makefile);
+
+  if (append) {
+    if (!this->CTest->ReadExistingTag(quiet)) {
+      return false;
+    }
+  } else {
+    if (!this->CTest->CreateNewTag(quiet)) {
+      return false;
+    }
+  }
+
+  cmCTestOptionalLog(this->CTest, OUTPUT,
+                     "   Use " << this->CTest->GetTestGroupString() << " tag: "
+                               << this->CTest->GetCurrentTag() << std::endl,
+                     quiet);
+  return true;
 }
 
 bool cmCTestStartCommand::InitialCheckout(std::ostream& ofs,
diff --git a/Source/CTest/cmCTestStartCommand.h b/Source/CTest/cmCTestStartCommand.h
index b3d06a7..22ae9bc 100644
--- a/Source/CTest/cmCTestStartCommand.h
+++ b/Source/CTest/cmCTestStartCommand.h
@@ -24,8 +24,6 @@
 class cmCTestStartCommand : public cmCTestCommand
 {
 public:
-  cmCTestStartCommand();
-
   /**
    * This is a virtual constructor for the command.
    */
@@ -33,9 +31,6 @@
   {
     auto ni = cm::make_unique<cmCTestStartCommand>();
     ni->CTest = this->CTest;
-    ni->CTestScriptHandler = this->CTestScriptHandler;
-    ni->CreateNewTag = this->CreateNewTag;
-    ni->Quiet = this->Quiet;
     return std::unique_ptr<cmCommand>(std::move(ni));
   }
 
@@ -46,18 +41,6 @@
   bool InitialPass(std::vector<std::string> const& args,
                    cmExecutionStatus& status) override;
 
-  /**
-   * Will this invocation of ctest_start create a new TAG file?
-   */
-  bool ShouldCreateNewTag() { return this->CreateNewTag; }
-
-  /**
-   * Should this invocation of ctest_start output non-error messages?
-   */
-  bool ShouldBeQuiet() { return this->Quiet; }
-
 private:
   bool InitialCheckout(std::ostream& ofs, std::string const& sourceDir);
-  bool CreateNewTag;
-  bool Quiet;
 };
diff --git a/Source/CTest/cmCTestSubmitCommand.cxx b/Source/CTest/cmCTestSubmitCommand.cxx
index 029f81f..0d2501d 100644
--- a/Source/CTest/cmCTestSubmitCommand.cxx
+++ b/Source/CTest/cmCTestSubmitCommand.cxx
@@ -29,7 +29,6 @@
 {
   auto ni = cm::make_unique<cmCTestSubmitCommand>();
   ni->CTest = this->CTest;
-  ni->CTestScriptHandler = this->CTestScriptHandler;
   return std::unique_ptr<cmCommand>(std::move(ni));
 }
 
diff --git a/Source/CTest/cmCTestSubmitHandler.cxx b/Source/CTest/cmCTestSubmitHandler.cxx
index 91dea55..cf95168 100644
--- a/Source/CTest/cmCTestSubmitHandler.cxx
+++ b/Source/CTest/cmCTestSubmitHandler.cxx
@@ -139,20 +139,6 @@
   this->Files.clear();
 }
 
-int cmCTestSubmitHandler::ProcessCommandLineArguments(
-  const std::string& currentArg, size_t& idx,
-  const std::vector<std::string>& allArgs, bool& validArg)
-{
-  if (cmHasLiteralPrefix(currentArg, "--http-header") &&
-      idx < allArgs.size() - 1) {
-    ++idx;
-    this->HttpHeaders.push_back(allArgs[idx]);
-    this->CommandLineHttpHeaders.push_back(allArgs[idx]);
-    validArg = true;
-  }
-  return 1;
-}
-
 bool cmCTestSubmitHandler::SubmitUsingHTTP(
   const std::string& localprefix, const std::vector<std::string>& files,
   const std::string& remoteprefix, const std::string& url)
@@ -275,7 +261,7 @@
         upload_as += "&stamp=";
         upload_as += ctest_curl.Escape(this->CTest->GetCurrentTag());
         upload_as += "-";
-        upload_as += ctest_curl.Escape(this->CTest->GetTestModelString());
+        upload_as += ctest_curl.Escape(this->CTest->GetTestGroupString());
         cmCTestScriptHandler* ch = this->CTest->GetScriptHandler();
         cmake* cm = ch->GetCMake();
         if (cm) {
@@ -584,18 +570,18 @@
   auto timeNow =
     std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
   str << "stamp=" << curl.Escape(this->CTest->GetCurrentTag()) << "-"
-      << curl.Escape(this->CTest->GetTestModelString()) << "&"
-      << "model=" << curl.Escape(this->CTest->GetTestModelString()) << "&"
+      << curl.Escape(this->CTest->GetTestGroupString()) << "&"
+      << "model=" << curl.Escape(this->CTest->GetTestGroupString()) << "&"
       << "build="
       << curl.Escape(this->CTest->GetCTestConfiguration("BuildName")) << "&"
       << "site=" << curl.Escape(this->CTest->GetCTestConfiguration("Site"))
       << "&"
-      << "group=" << curl.Escape(this->CTest->GetTestModelString())
+      << "group=" << curl.Escape(this->CTest->GetTestGroupString())
       << "&"
       // For now, we send both "track" and "group" to CDash in case we're
       // submitting to an older instance that still expects the prior
       // terminology.
-      << "track=" << curl.Escape(this->CTest->GetTestModelString()) << "&"
+      << "track=" << curl.Escape(this->CTest->GetTestGroupString()) << "&"
       << "starttime=" << timeNow << "&"
       << "endtime=" << timeNow << "&"
       << "datafilesmd5[0]=" << md5sum << "&"
@@ -906,7 +892,7 @@
     cmCTest::SafeBuildIdField(this->CTest->GetCTestConfiguration("BuildName"));
   std::string name = this->CTest->GetCTestConfiguration("Site") + "___" +
     buildname + "___" + this->CTest->GetCurrentTag() + "-" +
-    this->CTest->GetTestModelString() + "___XML___";
+    this->CTest->GetTestGroupString() + "___XML___";
   return name;
 }
 
diff --git a/Source/CTest/cmCTestSubmitHandler.h b/Source/CTest/cmCTestSubmitHandler.h
index d152b71..9993e29 100644
--- a/Source/CTest/cmCTestSubmitHandler.h
+++ b/Source/CTest/cmCTestSubmitHandler.h
@@ -4,7 +4,6 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
-#include <cstddef>
 #include <iosfwd>
 #include <set>
 #include <string>
@@ -34,11 +33,6 @@
 
   void Initialize() override;
 
-  //! Set all the submit arguments
-  int ProcessCommandLineArguments(const std::string& currentArg, size_t& idx,
-                                  const std::vector<std::string>& allArgs,
-                                  bool& validArg) override;
-
   /** Specify a set of parts (by name) to submit.  */
   void SelectParts(std::set<cmCTest::Part> const& parts);
 
@@ -48,6 +42,12 @@
   // handle the cdash file upload protocol
   int HandleCDashUploadFile(std::string const& file, std::string const& type);
 
+  void AddCommandLineHttpHeader(std::string const& h)
+  {
+    this->HttpHeaders.push_back(h);
+    this->CommandLineHttpHeaders.push_back(h);
+  }
+
   void SetHttpHeaders(std::vector<std::string> const& v)
   {
     if (this->CommandLineHttpHeaders.empty()) {
diff --git a/Source/CTest/cmCTestTestCommand.h b/Source/CTest/cmCTestTestCommand.h
index 23661c5..bcaa571 100644
--- a/Source/CTest/cmCTestTestCommand.h
+++ b/Source/CTest/cmCTestTestCommand.h
@@ -32,7 +32,6 @@
   {
     auto ni = cm::make_unique<cmCTestTestCommand>();
     ni->CTest = this->CTest;
-    ni->CTestScriptHandler = this->CTestScriptHandler;
     return std::unique_ptr<cmCommand>(std::move(ni));
   }
 
diff --git a/Source/CTest/cmCTestTestHandler.cxx b/Source/CTest/cmCTestTestHandler.cxx
index c7875cd..b213e97 100644
--- a/Source/CTest/cmCTestTestHandler.cxx
+++ b/Source/CTest/cmCTestTestHandler.cxx
@@ -8,7 +8,6 @@
 #include <cstddef> // IWYU pragma: keep
 #include <cstdio>
 #include <cstdlib>
-#include <cstring>
 #include <ctime>
 #include <functional>
 #include <iomanip>
@@ -97,8 +96,7 @@
   {
     cmWorkingDirectory workdir(fname);
     if (workdir.Failed()) {
-      status.SetError("Failed to change directory to " + fname + " : " +
-                      std::strerror(workdir.GetLastResult()));
+      status.SetError(workdir.GetError());
       return false;
     }
     const char* testFilename;
@@ -380,6 +378,33 @@
   }
 }
 
+void cmCTestTestHandler::SetCMakeVariables(cmMakefile& mf)
+{
+  mf.AddDefinition("CTEST_CUSTOM_PRE_TEST",
+                   cmList(this->CustomPreTest).to_string());
+  mf.AddDefinition("CTEST_CUSTOM_POST_TEST",
+                   cmList(this->CustomPostTest).to_string());
+  mf.AddDefinition("CTEST_CUSTOM_TESTS_IGNORE",
+                   cmList(this->CustomTestsIgnore).to_string());
+  mf.AddDefinition("CTEST_CUSTOM_MAXIMUM_PASSED_TEST_OUTPUT_SIZE",
+                   std::to_string(this->CustomMaximumPassedTestOutputSize));
+  mf.AddDefinition("CTEST_CUSTOM_MAXIMUM_FAILED_TEST_OUTPUT_SIZE",
+                   std::to_string(this->CustomMaximumFailedTestOutputSize));
+  mf.AddDefinition("CTEST_CUSTOM_TEST_OUTPUT_TRUNCATION",
+                   [this]() -> cm::string_view {
+                     switch (this->TestOutputTruncation) {
+                       case cmCTestTypes::TruncationMode::Tail:
+                         return "tail"_s;
+                       case cmCTestTypes::TruncationMode::Middle:
+                         return "middle"_s;
+                       case cmCTestTypes::TruncationMode::Head:
+                         return "head"_s;
+                       default:
+                         return ""_s;
+                     }
+                   }());
+}
+
 int cmCTestTestHandler::PreProcessHandler()
 {
   if (!this->ExecuteCommands(this->CustomPreTest)) {
@@ -1761,7 +1786,7 @@
   for (unsigned int ai = 0; ai < attempted.size() && fullPath.empty(); ++ai) {
     // first check without exe extension
     if (cmSystemTools::FileExists(attempted[ai], true)) {
-      fullPath = cmSystemTools::CollapseFullPath(attempted[ai]);
+      fullPath = cmSystemTools::ToNormalizedPathOnDisk(attempted[ai]);
       resultingConfig = attemptedConfigs[ai];
     }
     // then try with the exe extension
@@ -1770,7 +1795,7 @@
       tempPath =
         cmStrCat(attempted[ai], cmSystemTools::GetExecutableExtension());
       if (cmSystemTools::FileExists(tempPath, true)) {
-        fullPath = cmSystemTools::CollapseFullPath(tempPath);
+        fullPath = cmSystemTools::ToNormalizedPathOnDisk(tempPath);
         resultingConfig = attemptedConfigs[ai];
       } else {
         failed.push_back(tempPath);
diff --git a/Source/CTest/cmCTestTestHandler.h b/Source/CTest/cmCTestTestHandler.h
index dd1bc59..024b544 100644
--- a/Source/CTest/cmCTestTestHandler.h
+++ b/Source/CTest/cmCTestTestHandler.h
@@ -228,6 +228,11 @@
   // Support for writing test results in JUnit XML format.
   void SetJUnitXMLFileName(const std::string& id);
 
+  /**
+   * Set CMake variables from CTest Options
+   */
+  void SetCMakeVariables(cmMakefile& mf);
+
 protected:
   using SetOfTests =
     std::set<cmCTestTestHandler::cmCTestTestResult, cmCTestTestResultLess>;
diff --git a/Source/CTest/cmCTestUpdateCommand.h b/Source/CTest/cmCTestUpdateCommand.h
index e4c3453..fecc482 100644
--- a/Source/CTest/cmCTestUpdateCommand.h
+++ b/Source/CTest/cmCTestUpdateCommand.h
@@ -29,7 +29,6 @@
   {
     auto ni = cm::make_unique<cmCTestUpdateCommand>();
     ni->CTest = this->CTest;
-    ni->CTestScriptHandler = this->CTestScriptHandler;
     return std::unique_ptr<cmCommand>(std::move(ni));
   }
 
diff --git a/Source/CTest/cmCTestUpdateHandler.cxx b/Source/CTest/cmCTestUpdateHandler.cxx
index 045eb84..565f383 100644
--- a/Source/CTest/cmCTestUpdateHandler.cxx
+++ b/Source/CTest/cmCTestUpdateHandler.cxx
@@ -195,7 +195,7 @@
   xml.Element("BuildName", buildname);
   xml.Element("BuildStamp",
               this->CTest->GetCurrentTag() + "-" +
-                this->CTest->GetTestModelString());
+                this->CTest->GetTestGroupString());
   xml.Element("StartDateTime", start_time);
   xml.Element("StartTime", start_time_time);
   xml.Element("UpdateCommand", vc->GetUpdateCommandLine());
diff --git a/Source/CTest/cmCTestUploadCommand.h b/Source/CTest/cmCTestUploadCommand.h
index a9d1dd2..ade88b0 100644
--- a/Source/CTest/cmCTestUploadCommand.h
+++ b/Source/CTest/cmCTestUploadCommand.h
@@ -32,7 +32,6 @@
   {
     auto ni = cm::make_unique<cmCTestUploadCommand>();
     ni->CTest = this->CTest;
-    ni->CTestScriptHandler = this->CTestScriptHandler;
     return std::unique_ptr<cmCommand>(std::move(ni));
   }
 
diff --git a/Source/CTest/cmCTestUploadHandler.cxx b/Source/CTest/cmCTestUploadHandler.cxx
index 59e2b88..8fb10b6 100644
--- a/Source/CTest/cmCTestUploadHandler.cxx
+++ b/Source/CTest/cmCTestUploadHandler.cxx
@@ -49,7 +49,7 @@
   xml.Attribute("BuildName", buildname);
   xml.Attribute("BuildStamp",
                 this->CTest->GetCurrentTag() + "-" +
-                  this->CTest->GetTestModelString());
+                  this->CTest->GetTestGroupString());
   xml.Attribute("Name", this->CTest->GetCTestConfiguration("Site"));
   xml.Attribute("Generator",
                 std::string("ctest-") + cmVersion::GetCMakeVersion());
diff --git a/Source/Checks/Curses/CMakeLists.txt b/Source/Checks/Curses/CMakeLists.txt
index bd7c415..1f04bd2 100644
--- a/Source/Checks/Curses/CMakeLists.txt
+++ b/Source/Checks/Curses/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.13...3.29 FATAL_ERROR)
+cmake_minimum_required(VERSION 3.13...3.30 FATAL_ERROR)
 project(CheckCurses C)
 
 set(CURSES_NEED_NCURSES TRUE)
diff --git a/Source/QtDialog/CMakeSetup.cxx b/Source/QtDialog/CMakeSetup.cxx
index 21ed8c8..7847a4b 100644
--- a/Source/QtDialog/CMakeSetup.cxx
+++ b/Source/QtDialog/CMakeSetup.cxx
@@ -172,8 +172,8 @@
         }
       }
 
-      sourceDirectory = cmSystemTools::CollapseFullPath(path.toStdString());
-      cmSystemTools::ConvertToUnixSlashes(sourceDirectory);
+      sourceDirectory =
+        cmSystemTools::ToNormalizedPathOnDisk(path.toStdString());
     } else if (arg.startsWith("-B")) {
       QString path = arg.mid(2);
       if (path.isEmpty()) {
@@ -189,8 +189,8 @@
         }
       }
 
-      binaryDirectory = cmSystemTools::CollapseFullPath(path.toStdString());
-      cmSystemTools::ConvertToUnixSlashes(binaryDirectory);
+      binaryDirectory =
+        cmSystemTools::ToNormalizedPathOnDisk(path.toStdString());
     } else if (arg.startsWith("--preset=")) {
       QString preset = arg.mid(cmStrLen("--preset="));
       if (preset.isEmpty()) {
@@ -223,7 +223,7 @@
   } else {
     if (args.count() == 2) {
       std::string filePath =
-        cmSystemTools::CollapseFullPath(args[1].toStdString());
+        cmSystemTools::ToNormalizedPathOnDisk(args[1].toStdString());
 
       // check if argument is a directory containing CMakeCache.txt
       std::string buildFilePath = cmStrCat(filePath, "/CMakeCache.txt");
@@ -243,7 +243,7 @@
       } else if (cmSystemTools::FileExists(srcFilePath.c_str())) {
         dialog.setSourceDirectory(QString::fromStdString(filePath));
         dialog.setBinaryDirectory(
-          QString::fromStdString(cmSystemTools::CollapseFullPath(".")));
+          QString::fromStdString(cmSystemTools::GetCurrentWorkingDirectory()));
       }
     }
   }
diff --git a/Source/QtDialog/QCMake.cxx b/Source/QtDialog/QCMake.cxx
index 45c4717..7c94ea2 100644
--- a/Source/QtDialog/QCMake.cxx
+++ b/Source/QtDialog/QCMake.cxx
@@ -258,9 +258,7 @@
 #endif
     // Apply the same transformations that the command-line invocation does
     auto sanitizePath = [](QString const& value) -> std::string {
-      std::string path = cmSystemTools::CollapseFullPath(value.toStdString());
-      cmSystemTools::ConvertToUnixSlashes(path);
-      return path;
+      return cmSystemTools::ToNormalizedPathOnDisk(value.toStdString());
     };
 
     this->CMakeInstance->SetHomeDirectory(sanitizePath(this->SourceDirectory));
diff --git a/Source/QtDialog/QCMakeCacheView.cxx b/Source/QtDialog/QCMakeCacheView.cxx
index e67e0c2..94dfa3e 100644
--- a/Source/QtDialog/QCMakeCacheView.cxx
+++ b/Source/QtDialog/QCMakeCacheView.cxx
@@ -189,7 +189,7 @@
 
 static uint qHash(const QCMakeProperty& p)
 {
-  return static_cast<uint>(qHash(p.Key));
+  return qHash(p.Key);
 }
 
 void QCMakeCacheModel::setShowNewProperties(bool f)
@@ -242,7 +242,7 @@
   bool b = this->blockSignals(true);
 
   this->clear();
-  this->NewPropertyCount = static_cast<int>(newProps.size());
+  this->NewPropertyCount = newProps.size();
 
   if (View == FlatView) {
     QCMakePropertyList newP = newProps.values();
diff --git a/Source/QtDialog/QCMakePresetItemModel.cxx b/Source/QtDialog/QCMakePresetItemModel.cxx
index 31a6000..cb96b6b 100644
--- a/Source/QtDialog/QCMakePresetItemModel.cxx
+++ b/Source/QtDialog/QCMakePresetItemModel.cxx
@@ -83,7 +83,7 @@
   if (this->m_presets.empty()) {
     return 1;
   }
-  return static_cast<int>(this->m_presets.size() + 2);
+  return this->m_presets.size() + 2;
 }
 
 int QCMakePresetItemModel::columnCount(const QModelIndex& parent) const
@@ -144,5 +144,5 @@
     index++;
   }
 
-  return static_cast<int>(this->m_presets.size() + 1);
+  return this->m_presets.size() + 1;
 }
diff --git a/Source/cmCMakePath.h b/Source/cmCMakePath.h
index fd71c1f..52fd6aa 100644
--- a/Source/cmCMakePath.h
+++ b/Source/cmCMakePath.h
@@ -617,19 +617,18 @@
 
   // Non-members
   // ===========
-  friend inline bool operator==(const cmCMakePath& lhs,
-                                const cmCMakePath& rhs) noexcept
+  friend bool operator==(const cmCMakePath& lhs,
+                         const cmCMakePath& rhs) noexcept
   {
     return lhs.Compare(rhs) == 0;
   }
-  friend inline bool operator!=(const cmCMakePath& lhs,
-                                const cmCMakePath& rhs) noexcept
+  friend bool operator!=(const cmCMakePath& lhs,
+                         const cmCMakePath& rhs) noexcept
   {
     return lhs.Compare(rhs) != 0;
   }
 
-  friend inline cmCMakePath operator/(const cmCMakePath& lhs,
-                                      const cmCMakePath& rhs)
+  friend cmCMakePath operator/(const cmCMakePath& lhs, const cmCMakePath& rhs)
   {
     cmCMakePath result(lhs);
     result /= rhs;
diff --git a/Source/cmCPluginAPI.cxx b/Source/cmCPluginAPI.cxx
index 2e6cd40..6f101cb 100644
--- a/Source/cmCPluginAPI.cxx
+++ b/Source/cmCPluginAPI.cxx
@@ -168,7 +168,7 @@
 static int CCONV cmCommandExists(void* arg, const char* name)
 {
   cmMakefile* mf = static_cast<cmMakefile*>(arg);
-  return static_cast<int>(mf->GetState()->GetCommand(name) ? 1 : 0);
+  return mf->GetState()->GetCommand(name) ? 1 : 0;
 }
 
 static void CCONV cmAddDefineFlag(void* arg, const char* definition)
diff --git a/Source/cmCTest.cxx b/Source/cmCTest.cxx
index 829fbf9..c15075c 100644
--- a/Source/cmCTest.cxx
+++ b/Source/cmCTest.cxx
@@ -41,23 +41,25 @@
 #endif
 
 #include "cmCMakePresetsGraph.h"
-#include "cmCTestBuildAndTestHandler.h"
+#include "cmCTestBuildAndTest.h"
 #include "cmCTestBuildHandler.h"
 #include "cmCTestConfigureHandler.h"
 #include "cmCTestCoverageHandler.h"
 #include "cmCTestGenericHandler.h"
 #include "cmCTestMemCheckHandler.h"
 #include "cmCTestScriptHandler.h"
-#include "cmCTestStartCommand.h"
 #include "cmCTestSubmitHandler.h"
 #include "cmCTestTestHandler.h"
 #include "cmCTestUpdateHandler.h"
 #include "cmCTestUploadHandler.h"
+#include "cmCommandLineArgument.h"
 #include "cmDynamicLoader.h"
+#include "cmExecutionStatus.h"
 #include "cmGeneratedFileStream.h"
 #include "cmGlobalGenerator.h"
 #include "cmJSONState.h"
 #include "cmList.h"
+#include "cmListFileCache.h"
 #include "cmMakefile.h"
 #include "cmProcessOutput.h"
 #include "cmState.h"
@@ -71,6 +73,7 @@
 #include "cmValue.h"
 #include "cmVersion.h"
 #include "cmVersionConfig.h"
+#include "cmWorkingDirectory.h"
 #include "cmXMLWriter.h"
 #include "cmake.h"
 
@@ -80,6 +83,11 @@
 
 struct cmCTest::Private
 {
+  Private(cmCTest* ctest)
+    : BuildAndTest(ctest)
+  {
+  }
+
   /** Representation of one part.  */
   struct PartInfo
   {
@@ -114,13 +122,9 @@
 
   bool FlushTestProgressLine = false;
 
-  bool ForceNewCTestProcess = false;
-
-  bool RunConfigurationScript = false;
-
   // these are helper classes
   cmCTestBuildHandler BuildHandler;
-  cmCTestBuildAndTestHandler BuildAndTestHandler;
+  cmCTestBuildAndTest BuildAndTest;
   cmCTestCoverageHandler CoverageHandler;
   cmCTestScriptHandler ScriptHandler;
   cmCTestTestHandler TestHandler;
@@ -132,17 +136,16 @@
 
   std::vector<cmCTestGenericHandler*> GetTestingHandlers()
   {
-    return { &this->BuildHandler,     &this->BuildAndTestHandler,
-             &this->CoverageHandler,  &this->ScriptHandler,
-             &this->TestHandler,      &this->UpdateHandler,
-             &this->ConfigureHandler, &this->MemCheckHandler,
-             &this->SubmitHandler,    &this->UploadHandler };
+    return { &this->BuildHandler,    &this->CoverageHandler,
+             &this->ScriptHandler,   &this->TestHandler,
+             &this->UpdateHandler,   &this->ConfigureHandler,
+             &this->MemCheckHandler, &this->SubmitHandler,
+             &this->UploadHandler };
   }
 
   std::map<std::string, cmCTestGenericHandler*> GetNamedTestingHandlers()
   {
     return { { "build", &this->BuildHandler },
-             { "buildtest", &this->BuildAndTestHandler },
              { "coverage", &this->CoverageHandler },
              { "script", &this->ScriptHandler },
              { "test", &this->TestHandler },
@@ -199,14 +202,9 @@
   bool CompressXMLFiles = false;
   bool CompressTestOutput = true;
 
-  // By default we write output to the process output streams.
-  std::ostream* StreamOut = &std::cout;
-  std::ostream* StreamErr = &std::cerr;
-
   bool SuppressUpdatingCTestConfiguration = false;
 
   bool Debug = false;
-  bool ShowLineNumbers = false;
   bool Quiet = false;
 
   std::string BuildID;
@@ -216,7 +214,7 @@
   int SubmitIndex = 0;
 
   std::unique_ptr<cmGeneratedFileStream> OutputLogFile;
-  int OutputLogFileLastTag = -1;
+  cm::optional<cmCTest::LogType> OutputLogFileLastTag;
 
   bool OutputTestOutputOnTestFailure = false;
   bool OutputColorCode = cmCTest::ColoredOutputSupportedByConsole();
@@ -340,7 +338,7 @@
 }
 
 cmCTest::cmCTest()
-  : Impl(new Private)
+  : Impl(cm::make_unique<Private>(this))
 {
   std::string envValue;
   if (cmSystemTools::GetEnv("CTEST_OUTPUT_ON_FAILURE", envValue)) {
@@ -418,15 +416,143 @@
   return PartCount;
 }
 
-int cmCTest::Initialize(const std::string& binary_dir,
-                        cmCTestStartCommand* command)
+void cmCTest::Initialize(std::string const& binary_dir)
 {
-  bool quiet = false;
-  if (command && command->ShouldBeQuiet()) {
-    quiet = true;
+  this->Impl->BuildID = "";
+  for (Part p = PartStart; p != PartCount; p = static_cast<Part>(p + 1)) {
+    this->Impl->Parts[p].SubmitFiles.clear();
   }
 
-  cmCTestOptionalLog(this, DEBUG, "Here: " << __LINE__ << std::endl, quiet);
+  if (!this->Impl->InteractiveDebugMode) {
+    this->BlockTestErrorDiagnostics();
+  } else {
+    cmSystemTools::PutEnv("CTEST_INTERACTIVE_DEBUG_MODE=1");
+  }
+
+  this->Impl->BinaryDir = binary_dir;
+  cmSystemTools::ConvertToUnixSlashes(this->Impl->BinaryDir);
+}
+
+bool cmCTest::CreateNewTag(bool quiet)
+{
+  std::string const testingDir = this->Impl->BinaryDir + "/Testing";
+  std::string const tagfile = testingDir + "/TAG";
+
+  auto const result = cmSystemTools::MakeDirectory(testingDir);
+  if (!result.IsSuccess()) {
+    cmCTestLog(this, ERROR_MESSAGE,
+               "Cannot create directory \""
+                 << testingDir << "\": " << result.GetString() << std::endl);
+    return false;
+  }
+
+  cmCTestOptionalLog(this, DEBUG,
+                     "TestModel: " << this->GetTestGroupString() << std::endl,
+                     quiet);
+  cmCTestOptionalLog(
+    this, DEBUG, "TestModel: " << this->Impl->TestModel << std::endl, quiet);
+
+  struct tm* lctime = [this]() -> tm* {
+    if (this->Impl->TestModel == cmCTest::NIGHTLY) {
+      return this->GetNightlyTime(
+        this->GetCTestConfiguration("NightlyStartTime"),
+        this->Impl->TomorrowTag);
+    }
+    time_t tctime = time(nullptr);
+    if (this->Impl->TomorrowTag) {
+      tctime += (24 * 60 * 60);
+    }
+    return gmtime(&tctime);
+  }();
+
+  char datestring[100];
+  snprintf(datestring, sizeof(datestring), "%04d%02d%02d-%02d%02d",
+           lctime->tm_year + 1900, lctime->tm_mon + 1, lctime->tm_mday,
+           lctime->tm_hour, lctime->tm_min);
+  this->Impl->CurrentTag = datestring;
+
+  cmsys::ofstream ofs(tagfile.c_str());
+  ofs << this->Impl->CurrentTag << std::endl;
+  ofs << this->GetTestGroupString() << std::endl;
+  ofs << this->GetTestModelString() << std::endl;
+
+  return true;
+}
+
+bool cmCTest::ReadExistingTag(bool quiet)
+{
+  std::string const testingDir = this->Impl->BinaryDir + "/Testing";
+  std::string const tagfile = testingDir + "/TAG";
+
+  std::string tag;
+  std::string group;
+  std::string modelStr;
+  int model = cmCTest::UNKNOWN;
+
+  cmsys::ifstream tfin(tagfile.c_str());
+  if (tfin) {
+    cmSystemTools::GetLineFromStream(tfin, tag);
+    cmSystemTools::GetLineFromStream(tfin, group);
+    if (cmSystemTools::GetLineFromStream(tfin, modelStr)) {
+      model = GetTestModelFromString(modelStr);
+    }
+    tfin.close();
+  }
+
+  if (tag.empty()) {
+    if (!quiet) {
+      cmCTestLog(this, ERROR_MESSAGE,
+                 "Cannot read existing TAG file in " << testingDir
+                                                     << std::endl);
+    }
+    return false;
+  }
+
+  if (this->Impl->TestModel == cmCTest::UNKNOWN) {
+    if (model == cmCTest::UNKNOWN) {
+      cmCTestLog(this, ERROR_MESSAGE,
+                 "TAG file does not contain model and "
+                 "no model specified in start command"
+                   << std::endl);
+      return false;
+    }
+
+    this->SetTestModel(model);
+  }
+
+  if (model != this->Impl->TestModel && model != cmCTest::UNKNOWN &&
+      this->Impl->TestModel != cmCTest::UNKNOWN) {
+    cmCTestOptionalLog(this, WARNING,
+                       "Model given in TAG does not match "
+                       "model given in ctest_start()"
+                         << std::endl,
+                       quiet);
+  }
+
+  if (!this->Impl->SpecificGroup.empty() &&
+      group != this->Impl->SpecificGroup) {
+    cmCTestOptionalLog(this, WARNING,
+                       "Group given in TAG does not match "
+                       "group given in ctest_start()"
+                         << std::endl,
+                       quiet);
+  } else {
+    this->Impl->SpecificGroup = group;
+  }
+
+  cmCTestOptionalLog(this, OUTPUT,
+                     "  Use existing tag: " << tag << " - "
+                                            << this->GetTestGroupString()
+                                            << std::endl,
+                     quiet);
+
+  this->Impl->CurrentTag = tag;
+  return true;
+}
+
+void cmCTest::InitializeTesting(const std::string& binary_dir)
+{
+  cmCTestLog(this, DEBUG, "Here: " << __LINE__ << std::endl);
   if (!this->Impl->InteractiveDebugMode) {
     this->BlockTestErrorDiagnostics();
   } else {
@@ -438,31 +564,7 @@
 
   this->UpdateCTestConfiguration();
 
-  cmCTestOptionalLog(this, DEBUG, "Here: " << __LINE__ << std::endl, quiet);
-  if (this->Impl->ProduceXML) {
-    cmCTestOptionalLog(this, DEBUG, "Here: " << __LINE__ << std::endl, quiet);
-    cmCTestOptionalLog(this, OUTPUT,
-                       "   Site: "
-                         << this->GetCTestConfiguration("Site") << std::endl
-                         << "   Build name: "
-                         << cmCTest::SafeBuildIdField(
-                              this->GetCTestConfiguration("BuildName"))
-                         << std::endl,
-                       quiet);
-    cmCTestOptionalLog(this, DEBUG, "Produce XML is on" << std::endl, quiet);
-    if (this->Impl->TestModel == cmCTest::NIGHTLY &&
-        this->GetCTestConfiguration("NightlyStartTime").empty()) {
-      cmCTestOptionalLog(
-        this, WARNING,
-        "WARNING: No nightly start time found please set in CTestConfig.cmake"
-        " or DartConfig.cmake"
-          << std::endl,
-        quiet);
-      cmCTestOptionalLog(this, DEBUG, "Here: " << __LINE__ << std::endl,
-                         quiet);
-      return 0;
-    }
-  }
+  cmCTestLog(this, DEBUG, "Here: " << __LINE__ << std::endl);
 
   cmake cm(cmake::RoleScript, cmState::CTest);
   cm.SetHomeDirectory("");
@@ -470,235 +572,7 @@
   cm.GetCurrentSnapshot().SetDefaultDefinitions();
   cmGlobalGenerator gg(&cm);
   cmMakefile mf(&gg, cm.GetCurrentSnapshot());
-  if (!this->ReadCustomConfigurationFileTree(this->Impl->BinaryDir, &mf)) {
-    cmCTestOptionalLog(
-      this, DEBUG, "Cannot find custom configuration file tree" << std::endl,
-      quiet);
-    return 0;
-  }
-
-  if (this->Impl->ProduceXML) {
-    // Verify "Testing" directory exists:
-    //
-    std::string testingDir = this->Impl->BinaryDir + "/Testing";
-    if (cmSystemTools::FileExists(testingDir)) {
-      if (!cmSystemTools::FileIsDirectory(testingDir)) {
-        cmCTestLog(this, ERROR_MESSAGE,
-                   "File " << testingDir
-                           << " is in the place of the testing directory"
-                           << std::endl);
-        return 0;
-      }
-    } else {
-      if (!cmSystemTools::MakeDirectory(testingDir)) {
-        cmCTestLog(this, ERROR_MESSAGE,
-                   "Cannot create directory " << testingDir << std::endl);
-        return 0;
-      }
-    }
-
-    // Create new "TAG" file or read existing one:
-    //
-    bool createNewTag = true;
-    if (command) {
-      createNewTag = command->ShouldCreateNewTag();
-    }
-
-    std::string tagfile = testingDir + "/TAG";
-    cmsys::ifstream tfin(tagfile.c_str());
-    std::string tag;
-
-    if (createNewTag) {
-      time_t tctime = time(nullptr);
-      if (this->Impl->TomorrowTag) {
-        tctime += (24 * 60 * 60);
-      }
-      struct tm* lctime = gmtime(&tctime);
-      if (tfin && cmSystemTools::GetLineFromStream(tfin, tag)) {
-        int year = 0;
-        int mon = 0;
-        int day = 0;
-        int hour = 0;
-        int min = 0;
-        sscanf(tag.c_str(), "%04d%02d%02d-%02d%02d", &year, &mon, &day, &hour,
-               &min);
-        if (year != lctime->tm_year + 1900 || mon != lctime->tm_mon + 1 ||
-            day != lctime->tm_mday) {
-          tag.clear();
-        }
-        std::string group;
-        if (cmSystemTools::GetLineFromStream(tfin, group) &&
-            !this->Impl->Parts[PartStart] && !command) {
-          this->Impl->SpecificGroup = group;
-        }
-        std::string model;
-        if (cmSystemTools::GetLineFromStream(tfin, model) &&
-            !this->Impl->Parts[PartStart] && !command) {
-          this->Impl->TestModel = GetTestModelFromString(model);
-        }
-        tfin.close();
-      }
-      if (tag.empty() || command || this->Impl->Parts[PartStart]) {
-        cmCTestOptionalLog(
-          this, DEBUG,
-          "TestModel: " << this->GetTestModelString() << std::endl, quiet);
-        cmCTestOptionalLog(this, DEBUG,
-                           "TestModel: " << this->Impl->TestModel << std::endl,
-                           quiet);
-        if (this->Impl->TestModel == cmCTest::NIGHTLY) {
-          lctime = this->GetNightlyTime(
-            this->GetCTestConfiguration("NightlyStartTime"),
-            this->Impl->TomorrowTag);
-        }
-        char datestring[100];
-        snprintf(datestring, sizeof(datestring), "%04d%02d%02d-%02d%02d",
-                 lctime->tm_year + 1900, lctime->tm_mon + 1, lctime->tm_mday,
-                 lctime->tm_hour, lctime->tm_min);
-        tag = datestring;
-        cmsys::ofstream ofs(tagfile.c_str());
-        if (ofs) {
-          ofs << tag << std::endl;
-          ofs << this->GetTestModelString() << std::endl;
-          switch (this->Impl->TestModel) {
-            case cmCTest::EXPERIMENTAL:
-              ofs << "Experimental" << std::endl;
-              break;
-            case cmCTest::NIGHTLY:
-              ofs << "Nightly" << std::endl;
-              break;
-            case cmCTest::CONTINUOUS:
-              ofs << "Continuous" << std::endl;
-              break;
-          }
-        }
-        ofs.close();
-        if (!command) {
-          cmCTestOptionalLog(this, OUTPUT,
-                             "Create new tag: " << tag << " - "
-                                                << this->GetTestModelString()
-                                                << std::endl,
-                             quiet);
-        }
-      }
-    } else {
-      std::string group;
-      std::string modelStr;
-      int model = cmCTest::UNKNOWN;
-
-      if (tfin) {
-        cmSystemTools::GetLineFromStream(tfin, tag);
-        cmSystemTools::GetLineFromStream(tfin, group);
-        if (cmSystemTools::GetLineFromStream(tfin, modelStr)) {
-          model = GetTestModelFromString(modelStr);
-        }
-        tfin.close();
-      }
-
-      if (tag.empty()) {
-        cmCTestLog(this, ERROR_MESSAGE,
-                   "Cannot read existing TAG file in " << testingDir
-                                                       << std::endl);
-        return 0;
-      }
-
-      if (this->Impl->TestModel == cmCTest::UNKNOWN) {
-        if (model == cmCTest::UNKNOWN) {
-          cmCTestLog(this, ERROR_MESSAGE,
-                     "TAG file does not contain model and "
-                     "no model specified in start command"
-                       << std::endl);
-          return 0;
-        }
-
-        this->SetTestModel(model);
-      }
-
-      if (model != this->Impl->TestModel && model != cmCTest::UNKNOWN &&
-          this->Impl->TestModel != cmCTest::UNKNOWN) {
-        cmCTestOptionalLog(this, WARNING,
-                           "Model given in TAG does not match "
-                           "model given in ctest_start()"
-                             << std::endl,
-                           quiet);
-      }
-
-      if (!this->Impl->SpecificGroup.empty() &&
-          group != this->Impl->SpecificGroup) {
-        cmCTestOptionalLog(this, WARNING,
-                           "Group given in TAG does not match "
-                           "group given in ctest_start()"
-                             << std::endl,
-                           quiet);
-      } else {
-        this->Impl->SpecificGroup = group;
-      }
-
-      cmCTestOptionalLog(this, OUTPUT,
-                         "  Use existing tag: " << tag << " - "
-                                                << this->GetTestModelString()
-                                                << std::endl,
-                         quiet);
-    }
-
-    this->Impl->CurrentTag = tag;
-  }
-
-  return 1;
-}
-
-bool cmCTest::InitializeFromCommand(cmCTestStartCommand* command)
-{
-  std::string src_dir = this->GetCTestConfiguration("SourceDirectory");
-  std::string bld_dir = this->GetCTestConfiguration("BuildDirectory");
-  this->Impl->BuildID = "";
-  for (Part p = PartStart; p != PartCount; p = static_cast<Part>(p + 1)) {
-    this->Impl->Parts[p].SubmitFiles.clear();
-  }
-
-  cmMakefile* mf = command->GetMakefile();
-  std::string fname;
-
-  std::string src_dir_fname = cmStrCat(src_dir, "/CTestConfig.cmake");
-  cmSystemTools::ConvertToUnixSlashes(src_dir_fname);
-
-  std::string bld_dir_fname = cmStrCat(bld_dir, "/CTestConfig.cmake");
-  cmSystemTools::ConvertToUnixSlashes(bld_dir_fname);
-
-  if (cmSystemTools::FileExists(bld_dir_fname)) {
-    fname = bld_dir_fname;
-  } else if (cmSystemTools::FileExists(src_dir_fname)) {
-    fname = src_dir_fname;
-  }
-
-  if (!fname.empty()) {
-    cmCTestOptionalLog(this, OUTPUT,
-                       "   Reading ctest configuration file: " << fname
-                                                               << std::endl,
-                       command->ShouldBeQuiet());
-    bool readit = mf->ReadDependentFile(fname);
-    if (!readit) {
-      std::string m = cmStrCat("Could not find include file: ", fname);
-      command->SetError(m);
-      return false;
-    }
-  }
-
-  this->SetCTestConfigurationFromCMakeVariable(mf, "NightlyStartTime",
-                                               "CTEST_NIGHTLY_START_TIME",
-                                               command->ShouldBeQuiet());
-  this->SetCTestConfigurationFromCMakeVariable(mf, "Site", "CTEST_SITE",
-                                               command->ShouldBeQuiet());
-  this->SetCTestConfigurationFromCMakeVariable(
-    mf, "BuildName", "CTEST_BUILD_NAME", command->ShouldBeQuiet());
-
-  if (!this->Initialize(bld_dir, command)) {
-    return false;
-  }
-  cmCTestOptionalLog(this, OUTPUT,
-                     "   Use " << this->GetTestModelString() << " tag: "
-                               << this->GetCurrentTag() << std::endl,
-                     command->ShouldBeQuiet());
-  return true;
+  this->ReadCustomConfigurationFileTree(this->Impl->BinaryDir, &mf);
 }
 
 bool cmCTest::UpdateCTestConfiguration()
@@ -825,10 +699,6 @@
   return false;
 }
 
-void cmCTest::Finalize()
-{
-}
-
 bool cmCTest::OpenOutputFile(const std::string& path, const std::string& name,
                              cmGeneratedFileStream& stream, bool compress)
 {
@@ -894,11 +764,6 @@
   return &this->Impl->BuildHandler;
 }
 
-cmCTestBuildAndTestHandler* cmCTest::GetBuildAndTestHandler()
-{
-  return &this->Impl->BuildAndTestHandler;
-}
-
 cmCTestCoverageHandler* cmCTest::GetCoverageHandler()
 {
   return &this->Impl->CoverageHandler;
@@ -941,77 +806,141 @@
 
 int cmCTest::ProcessSteps()
 {
-  int res = 0;
-  bool notest = true;
-  int update_count = 0;
+  this->Impl->ExtraVerbose = this->Impl->Verbose;
+  this->Impl->Verbose = true;
+  this->Impl->ProduceXML = true;
 
-  for (Part p = PartStart; notest && p != PartCount;
-       p = static_cast<Part>(p + 1)) {
-    notest = !this->Impl->Parts[p];
+  for (auto& handler : this->Impl->GetTestingHandlers()) {
+    handler->SetVerbose(this->Impl->Verbose);
+    handler->SetSubmitIndex(this->Impl->SubmitIndex);
   }
+
+  const std::string currDir = cmSystemTools::GetCurrentWorkingDirectory();
+  std::string workDir = currDir;
+  if (!this->Impl->TestDir.empty()) {
+    workDir = cmSystemTools::ToNormalizedPathOnDisk(this->Impl->TestDir);
+  }
+
+  cmWorkingDirectory changeDir(workDir);
+  if (changeDir.Failed()) {
+    cmCTestLog(this, ERROR_MESSAGE, changeDir.GetError() << std::endl);
+    return 1;
+  }
+
+  this->Impl->BinaryDir = workDir;
+  cmSystemTools::ConvertToUnixSlashes(this->Impl->BinaryDir);
+  this->UpdateCTestConfiguration();
+  this->BlockTestErrorDiagnostics();
+
+  int res = 0;
+  cmCTestScriptHandler script;
+  script.SetCTestInstance(this);
+  script.CreateCMake();
+  cmMakefile& mf = *script.GetMakefile();
+  this->ReadCustomConfigurationFileTree(this->Impl->BinaryDir, &mf);
+  this->SetCMakeVariables(mf);
+  std::vector<cmListFileArgument> args{
+    cmListFileArgument("RETURN_VALUE", cmListFileArgument::Unquoted, 0),
+    cmListFileArgument("return_value", cmListFileArgument::Unquoted, 0),
+  };
+
+  if (this->Impl->Parts[PartStart]) {
+    auto const func = cmListFileFunction(
+      "ctest_start", 0, 0,
+      {
+        { this->GetTestModelString(), cmListFileArgument::Unquoted, 0 },
+        { "GROUP", cmListFileArgument::Unquoted, 0 },
+        { this->GetTestGroupString(), cmListFileArgument::Unquoted, 0 },
+      });
+    auto status = cmExecutionStatus(mf);
+    if (!mf.ExecuteCommand(func, status)) {
+      return 12;
+    }
+  } else if (!this->ReadExistingTag(true) && !this->CreateNewTag(false)) {
+    cmCTestLog(this, ERROR_MESSAGE,
+               "Problem initializing the dashboard." << std::endl);
+    return 12;
+  }
+
   if (this->Impl->Parts[PartUpdate] &&
       (this->GetRemainingTimeAllowed() > std::chrono::minutes(2))) {
-    cmCTestUpdateHandler* uphandler = this->GetUpdateHandler();
-    uphandler->SetPersistentOption(
-      "SourceDirectory", this->GetCTestConfiguration("SourceDirectory"));
-    update_count = uphandler->ProcessHandler();
-    if (update_count < 0) {
+    auto const func = cmListFileFunction("ctest_update", 0, 0, args);
+    auto status = cmExecutionStatus(mf);
+    if (!mf.ExecuteCommand(func, status)) {
       res |= cmCTest::UPDATE_ERRORS;
     }
   }
-  if (this->Impl->TestModel == cmCTest::CONTINUOUS && !update_count) {
+  if (this->Impl->TestModel == cmCTest::CONTINUOUS &&
+      mf.GetDefinition("return_value").IsOff()) {
     return 0;
   }
   if (this->Impl->Parts[PartConfigure] &&
       (this->GetRemainingTimeAllowed() > std::chrono::minutes(2))) {
-    if (this->GetConfigureHandler()->ProcessHandler() < 0) {
+    auto const func = cmListFileFunction("ctest_configure", 0, 0, args);
+    auto status = cmExecutionStatus(mf);
+    if (!mf.ExecuteCommand(func, status) ||
+        std::stoi(mf.GetDefinition("return_value")) < 0) {
       res |= cmCTest::CONFIGURE_ERRORS;
     }
   }
   if (this->Impl->Parts[PartBuild] &&
       (this->GetRemainingTimeAllowed() > std::chrono::minutes(2))) {
     this->UpdateCTestConfiguration();
-    if (this->GetBuildHandler()->ProcessHandler() < 0) {
+    this->SetCMakeVariables(mf);
+    auto const func = cmListFileFunction("ctest_build", 0, 0, args);
+    auto status = cmExecutionStatus(mf);
+    if (!mf.ExecuteCommand(func, status) ||
+        std::stoi(mf.GetDefinition("return_value")) < 0) {
       res |= cmCTest::BUILD_ERRORS;
     }
   }
-  if ((this->Impl->Parts[PartTest] || notest) &&
+  if (this->Impl->Parts[PartTest] &&
       (this->GetRemainingTimeAllowed() > std::chrono::minutes(2))) {
     this->UpdateCTestConfiguration();
-    if (this->GetTestHandler()->ProcessHandler() < 0) {
+    this->SetCMakeVariables(mf);
+    auto const func = cmListFileFunction("ctest_test", 0, 0, args);
+    auto status = cmExecutionStatus(mf);
+    if (!mf.ExecuteCommand(func, status) ||
+        std::stoi(mf.GetDefinition("return_value")) < 0) {
       res |= cmCTest::TEST_ERRORS;
     }
   }
   if (this->Impl->Parts[PartCoverage] &&
       (this->GetRemainingTimeAllowed() > std::chrono::minutes(2))) {
     this->UpdateCTestConfiguration();
-    if (this->GetCoverageHandler()->ProcessHandler() < 0) {
+    this->SetCMakeVariables(mf);
+    auto const func = cmListFileFunction("ctest_coverage", 0, 0, args);
+    auto status = cmExecutionStatus(mf);
+    if (!mf.ExecuteCommand(func, status) ||
+        std::stoi(mf.GetDefinition("return_value")) < 0) {
       res |= cmCTest::COVERAGE_ERRORS;
     }
   }
   if (this->Impl->Parts[PartMemCheck] &&
       (this->GetRemainingTimeAllowed() > std::chrono::minutes(2))) {
     this->UpdateCTestConfiguration();
-    if (this->GetMemCheckHandler()->ProcessHandler() < 0) {
+    this->SetCMakeVariables(mf);
+    auto const func = cmListFileFunction("ctest_memcheck", 0, 0, args);
+    auto status = cmExecutionStatus(mf);
+    if (!mf.ExecuteCommand(func, status) ||
+        std::stoi(mf.GetDefinition("return_value")) < 0) {
       res |= cmCTest::MEMORY_ERRORS;
     }
   }
-  if (!notest) {
-    std::string notes_dir = this->Impl->BinaryDir + "/Testing/Notes";
-    if (cmSystemTools::FileIsDirectory(notes_dir)) {
-      cmsys::Directory d;
-      d.Load(notes_dir);
-      unsigned long kk;
-      for (kk = 0; kk < d.GetNumberOfFiles(); kk++) {
-        const char* file = d.GetFile(kk);
-        std::string fullname = notes_dir + "/" + file;
-        if (cmSystemTools::FileExists(fullname, true)) {
-          if (!this->Impl->NotesFiles.empty()) {
-            this->Impl->NotesFiles += ";";
-          }
-          this->Impl->NotesFiles += fullname;
-          this->Impl->Parts[PartNotes].Enable();
+  std::string notes_dir = this->Impl->BinaryDir + "/Testing/Notes";
+  if (cmSystemTools::FileIsDirectory(notes_dir)) {
+    cmsys::Directory d;
+    d.Load(notes_dir);
+    unsigned long kk;
+    for (kk = 0; kk < d.GetNumberOfFiles(); kk++) {
+      const char* file = d.GetFile(kk);
+      std::string fullname = notes_dir + "/" + file;
+      if (cmSystemTools::FileExists(fullname, true)) {
+        if (!this->Impl->NotesFiles.empty()) {
+          this->Impl->NotesFiles += ";";
         }
+        this->Impl->NotesFiles += fullname;
+        this->Impl->Parts[PartNotes].Enable();
       }
     }
   }
@@ -1023,32 +952,31 @@
   }
   if (this->Impl->Parts[PartSubmit]) {
     this->UpdateCTestConfiguration();
-    if (this->GetSubmitHandler()->ProcessHandler() < 0) {
+    this->SetCMakeVariables(mf);
+
+    std::string count = this->GetCTestConfiguration("CTestSubmitRetryCount");
+    std::string delay = this->GetCTestConfiguration("CTestSubmitRetryDelay");
+    auto const func = cmListFileFunction(
+      "ctest_submit", 0, 0,
+      {
+        cmListFileArgument("RETRY_COUNT", cmListFileArgument::Unquoted, 0),
+        cmListFileArgument(count, cmListFileArgument::Quoted, 0),
+        cmListFileArgument("RETRY_DELAY", cmListFileArgument::Unquoted, 0),
+        cmListFileArgument(delay, cmListFileArgument::Quoted, 0),
+        cmListFileArgument("RETURN_VALUE", cmListFileArgument::Unquoted, 0),
+        cmListFileArgument("return_value", cmListFileArgument::Unquoted, 0),
+      });
+    auto status = cmExecutionStatus(mf);
+    if (!mf.ExecuteCommand(func, status) ||
+        std::stoi(mf.GetDefinition("return_value")) < 0) {
       res |= cmCTest::SUBMIT_ERRORS;
     }
   }
-  if (res != 0) {
-    cmCTestLog(this, ERROR_MESSAGE, "Errors while running CTest" << std::endl);
-    if (!this->Impl->OutputTestOutputOnTestFailure) {
-      const std::string lastTestLog =
-        this->GetBinaryDir() + "/Testing/Temporary/LastTest.log";
-      cmCTestLog(this, ERROR_MESSAGE,
-                 "Output from these tests are in: " << lastTestLog
-                                                    << std::endl);
-      cmCTestLog(this, ERROR_MESSAGE,
-                 "Use \"--rerun-failed --output-on-failure\" to re-run the "
-                 "failed cases verbosely."
-                   << std::endl);
-    }
-  }
   return res;
 }
 
-std::string cmCTest::GetTestModelString()
+std::string cmCTest::GetTestModelString() const
 {
-  if (!this->Impl->SpecificGroup.empty()) {
-    return this->Impl->SpecificGroup;
-  }
   switch (this->Impl->TestModel) {
     case cmCTest::NIGHTLY:
       return "Nightly";
@@ -1058,6 +986,14 @@
   return "Experimental";
 }
 
+std::string cmCTest::GetTestGroupString() const
+{
+  if (!this->Impl->SpecificGroup.empty()) {
+    return this->Impl->SpecificGroup;
+  }
+  return this->GetTestModelString();
+}
+
 int cmCTest::GetTestModelFromString(const std::string& str)
 {
   if (str.empty()) {
@@ -1185,177 +1121,6 @@
   return true;
 }
 
-bool cmCTest::RunTest(const std::vector<std::string>& argv,
-                      std::string* output, int* retVal, std::ostream* log,
-                      cmDuration testTimeOut,
-                      std::vector<std::string>* environment, Encoding encoding)
-{
-  bool modifyEnv = (environment && !environment->empty());
-
-  // determine how much time we have
-  cmDuration timeout = this->GetRemainingTimeAllowed();
-  if (timeout != cmCTest::MaxDuration()) {
-    timeout -= std::chrono::minutes(2);
-  }
-  if (this->Impl->TimeOut > cmDuration::zero() &&
-      this->Impl->TimeOut < timeout) {
-    timeout = this->Impl->TimeOut;
-  }
-  if (testTimeOut > cmDuration::zero() &&
-      testTimeOut < this->GetRemainingTimeAllowed()) {
-    timeout = testTimeOut;
-  }
-
-  // always have at least 1 second if we got to here
-  if (timeout <= cmDuration::zero()) {
-    timeout = std::chrono::seconds(1);
-  }
-  cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
-             "Test timeout computed to be: "
-               << (timeout == cmCTest::MaxDuration()
-                     ? std::string("infinite")
-                     : std::to_string(cmDurationTo<unsigned int>(timeout)))
-               << "\n");
-  if (cmSystemTools::SameFile(argv[0], cmSystemTools::GetCTestCommand()) &&
-      !this->Impl->ForceNewCTestProcess) {
-    cmCTest inst;
-    inst.Impl->ConfigType = this->Impl->ConfigType;
-    inst.Impl->TimeOut = timeout;
-
-    // Capture output of the child ctest.
-    std::ostringstream oss;
-    inst.SetStreams(&oss, &oss);
-
-    std::vector<std::string> args;
-    for (auto const& i : argv) {
-      // make sure we pass the timeout in for any build and test
-      // invocations. Since --build-generator is required this is a
-      // good place to check for it, and to add the arguments in
-      if (i == "--build-generator" && timeout != cmCTest::MaxDuration() &&
-          timeout > cmDuration::zero()) {
-        args.emplace_back("--test-timeout");
-        args.push_back(std::to_string(cmDurationTo<unsigned int>(timeout)));
-      }
-      args.emplace_back(i);
-    }
-    if (log) {
-      *log << "* Run internal CTest" << std::endl;
-    }
-
-    std::unique_ptr<cmSystemTools::SaveRestoreEnvironment> saveEnv;
-    if (modifyEnv) {
-      saveEnv = cm::make_unique<cmSystemTools::SaveRestoreEnvironment>();
-      cmSystemTools::AppendEnv(*environment);
-    }
-
-    *retVal = inst.Run(args, output);
-    if (output) {
-      *output += oss.str();
-    }
-    if (log && output) {
-      *log << *output;
-    }
-    if (output) {
-      cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
-                 "Internal cmCTest object used to run test." << std::endl
-                                                             << *output
-                                                             << std::endl);
-    }
-
-    return true;
-  }
-  std::vector<char> tempOutput;
-  if (output) {
-    output->clear();
-  }
-
-  std::unique_ptr<cmSystemTools::SaveRestoreEnvironment> saveEnv;
-  if (modifyEnv) {
-    saveEnv = cm::make_unique<cmSystemTools::SaveRestoreEnvironment>();
-    cmSystemTools::AppendEnv(*environment);
-  }
-
-  cmUVProcessChainBuilder builder;
-  builder.AddCommand(argv).SetMergedBuiltinStreams();
-  cmCTestLog(this, DEBUG, "Command is: " << argv[0] << std::endl);
-  auto chain = builder.Start();
-
-  cmProcessOutput processOutput(encoding);
-  cm::uv_pipe_ptr outputStream;
-  outputStream.init(chain.GetLoop(), 0);
-  uv_pipe_open(outputStream, chain.OutputStream());
-  auto outputHandle = cmUVStreamRead(
-    outputStream,
-    [this, &processOutput, &output, &tempOutput,
-     &log](std::vector<char> data) {
-      std::string strdata;
-      processOutput.DecodeText(data.data(), data.size(), strdata);
-      if (output) {
-        cm::append(tempOutput, data.data(), data.data() + data.size());
-      }
-      cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, strdata);
-      if (log) {
-        log->write(strdata.c_str(), strdata.size());
-      }
-    },
-    [this, &processOutput, &log]() {
-      std::string strdata;
-      processOutput.DecodeText(std::string(), strdata);
-      if (!strdata.empty()) {
-        cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, strdata);
-        if (log) {
-          log->write(strdata.c_str(), strdata.size());
-        }
-      }
-    });
-
-  bool complete = chain.Wait(static_cast<uint64_t>(timeout.count() * 1000.0));
-  processOutput.DecodeText(tempOutput, tempOutput);
-  if (output && tempOutput.begin() != tempOutput.end()) {
-    output->append(tempOutput.data(), tempOutput.size());
-  }
-  cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
-             "-- Process completed" << std::endl);
-
-  bool result = false;
-
-  if (complete) {
-    auto const& status = chain.GetStatus(0);
-    auto exception = status.GetException();
-    switch (exception.first) {
-      case cmUVProcessChain::ExceptionCode::None:
-        *retVal = static_cast<int>(status.ExitStatus);
-        if (*retVal != 0 && this->Impl->OutputTestOutputOnTestFailure) {
-          this->OutputTestErrors(tempOutput);
-        }
-        result = true;
-        break;
-      case cmUVProcessChain::ExceptionCode::Spawn: {
-        std::string outerr =
-          cmStrCat("\n*** ERROR executing: ", exception.second);
-        if (output) {
-          *output += outerr;
-        }
-        cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, outerr << std::endl);
-      } break;
-      default: {
-        if (this->Impl->OutputTestOutputOnTestFailure) {
-          this->OutputTestErrors(tempOutput);
-        }
-        *retVal = status.TermSignal;
-        std::string outerr =
-          cmStrCat("\n*** Exception executing: ", exception.second);
-        if (output) {
-          *output += outerr;
-        }
-        cmCTestLog(this, HANDLER_VERBOSE_OUTPUT, outerr << std::endl);
-      } break;
-    }
-  }
-
-  return result;
-}
-
 std::string cmCTest::SafeBuildIdField(const std::string& value)
 {
   std::string safevalue(value);
@@ -1405,7 +1170,7 @@
   std::string buildname =
     cmCTest::SafeBuildIdField(this->GetCTestConfiguration("BuildName"));
   std::string stamp = cmCTest::SafeBuildIdField(this->Impl->CurrentTag + "-" +
-                                                this->GetTestModelString());
+                                                this->GetTestGroupString());
   std::string site =
     cmCTest::SafeBuildIdField(this->GetCTestConfiguration("Site"));
 
@@ -1531,7 +1296,7 @@
   xml.StartElement("Site");
   xml.Attribute("BuildName", buildname);
   xml.Attribute("BuildStamp",
-                this->Impl->CurrentTag + "-" + this->GetTestModelString());
+                this->Impl->CurrentTag + "-" + this->GetTestGroupString());
   xml.Attribute("Name", this->GetCTestConfiguration("Site"));
   xml.Attribute("Generator",
                 std::string("ctest-") + cmVersion::GetCMakeVersion());
@@ -1616,31 +1381,14 @@
   return 0;
 }
 
-bool cmCTest::TryToChangeDirectory(std::string const& dir)
-{
-  cmCTestLog(this, OUTPUT,
-             "Internal ctest changing into directory: " << dir << std::endl);
-  cmsys::Status status = cmSystemTools::ChangeDirectory(dir);
-  if (!status) {
-    auto msg = "Failed to change working directory to \"" + dir +
-      "\" : " + status.GetString() + "\n";
-    cmCTestLog(this, ERROR_MESSAGE, msg);
-    return false;
-  }
-  return true;
-}
-
 std::string cmCTest::Base64GzipEncodeFile(std::string const& file)
 {
-  const std::string currDir = cmSystemTools::GetCurrentWorkingDirectory();
-  std::string parentDir = cmSystemTools::GetParentDirectory(file);
-
   // Temporarily change to the file's directory so the tar gets created
   // with a flat directory structure.
-  if (currDir != parentDir) {
-    if (!this->TryToChangeDirectory(parentDir)) {
-      return "";
-    }
+  cmWorkingDirectory workdir(cmSystemTools::GetParentDirectory(file));
+  if (workdir.Failed()) {
+    cmCTestLog(this, ERROR_MESSAGE, workdir.GetError() << std::endl);
+    return "";
   }
 
   std::string tarFile = file + "_temp.tar.gz";
@@ -1657,12 +1405,6 @@
   }
   std::string base64 = this->Base64EncodeFile(tarFile);
   cmSystemTools::RemoveFile(tarFile);
-
-  // Change back to the directory we started in.
-  if (currDir != parentDir) {
-    cmSystemTools::ChangeDirectory(currDir);
-  }
-
   return base64;
 }
 
@@ -1721,7 +1463,7 @@
 
 // for a -D argument convert the next argument into
 // the proper list of dashboard steps via SetTest
-bool cmCTest::AddTestsForDashboardType(std::string& targ)
+bool cmCTest::AddTestsForDashboardType(std::string const& targ)
 {
   if (targ == "Experimental") {
     this->SetTestModel(cmCTest::EXPERIMENTAL);
@@ -1844,7 +1586,7 @@
   return true;
 }
 
-void cmCTest::ErrorMessageUnknownDashDValue(std::string& val)
+void cmCTest::ErrorMessageUnknownDashDValue(std::string const& val)
 {
   cmCTestLog(this, ERROR_MESSAGE,
              "CTest -D called with incorrect option: " << val << '\n');
@@ -1869,389 +1611,6 @@
   return (arg == varg1) || (varg2 && arg == varg2);
 }
 
-// Processes one command line argument (and its arguments if any)
-// for many simple options and then returns
-bool cmCTest::HandleCommandLineArguments(size_t& i,
-                                         std::vector<std::string>& args,
-                                         std::string& errormsg)
-{
-  std::string arg = args[i];
-  cm::string_view noTestsPrefix = "--no-tests=";
-  if (this->CheckArgument(arg, "-F"_s)) {
-    this->Impl->Failover = true;
-  } else if (this->CheckArgument(arg, "-j"_s, "--parallel")) {
-    cm::optional<size_t> parallelLevel;
-    // No value or an empty value tells ctest to choose a default.
-    if (i + 1 < args.size() && !cmHasLiteralPrefix(args[i + 1], "-")) {
-      ++i;
-      if (!args[i].empty()) {
-        // A non-empty value must be a non-negative integer.
-        unsigned long plevel = 0;
-        if (!cmStrToULong(args[i], &plevel)) {
-          errormsg =
-            cmStrCat("'", arg, "' given invalid value '", args[i], "'");
-          return false;
-        }
-        parallelLevel = plevel;
-      }
-    }
-    this->SetParallelLevel(parallelLevel);
-    this->Impl->ParallelLevelSetInCli = true;
-  } else if (cmHasPrefix(arg, "-j")) {
-    // The value must be a non-negative integer.
-    unsigned long plevel = 0;
-    if (!cmStrToULong(arg.substr(2), &plevel)) {
-      errormsg = cmStrCat("'", arg, "' given invalid value '", args[i], "'");
-      return false;
-    }
-    this->SetParallelLevel(plevel);
-    this->Impl->ParallelLevelSetInCli = true;
-  }
-
-  else if (this->CheckArgument(arg, "--repeat-until-fail"_s)) {
-    if (i >= args.size() - 1) {
-      errormsg = "'--repeat-until-fail' requires an argument";
-      return false;
-    }
-    if (this->Impl->RepeatMode != cmCTest::Repeat::Never) {
-      errormsg = "At most one '--repeat' option may be used.";
-      return false;
-    }
-    i++;
-    long repeat = 1;
-    if (!cmStrToLong(args[i], &repeat)) {
-      errormsg = cmStrCat("'--repeat-until-fail' given non-integer value '",
-                          args[i], "'");
-      return false;
-    }
-    this->Impl->RepeatCount = static_cast<int>(repeat);
-    if (repeat > 1) {
-      this->Impl->RepeatMode = cmCTest::Repeat::UntilFail;
-    }
-  }
-
-  else if (this->CheckArgument(arg, "--repeat"_s)) {
-    if (i >= args.size() - 1) {
-      errormsg = "'--repeat' requires an argument";
-      return false;
-    }
-    if (this->Impl->RepeatMode != cmCTest::Repeat::Never) {
-      errormsg = "At most one '--repeat' option may be used.";
-      return false;
-    }
-    i++;
-    cmsys::RegularExpression repeatRegex(
-      "^(until-fail|until-pass|after-timeout):([0-9]+)$");
-    if (repeatRegex.find(args[i])) {
-      std::string const& count = repeatRegex.match(2);
-      unsigned long n = 1;
-      cmStrToULong(count, &n); // regex guarantees success
-      this->Impl->RepeatCount = static_cast<int>(n);
-      if (this->Impl->RepeatCount > 1) {
-        std::string const& mode = repeatRegex.match(1);
-        if (mode == "until-fail") {
-          this->Impl->RepeatMode = cmCTest::Repeat::UntilFail;
-        } else if (mode == "until-pass") {
-          this->Impl->RepeatMode = cmCTest::Repeat::UntilPass;
-        } else if (mode == "after-timeout") {
-          this->Impl->RepeatMode = cmCTest::Repeat::AfterTimeout;
-        }
-      }
-    } else {
-      errormsg = cmStrCat("'--repeat' given invalid value '", args[i], "'");
-      return false;
-    }
-  }
-
-  else if (this->CheckArgument(arg, "--test-load"_s) && i < args.size() - 1) {
-    i++;
-    unsigned long load;
-    if (cmStrToULong(args[i], &load)) {
-      this->SetTestLoad(load);
-    } else {
-      cmCTestLog(this, WARNING,
-                 "Invalid value for 'Test Load' : " << args[i] << '\n');
-    }
-  }
-
-  else if (this->CheckArgument(arg, "--no-compress-output"_s)) {
-    this->Impl->CompressTestOutput = false;
-  }
-
-  else if (this->CheckArgument(arg, "--print-labels"_s)) {
-    this->Impl->PrintLabels = true;
-  }
-
-  else if (this->CheckArgument(arg, "--http1.0"_s)) {
-    this->Impl->UseHTTP10 = true;
-  }
-
-  else if (this->CheckArgument(arg, "--timeout"_s) && i < args.size() - 1) {
-    i++;
-    auto timeout = cmDuration(atof(args[i].c_str()));
-    this->Impl->GlobalTimeout = timeout;
-  }
-
-  else if (this->CheckArgument(arg, "--stop-time"_s) && i < args.size() - 1) {
-    i++;
-    this->SetStopTime(args[i]);
-  }
-
-  else if (this->CheckArgument(arg, "--stop-on-failure"_s)) {
-    this->Impl->StopOnFailure = true;
-  }
-
-  else if (this->CheckArgument(arg, "-C"_s, "--build-config") &&
-           i < args.size() - 1) {
-    i++;
-    this->SetConfigType(args[i]);
-  }
-
-  else if (this->CheckArgument(arg, "--debug"_s)) {
-    this->Impl->Debug = true;
-    this->Impl->ShowLineNumbers = true;
-  } else if ((this->CheckArgument(arg, "--group"_s) ||
-              // This is an undocumented / deprecated option.
-              // "Track" has been renamed to "Group".
-              this->CheckArgument(arg, "--track"_s)) &&
-             i < args.size() - 1) {
-    i++;
-    this->Impl->SpecificGroup = args[i];
-  } else if (this->CheckArgument(arg, "--show-line-numbers"_s)) {
-    this->Impl->ShowLineNumbers = true;
-  } else if (this->CheckArgument(arg, "--no-label-summary"_s)) {
-    this->Impl->LabelSummary = false;
-  } else if (this->CheckArgument(arg, "--no-subproject-summary"_s)) {
-    this->Impl->SubprojectSummary = false;
-  } else if (this->CheckArgument(arg, "-Q"_s, "--quiet")) {
-    this->Impl->Quiet = true;
-  } else if (this->CheckArgument(arg, "--progress"_s)) {
-    this->Impl->TestProgressOutput = true;
-  } else if (this->CheckArgument(arg, "-V"_s, "--verbose")) {
-    this->Impl->Verbose = true;
-  } else if (this->CheckArgument(arg, "-VV"_s, "--extra-verbose")) {
-    this->Impl->ExtraVerbose = true;
-    this->Impl->Verbose = true;
-  } else if (this->CheckArgument(arg, "--output-on-failure"_s)) {
-    this->Impl->OutputTestOutputOnTestFailure = true;
-  } else if (this->CheckArgument(arg, "--test-output-size-passed"_s) &&
-             i < args.size() - 1) {
-    i++;
-    long outputSize;
-    if (cmStrToLong(args[i], &outputSize)) {
-      this->Impl->TestHandler.SetTestOutputSizePassed(
-        static_cast<int>(outputSize));
-    } else {
-      cmCTestLog(this, WARNING,
-                 "Invalid value for '--test-output-size-passed': " << args[i]
-                                                                   << "\n");
-    }
-  } else if (this->CheckArgument(arg, "--test-output-size-failed"_s) &&
-             i < args.size() - 1) {
-    i++;
-    long outputSize;
-    if (cmStrToLong(args[i], &outputSize)) {
-      this->Impl->TestHandler.SetTestOutputSizeFailed(
-        static_cast<int>(outputSize));
-    } else {
-      cmCTestLog(this, WARNING,
-                 "Invalid value for '--test-output-size-failed': " << args[i]
-                                                                   << "\n");
-    }
-  } else if (this->CheckArgument(arg, "--test-output-truncation"_s) &&
-             i < args.size() - 1) {
-    i++;
-    if (!this->Impl->TestHandler.SetTestOutputTruncation(args[i])) {
-      errormsg = "Invalid value for '--test-output-truncation': " + args[i];
-      return false;
-    }
-  } else if (this->CheckArgument(arg, "-N"_s, "--show-only")) {
-    this->Impl->ShowOnly = true;
-  } else if (cmHasLiteralPrefix(arg, "--show-only=")) {
-    this->Impl->ShowOnly = true;
-
-    // Check if a specific format is requested. Defaults to human readable
-    // text.
-    std::string argWithFormat = "--show-only=";
-    std::string format = arg.substr(argWithFormat.length());
-    if (format == "json-v1") {
-      // Force quiet mode so the only output is the json object model.
-      this->Impl->Quiet = true;
-      this->Impl->OutputAsJson = true;
-      this->Impl->OutputAsJsonVersion = 1;
-    } else if (format != "human") {
-      errormsg = "'--show-only=' given unknown value '" + format + "'";
-      return false;
-    }
-  }
-
-  else if (this->CheckArgument(arg, "-O"_s, "--output-log") &&
-           i < args.size() - 1) {
-    i++;
-    this->SetOutputLogFileName(args[i]);
-  }
-
-  else if (this->CheckArgument(arg, "--tomorrow-tag"_s)) {
-    this->Impl->TomorrowTag = true;
-  } else if (this->CheckArgument(arg, "--force-new-ctest-process"_s)) {
-    this->Impl->ForceNewCTestProcess = true;
-  } else if (this->CheckArgument(arg, "-W"_s, "--max-width") &&
-             i < args.size() - 1) {
-    i++;
-    this->Impl->MaxTestNameWidth = atoi(args[i].c_str());
-  } else if (this->CheckArgument(arg, "--interactive-debug-mode"_s) &&
-             i < args.size() - 1) {
-    i++;
-    this->Impl->InteractiveDebugMode = cmIsOn(args[i]);
-  } else if (this->CheckArgument(arg, "--submit-index"_s) &&
-             i < args.size() - 1) {
-    i++;
-    this->Impl->SubmitIndex = atoi(args[i].c_str());
-    if (this->Impl->SubmitIndex < 0) {
-      this->Impl->SubmitIndex = 0;
-    }
-  }
-
-  else if (this->CheckArgument(arg, "--overwrite"_s) && i < args.size() - 1) {
-    i++;
-    this->AddCTestConfigurationOverwrite(args[i]);
-  } else if (this->CheckArgument(arg, "-A"_s, "--add-notes") &&
-             i < args.size() - 1) {
-    this->Impl->ProduceXML = true;
-    this->SetTest("Notes");
-    i++;
-    this->SetNotesFiles(args[i]);
-    return true;
-  } else if (this->CheckArgument(arg, "--test-dir"_s)) {
-    if (i >= args.size() - 1) {
-      errormsg = "'--test-dir' requires an argument";
-      return false;
-    }
-    i++;
-    this->Impl->TestDir = std::string(args[i]);
-  } else if (this->CheckArgument(arg, "--output-junit"_s)) {
-    if (i >= args.size() - 1) {
-      errormsg = "'--output-junit' requires an argument";
-      return false;
-    }
-    i++;
-    this->SetOutputJUnitFileName(std::string(args[i]));
-  }
-
-  else if (cmHasPrefix(arg, noTestsPrefix)) {
-    cm::string_view noTestsMode =
-      cm::string_view(arg).substr(noTestsPrefix.length());
-    if (noTestsMode == "error") {
-      this->Impl->NoTestsMode = cmCTest::NoTests::Error;
-    } else if (noTestsMode != "ignore") {
-      errormsg =
-        cmStrCat("'--no-tests=' given unknown value '", noTestsMode, '\'');
-      return false;
-    } else {
-      this->Impl->NoTestsMode = cmCTest::NoTests::Ignore;
-    }
-    this->Impl->NoTestsModeSetInCli = true;
-  }
-
-  // options that control what tests are run
-  else if (this->CheckArgument(arg, "-I"_s, "--tests-information") &&
-           i < args.size() - 1) {
-    i++;
-    this->GetTestHandler()->SetPersistentOption("TestsToRunInformation",
-                                                args[i]);
-    this->GetMemCheckHandler()->SetPersistentOption("TestsToRunInformation",
-                                                    args[i]);
-  } else if (this->CheckArgument(arg, "-U"_s, "--union")) {
-    this->GetTestHandler()->SetPersistentOption("UseUnion", "true");
-    this->GetMemCheckHandler()->SetPersistentOption("UseUnion", "true");
-  } else if (this->CheckArgument(arg, "-R"_s, "--tests-regex") &&
-             i < args.size() - 1) {
-    i++;
-    this->GetTestHandler()->SetPersistentOption("IncludeRegularExpression",
-                                                args[i]);
-    this->GetMemCheckHandler()->SetPersistentOption("IncludeRegularExpression",
-                                                    args[i]);
-  } else if (this->CheckArgument(arg, "-L"_s, "--label-regex") &&
-             i < args.size() - 1) {
-    i++;
-    this->GetTestHandler()->AddPersistentMultiOption("LabelRegularExpression",
-                                                     args[i]);
-    this->GetMemCheckHandler()->AddPersistentMultiOption(
-      "LabelRegularExpression", args[i]);
-  } else if (this->CheckArgument(arg, "-LE"_s, "--label-exclude") &&
-             i < args.size() - 1) {
-    i++;
-    this->GetTestHandler()->AddPersistentMultiOption(
-      "ExcludeLabelRegularExpression", args[i]);
-    this->GetMemCheckHandler()->AddPersistentMultiOption(
-      "ExcludeLabelRegularExpression", args[i]);
-  }
-
-  else if (this->CheckArgument(arg, "-E"_s, "--exclude-regex") &&
-           i < args.size() - 1) {
-    i++;
-    this->GetTestHandler()->SetPersistentOption("ExcludeRegularExpression",
-                                                args[i]);
-    this->GetMemCheckHandler()->SetPersistentOption("ExcludeRegularExpression",
-                                                    args[i]);
-  }
-
-  else if (this->CheckArgument(arg, "-FA"_s, "--fixture-exclude-any") &&
-           i < args.size() - 1) {
-    i++;
-    this->GetTestHandler()->SetPersistentOption(
-      "ExcludeFixtureRegularExpression", args[i]);
-    this->GetMemCheckHandler()->SetPersistentOption(
-      "ExcludeFixtureRegularExpression", args[i]);
-  } else if (this->CheckArgument(arg, "-FS"_s, "--fixture-exclude-setup") &&
-             i < args.size() - 1) {
-    i++;
-    this->GetTestHandler()->SetPersistentOption(
-      "ExcludeFixtureSetupRegularExpression", args[i]);
-    this->GetMemCheckHandler()->SetPersistentOption(
-      "ExcludeFixtureSetupRegularExpression", args[i]);
-  } else if (this->CheckArgument(arg, "-FC"_s, "--fixture-exclude-cleanup") &&
-             i < args.size() - 1) {
-    i++;
-    this->GetTestHandler()->SetPersistentOption(
-      "ExcludeFixtureCleanupRegularExpression", args[i]);
-    this->GetMemCheckHandler()->SetPersistentOption(
-      "ExcludeFixtureCleanupRegularExpression", args[i]);
-  }
-
-  else if (this->CheckArgument(arg, "--resource-spec-file"_s) &&
-           i < args.size() - 1) {
-    i++;
-    this->GetTestHandler()->SetPersistentOption("ResourceSpecFile", args[i]);
-    this->GetMemCheckHandler()->SetPersistentOption("ResourceSpecFile",
-                                                    args[i]);
-  }
-
-  else if (this->CheckArgument(arg, "--tests-from-file"_s) &&
-           i < args.size() - 1) {
-    i++;
-    this->GetTestHandler()->SetPersistentOption("TestListFile", args[i]);
-    this->GetMemCheckHandler()->SetPersistentOption("TestListFile", args[i]);
-  }
-
-  else if (this->CheckArgument(arg, "--exclude-from-file"_s) &&
-           i < args.size() - 1) {
-    i++;
-    this->GetTestHandler()->SetPersistentOption("ExcludeTestListFile",
-                                                args[i]);
-    this->GetMemCheckHandler()->SetPersistentOption("ExcludeTestListFile",
-                                                    args[i]);
-  }
-
-  else if (this->CheckArgument(arg, "--rerun-failed"_s)) {
-    this->GetTestHandler()->SetPersistentOption("RerunFailed", "true");
-    this->GetMemCheckHandler()->SetPersistentOption("RerunFailed", "true");
-  } else {
-    return false;
-  }
-  return true;
-}
-
 #if !defined(_WIN32)
 bool cmCTest::ConsoleIsNotDumb()
 {
@@ -2296,46 +1655,6 @@
 #endif
 }
 
-// handle the -S -SR and -SP arguments
-bool cmCTest::HandleScriptArguments(size_t& i, std::vector<std::string>& args,
-                                    bool& SRArgumentSpecified)
-{
-  std::string arg = args[i];
-  if (this->CheckArgument(arg, "-SP"_s, "--script-new-process") &&
-      i < args.size() - 1) {
-    this->Impl->RunConfigurationScript = true;
-    i++;
-    cmCTestScriptHandler* ch = this->GetScriptHandler();
-    // -SR is an internal argument, -SP should be ignored when it is passed
-    if (!SRArgumentSpecified) {
-      ch->AddConfigurationScript(args[i], false);
-    }
-  }
-
-  else if (this->CheckArgument(arg, "-SR"_s, "--script-run") &&
-           i < args.size() - 1) {
-    SRArgumentSpecified = true;
-    this->Impl->RunConfigurationScript = true;
-    i++;
-    cmCTestScriptHandler* ch = this->GetScriptHandler();
-    ch->AddConfigurationScript(args[i], true);
-  }
-
-  else if (this->CheckArgument(arg, "-S"_s, "--script") &&
-           i < args.size() - 1) {
-    this->Impl->RunConfigurationScript = true;
-    i++;
-    cmCTestScriptHandler* ch = this->GetScriptHandler();
-    // -SR is an internal argument, -S should be ignored when it is passed
-    if (!SRArgumentSpecified) {
-      ch->AddConfigurationScript(args[i], true);
-    }
-  } else {
-    return false;
-  }
-  return true;
-}
-
 bool cmCTest::AddVariableDefinition(const std::string& arg)
 {
   std::string name;
@@ -2485,8 +1804,6 @@
     }
 
     this->Impl->Debug = expandedPreset->Output->Debug.value_or(false);
-    this->Impl->ShowLineNumbers =
-      expandedPreset->Output->Debug.value_or(false);
     this->Impl->OutputTestOutputOnTestFailure =
       expandedPreset->Output->OutputOnFailure.value_or(false);
     this->Impl->Quiet = expandedPreset->Output->Quiet.value_or(false);
@@ -2675,12 +1992,13 @@
 }
 
 // the main entry point of ctest, called from main
-int cmCTest::Run(std::vector<std::string>& args, std::string* output)
+int cmCTest::Run(std::vector<std::string> const& args)
 {
   const char* ctestExec = "ctest";
   bool cmakeAndTest = false;
-  bool executeTests = true;
+  bool processSteps = false;
   bool SRArgumentSpecified = false;
+  std::vector<std::pair<std::string, bool>> runScripts;
 
   // copy the command line
   cm::append(this->Impl->InitialCommandLineArguments, args);
@@ -2702,10 +2020,10 @@
       success = this->SetArgsFromPreset("", listPresets);
     } else {
       if (cmHasLiteralPrefix(*it, "--preset=")) {
-        auto presetName = it->substr(9);
+        auto const& presetName = it->substr(9);
         success = this->SetArgsFromPreset(presetName, listPresets);
       } else if (++it != args.end()) {
-        auto presetName = *it;
+        auto const& presetName = *it;
         success = this->SetArgsFromPreset(presetName, listPresets);
       } else {
         cmSystemTools::Error("'--preset' requires an argument");
@@ -2722,103 +2040,712 @@
     }
   }
 
+  auto const dashD = [this, &processSteps](std::string const& targ) -> bool {
+    // AddTestsForDashboard parses the dashboard type and converts it
+    // into the separate stages
+    if (this->AddTestsForDashboardType(targ)) {
+      processSteps = true;
+      return true;
+    }
+    if (this->AddVariableDefinition(targ)) {
+      return true;
+    }
+    this->ErrorMessageUnknownDashDValue(targ);
+    return false;
+  };
+  auto const dashT = [this, &processSteps,
+                      ctestExec](std::string const& action) -> bool {
+    if (!this->SetTest(action, false)) {
+      cmCTestLog(this, ERROR_MESSAGE,
+                 "CTest -T called with incorrect option: " << action << '\n');
+      /* clang-format off */
+      cmCTestLog(this, ERROR_MESSAGE,
+                 "Available options are:\n"
+                 "  " << ctestExec << " -T all\n"
+                 "  " << ctestExec << " -T start\n"
+                 "  " << ctestExec << " -T update\n"
+                 "  " << ctestExec << " -T configure\n"
+                 "  " << ctestExec << " -T build\n"
+                 "  " << ctestExec << " -T test\n"
+                 "  " << ctestExec << " -T coverage\n"
+                 "  " << ctestExec << " -T memcheck\n"
+                 "  " << ctestExec << " -T notes\n"
+                 "  " << ctestExec << " -T submit\n");
+      /* clang-format on */
+      return false;
+    }
+    processSteps = true;
+    return true;
+  };
+  auto const dashM = [this, &processSteps,
+                      ctestExec](std::string const& model) -> bool {
+    if (cmSystemTools::LowerCase(model) == "nightly"_s) {
+      this->SetTestModel(cmCTest::NIGHTLY);
+    } else if (cmSystemTools::LowerCase(model) == "continuous"_s) {
+      this->SetTestModel(cmCTest::CONTINUOUS);
+    } else if (cmSystemTools::LowerCase(model) == "experimental"_s) {
+      this->SetTestModel(cmCTest::EXPERIMENTAL);
+    } else {
+      cmCTestLog(this, ERROR_MESSAGE,
+                 "CTest -M called with incorrect option: " << model << '\n');
+      /* clang-format off */
+           cmCTestLog(this, ERROR_MESSAGE,
+                      "Available options are:\n"
+                      "  " << ctestExec << " -M Continuous\n"
+                      "  " << ctestExec << " -M Experimental\n"
+                      "  " << ctestExec << " -M Nightly\n");
+      /* clang-format on */
+      return false;
+    }
+    processSteps = true;
+    return true;
+  };
+  auto const dashSP =
+    [&runScripts, &SRArgumentSpecified](std::string const& script) -> bool {
+    // -SR is an internal argument, -SP should be ignored when it is passed
+    if (!SRArgumentSpecified) {
+      runScripts.emplace_back(cmSystemTools::ToNormalizedPathOnDisk(script),
+                              false);
+    }
+    return true;
+  };
+  auto const dashSR =
+    [&runScripts, &SRArgumentSpecified](std::string const& script) -> bool {
+    SRArgumentSpecified = true;
+    runScripts.emplace_back(cmSystemTools::ToNormalizedPathOnDisk(script),
+                            true);
+    return true;
+  };
+  auto const dash_S =
+    [&runScripts, &SRArgumentSpecified](std::string const& script) -> bool {
+    // -SR is an internal argument, -S should be ignored when it is passed
+    if (!SRArgumentSpecified) {
+      runScripts.emplace_back(cmSystemTools::ToNormalizedPathOnDisk(script),
+                              true);
+    }
+    return true;
+  };
+  auto const dashJ = [this](cm::string_view arg,
+                            std::string const& j) -> bool {
+    cm::optional<size_t> parallelLevel;
+    // No value or an empty value tells ctest to choose a default.
+    if (!j.empty()) {
+      // A non-empty value must be a non-negative integer.
+      unsigned long plevel = 0;
+      if (!cmStrToULong(j, &plevel)) {
+        cmSystemTools::Error(
+          cmStrCat('\'', arg, "' given invalid value '", j, '\''));
+        return false;
+      }
+      parallelLevel = plevel;
+    }
+    this->SetParallelLevel(parallelLevel);
+    this->Impl->ParallelLevelSetInCli = true;
+    return true;
+  };
+  auto const dashC = [this](std::string const& config) -> bool {
+    this->SetConfigType(config);
+    return true;
+  };
+  auto const dashGroup = [this](std::string const& group) -> bool {
+    this->Impl->SpecificGroup = group;
+    return true;
+  };
+  auto const dashQ = [this](std::string const&) -> bool {
+    this->Impl->Quiet = true;
+    return true;
+  };
+  auto const dashV = [this](std::string const&) -> bool {
+    this->Impl->Verbose = true;
+    return true;
+  };
+  auto const dashVV = [this](std::string const&) -> bool {
+    this->Impl->ExtraVerbose = true;
+    this->Impl->Verbose = true;
+    return true;
+  };
+  auto const dashO = [this](std::string const& log) -> bool {
+    this->SetOutputLogFileName(log);
+    return true;
+  };
+  auto const dashW = [this](std::string const& width) -> bool {
+    this->Impl->MaxTestNameWidth = atoi(width.c_str());
+    return true;
+  };
+  auto const dashA = [this, &processSteps](std::string const& notes) -> bool {
+    processSteps = true;
+    this->SetTest("Notes");
+    this->SetNotesFiles(notes);
+    return true;
+  };
+  auto const dashI = [this](std::string const& tests) -> bool {
+    this->Impl->TestHandler.SetPersistentOption("TestsToRunInformation",
+                                                tests);
+    this->Impl->MemCheckHandler.SetPersistentOption("TestsToRunInformation",
+                                                    tests);
+    return true;
+  };
+  auto const dashU = [this](std::string const&) -> bool {
+    this->Impl->TestHandler.SetPersistentOption("UseUnion", "true");
+    this->Impl->MemCheckHandler.SetPersistentOption("UseUnion", "true");
+    return true;
+  };
+  auto const dashR = [this](std::string const& expr) -> bool {
+    this->Impl->TestHandler.SetPersistentOption("IncludeRegularExpression",
+                                                expr);
+    this->Impl->MemCheckHandler.SetPersistentOption("IncludeRegularExpression",
+                                                    expr);
+    return true;
+  };
+  auto const dashE = [this](std::string const& expr) -> bool {
+    this->Impl->TestHandler.SetPersistentOption("ExcludeRegularExpression",
+                                                expr);
+    this->Impl->MemCheckHandler.SetPersistentOption("ExcludeRegularExpression",
+                                                    expr);
+    return true;
+  };
+  auto const dashL = [this](std::string const& expr) -> bool {
+    this->Impl->TestHandler.AddPersistentMultiOption("LabelRegularExpression",
+                                                     expr);
+    this->Impl->MemCheckHandler.AddPersistentMultiOption(
+      "LabelRegularExpression", expr);
+    return true;
+  };
+  auto const dashLE = [this](std::string const& expr) -> bool {
+    this->Impl->TestHandler.AddPersistentMultiOption(
+      "ExcludeLabelRegularExpression", expr);
+    this->Impl->MemCheckHandler.AddPersistentMultiOption(
+      "ExcludeLabelRegularExpression", expr);
+    return true;
+  };
+  auto const dashFA = [this](std::string const& expr) -> bool {
+    this->Impl->TestHandler.SetPersistentOption(
+      "ExcludeFixtureRegularExpression", expr);
+    this->Impl->MemCheckHandler.SetPersistentOption(
+      "ExcludeFixtureRegularExpression", expr);
+    return true;
+  };
+  auto const dashFS = [this](std::string const& expr) -> bool {
+    this->Impl->TestHandler.SetPersistentOption(
+      "ExcludeFixtureSetupRegularExpression", expr);
+    this->Impl->MemCheckHandler.SetPersistentOption(
+      "ExcludeFixtureSetupRegularExpression", expr);
+    return true;
+  };
+  auto const dashFC = [this](std::string const& expr) -> bool {
+    this->Impl->TestHandler.SetPersistentOption(
+      "ExcludeFixtureCleanupRegularExpression", expr);
+    this->Impl->MemCheckHandler.SetPersistentOption(
+      "ExcludeFixtureCleanupRegularExpression", expr);
+    return true;
+  };
+
+  using CommandArgument =
+    cmCommandLineArgument<bool(std::string const& value)>;
+
+  auto const arguments = std::vector<CommandArgument>{
+    CommandArgument{ "--dashboard", CommandArgument::Values::One, dashD },
+    CommandArgument{ "-D",
+                     "-D must be followed by dashboard mode or VAR=VALUE.",
+                     CommandArgument::Values::One, dashD },
+    CommandArgument{
+      "-D", "-D must be followed by dashboard mode or VAR=VALUE.",
+      CommandArgument::Values::One, CommandArgument::RequiresSeparator::No,
+      [this](std::string const& def) -> bool {
+        // Unsuccessful parsing of VAR=VALUE has historically
+        // been ignored.
+        this->AddVariableDefinition(def);
+        return true;
+      } },
+    CommandArgument{ "-T", CommandArgument::Values::One, dashT },
+    CommandArgument{ "--test-action", CommandArgument::Values::One, dashT },
+    CommandArgument{ "-M", CommandArgument::Values::One, dashM },
+    CommandArgument{ "--test-model", CommandArgument::Values::One, dashM },
+    CommandArgument{ "--extra-submit", CommandArgument::Values::One,
+                     [this, &processSteps](std::string const& extra) -> bool {
+                       processSteps = true;
+                       this->SetTest("Submit");
+                       return this->SubmitExtraFiles(extra);
+                     } },
+    CommandArgument{
+      "--build-and-test", "--build-and-test must have source and binary dir",
+      CommandArgument::Values::Two,
+      [this, &cmakeAndTest](std::string const& dirs) -> bool {
+        cmakeAndTest = true;
+        cmList dirList{ dirs };
+        if (dirList.size() != 2) {
+          return false;
+        }
+        this->Impl->BuildAndTest.SourceDir =
+          cmSystemTools::ToNormalizedPathOnDisk(dirList[0]);
+        this->Impl->BuildAndTest.BinaryDir =
+          cmSystemTools::ToNormalizedPathOnDisk(dirList[1]);
+        cmSystemTools::MakeDirectory(this->Impl->BuildAndTest.BinaryDir);
+        return true;
+      } },
+    CommandArgument{ "--build-target", CommandArgument::Values::One,
+                     [this](std::string const& t) -> bool {
+                       this->Impl->BuildAndTest.BuildTargets.emplace_back(t);
+                       return true;
+                     } },
+    CommandArgument{ "--build-noclean", CommandArgument::Values::Zero,
+                     [this](std::string const&) -> bool {
+                       this->Impl->BuildAndTest.BuildNoClean = true;
+                       return true;
+                     } },
+    CommandArgument{ "--build-nocmake", CommandArgument::Values::Zero,
+                     [this](std::string const&) -> bool {
+                       this->Impl->BuildAndTest.BuildNoCMake = true;
+                       return true;
+                     } },
+    CommandArgument{ "--build-two-config", CommandArgument::Values::Zero,
+                     [this](std::string const&) -> bool {
+                       this->Impl->BuildAndTest.BuildTwoConfig = true;
+                       return true;
+                     } },
+    CommandArgument{ "--build-run-dir", CommandArgument::Values::One,
+                     [this](std::string const& dir) -> bool {
+                       this->Impl->BuildAndTest.BuildRunDir = dir;
+                       return true;
+                     } },
+    CommandArgument{ "--build-exe-dir", CommandArgument::Values::One,
+                     [this](std::string const& dir) -> bool {
+                       this->Impl->BuildAndTest.ExecutableDirectory = dir;
+                       return true;
+                     } },
+    CommandArgument{ "--test-timeout", CommandArgument::Values::One,
+                     [this](std::string const& t) -> bool {
+                       this->Impl->BuildAndTest.Timeout =
+                         cmDuration(atof(t.c_str()));
+                       return true;
+                     } },
+    CommandArgument{ "--build-generator", CommandArgument::Values::One,
+                     [this](std::string const& g) -> bool {
+                       this->Impl->BuildAndTest.BuildGenerator = g;
+                       return true;
+                     } },
+    CommandArgument{ "--build-generator-platform",
+                     CommandArgument::Values::One,
+                     [this](std::string const& p) -> bool {
+                       this->Impl->BuildAndTest.BuildGeneratorPlatform = p;
+                       return true;
+                     } },
+    CommandArgument{ "--build-generator-toolset", CommandArgument::Values::One,
+                     [this](std::string const& t) -> bool {
+                       this->Impl->BuildAndTest.BuildGeneratorToolset = t;
+                       return true;
+                     } },
+    CommandArgument{ "--build-project", CommandArgument::Values::One,
+                     [this](std::string const& p) -> bool {
+                       this->Impl->BuildAndTest.BuildProject = p;
+                       return true;
+                     } },
+    CommandArgument{ "--build-makeprogram", CommandArgument::Values::One,
+                     [this](std::string const& p) -> bool {
+                       this->Impl->BuildAndTest.BuildMakeProgram = p;
+                       return true;
+                     } },
+    CommandArgument{ "--build-config-sample", CommandArgument::Values::One,
+                     [this](std::string const& s) -> bool {
+                       this->Impl->BuildAndTest.ConfigSample = s;
+                       return true;
+                     } },
+    CommandArgument{ "-SP", CommandArgument::Values::One, dashSP },
+    CommandArgument{ "--script-new-process", CommandArgument::Values::One,
+                     dashSP },
+    CommandArgument{ "-SR", CommandArgument::Values::One, dashSR },
+    CommandArgument{ "--script-run", CommandArgument::Values::One, dashSR },
+    CommandArgument{ "-S", CommandArgument::Values::One, dash_S },
+    CommandArgument{ "--script", CommandArgument::Values::One, dash_S },
+    CommandArgument{ "-F", CommandArgument::Values::Zero,
+                     [this](std::string const&) -> bool {
+                       this->Impl->Failover = true;
+                       return true;
+                     } },
+    CommandArgument{
+      "-j", CommandArgument::Values::ZeroOrOne,
+      [&dashJ](std::string const& j) -> bool { return dashJ("-j"_s, j); } },
+    CommandArgument{ "--parallel", CommandArgument::Values::ZeroOrOne,
+                     [&dashJ](std::string const& j) -> bool {
+                       return dashJ("--parallel"_s, j);
+                     } },
+    CommandArgument{ "-j", CommandArgument::Values::One,
+                     CommandArgument::RequiresSeparator::No,
+                     [this](std::string const& j) -> bool {
+                       // The value must be a non-negative integer.
+                       unsigned long plevel = 0;
+                       if (!cmStrToULong(j, &plevel)) {
+                         cmSystemTools::Error(
+                           cmStrCat("'-j' given invalid value '", j, '\''));
+                         return false;
+                       }
+                       this->SetParallelLevel(plevel);
+                       this->Impl->ParallelLevelSetInCli = true;
+                       return true;
+                     } },
+    CommandArgument{
+      "--repeat-until-fail", "'--repeat-until-fail' requires an argument",
+      CommandArgument::Values::One,
+      [this](std::string const& r) -> bool {
+        if (this->Impl->RepeatMode != cmCTest::Repeat::Never) {
+          cmSystemTools::Error("At most one '--repeat' option may be used.");
+          return false;
+        }
+        long repeat = 1;
+        if (!cmStrToLong(r, &repeat)) {
+          cmSystemTools::Error(cmStrCat(
+            "'--repeat-until-fail' given non-integer value '", r, '\''));
+          return false;
+        }
+        this->Impl->RepeatCount = static_cast<int>(repeat);
+        if (repeat > 1) {
+          this->Impl->RepeatMode = cmCTest::Repeat::UntilFail;
+        }
+        return true;
+      } },
+    CommandArgument{
+      "--repeat", CommandArgument::Values::One,
+      [this](std::string const& r) -> bool {
+        if (this->Impl->RepeatMode != cmCTest::Repeat::Never) {
+          cmSystemTools::Error("At most one '--repeat' option may be used.");
+          return false;
+        }
+        cmsys::RegularExpression repeatRegex(
+          "^(until-fail|until-pass|after-timeout):([0-9]+)$");
+        if (repeatRegex.find(r)) {
+          std::string const& count = repeatRegex.match(2);
+          unsigned long n = 1;
+          cmStrToULong(count, &n); // regex guarantees success
+          this->Impl->RepeatCount = static_cast<int>(n);
+          if (this->Impl->RepeatCount > 1) {
+            std::string const& mode = repeatRegex.match(1);
+            if (mode == "until-fail") {
+              this->Impl->RepeatMode = cmCTest::Repeat::UntilFail;
+            } else if (mode == "until-pass") {
+              this->Impl->RepeatMode = cmCTest::Repeat::UntilPass;
+            } else if (mode == "after-timeout") {
+              this->Impl->RepeatMode = cmCTest::Repeat::AfterTimeout;
+            }
+          }
+        } else {
+          cmSystemTools::Error(
+            cmStrCat("'--repeat' given invalid value '", r, '\''));
+          return false;
+        }
+        return true;
+      } },
+    CommandArgument{ "--test-load", CommandArgument::Values::One,
+                     [this](std::string const& l) -> bool {
+                       unsigned long load;
+                       if (cmStrToULong(l, &load)) {
+                         this->SetTestLoad(load);
+                       } else {
+                         cmCTestLog(
+                           this, WARNING,
+                           "Invalid value for 'Test Load' : " << l << '\n');
+                       }
+                       return true;
+                     } },
+    CommandArgument{ "--no-compress-output", CommandArgument::Values::Zero,
+                     [this](std::string const&) -> bool {
+                       this->Impl->CompressTestOutput = false;
+                       return true;
+                     } },
+    CommandArgument{ "--print-labels", CommandArgument::Values::Zero,
+                     [this](std::string const&) -> bool {
+                       this->Impl->PrintLabels = true;
+                       return true;
+                     } },
+    CommandArgument{ "--http1.0", CommandArgument::Values::Zero,
+                     [this](std::string const&) -> bool {
+                       this->Impl->UseHTTP10 = true;
+                       return true;
+                     } },
+    CommandArgument{ "--timeout", CommandArgument::Values::One,
+                     [this](std::string const& t) -> bool {
+                       auto timeout = cmDuration(atof(t.c_str()));
+                       this->Impl->GlobalTimeout = timeout;
+                       return true;
+                     } },
+    CommandArgument{ "--stop-time", CommandArgument::Values::One,
+                     [this](std::string const& t) -> bool {
+                       this->SetStopTime(t);
+                       return true;
+                     } },
+    CommandArgument{ "--stop-on-failure", CommandArgument::Values::Zero,
+                     [this](std::string const&) -> bool {
+                       this->Impl->StopOnFailure = true;
+                       return true;
+                     } },
+    CommandArgument{ "-C", CommandArgument::Values::One, dashC },
+    CommandArgument{ "--build-config", CommandArgument::Values::One, dashC },
+    CommandArgument{ "--debug", CommandArgument::Values::Zero,
+                     [this](std::string const&) -> bool {
+                       this->Impl->Debug = true;
+                       return true;
+                     } },
+    CommandArgument{ "--group", CommandArgument::Values::One, dashGroup },
+    // This is an undocumented / deprecated option.
+    // "Track" has been renamed to "Group".
+    CommandArgument{ "--track", CommandArgument::Values::One, dashGroup },
+    CommandArgument{ "--show-line-numbers", CommandArgument::Values::Zero,
+                     [](std::string const&) -> bool {
+                       // Silently ignore this never-documented and now-removed
+                       // option.
+                       return true;
+                     } },
+    CommandArgument{ "--no-label-summary", CommandArgument::Values::Zero,
+                     [this](std::string const&) -> bool {
+                       this->Impl->LabelSummary = false;
+                       return true;
+                     } },
+    CommandArgument{ "--no-subproject-summary", CommandArgument::Values::Zero,
+                     [this](std::string const&) -> bool {
+                       this->Impl->SubprojectSummary = false;
+                       return true;
+                     } },
+    CommandArgument{ "--progress", CommandArgument::Values::Zero,
+                     [this](std::string const&) -> bool {
+                       this->Impl->TestProgressOutput = true;
+                       return true;
+                     } },
+    CommandArgument{ "-Q", CommandArgument::Values::Zero, dashQ },
+    CommandArgument{ "--quiet", CommandArgument::Values::Zero, dashQ },
+    CommandArgument{ "-V", CommandArgument::Values::Zero, dashV },
+    CommandArgument{ "--verbose", CommandArgument::Values::Zero, dashV },
+    CommandArgument{ "-VV", CommandArgument::Values::Zero, dashVV },
+    CommandArgument{ "--extra-verbose", CommandArgument::Values::Zero,
+                     dashVV },
+    CommandArgument{ "--output-on-failure", CommandArgument::Values::Zero,
+                     [this](std::string const&) -> bool {
+                       this->Impl->OutputTestOutputOnTestFailure = true;
+                       return true;
+                     } },
+    CommandArgument{ "--test-output-size-passed", CommandArgument::Values::One,
+                     [this](std::string const& sz) -> bool {
+                       long outputSize;
+                       if (cmStrToLong(sz, &outputSize)) {
+                         this->Impl->TestHandler.SetTestOutputSizePassed(
+                           static_cast<int>(outputSize));
+                       } else {
+                         cmCTestLog(
+                           this, WARNING,
+                           "Invalid value for '--test-output-size-passed': "
+                             << sz << "\n");
+                       }
+                       return true;
+                     } },
+    CommandArgument{ "--test-output-size-failed", CommandArgument::Values::One,
+                     [this](std::string const& sz) -> bool {
+                       long outputSize;
+                       if (cmStrToLong(sz, &outputSize)) {
+                         this->Impl->TestHandler.SetTestOutputSizeFailed(
+                           static_cast<int>(outputSize));
+                       } else {
+                         cmCTestLog(
+                           this, WARNING,
+                           "Invalid value for '--test-output-size-failed': "
+                             << sz << "\n");
+                       }
+                       return true;
+                     } },
+    CommandArgument{
+      "--test-output-truncation", CommandArgument::Values::One,
+      [this](std::string const& mode) -> bool {
+        if (!this->Impl->TestHandler.SetTestOutputTruncation(mode)) {
+          cmSystemTools::Error(
+            cmStrCat("Invalid value for '--test-output-truncation': ", mode));
+          return false;
+        }
+        return true;
+      } },
+    CommandArgument{ "--show-only", CommandArgument::Values::ZeroOrOne,
+                     [this](std::string const& format) -> bool {
+                       this->Impl->ShowOnly = true;
+                       // Check if a specific format is requested.
+                       // Defaults to human readable text.
+                       if (format == "json-v1") {
+                         // Force quiet mode so the only output
+                         // is the json object model.
+                         this->Impl->Quiet = true;
+                         this->Impl->OutputAsJson = true;
+                         this->Impl->OutputAsJsonVersion = 1;
+                       } else if (format == "human") {
+                       } else if (!format.empty()) {
+                         cmSystemTools::Error(
+                           cmStrCat("'--show-only=' given unknown value '",
+                                    format, '\''));
+                         return false;
+                       }
+                       return true;
+                     } },
+    CommandArgument{ "-N", CommandArgument::Values::Zero,
+                     [this](std::string const&) -> bool {
+                       this->Impl->ShowOnly = true;
+                       return true;
+                     } },
+    CommandArgument{ "-O", CommandArgument::Values::One, dashO },
+    CommandArgument{ "--output-log", CommandArgument::Values::One, dashO },
+    CommandArgument{ "--tomorrow-tag", CommandArgument::Values::Zero,
+                     [this](std::string const&) -> bool {
+                       this->Impl->TomorrowTag = true;
+                       return true;
+                     } },
+    CommandArgument{ "--force-new-ctest-process",
+                     CommandArgument::Values::Zero,
+                     [](std::string const&) -> bool {
+                       // Silently ignore now-removed option.
+                       return true;
+                     } },
+    CommandArgument{ "-W", CommandArgument::Values::One, dashW },
+    CommandArgument{ "--max-width", CommandArgument::Values::One, dashW },
+    CommandArgument{ "--interactive-debug-mode", CommandArgument::Values::One,
+                     [this](std::string const& idm) -> bool {
+                       this->Impl->InteractiveDebugMode = cmIsOn(idm);
+                       return true;
+                     } },
+    CommandArgument{ "--http-header", CommandArgument::Values::One,
+                     [this](std::string const& h) -> bool {
+                       this->Impl->SubmitHandler.AddCommandLineHttpHeader(h);
+                       return true;
+                     } },
+    CommandArgument{ "--submit-index", CommandArgument::Values::One,
+                     [this](std::string const& index) -> bool {
+                       this->Impl->SubmitIndex = atoi(index.c_str());
+                       if (this->Impl->SubmitIndex < 0) {
+                         this->Impl->SubmitIndex = 0;
+                       }
+                       return true;
+                     } },
+    CommandArgument{ "--overwrite", CommandArgument::Values::One,
+                     [this](std::string const& opt) -> bool {
+                       this->AddCTestConfigurationOverwrite(opt);
+                       return true;
+                     } },
+    CommandArgument{ "-A", CommandArgument::Values::One, dashA },
+    CommandArgument{ "--add-notes", CommandArgument::Values::One, dashA },
+    CommandArgument{ "--test-dir", "'--test-dir' requires an argument",
+                     CommandArgument::Values::One,
+                     [this](std::string const& dir) -> bool {
+                       this->Impl->TestDir = dir;
+                       return true;
+                     } },
+    CommandArgument{ "--output-junit", CommandArgument::Values::One,
+                     [this](std::string const& file) -> bool {
+                       this->SetOutputJUnitFileName(file);
+                       return true;
+                     } },
+    CommandArgument{ "--no-tests", CommandArgument::Values::One,
+                     [this](std::string const& action) -> bool {
+                       if (action == "error"_s) {
+                         this->Impl->NoTestsMode = cmCTest::NoTests::Error;
+                       } else if (action == "ignore"_s) {
+                         this->Impl->NoTestsMode = cmCTest::NoTests::Ignore;
+                       } else {
+                         cmSystemTools::Error(
+                           cmStrCat("'--no-tests=' given unknown value '",
+                                    action, '\''));
+                         return false;
+                       }
+                       this->Impl->NoTestsModeSetInCli = true;
+                       return true;
+                     } },
+    CommandArgument{ "-I", CommandArgument::Values::One, dashI },
+    CommandArgument{ "--tests-information", CommandArgument::Values::One,
+                     dashI },
+    CommandArgument{ "-U", CommandArgument::Values::One, dashU },
+    CommandArgument{ "--union", CommandArgument::Values::One, dashU },
+    CommandArgument{ "-R", CommandArgument::Values::One, dashR },
+    CommandArgument{ "--tests-regex", CommandArgument::Values::One, dashR },
+    CommandArgument{ "-E", CommandArgument::Values::One, dashE },
+    CommandArgument{ "--exclude-regex", CommandArgument::Values::One, dashE },
+    CommandArgument{ "-L", CommandArgument::Values::One, dashL },
+    CommandArgument{ "--label-regex", CommandArgument::Values::One, dashL },
+    CommandArgument{ "-LE", CommandArgument::Values::One, dashLE },
+    CommandArgument{ "--label-exclude", CommandArgument::Values::One, dashLE },
+    CommandArgument{ "-FA", CommandArgument::Values::One, dashFA },
+    CommandArgument{ "--fixture-exclude-any", CommandArgument::Values::One,
+                     dashFA },
+    CommandArgument{ "-FS", CommandArgument::Values::One, dashFS },
+    CommandArgument{ "--fixture-exclude-setup", CommandArgument::Values::One,
+                     dashFS },
+    CommandArgument{ "-FC", CommandArgument::Values::One, dashFC },
+    CommandArgument{ "--fixture-exclude-cleanup", CommandArgument::Values::One,
+                     dashFC },
+    CommandArgument{ "--resource-spec-file", CommandArgument::Values::One,
+                     [this](std::string const& file) -> bool {
+                       this->Impl->TestHandler.SetPersistentOption(
+                         "ResourceSpecFile", file);
+                       this->Impl->MemCheckHandler.SetPersistentOption(
+                         "ResourceSpecFile", file);
+                       return true;
+                     } },
+    CommandArgument{
+      "--tests-from-file", CommandArgument::Values::One,
+      [this](std::string const& file) -> bool {
+        this->Impl->TestHandler.SetPersistentOption("TestListFile", file);
+        this->Impl->MemCheckHandler.SetPersistentOption("TestListFile", file);
+        return true;
+      } },
+    CommandArgument{ "--exclude-from-file", CommandArgument::Values::One,
+                     [this](std::string const& file) -> bool {
+                       this->Impl->TestHandler.SetPersistentOption(
+                         "ExcludeTestListFile", file);
+                       this->Impl->MemCheckHandler.SetPersistentOption(
+                         "ExcludeTestListFile", file);
+                       return true;
+                     } },
+    CommandArgument{ "--schedule-random", CommandArgument::Values::Zero,
+                     [this](std::string const&) -> bool {
+                       this->Impl->ScheduleType = "Random";
+                       return true;
+                     } },
+    CommandArgument{
+      "--rerun-failed", CommandArgument::Values::Zero,
+      [this](std::string const&) -> bool {
+        this->Impl->TestHandler.SetPersistentOption("RerunFailed", "true");
+        this->Impl->MemCheckHandler.SetPersistentOption("RerunFailed", "true");
+        return true;
+      } },
+  };
+
   // process the command line arguments
   for (size_t i = 1; i < args.size(); ++i) {
-    // handle the simple commandline arguments
-    std::string errormsg;
-    bool validArg = this->HandleCommandLineArguments(i, args, errormsg);
-    if (!validArg && !errormsg.empty()) {
-      cmSystemTools::Error(errormsg);
-      return 1;
-    }
-    std::string arg = args[i];
-
-    // handle the script arguments -S -SR -SP
-    validArg =
-      validArg || this->HandleScriptArguments(i, args, SRArgumentSpecified);
-
-    // --dashboard: handle a request for a dashboard
-    if (this->CheckArgument(arg, "-D"_s, "--dashboard") &&
-        i < args.size() - 1) {
-      this->Impl->ProduceXML = true;
-      i++;
-      std::string targ = args[i];
-      // AddTestsForDashboard parses the dashboard type and converts it
-      // into the separate stages
-      if (!this->AddTestsForDashboardType(targ)) {
-        if (!this->AddVariableDefinition(targ)) {
-          this->ErrorMessageUnknownDashDValue(targ);
-          executeTests = false;
+    std::string const& arg = args[i];
+    bool matched = false;
+    for (auto const& m : arguments) {
+      if (m.matches(arg)) {
+        matched = true;
+        if (!m.parse(arg, i, args)) {
+          return 1;
         }
-      }
-      validArg = true;
-    }
-
-    // If it's not exactly -D, but it starts with -D, then try to parse out
-    // a variable definition from it, same as CMake does. Unsuccessful
-    // attempts are simply ignored since previous ctest versions ignore
-    // this too. (As well as many other unknown command line args.)
-    //
-    if (arg != "-D" && cmHasLiteralPrefix(arg, "-D")) {
-      std::string input = arg.substr(2);
-      this->AddVariableDefinition(input);
-      validArg = true;
-    }
-
-    // --test-action: calls SetTest(<stage>, /*report=*/ false) to enable
-    // the corresponding stage
-    if (!this->HandleTestActionArgument(ctestExec, i, args, validArg)) {
-      executeTests = false;
-    }
-
-    // --test-model: what type of test model
-    if (!this->HandleTestModelArgument(ctestExec, i, args, validArg)) {
-      executeTests = false;
-    }
-
-    // --extra-submit
-    if (this->CheckArgument(arg, "--extra-submit"_s) && i < args.size() - 1) {
-      this->Impl->ProduceXML = true;
-      this->SetTest("Submit");
-      i++;
-      if (!this->SubmitExtraFiles(args[i])) {
-        return 0;
-      }
-      validArg = true;
-    }
-
-    // --build-and-test options
-    if (this->CheckArgument(arg, "--build-and-test"_s) &&
-        i < args.size() - 1) {
-      cmakeAndTest = true;
-      validArg = true;
-    }
-
-    // --schedule-random
-    if (this->CheckArgument(arg, "--schedule-random"_s)) {
-      this->Impl->ScheduleType = "Random";
-      validArg = true;
-    }
-
-    // pass the argument to all the handlers as well, but it may no longer be
-    // set to what it was originally so I'm not sure this is working as
-    // intended
-    for (auto& handler : this->Impl->GetTestingHandlers()) {
-      if (!handler->ProcessCommandLineArguments(arg, i, args, validArg)) {
-        cmCTestLog(
-          this, ERROR_MESSAGE,
-          "Problem parsing command line arguments within a handler\n");
-        return 0;
+        break;
       }
     }
-
-    if (!validArg && cmHasLiteralPrefix(arg, "-") &&
+    if (!matched && arg == "--build-options"_s) {
+      matched = true;
+      while (i + 1 < args.size() && args[i + 1] != "--build-target"_s &&
+             args[i + 1] != "--test-command"_s) {
+        ++i;
+        this->Impl->BuildAndTest.BuildOptions.emplace_back(args[i]);
+      }
+    }
+    if (!matched && arg == "--test-command"_s && i + 1 < args.size()) {
+      matched = true;
+      ++i;
+      this->Impl->BuildAndTest.TestCommand = args[i];
+      while (i + 1 < args.size()) {
+        ++i;
+        this->Impl->BuildAndTest.TestCommandArgs.emplace_back(args[i]);
+      }
+    }
+    if (!matched && cmHasLiteralPrefix(arg, "-") &&
         !cmHasLiteralPrefix(arg, "--preset")) {
       cmSystemTools::Error(cmStrCat("Unknown argument: ", arg));
       cmSystemTools::Error("Run 'ctest --help' for all supported options.");
       return 1;
     }
-  } // the close of the for argument loop
+  }
 
   // handle CTEST_PARALLEL_LEVEL environment variable
   if (!this->Impl->ParallelLevelSetInCli) {
@@ -2872,157 +2799,91 @@
   // now what should cmake do? if --build-and-test was specified then
   // we run the build and test handler and return
   if (cmakeAndTest) {
-    return this->RunCMakeAndTest(output);
+    return this->RunCMakeAndTest();
   }
 
-  if (executeTests) {
-    return this->ExecuteTests();
+  // -S, -SP, and/or -SP was specified
+  if (!runScripts.empty()) {
+    return this->RunScripts(runScripts);
   }
 
-  return 1;
+  // -D, -T, and/or -M was specified
+  if (processSteps) {
+    return this->ProcessSteps();
+  }
+
+  return this->ExecuteTests();
 }
 
-bool cmCTest::HandleTestActionArgument(const char* ctestExec, size_t& i,
-                                       const std::vector<std::string>& args,
-                                       bool& validArg)
+int cmCTest::RunScripts(
+  std::vector<std::pair<std::string, bool>> const& scripts)
 {
-  bool success = true;
-  std::string const& arg = args[i];
-  if (this->CheckArgument(arg, "-T"_s, "--test-action") &&
-      (i < args.size() - 1)) {
-    validArg = true;
-    this->Impl->ProduceXML = true;
-    i++;
-    if (!this->SetTest(args[i], false)) {
-      success = false;
-      cmCTestLog(this, ERROR_MESSAGE,
-                 "CTest -T called with incorrect option: " << args[i] << '\n');
-      /* clang-format off */
-      cmCTestLog(this, ERROR_MESSAGE,
-                 "Available options are:\n"
-                 "  " << ctestExec << " -T all\n"
-                 "  " << ctestExec << " -T start\n"
-                 "  " << ctestExec << " -T update\n"
-                 "  " << ctestExec << " -T configure\n"
-                 "  " << ctestExec << " -T build\n"
-                 "  " << ctestExec << " -T test\n"
-                 "  " << ctestExec << " -T coverage\n"
-                 "  " << ctestExec << " -T memcheck\n"
-                 "  " << ctestExec << " -T notes\n"
-                 "  " << ctestExec << " -T submit\n");
-      /* clang-format on */
-    }
+  if (this->Impl->ExtraVerbose) {
+    cmCTestLog(this, OUTPUT, "* Extra verbosity turned on" << std::endl);
   }
-  return success;
-}
 
-bool cmCTest::HandleTestModelArgument(const char* ctestExec, size_t& i,
-                                      const std::vector<std::string>& args,
-                                      bool& validArg)
-{
-  bool success = true;
-  std::string const& arg = args[i];
-  if (this->CheckArgument(arg, "-M"_s, "--test-model") &&
-      (i < args.size() - 1)) {
-    validArg = true;
-    i++;
-    std::string const& str = args[i];
-    if (cmSystemTools::LowerCase(str) == "nightly"_s) {
-      this->SetTestModel(cmCTest::NIGHTLY);
-    } else if (cmSystemTools::LowerCase(str) == "continuous"_s) {
-      this->SetTestModel(cmCTest::CONTINUOUS);
-    } else if (cmSystemTools::LowerCase(str) == "experimental"_s) {
-      this->SetTestModel(cmCTest::EXPERIMENTAL);
-    } else {
-      success = false;
-      cmCTestLog(this, ERROR_MESSAGE,
-                 "CTest -M called with incorrect option: " << str << '\n');
-      /* clang-format off */
-      cmCTestLog(this, ERROR_MESSAGE,
-                 "Available options are:\n"
-                 "  " << ctestExec << " -M Continuous\n"
-                 "  " << ctestExec << " -M Experimental\n"
-                 "  " << ctestExec << " -M Nightly\n");
-      /* clang-format on */
-    }
+  for (auto& handler : this->Impl->GetTestingHandlers()) {
+    handler->SetVerbose(this->Impl->ExtraVerbose);
+    handler->SetSubmitIndex(this->Impl->SubmitIndex);
   }
-  return success;
+
+  cmCTestScriptHandler* ch = this->GetScriptHandler();
+  ch->SetVerbose(this->Impl->Verbose);
+  for (auto const& script : scripts) {
+    ch->AddConfigurationScript(script.first, script.second);
+  }
+
+  int res = ch->ProcessHandler();
+  if (res != 0) {
+    cmCTestLog(this, DEBUG,
+               "running script failing returning: " << res << std::endl);
+  }
+
+  return res;
 }
 
 int cmCTest::ExecuteTests()
 {
-  int res;
-  // call process directory
-  if (this->Impl->RunConfigurationScript) {
-    if (this->Impl->ExtraVerbose) {
-      cmCTestLog(this, OUTPUT, "* Extra verbosity turned on" << std::endl);
-    }
-    for (auto& handler : this->Impl->GetTestingHandlers()) {
-      handler->SetVerbose(this->Impl->ExtraVerbose);
-      handler->SetSubmitIndex(this->Impl->SubmitIndex);
-    }
-    this->GetScriptHandler()->SetVerbose(this->Impl->Verbose);
-    res = this->GetScriptHandler()->ProcessHandler();
-    if (res != 0) {
-      cmCTestLog(this, DEBUG,
-                 "running script failing returning: " << res << std::endl);
-    }
+  this->Impl->ExtraVerbose = this->Impl->Verbose;
+  this->Impl->Verbose = true;
 
-  } else {
-    // What is this?  -V seems to be the same as -VV,
-    // and Verbose is always on in this case
-    this->Impl->ExtraVerbose = this->Impl->Verbose;
-    this->Impl->Verbose = true;
-    for (auto& handler : this->Impl->GetTestingHandlers()) {
-      handler->SetVerbose(this->Impl->Verbose);
-      handler->SetSubmitIndex(this->Impl->SubmitIndex);
-    }
+  const std::string currDir = cmSystemTools::GetCurrentWorkingDirectory();
+  std::string workDir = currDir;
+  if (!this->Impl->TestDir.empty()) {
+    workDir = cmSystemTools::ToNormalizedPathOnDisk(this->Impl->TestDir);
+  }
 
-    const std::string currDir = cmSystemTools::GetCurrentWorkingDirectory();
-    std::string workDir = currDir;
-    if (!this->Impl->TestDir.empty()) {
-      workDir = cmSystemTools::CollapseFullPath(this->Impl->TestDir);
-    }
+  cmWorkingDirectory changeDir(workDir);
+  if (changeDir.Failed()) {
+    cmCTestLog(this, ERROR_MESSAGE, changeDir.GetError() << std::endl);
+    return 1;
+  }
 
-    if (currDir != workDir) {
-      if (!this->TryToChangeDirectory(workDir)) {
-        return 1;
-      }
-    }
-
-    if (!this->Initialize(workDir, nullptr)) {
-      res = 12;
+  this->InitializeTesting(workDir);
+  this->GetTestHandler()->SetVerbose(this->Impl->Verbose);
+  if (this->GetTestHandler()->ProcessHandler() < 0) {
+    cmCTestLog(this, ERROR_MESSAGE, "Errors while running CTest\n");
+    if (!this->Impl->OutputTestOutputOnTestFailure) {
+      const std::string lastTestLog =
+        this->GetBinaryDir() + "/Testing/Temporary/LastTest.log";
       cmCTestLog(this, ERROR_MESSAGE,
-                 "Problem initializing the dashboard." << std::endl);
-    } else {
-      res = this->ProcessSteps();
+                 "Output from these tests are in: " << lastTestLog << '\n');
+      cmCTestLog(this, ERROR_MESSAGE,
+                 "Use \"--rerun-failed --output-on-failure\" to re-run the "
+                 "failed cases verbosely.\n");
     }
-    this->Finalize();
+    return cmCTest::TEST_ERRORS;
+  }
 
-    if (currDir != workDir) {
-      cmSystemTools::ChangeDirectory(currDir);
-    }
-  }
-  if (res != 0) {
-    cmCTestLog(this, DEBUG,
-               "Running a test(s) failed returning : " << res << std::endl);
-  }
-  return res;
+  return 0;
 }
 
-int cmCTest::RunCMakeAndTest(std::string* output)
+int cmCTest::RunCMakeAndTest()
 {
-  this->Impl->Verbose = true;
-  cmCTestBuildAndTestHandler* handler = this->GetBuildAndTestHandler();
-  int retv = handler->ProcessHandler();
-  *output = handler->GetOutput();
+  int retv = this->Impl->BuildAndTest.Run();
 #ifndef CMAKE_BOOTSTRAP
   cmDynamicLoader::FlushCache();
 #endif
-  if (retv != 0) {
-    cmCTestLog(this, DEBUG,
-               "build and test failing returning: " << retv << std::endl);
-  }
   return retv;
 }
 
@@ -3094,8 +2955,8 @@
   this->Impl->ScheduleType = type;
 }
 
-int cmCTest::ReadCustomConfigurationFileTree(const std::string& dir,
-                                             cmMakefile* mf)
+void cmCTest::ReadCustomConfigurationFileTree(const std::string& dir,
+                                              cmMakefile* mf)
 {
   bool found = false;
   cmCTestLog(this, DEBUG,
@@ -3151,8 +3012,6 @@
       handler.second->PopulateCustomVectors(mf);
     }
   }
-
-  return 1;
 }
 
 void cmCTest::PopulateCustomVector(cmMakefile* mf, const std::string& def,
@@ -3183,10 +3042,9 @@
 
 std::string cmCTest::GetShortPathToFile(const std::string& cfname)
 {
-  const std::string& sourceDir = cmSystemTools::CollapseFullPath(
-    this->GetCTestConfiguration("SourceDirectory"));
-  const std::string& buildDir = cmSystemTools::CollapseFullPath(
-    this->GetCTestConfiguration("BuildDirectory"));
+  const std::string& sourceDir =
+    this->GetCTestConfiguration("SourceDirectory");
+  const std::string& buildDir = this->GetCTestConfiguration("BuildDirectory");
   std::string fname = cmSystemTools::CollapseFullPath(cfname);
 
   // Find relative paths to both directories
@@ -3409,12 +3267,6 @@
   return this->Impl->ExtraVerbose;
 }
 
-void cmCTest::SetStreams(std::ostream* out, std::ostream* err)
-{
-  this->Impl->StreamOut = out;
-  this->Impl->StreamErr = err;
-}
-
 bool cmCTest::GetLabelSummary() const
 {
   return this->Impl->LabelSummary;
@@ -3520,6 +3372,81 @@
   return true;
 }
 
+void cmCTest::SetCMakeVariables(cmMakefile& mf)
+{
+  auto set = [&](char const* cmake_var, char const* ctest_opt) {
+    std::string val = this->GetCTestConfiguration(ctest_opt);
+    if (!val.empty()) {
+      cmCTestOptionalLog(
+        this, HANDLER_VERBOSE_OUTPUT,
+        "SetCMakeVariable:" << cmake_var << ":" << val << std::endl, false);
+      mf.AddDefinition(cmake_var, val);
+    }
+  };
+
+  set("CTEST_SITE", "Site");
+  set("CTEST_BUILD_NAME", "BuildName");
+  set("CTEST_NIGHTLY_START_TIME", "NightlyStartTime");
+  set("CTEST_SOURCE_DIRECTORY", "SourceDirectory");
+  set("CTEST_BINARY_DIRECTORY", "BuildDirectory");
+
+  // CTest Update Step
+  set("CTEST_UPDATE_COMMAND", "UpdateCommand");
+  set("CTEST_UPDATE_OPTIONS", "UpdateOptions");
+  set("CTEST_CVS_COMMAND", "CVSCommand");
+  set("CTEST_CVS_UPDATE_OPTIONS", "CVSUpdateOptions");
+  set("CTEST_SVN_COMMAND", "SVNCommand");
+  set("CTEST_SVN_UPDATE_OPTIONS", "SVNUpdateOptions");
+  set("CTEST_SVN_OPTIONS", "SVNOptions");
+  set("CTEST_BZR_COMMAND", "BZRCommand");
+  set("CTEST_BZR_UPDATE_OPTIONS", "BZRUpdateOptions");
+  set("CTEST_GIT_COMMAND", "GITCommand");
+  set("CTEST_GIT_UPDATE_OPTIONS", "GITUpdateOptions");
+  set("CTEST_GIT_INIT_SUBMODULES", "GITInitSubmodules");
+  set("CTEST_GIT_UPDATE_CUSTOM", "GITUpdateCustom");
+  set("CTEST_UPDATE_VERSION_ONLY", "UpdateVersionOnly");
+  set("CTEST_UPDATE_VERSION_OVERRIDE", "UpdateVersionOverride");
+  set("CTEST_HG_COMMAND", "HGCommand");
+  set("CTEST_HG_UPDATE_OPTIONS", "HGUpdateOptions");
+  set("CTEST_P4_COMMAND", "P4Command");
+  set("CTEST_P4_UPDATE_OPTIONS", "P4UpdateOptions");
+  set("CTEST_P4_CLIENT", "P4Client");
+  set("CTEST_P4_OPTIONS", "P4Options");
+
+  // CTest Configure Step
+  set("CTEST_CONFIGURE_COMMAND", "ConfigureCommand");
+  set("CTEST_LABELS_FOR_SUBPROJECTS", "LabelsForSubprojects");
+
+  // CTest Build Step
+  set("CTEST_BUILD_COMMAND", "MakeCommand");
+  set("CTEST_USE_LAUNCHERS", "UseLaunchers");
+
+  // CTest Coverage Step
+  set("CTEST_COVERAGE_COMMAND", "CoverageCommand");
+  set("CTEST_COVERAGE_EXTRA_FLAGS", "CoverageExtraFlags");
+
+  // CTest MemCheck Step
+  set("CTEST_MEMORYCHECK_TYPE", "MemoryCheckType");
+  set("CTEST_MEMORYCHECK_SANITIZER_OPTIONS", "MemoryCheckSanitizerOptions");
+  set("CTEST_MEMORYCHECK_COMMAND", "MemoryCheckCommand");
+  set("CTEST_MEMORYCHECK_COMMAND_OPTIONS", "MemoryCheckCommandOptions");
+  set("CTEST_MEMORYCHECK_SUPPRESSIONS_FILE", "MemoryCheckSuppressionFile");
+
+  // CTest Submit Step
+  set("CTEST_SUBMIT_URL", "SubmitURL");
+  set("CTEST_DROP_METHOD", "DropMethod");
+  set("CTEST_DROP_SITE_USER", "DropSiteUser");
+  set("CTEST_DROP_SITE_PASSWORD", "DropSitePassword");
+  set("CTEST_DROP_SITE", "DropSite");
+  set("CTEST_DROP_LOCATION", "DropLocation");
+  set("CTEST_TLS_VERIFY", "TLSVerify");
+  set("CTEST_TLS_VERSION", "TLSVersion");
+  set("CTEST_CURL_OPTIONS", "CurlOptions");
+  set("CTEST_SUBMIT_INACTIVITY_TIMEOUT", "SubmitInactivityTimeout");
+
+  this->GetTestHandler()->SetCMakeVariables(mf);
+}
+
 bool cmCTest::RunCommand(std::vector<std::string> const& args,
                          std::string* stdOut, std::string* stdErr, int* retVal,
                          const char* dir, cmDuration timeout,
@@ -3663,20 +3590,11 @@
                                               "HANDLER_TEST_PROGRESS_OUTPUT",
                                               "HANDLER_VERBOSE_OUTPUT",
                                               "WARNING",
-                                              "ERROR_MESSAGE",
-                                              nullptr };
+                                              "ERROR_MESSAGE" };
 
-#define cmCTestLogOutputFileLine(stream)                                      \
-  do {                                                                        \
-    if (this->Impl->ShowLineNumbers) {                                        \
-      (stream) << std::endl << file << ":" << line << " ";                    \
-    }                                                                         \
-  } while (false)
-
-void cmCTest::Log(int logType, const char* file, int line, const char* msg,
-                  bool suppress)
+void cmCTest::Log(LogType logType, std::string msg, bool suppress)
 {
-  if (!msg || !*msg) {
+  if (msg.empty()) {
     return;
   }
   if (suppress && logType != cmCTest::ERROR_MESSAGE) {
@@ -3696,49 +3614,38 @@
       display = false;
     }
     if (display) {
-      cmCTestLogOutputFileLine(*this->Impl->OutputLogFile);
-      if (logType != this->Impl->OutputLogFileLastTag) {
-        *this->Impl->OutputLogFile << "[";
-        if (logType >= OTHER || logType < 0) {
-          *this->Impl->OutputLogFile << "OTHER";
-        } else {
-          *this->Impl->OutputLogFile << cmCTestStringLogType[logType];
-        }
-        *this->Impl->OutputLogFile << "] " << std::endl;
+      if (this->Impl->OutputLogFileLastTag &&
+          logType != *this->Impl->OutputLogFileLastTag) {
+        *this->Impl->OutputLogFile << "[" << cmCTestStringLogType[logType]
+                                   << "] " << std::endl;
       }
       *this->Impl->OutputLogFile << msg << std::flush;
-      if (logType != this->Impl->OutputLogFileLastTag) {
+      if (this->Impl->OutputLogFileLastTag &&
+          logType != *this->Impl->OutputLogFileLastTag) {
         *this->Impl->OutputLogFile << std::endl;
         this->Impl->OutputLogFileLastTag = logType;
       }
     }
   }
   if (!this->Impl->Quiet) {
-    std::ostream& out = *this->Impl->StreamOut;
-    std::ostream& err = *this->Impl->StreamErr;
-
     if (logType == HANDLER_TEST_PROGRESS_OUTPUT) {
       if (this->Impl->TestProgressOutput) {
-        cmCTestLogOutputFileLine(out);
         if (this->Impl->FlushTestProgressLine) {
           printf("\r");
           this->Impl->FlushTestProgressLine = false;
-          out.flush();
+          std::cout.flush();
         }
 
-        std::string msg_str{ msg };
-        auto const lineBreakIt = msg_str.find('\n');
-        if (lineBreakIt != std::string::npos) {
+        if (msg.find('\n') != std::string::npos) {
           this->Impl->FlushTestProgressLine = true;
-          msg_str.erase(std::remove(msg_str.begin(), msg_str.end(), '\n'),
-                        msg_str.end());
+          msg.erase(std::remove(msg.begin(), msg.end(), '\n'), msg.end());
         }
 
-        out << msg_str;
+        std::cout << msg;
 #ifndef _WIN32
         printf("\x1B[K"); // move caret to end
 #endif
-        out.flush();
+        std::cout.flush();
         return;
       }
       logType = HANDLER_OUTPUT;
@@ -3747,41 +3654,29 @@
     switch (logType) {
       case DEBUG:
         if (this->Impl->Debug) {
-          cmCTestLogOutputFileLine(out);
-          out << msg;
-          out.flush();
+          std::cout << msg << std::flush;
         }
         break;
       case OUTPUT:
       case HANDLER_OUTPUT:
         if (this->Impl->Debug || this->Impl->Verbose) {
-          cmCTestLogOutputFileLine(out);
-          out << msg;
-          out.flush();
+          std::cout << msg << std::flush;
         }
         break;
       case HANDLER_VERBOSE_OUTPUT:
         if (this->Impl->Debug || this->Impl->ExtraVerbose) {
-          cmCTestLogOutputFileLine(out);
-          out << msg;
-          out.flush();
+          std::cout << msg << std::flush;
         }
         break;
       case WARNING:
-        cmCTestLogOutputFileLine(err);
-        err << msg;
-        err.flush();
+        std::cerr << msg << std::flush;
         break;
       case ERROR_MESSAGE:
-        cmCTestLogOutputFileLine(err);
-        err << msg;
-        err.flush();
+        std::cerr << msg << std::flush;
         cmSystemTools::SetErrorOccurred();
         break;
       default:
-        cmCTestLogOutputFileLine(out);
-        out << msg;
-        out.flush();
+        std::cout << msg << std::flush;
     }
   }
 }
@@ -3805,20 +3700,6 @@
   return cmDuration(1.0e7);
 }
 
-void cmCTest::SetRunCurrentScript(bool value)
-{
-  this->GetScriptHandler()->SetRunCurrentScript(value);
-}
-
-void cmCTest::OutputTestErrors(std::vector<char> const& process_output)
-{
-  std::string test_outputs("\n*** Test Failed:\n");
-  if (!process_output.empty()) {
-    test_outputs.append(process_output.data(), process_output.size());
-  }
-  cmCTestLog(this, HANDLER_OUTPUT, test_outputs << std::endl);
-}
-
 bool cmCTest::CompressString(std::string& str)
 {
   int ret;
diff --git a/Source/cmCTest.h b/Source/cmCTest.h
index 85f75fd..633725c 100644
--- a/Source/cmCTest.h
+++ b/Source/cmCTest.h
@@ -10,6 +10,7 @@
 #include <memory>
 #include <sstream>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include <cm/optional>
@@ -19,7 +20,6 @@
 #include "cmProcessOutput.h"
 
 class cmCTestBuildHandler;
-class cmCTestBuildAndTestHandler;
 class cmCTestCoverageHandler;
 class cmCTestScriptHandler;
 class cmCTestTestHandler;
@@ -28,7 +28,6 @@
 class cmCTestMemCheckHandler;
 class cmCTestSubmitHandler;
 class cmCTestUploadHandler;
-class cmCTestStartCommand;
 class cmGeneratedFileStream;
 class cmMakefile;
 class cmXMLWriter;
@@ -67,30 +66,21 @@
   Part GetPartFromName(const std::string& name);
 
   /** Process Command line arguments */
-  int Run(std::vector<std::string>&, std::string* output = nullptr);
+  int Run(std::vector<std::string> const& args);
+
+  /** Initialize a dashboard run in the given build tree. */
+  void Initialize(std::string const& binary_dir);
+
+  bool CreateNewTag(bool quiet);
+  bool ReadExistingTag(bool quiet);
 
   /**
-   * Initialize and finalize testing
+   * Initialize ctest for executing tests.
    */
-  bool InitializeFromCommand(cmCTestStartCommand* command);
-  void Finalize();
+  void InitializeTesting(const std::string& binary_dir);
 
   /**
    * Process the dashboard client steps.
-   *
-   * Steps are enabled using SetTest()
-   *
-   * The execution of the steps (or #Part) should look like this:
-   *
-   * /code
-   * ctest foo;
-   * foo.Initialize();
-   * // Set some things on foo
-   * foo.ProcessSteps();
-   * foo.Finalize();
-   * /endcode
-   *
-   * \sa Initialize(), Finalize(), Part, PartInfo, SetTest()
    */
   int ProcessSteps();
 
@@ -140,7 +130,8 @@
   void SetTestModel(int mode);
   int GetTestModel() const;
 
-  std::string GetTestModelString();
+  std::string GetTestModelString() const;
+  std::string GetTestGroupString() const;
   static int GetTestModelFromString(const std::string& str);
   static std::string CleanString(const std::string& str,
                                  std::string::size_type spos = 0);
@@ -299,21 +290,9 @@
   void SetProduceXML(bool v);
 
   /**
-   * Run command specialized for tests. Returns process status and retVal is
-   * return value or exception. If environment is non-null, it is used to set
-   * environment variables prior to running the test. After running the test,
-   * environment variables are restored to their previous values.
-   */
-  bool RunTest(const std::vector<std::string>& args, std::string* output,
-               int* retVal, std::ostream* logfile, cmDuration testTimeOut,
-               std::vector<std::string>* environment,
-               Encoding encoding = cmProcessOutput::Auto);
-
-  /**
    * Get the handler object
    */
   cmCTestBuildHandler* GetBuildHandler();
-  cmCTestBuildAndTestHandler* GetBuildAndTestHandler();
   cmCTestCoverageHandler* GetCoverageHandler();
   cmCTestScriptHandler* GetScriptHandler();
   cmCTestTestHandler* GetTestHandler();
@@ -331,6 +310,11 @@
                                               const std::string& cmake_var,
                                               bool suppress = false);
 
+  /**
+   * Set CMake variables from CTest Options
+   */
+  void SetCMakeVariables(cmMakefile& mf);
+
   /** Decode a URL to the original string.  */
   static std::string DecodeURL(const std::string&);
 
@@ -367,9 +351,9 @@
   void SetConfigType(const std::string& ct);
 
   /** Various log types */
-  enum
+  enum LogType
   {
-    DEBUG = 0,
+    DEBUG,
     OUTPUT,
     HANDLER_OUTPUT,
     HANDLER_PROGRESS_OUTPUT,
@@ -377,12 +361,10 @@
     HANDLER_VERBOSE_OUTPUT,
     WARNING,
     ERROR_MESSAGE,
-    OTHER
   };
 
   /** Add log to the output */
-  void Log(int logType, const char* file, int line, const char* msg,
-           bool suppress = false);
+  void Log(LogType logType, std::string msg, bool suppress = false);
 
   /** Color values */
   enum class Color
@@ -409,7 +391,7 @@
   /**
    * Read the custom configuration files and apply them to the current ctest
    */
-  int ReadCustomConfigurationFileTree(const std::string& dir, cmMakefile* mf);
+  void ReadCustomConfigurationFileTree(const std::string& dir, cmMakefile* mf);
 
   std::vector<std::string>& GetInitialCommandLineArguments();
 
@@ -425,9 +407,6 @@
   bool GetVerbose() const;
   bool GetExtraVerbose() const;
 
-  /** Direct process output to given streams.  */
-  void SetStreams(std::ostream* out, std::ostream* err);
-
   void AddSiteProperties(cmXMLWriter& xml);
 
   bool GetLabelSummary() const;
@@ -462,7 +441,8 @@
   void GenerateSubprojectsOutput(cmXMLWriter& xml);
   std::vector<std::string> GetLabelsForSubprojects();
 
-  void SetRunCurrentScript(bool value);
+  /** Reread the configuration file */
+  bool UpdateCTestConfiguration();
 
 private:
   void SetPersistentOptionIfNotEmpty(const std::string& value,
@@ -474,21 +454,11 @@
 
   void BlockTestErrorDiagnostics();
 
-  /**
-   * Initialize a dashboard run in the given build tree.  The "command"
-   * argument is non-NULL when running from a command-driven (ctest_start)
-   * dashboard script, and NULL when running from the CTest command
-   * line.  Note that a declarative dashboard script does not actually
-   * call this method because it sets CTEST_COMMAND to drive a build
-   * through the ctest command line.
-   */
-  int Initialize(const std::string& binary_dir, cmCTestStartCommand* command);
-
   /** parse the option after -D and convert it into the appropriate steps */
-  bool AddTestsForDashboardType(std::string& targ);
+  bool AddTestsForDashboardType(std::string const& targ);
 
   /** read as "emit an error message for an unknown -D value" */
-  void ErrorMessageUnknownDashDValue(std::string& val);
+  void ErrorMessageUnknownDashDValue(std::string const& val);
 
   /** add a variable definition from a command line -D value */
   bool AddVariableDefinition(const std::string& arg);
@@ -496,10 +466,6 @@
   /** set command line arguments read from a test preset */
   bool SetArgsFromPreset(const std::string& presetName, bool listPresets);
 
-  /** parse and process most common command line arguments */
-  bool HandleCommandLineArguments(size_t& i, std::vector<std::string>& args,
-                                  std::string& errormsg);
-
 #if !defined(_WIN32)
   /** returns true iff the console supports progress output */
   static bool ConsoleIsNotDumb();
@@ -511,13 +477,6 @@
   /** returns true iff the console supports colored output */
   static bool ColoredOutputSupportedByConsole();
 
-  /** handle the -S -SP and -SR arguments */
-  bool HandleScriptArguments(size_t& i, std::vector<std::string>& args,
-                             bool& SRArgumentSpecified);
-
-  /** Reread the configuration file */
-  bool UpdateCTestConfiguration();
-
   /** Create note from files. */
   int GenerateCTestNotesOutput(cmXMLWriter& xml,
                                std::vector<std::string> const& files);
@@ -526,25 +485,10 @@
   static bool CheckArgument(const std::string& arg, cm::string_view varg1,
                             const char* varg2 = nullptr);
 
-  /** Output errors from a test */
-  void OutputTestErrors(std::vector<char> const& process_output);
-
-  /** Handle the --test-action command line argument */
-  bool HandleTestActionArgument(const char* ctestExec, size_t& i,
-                                const std::vector<std::string>& args,
-                                bool& validArg);
-
-  /** Handle the --test-model command line argument */
-  bool HandleTestModelArgument(const char* ctestExec, size_t& i,
-                               const std::vector<std::string>& args,
-                               bool& validArg);
-
-  int RunCMakeAndTest(std::string* output);
+  int RunCMakeAndTest();
+  int RunScripts(std::vector<std::pair<std::string, bool>> const& scripts);
   int ExecuteTests();
 
-  /** return true iff change directory was successful */
-  bool TryToChangeDirectory(std::string const& dir);
-
   struct Private;
   std::unique_ptr<Private> Impl;
 };
@@ -553,14 +497,12 @@
   do {                                                                        \
     std::ostringstream cmCTestLog_msg;                                        \
     cmCTestLog_msg << msg;                                                    \
-    (ctSelf)->Log(cmCTest::logType, __FILE__, __LINE__,                       \
-                  cmCTestLog_msg.str().c_str());                              \
+    (ctSelf)->Log(cmCTest::logType, cmCTestLog_msg.str());                    \
   } while (false)
 
 #define cmCTestOptionalLog(ctSelf, logType, msg, suppress)                    \
   do {                                                                        \
     std::ostringstream cmCTestLog_msg;                                        \
     cmCTestLog_msg << msg;                                                    \
-    (ctSelf)->Log(cmCTest::logType, __FILE__, __LINE__,                       \
-                  cmCTestLog_msg.str().c_str(), suppress);                    \
+    (ctSelf)->Log(cmCTest::logType, cmCTestLog_msg.str(), suppress);          \
   } while (false)
diff --git a/Source/cmCommandLineArgument.h b/Source/cmCommandLineArgument.h
index 003e972..15a1f70 100644
--- a/Source/cmCommandLineArgument.h
+++ b/Source/cmCommandLineArgument.h
@@ -2,7 +2,10 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #pragma once
 
+#include <cctype>
+
 #include <cm/optional>
+#include <cm/string_view>
 
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
@@ -126,7 +129,7 @@
       if (input.size() == this->Name.size()) {
         auto nextValueIndex = index + 1;
         if (nextValueIndex >= allArgs.size() ||
-            allArgs[nextValueIndex][0] == '-') {
+            IsFlag(allArgs[nextValueIndex])) {
           if (this->Type == Values::ZeroOrOne) {
             parseState =
               this->StoreCall(std::string{}, std::forward<CallState>(state)...)
@@ -153,8 +156,8 @@
       }
     } else if (this->Type == Values::Two) {
       if (input.size() == this->Name.size()) {
-        if (index + 2 >= allArgs.size() || allArgs[index + 1][0] == '-' ||
-            allArgs[index + 2][0] == '-') {
+        if (index + 2 >= allArgs.size() || IsFlag(allArgs[index + 1]) ||
+            IsFlag(allArgs[index + 2])) {
           parseState = ParseMode::ValueError;
         } else {
           index += 2;
@@ -169,12 +172,12 @@
       if (input.size() == this->Name.size()) {
         auto nextValueIndex = index + 1;
         if (nextValueIndex >= allArgs.size() ||
-            allArgs[nextValueIndex][0] == '-') {
+            IsFlag(allArgs[nextValueIndex])) {
           parseState = ParseMode::ValueError;
         } else {
           std::string buffer = allArgs[nextValueIndex++];
           while (nextValueIndex < allArgs.size() &&
-                 allArgs[nextValueIndex][0] != '-') {
+                 !IsFlag(allArgs[nextValueIndex])) {
             buffer = cmStrCat(buffer, ";", allArgs[nextValueIndex++]);
           }
           parseState =
@@ -281,4 +284,10 @@
     }
     return std::string(possible_value);
   }
+
+  static bool IsFlag(cm::string_view arg)
+  {
+    return !arg.empty() && arg[0] == '-' &&
+      !(arg.size() >= 2 && std::isdigit(arg[1]));
+  }
 };
diff --git a/Source/cmComputeLinkInformation.cxx b/Source/cmComputeLinkInformation.cxx
index 26ff326..bd40115 100644
--- a/Source/cmComputeLinkInformation.cxx
+++ b/Source/cmComputeLinkInformation.cxx
@@ -1883,6 +1883,7 @@
   //   foo       ==>  -lfoo
   //   libfoo.a  ==>  -Wl,-Bstatic -lfoo
 
+  const cm::string_view LINKER{ "LINKER:" };
   BT<std::string> const& item = entry.Item;
 
   if (item.Value[0] == '-' || item.Value[0] == '$' || item.Value[0] == '`') {
@@ -1905,6 +1906,13 @@
     this->Items.emplace_back(item, ItemIsPath::No);
     return;
   }
+  if (cmHasPrefix(item.Value, LINKER)) {
+    std::vector<BT<std::string>> linkerFlag{ 1, item };
+    this->Target->ResolveLinkerWrapper(linkerFlag, this->GetLinkLanguage(),
+                                       true);
+    this->Items.emplace_back(linkerFlag.front(), ItemIsPath::No);
+    return;
+  }
 
   // Parse out the prefix, base, and suffix components of the
   // library name.  If the name matches that of a shared or static
diff --git a/Source/cmDebuggerAdapter.cxx b/Source/cmDebuggerAdapter.cxx
index c2e0d4f..0eacd0c 100644
--- a/Source/cmDebuggerAdapter.cxx
+++ b/Source/cmDebuggerAdapter.cxx
@@ -70,7 +70,7 @@
   {
   }
 
-  inline void Notify()
+  void Notify()
   {
     std::unique_lock<std::mutex> lock(Mutex);
     Count++;
@@ -78,7 +78,7 @@
     Cv.notify_one();
   }
 
-  inline void Wait()
+  void Wait()
   {
     std::unique_lock<std::mutex> lock(Mutex);
     while (Count == 0) {
diff --git a/Source/cmDebuggerVariables.h b/Source/cmDebuggerVariables.h
index 753b811..0c4a416 100644
--- a/Source/cmDebuggerVariables.h
+++ b/Source/cmDebuggerVariables.h
@@ -102,22 +102,16 @@
     std::shared_ptr<cmDebuggerVariablesManager> variablesManager,
     std::string name, bool supportsVariableType,
     std::function<std::vector<cmDebuggerVariableEntry>()> getKeyValuesFunc);
-  inline int64_t GetId() const noexcept { return this->Id; }
-  inline std::string GetName() const noexcept { return this->Name; }
-  inline std::string GetValue() const noexcept { return this->Value; }
-  inline void SetValue(std::string const& value) noexcept
-  {
-    this->Value = value;
-  }
+  int64_t GetId() const noexcept { return this->Id; }
+  std::string GetName() const noexcept { return this->Name; }
+  std::string GetValue() const noexcept { return this->Value; }
+  void SetValue(std::string const& value) noexcept { this->Value = value; }
   void AddSubVariables(std::shared_ptr<cmDebuggerVariables> const& variables);
-  inline void SetIgnoreEmptyStringEntries(bool value) noexcept
+  void SetIgnoreEmptyStringEntries(bool value) noexcept
   {
     this->IgnoreEmptyStringEntries = value;
   }
-  inline void SetEnableSorting(bool value) noexcept
-  {
-    this->EnableSorting = value;
-  }
+  void SetEnableSorting(bool value) noexcept { this->EnableSorting = value; }
   virtual ~cmDebuggerVariables();
 };
 
diff --git a/Source/cmDocumentationEntry.h b/Source/cmDocumentationEntry.h
index d971836..2a26ecd 100644
--- a/Source/cmDocumentationEntry.h
+++ b/Source/cmDocumentationEntry.h
@@ -17,7 +17,7 @@
   }
 #endif
 
-  std::string Name = {};
-  std::string Brief = {};
+  std::string Name;
+  std::string Brief;
   char CustomNamePrefix = ' ';
 };
diff --git a/Source/cmDocumentationFormatter.cxx b/Source/cmDocumentationFormatter.cxx
index b85b2f5..310966f 100644
--- a/Source/cmDocumentationFormatter.cxx
+++ b/Source/cmDocumentationFormatter.cxx
@@ -126,6 +126,8 @@
   const std::ptrdiff_t width = this->TextWidth - this->TextIndent;
   std::ptrdiff_t column = 0;
 
+  const auto padding = std::string(this->TextIndent, ' ');
+
   // Loop until the end of the text.
   for (const char *l = text.c_str(), *r = skipToSpace(text.c_str()); *l;
        l = skipSpaces(r), r = skipToSpace(l)) {
@@ -141,7 +143,7 @@
         } else if (!firstLine && this->TextIndent) {
           // First word on line.  Print indentation unless this is the
           // first line.
-          os << std::string(this->TextIndent, ' ');
+          os << padding;
         }
 
         // Print the word.
@@ -164,7 +166,7 @@
       os << '\n';
       firstLine = false;
       if (r > l) {
-        os << std::string(this->TextIndent, ' ');
+        os << padding;
         os.write(l, r - l);
         column = r - l;
         newSentence = (*(r - 1) == '.');
diff --git a/Source/cmELF.cxx b/Source/cmELF.cxx
index 003f47b..99d0ba4 100644
--- a/Source/cmELF.cxx
+++ b/Source/cmELF.cxx
@@ -509,7 +509,7 @@
     return 0;
   }
   ELF_Shdr const& sec = this->SectionHeaders[this->DynamicSectionIndex];
-  return static_cast<unsigned long>(sec.sh_offset + sec.sh_entsize * j);
+  return sec.sh_offset + sec.sh_entsize * static_cast<unsigned long>(j);
 }
 
 template <class Types>
diff --git a/Source/cmExportCMakeConfigGenerator.cxx b/Source/cmExportCMakeConfigGenerator.cxx
index 4f4765c..522c1c8 100644
--- a/Source/cmExportCMakeConfigGenerator.cxx
+++ b/Source/cmExportCMakeConfigGenerator.cxx
@@ -185,7 +185,7 @@
   // Isolate the file policy level.
   // Support CMake versions as far back as the
   // RequiredCMakeVersion{Major,Minor,Patch}, but also support using NEW
-  // policy settings for up to CMake 3.29 (this upper limit may be reviewed
+  // policy settings for up to CMake 3.30 (this upper limit may be reviewed
   // and increased from time to time). This reduces the opportunity for CMake
   // warnings when an older export file is later used with newer CMake
   // versions.
@@ -194,7 +194,7 @@
      << "cmake_policy(VERSION "
      << this->RequiredCMakeVersionMajor << '.'
      << this->RequiredCMakeVersionMinor << '.'
-     << this->RequiredCMakeVersionPatch << "...3.29)\n";
+     << this->RequiredCMakeVersionPatch << "...3.30)\n";
   /* clang-format on */
 }
 
diff --git a/Source/cmFileCommand.cxx b/Source/cmFileCommand.cxx
index 92e6b3e..9cb8d90 100644
--- a/Source/cmFileCommand.cxx
+++ b/Source/cmFileCommand.cxx
@@ -1420,6 +1420,7 @@
     std::string oldPolicyPath =
       cmSystemTools::CollapseFullPath(input, *arguments.BaseDirectory);
     oldPolicyPath = cmSystemTools::GetRealPath(oldPolicyPath);
+    oldPolicyPath = cmSystemTools::GetActualCaseForPath(oldPolicyPath);
     if (warnAbout152) {
       computeNewPath(input, realPath);
       if (oldPolicyPath != realPath) {
@@ -3852,8 +3853,7 @@
 
     cmWorkingDirectory workdir(destDir);
     if (workdir.Failed()) {
-      status.SetError(
-        cmStrCat("failed to change working directory to: ", destDir));
+      status.SetError(workdir.GetError());
       cmSystemTools::SetFatalErrorOccurred();
       return false;
     }
diff --git a/Source/cmFileCopier.cxx b/Source/cmFileCopier.cxx
index 686162b..47d3d51 100644
--- a/Source/cmFileCopier.cxx
+++ b/Source/cmFileCopier.cxx
@@ -713,7 +713,7 @@
   if (!source.empty()) {
     dir.Load(source);
   }
-  unsigned long numFiles = static_cast<unsigned long>(dir.GetNumberOfFiles());
+  unsigned long numFiles = dir.GetNumberOfFiles();
   for (unsigned long fileNum = 0; fileNum < numFiles; ++fileNum) {
     if (!(strcmp(dir.GetFile(fileNum), ".") == 0 ||
           strcmp(dir.GetFile(fileNum), "..") == 0)) {
diff --git a/Source/cmFindBase.cxx b/Source/cmFindBase.cxx
index 81e081c..ed29b82 100644
--- a/Source/cmFindBase.cxx
+++ b/Source/cmFindBase.cxx
@@ -56,7 +56,9 @@
       } else if (argsIn[j] == "ENV") {
         if (j + 1 < size) {
           j++;
-          cmSystemTools::GetPath(args, argsIn[j].c_str());
+          std::vector<std::string> p =
+            cmSystemTools::GetEnvPathNormalized(argsIn[j]);
+          std::move(p.begin(), p.end(), std::back_inserter(args));
         }
       } else {
         args.push_back(argsIn[j]);
@@ -338,7 +340,6 @@
 struct entry_to_remove
 {
   entry_to_remove(std::string const& name, cmMakefile* makefile)
-    : value()
   {
     if (cmValue to_skip = makefile->GetDefinition(
           cmStrCat("_CMAKE_SYSTEM_PREFIX_PATH_", name, "_PREFIX_COUNT"))) {
@@ -404,9 +405,11 @@
     cmList expanded{ *prefix_paths };
     install_entry.remove_self(expanded);
     staging_entry.remove_self(expanded);
-
-    paths.AddPrefixPaths(expanded,
-                         this->Makefile->GetCurrentSourceDirectory().c_str());
+    for (std::string& p : expanded) {
+      p = cmSystemTools::CollapseFullPath(
+        p, this->Makefile->GetCurrentSourceDirectory());
+    }
+    paths.AddPrefixPaths(expanded);
   } else if (add_install_prefix && !install_prefix_in_list) {
     paths.AddCMakePrefixPath("CMAKE_INSTALL_PREFIX");
     paths.AddCMakePrefixPath("CMAKE_STAGING_PREFIX");
@@ -494,7 +497,6 @@
             this->Makefile->GetCMakeInstance()->GetCMakeWorkingDirectory()))
           .Normal()
           .GenericString();
-      // value = cmSystemTools::CollapseFullPath(*existingValue);
       if (!cmSystemTools::FileExists(value, false)) {
         value = *existingValue;
       }
diff --git a/Source/cmFindLibraryCommand.cxx b/Source/cmFindLibraryCommand.cxx
index 9df7665..c5a6038 100644
--- a/Source/cmFindLibraryCommand.cxx
+++ b/Source/cmFindLibraryCommand.cxx
@@ -220,9 +220,6 @@
   };
   std::vector<Name> Names;
 
-  // Current full path under consideration.
-  std::string TestPath;
-
   void RegexFromLiteral(std::string& out, std::string const& in);
   void RegexFromList(std::string& out, cmList const& in);
   size_type GetPrefixIndex(std::string const& prefix)
@@ -423,20 +420,17 @@
   // one cannot tell just from the library name whether it is a static
   // library or an import library).
   if (name.TryRaw) {
-    this->TestPath = cmStrCat(path, name.Raw);
+    std::string testPath = cmStrCat(path, name.Raw);
 
-    const bool exists = cmSystemTools::FileExists(this->TestPath, true);
-    if (!exists) {
-      this->DebugLibraryFailed(name.Raw, path);
-    } else {
-      auto testPath = cmSystemTools::CollapseFullPath(this->TestPath);
+    if (cmSystemTools::FileExists(testPath, true)) {
+      testPath = cmSystemTools::ToNormalizedPathOnDisk(testPath);
       if (this->Validate(testPath)) {
         this->DebugLibraryFound(name.Raw, path);
         this->BestPath = testPath;
         return true;
       }
-      this->DebugLibraryFailed(name.Raw, path);
     }
+    this->DebugLibraryFailed(name.Raw, path);
   }
 
   // No library file has yet been found.
@@ -446,9 +440,7 @@
   unsigned int bestMinor = 0;
 
   // Search for a file matching the library name regex.
-  std::string dir = path;
-  cmSystemTools::ConvertToUnixSlashes(dir);
-  std::set<std::string> const& files = this->GG->GetDirectoryContent(dir);
+  std::set<std::string> const& files = this->GG->GetDirectoryContent(path);
   for (std::string const& origName : files) {
 #if defined(_WIN32) || defined(__APPLE__)
     std::string testName = cmSystemTools::LowerCase(origName);
@@ -456,14 +448,15 @@
     std::string const& testName = origName;
 #endif
     if (name.Regex.find(testName)) {
-      this->TestPath = cmStrCat(path, origName);
+      std::string testPath = cmStrCat(path, origName);
       // Make sure the path is readable and is not a directory.
-      if (cmSystemTools::FileExists(this->TestPath, true)) {
-        if (!this->Validate(cmSystemTools::CollapseFullPath(this->TestPath))) {
+      if (cmSystemTools::FileExists(testPath, true)) {
+        testPath = cmSystemTools::ToNormalizedPathOnDisk(testPath);
+        if (!this->Validate(testPath)) {
           continue;
         }
 
-        this->DebugLibraryFound(name.Raw, dir);
+        this->DebugLibraryFound(name.Raw, path);
         // This is a matching file.  Check if it is better than the
         // best name found so far.  Earlier prefixes are preferred,
         // followed by earlier suffixes.  For OpenBSD, shared library
@@ -480,7 +473,7 @@
             (prefix == bestPrefix && suffix == bestSuffix &&
              (major > bestMajor ||
               (major == bestMajor && minor > bestMinor)))) {
-          this->BestPath = this->TestPath;
+          this->BestPath = testPath;
           bestPrefix = prefix;
           bestSuffix = suffix;
           bestMajor = major;
@@ -491,7 +484,7 @@
   }
 
   if (this->BestPath.empty()) {
-    this->DebugLibraryFailed(name.Raw, dir);
+    this->DebugLibraryFailed(name.Raw, path);
   } else {
     this->DebugLibraryFound(name.Raw, this->BestPath);
   }
@@ -560,7 +553,7 @@
     for (std::string const& n : this->Names) {
       fwPath = cmStrCat(d, n, ".xcframework");
       if (cmSystemTools::FileIsDirectory(fwPath)) {
-        auto finalPath = cmSystemTools::CollapseFullPath(fwPath);
+        auto finalPath = cmSystemTools::ToNormalizedPathOnDisk(fwPath);
         if (this->Validate(finalPath)) {
           return finalPath;
         }
@@ -568,7 +561,7 @@
 
       fwPath = cmStrCat(d, n, ".framework");
       if (cmSystemTools::FileIsDirectory(fwPath)) {
-        auto finalPath = cmSystemTools::CollapseFullPath(fwPath);
+        auto finalPath = cmSystemTools::ToNormalizedPathOnDisk(fwPath);
         if (this->Validate(finalPath)) {
           return finalPath;
         }
@@ -588,7 +581,7 @@
     for (std::string const& d : this->SearchPaths) {
       fwPath = cmStrCat(d, n, ".xcframework");
       if (cmSystemTools::FileIsDirectory(fwPath)) {
-        auto finalPath = cmSystemTools::CollapseFullPath(fwPath);
+        auto finalPath = cmSystemTools::ToNormalizedPathOnDisk(fwPath);
         if (this->Validate(finalPath)) {
           return finalPath;
         }
@@ -596,7 +589,7 @@
 
       fwPath = cmStrCat(d, n, ".framework");
       if (cmSystemTools::FileIsDirectory(fwPath)) {
-        auto finalPath = cmSystemTools::CollapseFullPath(fwPath);
+        auto finalPath = cmSystemTools::ToNormalizedPathOnDisk(fwPath);
         if (this->Validate(finalPath)) {
           return finalPath;
         }
diff --git a/Source/cmFindPackageCommand.cxx b/Source/cmFindPackageCommand.cxx
index cc150fd..67b42a3 100644
--- a/Source/cmFindPackageCommand.cxx
+++ b/Source/cmFindPackageCommand.cxx
@@ -164,8 +164,7 @@
 {
 public:
   cmCaseInsensitiveDirectoryListGenerator(cm::string_view name)
-    : DirectoryLister{}
-    , DirName{ name }
+    : DirName{ name }
   {
   }
 
@@ -209,7 +208,6 @@
 public:
   cmDirectoryListGenerator(std::vector<std::string> const& names)
     : Names{ names }
-    , Matches{}
     , Current{ this->Matches.cbegin() }
   {
   }
@@ -332,8 +330,6 @@
 public:
   cmFileListGeneratorGlob(cm::string_view pattern)
     : Pattern(pattern)
-    , Files{}
-    , Current{}
   {
   }
 
@@ -1965,11 +1961,13 @@
     cmExpandList(*rootDEF, rootPaths);
   }
   if (rootEnv) {
-    std::vector<std::string> p = cmSystemTools::SplitEnvPath(*rootEnv);
+    std::vector<std::string> p =
+      cmSystemTools::SplitEnvPathNormalized(*rootEnv);
     std::move(p.begin(), p.end(), std::back_inserter(rootPaths));
   }
   if (rootENV) {
-    std::vector<std::string> p = cmSystemTools::SplitEnvPath(*rootENV);
+    std::vector<std::string> p =
+      cmSystemTools::SplitEnvPathNormalized(*rootENV);
     std::move(p.begin(), p.end(), std::back_inserter(rootPaths));
   }
 }
@@ -2120,9 +2118,9 @@
   // Use the system search path to generate prefixes.
   // Relative paths are interpreted with respect to the current
   // working directory.
-  std::vector<std::string> tmp;
-  cmSystemTools::GetPath(tmp);
-  for (std::string const& i : tmp) {
+  std::vector<std::string> envPATH =
+    cmSystemTools::GetEnvPathNormalized("PATH");
+  for (std::string const& i : envPATH) {
     // If the path is a PREFIX/bin case then add its parent instead.
     if ((cmHasLiteralSuffix(i, "/bin")) || (cmHasLiteralSuffix(i, "/sbin"))) {
       paths.AddPath(cmSystemTools::GetFilenamePath(i));
diff --git a/Source/cmFindPathCommand.cxx b/Source/cmFindPathCommand.cxx
index 74a69d8..b6a7834 100644
--- a/Source/cmFindPathCommand.cxx
+++ b/Source/cmFindPathCommand.cxx
@@ -106,7 +106,7 @@
   globIt.FindFiles(glob);
   std::vector<std::string> files = globIt.GetFiles();
   if (!files.empty()) {
-    std::string fheader = cmSystemTools::CollapseFullPath(files[0]);
+    std::string fheader = cmSystemTools::ToNormalizedPathOnDisk(files[0]);
     debug.FoundAt(fheader);
     if (this->IncludeFileInPath) {
       return fheader;
diff --git a/Source/cmFindProgramCommand.cxx b/Source/cmFindProgramCommand.cxx
index dd22b41..51be6e0 100644
--- a/Source/cmFindProgramCommand.cxx
+++ b/Source/cmFindProgramCommand.cxx
@@ -48,12 +48,6 @@
   // Current names under consideration.
   std::vector<std::string> Names;
 
-  // Current name with extension under consideration.
-  std::string TestNameExt;
-
-  // Current full path under consideration.
-  std::string TestPath;
-
   // Debug state
   cmFindBaseDebugState DebugSearches;
   cmMakefile* Makefile;
@@ -81,8 +75,6 @@
   {
     return std::any_of(this->Names.begin(), this->Names.end(),
                        [this, &path](std::string const& n) -> bool {
-                         // Only perform search relative to current directory
-                         // if the file name contains a directory separator.
                          return this->CheckDirectoryForName(path, n);
                        });
   }
@@ -93,20 +85,23 @@
                          if (!ext.empty() && cmHasSuffix(name, ext)) {
                            return false;
                          }
-                         this->TestNameExt = cmStrCat(name, ext);
-                         this->TestPath = cmSystemTools::CollapseFullPath(
-                           this->TestNameExt, path);
-                         bool exists = this->FileIsValid(this->TestPath);
-                         exists ? this->DebugSearches.FoundAt(this->TestPath)
-                                : this->DebugSearches.FailedAt(this->TestPath);
-                         if (exists) {
-                           this->BestPath = this->TestPath;
-                           return true;
+                         std::string testNameExt = cmStrCat(name, ext);
+                         std::string testPath =
+                           cmSystemTools::CollapseFullPath(testNameExt, path);
+                         if (this->FileIsExecutable(testPath)) {
+                           testPath =
+                             cmSystemTools::ToNormalizedPathOnDisk(testPath);
+                           if (this->FindBase->Validate(testPath)) {
+                             this->BestPath = testPath;
+                             this->DebugSearches.FoundAt(testPath);
+                             return true;
+                           }
                          }
+                         this->DebugSearches.FailedAt(testPath);
                          return false;
                        });
   }
-  bool FileIsValid(std::string const& file) const
+  bool FileIsExecutable(std::string const& file) const
   {
     if (!this->FileIsExecutableCMP0109(file)) {
       return false;
@@ -122,7 +117,7 @@
       }
     }
 #endif
-    return this->FindBase->Validate(file);
+    return true;
   }
   bool FileIsExecutableCMP0109(std::string const& file) const
   {
diff --git a/Source/cmGeneratorExpression.h b/Source/cmGeneratorExpression.h
index 1349308..8d21aa8 100644
--- a/Source/cmGeneratorExpression.h
+++ b/Source/cmGeneratorExpression.h
@@ -72,11 +72,11 @@
 
   static std::string StripEmptyListElements(const std::string& input);
 
-  static inline bool StartsWithGeneratorExpression(const std::string& input)
+  static bool StartsWithGeneratorExpression(const std::string& input)
   {
     return input.length() >= 2 && input[0] == '$' && input[1] == '<';
   }
-  static inline bool StartsWithGeneratorExpression(const char* input)
+  static bool StartsWithGeneratorExpression(const char* input)
   {
     return input && input[0] == '$' && input[1] == '<';
   }
diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx
index fb92771..6bf49a6 100644
--- a/Source/cmGeneratorTarget.cxx
+++ b/Source/cmGeneratorTarget.cxx
@@ -2516,8 +2516,8 @@
     return;
   }
 
-  return this->AddCUDAArchitectureFlagsImpl(compileOrLink, config, "CUDA",
-                                            std::move(arch), flags);
+  this->AddCUDAArchitectureFlagsImpl(compileOrLink, config, "CUDA",
+                                     std::move(arch), flags);
 }
 
 void cmGeneratorTarget::AddCUDAArchitectureFlagsImpl(cmBuildStep compileOrLink,
@@ -2695,8 +2695,9 @@
   }
 
   if (this->Makefile->GetSafeDefinition("CMAKE_HIP_PLATFORM") == "nvidia") {
-    return this->AddCUDAArchitectureFlagsImpl(compileOrLink, config, "HIP",
-                                              std::move(arch), flags);
+    this->AddCUDAArchitectureFlagsImpl(compileOrLink, config, "HIP",
+                                       std::move(arch), flags);
+    return;
   }
 
   cmList options(arch);
diff --git a/Source/cmGetFilenameComponentCommand.cxx b/Source/cmGetFilenameComponentCommand.cxx
index 1815c4d..6ca5930 100644
--- a/Source/cmGetFilenameComponentCommand.cxx
+++ b/Source/cmGetFilenameComponentCommand.cxx
@@ -113,6 +113,7 @@
       // Resolve symlinks if possible
       result = cmSystemTools::GetRealPath(result);
     }
+    result = cmSystemTools::GetActualCaseForPath(result);
   } else {
     std::string err = "unknown component " + args[2];
     status.SetError(err);
diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx
index ec44817..3d99aa6 100644
--- a/Source/cmGlobalGenerator.cxx
+++ b/Source/cmGlobalGenerator.cxx
@@ -963,18 +963,24 @@
       } // end if in try compile
     }   // end need test language
 
-    // load linker configuration
-    std::string langLinkerLoadedVar =
-      cmStrCat("CMAKE_", lang, "_LINKER_INFORMATION_LOADED");
-    if (!mf->GetDefinition(langLinkerLoadedVar)) {
-      fpath = cmStrCat("Internal/CMake", lang, "LinkerInformation.cmake");
-      std::string informationFile = mf->GetModulesFile(fpath);
-      if (informationFile.empty()) {
-        cmSystemTools::Error(
-          cmStrCat("Could not find cmake module file: ", fpath));
-      } else if (!mf->ReadListFile(informationFile)) {
-        cmSystemTools::Error(
-          cmStrCat("Could not process cmake module file: ", informationFile));
+    // load linker configuration,  if required
+    if (mf->GetDefinition(cmStrCat("CMAKE_", lang, "_USE_LINKER_INFORMATION"))
+          .IsOn()) {
+      std::string langLinkerLoadedVar =
+        cmStrCat("CMAKE_", lang, "_LINKER_INFORMATION_LOADED");
+      if (!mf->GetDefinition(langLinkerLoadedVar)) {
+        fpath = cmStrCat("CMake", lang, "LinkerInformation.cmake");
+        std::string informationFile = mf->GetModulesFile(fpath);
+        if (informationFile.empty()) {
+          informationFile = mf->GetModulesFile(cmStrCat("Internal/", fpath));
+        }
+        if (informationFile.empty()) {
+          cmSystemTools::Error(
+            cmStrCat("Could not find cmake module file: ", fpath));
+        } else if (!mf->ReadListFile(informationFile)) {
+          cmSystemTools::Error(cmStrCat(
+            "Could not process cmake module file: ", informationFile));
+        }
       }
     }
 
@@ -1497,12 +1503,14 @@
 
   // This generator does not support duplicate custom targets.
   std::ostringstream e;
+  // clang-format off
   e << "This project has enabled the ALLOW_DUPLICATE_CUSTOM_TARGETS "
-    << "global property.  "
-    << "The \"" << this->GetName() << "\" generator does not support "
-    << "duplicate custom targets.  "
-    << "Consider using a Makefiles generator or fix the project to not "
-    << "use duplicate target names.";
+       "global property.  "
+       "The \"" << this->GetName() << "\" generator does not support "
+       "duplicate custom targets.  "
+       "Consider using a Makefiles generator or fix the project to not "
+       "use duplicate target names.";
+  // clang-format on
   cmSystemTools::Error(e.str());
   return false;
 }
@@ -1771,11 +1779,12 @@
 
   if (!this->CMP0042WarnTargets.empty()) {
     std::ostringstream w;
-    w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0042) << "\n";
-    w << "MACOSX_RPATH is not specified for"
+    w << cmPolicies::GetPolicyWarning(cmPolicies::CMP0042)
+      << "\n"
+         "MACOSX_RPATH is not specified for"
          " the following targets:\n";
     for (std::string const& t : this->CMP0042WarnTargets) {
-      w << " " << t << "\n";
+      w << ' ' << t << '\n';
     }
     this->GetCMakeInstance()->IssueMessage(MessageType::AUTHOR_WARNING,
                                            w.str());
@@ -1792,7 +1801,7 @@
       ;
     /* clang-format on */
     for (std::string const& t : this->CMP0068WarnTargets) {
-      w << " " << t << "\n";
+      w << ' ' << t << '\n';
     }
     this->GetCMakeInstance()->IssueMessage(MessageType::AUTHOR_WARNING,
                                            w.str());
@@ -2212,9 +2221,9 @@
   cmBuildOptions defaultBuildOptions(false, fast, PackageResolveMode::Disable);
 
   std::stringstream ostr;
-  auto ret =
-    this->Build(jobs, srcdir, bindir, projectName, newTarget, ostr, "", config,
-                defaultBuildOptions, true, this->TryCompileTimeout);
+  auto ret = this->Build(jobs, srcdir, bindir, projectName, newTarget, ostr,
+                         "", config, defaultBuildOptions, true,
+                         this->TryCompileTimeout, cmSystemTools::OUTPUT_NONE);
   output = ostr.str();
   return ret;
 }
@@ -2243,7 +2252,7 @@
   const std::string& projectName, const std::vector<std::string>& targets,
   std::ostream& ostr, const std::string& makeCommandCSTR,
   const std::string& config, const cmBuildOptions& buildOptions, bool verbose,
-  cmDuration timeout, cmSystemTools::OutputOption outputflag,
+  cmDuration timeout, cmSystemTools::OutputOption outputMode,
   std::vector<std::string> const& nativeOptions)
 {
   bool hideconsole = cmSystemTools::GetRunCommandHideConsole();
@@ -2255,8 +2264,7 @@
   ostr << "Change Dir: '" << bindir << '\'' << std::endl;
   if (workdir.Failed()) {
     cmSystemTools::SetRunCommandHideConsole(hideconsole);
-    std::string err = cmStrCat("Failed to change directory: ",
-                               std::strerror(workdir.GetLastResult()));
+    std::string const& err = workdir.GetError();
     cmSystemTools::Error(err);
     ostr << err << std::endl;
     return 1;
@@ -2268,17 +2276,18 @@
 
   int retVal = 0;
   cmSystemTools::SetRunCommandHideConsole(true);
-  std::string outputBuffer;
-  std::string* outputPtr = &outputBuffer;
+
+  // Capture build command output when outputMode == OUTPUT_NONE.
+  std::string outputBuf;
 
   std::vector<GeneratedMakeCommand> makeCommand = this->GenerateBuildCommand(
     makeCommandCSTR, projectName, bindir, targets, realConfig, jobs, verbose,
     buildOptions, nativeOptions);
 
   // Workaround to convince some commands to produce output.
-  if (outputflag == cmSystemTools::OUTPUT_PASSTHROUGH &&
+  if (outputMode == cmSystemTools::OUTPUT_PASSTHROUGH &&
       makeCommand.back().RequiresOutputForward) {
-    outputflag = cmSystemTools::OUTPUT_FORWARD;
+    outputMode = cmSystemTools::OUTPUT_FORWARD;
   }
 
   // should we do a clean first?
@@ -2297,16 +2306,16 @@
       return 1;
     }
     if (!cmSystemTools::RunSingleCommand(cleanCommand.front().PrimaryCommand,
-                                         outputPtr, outputPtr, &retVal,
-                                         nullptr, outputflag, timeout)) {
+                                         &outputBuf, &outputBuf, &retVal,
+                                         nullptr, outputMode, timeout)) {
       cmSystemTools::SetRunCommandHideConsole(hideconsole);
       cmSystemTools::Error("Generator: execution of make clean failed.");
-      ostr << *outputPtr << "\nGenerator: execution of make clean failed."
+      ostr << outputBuf << "\nGenerator: execution of make clean failed."
            << std::endl;
 
       return 1;
     }
-    ostr << *outputPtr;
+    ostr << outputBuf;
   }
 
   // now build
@@ -2328,22 +2337,22 @@
     }
 
     ostr << outputMakeCommandStr << std::endl;
-    if (!cmSystemTools::RunSingleCommand(command->PrimaryCommand, outputPtr,
-                                         outputPtr, &retVal, nullptr,
-                                         outputflag, timeout)) {
+    if (!cmSystemTools::RunSingleCommand(command->PrimaryCommand, &outputBuf,
+                                         &outputBuf, &retVal, nullptr,
+                                         outputMode, timeout)) {
       cmSystemTools::SetRunCommandHideConsole(hideconsole);
       cmSystemTools::Error(
-        cmStrCat("Generator: execution of make failed. Make command was: ",
+        cmStrCat("Generator: build tool execution failed, command was: ",
                  makeCommandStr));
-      ostr << *outputPtr
-           << "\nGenerator: execution of make failed. Make command was: "
+      ostr << outputBuf
+           << "\nGenerator: build tool execution failed, command was: "
            << outputMakeCommandStr << std::endl;
 
       return 1;
     }
-    ostr << *outputPtr << std::flush;
+    ostr << outputBuf << std::flush;
     if (needBuildOutput) {
-      buildOutput += *outputPtr;
+      buildOutput += outputBuf;
     }
   }
   ostr << std::endl;
@@ -2805,7 +2814,7 @@
   bool issueMessage = false;
   switch (tgt->GetPolicyStatusCMP0037()) {
     case cmPolicies::WARN:
-      e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0037) << "\n";
+      e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0037) << '\n';
       issueMessage = true;
       CM_FALLTHROUGH;
     case cmPolicies::OLD:
@@ -2819,7 +2828,7 @@
   }
   if (issueMessage) {
     e << "The target name \"" << targetNameAsWritten << "\" is reserved "
-      << reason << ".";
+      << reason << '.';
     if (messageType == MessageType::AUTHOR_WARNING) {
       e << "  It may result in undefined behavior.";
     }
@@ -2978,7 +2987,6 @@
   }
   cmCustomCommandLine singleLine;
   singleLine.push_back(cmSystemTools::GetCTestCommand());
-  singleLine.push_back("--force-new-ctest-process");
   cmList args(mf->GetDefinition("CMAKE_CTEST_ARGUMENTS"));
   for (auto const& arg : args) {
     singleLine.push_back(arg);
@@ -3014,7 +3022,7 @@
   bool issueMessage = false;
   switch (policyStatus) {
     case cmPolicies::WARN:
-      e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0171) << "\n";
+      e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0171) << '\n';
       issueMessage = true;
       CM_FALLTHROUGH;
     case cmPolicies::OLD:
@@ -3121,8 +3129,8 @@
       std::set<std::string>* componentsSet = &this->InstallComponents;
       std::ostringstream ostr;
       if (!componentsSet->empty()) {
-        ostr << "Available install components are: ";
-        ostr << cmWrap('"', *componentsSet, '"', " ");
+        ostr << "Available install components are: "
+             << cmWrap('"', *componentsSet, '"', " ");
       } else {
         ostr << "Only default component available";
       }
@@ -3737,7 +3745,7 @@
     fout << "# Hashes of file build rules.\n";
     for (auto const& rh : this->RuleHashes) {
       fout.write(rh.second.Data, 32);
-      fout << " " << rh.first << "\n";
+      fout << ' ' << rh.first << '\n';
     }
   }
 }
@@ -3755,7 +3763,7 @@
         continue;
       }
       this->WriteSummary(tgt.get());
-      fout << tgt->GetSupportDirectory() << "\n";
+      fout << tgt->GetSupportDirectory() << '\n';
     }
   }
 }
@@ -3793,7 +3801,7 @@
       if (!labels.empty()) {
         fout << "# Target labels\n";
         for (std::string const& l : labels) {
-          fout << " " << l << "\n";
+          fout << ' ' << l << '\n';
           lj_target_labels.append(l);
         }
       }
@@ -3816,12 +3824,12 @@
     }
 
     for (auto const& li : directoryLabelsList) {
-      fout << " " << li << "\n";
+      fout << ' ' << li << '\n';
       lj_target_labels.append(li);
     }
 
     for (auto const& li : cmakeDirectoryLabelsList) {
-      fout << " " << li << "\n";
+      fout << ' ' << li << '\n';
       lj_target_labels.append(li);
     }
 
@@ -3838,13 +3846,13 @@
     for (cmSourceFile* sf : cmMakeRange(sources.cbegin(), sourcesEnd)) {
       Json::Value& lj_source = lj_sources.append(Json::objectValue);
       std::string const& sfp = sf->ResolveFullPath();
-      fout << sfp << "\n";
+      fout << sfp << '\n';
       lj_source["file"] = sfp;
       if (cmValue svalue = sf->GetProperty("LABELS")) {
         Json::Value& lj_source_labels = lj_source["labels"] = Json::arrayValue;
         labels.assign(*svalue);
         for (auto const& label : labels) {
-          fout << " " << label << "\n";
+          fout << ' ' << label << '\n';
           lj_source_labels.append(label);
         }
       }
diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h
index a865adb..2a1afa8 100644
--- a/Source/cmGlobalGenerator.h
+++ b/Source/cmGlobalGenerator.h
@@ -248,15 +248,14 @@
    * empty then all is assumed. clean indicates if a "make clean" should be
    * done first.
    */
-  int Build(
-    int jobs, const std::string& srcdir, const std::string& bindir,
-    const std::string& projectName,
-    std::vector<std::string> const& targetNames, std::ostream& ostr,
-    const std::string& makeProgram, const std::string& config,
-    const cmBuildOptions& buildOptions, bool verbose, cmDuration timeout,
-    cmSystemTools::OutputOption outputflag = cmSystemTools::OUTPUT_NONE,
-    std::vector<std::string> const& nativeOptions =
-      std::vector<std::string>());
+  int Build(int jobs, const std::string& srcdir, const std::string& bindir,
+            const std::string& projectName,
+            std::vector<std::string> const& targetNames, std::ostream& ostr,
+            const std::string& makeProgram, const std::string& config,
+            const cmBuildOptions& buildOptions, bool verbose,
+            cmDuration timeout, cmSystemTools::OutputOption outputMode,
+            std::vector<std::string> const& nativeOptions =
+              std::vector<std::string>());
 
   /**
    * Open a generated IDE project given the following information.
diff --git a/Source/cmGlobalXCodeGenerator.cxx b/Source/cmGlobalXCodeGenerator.cxx
index 7fa21d0..e933be7 100644
--- a/Source/cmGlobalXCodeGenerator.cxx
+++ b/Source/cmGlobalXCodeGenerator.cxx
@@ -1362,12 +1362,20 @@
   {
     std::string const& a = l->GetTarget()->GetName();
     std::string const& b = r->GetTarget()->GetName();
+    if (a == b) {
+      return false;
+    }
     if (a == "ALL_BUILD"_s) {
       return true;
     }
     if (b == "ALL_BUILD"_s) {
       return false;
     }
+    std::string a_low = cmSystemTools::LowerCase(l->GetTarget()->GetName());
+    std::string b_low = cmSystemTools::LowerCase(r->GetTarget()->GetName());
+    if (a_low != b_low) {
+      return a_low < b_low;
+    }
     return a < b;
   }
 };
diff --git a/Source/cmLinkItem.cxx b/Source/cmLinkItem.cxx
index 3654176..6744bbb 100644
--- a/Source/cmLinkItem.cxx
+++ b/Source/cmLinkItem.cxx
@@ -13,8 +13,6 @@
 
 const std::string cmLinkItem::DEFAULT = "DEFAULT";
 
-cmLinkItem::cmLinkItem() = default;
-
 cmLinkItem::cmLinkItem(std::string n, bool c, cmListFileBacktrace bt,
                        std::string feature)
   : String(std::move(n))
@@ -73,11 +71,6 @@
   return os << item.AsStr();
 }
 
-cmLinkImplItem::cmLinkImplItem()
-  : cmLinkItem()
-{
-}
-
 cmLinkImplItem::cmLinkImplItem(cmLinkItem item, bool checkCMP0027)
   : cmLinkItem(std::move(item))
   , CheckCMP0027(checkCMP0027)
diff --git a/Source/cmLinkItem.h b/Source/cmLinkItem.h
index 4e356b7..70efb16 100644
--- a/Source/cmLinkItem.h
+++ b/Source/cmLinkItem.h
@@ -29,7 +29,7 @@
   // default feature: link library without decoration
   static const std::string DEFAULT;
 
-  cmLinkItem();
+  cmLinkItem() = default;
   cmLinkItem(std::string s, bool c, cmListFileBacktrace bt,
              std::string feature = DEFAULT);
   cmLinkItem(cmGeneratorTarget const* t, bool c, cmListFileBacktrace bt,
@@ -50,7 +50,7 @@
 class cmLinkImplItem : public cmLinkItem
 {
 public:
-  cmLinkImplItem();
+  cmLinkImplItem() = default;
   cmLinkImplItem(cmLinkItem item, bool checkCMP0027);
   bool CheckCMP0027 = false;
 };
diff --git a/Source/cmList.h b/Source/cmList.h
index e107096..7771a0e 100644
--- a/Source/cmList.h
+++ b/Source/cmList.h
@@ -1135,11 +1135,11 @@
 
   // Non-members
   // ===========
-  friend inline bool operator==(const cmList& lhs, const cmList& rhs) noexcept
+  friend bool operator==(const cmList& lhs, const cmList& rhs) noexcept
   {
     return lhs.Values == rhs.Values;
   }
-  friend inline bool operator!=(const cmList& lhs, const cmList& rhs) noexcept
+  friend bool operator!=(const cmList& lhs, const cmList& rhs) noexcept
   {
     return lhs.Values != rhs.Values;
   }
diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx
index 42b517e..80d8226 100644
--- a/Source/cmLocalGenerator.cxx
+++ b/Source/cmLocalGenerator.cxx
@@ -2380,7 +2380,7 @@
     dep = cmStrCat(this->GetCurrentBinaryDirectory(), '/', inName);
   }
 
-  dep = cmSystemTools::CollapseFullPath(dep, this->GetBinaryDirectory());
+  dep = cmSystemTools::CollapseFullPath(dep);
 
   return true;
 }
@@ -3087,7 +3087,8 @@
 cmLocalGenerator::UnitySource cmLocalGenerator::WriteUnitySource(
   cmGeneratorTarget* target, std::vector<std::string> const& configs,
   cmRange<std::vector<UnityBatchedSource>::const_iterator> sources,
-  cmValue beforeInclude, cmValue afterInclude, std::string filename) const
+  cmValue beforeInclude, cmValue afterInclude, std::string filename,
+  std::string const& unityFileDirectory, UnityPathMode pathMode) const
 {
   cmValue uniqueIdName = target->GetProperty("UNITY_BUILD_UNIQUE_ID");
   cmGeneratedFileStream file(
@@ -3110,7 +3111,8 @@
     }
     RegisterUnitySources(target, ubs.Source, filename);
     WriteUnitySourceInclude(file, cond, ubs.Source->ResolveFullPath(),
-                            beforeInclude, afterInclude, uniqueIdName);
+                            beforeInclude, afterInclude, uniqueIdName,
+                            pathMode, unityFileDirectory);
   }
 
   return UnitySource(std::move(filename), perConfig);
@@ -3119,28 +3121,35 @@
 void cmLocalGenerator::WriteUnitySourceInclude(
   std::ostream& unity_file, cm::optional<std::string> const& cond,
   std::string const& sf_full_path, cmValue beforeInclude, cmValue afterInclude,
-  cmValue uniqueIdName) const
+  cmValue uniqueIdName, UnityPathMode pathMode,
+  std::string const& unityFileDirectory) const
 {
   if (cond) {
     unity_file << "#if " << *cond << "\n";
   }
 
+  std::string pathToHash;
+  std::string relocatableIncludePath;
+  auto PathEqOrSubDir = [](std::string const& a, std::string const& b) {
+    return (cmSystemTools::ComparePath(a, b) ||
+            cmSystemTools::IsSubDirectory(a, b));
+  };
+  const auto path = cmSystemTools::GetFilenamePath(sf_full_path);
+  if (PathEqOrSubDir(path, this->GetBinaryDirectory())) {
+    relocatableIncludePath =
+      cmSystemTools::RelativePath(unityFileDirectory, sf_full_path);
+    pathToHash = "BLD_" +
+      cmSystemTools::RelativePath(this->GetBinaryDirectory(), sf_full_path);
+  } else if (PathEqOrSubDir(path, this->GetSourceDirectory())) {
+    relocatableIncludePath =
+      cmSystemTools::RelativePath(this->GetSourceDirectory(), sf_full_path);
+    pathToHash = "SRC_" + relocatableIncludePath;
+  } else {
+    relocatableIncludePath = sf_full_path;
+    pathToHash = "ABS_" + sf_full_path;
+  }
+
   if (cmNonempty(uniqueIdName)) {
-    std::string pathToHash;
-    auto PathEqOrSubDir = [](std::string const& a, std::string const& b) {
-      return (cmSystemTools::ComparePath(a, b) ||
-              cmSystemTools::IsSubDirectory(a, b));
-    };
-    const auto path = cmSystemTools::GetFilenamePath(sf_full_path);
-    if (PathEqOrSubDir(path, this->GetBinaryDirectory())) {
-      pathToHash = "BLD_" +
-        cmSystemTools::RelativePath(this->GetBinaryDirectory(), sf_full_path);
-    } else if (PathEqOrSubDir(path, this->GetSourceDirectory())) {
-      pathToHash = "SRC_" +
-        cmSystemTools::RelativePath(this->GetSourceDirectory(), sf_full_path);
-    } else {
-      pathToHash = "ABS_" + sf_full_path;
-    }
     cmCryptoHash hasher(cmCryptoHash::AlgoMD5);
     unity_file << "/* " << pathToHash << " */\n"
                << "#undef " << *uniqueIdName << "\n"
@@ -3156,7 +3165,11 @@
   unity_file
     << "/* NOLINTNEXTLINE(bugprone-suspicious-include,misc-include-cleaner) "
        "*/\n";
-  unity_file << "#include \"" << sf_full_path << "\"\n";
+  if (pathMode == UnityPathMode::Relative) {
+    unity_file << "#include \"" << relocatableIncludePath << "\"\n";
+  } else {
+    unity_file << "#include \"" << sf_full_path << "\"\n";
+  }
 
   if (afterInclude) {
     unity_file << *afterInclude << "\n";
@@ -3192,7 +3205,7 @@
   std::vector<std::string> const& configs,
   std::vector<UnityBatchedSource> const& filtered_sources,
   cmValue beforeInclude, cmValue afterInclude,
-  std::string const& filename_base, size_t batchSize)
+  std::string const& filename_base, UnityPathMode pathMode, size_t batchSize)
 {
   if (batchSize == 0) {
     batchSize = filtered_sources.size();
@@ -3210,7 +3223,7 @@
     auto const end = begin + chunk;
     unity_files.emplace_back(this->WriteUnitySource(
       target, configs, cmMakeRange(begin, end), beforeInclude, afterInclude,
-      std::move(filename)));
+      std::move(filename), filename_base, pathMode));
   }
   return unity_files;
 }
@@ -3221,7 +3234,7 @@
   std::vector<std::string> const& configs,
   std::vector<UnityBatchedSource> const& filtered_sources,
   cmValue beforeInclude, cmValue afterInclude,
-  std::string const& filename_base)
+  std::string const& filename_base, UnityPathMode pathMode)
 {
   std::vector<UnitySource> unity_files;
 
@@ -3247,7 +3260,7 @@
       cmStrCat(filename_base, "unity_", name, unity_file_extension(lang));
     unity_files.emplace_back(this->WriteUnitySource(
       target, configs, cmMakeRange(item.second), beforeInclude, afterInclude,
-      std::move(filename)));
+      std::move(filename), filename_base, pathMode));
   }
 
   return unity_files;
@@ -3305,6 +3318,9 @@
     target->GetProperty("UNITY_BUILD_CODE_BEFORE_INCLUDE");
   cmValue afterInclude = target->GetProperty("UNITY_BUILD_CODE_AFTER_INCLUDE");
   cmValue unityMode = target->GetProperty("UNITY_BUILD_MODE");
+  UnityPathMode pathMode = target->GetPropertyAsBool("UNITY_BUILD_RELOCATABLE")
+    ? UnityPathMode::Relative
+    : UnityPathMode::Absolute;
 
   for (std::string lang : { "C", "CXX", "OBJC", "OBJCXX", "CUDA" }) {
     std::vector<UnityBatchedSource> filtered_sources;
@@ -3325,11 +3341,11 @@
     if (!unityMode || *unityMode == "BATCH") {
       unity_files = AddUnityFilesModeAuto(
         target, lang, configs, filtered_sources, beforeInclude, afterInclude,
-        filename_base, unityBatchSize);
+        filename_base, pathMode, unityBatchSize);
     } else if (unityMode && *unityMode == "GROUP") {
-      unity_files =
-        AddUnityFilesModeGroup(target, lang, configs, filtered_sources,
-                               beforeInclude, afterInclude, filename_base);
+      unity_files = AddUnityFilesModeGroup(
+        target, lang, configs, filtered_sources, beforeInclude, afterInclude,
+        filename_base, pathMode);
     } else {
       // unity mode is set to an unsupported value
       std::string e("Invalid UNITY_BUILD_MODE value of " + *unityMode +
@@ -3348,6 +3364,11 @@
         unity->SetProperty("COMPILE_DEFINITIONS",
                            "CMAKE_UNITY_CONFIG_$<UPPER_CASE:$<CONFIG>>");
       }
+
+      if (pathMode == UnityPathMode::Relative) {
+        unity->AppendProperty("INCLUDE_DIRECTORIES",
+                              this->GetSourceDirectory(), false);
+      }
     }
   }
 }
diff --git a/Source/cmLocalGenerator.h b/Source/cmLocalGenerator.h
index 3ec349b..d365af3 100644
--- a/Source/cmLocalGenerator.h
+++ b/Source/cmLocalGenerator.h
@@ -699,28 +699,37 @@
     {
     }
   };
+  /** Whether to insert relative or absolute paths into unity files */
+  enum class UnityPathMode
+  {
+    Absolute,
+    Relative
+  };
 
   UnitySource WriteUnitySource(
     cmGeneratorTarget* target, std::vector<std::string> const& configs,
     cmRange<std::vector<UnityBatchedSource>::const_iterator> sources,
-    cmValue beforeInclude, cmValue afterInclude, std::string filename) const;
+    cmValue beforeInclude, cmValue afterInclude, std::string filename,
+    std::string const& unityFileDirectory, UnityPathMode pathMode) const;
   void WriteUnitySourceInclude(std::ostream& unity_file,
                                cm::optional<std::string> const& cond,
                                std::string const& sf_full_path,
                                cmValue beforeInclude, cmValue afterInclude,
-                               cmValue uniqueIdName) const;
+                               cmValue uniqueIdName, UnityPathMode pathMode,
+                               std::string const& unityFileDirectory) const;
   std::vector<UnitySource> AddUnityFilesModeAuto(
     cmGeneratorTarget* target, std::string const& lang,
     std::vector<std::string> const& configs,
     std::vector<UnityBatchedSource> const& filtered_sources,
     cmValue beforeInclude, cmValue afterInclude,
-    std::string const& filename_base, size_t batchSize);
+    std::string const& filename_base, UnityPathMode pathMode,
+    size_t batchSize);
   std::vector<UnitySource> AddUnityFilesModeGroup(
     cmGeneratorTarget* target, std::string const& lang,
     std::vector<std::string> const& configs,
     std::vector<UnityBatchedSource> const& filtered_sources,
     cmValue beforeInclude, cmValue afterInclude,
-    std::string const& filename_base);
+    std::string const& filename_base, UnityPathMode pathMode);
 };
 
 namespace detail {
diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx
index 98da10d..e73a49b 100644
--- a/Source/cmMakefile.cxx
+++ b/Source/cmMakefile.cxx
@@ -486,10 +486,6 @@
     return result;
   }
 
-  if (this->ExecuteCommandCallback) {
-    this->ExecuteCommandCallback();
-  }
-
   // Place this call on the call stack.
   cmMakefileCall stack_manager(this, lff, std::move(deferId), status);
   static_cast<void>(stack_manager);
@@ -545,6 +541,10 @@
     }
   }
 
+  if (this->ExecuteCommandCallback) {
+    this->ExecuteCommandCallback();
+  }
+
   return result;
 }
 
@@ -2088,7 +2088,7 @@
       cmList files(value);
       for (auto& file : files) {
         if (!cmIsOff(file)) {
-          file = cmSystemTools::CollapseFullPath(file);
+          file = cmSystemTools::ToNormalizedPathOnDisk(file);
         }
       }
       nvalue = files.to_string();
@@ -3722,10 +3722,7 @@
   // use the cmake object instead of calling cmake
   cmWorkingDirectory workdir(bindir);
   if (workdir.Failed()) {
-    this->IssueMessage(MessageType::FATAL_ERROR,
-                       cmStrCat("Failed to set working directory to ", bindir,
-                                " : ",
-                                std::strerror(workdir.GetLastResult())));
+    this->IssueMessage(MessageType::FATAL_ERROR, workdir.GetError());
     cmSystemTools::SetFatalErrorOccurred();
     this->IsSourceFileTryCompile = false;
     return 1;
@@ -4645,14 +4642,14 @@
   }
 
   // Deprecate old policies.
-  if (status == cmPolicies::OLD && id <= cmPolicies::CMP0129 &&
+  if (status == cmPolicies::OLD && id <= cmPolicies::CMP0139 &&
       !(this->GetCMakeInstance()->GetIsInTryCompile() &&
         (
           // Policies set by cmCoreTryCompile::TryCompileCode.
           id == cmPolicies::CMP0065 || id == cmPolicies::CMP0083 ||
           id == cmPolicies::CMP0091 || id == cmPolicies::CMP0104 ||
           id == cmPolicies::CMP0123 || id == cmPolicies::CMP0126 ||
-          id == cmPolicies::CMP0128)) &&
+          id == cmPolicies::CMP0128 || id == cmPolicies::CMP0136)) &&
       (!this->IsSet("CMAKE_WARN_DEPRECATED") ||
        this->IsOn("CMAKE_WARN_DEPRECATED"))) {
     this->IssueMessage(MessageType::DEPRECATION_WARNING,
diff --git a/Source/cmMakefileTargetGenerator.cxx b/Source/cmMakefileTargetGenerator.cxx
index 9d0d466..39efca4 100644
--- a/Source/cmMakefileTargetGenerator.cxx
+++ b/Source/cmMakefileTargetGenerator.cxx
@@ -683,9 +683,7 @@
   // The object file should be checked for dependency integrity.
   std::string objFullPath =
     cmStrCat(this->LocalGenerator->GetCurrentBinaryDirectory(), '/', obj);
-  objFullPath = cmSystemTools::CollapseFullPath(objFullPath);
-  std::string const srcFullPath =
-    cmSystemTools::CollapseFullPath(source.GetFullPath());
+  std::string const srcFullPath = source.GetFullPath();
   this->LocalGenerator->AddImplicitDepends(this->GeneratorTarget, lang,
                                            objFullPath, srcFullPath, scanner);
 
@@ -1560,8 +1558,7 @@
               cmOutputConverter::SHELL)
          << " "
          << this->LocalGenerator->ConvertToOutputFormat(
-              cmSystemTools::CollapseFullPath(this->InfoFileNameFull),
-              cmOutputConverter::SHELL);
+              this->InfoFileNameFull, cmOutputConverter::SHELL);
   if (this->LocalGenerator->GetColorMakefile()) {
     depCmd << " \"--color=$(COLOR)\"";
   }
diff --git a/Source/cmMessageCommand.cxx b/Source/cmMessageCommand.cxx
index a4b2538..3a40069 100644
--- a/Source/cmMessageCommand.cxx
+++ b/Source/cmMessageCommand.cxx
@@ -26,14 +26,6 @@
 
 namespace {
 
-enum class CheckingType
-{
-  UNDEFINED,
-  CHECK_START,
-  CHECK_PASS,
-  CHECK_FAIL
-};
-
 std::string IndentText(std::string text, cmMakefile& mf)
 {
   auto indent =
@@ -106,7 +98,7 @@
   auto type = MessageType::MESSAGE;
   auto fatal = false;
   auto level = Message::LogLevel::LOG_UNDEFINED;
-  auto checkingType = CheckingType::UNDEFINED;
+  auto checkingType = Message::CheckType::UNDEFINED;
   if (*i == "SEND_ERROR") {
     type = MessageType::FATAL_ERROR;
     level = Message::LogLevel::LOG_ERROR;
@@ -135,15 +127,15 @@
     ++i;
   } else if (*i == "CHECK_START") {
     level = Message::LogLevel::LOG_STATUS;
-    checkingType = CheckingType::CHECK_START;
+    checkingType = Message::CheckType::CHECK_START;
     ++i;
   } else if (*i == "CHECK_PASS") {
     level = Message::LogLevel::LOG_STATUS;
-    checkingType = CheckingType::CHECK_PASS;
+    checkingType = Message::CheckType::CHECK_PASS;
     ++i;
   } else if (*i == "CHECK_FAIL") {
     level = Message::LogLevel::LOG_STATUS;
-    checkingType = CheckingType::CHECK_FAIL;
+    checkingType = Message::CheckType::CHECK_FAIL;
     ++i;
   } else if (*i == "CONFIGURE_LOG") {
 #ifndef CMAKE_BOOTSTRAP
@@ -217,16 +209,16 @@
 
     case Message::LogLevel::LOG_STATUS:
       switch (checkingType) {
-        case CheckingType::CHECK_START:
+        case Message::CheckType::CHECK_START:
           mf.DisplayStatus(IndentText(message, mf), -1);
           mf.GetCMakeInstance()->PushCheckInProgressMessage(message);
           break;
 
-        case CheckingType::CHECK_PASS:
+        case Message::CheckType::CHECK_PASS:
           ReportCheckResult("CHECK_PASS"_s, message, mf);
           break;
 
-        case CheckingType::CHECK_FAIL:
+        case Message::CheckType::CHECK_FAIL:
           ReportCheckResult("CHECK_FAIL"_s, message, mf);
           break;
 
diff --git a/Source/cmMessageType.h b/Source/cmMessageType.h
index decb4b3..a877592 100644
--- a/Source/cmMessageType.h
+++ b/Source/cmMessageType.h
@@ -6,6 +6,7 @@
 
 enum class MessageType
 {
+  UNDEFINED,
   AUTHOR_WARNING,
   AUTHOR_ERROR,
   FATAL_ERROR,
@@ -31,4 +32,13 @@
   LOG_DEBUG,
   LOG_TRACE
 };
+
+enum class CheckType
+{
+  UNDEFINED,
+  CHECK_START,
+  CHECK_PASS,
+  CHECK_FAIL
+};
+
 }
diff --git a/Source/cmMessenger.cxx b/Source/cmMessenger.cxx
index 01ff7f0..1583353 100644
--- a/Source/cmMessenger.cxx
+++ b/Source/cmMessenger.cxx
@@ -102,11 +102,10 @@
       t == MessageType::DEPRECATION_ERROR || t == MessageType::AUTHOR_ERROR) {
     cmSystemTools::SetErrorOccurred();
     md.title = "Error";
-    cmSystemTools::Message(msg.str(), md);
   } else {
     md.title = "Warning";
-    cmSystemTools::Message(msg.str(), md);
   }
+  cmSystemTools::Message(msg.str(), md);
 }
 
 void PrintCallStack(std::ostream& out, cmListFileBacktrace bt,
diff --git a/Source/cmNinjaTargetGenerator.cxx b/Source/cmNinjaTargetGenerator.cxx
index 720020d..36b4f38 100644
--- a/Source/cmNinjaTargetGenerator.cxx
+++ b/Source/cmNinjaTargetGenerator.cxx
@@ -2273,7 +2273,10 @@
   }
 
   compileObjectVars.Source = escapedSourceFileName.c_str();
-  compileObjectVars.Object = objectFileName.c_str();
+  std::string escapedObjectFileName =
+    this->LocalGenerator->ConvertToOutputFormat(objectFileName,
+                                                cmOutputConverter::SHELL);
+  compileObjectVars.Object = escapedObjectFileName.c_str();
   compileObjectVars.ObjectDir = objectDir.c_str();
   compileObjectVars.ObjectFileDir = objectFileDir.c_str();
   compileObjectVars.Flags = fullFlags.c_str();
diff --git a/Source/cmQtAutoGenInitializer.cxx b/Source/cmQtAutoGenInitializer.cxx
index 1fd406c..720cfa2 100644
--- a/Source/cmQtAutoGenInitializer.cxx
+++ b/Source/cmQtAutoGenInitializer.cxx
@@ -360,11 +360,11 @@
   if (genVars.ExecutableTarget) {
     dependencies.push_back(genVars.ExecutableTarget->Target->GetName());
   } else if (this->MultiConfig && this->UseBetterGraph) {
-    cm::string_view const& configGenexWithCommandConfig =
+    cm::string_view const configGenexWithCommandConfig =
       "$<COMMAND_CONFIG:$<$<CONFIG:";
-    cm::string_view const& configGenex = "$<$<CONFIG:";
-    cm::string_view const& configGenexEnd = ">";
-    cm::string_view const& configGenexEndWithCommandConfig = ">>";
+    cm::string_view const configGenex = "$<$<CONFIG:";
+    cm::string_view const configGenexEnd = ">";
+    cm::string_view const configGenexEndWithCommandConfig = ">>";
     auto genexBegin =
       this->CrossConfig ? configGenexWithCommandConfig : configGenex;
     auto genexEnd =
diff --git a/Source/cmQtAutoGenerator.cxx b/Source/cmQtAutoGenerator.cxx
index 376ac2a..cbd5949 100644
--- a/Source/cmQtAutoGenerator.cxx
+++ b/Source/cmQtAutoGenerator.cxx
@@ -439,7 +439,6 @@
 
   // Info file
   this->InfoFile_ = std::string(infoFile);
-  cmSystemTools::CollapseFullPath(this->InfoFile_);
   this->InfoDir_ = cmSystemTools::GetFilenamePath(this->InfoFile_);
 
   // Load info file time
diff --git a/Source/cmSearchPath.cxx b/Source/cmSearchPath.cxx
index ec70c05..bf3480f 100644
--- a/Source/cmSearchPath.cxx
+++ b/Source/cmSearchPath.cxx
@@ -62,7 +62,9 @@
   // Process them all from the current directory
   for (std::string const& p : outPaths) {
     this->AddPathInternal(
-      p, "", this->FC->Makefile->GetCurrentSourceDirectory().c_str());
+      cmSystemTools::CollapseFullPath(
+        p, this->FC->Makefile->GetCurrentSourceDirectory()),
+      "");
   }
 }
 
@@ -76,15 +78,17 @@
 
     for (std::string const& p : expanded) {
       this->AddPathInternal(
-        p, "", this->FC->Makefile->GetCurrentSourceDirectory().c_str());
+        cmSystemTools::CollapseFullPath(
+          p, this->FC->Makefile->GetCurrentSourceDirectory()),
+        "");
     }
   }
 }
 
 void cmSearchPath::AddEnvPath(const std::string& variable)
 {
-  std::vector<std::string> expanded;
-  cmSystemTools::GetPath(expanded, variable.c_str());
+  std::vector<std::string> expanded =
+    cmSystemTools::GetEnvPathNormalized(variable);
   for (std::string const& p : expanded) {
     this->AddPathInternal(p, "");
   }
@@ -97,9 +101,11 @@
   // Get a path from a CMake variable.
   if (cmValue value = this->FC->Makefile->GetDefinition(variable)) {
     cmList expanded{ *value };
-
-    this->AddPrefixPaths(
-      expanded, this->FC->Makefile->GetCurrentSourceDirectory().c_str());
+    for (std::string& p : expanded) {
+      p = cmSystemTools::CollapseFullPath(
+        p, this->FC->Makefile->GetCurrentSourceDirectory());
+    }
+    this->AddPrefixPaths(expanded);
   }
 }
 
@@ -114,8 +120,8 @@
 
 void cmSearchPath::AddEnvPrefixPath(const std::string& variable, bool stripBin)
 {
-  std::vector<std::string> expanded;
-  cmSystemTools::GetPath(expanded, variable.c_str());
+  std::vector<std::string> expanded =
+    cmSystemTools::GetEnvPathNormalized(variable);
   if (stripBin) {
     std::transform(expanded.begin(), expanded.end(), expanded.begin(),
                    cmSearchPathStripBin);
@@ -151,8 +157,7 @@
   }
 }
 
-void cmSearchPath::AddPrefixPaths(const std::vector<std::string>& paths,
-                                  const char* base)
+void cmSearchPath::AddPrefixPaths(const std::vector<std::string>& paths)
 {
   assert(this->FC);
 
@@ -192,52 +197,43 @@
               "CMAKE_PREFIX_LIBRARY_ARCHITECTURE")) {
           if (foundUnknown) {
             this->AddPathInternal(cmStrCat('/', archNoUnknown, dir, subdir),
-                                  cmStrCat('/', archNoUnknown, prefix), base);
+                                  cmStrCat('/', archNoUnknown, prefix));
           }
           this->AddPathInternal(cmStrCat('/', *arch, dir, subdir),
-                                cmStrCat('/', *arch, prefix), base);
+                                cmStrCat('/', *arch, prefix));
         } else {
           if (foundUnknown) {
             this->AddPathInternal(cmStrCat(dir, subdir, '/', archNoUnknown),
-                                  prefix, base);
+                                  prefix);
           }
-          this->AddPathInternal(cmStrCat(dir, subdir, '/', *arch), prefix,
-                                base);
+          this->AddPathInternal(cmStrCat(dir, subdir, '/', *arch), prefix);
         }
       }
     }
     std::string add = dir + subdir;
     if (add != "/") {
-      this->AddPathInternal(add, prefix, base);
+      this->AddPathInternal(add, prefix);
     }
     if (subdir == "bin") {
-      this->AddPathInternal(dir + "sbin", prefix, base);
+      this->AddPathInternal(dir + "sbin", prefix);
     }
     if (!subdir.empty() && path != "/") {
-      this->AddPathInternal(path, prefix, base);
+      this->AddPathInternal(path, prefix);
     }
   }
 }
 
 void cmSearchPath::AddPathInternal(const std::string& path,
-                                   const std::string& prefix, const char* base)
+                                   const std::string& prefix)
 {
   assert(this->FC);
 
-  std::string collapsedPath = cmSystemTools::CollapseFullPath(path, base);
-
-  if (collapsedPath.empty()) {
+  if (path.empty()) {
     return;
   }
 
-  std::string collapsedPrefix;
-  if (!prefix.empty()) {
-    collapsedPrefix = cmSystemTools::CollapseFullPath(prefix, base);
-  }
-
   // Insert the path if has not already been emitted.
-  PathWithPrefix pathWithPrefix{ std::move(collapsedPath),
-                                 std::move(collapsedPrefix) };
+  PathWithPrefix pathWithPrefix{ path, prefix };
   if (this->FC->SearchPathsEmitted.insert(pathWithPrefix).second) {
     this->Paths.emplace_back(std::move(pathWithPrefix));
   }
diff --git a/Source/cmSearchPath.h b/Source/cmSearchPath.h
index 4c0cabb..895f4a3 100644
--- a/Source/cmSearchPath.h
+++ b/Source/cmSearchPath.h
@@ -55,12 +55,10 @@
   void AddCMakePrefixPath(const std::string& variable);
   void AddEnvPrefixPath(const std::string& variable, bool stripBin = false);
   void AddSuffixes(const std::vector<std::string>& suffixes);
-  void AddPrefixPaths(const std::vector<std::string>& paths,
-                      const char* base = nullptr);
+  void AddPrefixPaths(const std::vector<std::string>& paths);
 
 protected:
-  void AddPathInternal(const std::string& path, const std::string& prefix,
-                       const char* base = nullptr);
+  void AddPathInternal(const std::string& path, const std::string& prefix);
 
   cmFindCommon* FC;
   std::vector<PathWithPrefix> Paths;
diff --git a/Source/cmStateDirectory.cxx b/Source/cmStateDirectory.cxx
index 343bee5..104e707 100644
--- a/Source/cmStateDirectory.cxx
+++ b/Source/cmStateDirectory.cxx
@@ -21,7 +21,6 @@
 #include "cmState.h"
 #include "cmStatePrivate.h"
 #include "cmStateTypes.h"
-#include "cmSystemTools.h"
 #include "cmValue.h"
 
 static std::string const kBINARY_DIR = "BINARY_DIR";
@@ -36,11 +35,9 @@
 
 void cmStateDirectory::SetCurrentSource(std::string const& dir)
 {
-  std::string& loc = this->DirectoryState->Location;
-  loc = dir;
-  cmSystemTools::ConvertToUnixSlashes(loc);
-  loc = cmSystemTools::CollapseFullPath(loc);
-  this->Snapshot_.SetDefinition("CMAKE_CURRENT_SOURCE_DIR", loc);
+  this->DirectoryState->Location = dir;
+  this->Snapshot_.SetDefinition("CMAKE_CURRENT_SOURCE_DIR",
+                                this->DirectoryState->Location);
 }
 
 std::string const& cmStateDirectory::GetCurrentBinary() const
@@ -50,11 +47,9 @@
 
 void cmStateDirectory::SetCurrentBinary(std::string const& dir)
 {
-  std::string& loc = this->DirectoryState->OutputLocation;
-  loc = dir;
-  cmSystemTools::ConvertToUnixSlashes(loc);
-  loc = cmSystemTools::CollapseFullPath(loc);
-  this->Snapshot_.SetDefinition("CMAKE_CURRENT_BINARY_DIR", loc);
+  this->DirectoryState->OutputLocation = dir;
+  this->Snapshot_.SetDefinition("CMAKE_CURRENT_BINARY_DIR",
+                                this->DirectoryState->OutputLocation);
 }
 
 cmStateDirectory::cmStateDirectory(
diff --git a/Source/cmSystemTools.cxx b/Source/cmSystemTools.cxx
index 5ad0439..dcd90bc 100644
--- a/Source/cmSystemTools.cxx
+++ b/Source/cmSystemTools.cxx
@@ -20,6 +20,8 @@
 
 #include "cmSystemTools.h"
 
+#include <iterator>
+
 #include <cm/optional>
 #include <cmext/algorithm>
 #include <cmext/string_view>
@@ -133,7 +135,7 @@
 #  if defined(_WIN32)
 extern __declspec(dllimport) char** environ;
 #  else
-extern char** environ;
+extern char** environ; // NOLINT(readability-redundant-declaration)
 #  endif
 #endif
 
@@ -1614,20 +1616,60 @@
   return result;
 }
 
-std::vector<std::string> cmSystemTools::SplitEnvPath(std::string const& value)
+std::vector<std::string> cmSystemTools::GetEnvPathNormalized(
+  std::string const& var)
+{
+  std::vector<std::string> result;
+  if (cm::optional<std::string> env = cmSystemTools::GetEnvVar(var)) {
+    std::vector<std::string> p = cmSystemTools::SplitEnvPathNormalized(*env);
+    std::move(p.begin(), p.end(), std::back_inserter(result));
+  }
+  return result;
+}
+
+std::vector<std::string> cmSystemTools::SplitEnvPath(cm::string_view in)
 {
 #if defined(_WIN32) && !defined(__CYGWIN__)
   static cm::string_view sep = ";"_s;
 #else
   static cm::string_view sep = ":"_s;
 #endif
-  std::vector<std::string> paths = cmTokenize(value, sep);
-  for (std::string& p : paths) {
-    SystemTools::ConvertToUnixSlashes(p);
+  std::vector<std::string> paths;
+  cm::string_view::size_type e = 0;
+  for (;;) {
+    cm::string_view::size_type b = in.find_first_not_of(sep, e);
+    if (b == cm::string_view::npos) {
+      break;
+    }
+    e = in.find_first_of(sep, b);
+    if (e == cm::string_view::npos) {
+      paths.emplace_back(in.substr(b));
+      break;
+    }
+    paths.emplace_back(in.substr(b, e - b));
   }
   return paths;
 }
 
+std::vector<std::string> cmSystemTools::SplitEnvPathNormalized(
+  cm::string_view in)
+{
+  std::vector<std::string> paths = cmSystemTools::SplitEnvPath(in);
+  std::transform(paths.begin(), paths.end(), paths.begin(),
+                 cmSystemTools::ToNormalizedPathOnDisk);
+  return paths;
+}
+
+std::string cmSystemTools::ToNormalizedPathOnDisk(std::string p)
+{
+  p = cmSystemTools::CollapseFullPath(p);
+  cmSystemTools::ConvertToUnixSlashes(p);
+#ifdef _WIN32
+  p = cmSystemTools::GetActualCaseForPathCached(p);
+#endif
+  return p;
+}
+
 #ifndef CMAKE_BOOTSTRAP
 bool cmSystemTools::UnsetEnv(const char* value)
 {
@@ -2739,7 +2781,7 @@
 
 std::string cmSystemTools::GetCurrentWorkingDirectory()
 {
-  return cmSystemTools::CollapseFullPath(
+  return cmSystemTools::ToNormalizedPathOnDisk(
     cmsys::SystemTools::GetCurrentWorkingDirectory());
 }
 
diff --git a/Source/cmSystemTools.h b/Source/cmSystemTools.h
index 0531f63..5c45cc7 100644
--- a/Source/cmSystemTools.h
+++ b/Source/cmSystemTools.h
@@ -399,7 +399,12 @@
                                      std::string const& in);
 
   static cm::optional<std::string> GetEnvVar(std::string const& var);
-  static std::vector<std::string> SplitEnvPath(std::string const& value);
+  static std::vector<std::string> GetEnvPathNormalized(std::string const& var);
+
+  static std::vector<std::string> SplitEnvPath(cm::string_view in);
+  static std::vector<std::string> SplitEnvPathNormalized(cm::string_view in);
+
+  static std::string ToNormalizedPathOnDisk(std::string p);
 
 #ifndef CMAKE_BOOTSTRAP
   /** Remove an environment variable */
diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx
index f220837..87678b1 100644
--- a/Source/cmTarget.cxx
+++ b/Source/cmTarget.cxx
@@ -250,7 +250,7 @@
 
   void CopyFromEntries(cmBTStringRange entries)
   {
-    return cm::append(this->Entries, entries);
+    cm::append(this->Entries, entries);
   }
 
   enum class Action
@@ -354,6 +354,8 @@
   }
 
   cm::static_string_view const Name;
+  // Explicit initialization is needed for AppleClang in Xcode 8 and below
+  // NOLINTNEXTLINE(readability-redundant-member-init)
   cm::optional<cm::static_string_view> const Default = {};
   InitCondition const InitConditional = InitCondition::Always;
   Repetition const Repeat = Repetition::Once;
@@ -551,6 +553,7 @@
   { "UNITY_BUILD_UNIQUE_ID"_s, IC::CanCompileSources },
   { "UNITY_BUILD_BATCH_SIZE"_s, "8"_s, IC::CanCompileSources },
   { "UNITY_BUILD_MODE"_s, "BATCH"_s, IC::CanCompileSources },
+  { "UNITY_BUILD_RELOCATABLE"_s, IC::CanCompileSources },
   { "OPTIMIZE_DEPENDENCIES"_s, IC::CanCompileSources },
   { "VERIFY_INTERFACE_HEADER_SETS"_s },
   // -- Android
@@ -2047,10 +2050,13 @@
 {
   ReadOnlyProperty(ReadOnlyCondition cond)
     : Condition{ cond }
-    , Policy{} {};
+  {
+  }
   ReadOnlyProperty(ReadOnlyCondition cond, cmPolicies::PolicyID id)
     : Condition{ cond }
-    , Policy{ id } {};
+    , Policy{ id }
+  {
+  }
 
   ReadOnlyCondition Condition;
   cm::optional<cmPolicies::PolicyID> Policy;
diff --git a/Source/cmTargetLinkLibrariesCommand.cxx b/Source/cmTargetLinkLibrariesCommand.cxx
index 51dce76..efa8f44 100644
--- a/Source/cmTargetLinkLibrariesCommand.cxx
+++ b/Source/cmTargetLinkLibrariesCommand.cxx
@@ -97,8 +97,8 @@
   if (!target) {
     MessageType t = MessageType::FATAL_ERROR; // fail by default
     std::ostringstream e;
-    e << "Cannot specify link libraries for target \"" << args[0] << "\" "
-      << "which is not built by this project.";
+    e << "Cannot specify link libraries for target \"" << args[0]
+      << "\" which is not built by this project.";
     // The bad target is the only argument. Check how policy CMP0016 is set,
     // and accept, warn or fail respectively:
     if (args.size() < 2) {
@@ -107,9 +107,9 @@
           t = MessageType::AUTHOR_WARNING;
           // Print the warning.
           e << "\n"
-            << "CMake does not support this but it used to work accidentally "
-            << "and is being allowed for compatibility."
-            << "\n"
+               "CMake does not support this but it used to work accidentally "
+               "and is being allowed for compatibility."
+               "\n"
             << cmPolicies::GetPolicyWarning(cmPolicies::CMP0016);
           break;
         case cmPolicies::OLD: // OLD behavior does not warn.
@@ -117,7 +117,7 @@
           break;
         case cmPolicies::REQUIRED_IF_USED:
         case cmPolicies::REQUIRED_ALWAYS:
-          e << "\n" << cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0016);
+          e << '\n' << cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0016);
           break;
         case cmPolicies::NEW: // NEW behavior prints the error.
           break;
@@ -145,7 +145,7 @@
     MessageType messageType = MessageType::AUTHOR_WARNING;
     switch (mf.GetPolicyStatus(cmPolicies::CMP0039)) {
       case cmPolicies::WARN:
-        e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0039) << "\n";
+        e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0039) << '\n';
         modal = "should";
         CM_FALLTHROUGH;
       case cmPolicies::OLD:
@@ -456,7 +456,7 @@
     MessageType messageType = MessageType::AUTHOR_WARNING;
     switch (this->Makefile.GetPolicyStatus(cmPolicies::CMP0023)) {
       case cmPolicies::WARN:
-        e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0023) << "\n";
+        e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0023) << '\n';
         modal = "should";
         CM_FALLTHROUGH;
       case cmPolicies::OLD:
diff --git a/Source/cmUVHandlePtr.h b/Source/cmUVHandlePtr.h
index b8b3491..ef88ef1 100644
--- a/Source/cmUVHandlePtr.h
+++ b/Source/cmUVHandlePtr.h
@@ -158,10 +158,10 @@
 };
 
 template <typename T>
-inline uv_handle_ptr_base_<T>::uv_handle_ptr_base_(
+uv_handle_ptr_base_<T>::uv_handle_ptr_base_(
   uv_handle_ptr_base_<T>&&) noexcept = default;
 template <typename T>
-inline uv_handle_ptr_base_<T>& uv_handle_ptr_base_<T>::operator=(
+uv_handle_ptr_base_<T>& uv_handle_ptr_base_<T>::operator=(
   uv_handle_ptr_base_<T>&&) noexcept = default;
 
 /**
diff --git a/Source/cmWorkingDirectory.cxx b/Source/cmWorkingDirectory.cxx
index 12fae12..574699c 100644
--- a/Source/cmWorkingDirectory.cxx
+++ b/Source/cmWorkingDirectory.cxx
@@ -2,8 +2,7 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmWorkingDirectory.h"
 
-#include <cerrno>
-
+#include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 
 cmWorkingDirectory::cmWorkingDirectory(std::string const& newdir)
@@ -19,11 +18,13 @@
 
 bool cmWorkingDirectory::SetDirectory(std::string const& newdir)
 {
-  if (cmSystemTools::ChangeDirectory(newdir)) {
-    this->ResultCode = 0;
+  cmsys::Status status = cmSystemTools::ChangeDirectory(newdir);
+  if (status) {
+    this->Error.clear();
     return true;
   }
-  this->ResultCode = errno;
+  this->Error = cmStrCat("Failed to change working directory to \"", newdir,
+                         "\": ", status.GetString());
   return false;
 }
 
diff --git a/Source/cmWorkingDirectory.h b/Source/cmWorkingDirectory.h
index e593621..82f79bc 100644
--- a/Source/cmWorkingDirectory.h
+++ b/Source/cmWorkingDirectory.h
@@ -26,19 +26,11 @@
 
   bool SetDirectory(std::string const& newdir);
   void Pop();
-  bool Failed() const { return this->ResultCode != 0; }
-
-  /** \return 0 if the last attempt to set the working directory was
-   *          successful. If it failed, the value returned will be the
-   *          \c errno value associated with the failure. A description
-   *          of the error code can be obtained by passing the result
-   *          to \c std::strerror().
-   */
-  int GetLastResult() const { return this->ResultCode; }
-
+  bool Failed() const { return !this->Error.empty(); }
+  std::string const& GetError() const { return this->Error; }
   std::string const& GetOldDirectory() const { return this->OldDir; }
 
 private:
   std::string OldDir;
-  int ResultCode;
+  std::string Error;
 };
diff --git a/Source/cmake.cxx b/Source/cmake.cxx
index fcee5e5..ffbf706 100644
--- a/Source/cmake.cxx
+++ b/Source/cmake.cxx
@@ -7,7 +7,6 @@
 #include <chrono>
 #include <cstdio>
 #include <cstdlib>
-#include <cstring>
 #include <initializer_list>
 #include <iomanip>
 #include <iostream>
@@ -690,7 +689,7 @@
         cmSystemTools::Stdout("loading initial cache file " + value + "\n");
         // Resolve script path specified on command line
         // relative to $PWD.
-        auto path = cmSystemTools::CollapseFullPath(value);
+        auto path = cmSystemTools::ToNormalizedPathOnDisk(value);
         state->ReadListFile(args, path);
         return true;
       } },
@@ -779,10 +778,7 @@
     snapshot.SetDefaultDefinitions();
     cmMakefile mf(gg, snapshot);
     if (this->GetWorkingMode() != NORMAL_MODE) {
-      std::string file(cmSystemTools::CollapseFullPath(path));
-      cmSystemTools::ConvertToUnixSlashes(file);
-      mf.SetScriptModeFile(file);
-
+      mf.SetScriptModeFile(cmSystemTools::ToNormalizedPathOnDisk(path));
       mf.SetArgcArgv(args);
     }
     if (!cmSystemTools::FileExists(path, true)) {
@@ -956,10 +952,8 @@
       cmSystemTools::Error("No source directory specified for -S");
       return false;
     }
-    std::string path = cmSystemTools::CollapseFullPath(value);
-    cmSystemTools::ConvertToUnixSlashes(path);
-
-    state->SetHomeDirectoryViaCommandLine(path);
+    state->SetHomeDirectoryViaCommandLine(
+      cmSystemTools::ToNormalizedPathOnDisk(value));
     return true;
   };
 
@@ -968,9 +962,8 @@
       cmSystemTools::Error("No build directory specified for -B");
       return false;
     }
-    std::string path = cmSystemTools::CollapseFullPath(value);
-    cmSystemTools::ConvertToUnixSlashes(path);
-    state->SetHomeOutputDirectory(path);
+    state->SetHomeOutputDirectory(
+      cmSystemTools::ToNormalizedPathOnDisk(value));
     haveBArg = true;
     return true;
   };
@@ -1074,7 +1067,8 @@
     CommandArgument{ "--graphviz", "No file specified for --graphviz",
                      CommandArgument::Values::One,
                      [](std::string const& value, cmake* state) -> bool {
-                       state->SetGraphVizFile(value);
+                       state->SetGraphVizFile(
+                         cmSystemTools::ToNormalizedPathOnDisk(value));
                        return true;
                      } },
 
@@ -1278,23 +1272,22 @@
                        return false;
 #endif
                      } },
-    CommandArgument{
-      "--debugger-dap-log", "No file specified for --debugger-dap-log",
-      CommandArgument::Values::One,
-      [](std::string const& value, cmake* state) -> bool {
+    CommandArgument{ "--debugger-dap-log",
+                     "No file specified for --debugger-dap-log",
+                     CommandArgument::Values::One,
+                     [](std::string const& value, cmake* state) -> bool {
 #ifdef CMake_ENABLE_DEBUGGER
-        std::string path = cmSystemTools::CollapseFullPath(value);
-        cmSystemTools::ConvertToUnixSlashes(path);
-        state->DebuggerDapLogFile = path;
-        return true;
+                       state->DebuggerDapLogFile =
+                         cmSystemTools::ToNormalizedPathOnDisk(value);
+                       return true;
 #else
-        static_cast<void>(value);
-        static_cast<void>(state);
-        cmSystemTools::Error(
-          "CMake was not built with support for --debugger-dap-log");
-        return false;
+                       static_cast<void>(value);
+                       static_cast<void>(state);
+                       cmSystemTools::Error("CMake was not built with support "
+                                            "for --debugger-dap-log");
+                       return false;
 #endif
-      } },
+                     } },
   };
 
 #if defined(CMAKE_HAVE_VS_GENERATORS)
@@ -1316,9 +1309,8 @@
   arguments.emplace_back(
     "--profiling-output", "No path specified for --profiling-output",
     CommandArgument::Values::One,
-    [&](std::string const& value, cmake*) -> bool {
-      profilingOutput = cmSystemTools::CollapseFullPath(value);
-      cmSystemTools::ConvertToUnixSlashes(profilingOutput);
+    [&profilingOutput](std::string const& value, cmake*) -> bool {
+      profilingOutput = cmSystemTools::ToNormalizedPathOnDisk(value);
       return true;
     });
   arguments.emplace_back("--preset", "No preset specified for --preset",
@@ -1588,7 +1580,8 @@
 
     if (!expandedPreset->GraphVizFile.empty()) {
       if (this->GraphVizFile.empty()) {
-        this->SetGraphVizFile(expandedPreset->GraphVizFile);
+        this->SetGraphVizFile(
+          cmSystemTools::CollapseFullPath(expandedPreset->GraphVizFile));
       }
     }
 
@@ -1779,8 +1772,7 @@
   bool is_source_dir = false;
   bool is_empty_directory = false;
   if (cmSystemTools::FileIsDirectory(arg)) {
-    std::string path = cmSystemTools::CollapseFullPath(arg);
-    cmSystemTools::ConvertToUnixSlashes(path);
+    std::string path = cmSystemTools::ToNormalizedPathOnDisk(arg);
     std::string cacheFile = cmStrCat(path, "/CMakeCache.txt");
     std::string listFile = cmStrCat(path, "/CMakeLists.txt");
 
@@ -1795,7 +1787,7 @@
       is_source_dir = true;
     }
   } else if (cmSystemTools::FileExists(arg)) {
-    std::string fullPath = cmSystemTools::CollapseFullPath(arg);
+    std::string fullPath = cmSystemTools::ToNormalizedPathOnDisk(arg);
     std::string name = cmSystemTools::GetFilenameName(fullPath);
     name = cmSystemTools::LowerCase(name);
     if (name == "cmakecache.txt"_s) {
@@ -1846,14 +1838,13 @@
     if (is_source_dir) {
       this->SetHomeDirectoryViaCommandLine(listPath);
       if (no_build_tree) {
-        std::string cwd = cmSystemTools::GetCurrentWorkingDirectory();
-        this->SetHomeOutputDirectory(cwd);
+        this->SetHomeOutputDirectory(
+          cmSystemTools::GetCurrentWorkingDirectory());
       }
     } else if (no_source_tree && no_build_tree) {
       this->SetHomeDirectory(listPath);
-
-      std::string cwd = cmSystemTools::GetCurrentWorkingDirectory();
-      this->SetHomeOutputDirectory(cwd);
+      this->SetHomeOutputDirectory(
+        cmSystemTools::GetCurrentWorkingDirectory());
     } else if (no_build_tree) {
       this->SetHomeOutputDirectory(listPath);
     }
@@ -1861,18 +1852,16 @@
     if (no_source_tree) {
       // We didn't find a CMakeLists.txt and it wasn't specified
       // with -S. Assume it is the path to the source tree
-      std::string full = cmSystemTools::CollapseFullPath(arg);
-      this->SetHomeDirectory(full);
+      this->SetHomeDirectory(cmSystemTools::ToNormalizedPathOnDisk(arg));
     }
     if (no_build_tree && !no_source_tree && is_empty_directory) {
       // passed `-S <path> <build_dir> when build_dir is an empty directory
-      std::string full = cmSystemTools::CollapseFullPath(arg);
-      this->SetHomeOutputDirectory(full);
+      this->SetHomeOutputDirectory(cmSystemTools::ToNormalizedPathOnDisk(arg));
     } else if (no_build_tree) {
       // We didn't find a CMakeCache.txt and it wasn't specified
       // with -B. Assume the current working directory as the build tree.
-      std::string cwd = cmSystemTools::GetCurrentWorkingDirectory();
-      this->SetHomeOutputDirectory(cwd);
+      this->SetHomeOutputDirectory(
+        cmSystemTools::GetCurrentWorkingDirectory());
       used_provided_path = false;
     }
   }
@@ -3548,8 +3537,7 @@
       // file to it, so we wouldn't expect to get here unless the default
       // permissions are questionable or some other process has deleted the
       // directory
-      std::cerr << "Failed to change to directory " << destPath << " : "
-                << std::strerror(workdir.GetLastResult()) << '\n';
+      std::cerr << workdir.GetError() << '\n';
       return 1;
     }
     std::vector<std::string> args2;
diff --git a/Source/cmake.h b/Source/cmake.h
index cfe4edd..1f2484b 100644
--- a/Source/cmake.h
+++ b/Source/cmake.h
@@ -26,7 +26,6 @@
 #include "cmState.h"
 #include "cmStateSnapshot.h"
 #include "cmStateTypes.h"
-#include "cmSystemTools.h"
 #include "cmValue.h"
 
 #if !defined(CMAKE_BOOTSTRAP)
@@ -300,12 +299,7 @@
   }
 
   //! Set the name of the graphviz file.
-  void SetGraphVizFile(std::string const& ts)
-  {
-    std::string path = cmSystemTools::CollapseFullPath(ts);
-    cmSystemTools::ConvertToUnixSlashes(path);
-    this->GraphVizFile = path;
-  }
+  void SetGraphVizFile(std::string const& ts) { this->GraphVizFile = ts; }
 
   bool IsAKnownSourceExtension(cm::string_view ext) const
   {
diff --git a/Source/cmakemain.cxx b/Source/cmakemain.cxx
index b57cdb3..eb4f9f2 100644
--- a/Source/cmakemain.cxx
+++ b/Source/cmakemain.cxx
@@ -601,7 +601,7 @@
         }
       }
       if (!matched && i == 0) {
-        dir = cmSystemTools::CollapseFullPath(arg);
+        dir = cmSystemTools::ToNormalizedPathOnDisk(arg);
         matched = true;
         parsed = true;
       }
@@ -873,7 +873,7 @@
   };
 
   if (ac >= 3) {
-    dir = cmSystemTools::CollapseFullPath(av[2]);
+    dir = cmSystemTools::ToNormalizedPathOnDisk(av[2]);
 
     std::vector<std::string> inputArgs;
     inputArgs.reserve(ac - 3);
@@ -1098,7 +1098,7 @@
   for (int i = 2; i < ac; ++i) {
     switch (doing) {
       case DoingDir:
-        dir = cmSystemTools::CollapseFullPath(av[i]);
+        dir = cmSystemTools::ToNormalizedPathOnDisk(av[i]);
         doing = DoingNone;
         break;
       default:
diff --git a/Source/cmcmd.cxx b/Source/cmcmd.cxx
index c82cb32..997c1a6 100644
--- a/Source/cmcmd.cxx
+++ b/Source/cmcmd.cxx
@@ -1347,10 +1347,10 @@
 
       // Create a local generator configured for the directory in
       // which dependencies will be scanned.
-      homeDir = cmSystemTools::CollapseFullPath(homeDir);
-      startDir = cmSystemTools::CollapseFullPath(startDir);
-      homeOutDir = cmSystemTools::CollapseFullPath(homeOutDir);
-      startOutDir = cmSystemTools::CollapseFullPath(startOutDir);
+      homeDir = cmSystemTools::ToNormalizedPathOnDisk(homeDir);
+      startDir = cmSystemTools::ToNormalizedPathOnDisk(startDir);
+      homeOutDir = cmSystemTools::ToNormalizedPathOnDisk(homeOutDir);
+      startOutDir = cmSystemTools::ToNormalizedPathOnDisk(startOutDir);
       cm.SetHomeDirectory(homeDir);
       cm.SetHomeOutputDirectory(homeOutDir);
       cm.GetCurrentSnapshot().SetDefaultDefinitions();
@@ -1643,10 +1643,10 @@
       std::string startDir;
       std::string homeOutDir;
       std::string startOutDir;
-      homeDir = cmSystemTools::CollapseFullPath(args[4]);
-      startDir = cmSystemTools::CollapseFullPath(args[5]);
-      homeOutDir = cmSystemTools::CollapseFullPath(args[6]);
-      startOutDir = cmSystemTools::CollapseFullPath(args[7]);
+      homeDir = cmSystemTools::ToNormalizedPathOnDisk(args[4]);
+      startDir = cmSystemTools::ToNormalizedPathOnDisk(args[5]);
+      homeOutDir = cmSystemTools::ToNormalizedPathOnDisk(args[6]);
+      startOutDir = cmSystemTools::ToNormalizedPathOnDisk(args[7]);
       cm.SetHomeDirectory(homeDir);
       cm.SetHomeOutputDirectory(homeOutDir);
       cm.GetCurrentSnapshot().SetDefaultDefinitions();
@@ -2413,7 +2413,7 @@
 
   // Create a resource file referencing the manifest.
   std::string absManifestFile =
-    cmSystemTools::CollapseFullPath(this->ManifestFile);
+    cmSystemTools::ToNormalizedPathOnDisk(this->ManifestFile);
   if (this->Verbose) {
     std::cout << "Create " << this->ManifestFileRC << '\n';
   }
diff --git a/Source/ctest.cxx b/Source/ctest.cxx
index 68c36df..99d6cf0 100644
--- a/Source/ctest.cxx
+++ b/Source/ctest.cxx
@@ -147,8 +147,6 @@
   { "--overwrite", "Overwrite CTest configuration option." },
   { "--extra-submit <file>[;<file>]", "Submit extra files to the dashboard." },
   { "--http-header <header>", "Append HTTP header when submitting" },
-  { "--force-new-ctest-process",
-    "Run child CTest instances as new processes" },
   { "--schedule-random", "Use a random order for scheduling tests" },
   { "--submit-index",
     "Submit individual dashboard tests with specific index" },
@@ -228,9 +226,5 @@
     args.emplace_back(argv[i]);
   }
   // run ctest
-  std::string output;
-  int res = inst.Run(args, &output);
-  cmCTestLog(&inst, OUTPUT, output);
-
-  return res;
+  return inst.Run(args);
 }
diff --git a/Source/kwsys/SystemTools.cxx b/Source/kwsys/SystemTools.cxx
index 6cc103d..cb6d73b 100644
--- a/Source/kwsys/SystemTools.cxx
+++ b/Source/kwsys/SystemTools.cxx
@@ -3647,7 +3647,6 @@
   SystemTools::CheckTranslationPath(newPath);
 #endif
 #ifdef _WIN32
-  newPath = SystemToolsStatics->GetActualCaseForPathCached(newPath);
   SystemTools::ConvertToUnixSlashes(newPath);
 #endif
   // Return the reconstructed path.
@@ -3762,6 +3761,15 @@
 #endif
 }
 
+std::string SystemTools::GetActualCaseForPathCached(const std::string& p)
+{
+#ifdef _WIN32
+  return SystemToolsStatic::GetActualCaseForPathCached(p);
+#else
+  return p;
+#endif
+}
+
 const char* SystemTools::SplitPathRootComponent(const std::string& p,
                                                 std::string* root)
 {
diff --git a/Source/kwsys/SystemTools.hxx.in b/Source/kwsys/SystemTools.hxx.in
index 294ffca..43663c6 100644
--- a/Source/kwsys/SystemTools.hxx.in
+++ b/Source/kwsys/SystemTools.hxx.in
@@ -372,6 +372,7 @@
    * This does nothing on non-Windows systems but return the path.
    */
   static std::string GetActualCaseForPath(const std::string& path);
+  static std::string GetActualCaseForPathCached(const std::string& path);
 
   /**
    * Given the path to a program executable, get the directory part of
diff --git a/Tests/CMakeLib/CMakeLists.txt b/Tests/CMakeLib/CMakeLists.txt
index 5b189e7..01ad7b4 100644
--- a/Tests/CMakeLib/CMakeLists.txt
+++ b/Tests/CMakeLib/CMakeLists.txt
@@ -16,6 +16,7 @@
   testCTestResourceSpec.cxx
   testCTestResourceGroups.cxx
   testDebug.cxx
+  testDocumentationFormatter.cxx
   testGccDepfileReader.cxx
   testGeneratedFileStream.cxx
   testJSONHelpers.cxx
diff --git a/Tests/CMakeLib/testDocumentationFormatter.cxx b/Tests/CMakeLib/testDocumentationFormatter.cxx
new file mode 100644
index 0000000..6f59b1d
--- /dev/null
+++ b/Tests/CMakeLib/testDocumentationFormatter.cxx
@@ -0,0 +1,138 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include <sstream>
+#include <string>
+#include <utility>
+
+#include <cmDocumentationFormatter.h>
+
+#include "testCommon.h"
+
+namespace {
+using TestCases =
+  const std::initializer_list<std::pair<std::string, std::string>>;
+
+bool testPrintFormattedNoIndent()
+{
+  const TestCases testCases = {
+    { "", "" },
+    { "One line no EOL text", "One line no EOL text\n" },
+    { "Short text. Two sentences.", "Short text.  Two sentences.\n" },
+    { "Short text\non\nmultiple\nlines\n",
+      "Short text\n\non\n\nmultiple\n\nlines\n\n" },
+    { "Just one a very long word: "
+      "01234567890123456789012345678901234567890123456789012345"
+      "678901234567890123456789",
+      "Just one a very long "
+      "word:\n01234567890123456789012345678901234567890123456789012345"
+      "678901234567890123456789\n" },
+    { " Pre-formatted paragraph with the very long word stays the same: "
+      "0123456789012345678901234567890123456789012345678901234567890123456789",
+      " Pre-formatted paragraph with the very long word stays the same: "
+      "0123456789012345678901234567890123456789012345678901234567890123456789"
+      "\n" },
+    { "Extra  spaces  are     removed.   However, \n   not in    a "
+      "pre-formatted\n  "
+      "paragraph",
+      "Extra spaces are removed.  However,\n\n   not in    a pre-formatted\n  "
+      "paragraph\n" },
+    { "This is the text paragraph longer than a pre-defined wrapping position "
+      "of the `cmDocumentationFormatter` class. And it's gonna be wrapped "
+      "over multiple lines!",
+      "This is the text paragraph longer than a pre-defined wrapping position "
+      "of the\n`cmDocumentationFormatter` class.  And it's gonna be wrapped "
+      "over multiple\nlines!\n" }
+  };
+
+  cmDocumentationFormatter formatter;
+
+  for (auto& test : testCases) {
+    std::ostringstream out;
+    formatter.PrintFormatted(out, test.first);
+    ASSERT_EQUAL(out.str(), test.second);
+  }
+
+  return true;
+}
+
+bool testPrintFormattedIndent2()
+{
+  const TestCases testCases = {
+    { "", "" },
+    { "One line no EOL text", "  One line no EOL text\n" },
+    { "Short text. Two sentences.", "  Short text.  Two sentences.\n" },
+    { "Short text\non\nmultiple\nlines\n",
+      "  Short text\n\n  on\n\n  multiple\n\n  lines\n\n" },
+    { "Just one a very long word: "
+      "01234567890123456789012345678901234567890123456789012345"
+      "678901234567890123456789",
+      "  Just one a very long "
+      "word:\n  01234567890123456789012345678901234567890123456789012345"
+      "678901234567890123456789\n" },
+    { " Pre-formatted paragraph with the very long word stays the same: "
+      "0123456789012345678901234567890123456789012345678901234567890123456789",
+      "   Pre-formatted paragraph with the very long word stays the same: "
+      "0123456789012345678901234567890123456789012345678901234567890123456789"
+      "\n" },
+    { "Extra  spaces  are     removed.   However, \n  not in    a "
+      "pre-formatted\n  "
+      "paragraph",
+      "  Extra spaces are removed.  However,\n\n    not in    a "
+      "pre-formatted\n    "
+      "paragraph\n" },
+    { "This is the text paragraph longer than a pre-defined wrapping position "
+      "of the `cmDocumentationFormatter` class. And it's gonna be wrapped "
+      "over multiple lines!",
+      "  This is the text paragraph longer than a pre-defined wrapping "
+      "position of\n"
+      "  the `cmDocumentationFormatter` class.  And it's gonna be wrapped "
+      "over\n  multiple lines!\n" }
+  };
+
+  cmDocumentationFormatter formatter;
+  formatter.SetIndent(2);
+
+  for (auto& test : testCases) {
+    std::ostringstream out;
+    formatter.PrintFormatted(out, test.first);
+    ASSERT_EQUAL(out.str(), test.second);
+  }
+
+  return true;
+}
+
+bool testPrintFormattedIndent10()
+{
+  const TestCases testCases = {
+    { "", "" },
+    { "One line no EOL text", "          One line no EOL text\n" },
+    { "This is the text paragraph longer than a pre-defined wrapping position "
+      "of the `cmDocumentationFormatter` class. And it's gonna be wrapped "
+      "over multiple lines!",
+      "          This is the text paragraph longer than a pre-defined "
+      "wrapping\n"
+      "          position of the `cmDocumentationFormatter` class.  "
+      "And it's gonna\n"
+      "          be wrapped over multiple lines!\n" }
+  };
+
+  cmDocumentationFormatter formatter;
+  formatter.SetIndent(10);
+
+  for (auto& test : testCases) {
+    std::ostringstream out;
+    formatter.PrintFormatted(out, test.first);
+    ASSERT_EQUAL(out.str(), test.second);
+  }
+
+  return true;
+}
+}
+
+int testDocumentationFormatter(int /*unused*/, char* /*unused*/[])
+{
+  return runTests({ testPrintFormattedNoIndent, testPrintFormattedIndent2,
+                    testPrintFormattedIndent10 },
+                  false);
+}
diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt
index 428ec8b..7f642b3 100644
--- a/Tests/CMakeLists.txt
+++ b/Tests/CMakeLists.txt
@@ -663,7 +663,6 @@
     ${build_generator_args}
     --build-project ExternalDataTest
     --build-noclean
-    --force-new-ctest-process
     --build-options
       -DMAKE_SUPPORTS_SPACES=${MAKE_SUPPORTS_SPACES}
     --test-command ${CMAKE_CTEST_COMMAND} -C \${CTEST_CONFIGURATION_TYPE} -V
@@ -1383,7 +1382,6 @@
     ${build_generator_args}
     --build-project EnvironmentProj
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/Environment"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
   list(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/Environment")
@@ -1422,7 +1420,6 @@
       ${build_generator_args}
       --build-project Qt4Targets
       --build-exe-dir "${CMake_BINARY_DIR}/Tests/Qt4Targets"
-      --force-new-ctest-process
       --build-options
         -DQT_QMAKE_EXECUTABLE:FILEPATH=${QT_QMAKE_EXECUTABLE}
       --test-command ${CMAKE_CTEST_COMMAND} -V
@@ -1437,7 +1434,6 @@
         ${build_generator_args}
         --build-project Qt4And5Automoc
         --build-exe-dir "${CMake_BINARY_DIR}/Tests/Qt4And5AutomocForward"
-        --force-new-ctest-process
         --test-command ${CMAKE_CTEST_COMMAND} -V
         )
       list(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/Qt4And5AutomocForward")
@@ -1448,7 +1444,6 @@
         ${build_generator_args}
         --build-project Qt4And5Automoc
         --build-exe-dir "${CMake_BINARY_DIR}/Tests/Qt4And5AutomocReverse"
-        --force-new-ctest-process
         --build-options -DQT_REVERSE_FIND_ORDER=1
         --test-command ${CMAKE_CTEST_COMMAND} -V
         )
@@ -1645,7 +1640,6 @@
     ${build_generator_args}
     --build-project ExternalProjectTest
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/ExternalProject"
-    --force-new-ctest-process
     --build-options ${ExternalProject_BUILD_OPTIONS}
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
@@ -1662,7 +1656,6 @@
     "${CMake_BINARY_DIR}/Tests/ExternalProjectSubdir"
     ${build_generator_args}
     --build-project ExternalProjectSubdir
-    --force-new-ctest-process
     )
   list(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/ExternalProjectSubdir")
 
@@ -1673,7 +1666,6 @@
     "${CMake_BINARY_DIR}/Tests/ExternalProjectSourceSubdir"
     ${build_generator_args}
     --build-project ExternalProjectSourceSubdir
-    --force-new-ctest-process
     )
   list(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/ExternalProjectSourceSubdir")
 
@@ -1684,7 +1676,6 @@
     "${CMake_BINARY_DIR}/Tests/ExternalProjectSourceSubdirNotCMake"
     ${build_generator_args}
     --build-project ExternalProjectSourceSubdirNotCMake
-    --force-new-ctest-process
     )
   list(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/ExternalProjectSourceSubdirNotCMake")
 
@@ -1695,7 +1686,6 @@
     ${build_generator_args}
     --build-project ExternalProjectLocalTest
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/ExternalProjectLocal"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
   list(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/ExternalProjectLocal")
@@ -1710,7 +1700,6 @@
     ${build_generator_args}
     --build-project ExternalProjectUpdateTest
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/ExternalProjectUpdate"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
   list(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/ExternalProjectUpdate")
@@ -1766,7 +1755,6 @@
         ${build_generator_args}
         --build-project superpro
         --build-exe-dir "${CMake_BINARY_DIR}/Tests/InstallMode-${_mode}"
-        --force-new-ctest-process
         --build-options
           ${_maybe_BUILD_OPTIONS}
           "-DCMAKE_INSTALL_PREFIX:PATH=${CMake_BINARY_DIR}/Tests/InstallMode-${_mode}/install"
@@ -2363,7 +2351,6 @@
         --build-generator "Green Hills MULTI"
         --build-project test
         --build-config $<CONFIGURATION>
-        --force-new-ctest-process
         --build-options ${ghs_target_arch} ${ghs_toolset_name} ${ghs_toolset_root} ${ghs_target_platform}
           ${ghs_os_root} ${ghs_os_dir} ${ghs_bsp_name} ${_ghs_build_opts} ${_ghs_toolset_extra}
           ${_ghs_test_command}
@@ -2577,7 +2564,6 @@
     ${build_generator_args}
     --build-project TestsWorkingDirectoryProj
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/TestsWorkingDirectory"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V -C \${CTEST_CONFIGURATION_TYPE}
     )
   list(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/TestsWorkingDirectory")
@@ -2589,9 +2575,9 @@
   # A simple test for ctest in script mode
   configure_file("${CMake_SOURCE_DIR}/Tests/CTestScriptMode/CTestTestScriptMode.cmake.in"
           "${CMake_BINARY_DIR}/Tests/CTestScriptMode/CTestTestScriptMode.cmake" @ONLY)
-#  add_test(CTest.ScriptMode ${CMAKE_CTEST_COMMAND}
-#        -S "${CMake_BINARY_DIR}/Tests/CTestScriptMode/CTestTestScriptMode.cmake"
-#        )
+  add_test(CTest.ScriptMode ${CMAKE_CTEST_COMMAND}
+    -S "${CMake_BINARY_DIR}/Tests/CTestScriptMode/CTestTestScriptMode.cmake"
+    )
 
   # Test CTest Update with Subversion
   if(NOT DEFINED CMake_TEST_CTestUpdate_SVN OR CMake_TEST_CTestUpdate_SVN)
@@ -3032,7 +3018,6 @@
   )
 
   if(NOT BORLAND)
-    set(CTestLimitDashJ_CTEST_OPTIONS --force-new-ctest-process)
     add_test_macro(CTestLimitDashJ ${CMAKE_CTEST_COMMAND} -j 4
       --output-on-failure -C "\${CTestTest_CONFIG}")
   endif()
diff --git a/Tests/CTestConfig/ScriptWithArgs.cmake b/Tests/CTestConfig/ScriptWithArgs.cmake
index 79896a7..4d63e22 100644
--- a/Tests/CTestConfig/ScriptWithArgs.cmake
+++ b/Tests/CTestConfig/ScriptWithArgs.cmake
@@ -1,5 +1,3 @@
-set(CTEST_RUN_CURRENT_SCRIPT 0)
-
 macro(check_arg name expected_value)
   message("${name}='${${name}}'")
   if(NOT "${${name}}" STREQUAL "${expected_value}")
diff --git a/Tests/CTestLimitDashJ/CreateSleepDelete.cmake b/Tests/CTestLimitDashJ/CreateSleepDelete.cmake
index b09307f..f16deaa 100644
--- a/Tests/CTestLimitDashJ/CreateSleepDelete.cmake
+++ b/Tests/CTestLimitDashJ/CreateSleepDelete.cmake
@@ -1,5 +1,3 @@
-set(CTEST_RUN_CURRENT_SCRIPT 0)
-
 if(NOT DEFINED basefilename)
   message(FATAL_ERROR "pass -Dbasefilename=f1")
 endif()
diff --git a/Tests/CTestScriptMode/CTestTestScriptMode.cmake.in b/Tests/CTestScriptMode/CTestTestScriptMode.cmake.in
index 45f0e37..5ff1bf2 100644
--- a/Tests/CTestScriptMode/CTestTestScriptMode.cmake.in
+++ b/Tests/CTestScriptMode/CTestTestScriptMode.cmake.in
@@ -6,9 +6,3 @@
 if (NOT "${CMAKE_SYSTEM_NAME}" STREQUAL "${CMAKE_CMAKE_SYSTEM_NAME}")
    message(FATAL_ERROR "Error: CMAKE_SYSTEM_NAME is \"${CMAKE_SYSTEM_NAME}\", but should be \"@CMAKE_SYSTEM_NAME@\"")
 endif()
-
-# this seems to be necessary, otherwise ctest complains that these
-# variables are not set:
-set(CTEST_COMMAND "\"@CMAKE_CTEST_COMMAND@\"")
-set(CTEST_SOURCE_DIRECTORY "@CMake_SOURCE_DIR@/Tests/CTestScriptMode/")
-set(CTEST_BINARY_DIRECTORY "@CMake_BINARY_DIR@/Tests/CTestScriptMode/")
diff --git a/Tests/CTestTestEmptyBinaryDirectory/test.cmake.in b/Tests/CTestTestEmptyBinaryDirectory/test.cmake.in
index 3f8437c..e71d514 100644
--- a/Tests/CTestTestEmptyBinaryDirectory/test.cmake.in
+++ b/Tests/CTestTestEmptyBinaryDirectory/test.cmake.in
@@ -1,7 +1,5 @@
 cmake_minimum_required(VERSION 3.10)
 
-set(CTEST_RUN_CURRENT_SCRIPT 0)
-
 set(CTEST_SOURCE_DIRECTORY "@CMake_SOURCE_DIR@/Tests/CTestTestEmptyBinaryDirectory")
 set(CTEST_BINARY_DIRECTORY "@CMake_BINARY_DIR@/Tests/CTestTestEmptyBinaryDirectory")
 
diff --git a/Tests/CTestTestRunScript/hello.cmake.in b/Tests/CTestTestRunScript/hello.cmake.in
index 37905e3..4fa6446 100644
--- a/Tests/CTestTestRunScript/hello.cmake.in
+++ b/Tests/CTestTestRunScript/hello.cmake.in
@@ -1,2 +1 @@
-set(CTEST_RUN_CURRENT_SCRIPT 0)
 message("hello world")
diff --git a/Tests/CTestTestRunScript/test.cmake.in b/Tests/CTestTestRunScript/test.cmake.in
index 3074a51..9e50f7f 100644
--- a/Tests/CTestTestRunScript/test.cmake.in
+++ b/Tests/CTestTestRunScript/test.cmake.in
@@ -1,2 +1 @@
-set(CTEST_RUN_CURRENT_SCRIPT 0)
 CTEST_RUN_SCRIPT("CTestTestRunScript/hello.cmake" RETURN_VALUE res RETURN_VALUE)
diff --git a/Tests/CTestTestSerialInDepends/test.ctest b/Tests/CTestTestSerialInDepends/test.ctest
index cf0d314..71c6da2 100644
--- a/Tests/CTestTestSerialInDepends/test.ctest
+++ b/Tests/CTestTestSerialInDepends/test.ctest
@@ -1,5 +1,3 @@
-set(CTEST_RUN_CURRENT_SCRIPT 0)
-
 set(LOCK_FILE "${TEST_NAME}.lock")
 
 # Delete the old lock file in case it's lingering from a previous failed test run
diff --git a/Tests/FindGTK2/CMakeLists.txt b/Tests/FindGTK2/CMakeLists.txt
index 0105fae..15a7c29 100644
--- a/Tests/FindGTK2/CMakeLists.txt
+++ b/Tests/FindGTK2/CMakeLists.txt
@@ -11,7 +11,6 @@
     --build-target gtk-all-libs
     --build-project gtk
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/FindGTK2/GTK2Components/gtk"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
 endif()
@@ -25,7 +24,6 @@
     --build-target gtkmm-all-libs
     --build-project gtkmm
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/FindGTK2/GTK2Components/gtkmm"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
 endif()
@@ -40,7 +38,6 @@
     ${build_generator_args}
     --build-project glib
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/FindGTK2/GTK2Targets/glib"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
 endif()
@@ -53,7 +50,6 @@
     ${build_generator_args}
     --build-project gobject
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/FindGTK2/GTK2Targets/gobject"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
 endif()
@@ -66,7 +62,6 @@
     ${build_generator_args}
     --build-project gio
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/FindGTK2/GTK2Targets/gio"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
 endif()
@@ -79,7 +74,6 @@
     ${build_generator_args}
     --build-project gmodule
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/FindGTK2/GTK2Targets/gmodule"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
 endif()
@@ -92,7 +86,6 @@
     ${build_generator_args}
     --build-project gthread
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/FindGTK2/GTK2Targets/gthread"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
 endif()
@@ -105,7 +98,6 @@
     ${build_generator_args}
     --build-project atk
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/FindGTK2/GTK2Targets/atk"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
 endif()
@@ -118,7 +110,6 @@
     ${build_generator_args}
     --build-project gdk_pixbuf
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/FindGTK2/GTK2Targets/gdk_pixbuf"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
 endif()
@@ -131,7 +122,6 @@
     ${build_generator_args}
     --build-project cairo
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/FindGTK2/GTK2Targets/cairo"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
 endif()
@@ -144,7 +134,6 @@
     ${build_generator_args}
     --build-project pango
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/FindGTK2/GTK2Targets/pango"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
 endif()
@@ -157,7 +146,6 @@
     ${build_generator_args}
     --build-project pangocairo
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/FindGTK2/GTK2Targets/pangocairo"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
 endif()
@@ -170,7 +158,6 @@
     ${build_generator_args}
     --build-project pangoxft
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/FindGTK2/GTK2Targets/pangoxft"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
 endif()
@@ -183,7 +170,6 @@
     ${build_generator_args}
     --build-project pangoft2
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/FindGTK2/GTK2Targets/pangoft2"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
 endif()
@@ -196,7 +182,6 @@
     ${build_generator_args}
     --build-project gdk
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/FindGTK2/GTK2Targets/gdk"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
 endif()
@@ -209,7 +194,6 @@
     ${build_generator_args}
     --build-project gtk
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/FindGTK2/GTK2Targets/gtk"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
 endif()
@@ -222,7 +206,6 @@
      ${build_generator_args}
     --build-project sigc++
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/FindGTK2/GTK2Targets/sigc++"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
 endif()
@@ -235,7 +218,6 @@
      ${build_generator_args}
     --build-project glibmm
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/FindGTK2/GTK2Targets/glibmm"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
 endif()
@@ -248,7 +230,6 @@
      ${build_generator_args}
     --build-project giomm
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/FindGTK2/GTK2Targets/giomm"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
 endif()
@@ -261,7 +242,6 @@
      ${build_generator_args}
     --build-project atkmm
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/FindGTK2/GTK2Targets/atkmm"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
 endif()
@@ -274,7 +254,6 @@
      ${build_generator_args}
     --build-project cairomm
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/FindGTK2/GTK2Targets/cairomm"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
 endif()
@@ -287,7 +266,6 @@
      ${build_generator_args}
     --build-project pangomm
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/FindGTK2/GTK2Targets/pangomm"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
 endif()
@@ -300,7 +278,6 @@
      ${build_generator_args}
     --build-project gdkmm
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/FindGTK2/GTK2Targets/GTK2Targets/gdkmm"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
 endif()
@@ -314,7 +291,6 @@
     --build-target gtkmm-target
     --build-project gtkmm
     --build-exe-dir "${CMake_BINARY_DIR}/Tests/FindGTK2/GTK2Targets/gtkmm"
-    --force-new-ctest-process
     --test-command ${CMAKE_CTEST_COMMAND} -V
     )
 endif()
diff --git a/Tests/FindPackageTest/CMakeLists.txt b/Tests/FindPackageTest/CMakeLists.txt
index 73d3fb4..e92c6bb 100644
--- a/Tests/FindPackageTest/CMakeLists.txt
+++ b/Tests/FindPackageTest/CMakeLists.txt
@@ -204,7 +204,7 @@
   # The result must preserve the /symlink/ path.
   set(SetFoundResolved_EXPECTED "${CMAKE_CURRENT_SOURCE_DIR}/symlink/cmake")
   if(NOT "${SetFoundResolved_DIR}" STREQUAL "${SetFoundResolved_EXPECTED}")
-    message(SEND_ERROR "SetFoundResolved_DIR set by find_package() is set to \"${SetFoundResolved_DIR}\" (expected \"${SetFoundResolved_EXPECTED}\")")
+    message(SEND_ERROR "SetFoundResolved_DIR set by find_package() is set to\n  \"${SetFoundResolved_DIR}\"\nnot the expected\n  \"${SetFoundResolved_EXPECTED}\"")
   endif()
 
   # This part of the test only works if there are no symlinks in our path.
@@ -217,7 +217,7 @@
     # ./symlink points back here so it should be gone when resolved.
     set(SetFoundResolved_EXPECTED "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
     if(NOT "${SetFoundResolved_DIR}" STREQUAL "${SetFoundResolved_EXPECTED}")
-      message(SEND_ERROR "SetFoundResolved_DIR set by find_package() is set to \"${SetFoundResolved_DIR}\" (expected \"${SetFoundResolved_EXPECTED}\")")
+      message(SEND_ERROR "SetFoundResolved_DIR set by find_package() is set to\n  \"${SetFoundResolved_DIR}\"\nnot the expected\n  \"${SetFoundResolved_EXPECTED}\"")
     endif()
   endif()
 
@@ -492,15 +492,15 @@
 include("${CMAKE_CURRENT_BINARY_DIR}/RelocatableConfig.cmake")
 
 if(NOT "${RELOC_INCLUDE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}/include")
-  message(SEND_ERROR "RELOC_INCLUDE_DIR set by configure_package_config_file() is set to \"${RELOC_INCLUDE_DIR}\" (expected \"${CMAKE_CURRENT_BINARY_DIR}/include\")")
+  message(SEND_ERROR "RELOC_INCLUDE_DIR set by configure_package_config_file() is set to\n  \"${RELOC_INCLUDE_DIR}\"\nnot the expected\n  \"${CMAKE_CURRENT_BINARY_DIR}/include\"")
 endif()
 
 if(NOT "${RELOC_SHARE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}/share/")
-  message(SEND_ERROR "RELOC_SHARE_DIR set by configure_package_config_file() is set to \"${RELOC_SHARE_DIR}\" (expected \"${CMAKE_CURRENT_BINARY_DIR}/share/\")")
+  message(SEND_ERROR "RELOC_SHARE_DIR set by configure_package_config_file() is set to\n  \"${RELOC_SHARE_DIR}\"\nnot the expected\n  \"${CMAKE_CURRENT_BINARY_DIR}/share/\"")
 endif()
 
 if(NOT "${RELOC_BUILD_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}")
-  message(SEND_ERROR "RELOC_BUILD_DIR set by configure_package_config_file() is set to \"${RELOC_BUILD_DIR}\" (expected \"${CMAKE_CURRENT_BINARY_DIR}\")")
+  message(SEND_ERROR "RELOC_BUILD_DIR set by configure_package_config_file() is set to\n  \"${RELOC_BUILD_DIR}\"\nnot the expected\n  \"${CMAKE_CURRENT_BINARY_DIR}\"")
 endif()
 
 if(NOT DEFINED Relocatable_FOUND)
@@ -527,15 +527,15 @@
 include("${CMAKE_CURRENT_BINARY_DIR}/RelocatableConfig.cmake")
 
 if(NOT "${RELOC_INCLUDE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}/include")
-  message(SEND_ERROR "RELOC_INCLUDE_DIR set by configure_package_config_file() is set to \"${RELOC_INCLUDE_DIR}\" (expected \"${CMAKE_CURRENT_BINARY_DIR}/include\")")
+  message(SEND_ERROR "RELOC_INCLUDE_DIR set by configure_package_config_file() is set to\n  \"${RELOC_INCLUDE_DIR}\"\nnot the expected\n  \"${CMAKE_CURRENT_BINARY_DIR}/include\"")
 endif()
 
 if(NOT "${RELOC_SHARE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}/share/")
-  message(SEND_ERROR "RELOC_SHARE_DIR set by configure_package_config_file() is set to \"${RELOC_SHARE_DIR}\" (expected \"${CMAKE_CURRENT_BINARY_DIR}/share/\")")
+  message(SEND_ERROR "RELOC_SHARE_DIR set by configure_package_config_file() is set to\n  \"${RELOC_SHARE_DIR}\"\nnot the expected\n  \"${CMAKE_CURRENT_BINARY_DIR}/share/\"")
 endif()
 
 if(NOT "${RELOC_BUILD_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}")
-  message(SEND_ERROR "RELOC_BUILD_DIR set by configure_package_config_file() is set to \"${RELOC_BUILD_DIR}\" (expected \"${CMAKE_CURRENT_BINARY_DIR}\")")
+  message(SEND_ERROR "RELOC_BUILD_DIR set by configure_package_config_file() is set to\n  \"${RELOC_BUILD_DIR}\"\nnot the expected\n  \"${CMAKE_CURRENT_BINARY_DIR}\"")
 endif()
 
 if(NOT DEFINED Relocatable_FOUND)
diff --git a/Tests/FindProtobuf/CMakeLists.txt b/Tests/FindProtobuf/CMakeLists.txt
index b4ca29b..8e46ff8 100644
--- a/Tests/FindProtobuf/CMakeLists.txt
+++ b/Tests/FindProtobuf/CMakeLists.txt
@@ -9,3 +9,4 @@
   "-DCMake_TEST_FindProtobuf_gRPC=${CMake_TEST_FindProtobuf_gRPC}"
   --test-command ${CMAKE_CTEST_COMMAND} -V -C $<CONFIGURATION>
   )
+set_property(TEST FindProtobuf.Test PROPERTY FAIL_REGULAR_EXPRESSION PROTOC_EXE)
diff --git a/Tests/FindProtobuf/Test/CMakeLists.txt b/Tests/FindProtobuf/Test/CMakeLists.txt
index 2859c48..1409bd9 100644
--- a/Tests/FindProtobuf/Test/CMakeLists.txt
+++ b/Tests/FindProtobuf/Test/CMakeLists.txt
@@ -70,7 +70,7 @@
   # NOTE: with IMPORT_DIRS msgs/, generated files will be placed under ${CMAKE_CURRENT_BINARY_DIR}/grpc/
   target_include_directories(msgs_grpc_IMPORT_DIRS PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/grpc/)
   target_link_libraries(msgs_grpc_IMPORT_DIRS PUBLIC ${Protobuf_LIBRARIES})
-  protobuf_generate(TARGET msgs_grpc_IMPORT_DIRS LANGUAGE cpp IMPORT_DIRS msgs/)
+  protobuf_generate(TARGET msgs_grpc_IMPORT_DIRS LANGUAGE cpp IMPORT_DIRS msgs/ PROTOC_EXE ${Protobuf_PROTOC_EXECUTABLE})
   protobuf_generate(TARGET msgs_grpc_IMPORT_DIRS LANGUAGE grpc IMPORT_DIRS msgs/ GENERATE_EXTENSIONS .grpc.pb.h .grpc.pb.cc PLUGIN "protoc-gen-grpc=${gRPC_CPP_PLUGIN}")
   add_executable(test_generate_grpc_IMPORT_DIRS main-generate-grpc.cxx)
   target_link_libraries(test_generate_grpc_IMPORT_DIRS PRIVATE msgs_grpc_IMPORT_DIRS)
diff --git a/Tests/QtAutogen/TestMacros.cmake b/Tests/QtAutogen/TestMacros.cmake
index 529592e..a2e0fca 100644
--- a/Tests/QtAutogen/TestMacros.cmake
+++ b/Tests/QtAutogen/TestMacros.cmake
@@ -48,7 +48,6 @@
     --build-project ${NAME}
     ${Autogen_CTEST_OPTIONS}
     --build-exe-dir "${_BuildDir}"
-    --force-new-ctest-process
     --build-options ${build_options} ${Autogen_BUILD_OPTIONS}
     ${_TestCommand}
   )
diff --git a/Tests/RunCMake/CMP0132/CMP0132-OLD-stderr.txt b/Tests/RunCMake/CMP0132/CMP0132-OLD-stderr.txt
new file mode 100644
index 0000000..025665d
--- /dev/null
+++ b/Tests/RunCMake/CMP0132/CMP0132-OLD-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at CMP0132-OLD\.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0132 will be removed from a future version
+  of CMake\.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances\.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/CMP0135/CMP0135-OLD-stderr.txt b/Tests/RunCMake/CMP0135/CMP0135-OLD-stderr.txt
new file mode 100644
index 0000000..59a7a58
--- /dev/null
+++ b/Tests/RunCMake/CMP0135/CMP0135-OLD-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at CMP0135-OLD\.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0135 will be removed from a future version
+  of CMake\.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances\.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/CMP0139/CMP0139-OLD-stderr.txt b/Tests/RunCMake/CMP0139/CMP0139-OLD-stderr.txt
index 1cfb319..b04cd1a 100644
--- a/Tests/RunCMake/CMP0139/CMP0139-OLD-stderr.txt
+++ b/Tests/RunCMake/CMP0139/CMP0139-OLD-stderr.txt
@@ -1,3 +1,14 @@
+^CMake Deprecation Warning at CMP0139-OLD\.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0139 will be removed from a future version
+  of CMake\.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances\.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)
++
 CMake Error at CMP0139-OLD.cmake:[0-9]+ \(if\):
   if given arguments:
 
@@ -5,4 +16,4 @@
 
   Unknown arguments specified
 Call Stack \(most recent call first\):
-  CMakeLists.txt:[0-9]+ \(include\)
+  CMakeLists.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index 0a96aef..85da0cd 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/Tests/RunCMake/CMakeLists.txt
@@ -875,6 +875,7 @@
                                                    -DCMAKE_IMPORT_LIBRARY_PREFIX=${CMAKE_IMPORT_LIBRARY_PREFIX}
                                                    -DCMAKE_IMPORT_LIBRARY_SUFFIX=${CMAKE_IMPORT_LIBRARY_SUFFIX}
                                                    -DCMAKE_LINK_LIBRARY_FLAG=${CMAKE_LINK_LIBRARY_FLAG})
+add_RunCMake_test(target_link_libraries-LINKER-prefix -DCMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID})
 add_RunCMake_test(add_link_options -DCMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID})
 add_RunCMake_test(target_link_options -DCMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID}
                                       -DCMake_TEST_CUDA=${CMake_TEST_CUDA})
diff --git a/Tests/RunCMake/CMakeRoleGlobalProperty/test.cmake.in b/Tests/RunCMake/CMakeRoleGlobalProperty/test.cmake.in
index 4e2c085..d8f2e7b 100644
--- a/Tests/RunCMake/CMakeRoleGlobalProperty/test.cmake.in
+++ b/Tests/RunCMake/CMakeRoleGlobalProperty/test.cmake.in
@@ -1,5 +1,4 @@
 cmake_minimum_required(VERSION 3.12)
-set(CTEST_RUN_CURRENT_SCRIPT 0)
 
 get_property(role GLOBAL PROPERTY CMAKE_ROLE)
 if(NOT role STREQUAL "CTEST")
diff --git a/Tests/RunCMake/CTestCommandLine/EmptyDirCoverage-ctest-result.txt b/Tests/RunCMake/CTestCommandLine/EmptyDirCoverage-ctest-result.txt
new file mode 100644
index 0000000..f5c8955
--- /dev/null
+++ b/Tests/RunCMake/CTestCommandLine/EmptyDirCoverage-ctest-result.txt
@@ -0,0 +1 @@
+32
diff --git a/Tests/RunCMake/CTestCommandLine/EmptyDirCoverage-ctest-stderr.txt b/Tests/RunCMake/CTestCommandLine/EmptyDirCoverage-ctest-stderr.txt
index e6f9325..f6d28a1 100644
--- a/Tests/RunCMake/CTestCommandLine/EmptyDirCoverage-ctest-stderr.txt
+++ b/Tests/RunCMake/CTestCommandLine/EmptyDirCoverage-ctest-stderr.txt
@@ -1,3 +1,3 @@
 Cannot find file: [^
 ]*/Tests/RunCMake/CTestCommandLine/EmptyDirCoverage-ctest-build/DartConfiguration.tcl
-Binary directory is not set.  No coverage checking will be performed.$
+CTEST_BINARY_DIRECTORY not set
diff --git a/Tests/RunCMake/CTestCommandLine/RunCMakeTest.cmake b/Tests/RunCMake/CTestCommandLine/RunCMakeTest.cmake
index b2374ca..e5e385e 100644
--- a/Tests/RunCMake/CTestCommandLine/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CTestCommandLine/RunCMakeTest.cmake
@@ -355,6 +355,9 @@
   set(RunCMake_TEST_NO_CLEAN 1)
   file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
   file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
+  file(WRITE "${RunCMake_TEST_BINARY_DIR}/DartConfiguration.tcl" "
+BuildDirectory: ${RunCMake_TEST_BINARY_DIR}
+")
   file(WRITE "${RunCMake_TEST_BINARY_DIR}/CTestTestfile.cmake" "
   add_test(PassingTest \"${CMAKE_COMMAND}\" -E echo PassingTestOutput)
   add_test(FailingTest \"${CMAKE_COMMAND}\" -E no_such_command)
@@ -375,6 +378,9 @@
   set(TRUNCATED_OUTPUT ${expected})  # used in TestOutputTruncation-check.cmake
   file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
   file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
+  file(WRITE "${RunCMake_TEST_BINARY_DIR}/DartConfiguration.tcl" "
+BuildDirectory: ${RunCMake_TEST_BINARY_DIR}
+")
   file(WRITE "${RunCMake_TEST_BINARY_DIR}/CTestTestfile.cmake" "
   add_test(Truncation_${mode} \"${CMAKE_COMMAND}\" -E echo 123456789)
 ")
diff --git a/Tests/RunCMake/CTestCommandLine/TestOutputSize-stderr.txt b/Tests/RunCMake/CTestCommandLine/TestOutputSize-stderr.txt
deleted file mode 100644
index 19310b8..0000000
--- a/Tests/RunCMake/CTestCommandLine/TestOutputSize-stderr.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-^Cannot find file: .*/Tests/RunCMake/CTestCommandLine/TestOutputSize/DartConfiguration.tcl
-Errors while running CTest
diff --git a/Tests/RunCMake/CTestCommandLine/TestOutputTruncation_head-stderr.txt b/Tests/RunCMake/CTestCommandLine/TestOutputTruncation_head-stderr.txt
deleted file mode 100644
index 30b46ce..0000000
--- a/Tests/RunCMake/CTestCommandLine/TestOutputTruncation_head-stderr.txt
+++ /dev/null
@@ -1 +0,0 @@
-^Cannot find file: .*/Tests/RunCMake/CTestCommandLine/TestOutputTruncation.*/DartConfiguration.tcl
diff --git a/Tests/RunCMake/CTestCommandLine/TestOutputTruncation_middle-stderr.txt b/Tests/RunCMake/CTestCommandLine/TestOutputTruncation_middle-stderr.txt
deleted file mode 100644
index 30b46ce..0000000
--- a/Tests/RunCMake/CTestCommandLine/TestOutputTruncation_middle-stderr.txt
+++ /dev/null
@@ -1 +0,0 @@
-^Cannot find file: .*/Tests/RunCMake/CTestCommandLine/TestOutputTruncation.*/DartConfiguration.tcl
diff --git a/Tests/RunCMake/CTestCommandLine/TestOutputTruncation_tail-stderr.txt b/Tests/RunCMake/CTestCommandLine/TestOutputTruncation_tail-stderr.txt
deleted file mode 100644
index 30b46ce..0000000
--- a/Tests/RunCMake/CTestCommandLine/TestOutputTruncation_tail-stderr.txt
+++ /dev/null
@@ -1 +0,0 @@
-^Cannot find file: .*/Tests/RunCMake/CTestCommandLine/TestOutputTruncation.*/DartConfiguration.tcl
diff --git a/Tests/RunCMake/CTestCommandLine/test-dir-non-existing-dir-stderr.txt b/Tests/RunCMake/CTestCommandLine/test-dir-non-existing-dir-stderr.txt
index 017ccb0..6b16868 100644
--- a/Tests/RunCMake/CTestCommandLine/test-dir-non-existing-dir-stderr.txt
+++ b/Tests/RunCMake/CTestCommandLine/test-dir-non-existing-dir-stderr.txt
@@ -1 +1 @@
-Failed to change working directory to ".*/non-existing-dir" : No such file or directory
+Failed to change working directory to ".*/non-existing-dir": No such file or directory
diff --git a/Tests/RunCMake/CTestCommandLine/test-dir-non-existing-dir-stdout.txt b/Tests/RunCMake/CTestCommandLine/test-dir-non-existing-dir-stdout.txt
deleted file mode 100644
index ddcd238..0000000
--- a/Tests/RunCMake/CTestCommandLine/test-dir-non-existing-dir-stdout.txt
+++ /dev/null
@@ -1 +0,0 @@
-Internal ctest changing into directory: .*/non-existing-dir
diff --git a/Tests/RunCMake/CXXModules/check-json.cmake b/Tests/RunCMake/CXXModules/check-json.cmake
index 8d95973..ec15f14 100644
--- a/Tests/RunCMake/CXXModules/check-json.cmake
+++ b/Tests/RunCMake/CXXModules/check-json.cmake
@@ -50,8 +50,8 @@
 
 function (check_json_value path actual_type expect_type actual_value expect_value)
   if (NOT actual_type STREQUAL expect_type)
-    list(APPEND RunCMake_TEST_FAILED
-      "Type mismatch at ${path}: ${actual_type} vs. ${expect_type}")
+    string(APPEND RunCMake_TEST_FAILED
+      "Type mismatch at:\n ${path}\nexpected:\n ${expect_type}\nactual:\n ${actual_type}\n")
     set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE)
     return ()
   endif ()
@@ -60,13 +60,13 @@
     # Nothing to check
   elseif (actual_type STREQUAL BOOLEAN)
     if (NOT actual_value STREQUAL expect_value)
-      list(APPEND RunCMake_TEST_FAILED
-        "Boolean mismatch at ${path}: ${actual_value} vs. ${expect_value}")
+      string(APPEND RunCMake_TEST_FAILED
+        "Boolean mismatch at:\n ${path}\nexpected:\n ${expect_value}\nactual:\n ${actual_value}\n")
     endif ()
   elseif (actual_type STREQUAL NUMBER)
     if (NOT actual_value EQUAL expect_value)
-      list(APPEND RunCMake_TEST_FAILED
-        "Number mismatch at ${path}: ${actual_value} vs. ${expect_value}")
+      string(APPEND RunCMake_TEST_FAILED
+        "Number mismatch at:\n ${path}\nexpected:\n ${expect_value}\nactual:\n ${actual_value}\n")
     endif ()
   elseif (actual_type STREQUAL STRING)
     # Allow some values to be ignored.
@@ -79,24 +79,24 @@
       string(REPLACE "\\" "/" actual_value_check "${actual_value}")
       string(REGEX REPLACE "^\"(.*)\"$" "\\1" actual_value_check "${actual_value_check}")
       if (NOT actual_value_check MATCHES "^${expect_value_expanded}$")
-        list(APPEND RunCMake_TEST_FAILED
-          "String mismatch (path regex) at ${path}: ${actual_value} vs. ^${expect_value_expanded}$")
+        string(APPEND RunCMake_TEST_FAILED
+          "String mismatch (path regex) at:\n ${path}\nexpected:\n ^${expect_value_expanded}$\nactual:\n ${actual_value}\n")
       endif ()
     elseif (expect_value MATCHES "^REGEX:")
       if (NOT actual_value MATCHES "^${expect_value_expanded}$")
-        list(APPEND RunCMake_TEST_FAILED
-          "String mismatch (regex) at ${path}: ${actual_value} vs. ^${expect_value_expanded}$")
+        string(APPEND RunCMake_TEST_FAILED
+          "String mismatch (regex) at:\n ${path}\nexpected:\n ^${expect_value_expanded}$\nactual:\n ${actual_value}\n")
       endif ()
     elseif (expect_value MATCHES "^PATH:")
       string(REPLACE "\\" "/" actual_value_check "${actual_value}")
       string(REGEX REPLACE "^\"(.*)\"$" "\\1" actual_value_check "${actual_value_check}")
       if (NOT actual_value_check STREQUAL "${expect_value_expanded}")
-        list(APPEND RunCMake_TEST_FAILED
-          "String mismatch (path) at ${path}: ${actual_value} vs. ^${expect_value_expanded}$")
+        string(APPEND RunCMake_TEST_FAILED
+          "String mismatch (path) at:\n ${path}\nexpected:\n ${expect_value_expanded}\nactual:\n ${actual_value}\n")
       endif ()
     elseif (NOT actual_value STREQUAL expect_value_expanded)
-      list(APPEND RunCMake_TEST_FAILED
-        "String mismatch at ${path}: ${actual_value} vs. ${expect_value_expanded}")
+      string(APPEND RunCMake_TEST_FAILED
+        "String mismatch at:\n ${path}\nexpected:\n ${expect_value_expanded}\nactual:\n ${actual_value}\n")
     endif ()
   elseif (actual_type STREQUAL ARRAY)
     check_json_array("${path}" "${actual_value}" "${expect_value}")
@@ -130,11 +130,11 @@
 
   set(iter_len "${actual_len}")
   if (actual_len LESS expect_len)
-    list(APPEND RunCMake_TEST_FAILED
-      "Missing array items at ${path}")
+    string(APPEND RunCMake_TEST_FAILED
+      "Missing array items at:\n ${path}\n")
   elseif (expect_len LESS actual_len)
-    list(APPEND RunCMake_TEST_FAILED
-      "Extra array items at ${path}")
+    string(APPEND RunCMake_TEST_FAILED
+      "Extra array items at:\n ${path}\n")
     set(iter_len "${expect_len}")
   endif ()
 
@@ -200,13 +200,13 @@
 
   if (actual_keys_missed)
     string(REPLACE ";" ", " actual_keys_missed_text "${actual_keys_missed}")
-    list(APPEND RunCMake_TEST_FAILED
-      "Extra unexpected members at ${path}: ${actual_keys_missed_text}")
+    string(APPEND RunCMake_TEST_FAILED
+      "Extra unexpected members at:\n ${path}\nactual:\n ${actual_keys_missed_text}\n")
   endif ()
   if (expect_keys_missed)
     string(REPLACE ";" ", " expect_keys_missed_text "${expect_keys_missed}")
-    list(APPEND RunCMake_TEST_FAILED
-      "Missing expected members at ${path}: ${expect_keys_missed_text}")
+    string(APPEND RunCMake_TEST_FAILED
+      "Missing expected members at\n ${path}\nactual:\n ${expect_keys_missed_text}\n")
   endif ()
 
   foreach (key IN LISTS common_keys)
diff --git a/Tests/RunCMake/CheckIPOSupported/CMP0138-OLD-stderr.txt b/Tests/RunCMake/CheckIPOSupported/CMP0138-OLD-stderr.txt
new file mode 100644
index 0000000..375ab1d
--- /dev/null
+++ b/Tests/RunCMake/CheckIPOSupported/CMP0138-OLD-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at CMP0138-OLD\.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0138 will be removed from a future version
+  of CMake\.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances\.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/CommandLine/C_basic-stdout.txt b/Tests/RunCMake/CommandLine/C_basic-stdout.txt
index 74a938e..1cb0322 100644
--- a/Tests/RunCMake/CommandLine/C_basic-stdout.txt
+++ b/Tests/RunCMake/CommandLine/C_basic-stdout.txt
@@ -1 +1 @@
-loading initial cache file ../C_basic_initial-cache.txt
+loading initial cache file \.\./C_basic_initial-cache.txt
diff --git a/Tests/RunCMake/CommandLine/C_basic_fullpath-stdout.txt b/Tests/RunCMake/CommandLine/C_basic_fullpath-stdout.txt
index 32724f5..700e449 100644
--- a/Tests/RunCMake/CommandLine/C_basic_fullpath-stdout.txt
+++ b/Tests/RunCMake/CommandLine/C_basic_fullpath-stdout.txt
@@ -1 +1,2 @@
-loading initial cache file .*/Tests/RunCMake/CommandLine/C_basic_initial-cache.txt
+loading initial cache file [^
+]*/Tests/RunCMake/CommandLine/C_basic_initial-cache.txt
diff --git a/Tests/RunCMake/CommandLine/ExplicitDirs-C_buildsrcdir-stdout.txt b/Tests/RunCMake/CommandLine/ExplicitDirs-C_buildsrcdir-stdout.txt
index 862cfeb..c73adbd 100644
--- a/Tests/RunCMake/CommandLine/ExplicitDirs-C_buildsrcdir-stdout.txt
+++ b/Tests/RunCMake/CommandLine/ExplicitDirs-C_buildsrcdir-stdout.txt
@@ -1,2 +1,2 @@
-loading initial cache file .*initial-cache.txt
-.*
+loading initial cache file [^
+]*initial-cache.txt
diff --git a/Tests/RunCMake/MaxRecursionDepth/ctest_read_custom_files-env-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/ctest_read_custom_files-env-stderr.txt
index b664fa0..01d446c 100644
--- a/Tests/RunCMake/MaxRecursionDepth/ctest_read_custom_files-env-stderr.txt
+++ b/Tests/RunCMake/MaxRecursionDepth/ctest_read_custom_files-env-stderr.txt
@@ -19,7 +19,7 @@
   .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake:3 \(ctest_read_custom_files\)
   .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake:3 \(ctest_read_custom_files\)
   .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake:3 \(ctest_read_custom_files\)
-  .*/Tests/RunCMake/MaxRecursionDepth/ctest_read_custom_files-env/test\.cmake:10 \(ctest_read_custom_files\)
+  .*/Tests/RunCMake/MaxRecursionDepth/ctest_read_custom_files-env/test\.cmake:5 \(ctest_read_custom_files\)
 
 
 Problem reading custom configuration: .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake
diff --git a/Tests/RunCMake/MaxRecursionDepth/ctest_read_custom_files-var-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/ctest_read_custom_files-var-stderr.txt
index bc89703..2e81bcb 100644
--- a/Tests/RunCMake/MaxRecursionDepth/ctest_read_custom_files-var-stderr.txt
+++ b/Tests/RunCMake/MaxRecursionDepth/ctest_read_custom_files-var-stderr.txt
@@ -19,7 +19,7 @@
   .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake:3 \(ctest_read_custom_files\)
   .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake:3 \(ctest_read_custom_files\)
   .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake:3 \(ctest_read_custom_files\)
-  .*/Tests/RunCMake/MaxRecursionDepth/ctest_read_custom_files-var/test\.cmake:10 \(ctest_read_custom_files\)
+  .*/Tests/RunCMake/MaxRecursionDepth/ctest_read_custom_files-var/test\.cmake:5 \(ctest_read_custom_files\)
 
 
 Problem reading custom configuration: .*/Tests/RunCMake/MaxRecursionDepth/CTestCustom\.cmake
diff --git a/Tests/RunCMake/MaxRecursionDepth/ctest_run_script-var-stderr.txt b/Tests/RunCMake/MaxRecursionDepth/ctest_run_script-var-stderr.txt
index b10b26d..0586d4f 100644
--- a/Tests/RunCMake/MaxRecursionDepth/ctest_run_script-var-stderr.txt
+++ b/Tests/RunCMake/MaxRecursionDepth/ctest_run_script-var-stderr.txt
@@ -11,41 +11,41 @@
   Maximum recursion depth of 10 exceeded
 
 
-CMake Error at .*/Tests/RunCMake/MaxRecursionDepth/ctest_run_script_10\.cmake:13 \(message\):
+CMake Error at .*/Tests/RunCMake/MaxRecursionDepth/ctest_run_script_10\.cmake:8 \(message\):
   Nested script failed
 
 
-CMake Error at .*/Tests/RunCMake/MaxRecursionDepth/ctest_run_script_9\.cmake:13 \(message\):
+CMake Error at .*/Tests/RunCMake/MaxRecursionDepth/ctest_run_script_9\.cmake:8 \(message\):
   Nested script failed
 
 
-CMake Error at .*/Tests/RunCMake/MaxRecursionDepth/ctest_run_script_8\.cmake:13 \(message\):
+CMake Error at .*/Tests/RunCMake/MaxRecursionDepth/ctest_run_script_8\.cmake:8 \(message\):
   Nested script failed
 
 
-CMake Error at .*/Tests/RunCMake/MaxRecursionDepth/ctest_run_script_7\.cmake:13 \(message\):
+CMake Error at .*/Tests/RunCMake/MaxRecursionDepth/ctest_run_script_7\.cmake:8 \(message\):
   Nested script failed
 
 
-CMake Error at .*/Tests/RunCMake/MaxRecursionDepth/ctest_run_script_6\.cmake:13 \(message\):
+CMake Error at .*/Tests/RunCMake/MaxRecursionDepth/ctest_run_script_6\.cmake:8 \(message\):
   Nested script failed
 
 
-CMake Error at .*/Tests/RunCMake/MaxRecursionDepth/ctest_run_script_5\.cmake:13 \(message\):
+CMake Error at .*/Tests/RunCMake/MaxRecursionDepth/ctest_run_script_5\.cmake:8 \(message\):
   Nested script failed
 
 
-CMake Error at .*/Tests/RunCMake/MaxRecursionDepth/ctest_run_script_4\.cmake:13 \(message\):
+CMake Error at .*/Tests/RunCMake/MaxRecursionDepth/ctest_run_script_4\.cmake:8 \(message\):
   Nested script failed
 
 
-CMake Error at .*/Tests/RunCMake/MaxRecursionDepth/ctest_run_script_3\.cmake:13 \(message\):
+CMake Error at .*/Tests/RunCMake/MaxRecursionDepth/ctest_run_script_3\.cmake:8 \(message\):
   Nested script failed
 
 
-CMake Error at .*/Tests/RunCMake/MaxRecursionDepth/ctest_run_script_2\.cmake:13 \(message\):
+CMake Error at .*/Tests/RunCMake/MaxRecursionDepth/ctest_run_script_2\.cmake:8 \(message\):
   Nested script failed
 
 
-CMake Error at .*/Tests/RunCMake/MaxRecursionDepth/ctest_run_script-var/test\.cmake:19 \(message\):
+CMake Error at .*/Tests/RunCMake/MaxRecursionDepth/ctest_run_script-var/test\.cmake:14 \(message\):
   Nested script failed$
diff --git a/Tests/RunCMake/MaxRecursionDepth/ctest_run_script.cmake.in b/Tests/RunCMake/MaxRecursionDepth/ctest_run_script.cmake.in
index d4f28c4..5646a00 100644
--- a/Tests/RunCMake/MaxRecursionDepth/ctest_run_script.cmake.in
+++ b/Tests/RunCMake/MaxRecursionDepth/ctest_run_script.cmake.in
@@ -1,12 +1,7 @@
 cmake_minimum_required(VERSION 3.12)
-set(CTEST_RUN_CURRENT_SCRIPT 0)
 
 message("@LEVEL_CURRENT@")
 
-set(CTEST_SOURCE_DIRECTORY "@CTEST_SOURCE_DIRECTORY@")
-set(CTEST_BINARY_DIRECTORY "@CTEST_BINARY_DIRECTORY@")
-set(CTEST_COMMAND "@CTEST_COMMAND@")
-
 ctest_run_script("${CMAKE_CURRENT_LIST_DIR}/ctest_run_script_@LEVEL_NEXT@.cmake" RETURN_VALUE val)
 
 if(NOT val EQUAL 0)
diff --git a/Tests/RunCMake/MaxRecursionDepth/test.cmake.in b/Tests/RunCMake/MaxRecursionDepth/test.cmake.in
index fd1fc10..62c8e41 100644
--- a/Tests/RunCMake/MaxRecursionDepth/test.cmake.in
+++ b/Tests/RunCMake/MaxRecursionDepth/test.cmake.in
@@ -1,9 +1,4 @@
 cmake_minimum_required(VERSION 3.12)
-set(CTEST_RUN_CURRENT_SCRIPT 0)
-
-set(CTEST_SOURCE_DIRECTORY "@RunCMake_SOURCE_DIR@")
-set(CTEST_BINARY_DIRECTORY "@RunCMake_BINARY_DIR@")
-set(CTEST_COMMAND "${CMAKE_CTEST_COMMAND}")
 
 if(TEST_NAME STREQUAL "ctest_read_custom_files")
   set(x 2)
diff --git a/Tests/RunCMake/ParseImplicitData/linux-Fortran-LFortran-0.41.0-clang.input b/Tests/RunCMake/ParseImplicitData/linux-Fortran-LFortran-0.41.0-clang.input
new file mode 100644
index 0000000..7f84f44
--- /dev/null
+++ b/Tests/RunCMake/ParseImplicitData/linux-Fortran-LFortran-0.41.0-clang.input
@@ -0,0 +1,38 @@
+CMAKE_LANG=Fortran
+CMAKE_LINKER=/usr/bin/ld
+CMAKE_Fortran_COMPILER_ABI=ELF
+CMAKE_Fortran_COMPILER_AR=
+CMAKE_Fortran_COMPILER_ARCHITECTURE_ID=
+CMAKE_Fortran_COMPILER_EXTERNAL_TOOLCHAIN=
+CMAKE_Fortran_COMPILER_ID=LFortran
+CMAKE_Fortran_COMPILER_LAUNCHER=
+CMAKE_Fortran_COMPILER_LOADED=1
+CMAKE_Fortran_COMPILER_RANLIB=
+CMAKE_Fortran_COMPILER_TARGET=
+CMAKE_Fortran_COMPILER_VERSION=0.41.0
+CMAKE_Fortran_COMPILER_VERSION_INTERAL=
+Change Dir: '/tmp/ii/CMakeFiles/CMakeTmp'
+
+Run Build Command(s): /tmp/CMake/bin/cmake -E env VERBOSE=1 /usr/bin/gmake -f Makefile cmTC_e3038/fast
+/usr/bin/gmake  -f CMakeFiles/cmTC_e3038.dir/build.make CMakeFiles/cmTC_e3038.dir/build
+gmake[1]: Entering directory '/tmp/ii/CMakeFiles/CMakeTmp'
+Building Fortran object CMakeFiles/cmTC_e3038.dir/CMakeFortranCompilerABI.F.o
+/usr/bin/lfortran --cpp-infer   -v -Wl,-v --generate-object-code -c /tmp/CMake/Modules/CMakeFortranCompilerABI.F -o CMakeFiles/cmTC_e3038.dir/CMakeFortranCompilerABI.F.o
+Linking Fortran executable cmTC_e3038
+/tmp/CMake/bin/cmake -E cmake_link_script CMakeFiles/cmTC_e3038.dir/link.txt --verbose=1
+clang -o cmTC_e3038 CMakeFiles/cmTC_e3038.dir/CMakeFortranCompilerABI.F.o  -L"/usr/bin/../lib64" -Wl,-rpath,"/usr/bin/../lib64" -Wl,-v -llfortran_runtime -lm -v
+clang version 19.1.0 (Fedora 19.1.0-1.fc41)
+Target: x86_64-redhat-linux-gnu
+Thread model: posix
+InstalledDir: /usr/bin
+Configuration file: /etc/clang/x86_64-redhat-linux-gnu-clang.cfg
+System configuration file directory: /etc/clang/
+Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-redhat-linux/14
+Selected GCC installation: /usr/bin/../lib/gcc/x86_64-redhat-linux/14
+Candidate multilib: .@m64
+Candidate multilib: 32@m32
+Selected multilib: .@m64
+ "/usr/bin/ld" --hash-style=gnu --build-id --eh-frame-hdr -m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o cmTC_e3038 /usr/bin/../lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crt1.o /usr/bin/../lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crti.o /usr/bin/../lib/gcc/x86_64-redhat-linux/14/crtbegin.o -L/usr/bin/../lib64 -L/usr/bin/../lib/clang/19/lib/x86_64-redhat-linux-gnu -L/usr/bin/../lib/gcc/x86_64-redhat-linux/14 -L/usr/bin/../lib/gcc/x86_64-redhat-linux/14/../../../../lib64 -L/lib/../lib64 -L/usr/lib/../lib64 -L/lib -L/usr/lib CMakeFiles/cmTC_e3038.dir/CMakeFortranCompilerABI.F.o -rpath /usr/bin/../lib64 -v -llfortran_runtime -lm -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/bin/../lib/gcc/x86_64-redhat-linux/14/crtend.o /usr/bin/../lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crtn.o
+GNU ld version 2.43.1-2.fc41
+/usr/bin/lfortran  -v -Wl,-v CMakeFiles/cmTC_e3038.dir/CMakeFortranCompilerABI.F.o -o cmTC_e3038
+gmake[1]: Leaving directory '/tmp/ii/CMakeFiles/CMakeTmp'
diff --git a/Tests/RunCMake/ParseImplicitData/linux-Fortran-LFortran-0.41.0-gcc.input b/Tests/RunCMake/ParseImplicitData/linux-Fortran-LFortran-0.41.0-gcc.input
new file mode 100644
index 0000000..4a79262
--- /dev/null
+++ b/Tests/RunCMake/ParseImplicitData/linux-Fortran-LFortran-0.41.0-gcc.input
@@ -0,0 +1,43 @@
+CMAKE_LANG=Fortran
+CMAKE_LINKER=/usr/bin/ld
+CMAKE_Fortran_COMPILER_ABI=ELF
+CMAKE_Fortran_COMPILER_AR=
+CMAKE_Fortran_COMPILER_ARCHITECTURE_ID=
+CMAKE_Fortran_COMPILER_EXTERNAL_TOOLCHAIN=
+CMAKE_Fortran_COMPILER_ID=LFortran
+CMAKE_Fortran_COMPILER_LAUNCHER=
+CMAKE_Fortran_COMPILER_LOADED=1
+CMAKE_Fortran_COMPILER_RANLIB=
+CMAKE_Fortran_COMPILER_TARGET=
+CMAKE_Fortran_COMPILER_VERSION=0.41.0
+CMAKE_Fortran_COMPILER_VERSION_INTERAL=
+Change Dir: '/tmp/ii/CMakeFiles/CMakeTmp'
+
+Run Build Command(s): /tmp/CMake/bin/cmake -E env VERBOSE=1 /usr/bin/gmake -f Makefile cmTC_fdc77/fast
+/usr/bin/gmake  -f CMakeFiles/cmTC_fdc77.dir/build.make CMakeFiles/cmTC_fdc77.dir/build
+gmake[1]: Entering directory '/tmp/ii/CMakeFiles/CMakeTmp'
+Building Fortran object CMakeFiles/cmTC_fdc77.dir/CMakeFortranCompilerABI.F.o
+/usr/bin/lfortran --cpp-infer   --link-with-gcc    -v -Wl,-v --generate-object-code -c /tmp/CMake/Modules/CMakeFortranCompilerABI.F -o CMakeFiles/cmTC_fdc77.dir/CMakeFortranCompilerABI.F.o
+Linking Fortran executable cmTC_fdc77
+/tmp/CMake/bin/cmake -E cmake_link_script CMakeFiles/cmTC_fdc77.dir/link.txt --verbose=1
+gcc -o cmTC_fdc77 CMakeFiles/cmTC_fdc77.dir/CMakeFortranCompilerABI.F.o  -L"/usr/bin/../lib64" -Wl,-rpath,"/usr/bin/../lib64" -Wl,-v -llfortran_runtime -lm -v
+Using built-in specs.
+COLLECT_GCC=gcc
+COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/14/lto-wrapper
+OFFLOAD_TARGET_NAMES=nvptx-none:amdgcn-amdhsa
+OFFLOAD_TARGET_DEFAULT=1
+Target: x86_64-redhat-linux
+Configured with: ../configure --enable-bootstrap --enable-languages=c,c++,fortran,objc,obj-c++,ada,go,d,m2,lto --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-shared --enable-threads=posix --enable-checking=release --enable-multilib --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-gcc-major-version-only --enable-libstdcxx-backtrace --with-libstdcxx-zoneinfo=/usr/share/zoneinfo --with-linker-hash-style=gnu --enable-plugin --enable-initfini-array --with-isl=/builddir/build/BUILD/gcc-14.2.1-build/gcc-14.2.1-20240912/obj-x86_64-redhat-linux/isl-install --enable-offload-targets=nvptx-none,amdgcn-amdhsa --enable-offload-defaulted --without-cuda-driver --enable-gnu-indirect-function --enable-cet --with-tune=generic --with-arch_32=i686 --build=x86_64-redhat-linux --with-build-config=bootstrap-lto --enable-link-serialization=1
+Thread model: posix
+Supported LTO compression algorithms: zlib zstd
+gcc version 14.2.1 20240912 (Red Hat 14.2.1-3) (GCC)
+COMPILER_PATH=/usr/libexec/gcc/x86_64-redhat-linux/14/:/usr/libexec/gcc/x86_64-redhat-linux/14/:/usr/libexec/gcc/x86_64-redhat-linux/:/usr/lib/gcc/x86_64-redhat-linux/14/:/usr/lib/gcc/x86_64-redhat-linux/
+LIBRARY_PATH=/usr/lib/gcc/x86_64-redhat-linux/14/:/usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/:/lib/../lib64/:/usr/lib/../lib64/:/usr/lib/gcc/x86_64-redhat-linux/14/../../../:/lib/:/usr/lib/
+COLLECT_GCC_OPTIONS='-o' 'cmTC_fdc77' '-L/usr/bin/../lib64' '-foffload-options=-l_GCC_m' '-v' '-mtune=generic' '-march=x86-64' '-dumpdir' 'cmTC_fdc77.'
+ /usr/libexec/gcc/x86_64-redhat-linux/14/collect2 -plugin /usr/libexec/gcc/x86_64-redhat-linux/14/liblto_plugin.so -plugin-opt=/usr/libexec/gcc/x86_64-redhat-linux/14/lto-wrapper -plugin-opt=-fresolution=/tmp/ccg4ypde.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --build-id --no-add-needed --eh-frame-hdr --hash-style=gnu -m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o cmTC_fdc77 /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crt1.o /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crti.o /usr/lib/gcc/x86_64-redhat-linux/14/crtbegin.o -L/usr/bin/../lib64 -L/usr/lib/gcc/x86_64-redhat-linux/14 -L/usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64 -L/lib/../lib64 -L/usr/lib/../lib64 -L/usr/lib/gcc/x86_64-redhat-linux/14/../../.. CMakeFiles/cmTC_fdc77.dir/CMakeFortranCompilerABI.F.o -rpath /usr/bin/../lib64 -v -llfortran_runtime -lm -lgcc --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state --as-needed -lgcc_s --pop-state /usr/lib/gcc/x86_64-redhat-linux/14/crtend.o /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crtn.o
+collect2 version 14.2.1 20240912 (Red Hat 14.2.1-3)
+/usr/bin/ld -plugin /usr/libexec/gcc/x86_64-redhat-linux/14/liblto_plugin.so -plugin-opt=/usr/libexec/gcc/x86_64-redhat-linux/14/lto-wrapper -plugin-opt=-fresolution=/tmp/ccg4ypde.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --build-id --no-add-needed --eh-frame-hdr --hash-style=gnu -m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o cmTC_fdc77 /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crt1.o /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crti.o /usr/lib/gcc/x86_64-redhat-linux/14/crtbegin.o -L/usr/bin/../lib64 -L/usr/lib/gcc/x86_64-redhat-linux/14 -L/usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64 -L/lib/../lib64 -L/usr/lib/../lib64 -L/usr/lib/gcc/x86_64-redhat-linux/14/../../.. CMakeFiles/cmTC_fdc77.dir/CMakeFortranCompilerABI.F.o -rpath /usr/bin/../lib64 -v -llfortran_runtime -lm -lgcc --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state --as-needed -lgcc_s --pop-state /usr/lib/gcc/x86_64-redhat-linux/14/crtend.o /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crtn.o
+GNU ld version 2.43.1-2.fc41
+COLLECT_GCC_OPTIONS='-o' 'cmTC_fdc77' '-L/usr/bin/../lib64' '-foffload-options=-l_GCC_m' '-v' '-mtune=generic' '-march=x86-64' '-dumpdir' 'cmTC_fdc77.'
+/usr/bin/lfortran  -v -Wl,-v --link-with-gcc  CMakeFiles/cmTC_fdc77.dir/CMakeFortranCompilerABI.F.o -o cmTC_fdc77
+gmake[1]: Leaving directory '/tmp/ii/CMakeFiles/CMakeTmp'
diff --git a/Tests/RunCMake/ParseImplicitLinkInfo/ParseImplicitLinkInfo.cmake b/Tests/RunCMake/ParseImplicitLinkInfo/ParseImplicitLinkInfo.cmake
index 121a1d2..5b4b8d8 100644
--- a/Tests/RunCMake/ParseImplicitLinkInfo/ParseImplicitLinkInfo.cmake
+++ b/Tests/RunCMake/ParseImplicitLinkInfo/ParseImplicitLinkInfo.cmake
@@ -43,6 +43,8 @@
   linux-Fortran-LLVMFlang-15.0.0
   linux-Fortran-LFortran-0.35.0-clang
   linux-Fortran-LFortran-0.35.0-gcc
+  linux-Fortran-LFortran-0.41.0-clang
+  linux-Fortran-LFortran-0.41.0-gcc
   linux-custom_clang-C-Clang-13.0.0 linux-custom_clang-CXX-Clang-13.0.0
   mingw.org-C-GNU-4.9.3 mingw.org-CXX-GNU-4.9.3
   netbsd-C-GNU-4.8.5 netbsd-CXX-GNU-4.8.5
diff --git a/Tests/RunCMake/ParseImplicitLinkInfo/results/linux-Fortran-LFortran-0.41.0-clang.output b/Tests/RunCMake/ParseImplicitLinkInfo/results/linux-Fortran-LFortran-0.41.0-clang.output
new file mode 100644
index 0000000..e0af3e3
--- /dev/null
+++ b/Tests/RunCMake/ParseImplicitLinkInfo/results/linux-Fortran-LFortran-0.41.0-clang.output
@@ -0,0 +1,4 @@
+libs=lfortran_runtime;m;gcc;gcc_s;c;gcc;gcc_s
+dirs=/usr/lib64;/usr/lib/clang/19/lib/x86_64-redhat-linux-gnu;/usr/lib/gcc/x86_64-redhat-linux/14;/lib64;/lib;/usr/lib
+library_arch=x86_64-redhat-linux-gnu
+linker_tool=/usr/bin/ld
diff --git a/Tests/RunCMake/ParseImplicitLinkInfo/results/linux-Fortran-LFortran-0.41.0-gcc.output b/Tests/RunCMake/ParseImplicitLinkInfo/results/linux-Fortran-LFortran-0.41.0-gcc.output
new file mode 100644
index 0000000..f1b1993
--- /dev/null
+++ b/Tests/RunCMake/ParseImplicitLinkInfo/results/linux-Fortran-LFortran-0.41.0-gcc.output
@@ -0,0 +1,4 @@
+libs=lfortran_runtime;m;gcc;gcc_s;c;gcc;gcc_s
+dirs=/usr/lib64;/usr/lib/gcc/x86_64-redhat-linux/14;/lib64;/usr/lib
+library_arch=
+linker_tool=/usr/bin/ld
diff --git a/Tests/RunCMake/UnityBuild/RunCMakeTest.cmake b/Tests/RunCMake/UnityBuild/RunCMakeTest.cmake
index 78996e4..423262b 100644
--- a/Tests/RunCMake/UnityBuild/RunCMakeTest.cmake
+++ b/Tests/RunCMake/UnityBuild/RunCMakeTest.cmake
@@ -8,10 +8,16 @@
 endfunction()
 
 run_cmake(unitybuild_c)
+run_cmake(unitybuild_c_absolute_path)
+run_cmake(unitybuild_c_relocatable_path)
 run_cmake(unitybuild_c_batch)
 run_cmake(unitybuild_c_group)
 run_cmake(unitybuild_cxx)
+run_cmake(unitybuild_cxx_absolute_path)
+run_cmake(unitybuild_cxx_relocatable_path)
 run_cmake(unitybuild_cxx_group)
+run_cmake(unitybuild_c_and_cxx_absolute_path)
+run_cmake(unitybuild_c_and_cxx_relocatable_path)
 run_cmake(unitybuild_c_and_cxx)
 run_cmake(unitybuild_c_and_cxx_group)
 if(CMake_TEST_OBJC)
@@ -37,6 +43,7 @@
 run_build(unitybuild_anon_ns)
 run_build(unitybuild_anon_ns_no_unity_build)
 run_build(unitybuild_anon_ns_group_mode)
+run_cmake(unitybuild_relocatable_locations)
 
 function(run_per_config name)
   set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${name}-build)
diff --git a/Tests/RunCMake/UnityBuild/relocatable/foo.c b/Tests/RunCMake/UnityBuild/relocatable/foo.c
new file mode 100644
index 0000000..de1160c
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/relocatable/foo.c
@@ -0,0 +1,5 @@
+int foo(int x)
+{
+  (void)x;
+  return 0;
+}
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_c_absolute_path-check.cmake b/Tests/RunCMake/UnityBuild/unitybuild_c_absolute_path-check.cmake
new file mode 100644
index 0000000..e716e93
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_c_absolute_path-check.cmake
@@ -0,0 +1,17 @@
+set(unitybuild_c "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_0_c.c")
+if(NOT EXISTS "${unitybuild_c}")
+  set(RunCMake_TEST_FAILED "Generated unity source file ${unitybuild_c} does not exist.")
+  return()
+endif()
+
+string(JOIN ".*" EXPECTED_UNITY_FILE_CONTENT
+  [[#include "([A-Za-z]:)?/[^"]*/Tests/RunCMake/UnityBuild/unitybuild_c_absolute_path-build/s1\.c"]]
+  [[#include "([A-Za-z]:)?/[^"]*/Tests/RunCMake/UnityBuild/unitybuild_c_absolute_path-build/s2\.c"]]
+  [[#include "([A-Za-z]:)?/[^"]*/Tests/RunCMake/UnityBuild/unitybuild_c_absolute_path-build/s3\.c"]]
+)
+
+file(STRINGS ${unitybuild_c} unitybuild_c_strings)
+if(NOT unitybuild_c_strings MATCHES "${EXPECTED_UNITY_FILE_CONTENT}")
+  set(RunCMake_TEST_FAILED "Generated unity file ${unitybuild_c} doesn't contain absolute paths")
+  return()
+endif()
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_c_absolute_path.cmake b/Tests/RunCMake/UnityBuild/unitybuild_c_absolute_path.cmake
new file mode 100644
index 0000000..0fe6fbf
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_c_absolute_path.cmake
@@ -0,0 +1,13 @@
+project(unitybuild_c_absolute_path C)
+
+set(srcs "")
+foreach(s RANGE 1 3)
+  set(src "${CMAKE_CURRENT_BINARY_DIR}/s${s}.c")
+  file(WRITE "${src}" "int s${s}(void) { return 0; }\n")
+  list(APPEND srcs "${src}")
+endforeach()
+
+add_library(tgt SHARED ${srcs})
+
+set_target_properties(tgt PROPERTIES UNITY_BUILD ON
+                                     UNITY_BUILD_RELOCATABLE FALSE)
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_absolute_path-check.cmake b/Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_absolute_path-check.cmake
new file mode 100644
index 0000000..aa41f78
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_absolute_path-check.cmake
@@ -0,0 +1,35 @@
+set(unitybuild_c "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_0_c.c")
+if(NOT EXISTS "${unitybuild_c}")
+  set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_c} does not exist.")
+  return()
+endif()
+
+string(JOIN ".*" EXPECTED_UNITY_FILE_CONTENT
+  [[#include "([A-Za-z]:)?/[^"]*/Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_absolute_path-build/s1\.c"]]
+  [[#include "([A-Za-z]:)?/[^"]*/Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_absolute_path-build/s2\.c"]]
+  [[#include "([A-Za-z]:)?/[^"]*/Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_absolute_path-build/s3\.c"]]
+)
+
+file(STRINGS ${unitybuild_c} unitybuild_c_strings)
+if(NOT unitybuild_c_strings MATCHES "${EXPECTED_UNITY_FILE_CONTENT}")
+  set(RunCMake_TEST_FAILED "Generated unity file ${unitybuild_c} doesn't contain absolute paths")
+  return()
+endif()
+
+set(unitybuild_cxx "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_0_cxx.cxx")
+if(NOT EXISTS "${unitybuild_cxx}")
+  set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_cxx} does not exist.")
+  return()
+endif()
+
+string(JOIN ".*" EXPECTED_UNITY_FILE_CONTENT_CXX
+  [[#include "([A-Za-z]:)?/[^"]*/Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_absolute_path-build/s1\.cxx"]]
+  [[#include "([A-Za-z]:)?/[^"]*/Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_absolute_path-build/s2\.cxx"]]
+  [[#include "([A-Za-z]:)?/[^"]*/Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_absolute_path-build/s3\.cxx"]]
+)
+
+file(STRINGS ${unitybuild_cxx} unitybuild_cxx_strings)
+if(NOT unitybuild_cxx_strings MATCHES "${EXPECTED_UNITY_FILE_CONTENT_CXX}")
+  set(RunCMake_TEST_FAILED "Generated unity file ${unitybuild_cxx} doesn't contain absolute paths")
+  return()
+endif()
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_absolute_path.cmake b/Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_absolute_path.cmake
new file mode 100644
index 0000000..9c6b0e4
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_absolute_path.cmake
@@ -0,0 +1,18 @@
+project(unitybuild_c_and_cxx_absolute_path C CXX)
+
+set(srcs "")
+foreach(s RANGE 1 3)
+  set(src_c "${CMAKE_CURRENT_BINARY_DIR}/s${s}.c")
+  file(WRITE "${src_c}" "int s${s}(void) { return 0; }\n")
+
+  set(src_cxx "${CMAKE_CURRENT_BINARY_DIR}/s${s}.cxx")
+  file(WRITE "${src_cxx}" "int s${s}(void) { return 0; }\n")
+
+  list(APPEND srcs "${src_c}")
+  list(APPEND srcs "${src_cxx}")
+endforeach()
+
+add_library(tgt SHARED ${srcs})
+
+set_target_properties(tgt PROPERTIES UNITY_BUILD ON
+                                     UNITY_BUILD_RELOCATABLE FALSE)
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_relocatable_path-check.cmake b/Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_relocatable_path-check.cmake
new file mode 100644
index 0000000..e5fd4df
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_relocatable_path-check.cmake
@@ -0,0 +1,35 @@
+set(unitybuild_c "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_0_c.c")
+if(NOT EXISTS "${unitybuild_c}")
+  set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_c} does not exist.")
+  return()
+endif()
+
+string(JOIN ".*" EXPECTED_UNITY_FILE_CONTENT
+  [[#include "\.\./\.\./\.\./s1\.c"]]
+  [[#include "\.\./\.\./\.\./s2\.c"]]
+  [[#include "\.\./\.\./\.\./s3\.c"]]
+)
+
+file(STRINGS ${unitybuild_c} unitybuild_c_strings)
+if(NOT unitybuild_c_strings MATCHES "${EXPECTED_UNITY_FILE_CONTENT}")
+  set(RunCMake_TEST_FAILED "Generated unity file ${unitybuild_c} doesn't contain relative paths")
+  return()
+endif()
+
+set(unitybuild_cxx "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_0_cxx.cxx")
+if(NOT EXISTS "${unitybuild_cxx}")
+  set(RunCMake_TEST_FAILED "Generated unity source files ${unitybuild_cxx} does not exist.")
+  return()
+endif()
+
+string(JOIN ".*" EXPECTED_UNITY_FILE_CONTENT_CXX
+  [[#include "\.\./\.\./\.\./s1\.cxx"]]
+  [[#include "\.\./\.\./\.\./s2\.cxx"]]
+  [[#include "\.\./\.\./\.\./s3\.cxx"]]
+)
+
+file(STRINGS ${unitybuild_cxx} unitybuild_cxx_strings)
+if(NOT unitybuild_cxx_strings MATCHES "${EXPECTED_UNITY_FILE_CONTENT_CXX}")
+  set(RunCMake_TEST_FAILED "Generated unity file ${unitybuild_cxx} doesn't contain relative paths")
+  return()
+endif()
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_relocatable_path.cmake b/Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_relocatable_path.cmake
new file mode 100644
index 0000000..21257f4
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_c_and_cxx_relocatable_path.cmake
@@ -0,0 +1,18 @@
+project(unitybuild_c_and_cxx_relocatable_path C CXX)
+
+set(srcs "")
+foreach(s RANGE 1 3)
+  set(src_c "${CMAKE_CURRENT_BINARY_DIR}/s${s}.c")
+  file(WRITE "${src_c}" "int s${s}(void) { return 0; }\n")
+
+  set(src_cxx "${CMAKE_CURRENT_BINARY_DIR}/s${s}.cxx")
+  file(WRITE "${src_cxx}" "int s${s}(void) { return 0; }\n")
+
+  list(APPEND srcs "${src_c}")
+  list(APPEND srcs "${src_cxx}")
+endforeach()
+
+add_library(tgt SHARED ${srcs})
+
+set_target_properties(tgt PROPERTIES UNITY_BUILD ON
+                                     UNITY_BUILD_RELOCATABLE TRUE)
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_c_relocatable_path-check.cmake b/Tests/RunCMake/UnityBuild/unitybuild_c_relocatable_path-check.cmake
new file mode 100644
index 0000000..f8e41ce
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_c_relocatable_path-check.cmake
@@ -0,0 +1,17 @@
+set(unitybuild_c "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_0_c.c")
+if(NOT EXISTS "${unitybuild_c}")
+  set(RunCMake_TEST_FAILED "Generated unity source file ${unitybuild_c} does not exist.")
+  return()
+endif()
+
+string(JOIN ".*" EXPECTED_UNITY_FILE_CONTENT
+  [[#include "\.\./\.\./\.\./s1\.c"]]
+  [[#include "\.\./\.\./\.\./s2\.c"]]
+  [[#include "\.\./\.\./\.\./s3\.c"]]
+)
+
+file(STRINGS ${unitybuild_c} unitybuild_c_strings)
+if(NOT unitybuild_c_strings MATCHES "${EXPECTED_UNITY_FILE_CONTENT}")
+  set(RunCMake_TEST_FAILED "Generated unity file ${unitybuild_c} doesn't contain relative paths")
+  return()
+endif()
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_c_relocatable_path.cmake b/Tests/RunCMake/UnityBuild/unitybuild_c_relocatable_path.cmake
new file mode 100644
index 0000000..e6046e6
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_c_relocatable_path.cmake
@@ -0,0 +1,14 @@
+project(unitybuild_c_relocatable_path C)
+
+set(srcs "")
+foreach(s RANGE 1 3)
+  set(src "${CMAKE_CURRENT_BINARY_DIR}/s${s}.c")
+  file(WRITE "${src}" "int s${s}(void) { return 0; }\n")
+  list(APPEND srcs "${src}")
+endforeach()
+
+add_library(tgt SHARED ${srcs})
+
+set_target_properties(tgt PROPERTIES UNITY_BUILD ON
+                                     UNITY_BUILD_RELOCATABLE TRUE
+                                     UNITY_BUILD_UNIQUE_ID "anon")
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_cxx_absolute_path-check.cmake b/Tests/RunCMake/UnityBuild/unitybuild_cxx_absolute_path-check.cmake
new file mode 100644
index 0000000..95d96ec
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_cxx_absolute_path-check.cmake
@@ -0,0 +1,17 @@
+set(unitybuild_cxx "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_0_cxx.cxx")
+if(NOT EXISTS "${unitybuild_cxx}")
+  set(RunCMake_TEST_FAILED "Generated unity source file ${unitybuild_cxx} does not exist.")
+  return()
+endif()
+
+string(JOIN ".*" EXPECTED_UNITY_FILE_CONTENT
+  [[#include "([A-Za-z]:)?/[^"]*/Tests/RunCMake/UnityBuild/unitybuild_cxx_absolute_path-build/s1\.cxx"]]
+  [[#include "([A-Za-z]:)?/[^"]*/Tests/RunCMake/UnityBuild/unitybuild_cxx_absolute_path-build/s2\.cxx"]]
+  [[#include "([A-Za-z]:)?/[^"]*/Tests/RunCMake/UnityBuild/unitybuild_cxx_absolute_path-build/s3\.cxx"]]
+)
+
+file(STRINGS ${unitybuild_cxx} unitybuild_cxx_strings)
+if(NOT unitybuild_cxx_strings MATCHES "${EXPECTED_UNITY_FILE_CONTENT}")
+  set(RunCMake_TEST_FAILED "Generated unity file ${unitybuild_cxx} doesn't contain absolute paths")
+  return()
+endif()
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_cxx_absolute_path.cmake b/Tests/RunCMake/UnityBuild/unitybuild_cxx_absolute_path.cmake
new file mode 100644
index 0000000..07543f4
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_cxx_absolute_path.cmake
@@ -0,0 +1,13 @@
+project(unitybuild_cxx_absolute_path CXX)
+
+set(srcs "")
+foreach(s RANGE 1 3)
+  set(src "${CMAKE_CURRENT_BINARY_DIR}/s${s}.cxx")
+  file(WRITE "${src}" "int s${s}(void) { return 0; }\n")
+  list(APPEND srcs "${src}")
+endforeach()
+
+add_library(tgt SHARED ${srcs})
+
+set_target_properties(tgt PROPERTIES UNITY_BUILD ON
+                                     UNITY_BUILD_RELOCATABLE FALSE)
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_cxx_relocatable_path-check.cmake b/Tests/RunCMake/UnityBuild/unitybuild_cxx_relocatable_path-check.cmake
new file mode 100644
index 0000000..d06547d
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_cxx_relocatable_path-check.cmake
@@ -0,0 +1,17 @@
+set(unitybuild_cxx "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_0_cxx.cxx")
+if(NOT EXISTS "${unitybuild_cxx}")
+  set(RunCMake_TEST_FAILED "Generated unity source file ${unitybuild_cxx} does not exist.")
+  return()
+endif()
+
+string(JOIN ".*" EXPECTED_UNITY_FILE_CONTENT
+  [[#include "\.\./\.\./\.\./s1\.cxx"]]
+  [[#include "\.\./\.\./\.\./s2\.cxx"]]
+  [[#include "\.\./\.\./\.\./s3\.cxx"]]
+)
+
+file(STRINGS ${unitybuild_cxx} unitybuild_cxx_strings)
+if(NOT unitybuild_cxx_strings MATCHES "${EXPECTED_UNITY_FILE_CONTENT}")
+  set(RunCMake_TEST_FAILED "Generated unity file ${unitybuild_cxx} doesn't contain relative paths")
+  return()
+endif()
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_cxx_relocatable_path.cmake b/Tests/RunCMake/UnityBuild/unitybuild_cxx_relocatable_path.cmake
new file mode 100644
index 0000000..abf672d
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_cxx_relocatable_path.cmake
@@ -0,0 +1,13 @@
+project(unitybuild_cxx_relocatable_path CXX)
+
+set(srcs "")
+foreach(s RANGE 1 3)
+  set(src "${CMAKE_CURRENT_BINARY_DIR}/s${s}.cxx")
+  file(WRITE "${src}" "int s${s}(void) { return 0; }\n")
+  list(APPEND srcs "${src}")
+endforeach()
+
+add_library(tgt SHARED ${srcs})
+
+set_target_properties(tgt PROPERTIES UNITY_BUILD ON
+                                     UNITY_BUILD_RELOCATABLE TRUE)
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_relocatable_locations-check.cmake b/Tests/RunCMake/UnityBuild/unitybuild_relocatable_locations-check.cmake
new file mode 100644
index 0000000..565920c
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_relocatable_locations-check.cmake
@@ -0,0 +1,22 @@
+set(unitybuild_c "${RunCMake_TEST_BINARY_DIR}/CMakeFiles/tgt.dir/Unity/unity_0_c.c")
+if(NOT EXISTS "${unitybuild_c}")
+  set(RunCMake_TEST_FAILED "Generated unity source file ${unitybuild_c} does not exist.")
+  return()
+endif()
+
+string(JOIN ".*" EXPECTED_UNITY_FILE_CONTENT
+  [[#include "\.\./\.\./\.\./s1\.c"]]
+  [[#include "\.\./\.\./\.\./s2\.c"]]
+  [[#include "\.\./\.\./\.\./s3\.c"]]
+  [[#include "\.\./\.\./\.\./subFolder/sub1\.c"]]
+  [[#include "\.\./\.\./\.\./subFolder/sub2\.c"]]
+  [[#include "\.\./\.\./\.\./subFolder/sub3\.c"]]
+  [[#include "f\.c"]]
+  [[#include "relocatable/foo\.c"]]
+)
+
+file(STRINGS ${unitybuild_c} unitybuild_c_strings)
+if(NOT unitybuild_c_strings MATCHES "${EXPECTED_UNITY_FILE_CONTENT}")
+  set(RunCMake_TEST_FAILED "Generated unity file ${unitybuild_c} doesn't contain relative paths")
+  return()
+endif()
diff --git a/Tests/RunCMake/UnityBuild/unitybuild_relocatable_locations.cmake b/Tests/RunCMake/UnityBuild/unitybuild_relocatable_locations.cmake
new file mode 100644
index 0000000..a28d89e
--- /dev/null
+++ b/Tests/RunCMake/UnityBuild/unitybuild_relocatable_locations.cmake
@@ -0,0 +1,23 @@
+project(unitybuild_relocatable_locations C)
+
+# Binary path relative source file
+set(srcs "")
+foreach(s RANGE 1 3)
+  set(src "${CMAKE_CURRENT_BINARY_DIR}/s${s}.c")
+  file(WRITE "${src}" "int s${s}(void) { return 0; }\n")
+  list(APPEND srcs "${src}")
+endforeach()
+foreach(s RANGE 1 3)
+  set(src "${CMAKE_CURRENT_BINARY_DIR}/subFolder/sub${s}.c")
+  file(WRITE "${src}" "int sub${s}(void) { return 0; }\n")
+  list(APPEND srcs "${src}")
+endforeach()
+
+# Source path relative source file
+list(APPEND srcs "${CMAKE_SOURCE_DIR}/f.c")
+list(APPEND srcs "${CMAKE_SOURCE_DIR}/relocatable/foo.c")
+
+add_library(tgt SHARED ${srcs})
+
+set_target_properties(tgt PROPERTIES UNITY_BUILD ON
+                                     UNITY_BUILD_RELOCATABLE TRUE)
diff --git a/Tests/RunCMake/VS10Project/CSharpSourceGroup/images/empty.bmp b/Tests/RunCMake/VS10Project/CSharpSourceGroup/Images/empty.bmp
similarity index 100%
rename from Tests/RunCMake/VS10Project/CSharpSourceGroup/images/empty.bmp
rename to Tests/RunCMake/VS10Project/CSharpSourceGroup/Images/empty.bmp
diff --git a/Tests/RunCMake/VS10Project/VsCsharpSourceGroup-check.cmake b/Tests/RunCMake/VS10Project/VsCsharpSourceGroup-check.cmake
index 9c9409c..5d1ee3e 100644
--- a/Tests/RunCMake/VS10Project/VsCsharpSourceGroup-check.cmake
+++ b/Tests/RunCMake/VS10Project/VsCsharpSourceGroup-check.cmake
@@ -11,7 +11,7 @@
 set(SOURCE_GROUPS_TO_FIND
   "CSharpSourceGroup\\\\foo\\.cs"
   "CSharpSourceGroup\\\\nested\\\\baz\\.cs"
-  "CSharpSourceGroup\\\\images\\\\empty\\.bmp"
+  "Images\\\\empty\\.bmp"
   "VsCsharpSourceGroup\\.png"
   "AssemblyInfo\\.cs"
 )
diff --git a/Tests/RunCMake/WorkingDirectory/buildAndTestNoBuildDir-stdout.txt b/Tests/RunCMake/WorkingDirectory/buildAndTestNoBuildDir-stdout.txt
index da89317..a0098e7 100644
--- a/Tests/RunCMake/WorkingDirectory/buildAndTestNoBuildDir-stdout.txt
+++ b/Tests/RunCMake/WorkingDirectory/buildAndTestNoBuildDir-stdout.txt
@@ -1 +1 @@
-Failed to change working directory to .*[/\\]buildAndTestNoBuildDir[/\\]CMakeLists.txt :
+Failed to change working directory to ".*[/\\]buildAndTestNoBuildDir[/\\]CMakeLists.txt": (Not a directory|Invalid argument)
diff --git a/Tests/RunCMake/WorkingDirectory/dirNotExist-stderr.txt b/Tests/RunCMake/WorkingDirectory/dirNotExist-stderr.txt
index 3cea890..21d288e 100644
--- a/Tests/RunCMake/WorkingDirectory/dirNotExist-stderr.txt
+++ b/Tests/RunCMake/WorkingDirectory/dirNotExist-stderr.txt
@@ -1 +1 @@
-Failed to change working directory to .*[/\\]dirNotExist-build[/\\]thisDirWillNotExist :
+Failed to change working directory to ".*[/\\]dirNotExist-build[/\\]thisDirWillNotExist": No such file or directory
diff --git a/Tests/RunCMake/block/Scope-POLICIES-stderr.txt b/Tests/RunCMake/block/Scope-POLICIES-stderr.txt
new file mode 100644
index 0000000..84b99aa
--- /dev/null
+++ b/Tests/RunCMake/block/Scope-POLICIES-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at Scope-POLICIES\.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0139 will be removed from a future version
+  of CMake\.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances\.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/block/Scope-VARIABLES-stderr.txt b/Tests/RunCMake/block/Scope-VARIABLES-stderr.txt
new file mode 100644
index 0000000..458daa7
--- /dev/null
+++ b/Tests/RunCMake/block/Scope-VARIABLES-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at Scope-VARIABLES\.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0139 will be removed from a future version
+  of CMake\.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances\.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/block/Scope-stderr.txt b/Tests/RunCMake/block/Scope-stderr.txt
new file mode 100644
index 0000000..af84e88
--- /dev/null
+++ b/Tests/RunCMake/block/Scope-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at Scope\.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0139 will be removed from a future version
+  of CMake\.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances\.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/find_file/Registry-query-stderr.txt b/Tests/RunCMake/find_file/Registry-query-stderr.txt
new file mode 100644
index 0000000..0532022
--- /dev/null
+++ b/Tests/RunCMake/find_file/Registry-query-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at Registry-query\.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0134 will be removed from a future version
+  of CMake\.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances\.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/find_library/Registry-query-stderr.txt b/Tests/RunCMake/find_library/Registry-query-stderr.txt
new file mode 100644
index 0000000..0532022
--- /dev/null
+++ b/Tests/RunCMake/find_library/Registry-query-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at Registry-query\.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0134 will be removed from a future version
+  of CMake\.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances\.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/find_package/Registry-query-stderr.txt b/Tests/RunCMake/find_package/Registry-query-stderr.txt
new file mode 100644
index 0000000..0532022
--- /dev/null
+++ b/Tests/RunCMake/find_package/Registry-query-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at Registry-query\.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0134 will be removed from a future version
+  of CMake\.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances\.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/find_path/Registry-query-stderr.txt b/Tests/RunCMake/find_path/Registry-query-stderr.txt
new file mode 100644
index 0000000..0532022
--- /dev/null
+++ b/Tests/RunCMake/find_path/Registry-query-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at Registry-query\.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0134 will be removed from a future version
+  of CMake\.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances\.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/find_program/Registry-query-stderr.txt b/Tests/RunCMake/find_program/Registry-query-stderr.txt
new file mode 100644
index 0000000..0532022
--- /dev/null
+++ b/Tests/RunCMake/find_program/Registry-query-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at Registry-query\.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0134 will be removed from a future version
+  of CMake\.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances\.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/get_filename_component/KnownComponents.cmake b/Tests/RunCMake/get_filename_component/KnownComponents.cmake
index c2762ad..34af12e 100644
--- a/Tests/RunCMake/get_filename_component/KnownComponents.cmake
+++ b/Tests/RunCMake/get_filename_component/KnownComponents.cmake
@@ -1,7 +1,7 @@
 # Assertion macro
 macro(check desc actual expect)
   if(NOT "x${actual}" STREQUAL "x${expect}")
-    message(SEND_ERROR "${desc}: got \"${actual}\", not \"${expect}\"")
+    message(SEND_ERROR "${desc}: got\n  \"${actual}\"\nnot\n  \"${expect}\"")
   endif()
 endmacro()
 
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/CMakeLists.txt b/Tests/RunCMake/target_link_libraries-LINKER-prefix/CMakeLists.txt
new file mode 100644
index 0000000..a8cb6d1
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/CMakeLists.txt
@@ -0,0 +1,5 @@
+cmake_minimum_required(VERSION 3.30...3.32)
+
+project(${RunCMake_TEST} LANGUAGES NONE)
+
+include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion-LINKER-check.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion-LINKER-check.cmake
new file mode 100644
index 0000000..99f5aa3
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion-LINKER-check.cmake
@@ -0,0 +1,3 @@
+
+set(reference_file "LINKER.txt")
+include ("${CMAKE_CURRENT_LIST_DIR}/LINKER_expansion-validation.cmake")
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion-LINKER_CONSUMER-check.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion-LINKER_CONSUMER-check.cmake
new file mode 100644
index 0000000..99f5aa3
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion-LINKER_CONSUMER-check.cmake
@@ -0,0 +1,3 @@
+
+set(reference_file "LINKER.txt")
+include ("${CMAKE_CURRENT_LIST_DIR}/LINKER_expansion-validation.cmake")
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion-LINKER_SHELL-check.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion-LINKER_SHELL-check.cmake
new file mode 100644
index 0000000..99f5aa3
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion-LINKER_SHELL-check.cmake
@@ -0,0 +1,3 @@
+
+set(reference_file "LINKER.txt")
+include ("${CMAKE_CURRENT_LIST_DIR}/LINKER_expansion-validation.cmake")
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion-validation.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion-validation.cmake
new file mode 100644
index 0000000..27adde6
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion-validation.cmake
@@ -0,0 +1,15 @@
+
+if (actual_stdout MATCHES "LINKER:" AND NOT linker_prefix_expected)
+  set (RunCMake_TEST_FAILED "LINKER: prefix was not expanded.")
+  return()
+endif()
+
+if (NOT EXISTS "${RunCMake_TEST_BINARY_DIR}/${reference_file}")
+  set (RunCMake_TEST_FAILED "${RunCMake_TEST_BINARY_DIR}/${reference_file}: Reference file not found.")
+  return()
+endif()
+file(READ "${RunCMake_TEST_BINARY_DIR}/${reference_file}" linker_flag)
+
+if (NOT actual_stdout MATCHES "${linker_flag}")
+  set (RunCMake_TEST_FAILED "LINKER: was not expanded correctly.")
+endif()
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion.cmake
new file mode 100644
index 0000000..f71efbd
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion.cmake
@@ -0,0 +1,63 @@
+
+enable_language(C)
+
+set(cfg_dir)
+get_property(_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(_isMultiConfig)
+  set(cfg_dir /Debug)
+endif()
+set(DUMP_EXE "${CMAKE_CURRENT_BINARY_DIR}${cfg_dir}/dump${CMAKE_EXECUTABLE_SUFFIX}")
+
+add_executable(dump dump.c)
+
+# ensure no temp file nor response file will be used
+set(CMAKE_C_USE_RESPONSE_FILE_FOR_LIBRARIES 0)
+string(REPLACE "${CMAKE_START_TEMP_FILE}" "" CMAKE_C_CREATE_SHARED_LIBRARY "${CMAKE_C_CREATE_SHARED_LIBRARY}")
+string(REPLACE "${CMAKE_END_TEMP_FILE}" "" CMAKE_C_CREATE_SHARED_LIBRARY "${CMAKE_C_CREATE_SHARED_LIBRARY}")
+
+function (add_test_library target_name)
+  add_library(${target_name} SHARED LinkOptionsLib.c)
+
+  # use LAUNCH facility to dump linker command
+  set_property(TARGET ${target_name} PROPERTY RULE_LAUNCH_LINK "\"${DUMP_EXE}\"")
+
+  add_dependencies(${target_name} dump)
+endfunction()
+
+# Use LINKER alone
+add_test_library(linker)
+target_link_libraries(linker PRIVATE "LINKER:-foo,bar")
+
+# Use LINKER with SHELL
+add_test_library(linker_shell)
+target_link_libraries(linker_shell PRIVATE "LINKER:SHELL:-foo bar")
+
+# Propagate LINKER
+add_library(linker_interface INTERFACE)
+target_link_libraries(linker_interface INTERFACE "LINKER:-foo,bar")
+add_test_library(linker_consumer)
+target_link_libraries(linker_consumer PRIVATE linker_interface)
+
+# generate reference for LINKER flag
+if (CMAKE_C_LINKER_WRAPPER_FLAG)
+  set(linker_flag ${CMAKE_C_LINKER_WRAPPER_FLAG})
+  list(GET linker_flag -1 linker_space)
+  if (linker_space STREQUAL " ")
+    list(REMOVE_AT linker_flag -1)
+  else()
+    set(linker_space)
+  endif()
+  list (JOIN linker_flag " " linker_flag)
+  if (CMAKE_C_LINKER_WRAPPER_FLAG_SEP)
+    set(linker_sep "${CMAKE_C_LINKER_WRAPPER_FLAG_SEP}")
+
+    string (APPEND  linker_flag "${linker_space}" "-foo${linker_sep}bar")
+  else()
+    set(linker_prefix "${linker_flag}${linker_space}")
+
+    set (linker_flag "${linker_prefix}-foo ${linker_prefix}bar")
+  endif()
+else()
+  set(linker_flag "-foo bar")
+endif()
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/LINKER.txt" "${linker_flag}")
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LinkOptionsLib.c b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LinkOptionsLib.c
new file mode 100644
index 0000000..9bbd24c
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LinkOptionsLib.c
@@ -0,0 +1,7 @@
+#if defined(_WIN32)
+__declspec(dllexport)
+#endif
+  int flags_lib(void)
+{
+  return 0;
+}
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/RunCMakeTest.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/RunCMakeTest.cmake
new file mode 100644
index 0000000..cabf2cc
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/RunCMakeTest.cmake
@@ -0,0 +1,22 @@
+
+include(RunCMake)
+
+macro(run_cmake_target test subtest target)
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${test}-build)
+  set(RunCMake_TEST_NO_CLEAN 1)
+  run_cmake_command(${test}-${subtest} ${CMAKE_COMMAND} --build . --target ${target} ${ARGN})
+
+  unset(RunCMake_TEST_BINARY_DIR)
+  unset(RunCMake_TEST_NO_CLEAN)
+endmacro()
+
+
+run_cmake(bad_SHELL_usage)
+
+if(RunCMake_GENERATOR MATCHES "(Ninja|Makefile)")
+  run_cmake(LINKER_expansion)
+
+  run_cmake_target(LINKER_expansion LINKER linker)
+  run_cmake_target(LINKER_expansion LINKER_SHELL linker_shell)
+  run_cmake_target(LINKER_expansion LINKER_CONSUMER linker_consumer)
+endif()
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/bad_SHELL_usage-result.txt b/Tests/RunCMake/target_link_libraries-LINKER-prefix/bad_SHELL_usage-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/bad_SHELL_usage-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/bad_SHELL_usage-stderr.txt b/Tests/RunCMake/target_link_libraries-LINKER-prefix/bad_SHELL_usage-stderr.txt
new file mode 100644
index 0000000..d6a298d
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/bad_SHELL_usage-stderr.txt
@@ -0,0 +1,4 @@
+CMake Error at bad_SHELL_usage.cmake:[0-9]+ \(add_library\):
+  'SHELL:' prefix is not supported as part of 'LINKER:' arguments.
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/bad_SHELL_usage.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/bad_SHELL_usage.cmake
new file mode 100644
index 0000000..e25bef6
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/bad_SHELL_usage.cmake
@@ -0,0 +1,5 @@
+
+enable_language(C)
+
+add_library(example SHARED LinkOptionsLib.c)
+target_link_libraries(example PRIVATE "LINKER:-foo,SHELL:-bar")
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/dump.c b/Tests/RunCMake/target_link_libraries-LINKER-prefix/dump.c
new file mode 100644
index 0000000..8baa313
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/dump.c
@@ -0,0 +1,13 @@
+
+#include "stdio.h"
+
+int main(int argc, char* argv[])
+{
+  int i;
+
+  for (i = 1; i < argc; i++)
+    printf("%s ", argv[i]);
+  printf("\n");
+
+  return 0;
+}
diff --git a/Tests/RunCMake/while/CMP0130-OLD-stderr.txt b/Tests/RunCMake/while/CMP0130-OLD-stderr.txt
new file mode 100644
index 0000000..e673966
--- /dev/null
+++ b/Tests/RunCMake/while/CMP0130-OLD-stderr.txt
@@ -0,0 +1,10 @@
+^CMake Deprecation Warning at CMP0130-OLD\.cmake:[0-9]+ \(cmake_policy\):
+  The OLD behavior for policy CMP0130 will be removed from a future version
+  of CMake\.
+
+  The cmake-policies\(7\) manual explains that the OLD behaviors of all
+  policies are deprecated and that a policy should be set to OLD only under
+  specific short-term circumstances\.  Projects should be ported to the NEW
+  behavior and not rely on setting a policy to OLD\.
+Call Stack \(most recent call first\):
+  CMakeLists\.txt:[0-9]+ \(include\)$
diff --git a/Utilities/Doxygen/CMakeLists.txt b/Utilities/Doxygen/CMakeLists.txt
index 111bd6f..64f9a3f 100644
--- a/Utilities/Doxygen/CMakeLists.txt
+++ b/Utilities/Doxygen/CMakeLists.txt
@@ -3,7 +3,7 @@
 
 if(NOT CMake_SOURCE_DIR)
   set(CMakeDeveloperReference_STANDALONE 1)
-  cmake_minimum_required(VERSION 3.13...3.29 FATAL_ERROR)
+  cmake_minimum_required(VERSION 3.13...3.30 FATAL_ERROR)
   get_filename_component(tmp "${CMAKE_CURRENT_SOURCE_DIR}" PATH)
   get_filename_component(CMake_SOURCE_DIR "${tmp}" PATH)
   include(${CMake_SOURCE_DIR}/Modules/CTestUseLaunchers.cmake)
diff --git a/Utilities/Sphinx/CMakeLists.txt b/Utilities/Sphinx/CMakeLists.txt
index ed7b631..a919b91 100644
--- a/Utilities/Sphinx/CMakeLists.txt
+++ b/Utilities/Sphinx/CMakeLists.txt
@@ -3,7 +3,7 @@
 
 if(NOT CMake_SOURCE_DIR)
   set(CMakeHelp_STANDALONE 1)
-  cmake_minimum_required(VERSION 3.13...3.29 FATAL_ERROR)
+  cmake_minimum_required(VERSION 3.13...3.30 FATAL_ERROR)
   get_filename_component(tmp "${CMAKE_CURRENT_SOURCE_DIR}" PATH)
   get_filename_component(CMake_SOURCE_DIR "${tmp}" PATH)
   include(${CMake_SOURCE_DIR}/Modules/CTestUseLaunchers.cmake)