Merge branch 'release-3.31'
diff --git a/.clang-tidy b/.clang-tidy
index c85fd67..35503d4 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -8,7 +8,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-return-const-ref-from-parameter,\
@@ -30,6 +29,7 @@
 -misc-use-internal-linkage,\
 modernize-*,\
 -modernize-avoid-c-arrays,\
+-modernize-concat-nested-namespaces,\
 -modernize-macro-to-enum,\
 -modernize-return-braced-init-list,\
 -modernize-type-traits,\
@@ -45,7 +45,6 @@
 -performance-unnecessary-value-param,\
 readability-*,\
 -readability-avoid-nested-conditional-operator,\
--readability-avoid-return-with-void-value,\
 -readability-avoid-unconditional-preprocessor-if,\
 -readability-convert-member-functions-to-static,\
 -readability-enum-initial-value,\
@@ -59,11 +58,6 @@
 -readability-make-member-function-const,\
 -readability-math-missing-parentheses,\
 -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 222ac91..b4a2425 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -736,6 +736,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/add_test.rst b/Help/command/add_test.rst
index 2a3c759..dbaa4fb 100644
--- a/Help/command/add_test.rst
+++ b/Help/command/add_test.rst
@@ -16,7 +16,7 @@
 
 CMake only generates tests if the :command:`enable_testing` command has been
 invoked.  The :module:`CTest` module invokes ``enable_testing`` automatically
-unless ``BUILD_TESTING`` is set to ``OFF``.
+unless :variable:`BUILD_TESTING` is set to ``OFF``.
 
 Tests added with the ``add_test(NAME)`` signature support using
 :manual:`generator expressions <cmake-generator-expressions(7)>`
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/enable_testing.rst b/Help/command/enable_testing.rst
index 3ac1a19..7bae5c0 100644
--- a/Help/command/enable_testing.rst
+++ b/Help/command/enable_testing.rst
@@ -9,12 +9,12 @@
 
 Enables testing for this directory and below.
 
-This command should be in the source directory root
-because ctest expects to find a test file in the build
-directory root.
+This command should be in the top-level source directory because
+:manual:`ctest(1)` expects to find a test file in the top-level
+build directory.
 
 This command is automatically invoked when the :module:`CTest`
-module is included, except if the ``BUILD_TESTING`` option is
-turned off.
+module is included, except if the :variable:`BUILD_TESTING`
+option is turned off.
 
 See also the :command:`add_test` command.
diff --git a/Help/command/find_package.rst b/Help/command/find_package.rst
index c26076b..d99dbd5 100644
--- a/Help/command/find_package.rst
+++ b/Help/command/find_package.rst
@@ -507,13 +507,12 @@
 configuration file found is used, even if a newer version of the package
 resides later in the list of search paths.
 
-For search paths which contain ``<name>*``, the order among matching paths
-is unspecified unless the :variable:`CMAKE_FIND_PACKAGE_SORT_ORDER` variable
-is set.  This variable, along with the
-:variable:`CMAKE_FIND_PACKAGE_SORT_DIRECTION` variable, determines the order
-in which CMake considers paths that match a single search path containing
-``<name>*``.  For example, if the file system contains the package
-configuration files
+For search paths which contain glob expressions (``*``), the order in which
+directories matching the glob are searched is unspecified unless the
+:variable:`CMAKE_FIND_PACKAGE_SORT_ORDER` variable is set.  This variable,
+along with the :variable:`CMAKE_FIND_PACKAGE_SORT_DIRECTION` variable,
+determines the order in which CMake considers glob matches.  For example, if
+the file system contains the package configuration files
 
 ::
 
@@ -543,6 +542,14 @@
    Added the ``CMAKE_FIND_USE_<CATEGORY>`` variables to globally disable
    various search locations.
 
+.. versionchanged:: 3.32
+   The variables :variable:`CMAKE_FIND_PACKAGE_SORT_ORDER` and
+   :variable:`CMAKE_FIND_PACKAGE_SORT_DIRECTION` now also control the order
+   in which ``find_package`` searches directories matching the glob expression
+   in the search paths ``<prefix>/<name>.framework/Versions/*/Resources/``
+   and ``<prefix>/<name>.framework/Versions/*/Resources/CMake``.  In previous
+   versions of CMake, this order was unspecified.
+
 .. include:: FIND_XXX_ROOT.txt
 .. include:: FIND_XXX_ORDER.txt
 
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/command/try_compile.rst b/Help/command/try_compile.rst
index 30ab41a..93713da 100644
--- a/Help/command/try_compile.rst
+++ b/Help/command/try_compile.rst
@@ -336,6 +336,10 @@
 The current settings of :policy:`CMP0065` and :policy:`CMP0083` are propagated
 through to the generated test project.
 
+.. versionadded:: 3.32
+  The current setting of :policy:`CMP0181` policy is propagated through to the
+  generated test project.
+
 Set variable :variable:`CMAKE_TRY_COMPILE_CONFIGURATION` to choose a build
 configuration:
 
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-policies.7.rst b/Help/manual/cmake-policies.7.rst
index c62fb48..d32c737 100644
--- a/Help/manual/cmake-policies.7.rst
+++ b/Help/manual/cmake-policies.7.rst
@@ -51,6 +51,14 @@
 to determine whether to report an error on use of deprecated macros or
 functions.
 
+Policies Introduced by CMake 3.32
+=================================
+
+.. toctree::
+   :maxdepth: 1
+
+   CMP0181: Link command-line fragment variables are parsed and re-quoted. </policy/CMP0181>
+
 Policies Introduced by CMake 3.31
 =================================
 
diff --git a/Help/manual/cmake-properties.7.rst b/Help/manual/cmake-properties.7.rst
index 10dbe10..8adc64a 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
@@ -583,6 +584,7 @@
    /prop_sf/UNITY_GROUP
    /prop_sf/VS_COPY_TO_OUT_DIR
    /prop_sf/VS_CSHARP_tagname
+   /prop_sf/VS_CUSTOM_COMMAND_DISABLE_PARALLEL_BUILD
    /prop_sf/VS_DEPLOYMENT_CONTENT
    /prop_sf/VS_DEPLOYMENT_LOCATION
    /prop_sf/VS_INCLUDE_IN_VSIX
diff --git a/Help/manual/cmake-variables.7.rst b/Help/manual/cmake-variables.7.rst
index 4c27bd5..0c0f1c5 100644
--- a/Help/manual/cmake-variables.7.rst
+++ b/Help/manual/cmake-variables.7.rst
@@ -175,6 +175,7 @@
    :maxdepth: 1
 
    /variable/BUILD_SHARED_LIBS
+   /variable/BUILD_TESTING
    /variable/CMAKE_ABSOLUTE_DESTINATION_FILES
    /variable/CMAKE_ADD_CUSTOM_COMMAND_DEPENDS_EXPLICIT_ONLY
    /variable/CMAKE_APPBUNDLE_PATH
diff --git a/Help/manual/ctest.1.rst b/Help/manual/ctest.1.rst
index 9281339..4d60ca4 100644
--- a/Help/manual/ctest.1.rst
+++ b/Help/manual/ctest.1.rst
@@ -353,18 +353,32 @@
  This allows the user to widen the output to avoid clipping the test
  name which can be very annoying.
 
-.. option:: --interactive-debug-mode [0|1]
+.. option:: --interactive-debug-mode <0|1>
 
- Set the interactive mode to ``0`` or ``1``.
+ Disable (``0``) or enable (``1``) interactive debug mode.
 
  This option causes CTest to run tests in either an interactive mode
  or a non-interactive mode.  In dashboard mode (``Experimental``, ``Nightly``,
  ``Continuous``), the default is non-interactive.  In non-interactive mode,
  the environment variable :envvar:`DASHBOARD_TEST_FROM_CTEST` is set.
 
- Prior to CMake 3.11, interactive mode on Windows allowed system debug
- popup windows to appear.  Now, due to CTest's use of ``libuv`` to launch
- test processes, all system debug popup windows are always blocked.
+ Interactive Mode allows Windows Error Reporting (WER) to show debug popup
+ windows and to create core dumps.  To enable core dumps in tests,
+ use interactive mode, and follow the Windows documentation
+ on `Collecting User-Mode Dumps`_.
+
+ .. versionchanged:: 3.32
+   Windows Error Reporting (WER) is enabled in interactive mode, so
+   test processes may show debug popup windows and create core dumps.
+   This was made possible by updates to ``libuv``.
+
+ .. versionchanged:: 3.11
+   Windows Error Reporting (WER) is disabled in both interactive and
+   non-interactive modes, so test processes do not show popup windows
+   or create core dumps.  This is due to launching test processes with
+   ``libuv``.
+
+.. _`Collecting User-Mode Dumps`: https://learn.microsoft.com/en-us/windows/win32/wer/collecting-user-mode-dumps
 
 .. option:: --no-label-summary
 
@@ -421,11 +435,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/policy/CMP0147.rst b/Help/policy/CMP0147.rst
index fd5dc5f..1790678 100644
--- a/Help/policy/CMP0147.rst
+++ b/Help/policy/CMP0147.rst
@@ -10,7 +10,8 @@
 a ``BuildInParallel`` setting to custom commands in ``.vcxproj`` files.
 This policy provides compatibility for projects that have not been updated
 to expect this, e.g., because their custom commands were accidentally
-relying on serial execution by MSBuild.
+relying on serial execution by MSBuild. To control this behavior in a more
+precise way, refer to :prop_sf:`VS_CUSTOM_COMMAND_DISABLE_PARALLEL_BUILD`.
 
 The ``OLD`` behavior for this policy is to not add ``BuildInParallel``.
 The ``NEW`` behavior for this policy is to add ``BuildInParallel`` for
diff --git a/Help/policy/CMP0181.rst b/Help/policy/CMP0181.rst
new file mode 100644
index 0000000..18fb8db
--- /dev/null
+++ b/Help/policy/CMP0181.rst
@@ -0,0 +1,41 @@
+CMP0181
+-------
+
+.. versionadded:: 3.32
+
+The :variable:`CMAKE_EXE_LINKER_FLAGS`,
+:variable:`CMAKE_EXE_LINKER_FLAGS_<CONFIG>`,
+:variable:`CMAKE_SHARED_LINKER_FLAGS`,
+:variable:`CMAKE_SHARED_LINKER_FLAGS_<CONFIG>`,
+:variable:`CMAKE_MODULE_LINKER_FLAGS`,
+and :variable:`CMAKE_MODULE_LINKER_FLAGS_<CONFIG>` variables are parsed and
+re-quoted and support the ``LINKER:`` prefix.
+
+CMake 3.31 and below use the content of these variables as is.
+
+CMake 3.32 and above parse the content of these variables and manage the
+escaping of special characters. Moreover, the ``LINKER:`` prefix is now
+recognized and expanded.
+
+The ``OLD`` behavior of this policy is to consume the content of the
+:variable:`CMAKE_EXE_LINKER_FLAGS`,
+:variable:`CMAKE_EXE_LINKER_FLAGS_<CONFIG>`,
+:variable:`CMAKE_SHARED_LINKER_FLAGS`,
+:variable:`CMAKE_SHARED_LINKER_FLAGS_<CONFIG>`,
+:variable:`CMAKE_MODULE_LINKER_FLAGS`,
+and :variable:`CMAKE_MODULE_LINKER_FLAGS_<CONFIG>` variables as is.
+
+The ``NEW`` behavior of this policy is to parse and re-quote the content of the
+:variable:`CMAKE_EXE_LINKER_FLAGS`,
+:variable:`CMAKE_EXE_LINKER_FLAGS_<CONFIG>`,
+:variable:`CMAKE_SHARED_LINKER_FLAGS`,
+:variable:`CMAKE_SHARED_LINKER_FLAGS_<CONFIG>`,
+:variable:`CMAKE_MODULE_LINKER_FLAGS`,
+and :variable:`CMAKE_MODULE_LINKER_FLAGS_<CONFIG>` variables as well as to
+expand the ``LINKER:`` prefix.
+
+.. |INTRODUCED_IN_CMAKE_VERSION| replace:: 3.32
+.. |WARNS_OR_DOES_NOT_WARN| replace:: does *not* warn
+.. include:: STANDARD_ADVICE.txt
+
+.. include:: DEPRECATED.txt
diff --git a/Help/prop_sf/VS_CUSTOM_COMMAND_DISABLE_PARALLEL_BUILD.rst b/Help/prop_sf/VS_CUSTOM_COMMAND_DISABLE_PARALLEL_BUILD.rst
new file mode 100644
index 0000000..f8bb93d
--- /dev/null
+++ b/Help/prop_sf/VS_CUSTOM_COMMAND_DISABLE_PARALLEL_BUILD.rst
@@ -0,0 +1,9 @@
+VS_CUSTOM_COMMAND_DISABLE_PARALLEL_BUILD
+----------------------------------------
+
+.. versionadded:: 3.32
+
+A boolean property that disables parallel building for the source file in
+Visual Studio if it is built via :command:`add_custom_command` and is the
+``MAIN_DEPENDENCY`` input for the custom command.
+See policy :policy:`CMP0147`.
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/CMAKE_TYPE_LINKER_FLAGS-LINKER-prefix-support.rst b/Help/release/dev/CMAKE_TYPE_LINKER_FLAGS-LINKER-prefix-support.rst
new file mode 100644
index 0000000..3598dc5
--- /dev/null
+++ b/Help/release/dev/CMAKE_TYPE_LINKER_FLAGS-LINKER-prefix-support.rst
@@ -0,0 +1,13 @@
+CMAKE_TYPE_LINKER_FLAGS-LINKER-prefix-support
+---------------------------------------------
+
+* The :variable:`CMAKE_EXE_LINKER_FLAGS`,
+  :variable:`CMAKE_EXE_LINKER_FLAGS_<CONFIG>`,
+  :variable:`CMAKE_SHARED_LINKER_FLAGS`,
+  :variable:`CMAKE_SHARED_LINKER_FLAGS_<CONFIG>`,
+  :variable:`CMAKE_MODULE_LINKER_FLAGS`,
+  and :variable:`CMAKE_MODULE_LINKER_FLAGS_<CONFIG>` variables learned to
+  support the ``LINKER:`` prefix.
+
+  This support implies to parse and re-quote the content of these variables.
+  This parsing is controlled by :policy:`CMP0181` policy.
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/apple-compiler-selection.rst b/Help/release/dev/apple-compiler-selection.rst
new file mode 100644
index 0000000..79727df
--- /dev/null
+++ b/Help/release/dev/apple-compiler-selection.rst
@@ -0,0 +1,19 @@
+apple-compiler-selection
+------------------------
+
+* Builds targeting macOS no longer choose any SDK or pass an ``-isysroot``
+  flag to the compiler by default.  Instead, compilers are expected to
+  choose a default macOS SDK on their own.  In order to use a compiler that
+  does not do this, users must now specify ``-DCMAKE_OSX_SYSROOT=macosx``
+  when configuring their build.
+
+* On macOS with :ref:`Ninja Generators` and :ref:`Makefile Generators`, when
+  a compiler is found in ``/usr/bin``, it is now used as-is and is no longer
+  mapped to the corresponding compiler inside Xcode.  The mapping was
+  introduced by CMake 3.2 to allow build trees to continue to work with their
+  original compiler even when ``xcode-select`` switches to a different
+  Xcode installation.  However, the compilers inside Xcode cannot be used
+  without explicit ``-isysroot`` flags and are therefore not suitable for
+  passing to arbitrary third-party build systems.  Furthermore, the mapping
+  behavior can override user-specified compiler paths.  Therefore, this
+  behavior has been reverted.
diff --git a/Help/release/dev/ctest-crash-handling.rst b/Help/release/dev/ctest-crash-handling.rst
new file mode 100644
index 0000000..266706f
--- /dev/null
+++ b/Help/release/dev/ctest-crash-handling.rst
@@ -0,0 +1,7 @@
+ctest-crash-handling
+--------------------
+
+* The :option:`ctest --interactive-debug-mode` option on Windows
+  now enables Windows Error Reporting by default in test processes,
+  allowing them to creating debug popup windows and core dumps.
+  This restores behavior previously removed by CMake 3.11.
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/dev/vs-custom-command-disable-parallel-build.rst b/Help/release/dev/vs-custom-command-disable-parallel-build.rst
new file mode 100644
index 0000000..1867a4a
--- /dev/null
+++ b/Help/release/dev/vs-custom-command-disable-parallel-build.rst
@@ -0,0 +1,6 @@
+vs-custom-command-disable-parallel-build
+----------------------------------------
+
+* The :prop_sf:`VS_CUSTOM_COMMAND_DISABLE_PARALLEL_BUILD` source file property
+  was added to tell :ref:`Visual Studio Generators` not to run a custom command
+  in parallel.
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/BUILD_TESTING.rst b/Help/variable/BUILD_TESTING.rst
new file mode 100644
index 0000000..4d460b2
--- /dev/null
+++ b/Help/variable/BUILD_TESTING.rst
@@ -0,0 +1,27 @@
+BUILD_TESTING
+-------------
+
+Control whether the :module:`CTest` module invokes :command:`enable_testing`.
+
+The :module:`CTest` module, when loaded by ``include(CTest)``,
+runs code of the form:
+
+.. code-block:: cmake
+
+  option(BUILD_TESTING "..." ON)
+  if (BUILD_TESTING)
+     # ...
+     enable_testing()
+     # ...
+  endif()
+
+This creates a ``BUILD_TESTING`` option that controls whether the
+:command:`enable_testing` command is invoked to enable generation
+of tests to run using :manual:`ctest(1)`.  See the :command:`add_test`
+command to create tests.
+
+.. note::
+
+  Call ``include(CTest)`` in the top-level source directory since
+  :manual:`ctest(1)` expects to find a test file in the top-level
+  build directory.
diff --git a/Help/variable/CMAKE_EXE_LINKER_FLAGS.rst b/Help/variable/CMAKE_EXE_LINKER_FLAGS.rst
index 9e108f8..a978d11 100644
--- a/Help/variable/CMAKE_EXE_LINKER_FLAGS.rst
+++ b/Help/variable/CMAKE_EXE_LINKER_FLAGS.rst
@@ -4,3 +4,5 @@
 Linker flags to be used to create executables.
 
 These flags will be used by the linker when creating an executable.
+
+.. include:: ../variable/LINKER_FLAGS.txt
diff --git a/Help/variable/CMAKE_EXE_LINKER_FLAGS_CONFIG.rst b/Help/variable/CMAKE_EXE_LINKER_FLAGS_CONFIG.rst
index 0cd8113..1d74077 100644
--- a/Help/variable/CMAKE_EXE_LINKER_FLAGS_CONFIG.rst
+++ b/Help/variable/CMAKE_EXE_LINKER_FLAGS_CONFIG.rst
@@ -5,3 +5,5 @@
 
 Same as ``CMAKE_C_FLAGS_*`` but used by the linker when creating
 executables.
+
+.. include:: ../variable/LINKER_FLAGS.txt
diff --git a/Help/variable/CMAKE_MODULE_LINKER_FLAGS.rst b/Help/variable/CMAKE_MODULE_LINKER_FLAGS.rst
index 6372bbd..eed6b65 100644
--- a/Help/variable/CMAKE_MODULE_LINKER_FLAGS.rst
+++ b/Help/variable/CMAKE_MODULE_LINKER_FLAGS.rst
@@ -4,3 +4,5 @@
 Linker flags to be used to create modules.
 
 These flags will be used by the linker when creating a module.
+
+.. include:: ../variable/LINKER_FLAGS.txt
diff --git a/Help/variable/CMAKE_MODULE_LINKER_FLAGS_CONFIG.rst b/Help/variable/CMAKE_MODULE_LINKER_FLAGS_CONFIG.rst
index 393263e..fd0769c 100644
--- a/Help/variable/CMAKE_MODULE_LINKER_FLAGS_CONFIG.rst
+++ b/Help/variable/CMAKE_MODULE_LINKER_FLAGS_CONFIG.rst
@@ -4,3 +4,5 @@
 Flags to be used when linking a module.
 
 Same as ``CMAKE_C_FLAGS_*`` but used by the linker when creating modules.
+
+.. include:: ../variable/LINKER_FLAGS.txt
diff --git a/Help/variable/CMAKE_SHARED_LINKER_FLAGS.rst b/Help/variable/CMAKE_SHARED_LINKER_FLAGS.rst
index fce950c..45748ff 100644
--- a/Help/variable/CMAKE_SHARED_LINKER_FLAGS.rst
+++ b/Help/variable/CMAKE_SHARED_LINKER_FLAGS.rst
@@ -4,3 +4,5 @@
 Linker flags to be used to create shared libraries.
 
 These flags will be used by the linker when creating a shared library.
+
+.. include:: ../variable/LINKER_FLAGS.txt
diff --git a/Help/variable/CMAKE_SHARED_LINKER_FLAGS_CONFIG.rst b/Help/variable/CMAKE_SHARED_LINKER_FLAGS_CONFIG.rst
index 4bf87a0..b968820 100644
--- a/Help/variable/CMAKE_SHARED_LINKER_FLAGS_CONFIG.rst
+++ b/Help/variable/CMAKE_SHARED_LINKER_FLAGS_CONFIG.rst
@@ -5,3 +5,5 @@
 
 Same as ``CMAKE_C_FLAGS_*`` but used by the linker when creating shared
 libraries.
+
+.. include:: ../variable/LINKER_FLAGS.txt
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/Help/variable/LINKER_FLAGS.txt b/Help/variable/LINKER_FLAGS.txt
new file mode 100644
index 0000000..7b0630a
--- /dev/null
+++ b/Help/variable/LINKER_FLAGS.txt
@@ -0,0 +1,5 @@
+
+.. include:: ../command/LINK_LIBRARIES_LINKER.txt
+
+This support implies to parse and re-quote the content of the variable. See
+policy :policy:`CMP0181`.
diff --git a/Modules/CMakeDetermineCompiler.cmake b/Modules/CMakeDetermineCompiler.cmake
index fc0b714..6036897 100644
--- a/Modules/CMakeDetermineCompiler.cmake
+++ b/Modules/CMakeDetermineCompiler.cmake
@@ -82,34 +82,6 @@
   endif()
   unset(_${lang}_COMPILER_HINTS)
   unset(_languages)
-
-  # Look for a make tool provided by Xcode
-  if(CMAKE_HOST_APPLE)
-    macro(_query_xcrun compiler_name result_var_keyword result_var)
-      if(NOT "x${result_var_keyword}" STREQUAL "xRESULT_VAR")
-        message(FATAL_ERROR "Bad arguments to macro")
-      endif()
-      execute_process(COMMAND xcrun --find ${compiler_name}
-        OUTPUT_VARIABLE _xcrun_out OUTPUT_STRIP_TRAILING_WHITESPACE
-        ERROR_VARIABLE _xcrun_err)
-      set("${result_var}" "${_xcrun_out}")
-    endmacro()
-
-    set(xcrun_result)
-    if (CMAKE_${lang}_COMPILER MATCHES "^/usr/bin/(.+)$")
-      _query_xcrun("${CMAKE_MATCH_1}" RESULT_VAR xcrun_result)
-    elseif (CMAKE_${lang}_COMPILER STREQUAL "CMAKE_${lang}_COMPILER-NOTFOUND")
-      foreach(comp IN LISTS CMAKE_${lang}_COMPILER_LIST)
-        _query_xcrun("${comp}" RESULT_VAR xcrun_result)
-        if(xcrun_result)
-          break()
-        endif()
-      endforeach()
-    endif()
-    if (xcrun_result)
-      set_property(CACHE CMAKE_${lang}_COMPILER PROPERTY VALUE "${xcrun_result}")
-    endif()
-  endif()
 endmacro()
 
 macro(_cmake_find_compiler_path lang)
diff --git a/Modules/CMakeDetermineCompilerABI.cmake b/Modules/CMakeDetermineCompilerABI.cmake
index 4a75e25..806f0b7 100644
--- a/Modules/CMakeDetermineCompilerABI.cmake
+++ b/Modules/CMakeDetermineCompilerABI.cmake
@@ -52,14 +52,21 @@
 
     __TestCompiler_setTryCompileTargetType()
 
-    # Avoid failing ABI detection on warnings.
+    # Avoid failing ABI detection caused by non-functionally relevant
+    # compiler arguments
     if(CMAKE_TRY_COMPILE_CONFIGURATION)
       string(TOUPPER "${CMAKE_TRY_COMPILE_CONFIGURATION}" _tc_config)
     else()
       set(_tc_config "DEBUG")
     endif()
     foreach(v CMAKE_${lang}_FLAGS CMAKE_${lang}_FLAGS_${_tc_config})
+      # Avoid failing ABI detection on warnings.
       string(REGEX REPLACE "(^| )-Werror([= ][^-][^ ]*)?( |$)" " " ${v} "${${v}}")
+      # Avoid passing of "-pipe" when determining the compiler internals. With
+      # "-pipe" GCC will use pipes to pass data between the involved
+      # executables.  This may lead to issues when their stderr output (which
+      # contains the relevant compiler internals) becomes interweaved.
+      string(REGEX REPLACE "(^| )-pipe( |$)" " " ${v} "${${v}}")
     endforeach()
 
     # Save the current LC_ALL, LC_MESSAGES, and LANG environment variables
diff --git a/Modules/CMakeUnixFindMake.cmake b/Modules/CMakeUnixFindMake.cmake
index 1165656..58dedee 100644
--- a/Modules/CMakeUnixFindMake.cmake
+++ b/Modules/CMakeUnixFindMake.cmake
@@ -4,13 +4,3 @@
 
 find_program(CMAKE_MAKE_PROGRAM NAMES gmake make smake)
 mark_as_advanced(CMAKE_MAKE_PROGRAM)
-
-# Look for a make tool provided by Xcode
-if(NOT CMAKE_MAKE_PROGRAM AND CMAKE_HOST_APPLE)
-  execute_process(COMMAND xcrun --find make
-    OUTPUT_VARIABLE _xcrun_out OUTPUT_STRIP_TRAILING_WHITESPACE
-    ERROR_VARIABLE _xcrun_err)
-  if(_xcrun_out)
-    set_property(CACHE CMAKE_MAKE_PROGRAM PROPERTY VALUE "${_xcrun_out}")
-  endif()
-endif()
diff --git a/Modules/CTest.cmake b/Modules/CTest.cmake
index 16283d6..6829d66 100644
--- a/Modules/CTest.cmake
+++ b/Modules/CTest.cmake
@@ -8,15 +8,24 @@
 Configure a project for testing with CTest/CDash
 
 Include this module in the top CMakeLists.txt file of a project to
-enable testing with CTest and dashboard submissions to CDash::
+enable testing with CTest and dashboard submissions to CDash:
+
+.. code-block:: cmake
 
   project(MyProject)
   ...
   include(CTest)
 
-The module automatically creates a ``BUILD_TESTING`` option that selects
-whether to enable testing support (``ON`` by default).  After including
-the module, use code like::
+The module automatically creates the following variables:
+
+:variable:`BUILD_TESTING`
+
+  Option selecting whether ``include(CTest)`` calls :command:`enable_testing`.
+  The option is ``ON`` by default when created by the module.
+
+After including the module, use code like:
+
+.. code-block:: cmake
 
   if(BUILD_TESTING)
     # ... CMake code to create tests ...
@@ -25,7 +34,9 @@
 to creating tests when testing is enabled.
 
 To enable submissions to a CDash server, create a ``CTestConfig.cmake``
-file at the top of the project with content such as::
+file at the top of the project with content such as:
+
+.. code-block:: cmake
 
   set(CTEST_NIGHTLY_START_TIME "01:00:00 UTC")
   set(CTEST_SUBMIT_URL "http://my.cdash.org/submit.php?project=MyProject")
@@ -40,7 +51,9 @@
 context from the build log.  This generic approach works for all build
 tools, but does not give details about the command invocation that
 produced a given problem.  One may get more detailed reports by setting
-the :variable:`CTEST_USE_LAUNCHERS` variable::
+the :variable:`CTEST_USE_LAUNCHERS` variable:
+
+.. code-block:: cmake
 
   set(CTEST_USE_LAUNCHERS 1)
 
diff --git a/Modules/CheckTypeSize.cmake b/Modules/CheckTypeSize.cmake
index ee54d92..c2e131c 100644
--- a/Modules/CheckTypeSize.cmake
+++ b/Modules/CheckTypeSize.cmake
@@ -54,6 +54,7 @@
   ``LANGUAGE <language>``
     Use the ``<language>`` compiler to perform the check.
     Acceptable values are ``C`` and ``CXX``.
+    If not specified, it defaults to ``C``.
 
 Despite the name of the macro you may use it to check the size of more
 complex expressions, too.  To check e.g.  for the size of a struct
@@ -83,6 +84,62 @@
 
 ``CMAKE_EXTRA_INCLUDE_FILES``
   list of extra headers to include.
+
+Examples
+^^^^^^^^
+
+Consider the code:
+
+.. code-block:: cmake
+
+  include(CheckTypeSize)
+
+  # Check for size of long.
+  check_type_size(long SIZEOF_LONG)
+  message("HAVE_SIZEOF_LONG: ${HAVE_SIZEOF_LONG}")
+  message("SIZEOF_LONG: ${SIZEOF_LONG}")
+  message("SIZEOF_LONG_CODE: ${SIZEOF_LONG_CODE}")
+
+On a 64-bit architecture, the output may look something like this::
+
+  HAVE_SIZEOF_LONG: TRUE
+  SIZEOF_LONG: 8
+  SIZEOF_LONG_CODE: #define SIZEOF_LONG 8
+
+On Apple platforms, when :variable:`CMAKE_OSX_ARCHITECTURES` has multiple
+architectures, types may have architecture-dependent sizes.
+For example, with the code
+
+.. code-block:: cmake
+
+  include(CheckTypeSize)
+
+  check_type_size(long SIZEOF_LONG)
+  message("HAVE_SIZEOF_LONG: ${HAVE_SIZEOF_LONG}")
+  message("SIZEOF_LONG: ${SIZEOF_LONG}")
+  foreach(key IN LISTS SIZE_OF_LONG_KEYS)
+    message("key: ${key}")
+    message("value: ${SIZE_OF_LONG-${key}}")
+  endforeach()
+  message("SIZEOF_LONG_CODE:
+  ${SIZEOF_LONG_CODE}")
+
+the result may be::
+
+  HAVE_SIZEOF_LONG: TRUE
+  SIZEOF_LONG: 0
+  key: __i386
+  value: 4
+  key: __x86_64
+  value: 8
+  SIZEOF_LONG_CODE:
+  #if defined(__i386)
+  # define SIZE_OF_LONG 4
+  #elif defined(__x86_64)
+  # define SIZE_OF_LONG 8
+  #else
+  # error SIZE_OF_LONG unknown
+  #endif
 #]=======================================================================]
 
 include(CheckIncludeFile)
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/Platform/Darwin-Initialize.cmake b/Modules/Platform/Darwin-Initialize.cmake
index 1b9cece..1a47d9a 100644
--- a/Modules/Platform/Darwin-Initialize.cmake
+++ b/Modules/Platform/Darwin-Initialize.cmake
@@ -80,50 +80,8 @@
   set(_CMAKE_OSX_SYSROOT_DEFAULT "xros")
 elseif(CMAKE_SYSTEM_NAME STREQUAL watchOS)
   set(_CMAKE_OSX_SYSROOT_DEFAULT "watchos")
-elseif("${CMAKE_GENERATOR}" MATCHES Xcode
-       OR CMAKE_OSX_DEPLOYMENT_TARGET
-       OR CMAKE_OSX_ARCHITECTURES MATCHES "[^;]"
-       OR NOT EXISTS "/usr/include/sys/types.h")
-  # Find installed SDKs in either Xcode-4.3+ or pre-4.3 SDKs directory.
-  set(_CMAKE_OSX_SDKS_DIR "")
-  if(OSX_DEVELOPER_ROOT)
-    foreach(_d Platforms/MacOSX.platform/Developer/SDKs SDKs)
-      file(GLOB _CMAKE_OSX_SDKS ${OSX_DEVELOPER_ROOT}/${_d}/*)
-      if(_CMAKE_OSX_SDKS)
-        set(_CMAKE_OSX_SDKS_DIR ${OSX_DEVELOPER_ROOT}/${_d})
-        break()
-      endif()
-    endforeach()
-  endif()
-
-  if(_CMAKE_OSX_SDKS_DIR)
-    # Find the latest SDK as recommended by Apple (Technical Q&A QA1806)
-    set(_CMAKE_OSX_LATEST_SDK_VERSION "0.0")
-    file(GLOB _CMAKE_OSX_SDKS RELATIVE "${_CMAKE_OSX_SDKS_DIR}" "${_CMAKE_OSX_SDKS_DIR}/MacOSX*.sdk")
-    foreach(_SDK ${_CMAKE_OSX_SDKS})
-      if(IS_DIRECTORY "${_CMAKE_OSX_SDKS_DIR}/${_SDK}"
-         AND _SDK MATCHES "MacOSX([0-9]+\\.[0-9]+)[^/]*\\.sdk"
-         AND CMAKE_MATCH_1 VERSION_GREATER ${_CMAKE_OSX_LATEST_SDK_VERSION})
-        set(_CMAKE_OSX_LATEST_SDK_VERSION "${CMAKE_MATCH_1}")
-      endif()
-    endforeach()
-
-    if(NOT _CMAKE_OSX_LATEST_SDK_VERSION STREQUAL "0.0")
-      set(_CMAKE_OSX_SYSROOT_DEFAULT "${_CMAKE_OSX_SDKS_DIR}/MacOSX${_CMAKE_OSX_LATEST_SDK_VERSION}.sdk")
-    else()
-      message(WARNING "Could not find any valid SDKs in ${_CMAKE_OSX_SDKS_DIR}")
-    endif()
-
-    if(NOT CMAKE_CROSSCOMPILING AND NOT CMAKE_OSX_DEPLOYMENT_TARGET
-       AND (_CURRENT_OSX_VERSION VERSION_LESS _CMAKE_OSX_LATEST_SDK_VERSION
-            OR _CMAKE_OSX_LATEST_SDK_VERSION STREQUAL "0.0"))
-      set(CMAKE_OSX_DEPLOYMENT_TARGET ${_CURRENT_OSX_VERSION} CACHE STRING
-        "Minimum OS X version to target for deployment (at runtime); newer APIs weak linked. Set to empty string for default value." FORCE)
-    endif()
-  else()
-    # Assume developer files are in root (such as Xcode 4.5 command-line tools).
-    set(_CMAKE_OSX_SYSROOT_DEFAULT "")
-  endif()
+else()
+  set(_CMAKE_OSX_SYSROOT_DEFAULT "")
 endif()
 
 # Set cache variable - end user may change this during ccmake or cmake-gui configure.
@@ -244,7 +202,7 @@
     return() # Only apply to multi-arch
   endif()
 
-  if(CMAKE_OSX_SYSROOT STREQUAL "macosx")
+  if(NOT CMAKE_OSX_SYSROOT OR CMAKE_OSX_SYSROOT STREQUAL "macosx")
     # macOS doesn't have a simulator sdk / sysroot, so there is no need to handle per-sdk arches.
     return()
   endif()
@@ -256,7 +214,7 @@
     return()
   endif()
 
-  string(REPLACE "os" "simulator" _simulator_sdk ${CMAKE_OSX_SYSROOT})
+  string(REPLACE "os" "simulator" _simulator_sdk "${CMAKE_OSX_SYSROOT}")
   set(_sdks "${CMAKE_OSX_SYSROOT};${_simulator_sdk}")
   foreach(sdk ${_sdks})
     _apple_resolve_sdk_path(${sdk} _sdk_path)
@@ -302,25 +260,36 @@
 
 _apple_resolve_multi_arch_sysroots()
 
-# Transform CMAKE_OSX_SYSROOT to absolute path
-set(_CMAKE_OSX_SYSROOT_PATH "")
-if(CMAKE_OSX_SYSROOT)
-  if("x${CMAKE_OSX_SYSROOT}" MATCHES "/")
-    # This is a path to the SDK.  Make sure it exists.
-    if(NOT IS_DIRECTORY "${CMAKE_OSX_SYSROOT}")
-      message(WARNING "Ignoring CMAKE_OSX_SYSROOT value:\n ${CMAKE_OSX_SYSROOT}\n"
-        "because the directory does not exist.")
-      set(CMAKE_OSX_SYSROOT "")
-    endif()
-    set(_CMAKE_OSX_SYSROOT_PATH "${CMAKE_OSX_SYSROOT}")
-  else()
-    _apple_resolve_sdk_path(${CMAKE_OSX_SYSROOT} _sdk_path)
-    if(IS_DIRECTORY "${_sdk_path}")
-      set(_CMAKE_OSX_SYSROOT_PATH "${_sdk_path}")
-      # For non-Xcode generators use the path.
-      if(NOT "${CMAKE_GENERATOR}" MATCHES "Xcode")
-        set(CMAKE_OSX_SYSROOT "${_CMAKE_OSX_SYSROOT_PATH}")
-      endif()
-    endif()
+if(CMAKE_OSX_SYSROOT MATCHES "/")
+  # This is a path to a SDK.  Make sure it exists.
+  if(NOT IS_DIRECTORY "${CMAKE_OSX_SYSROOT}")
+    message(WARNING "Ignoring CMAKE_OSX_SYSROOT value:\n ${CMAKE_OSX_SYSROOT}\n"
+      "because the directory does not exist.")
+    set(CMAKE_OSX_SYSROOT "")
   endif()
+  set(_CMAKE_OSX_SYSROOT_PATH "${CMAKE_OSX_SYSROOT}")
+elseif(CMAKE_OSX_SYSROOT)
+  # This is the name of a SDK.  Transform it to a path.
+  _apple_resolve_sdk_path("${CMAKE_OSX_SYSROOT}" _CMAKE_OSX_SYSROOT_PATH)
+  # Use the path for non-Xcode generators.
+  if(IS_DIRECTORY "${_CMAKE_OSX_SYSROOT_PATH}" AND NOT CMAKE_GENERATOR MATCHES "Xcode")
+    set(CMAKE_OSX_SYSROOT "${_CMAKE_OSX_SYSROOT_PATH}")
+  endif()
+endif()
+if(NOT CMAKE_OSX_SYSROOT)
+  # Without any explicit SDK we rely on the toolchain default,
+  # which we assume to be what wrappers like /usr/bin/cc use.
+  if(CMAKE_GENERATOR STREQUAL "Xcode")
+    set(_sdk_macosx --sdk macosx)
+  else()
+    set(_sdk_macosx)
+  endif()
+  execute_process(
+    COMMAND xcrun ${_sdk_macosx} --show-sdk-path
+    OUTPUT_VARIABLE _CMAKE_OSX_SYSROOT_PATH
+    OUTPUT_STRIP_TRAILING_WHITESPACE
+    ERROR_VARIABLE _stderr
+    RESULT_VARIABLE _result
+  )
+  unset(_sdk_macosx)
 endif()
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..30e6b94 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -412,6 +412,8 @@
   cmNewLineStyle.cxx
   cmOrderDirectories.cxx
   cmOrderDirectories.h
+  cmPathResolver.cxx
+  cmPathResolver.h
   cmPlistParser.cxx
   cmPlistParser.h
   cmPolicies.h
@@ -508,8 +510,6 @@
   cmake.cxx
   cmake.h
 
-  cmCommand.cxx
-  cmCommand.h
   cmCommands.cxx
   cmCommands.h
   cmAddCompileDefinitionsCommand.cxx
@@ -1071,9 +1071,10 @@
   cmCTest.cxx
   CTest/cmProcess.cxx
   CTest/cmCTestBinPacker.cxx
-  CTest/cmCTestBuildAndTestHandler.cxx
+  CTest/cmCTestBuildAndTest.cxx
   CTest/cmCTestBuildCommand.cxx
   CTest/cmCTestBuildHandler.cxx
+  CTest/cmCTestCommand.cxx
   CTest/cmCTestConfigureCommand.cxx
   CTest/cmCTestConfigureHandler.cxx
   CTest/cmCTestCoverageCommand.cxx
@@ -1109,10 +1110,10 @@
   CTest/cmCTestTestCommand.cxx
   CTest/cmCTestTestHandler.cxx
   CTest/cmCTestTestMeasurementXMLParser.cxx
+  CTest/cmCTestTypes.cxx
   CTest/cmCTestUpdateCommand.cxx
   CTest/cmCTestUpdateHandler.cxx
   CTest/cmCTestUploadCommand.cxx
-  CTest/cmCTestUploadHandler.cxx
 
   CTest/cmCTestVC.cxx
   CTest/cmCTestVC.h
diff --git a/Source/CMakeVersion.cmake b/Source/CMakeVersion.cmake
index 5755e5a..01a06e0 100644
--- a/Source/CMakeVersion.cmake
+++ b/Source/CMakeVersion.cmake
@@ -1,7 +1,7 @@
 # CMake version number components.
 set(CMake_VERSION_MAJOR 3)
 set(CMake_VERSION_MINOR 31)
-set(CMake_VERSION_PATCH 0)
+set(CMake_VERSION_PATCH 20241113)
 #set(CMake_VERSION_RC 0)
 set(CMake_VERSION_IS_DIRTY 0)
 
diff --git a/Source/CPack/WiX/cmWIXAccessControlList.cxx b/Source/CPack/WiX/cmWIXAccessControlList.cxx
index 0ebe2f4..5d0df06 100644
--- a/Source/CPack/WiX/cmWIXAccessControlList.cxx
+++ b/Source/CPack/WiX/cmWIXAccessControlList.cxx
@@ -50,14 +50,13 @@
     user = user_and_domain;
   }
 
-  std::vector<std::string> permissions = cmTokenize(permission_string, ",");
-
   this->SourceWriter.BeginElement("Permission");
   this->SourceWriter.AddAttribute("User", std::string(user));
   if (!domain.empty()) {
     this->SourceWriter.AddAttribute("Domain", std::string(domain));
   }
-  for (std::string const& permission : permissions) {
+  for (auto permission :
+       cmTokenizedView(permission_string, ',', cmTokenizerMode::New)) {
     this->EmitBooleanAttribute(entry, cmTrimWhitespace(permission));
   }
   this->SourceWriter.EndElement("Permission");
diff --git a/Source/CPack/cmCPackArchiveGenerator.cxx b/Source/CPack/cmCPackArchiveGenerator.cxx
index c70a2f1..bb1545b 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>
@@ -192,15 +191,16 @@
   std::string componentUpper(cmSystemTools::UpperCase(component));
   std::string packageFileName;
 
-  if (this->IsSet("CPACK_ARCHIVE_" + componentUpper + "_FILE_NAME")) {
+  if (cmValue v = this->GetOptionIfSet("CPACK_ARCHIVE_" + componentUpper +
+                                       "_FILE_NAME")) {
+    packageFileName += *v;
+  } else if ((v = this->GetOptionIfSet("CPACK_ARCHIVE_FILE_NAME"))) {
     packageFileName +=
-      *this->GetOption("CPACK_ARCHIVE_" + componentUpper + "_FILE_NAME");
-  } else if (this->IsSet("CPACK_ARCHIVE_FILE_NAME")) {
-    packageFileName += this->GetComponentPackageFileName(
-      *this->GetOption("CPACK_ARCHIVE_FILE_NAME"), component, isGroupName);
+      this->GetComponentPackageFileName(*v, component, isGroupName);
   } else {
-    packageFileName += this->GetComponentPackageFileName(
-      *this->GetOption("CPACK_PACKAGE_FILE_NAME"), component, isGroupName);
+    v = this->GetOption("CPACK_PACKAGE_FILE_NAME");
+    packageFileName +=
+      this->GetComponentPackageFileName(*v, component, isGroupName);
   }
 
   packageFileName += this->GetOutputExtension();
@@ -238,10 +238,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;
@@ -398,10 +395,11 @@
   this->packageFileNames.emplace_back(this->toplevel);
   this->packageFileNames[0] += "/";
 
-  if (this->IsSet("CPACK_ARCHIVE_FILE_NAME")) {
-    this->packageFileNames[0] += *this->GetOption("CPACK_ARCHIVE_FILE_NAME");
+  if (cmValue v = this->GetOptionIfSet("CPACK_ARCHIVE_FILE_NAME")) {
+    this->packageFileNames[0] += *v;
   } else {
-    this->packageFileNames[0] += *this->GetOption("CPACK_PACKAGE_FILE_NAME");
+    v = this->GetOption("CPACK_PACKAGE_FILE_NAME");
+    this->packageFileNames[0] += *v;
   }
 
   this->packageFileNames[0] += this->GetOutputExtension();
@@ -448,10 +446,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) {
@@ -488,10 +483,10 @@
   int threads = 1;
 
   // CPACK_ARCHIVE_THREADS overrides CPACK_THREADS
-  if (this->IsSet("CPACK_ARCHIVE_THREADS")) {
-    threads = std::stoi(*this->GetOption("CPACK_ARCHIVE_THREADS"));
-  } else if (this->IsSet("CPACK_THREADS")) {
-    threads = std::stoi(*this->GetOption("CPACK_THREADS"));
+  if (cmValue v = this->GetOptionIfSet("CPACK_ARCHIVE_THREADS")) {
+    threads = std::stoi(*v);
+  } else if (cmValue v2 = this->GetOptionIfSet("CPACK_THREADS")) {
+    threads = std::stoi(*v2);
   }
 
   return threads;
diff --git a/Source/CPack/cmCPackDragNDropGenerator.cxx b/Source/CPack/cmCPackDragNDropGenerator.cxx
index 4c518ac..038104e 100644
--- a/Source/CPack/cmCPackDragNDropGenerator.cxx
+++ b/Source/CPack/cmCPackDragNDropGenerator.cxx
@@ -101,13 +101,12 @@
   }
   this->SetOptionIfNotSet("CPACK_COMMAND_REZ", rez_path);
 
-  if (this->IsSet("CPACK_DMG_SLA_DIR")) {
-    slaDirectory = this->GetOption("CPACK_DMG_SLA_DIR");
+  if (cmValue v = this->GetOptionIfSet("CPACK_DMG_SLA_DIR")) {
+    slaDirectory = *v;
     if (!slaDirectory.empty() &&
         this->IsOn("CPACK_DMG_SLA_USE_RESOURCE_FILE_LICENSE") &&
-        this->IsSet("CPACK_RESOURCE_FILE_LICENSE")) {
-      std::string license_file =
-        this->GetOption("CPACK_RESOURCE_FILE_LICENSE");
+        (v = this->GetOptionIfSet("CPACK_RESOURCE_FILE_LICENSE"))) {
+      std::string license_file = *v;
       if (!license_file.empty() &&
           (license_file.find("CPack.GenericLicense.txt") ==
            std::string::npos)) {
@@ -119,7 +118,8 @@
         singleLicense = true;
       }
     }
-    if (!this->IsSet("CPACK_DMG_SLA_LANGUAGES")) {
+    cmValue lang = this->GetOptionIfSet("CPACK_DMG_SLA_LANGUAGES");
+    if (!lang) {
       cmCPackLogger(cmCPackLog::LOG_ERROR,
                     "CPACK_DMG_SLA_DIR set but no languages defined "
                     "(set CPACK_DMG_SLA_LANGUAGES)"
@@ -132,7 +132,7 @@
       return 0;
     }
 
-    cmList languages{ this->GetOption("CPACK_DMG_SLA_LANGUAGES") };
+    cmList languages{ *lang };
     if (languages.empty()) {
       cmCPackLogger(cmCPackLog::LOG_ERROR,
                     "CPACK_DMG_SLA_LANGUAGES set but empty" << std::endl);
@@ -742,8 +742,8 @@
 
   std::string componentFileName = cmStrCat(
     "CPACK_DMG_", cmSystemTools::UpperCase(componentName), "_FILE_NAME");
-  if (this->IsSet(componentFileName)) {
-    return this->GetOption(componentFileName);
+  if (cmValue v = this->GetOptionIfSet(componentFileName)) {
+    return *v;
   }
   return GetComponentPackageFileName(package_file_name, componentName, false);
 }
diff --git a/Source/CPack/cmCPackGenerator.cxx b/Source/CPack/cmCPackGenerator.cxx
index 44d0ece..82116b3 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) {
@@ -1323,6 +1318,15 @@
   return this->MakefileMap->IsSet(name);
 }
 
+cmValue cmCPackGenerator::GetOptionIfSet(const std::string& name) const
+{
+  cmValue ret = this->MakefileMap->GetDefinition(name);
+  if (!ret || ret->empty() || cmIsNOTFOUND(*ret)) {
+    return {};
+  }
+  return ret;
+}
+
 bool cmCPackGenerator::IsOn(const std::string& name) const
 {
   return this->GetOption(name).IsOn();
diff --git a/Source/CPack/cmCPackGenerator.h b/Source/CPack/cmCPackGenerator.h
index 980ab8f..30e8451 100644
--- a/Source/CPack/cmCPackGenerator.h
+++ b/Source/CPack/cmCPackGenerator.h
@@ -103,6 +103,7 @@
   cmValue GetOption(const std::string& op) const;
   std::vector<std::string> GetOptions() const;
   bool IsSet(const std::string& name) const;
+  cmValue GetOptionIfSet(const std::string& name) const;
   bool IsOn(const std::string& name) const;
   bool IsSetToOff(const std::string& op) const;
   bool IsSetToEmpty(const std::string& op) const;
diff --git a/Source/CPack/cmCPackInnoSetupGenerator.cxx b/Source/CPack/cmCPackInnoSetupGenerator.cxx
index 35d126f..df99e15 100644
--- a/Source/CPack/cmCPackInnoSetupGenerator.cxx
+++ b/Source/CPack/cmCPackInnoSetupGenerator.cxx
@@ -97,9 +97,8 @@
 int cmCPackInnoSetupGenerator::PackageFiles()
 {
   // Includes
-  if (IsSet("CPACK_INNOSETUP_EXTRA_SCRIPTS")) {
-    const cmList extraScripts(GetOption("CPACK_INNOSETUP_EXTRA_SCRIPTS"));
-
+  if (cmValue v = GetOptionIfSet("CPACK_INNOSETUP_EXTRA_SCRIPTS")) {
+    const cmList extraScripts(*v);
     for (const std::string& i : extraScripts) {
       includeDirectives.emplace_back(cmStrCat(
         "#include ", QuotePath(cmSystemTools::CollapseFullPath(i, toplevel))));
@@ -133,9 +132,8 @@
   }
 
   // [Code] section
-  if (IsSet("CPACK_INNOSETUP_CODE_FILES")) {
-    const cmList codeFiles(GetOption("CPACK_INNOSETUP_CODE_FILES"));
-
+  if (cmValue v = GetOptionIfSet("CPACK_INNOSETUP_CODE_FILES")) {
+    const cmList codeFiles(*v);
     for (const std::string& i : codeFiles) {
       codeIncludes.emplace_back(cmStrCat(
         "#include ", QuotePath(cmSystemTools::CollapseFullPath(i, toplevel))));
@@ -168,26 +166,26 @@
   }
   setupDirectives["AppPublisher"] = GetOption("CPACK_PACKAGE_VENDOR");
 
-  if (IsSet("CPACK_PACKAGE_HOMEPAGE_URL")) {
-    setupDirectives["AppPublisherURL"] =
-      GetOption("CPACK_PACKAGE_HOMEPAGE_URL");
-    setupDirectives["AppSupportURL"] = GetOption("CPACK_PACKAGE_HOMEPAGE_URL");
-    setupDirectives["AppUpdatesURL"] = GetOption("CPACK_PACKAGE_HOMEPAGE_URL");
+  if (cmValue v = GetOptionIfSet("CPACK_PACKAGE_HOMEPAGE_URL")) {
+    setupDirectives["AppPublisherURL"] = *v;
+    setupDirectives["AppSupportURL"] = *v;
+    setupDirectives["AppUpdatesURL"] = *v;
   }
 
   SetOptionIfNotSet("CPACK_INNOSETUP_IGNORE_LICENSE_PAGE", "OFF");
-  if (IsSet("CPACK_RESOURCE_FILE_LICENSE") &&
-      !GetOption("CPACK_INNOSETUP_IGNORE_LICENSE_PAGE").IsOn()) {
-    setupDirectives["LicenseFile"] = cmSystemTools::ConvertToWindowsOutputPath(
-      GetOption("CPACK_RESOURCE_FILE_LICENSE"));
+  if (!GetOption("CPACK_INNOSETUP_IGNORE_LICENSE_PAGE").IsOn()) {
+    if (cmValue v = GetOptionIfSet("CPACK_RESOURCE_FILE_LICENSE")) {
+      setupDirectives["LicenseFile"] =
+        cmSystemTools::ConvertToWindowsOutputPath(*v);
+    }
   }
 
   SetOptionIfNotSet("CPACK_INNOSETUP_IGNORE_README_PAGE", "ON");
-  if (IsSet("CPACK_RESOURCE_FILE_README") &&
-      !GetOption("CPACK_INNOSETUP_IGNORE_README_PAGE").IsOn()) {
-    setupDirectives["InfoBeforeFile"] =
-      cmSystemTools::ConvertToWindowsOutputPath(
-        GetOption("CPACK_RESOURCE_FILE_README"));
+  if (!GetOption("CPACK_INNOSETUP_IGNORE_README_PAGE").IsOn()) {
+    if (cmValue v = GetOptionIfSet("CPACK_RESOURCE_FILE_README")) {
+      setupDirectives["InfoBeforeFile"] =
+        cmSystemTools::ConvertToWindowsOutputPath(*v);
+    }
   }
 
   SetOptionIfNotSet("CPACK_INNOSETUP_USE_MODERN_WIZARD", "OFF");
@@ -201,16 +199,14 @@
     setupDirectives["SetupIconFile"] = "compiler:SetupClassicIcon.ico";
   }
 
-  if (IsSet("CPACK_INNOSETUP_ICON_FILE")) {
+  if (cmValue v = GetOptionIfSet("CPACK_INNOSETUP_ICON_FILE")) {
     setupDirectives["SetupIconFile"] =
-      cmSystemTools::ConvertToWindowsOutputPath(
-        GetOption("CPACK_INNOSETUP_ICON_FILE"));
+      cmSystemTools::ConvertToWindowsOutputPath(*v);
   }
 
-  if (IsSet("CPACK_PACKAGE_ICON")) {
+  if (cmValue v = GetOptionIfSet("CPACK_PACKAGE_ICON")) {
     setupDirectives["WizardSmallImageFile"] =
-      cmSystemTools::ConvertToWindowsOutputPath(
-        GetOption("CPACK_PACKAGE_ICON"));
+      cmSystemTools::ConvertToWindowsOutputPath(*v);
   }
 
   if (!RequireOption("CPACK_PACKAGE_INSTALL_DIRECTORY")) {
@@ -238,8 +234,8 @@
     toplevelProgramFolder = false;
   }
 
-  if (IsSet("CPACK_INNOSETUP_PASSWORD")) {
-    setupDirectives["Password"] = GetOption("CPACK_INNOSETUP_PASSWORD");
+  if (cmValue v = GetOptionIfSet("CPACK_INNOSETUP_PASSWORD")) {
+    setupDirectives["Password"] = *v;
     setupDirectives["Encryption"] = "yes";
   }
 
@@ -306,9 +302,9 @@
 bool cmCPackInnoSetupGenerator::ProcessFiles()
 {
   std::map<std::string, std::string> customFileInstructions;
-  if (IsSet("CPACK_INNOSETUP_CUSTOM_INSTALL_INSTRUCTIONS")) {
-    const cmList instructions(
-      GetOption("CPACK_INNOSETUP_CUSTOM_INSTALL_INSTRUCTIONS"));
+  if (cmValue v =
+        GetOptionIfSet("CPACK_INNOSETUP_CUSTOM_INSTALL_INSTRUCTIONS")) {
+    const cmList instructions(*v);
     if (instructions.size() % 2 != 0) {
       cmCPackLogger(cmCPackLog::LOG_ERROR,
                     "CPACK_INNOSETUP_CUSTOM_INSTALL_INSTRUCTIONS should "
@@ -328,8 +324,8 @@
     toplevelProgramFolder ? "{autoprograms}\\" : "{group}\\";
 
   std::map<std::string, std::string> icons;
-  if (IsSet("CPACK_PACKAGE_EXECUTABLES")) {
-    const cmList executables(GetOption("CPACK_PACKAGE_EXECUTABLES"));
+  if (cmValue v = GetOptionIfSet("CPACK_PACKAGE_EXECUTABLES")) {
+    const cmList executables(*v);
     if (executables.size() % 2 != 0) {
       cmCPackLogger(cmCPackLog::LOG_ERROR,
                     "CPACK_PACKAGE_EXECUTABLES should should contain pairs of "
@@ -345,13 +341,13 @@
   }
 
   std::vector<std::string> desktopIcons;
-  if (IsSet("CPACK_CREATE_DESKTOP_LINKS")) {
-    cmExpandList(GetOption("CPACK_CREATE_DESKTOP_LINKS"), desktopIcons);
+  if (cmValue v = GetOptionIfSet("CPACK_CREATE_DESKTOP_LINKS")) {
+    cmExpandList(*v, desktopIcons);
   }
 
   std::vector<std::string> runExecutables;
-  if (IsSet("CPACK_INNOSETUP_RUN_EXECUTABLES")) {
-    cmExpandList(GetOption("CPACK_INNOSETUP_RUN_EXECUTABLES"), runExecutables);
+  if (cmValue v = GetOptionIfSet("CPACK_INNOSETUP_RUN_EXECUTABLES")) {
+    cmExpandList(*v, runExecutables);
   }
 
   for (const std::string& i : files) {
@@ -507,8 +503,8 @@
   static cmsys::RegularExpression urlRegex(
     "^(mailto:|(ftps?|https?|news)://).*$");
 
-  if (IsSet("CPACK_INNOSETUP_MENU_LINKS")) {
-    const cmList menuIcons(GetOption("CPACK_INNOSETUP_MENU_LINKS"));
+  if (cmValue v = GetOptionIfSet("CPACK_INNOSETUP_MENU_LINKS")) {
+    const cmList menuIcons(*v);
     if (menuIcons.size() % 2 != 0) {
       cmCPackLogger(cmCPackLog::LOG_ERROR,
                     "CPACK_INNOSETUP_MENU_LINKS should "
@@ -784,8 +780,7 @@
   const std::string& defaultMessage =
     "; CPack didn't find any entries for this section";
 
-  if (IsSet("CPACK_CREATE_DESKTOP_LINKS") &&
-      !GetOption("CPACK_CREATE_DESKTOP_LINKS").Get()->empty()) {
+  if (!IsSetToEmpty("CPACK_CREATE_DESKTOP_LINKS")) {
     cmCPackInnoSetupKeyValuePairs tasks;
     tasks["Name"] = "\"desktopicon\"";
     tasks["Description"] = "\"{cm:CreateDesktopIcon}\"";
@@ -858,9 +853,8 @@
     }
   }
 
-  if (IsSet("CPACK_INNOSETUP_EXECUTABLE_ARGUMENTS")) {
-    const cmList args(GetOption("CPACK_INNOSETUP_EXECUTABLE_ARGUMENTS"));
-
+  if (cmValue v = GetOptionIfSet("CPACK_INNOSETUP_EXECUTABLE_ARGUMENTS")) {
+    const cmList args(*v);
     isccArgs.insert(isccArgs.end(), args.begin(), args.end());
   }
 
@@ -986,7 +980,7 @@
                   "Problem running certutil command: " << hashCmd
                                                        << std::endl);
   }
-  *hash = cmTrimWhitespace(cmTokenize(hashOutput, "\n").at(1));
+  *hash = cmTrimWhitespace(cmTokenizedView(hashOutput, '\n').at(1));
 
   if (hash->length() != 64) {
     cmCPackLogger(cmCPackLog::LOG_WARNING,
diff --git a/Source/CPack/cmCPackNSISGenerator.cxx b/Source/CPack/cmCPackNSISGenerator.cxx
index 53871ee..8d81183 100644
--- a/Source/CPack/cmCPackNSISGenerator.cxx
+++ b/Source/CPack/cmCPackNSISGenerator.cxx
@@ -130,23 +130,20 @@
   if (this->IsSet("CPACK_NSIS_MUI_ICON") ||
       this->IsSet("CPACK_NSIS_MUI_UNIICON")) {
     std::string installerIconCode;
-    if (this->IsSet("CPACK_NSIS_MUI_ICON")) {
-      installerIconCode += cmStrCat(
-        "!define MUI_ICON \"", this->GetOption("CPACK_NSIS_MUI_ICON"), "\"\n");
+    if (cmValue icon = this->GetOptionIfSet("CPACK_NSIS_MUI_ICON")) {
+      installerIconCode += cmStrCat("!define MUI_ICON \"", *icon, "\"\n");
     }
-    if (this->IsSet("CPACK_NSIS_MUI_UNIICON")) {
-      installerIconCode +=
-        cmStrCat("!define MUI_UNICON \"",
-                 this->GetOption("CPACK_NSIS_MUI_UNIICON"), "\"\n");
+    if (cmValue icon = this->GetOptionIfSet("CPACK_NSIS_MUI_UNIICON")) {
+      installerIconCode += cmStrCat("!define MUI_UNICON \"", *icon, "\"\n");
     }
     this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_ICON_CODE",
                             installerIconCode.c_str());
   }
   std::string installerHeaderImage;
-  if (this->IsSet("CPACK_NSIS_MUI_HEADERIMAGE")) {
-    installerHeaderImage = *this->GetOption("CPACK_NSIS_MUI_HEADERIMAGE");
-  } else if (this->IsSet("CPACK_PACKAGE_ICON")) {
-    installerHeaderImage = *this->GetOption("CPACK_PACKAGE_ICON");
+  if (cmValue img = this->GetOptionIfSet("CPACK_NSIS_MUI_HEADERIMAGE")) {
+    installerHeaderImage = *img;
+  } else if (cmValue icon = this->GetOptionIfSet("CPACK_PACKAGE_ICON")) {
+    installerHeaderImage = *icon;
   }
   if (!installerHeaderImage.empty()) {
     std::string installerIconCode = cmStrCat(
@@ -155,35 +152,33 @@
                             installerIconCode);
   }
 
-  if (this->IsSet("CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP")) {
-    std::string installerBitmapCode = cmStrCat(
-      "!define MUI_WELCOMEFINISHPAGE_BITMAP \"",
-      this->GetOption("CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP"), "\"\n");
+  if (cmValue v =
+        this->GetOptionIfSet("CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP")) {
+    std::string installerBitmapCode =
+      cmStrCat("!define MUI_WELCOMEFINISHPAGE_BITMAP \"", *v, "\"\n");
     this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_WELCOMEFINISH_CODE",
                             installerBitmapCode);
   }
 
-  if (this->IsSet("CPACK_NSIS_MUI_UNWELCOMEFINISHPAGE_BITMAP")) {
-    std::string installerBitmapCode = cmStrCat(
-      "!define MUI_UNWELCOMEFINISHPAGE_BITMAP \"",
-      this->GetOption("CPACK_NSIS_MUI_UNWELCOMEFINISHPAGE_BITMAP"), "\"\n");
+  if (cmValue v =
+        this->GetOptionIfSet("CPACK_NSIS_MUI_UNWELCOMEFINISHPAGE_BITMAP")) {
+    std::string installerBitmapCode =
+      cmStrCat("!define MUI_UNWELCOMEFINISHPAGE_BITMAP \"", *v, "\"\n");
     this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_UNWELCOMEFINISH_CODE",
                             installerBitmapCode);
   }
 
-  if (this->IsSet("CPACK_NSIS_MUI_FINISHPAGE_RUN")) {
-    std::string installerRunCode =
-      cmStrCat("!define MUI_FINISHPAGE_RUN \"$INSTDIR\\",
-               this->GetOption("CPACK_NSIS_EXECUTABLES_DIRECTORY"), '\\',
-               this->GetOption("CPACK_NSIS_MUI_FINISHPAGE_RUN"), "\"\n");
+  if (cmValue v = this->GetOptionIfSet("CPACK_NSIS_MUI_FINISHPAGE_RUN")) {
+    std::string installerRunCode = cmStrCat(
+      "!define MUI_FINISHPAGE_RUN \"$INSTDIR\\",
+      this->GetOption("CPACK_NSIS_EXECUTABLES_DIRECTORY"), '\\', *v, "\"\n");
     this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_FINISHPAGE_RUN_CODE",
                             installerRunCode);
   }
 
-  if (this->IsSet("CPACK_NSIS_WELCOME_TITLE")) {
+  if (cmValue v = this->GetOptionIfSet("CPACK_NSIS_WELCOME_TITLE")) {
     std::string welcomeTitleCode =
-      cmStrCat("!define MUI_WELCOMEPAGE_TITLE \"",
-               this->GetOption("CPACK_NSIS_WELCOME_TITLE"), "\"");
+      cmStrCat("!define MUI_WELCOMEPAGE_TITLE \"", *v, "\"");
     this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_WELCOME_TITLE_CODE",
                             welcomeTitleCode);
   }
@@ -193,10 +188,9 @@
                             "!define MUI_WELCOMEPAGE_TITLE_3LINES");
   }
 
-  if (this->IsSet("CPACK_NSIS_FINISH_TITLE")) {
+  if (cmValue v = this->GetOptionIfSet("CPACK_NSIS_FINISH_TITLE")) {
     std::string finishTitleCode =
-      cmStrCat("!define MUI_FINISHPAGE_TITLE \"",
-               this->GetOption("CPACK_NSIS_FINISH_TITLE"), "\"");
+      cmStrCat("!define MUI_FINISHPAGE_TITLE \"", *v, "\"");
     this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_FINISH_TITLE_CODE",
                             finishTitleCode);
   }
@@ -211,28 +205,28 @@
                             "ManifestDPIAware true");
   }
 
-  if (this->IsSet("CPACK_NSIS_BRANDING_TEXT")) {
+  if (cmValue brandingText =
+        this->GetOptionIfSet("CPACK_NSIS_BRANDING_TEXT")) {
     // Default position to LEFT
     std::string brandingTextPosition = "LEFT";
-    if (this->IsSet("CPACK_NSIS_BRANDING_TEXT_TRIM_POSITION")) {
-      std::string wantedPosition =
-        this->GetOption("CPACK_NSIS_BRANDING_TEXT_TRIM_POSITION");
-      if (!wantedPosition.empty()) {
+    if (cmValue wantedPosition =
+          this->GetOptionIfSet("CPACK_NSIS_BRANDING_TEXT_TRIM_POSITION")) {
+      if (!wantedPosition->empty()) {
         const std::set<std::string> possiblePositions{ "CENTER", "LEFT",
                                                        "RIGHT" };
-        if (possiblePositions.find(wantedPosition) ==
+        if (possiblePositions.find(*wantedPosition) ==
             possiblePositions.end()) {
           cmCPackLogger(cmCPackLog::LOG_ERROR,
                         "Unsupported branding text trim position "
-                          << wantedPosition << std::endl);
+                          << *wantedPosition << std::endl);
           return false;
         }
-        brandingTextPosition = wantedPosition;
+        brandingTextPosition = *wantedPosition;
       }
     }
     std::string brandingTextCode =
       cmStrCat("BrandingText /TRIM", brandingTextPosition, " \"",
-               this->GetOption("CPACK_NSIS_BRANDING_TEXT"), "\"\n");
+               *brandingText, "\"\n");
     this->SetOptionIfNotSet("CPACK_NSIS_BRANDING_TEXT_CODE", brandingTextCode);
   }
 
diff --git a/Source/CPack/cmCPackProductBuildGenerator.cxx b/Source/CPack/cmCPackProductBuildGenerator.cxx
index ae3c50e..b5b70dc 100644
--- a/Source/CPack/cmCPackProductBuildGenerator.cxx
+++ b/Source/CPack/cmCPackProductBuildGenerator.cxx
@@ -60,10 +60,8 @@
 
   std::string resDir = cmStrCat(packageDirFileName, "/Contents");
 
-  if (this->IsSet("CPACK_PRODUCTBUILD_RESOURCES_DIR")) {
-    std::string userResDir =
-      this->GetOption("CPACK_PRODUCTBUILD_RESOURCES_DIR");
-
+  if (cmValue v = this->GetOptionIfSet("CPACK_PRODUCTBUILD_RESOURCES_DIR")) {
+    std::string userResDir = *v;
     if (!cmSystemTools::CopyADirectory(userResDir, resDir)) {
       cmCPackLogger(cmCPackLog::LOG_ERROR,
                     "Problem copying the resource files" << std::endl);
diff --git a/Source/CPack/cpack.cxx b/Source/CPack/cpack.cxx
index 305ac56..9979dfa 100644
--- a/Source/CPack/cpack.cxx
+++ b/Source/CPack/cpack.cxx
@@ -113,7 +113,7 @@
   log.SetOutputPrefix("CPack: ");
   log.SetVerbosePrefix("CPack Verbose: ");
 
-  if (cmSystemTools::GetCurrentWorkingDirectory().empty()) {
+  if (cmSystemTools::GetLogicalWorkingDirectory().empty()) {
     cmCPack_Log(&log, cmCPackLog::LOG_ERROR,
                 "Current working directory cannot be established.\n");
     return 1;
@@ -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;
@@ -254,7 +255,7 @@
 
   // Set up presets
   if (!preset.empty() || listPresets) {
-    const auto workingDirectory = cmSystemTools::GetCurrentWorkingDirectory();
+    const auto workingDirectory = cmSystemTools::GetLogicalWorkingDirectory();
 
     auto const presetGeneratorsPresent =
       [&generators](const cmCMakePresetsGraph::PackagePreset& p) {
@@ -349,7 +350,8 @@
       return 1;
     }
 
-    cmSystemTools::ChangeDirectory(expandedConfigurePreset->BinaryDir);
+    cmSystemTools::SetLogicalWorkingDirectory(
+      expandedConfigurePreset->BinaryDir);
 
     auto presetEnvironment = expandedPreset->Environment;
     for (auto const& var : presetEnvironment) {
@@ -405,9 +407,9 @@
 
   bool cpackConfigFileSpecified = true;
   if (!cpackConfigFile.empty()) {
-    cpackConfigFile = cmSystemTools::CollapseFullPath(cpackConfigFile);
+    cpackConfigFile = cmSystemTools::ToNormalizedPathOnDisk(cpackConfigFile);
   } else {
-    cpackConfigFile = cmStrCat(cmSystemTools::GetCurrentWorkingDirectory(),
+    cpackConfigFile = cmStrCat(cmSystemTools::GetLogicalWorkingDirectory(),
                                "/CPackConfig.cmake");
     cpackConfigFileSpecified = false;
   }
@@ -479,7 +481,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")) {
@@ -487,7 +489,7 @@
         cpackProjectDirectory = cmSystemTools::CollapseFullPath(*pd);
       } else {
         // Default to the current working directory.
-        cpackProjectDirectory = cmSystemTools::GetCurrentWorkingDirectory();
+        cpackProjectDirectory = cmSystemTools::GetLogicalWorkingDirectory();
       }
     }
     globalMF.AddDefinition("CPACK_PACKAGE_DIRECTORY", cpackProjectDirectory);
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..b57c903 100644
--- a/Source/CTest/cmCTestBuildCommand.cxx
+++ b/Source/CTest/cmCTestBuildCommand.cxx
@@ -5,11 +5,14 @@
 #include <sstream>
 #include <utility>
 
+#include <cm/memory>
 #include <cmext/string_view>
 
+#include "cmArgumentParser.h"
 #include "cmCTest.h"
 #include "cmCTestBuildHandler.h"
-#include "cmCommand.h"
+#include "cmCTestGenericHandler.h"
+#include "cmExecutionStatus.h"
 #include "cmGlobalGenerator.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
@@ -18,45 +21,37 @@
 #include "cmValue.h"
 #include "cmake.h"
 
-class cmExecutionStatus;
-
-std::unique_ptr<cmCommand> cmCTestBuildCommand::Clone()
+bool cmCTestBuildCommand::InitialPass(std::vector<std::string> const& args,
+                                      cmExecutionStatus& status) const
 {
-  auto ni = cm::make_unique<cmCTestBuildCommand>();
-  ni->CTest = this->CTest;
-  ni->CTestScriptHandler = this->CTestScriptHandler;
-  return std::unique_ptr<cmCommand>(std::move(ni));
+  static auto const parser =
+    cmArgumentParser<BuildArguments>{ MakeHandlerParser<BuildArguments>() }
+      .Bind("NUMBER_ERRORS"_s, &BuildArguments::NumberErrors)
+      .Bind("NUMBER_WARNINGS"_s, &BuildArguments::NumberWarnings)
+      .Bind("TARGET"_s, &BuildArguments::Target)
+      .Bind("CONFIGURATION"_s, &BuildArguments::Configuration)
+      .Bind("FLAGS"_s, &BuildArguments::Flags)
+      .Bind("PROJECT_NAME"_s, &BuildArguments::ProjectName)
+      .Bind("PARALLEL_LEVEL"_s, &BuildArguments::ParallelLevel);
+
+  return this->Invoke(parser, args, status, [&](BuildArguments& a) {
+    return this->ExecuteHandlerCommand(a, status);
+  });
 }
 
-void cmCTestBuildCommand::BindArguments()
+std::unique_ptr<cmCTestGenericHandler> cmCTestBuildCommand::InitializeHandler(
+  HandlerArguments& arguments, cmExecutionStatus& status) const
 {
-  this->cmCTestHandlerCommand::BindArguments();
-  this->Bind("NUMBER_ERRORS"_s, this->NumberErrors);
-  this->Bind("NUMBER_WARNINGS"_s, this->NumberWarnings);
-  this->Bind("TARGET"_s, this->Target);
-  this->Bind("CONFIGURATION"_s, this->Configuration);
-  this->Bind("FLAGS"_s, this->Flags);
-  this->Bind("PROJECT_NAME"_s, this->ProjectName);
-  this->Bind("PARALLEL_LEVEL"_s, this->ParallelLevel);
-}
+  cmMakefile& mf = status.GetMakefile();
+  auto const& args = static_cast<BuildArguments&>(arguments);
+  auto handler = cm::make_unique<cmCTestBuildHandler>(this->CTest);
 
-cmCTestBuildCommand::~cmCTestBuildCommand() = default;
-
-cmCTestGenericHandler* cmCTestBuildCommand::InitializeHandler()
-{
-  cmCTestBuildHandler* handler = this->CTest->GetBuildHandler();
-  handler->Initialize();
-
-  this->Handler = handler;
-
-  cmValue ctestBuildCommand =
-    this->Makefile->GetDefinition("CTEST_BUILD_COMMAND");
+  cmValue ctestBuildCommand = mf.GetDefinition("CTEST_BUILD_COMMAND");
   if (cmNonempty(ctestBuildCommand)) {
     this->CTest->SetCTestConfiguration("MakeCommand", *ctestBuildCommand,
-                                       this->Quiet);
+                                       args.Quiet);
   } else {
-    cmValue cmakeGeneratorName =
-      this->Makefile->GetDefinition("CTEST_CMAKE_GENERATOR");
+    cmValue cmakeGeneratorName = mf.GetDefinition("CTEST_CMAKE_GENERATOR");
 
     // Build configuration is determined by: CONFIGURATION argument,
     // or CTEST_BUILD_CONFIGURATION script variable, or
@@ -64,54 +59,46 @@
     // line argument... in that order.
     //
     cmValue ctestBuildConfiguration =
-      this->Makefile->GetDefinition("CTEST_BUILD_CONFIGURATION");
-    std::string cmakeBuildConfiguration = cmNonempty(this->Configuration)
-      ? this->Configuration
+      mf.GetDefinition("CTEST_BUILD_CONFIGURATION");
+    std::string cmakeBuildConfiguration = cmNonempty(args.Configuration)
+      ? args.Configuration
       : cmNonempty(ctestBuildConfiguration) ? *ctestBuildConfiguration
                                             : this->CTest->GetConfigType();
 
-    const std::string& cmakeBuildAdditionalFlags = cmNonempty(this->Flags)
-      ? this->Flags
-      : this->Makefile->GetSafeDefinition("CTEST_BUILD_FLAGS");
-    const std::string& cmakeBuildTarget = cmNonempty(this->Target)
-      ? this->Target
-      : this->Makefile->GetSafeDefinition("CTEST_BUILD_TARGET");
+    const std::string& cmakeBuildAdditionalFlags = cmNonempty(args.Flags)
+      ? args.Flags
+      : mf.GetSafeDefinition("CTEST_BUILD_FLAGS");
+    const std::string& cmakeBuildTarget = cmNonempty(args.Target)
+      ? args.Target
+      : mf.GetSafeDefinition("CTEST_BUILD_TARGET");
 
     if (cmNonempty(cmakeGeneratorName)) {
       if (cmakeBuildConfiguration.empty()) {
         cmakeBuildConfiguration = "Release";
       }
-      if (this->GlobalGenerator) {
-        if (this->GlobalGenerator->GetName() != *cmakeGeneratorName) {
-          this->GlobalGenerator.reset();
-        }
-      }
-      if (!this->GlobalGenerator) {
-        this->GlobalGenerator =
-          this->Makefile->GetCMakeInstance()->CreateGlobalGenerator(
-            *cmakeGeneratorName);
-        if (!this->GlobalGenerator) {
-          std::string e = cmStrCat("could not create generator named \"",
-                                   *cmakeGeneratorName, '"');
-          this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e);
-          cmSystemTools::SetFatalErrorOccurred();
-          return nullptr;
-        }
+
+      auto globalGenerator =
+        mf.GetCMakeInstance()->CreateGlobalGenerator(*cmakeGeneratorName);
+      if (!globalGenerator) {
+        std::string e = cmStrCat("could not create generator named \"",
+                                 *cmakeGeneratorName, '"');
+        mf.IssueMessage(MessageType::FATAL_ERROR, e);
+        cmSystemTools::SetFatalErrorOccurred();
+        return nullptr;
       }
       if (cmakeBuildConfiguration.empty()) {
         cmakeBuildConfiguration = "Debug";
       }
 
       std::string dir = this->CTest->GetCTestConfiguration("BuildDirectory");
-      std::string buildCommand =
-        this->GlobalGenerator->GenerateCMakeBuildCommand(
-          cmakeBuildTarget, cmakeBuildConfiguration, this->ParallelLevel,
-          cmakeBuildAdditionalFlags, this->Makefile->IgnoreErrorsCMP0061());
+      std::string buildCommand = globalGenerator->GenerateCMakeBuildCommand(
+        cmakeBuildTarget, cmakeBuildConfiguration, args.ParallelLevel,
+        cmakeBuildAdditionalFlags, mf.IgnoreErrorsCMP0061());
       cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                          "SetMakeCommand:" << buildCommand << "\n",
-                         this->Quiet);
+                         args.Quiet);
       this->CTest->SetCTestConfiguration("MakeCommand", buildCommand,
-                                         this->Quiet);
+                                         args.Quiet);
     } else {
       std::ostringstream ostr;
       /* clang-format off */
@@ -120,38 +107,39 @@
         "is set. Otherwise, set CTEST_BUILD_COMMAND to build the project "
         "with a custom command line.";
       /* clang-format on */
-      this->SetError(ostr.str());
+      status.SetError(ostr.str());
       return nullptr;
     }
   }
 
-  if (cmValue useLaunchers =
-        this->Makefile->GetDefinition("CTEST_USE_LAUNCHERS")) {
+  if (cmValue useLaunchers = mf.GetDefinition("CTEST_USE_LAUNCHERS")) {
     this->CTest->SetCTestConfiguration("UseLaunchers", *useLaunchers,
-                                       this->Quiet);
+                                       args.Quiet);
   }
 
   if (cmValue labelsForSubprojects =
-        this->Makefile->GetDefinition("CTEST_LABELS_FOR_SUBPROJECTS")) {
+        mf.GetDefinition("CTEST_LABELS_FOR_SUBPROJECTS")) {
     this->CTest->SetCTestConfiguration("LabelsForSubprojects",
-                                       *labelsForSubprojects, this->Quiet);
+                                       *labelsForSubprojects, args.Quiet);
   }
 
-  handler->SetQuiet(this->Quiet);
-  return handler;
+  handler->SetQuiet(args.Quiet);
+  return std::unique_ptr<cmCTestGenericHandler>(std::move(handler));
 }
 
-bool cmCTestBuildCommand::InitialPass(std::vector<std::string> const& args,
-                                      cmExecutionStatus& status)
+void cmCTestBuildCommand::ProcessAdditionalValues(
+  cmCTestGenericHandler* generic, HandlerArguments const& arguments,
+  cmExecutionStatus& status) const
 {
-  bool ret = this->cmCTestHandlerCommand::InitialPass(args, status);
-  if (!this->NumberErrors.empty()) {
-    this->Makefile->AddDefinition(
-      this->NumberErrors, std::to_string(this->Handler->GetTotalErrors()));
+  cmMakefile& mf = status.GetMakefile();
+  auto const& args = static_cast<BuildArguments const&>(arguments);
+  auto const* handler = static_cast<cmCTestBuildHandler*>(generic);
+  if (!args.NumberErrors.empty()) {
+    mf.AddDefinition(args.NumberErrors,
+                     std::to_string(handler->GetTotalErrors()));
   }
-  if (!this->NumberWarnings.empty()) {
-    this->Makefile->AddDefinition(
-      this->NumberWarnings, std::to_string(this->Handler->GetTotalWarnings()));
+  if (!args.NumberWarnings.empty()) {
+    mf.AddDefinition(args.NumberWarnings,
+                     std::to_string(handler->GetTotalWarnings()));
   }
-  return ret;
 }
diff --git a/Source/CTest/cmCTestBuildCommand.h b/Source/CTest/cmCTestBuildCommand.h
index 3b7f4f9..a23966d 100644
--- a/Source/CTest/cmCTestBuildCommand.h
+++ b/Source/CTest/cmCTestBuildCommand.h
@@ -4,54 +4,42 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
+#include <memory>
 #include <string>
 #include <vector>
 
-#include <cm/memory>
-
 #include "cmCTestHandlerCommand.h"
 
-class cmCommand;
-class cmCTestBuildHandler;
-class cmCTestGenericHandler;
 class cmExecutionStatus;
-class cmGlobalGenerator;
+class cmCTestGenericHandler;
 
-/** \class cmCTestBuild
- * \brief Run a ctest script
- *
- * cmCTestBuildCommand defineds the command to build the project.
- */
 class cmCTestBuildCommand : public cmCTestHandlerCommand
 {
 public:
-  ~cmCTestBuildCommand() override;
-
-  /**
-   * This is a virtual constructor for the command.
-   */
-  std::unique_ptr<cmCommand> Clone() override;
-
-  /**
-   * The name of the command as specified in CMakeList.txt.
-   */
-  std::string GetName() const override { return "ctest_build"; }
-
-  bool InitialPass(std::vector<std::string> const& args,
-                   cmExecutionStatus& status) override;
-
-  std::unique_ptr<cmGlobalGenerator> GlobalGenerator;
+  using cmCTestHandlerCommand::cmCTestHandlerCommand;
 
 protected:
-  cmCTestBuildHandler* Handler;
-  void BindArguments() override;
-  cmCTestGenericHandler* InitializeHandler() override;
+  struct BuildArguments : HandlerArguments
+  {
+    std::string NumberErrors;
+    std::string NumberWarnings;
+    std::string Target;
+    std::string Configuration;
+    std::string Flags;
+    std::string ProjectName;
+    std::string ParallelLevel;
+  };
 
-  std::string NumberErrors;
-  std::string NumberWarnings;
-  std::string Target;
-  std::string Configuration;
-  std::string Flags;
-  std::string ProjectName;
-  std::string ParallelLevel;
+private:
+  std::string GetName() const override { return "ctest_build"; }
+
+  std::unique_ptr<cmCTestGenericHandler> InitializeHandler(
+    HandlerArguments& arguments, cmExecutionStatus& status) const override;
+
+  void ProcessAdditionalValues(cmCTestGenericHandler* handler,
+                               HandlerArguments const& arguments,
+                               cmExecutionStatus& status) const override;
+
+  bool InitialPass(std::vector<std::string> const& args,
+                   cmExecutionStatus& status) const override;
 };
diff --git a/Source/CTest/cmCTestBuildHandler.cxx b/Source/CTest/cmCTestBuildHandler.cxx
index e962cc7..d5d269e 100644
--- a/Source/CTest/cmCTestBuildHandler.cxx
+++ b/Source/CTest/cmCTestBuildHandler.cxx
@@ -176,62 +176,10 @@
   { nullptr, 0, 0 }
 };
 
-cmCTestBuildHandler::cmCTestBuildHandler()
+cmCTestBuildHandler::cmCTestBuildHandler(cmCTest* ctest)
+  : Superclass(ctest)
+  , LastErrorOrWarning(this->ErrorsAndWarnings.end())
 {
-  this->MaxPreContext = 10;
-  this->MaxPostContext = 10;
-
-  this->MaxErrors = 50;
-  this->MaxWarnings = 50;
-
-  this->LastErrorOrWarning = this->ErrorsAndWarnings.end();
-
-  this->UseCTestLaunch = false;
-}
-
-void cmCTestBuildHandler::Initialize()
-{
-  this->Superclass::Initialize();
-  this->StartBuild.clear();
-  this->EndBuild.clear();
-  this->CustomErrorMatches.clear();
-  this->CustomErrorExceptions.clear();
-  this->CustomWarningMatches.clear();
-  this->CustomWarningExceptions.clear();
-  this->ReallyCustomWarningMatches.clear();
-  this->ReallyCustomWarningExceptions.clear();
-  this->ErrorWarningFileLineRegex.clear();
-
-  this->ErrorMatchRegex.clear();
-  this->ErrorExceptionRegex.clear();
-  this->WarningMatchRegex.clear();
-  this->WarningExceptionRegex.clear();
-  this->BuildProcessingQueue.clear();
-  this->BuildProcessingErrorQueue.clear();
-  this->BuildOutputLogSize = 0;
-  this->CurrentProcessingLine.clear();
-
-  this->SimplifySourceDir.clear();
-  this->SimplifyBuildDir.clear();
-  this->OutputLineCounter = 0;
-  this->ErrorsAndWarnings.clear();
-  this->LastErrorOrWarning = this->ErrorsAndWarnings.end();
-  this->PostContextCount = 0;
-  this->MaxPreContext = 10;
-  this->MaxPostContext = 10;
-  this->PreContext.clear();
-
-  this->TotalErrors = 0;
-  this->TotalWarnings = 0;
-  this->LastTickChar = 0;
-
-  this->ErrorQuotaReached = false;
-  this->WarningQuotaReached = false;
-
-  this->MaxErrors = 50;
-  this->MaxWarnings = 50;
-
-  this->UseCTestLaunch = false;
 }
 
 void cmCTestBuildHandler::PopulateCustomVectors(cmMakefile* mf)
@@ -502,7 +450,7 @@
 
 void cmCTestBuildHandler::GenerateXMLHeader(cmXMLWriter& xml)
 {
-  this->CTest->StartXML(xml, this->AppendXML);
+  this->CTest->StartXML(xml, this->CMake, this->AppendXML);
   this->CTest->GenerateSubprojectsOutput(xml);
   xml.StartElement("Build");
   xml.Element("StartDateTime", this->StartBuild);
@@ -580,9 +528,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 +558,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/cmCTestBuildHandler.h b/Source/CTest/cmCTestBuildHandler.h
index 90945b1..98fe2da 100644
--- a/Source/CTest/cmCTestBuildHandler.h
+++ b/Source/CTest/cmCTestBuildHandler.h
@@ -20,6 +20,7 @@
 class cmMakefile;
 class cmStringReplaceHelper;
 class cmXMLWriter;
+class cmCTest;
 
 /** \class cmCTestBuildHandler
  * \brief A class that handles ctest -S invocations
@@ -36,17 +37,12 @@
    */
   int ProcessHandler() override;
 
-  cmCTestBuildHandler();
+  cmCTestBuildHandler(cmCTest* ctest);
 
   void PopulateCustomVectors(cmMakefile* mf) override;
 
-  /**
-   * Initialize handler
-   */
-  void Initialize() override;
-
-  int GetTotalErrors() { return this->TotalErrors; }
-  int GetTotalWarnings() { return this->TotalWarnings; }
+  int GetTotalErrors() const { return this->TotalErrors; }
+  int GetTotalWarnings() const { return this->TotalWarnings; }
 
 private:
   std::string GetMakeCommand();
@@ -120,34 +116,34 @@
 
   t_BuildProcessingQueueType BuildProcessingQueue;
   t_BuildProcessingQueueType BuildProcessingErrorQueue;
-  size_t BuildOutputLogSize;
+  size_t BuildOutputLogSize = 0;
   std::vector<char> CurrentProcessingLine;
 
   std::string SimplifySourceDir;
   std::string SimplifyBuildDir;
-  size_t OutputLineCounter;
+  size_t OutputLineCounter = 0;
   using t_ErrorsAndWarningsVector = std::vector<cmCTestBuildErrorWarning>;
   t_ErrorsAndWarningsVector ErrorsAndWarnings;
   t_ErrorsAndWarningsVector::iterator LastErrorOrWarning;
-  size_t PostContextCount;
-  size_t MaxPreContext;
-  size_t MaxPostContext;
+  size_t PostContextCount = 0;
+  size_t MaxPreContext = 10;
+  size_t MaxPostContext = 10;
   std::deque<std::string> PreContext;
 
-  int TotalErrors;
-  int TotalWarnings;
-  char LastTickChar;
+  int TotalErrors = 0;
+  int TotalWarnings = 0;
+  char LastTickChar = '\0';
 
-  bool ErrorQuotaReached;
-  bool WarningQuotaReached;
+  bool ErrorQuotaReached = false;
+  bool WarningQuotaReached = false;
 
-  int MaxErrors;
-  int MaxWarnings;
+  int MaxErrors = 50;
+  int MaxWarnings = 50;
 
   // Used to remove ANSI color codes before checking for errors and warnings.
   cmStringReplaceHelper* ColorRemover;
 
-  bool UseCTestLaunch;
+  bool UseCTestLaunch = false;
   std::string CTestLaunchDir;
   class LaunchHelper;
 
diff --git a/Source/CTest/cmCTestCommand.cxx b/Source/CTest/cmCTestCommand.cxx
new file mode 100644
index 0000000..6ecd18b
--- /dev/null
+++ b/Source/CTest/cmCTestCommand.cxx
@@ -0,0 +1,19 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmCTestCommand.h"
+
+#include "cmExecutionStatus.h"
+#include "cmMakefile.h"
+
+bool cmCTestCommand::operator()(std::vector<cmListFileArgument> const& args,
+                                cmExecutionStatus& status) const
+{
+  cmMakefile& mf = status.GetMakefile();
+  std::vector<std::string> expandedArguments;
+  if (!mf.ExpandArguments(args, expandedArguments)) {
+    // There was an error expanding arguments.  It was already
+    // reported, so we can skip this command without error.
+    return true;
+  }
+  return this->InitialPass(expandedArguments, status);
+}
diff --git a/Source/CTest/cmCTestCommand.h b/Source/CTest/cmCTestCommand.h
index 007378d..777910a 100644
--- a/Source/CTest/cmCTestCommand.h
+++ b/Source/CTest/cmCTestCommand.h
@@ -2,10 +2,14 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #pragma once
 
-#include "cmCommand.h"
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <string>
+#include <vector>
 
 class cmCTest;
-class cmCTestScriptHandler;
+class cmExecutionStatus;
+struct cmListFileArgument;
 
 /** \class cmCTestCommand
  * \brief A superclass for all commands added to the CTestScriptHandler
@@ -14,15 +18,25 @@
  * the ctest script handlers parser.
  *
  */
-class cmCTestCommand : public cmCommand
+class cmCTestCommand
 {
 public:
-  cmCTestCommand()
+  cmCTestCommand(cmCTest* ctest)
+    : CTest(ctest)
   {
-    this->CTest = nullptr;
-    this->CTestScriptHandler = nullptr;
   }
 
-  cmCTest* CTest;
-  cmCTestScriptHandler* CTestScriptHandler;
+  virtual ~cmCTestCommand() = default;
+  cmCTestCommand(cmCTestCommand const&) = default;
+  cmCTestCommand& operator=(cmCTestCommand const&) = default;
+
+  bool operator()(std::vector<cmListFileArgument> const& args,
+                  cmExecutionStatus& status) const;
+
+private:
+  virtual bool InitialPass(std::vector<std::string> const& args,
+                           cmExecutionStatus&) const = 0;
+
+protected:
+  cmCTest* CTest = nullptr;
 };
diff --git a/Source/CTest/cmCTestConfigureCommand.cxx b/Source/CTest/cmCTestConfigureCommand.cxx
index e0326a9..6255416 100644
--- a/Source/CTest/cmCTestConfigureCommand.cxx
+++ b/Source/CTest/cmCTestConfigureCommand.cxx
@@ -4,12 +4,17 @@
 
 #include <cstring>
 #include <sstream>
+#include <utility>
 #include <vector>
 
+#include <cm/memory>
 #include <cmext/string_view>
 
+#include "cmArgumentParser.h"
 #include "cmCTest.h"
 #include "cmCTestConfigureHandler.h"
+#include "cmCTestGenericHandler.h"
+#include "cmExecutionStatus.h"
 #include "cmGlobalGenerator.h"
 #include "cmList.h"
 #include "cmMakefile.h"
@@ -18,42 +23,38 @@
 #include "cmValue.h"
 #include "cmake.h"
 
-void cmCTestConfigureCommand::BindArguments()
+std::unique_ptr<cmCTestGenericHandler>
+cmCTestConfigureCommand::InitializeHandler(HandlerArguments& arguments,
+                                           cmExecutionStatus& status) const
 {
-  this->cmCTestHandlerCommand::BindArguments();
-  this->Bind("OPTIONS"_s, this->Options);
-}
-
-cmCTestGenericHandler* cmCTestConfigureCommand::InitializeHandler()
-{
+  cmMakefile& mf = status.GetMakefile();
+  auto const& args = static_cast<ConfigureArguments&>(arguments);
   cmList options;
 
-  if (!this->Options.empty()) {
-    options.assign(this->Options);
+  if (!args.Options.empty()) {
+    options.assign(args.Options);
   }
 
   if (this->CTest->GetCTestConfiguration("BuildDirectory").empty()) {
-    this->SetError(
+    status.SetError(
       "Build directory not specified. Either use BUILD "
       "argument to CTEST_CONFIGURE command or set CTEST_BINARY_DIRECTORY "
       "variable");
     return nullptr;
   }
 
-  cmValue ctestConfigureCommand =
-    this->Makefile->GetDefinition("CTEST_CONFIGURE_COMMAND");
+  cmValue ctestConfigureCommand = mf.GetDefinition("CTEST_CONFIGURE_COMMAND");
 
   if (cmNonempty(ctestConfigureCommand)) {
     this->CTest->SetCTestConfiguration("ConfigureCommand",
-                                       *ctestConfigureCommand, this->Quiet);
+                                       *ctestConfigureCommand, args.Quiet);
   } else {
-    cmValue cmakeGeneratorName =
-      this->Makefile->GetDefinition("CTEST_CMAKE_GENERATOR");
+    cmValue cmakeGeneratorName = mf.GetDefinition("CTEST_CMAKE_GENERATOR");
     if (cmNonempty(cmakeGeneratorName)) {
       const std::string& source_dir =
         this->CTest->GetCTestConfiguration("SourceDirectory");
       if (source_dir.empty()) {
-        this->SetError(
+        status.SetError(
           "Source directory not specified. Either use SOURCE "
           "argument to CTEST_CONFIGURE command or set CTEST_SOURCE_DIRECTORY "
           "variable");
@@ -64,15 +65,15 @@
       if (!cmSystemTools::FileExists(cmakelists_file)) {
         std::ostringstream e;
         e << "CMakeLists.txt file does not exist [" << cmakelists_file << "]";
-        this->SetError(e.str());
+        status.SetError(e.str());
         return nullptr;
       }
 
       bool multiConfig = false;
       bool cmakeBuildTypeInOptions = false;
 
-      auto gg = this->Makefile->GetCMakeInstance()->CreateGlobalGenerator(
-        *cmakeGeneratorName);
+      auto gg =
+        mf.GetCMakeInstance()->CreateGlobalGenerator(*cmakeGeneratorName);
       if (gg) {
         multiConfig = gg->IsMultiConfig();
         gg.reset();
@@ -99,7 +100,7 @@
         cmakeConfigureCommand += "\"";
       }
 
-      if (this->Makefile->IsOn("CTEST_USE_LAUNCHERS")) {
+      if (mf.IsOn("CTEST_USE_LAUNCHERS")) {
         cmakeConfigureCommand += " \"-DCTEST_USE_LAUNCHERS:BOOL=TRUE\"";
       }
 
@@ -108,7 +109,7 @@
       cmakeConfigureCommand += "\"";
 
       cmValue cmakeGeneratorPlatform =
-        this->Makefile->GetDefinition("CTEST_CMAKE_GENERATOR_PLATFORM");
+        mf.GetDefinition("CTEST_CMAKE_GENERATOR_PLATFORM");
       if (cmNonempty(cmakeGeneratorPlatform)) {
         cmakeConfigureCommand += " \"-A";
         cmakeConfigureCommand += *cmakeGeneratorPlatform;
@@ -116,7 +117,7 @@
       }
 
       cmValue cmakeGeneratorToolset =
-        this->Makefile->GetDefinition("CTEST_CMAKE_GENERATOR_TOOLSET");
+        mf.GetDefinition("CTEST_CMAKE_GENERATOR_TOOLSET");
       if (cmNonempty(cmakeGeneratorToolset)) {
         cmakeConfigureCommand += " \"-T";
         cmakeConfigureCommand += *cmakeGeneratorToolset;
@@ -133,9 +134,9 @@
       cmakeConfigureCommand += "\"";
 
       this->CTest->SetCTestConfiguration("ConfigureCommand",
-                                         cmakeConfigureCommand, this->Quiet);
+                                         cmakeConfigureCommand, args.Quiet);
     } else {
-      this->SetError(
+      status.SetError(
         "Configure command is not specified. If this is a "
         "\"built with CMake\" project, set CTEST_CMAKE_GENERATOR. If not, "
         "set CTEST_CONFIGURE_COMMAND.");
@@ -144,13 +145,25 @@
   }
 
   if (cmValue labelsForSubprojects =
-        this->Makefile->GetDefinition("CTEST_LABELS_FOR_SUBPROJECTS")) {
+        mf.GetDefinition("CTEST_LABELS_FOR_SUBPROJECTS")) {
     this->CTest->SetCTestConfiguration("LabelsForSubprojects",
-                                       *labelsForSubprojects, this->Quiet);
+                                       *labelsForSubprojects, args.Quiet);
   }
 
-  cmCTestConfigureHandler* handler = this->CTest->GetConfigureHandler();
-  handler->Initialize();
-  handler->SetQuiet(this->Quiet);
-  return handler;
+  auto handler = cm::make_unique<cmCTestConfigureHandler>(this->CTest);
+  handler->SetQuiet(args.Quiet);
+  return std::unique_ptr<cmCTestGenericHandler>(std::move(handler));
+}
+
+bool cmCTestConfigureCommand::InitialPass(std::vector<std::string> const& args,
+                                          cmExecutionStatus& status) const
+{
+  using Args = ConfigureArguments;
+  static auto const parser =
+    cmArgumentParser<Args>{ MakeHandlerParser<Args>() } //
+      .Bind("OPTIONS"_s, &ConfigureArguments::Options);
+
+  return this->Invoke(parser, args, status, [&](ConfigureArguments& a) {
+    return this->ExecuteHandlerCommand(a, status);
+  });
 }
diff --git a/Source/CTest/cmCTestConfigureCommand.h b/Source/CTest/cmCTestConfigureCommand.h
index f338637..c2c6b3a 100644
--- a/Source/CTest/cmCTestConfigureCommand.h
+++ b/Source/CTest/cmCTestConfigureCommand.h
@@ -4,43 +4,32 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
+#include <memory>
 #include <string>
-#include <utility>
-
-#include <cm/memory>
+#include <vector>
 
 #include "cmCTestHandlerCommand.h"
-#include "cmCommand.h"
 
+class cmExecutionStatus;
 class cmCTestGenericHandler;
 
-/** \class cmCTestConfigure
- * \brief Run a ctest script
- *
- * cmCTestConfigureCommand defineds the command to configures the project.
- */
 class cmCTestConfigureCommand : public cmCTestHandlerCommand
 {
 public:
-  /**
-   * This is a virtual constructor for the command.
-   */
-  std::unique_ptr<cmCommand> Clone() override
-  {
-    auto ni = cm::make_unique<cmCTestConfigureCommand>();
-    ni->CTest = this->CTest;
-    ni->CTestScriptHandler = this->CTestScriptHandler;
-    return std::unique_ptr<cmCommand>(std::move(ni));
-  }
-
-  /**
-   * The name of the command as specified in CMakeList.txt.
-   */
-  std::string GetName() const override { return "ctest_configure"; }
+  using cmCTestHandlerCommand::cmCTestHandlerCommand;
 
 protected:
-  void BindArguments() override;
-  cmCTestGenericHandler* InitializeHandler() override;
+  struct ConfigureArguments : HandlerArguments
+  {
+    std::string Options;
+  };
 
-  std::string Options;
+private:
+  std::string GetName() const override { return "ctest_configure"; }
+
+  std::unique_ptr<cmCTestGenericHandler> InitializeHandler(
+    HandlerArguments& arguments, cmExecutionStatus& status) const override;
+
+  bool InitialPass(std::vector<std::string> const& args,
+                   cmExecutionStatus& status) const override;
 };
diff --git a/Source/CTest/cmCTestConfigureHandler.cxx b/Source/CTest/cmCTestConfigureHandler.cxx
index dd8952f..35b6dce 100644
--- a/Source/CTest/cmCTestConfigureHandler.cxx
+++ b/Source/CTest/cmCTestConfigureHandler.cxx
@@ -11,11 +11,9 @@
 #include "cmGeneratedFileStream.h"
 #include "cmXMLWriter.h"
 
-cmCTestConfigureHandler::cmCTestConfigureHandler() = default;
-
-void cmCTestConfigureHandler::Initialize()
+cmCTestConfigureHandler::cmCTestConfigureHandler(cmCTest* ctest)
+  : Superclass(ctest)
 {
-  this->Superclass::Initialize();
 }
 
 // clearly it would be nice if this were broken up into a few smaller
@@ -71,7 +69,7 @@
 
     if (os) {
       cmXMLWriter xml(os);
-      this->CTest->StartXML(xml, this->AppendXML);
+      this->CTest->StartXML(xml, this->CMake, this->AppendXML);
       this->CTest->GenerateSubprojectsOutput(xml);
       xml.StartElement("Configure");
       xml.Element("StartDateTime", start_time);
diff --git a/Source/CTest/cmCTestConfigureHandler.h b/Source/CTest/cmCTestConfigureHandler.h
index 2aad98c..bcbead7 100644
--- a/Source/CTest/cmCTestConfigureHandler.h
+++ b/Source/CTest/cmCTestConfigureHandler.h
@@ -6,6 +6,8 @@
 
 #include "cmCTestGenericHandler.h"
 
+class cmCTest;
+
 /** \class cmCTestConfigureHandler
  * \brief A class that handles ctest -S invocations
  *
@@ -20,7 +22,5 @@
    */
   int ProcessHandler() override;
 
-  cmCTestConfigureHandler();
-
-  void Initialize() override;
+  cmCTestConfigureHandler(cmCTest* ctest);
 };
diff --git a/Source/CTest/cmCTestCoverageCommand.cxx b/Source/CTest/cmCTestCoverageCommand.cxx
index 97b0a89..6bed1a3 100644
--- a/Source/CTest/cmCTestCoverageCommand.cxx
+++ b/Source/CTest/cmCTestCoverageCommand.cxx
@@ -3,36 +3,50 @@
 #include "cmCTestCoverageCommand.h"
 
 #include <set>
+#include <utility>
 
+#include <cm/memory>
 #include <cmext/string_view>
 
+#include "cmArgumentParser.h"
 #include "cmCTest.h"
 #include "cmCTestCoverageHandler.h"
+#include "cmCTestGenericHandler.h"
+#include "cmExecutionStatus.h"
 
-class cmCTestGenericHandler;
+class cmMakefile;
 
-void cmCTestCoverageCommand::BindArguments()
+std::unique_ptr<cmCTestGenericHandler>
+cmCTestCoverageCommand::InitializeHandler(HandlerArguments& arguments,
+                                          cmExecutionStatus& status) const
 {
-  this->cmCTestHandlerCommand::BindArguments();
-  this->Bind("LABELS"_s, this->Labels);
-}
-
-cmCTestGenericHandler* cmCTestCoverageCommand::InitializeHandler()
-{
+  cmMakefile& mf = status.GetMakefile();
+  auto& args = static_cast<CoverageArguments&>(arguments);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "CoverageCommand", "CTEST_COVERAGE_COMMAND", this->Quiet);
+    &mf, "CoverageCommand", "CTEST_COVERAGE_COMMAND", args.Quiet);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "CoverageExtraFlags", "CTEST_COVERAGE_EXTRA_FLAGS",
-    this->Quiet);
-  cmCTestCoverageHandler* handler = this->CTest->GetCoverageHandler();
-  handler->Initialize();
+    &mf, "CoverageExtraFlags", "CTEST_COVERAGE_EXTRA_FLAGS", args.Quiet);
+  auto handler = cm::make_unique<cmCTestCoverageHandler>(this->CTest);
 
   // If a LABELS option was given, select only files with the labels.
-  if (this->Labels) {
+  if (args.Labels) {
     handler->SetLabelFilter(
-      std::set<std::string>(this->Labels->begin(), this->Labels->end()));
+      std::set<std::string>(args.Labels->begin(), args.Labels->end()));
   }
 
-  handler->SetQuiet(this->Quiet);
-  return handler;
+  handler->SetQuiet(args.Quiet);
+  return std::unique_ptr<cmCTestGenericHandler>(std::move(handler));
+}
+
+bool cmCTestCoverageCommand::InitialPass(std::vector<std::string> const& args,
+                                         cmExecutionStatus& status) const
+{
+  using Args = CoverageArguments;
+  static auto const parser =
+    cmArgumentParser<Args>{ MakeHandlerParser<Args>() } //
+      .Bind("LABELS"_s, &CoverageArguments::Labels);
+
+  return this->Invoke(parser, args, status, [&](CoverageArguments& a) {
+    return this->ExecuteHandlerCommand(a, status);
+  });
 }
diff --git a/Source/CTest/cmCTestCoverageCommand.h b/Source/CTest/cmCTestCoverageCommand.h
index 55c68b2..dfbb9be 100644
--- a/Source/CTest/cmCTestCoverageCommand.h
+++ b/Source/CTest/cmCTestCoverageCommand.h
@@ -4,46 +4,35 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
+#include <memory>
 #include <string>
-#include <utility>
 #include <vector>
 
-#include <cm/memory>
 #include <cm/optional>
 
 #include "cmArgumentParserTypes.h" // IWYU pragma: keep
 #include "cmCTestHandlerCommand.h"
-#include "cmCommand.h"
 
+class cmExecutionStatus;
 class cmCTestGenericHandler;
 
-/** \class cmCTestCoverage
- * \brief Run a ctest script
- *
- * cmCTestCoverageCommand defineds the command to test the project.
- */
 class cmCTestCoverageCommand : public cmCTestHandlerCommand
 {
 public:
-  /**
-   * This is a virtual constructor for the command.
-   */
-  std::unique_ptr<cmCommand> Clone() override
-  {
-    auto ni = cm::make_unique<cmCTestCoverageCommand>();
-    ni->CTest = this->CTest;
-    ni->CTestScriptHandler = this->CTestScriptHandler;
-    return std::unique_ptr<cmCommand>(std::move(ni));
-  }
-
-  /**
-   * The name of the command as specified in CMakeList.txt.
-   */
-  std::string GetName() const override { return "ctest_coverage"; }
+  using cmCTestHandlerCommand::cmCTestHandlerCommand;
 
 protected:
-  void BindArguments() override;
-  cmCTestGenericHandler* InitializeHandler() override;
+  struct CoverageArguments : HandlerArguments
+  {
+    cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> Labels;
+  };
 
-  cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> Labels;
+private:
+  std::string GetName() const override { return "ctest_coverage"; }
+
+  std::unique_ptr<cmCTestGenericHandler> InitializeHandler(
+    HandlerArguments& arguments, cmExecutionStatus& status) const override;
+
+  bool InitialPass(std::vector<std::string> const& args,
+                   cmExecutionStatus& status) const override;
 };
diff --git a/Source/CTest/cmCTestCoverageHandler.cxx b/Source/CTest/cmCTestCoverageHandler.cxx
index cec4a7e..2e63fc8 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>
@@ -41,17 +40,9 @@
 
 #define SAFEDIV(x, y) (((y) != 0) ? ((x) / (y)) : (0))
 
-cmCTestCoverageHandler::cmCTestCoverageHandler() = default;
-
-void cmCTestCoverageHandler::Initialize()
+cmCTestCoverageHandler::cmCTestCoverageHandler(cmCTest* ctest)
+  : Superclass(ctest)
 {
-  this->Superclass::Initialize();
-  this->CustomCoverageExclude.clear();
-  this->SourceLabels.clear();
-  this->TargetDirs.clear();
-  this->LabelIdMap.clear();
-  this->Labels.clear();
-  this->LabelFilter.clear();
 }
 
 void cmCTestCoverageHandler::CleanCoverageLogFiles(std::ostream& log)
@@ -100,7 +91,7 @@
 
 void cmCTestCoverageHandler::StartCoverageLogXML(cmXMLWriter& xml)
 {
-  this->CTest->StartXML(xml, this->AppendXML);
+  this->CTest->StartXML(xml, this->CMake, this->AppendXML);
   xml.StartElement("CoverageLog");
   xml.Element("StartDateTime", this->CTest->CurrentTime());
   xml.Element("StartTime", std::chrono::system_clock::now());
@@ -331,7 +322,7 @@
   covSumFile.setf(std::ios::fixed, std::ios::floatfield);
   covSumFile.precision(2);
 
-  this->CTest->StartXML(covSumXML, this->AppendXML);
+  this->CTest->StartXML(covSumXML, this->CMake, this->AppendXML);
   // Produce output xml files
 
   covSumXML.StartElement("Coverage");
@@ -1325,10 +1316,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 +1538,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;
   }
 
@@ -1915,7 +1901,7 @@
                "Cannot open coverage summary file." << std::endl);
     return 0;
   }
-  this->CTest->StartXML(xml, this->AppendXML);
+  this->CTest->StartXML(xml, this->CMake, this->AppendXML);
   auto elapsed_time_start = std::chrono::steady_clock::now();
   std::string coverage_start_time = this->CTest->CurrentTime();
   xml.StartElement("Coverage");
diff --git a/Source/CTest/cmCTestCoverageHandler.h b/Source/CTest/cmCTestCoverageHandler.h
index 8732723..89969ce 100644
--- a/Source/CTest/cmCTestCoverageHandler.h
+++ b/Source/CTest/cmCTestCoverageHandler.h
@@ -17,6 +17,7 @@
 class cmGeneratedFileStream;
 class cmMakefile;
 class cmXMLWriter;
+class cmCTest;
 
 class cmCTestCoverageHandlerContainer
 {
@@ -44,9 +45,7 @@
    */
   int ProcessHandler() override;
 
-  cmCTestCoverageHandler();
-
-  void Initialize() override;
+  cmCTestCoverageHandler(cmCTest* ctest);
 
   /**
    * This method is called when reading CTest custom file
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.cxx b/Source/CTest/cmCTestGenericHandler.cxx
index dd69968..e4acbef 100644
--- a/Source/CTest/cmCTestGenericHandler.cxx
+++ b/Source/CTest/cmCTestGenericHandler.cxx
@@ -3,113 +3,20 @@
 #include "cmCTestGenericHandler.h"
 
 #include <sstream>
-#include <utility>
 
 #include "cmCTest.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 
-cmCTestGenericHandler::cmCTestGenericHandler()
+cmCTestGenericHandler::cmCTestGenericHandler(cmCTest* ctest)
+  : CTest(ctest)
 {
-  this->HandlerVerbose = cmSystemTools::OUTPUT_NONE;
-  this->CTest = nullptr;
-  this->SubmitIndex = 0;
-  this->AppendXML = false;
-  this->Quiet = false;
-  this->TestLoad = 0;
+  this->SetVerbose(ctest->GetExtraVerbose());
+  this->SetSubmitIndex(ctest->GetSubmitIndex());
 }
 
 cmCTestGenericHandler::~cmCTestGenericHandler() = default;
 
-namespace {
-/* Modify the given `map`, setting key `op` to `value` if `value`
- * is non-null, otherwise removing key `op` (if it exists).
- */
-void SetMapValue(cmCTestGenericHandler::t_StringToString& map,
-                 const std::string& op, const std::string& value)
-{
-  map[op] = value;
-}
-void SetMapValue(cmCTestGenericHandler::t_StringToString& map,
-                 const std::string& op, cmValue value)
-{
-  if (!value) {
-    map.erase(op);
-    return;
-  }
-
-  map[op] = *value;
-}
-}
-
-void cmCTestGenericHandler::SetOption(const std::string& op,
-                                      const std::string& value)
-{
-  SetMapValue(this->Options, op, value);
-}
-void cmCTestGenericHandler::SetOption(const std::string& op, cmValue value)
-{
-  SetMapValue(this->Options, op, value);
-}
-
-void cmCTestGenericHandler::SetPersistentOption(const std::string& op,
-                                                const std::string& value)
-{
-  this->SetOption(op, value);
-  SetMapValue(this->PersistentOptions, op, value);
-}
-void cmCTestGenericHandler::SetPersistentOption(const std::string& op,
-                                                cmValue value)
-{
-  this->SetOption(op, value);
-  SetMapValue(this->PersistentOptions, op, value);
-}
-
-void cmCTestGenericHandler::AddMultiOption(const std::string& op,
-                                           const std::string& value)
-{
-  if (!value.empty()) {
-    this->MultiOptions[op].emplace_back(value);
-  }
-}
-
-void cmCTestGenericHandler::AddPersistentMultiOption(const std::string& op,
-                                                     const std::string& value)
-{
-  if (!value.empty()) {
-    this->MultiOptions[op].emplace_back(value);
-    this->PersistentMultiOptions[op].emplace_back(value);
-  }
-}
-
-void cmCTestGenericHandler::Initialize()
-{
-  this->AppendXML = false;
-  this->TestLoad = 0;
-  this->Options = this->PersistentOptions;
-  this->MultiOptions = this->PersistentMultiOptions;
-}
-
-cmValue cmCTestGenericHandler::GetOption(const std::string& op)
-{
-  auto remit = this->Options.find(op);
-  if (remit == this->Options.end()) {
-    return nullptr;
-  }
-  return cmValue(remit->second);
-}
-
-std::vector<std::string> cmCTestGenericHandler::GetMultiOption(
-  const std::string& optionName) const
-{
-  // Avoid inserting a key, which MultiOptions[op] would do.
-  auto remit = this->MultiOptions.find(optionName);
-  if (remit == this->MultiOptions.end()) {
-    return {};
-  }
-  return remit->second;
-}
-
 bool cmCTestGenericHandler::StartResultingXML(cmCTest::Part part,
                                               const char* name,
                                               cmGeneratedFileStream& xofs)
diff --git a/Source/CTest/cmCTestGenericHandler.h b/Source/CTest/cmCTestGenericHandler.h
index a189d60..24251c0 100644
--- a/Source/CTest/cmCTestGenericHandler.h
+++ b/Source/CTest/cmCTestGenericHandler.h
@@ -4,17 +4,15 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
-#include <cstddef>
 #include <map>
 #include <string>
-#include <vector>
 
 #include "cmCTest.h"
 #include "cmSystemTools.h"
-#include "cmValue.h"
 
 class cmGeneratedFileStream;
 class cmMakefile;
+class cmake;
 
 /** \class cmCTestGenericHandler
  * \brief A superclass of all CTest Handlers
@@ -44,69 +42,17 @@
   virtual int ProcessHandler() = 0;
 
   /**
-   * Process command line arguments that are applicable for the handler
+   * Get the CTest instance
    */
-  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();
-
-  /**
-   * Set the CTest instance
-   */
-  void SetCTestInstance(cmCTest* ctest) { this->CTest = ctest; }
   cmCTest* GetCTestInstance() { return this->CTest; }
 
   /**
    * Construct handler
    */
-  cmCTestGenericHandler();
+  cmCTestGenericHandler(cmCTest* ctest);
   virtual ~cmCTestGenericHandler();
 
   using t_StringToString = std::map<std::string, std::string>;
-  using t_StringToMultiString =
-    std::map<std::string, std::vector<std::string>>;
-
-  /**
-   * Options collect a single value from flags; passing the
-   * flag multiple times on the command-line *overwrites* values,
-   * and only the last one specified counts. Set an option to
-   * nullptr to "unset" it.
-   *
-   * The value is stored as a string. The values set for single
-   * and multi-options (see below) live in different spaces,
-   * so calling a single-getter for a key that has only been set
-   * as a multi-value will return nullptr.
-   */
-  void SetPersistentOption(const std::string& op, const std::string& value);
-  void SetPersistentOption(const std::string& op, cmValue value);
-  void SetOption(const std::string& op, const std::string& value);
-  void SetOption(const std::string& op, cmValue value);
-  cmValue GetOption(const std::string& op);
-
-  /**
-   * Multi-Options collect one or more values from flags; passing
-   * the flag multiple times on the command-line *adds* values,
-   * rather than overwriting the previous values.
-   *
-   * Adding an empty value does nothing.
-   *
-   * The value is stored as a vector of strings. The values set for single
-   * (see above) and multi-options live in different spaces,
-   * so calling a multi-getter for a key that has only been set
-   * as a single-value will return an empty vector.
-   */
-  void AddPersistentMultiOption(const std::string& optionName,
-                                const std::string& value);
-  void AddMultiOption(const std::string& optionName, const std::string& value);
-  std::vector<std::string> GetMultiOption(const std::string& op) const;
 
   void SetSubmitIndex(int idx) { this->SubmitIndex = idx; }
   int GetSubmitIndex() { return this->SubmitIndex; }
@@ -117,21 +63,20 @@
   void SetTestLoad(unsigned long load) { this->TestLoad = load; }
   unsigned long GetTestLoad() const { return this->TestLoad; }
 
+  void SetCMakeInstance(cmake* cm) { this->CMake = cm; }
+
 protected:
   bool StartResultingXML(cmCTest::Part part, const char* name,
                          cmGeneratedFileStream& xofs);
   bool StartLogFile(const char* name, cmGeneratedFileStream& xofs);
 
-  bool AppendXML;
-  bool Quiet;
-  unsigned long TestLoad;
-  cmSystemTools::OutputOption HandlerVerbose;
+  bool AppendXML = false;
+  bool Quiet = false;
+  unsigned long TestLoad = 0;
+  cmSystemTools::OutputOption HandlerVerbose = cmSystemTools::OUTPUT_NONE;
   cmCTest* CTest;
-  t_StringToString Options;
-  t_StringToString PersistentOptions;
-  t_StringToMultiString MultiOptions;
-  t_StringToMultiString PersistentMultiOptions;
   t_StringToString LogFileNames;
+  cmake* CMake = nullptr;
 
-  int SubmitIndex;
+  int SubmitIndex = 0;
 };
diff --git a/Source/CTest/cmCTestHandlerCommand.cxx b/Source/CTest/cmCTestHandlerCommand.cxx
index c377d68..6847672 100644
--- a/Source/CTest/cmCTestHandlerCommand.cxx
+++ b/Source/CTest/cmCTestHandlerCommand.cxx
@@ -4,17 +4,14 @@
 
 #include <algorithm>
 #include <cstdlib>
-#include <cstring>
 #include <sstream>
 
 #include <cm/string_view>
-#include <cmext/string_view>
 
 #include "cmCTest.h"
 #include "cmCTestGenericHandler.h"
 #include "cmExecutionStatus.h"
 #include "cmMakefile.h"
-#include "cmMessageType.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmValue.h"
@@ -72,176 +69,150 @@
 };
 }
 
-bool cmCTestHandlerCommand::InitialPass(std::vector<std::string> const& args,
-                                        cmExecutionStatus& status)
+bool cmCTestHandlerCommand::InvokeImpl(
+  BasicArguments& args, std::vector<std::string> const& unparsed,
+  cmExecutionStatus& status, std::function<bool()> handler) const
 {
   // save error state and restore it if needed
   SaveRestoreErrorState errorState;
-  // Allocate space for argument values.
-  this->BindArguments();
-
-  // Process input arguments.
-  std::vector<std::string> unparsedArguments;
-  this->Parse(args, &unparsedArguments);
-  this->CheckArguments();
-
-  std::sort(this->ParsedKeywords.begin(), this->ParsedKeywords.end());
-  auto it = std::adjacent_find(this->ParsedKeywords.begin(),
-                               this->ParsedKeywords.end());
-  if (it != this->ParsedKeywords.end()) {
-    this->Makefile->IssueMessage(
-      MessageType::FATAL_ERROR,
-      cmStrCat("Called with more than one value for ", *it));
-  }
-
-  bool const foundBadArgument = !unparsedArguments.empty();
-  if (foundBadArgument) {
-    this->SetError(cmStrCat("called with unknown argument \"",
-                            unparsedArguments.front(), "\"."));
-  }
-  bool const captureCMakeError = !this->CaptureCMakeError.empty();
-  // now that arguments are parsed check to see if there is a
-  // CAPTURE_CMAKE_ERROR specified let the errorState object know.
-  if (captureCMakeError) {
+  if (!args.CaptureCMakeError.empty()) {
     errorState.CaptureCMakeError();
   }
-  // if we found a bad argument then exit before running command
-  if (foundBadArgument) {
-    // store the cmake error
-    if (captureCMakeError) {
-      this->Makefile->AddDefinition(this->CaptureCMakeError, "-1");
-      std::string const err = this->GetName() + " " + status.GetError();
-      if (!cmSystemTools::FindLastString(err.c_str(), "unknown error.")) {
-        cmCTestLog(this->CTest, ERROR_MESSAGE, err << " error from command\n");
-      }
-      // return success because failure is recorded in CAPTURE_CMAKE_ERROR
+
+  bool success = [&]() -> bool {
+    if (args.MaybeReportError(status.GetMakefile())) {
       return true;
     }
-    // return failure because of bad argument
-    return false;
+
+    std::sort(args.ParsedKeywords.begin(), args.ParsedKeywords.end());
+    auto const it = std::adjacent_find(args.ParsedKeywords.begin(),
+                                       args.ParsedKeywords.end());
+    if (it != args.ParsedKeywords.end()) {
+      status.SetError(cmStrCat("called with more than one value for ", *it));
+      return false;
+    }
+
+    if (!unparsed.empty()) {
+      status.SetError(
+        cmStrCat("called with unknown argument \"", unparsed.front(), "\"."));
+      return false;
+    }
+
+    return handler();
+  }();
+
+  if (args.CaptureCMakeError.empty()) {
+    return success;
   }
 
+  if (!success) {
+    cmCTestLog(this->CTest, ERROR_MESSAGE,
+               this->GetName() << ' ' << status.GetError() << '\n');
+  }
+
+  cmMakefile& mf = status.GetMakefile();
+  success = success && !cmSystemTools::GetErrorOccurredFlag();
+  mf.AddDefinition(args.CaptureCMakeError, success ? "0" : "-1");
+  return true;
+}
+
+bool cmCTestHandlerCommand::ExecuteHandlerCommand(
+  HandlerArguments& args, cmExecutionStatus& status) const
+{
+  cmMakefile& mf = status.GetMakefile();
+
+  // Process input arguments.
+  this->CheckArguments(args, status);
+
   // Set the config type of this ctest to the current value of the
   // CTEST_CONFIGURATION_TYPE script variable if it is defined.
   // The current script value trumps the -C argument on the command
   // line.
-  cmValue ctestConfigType =
-    this->Makefile->GetDefinition("CTEST_CONFIGURATION_TYPE");
+  cmValue ctestConfigType = mf.GetDefinition("CTEST_CONFIGURATION_TYPE");
   if (ctestConfigType) {
     this->CTest->SetConfigType(*ctestConfigType);
   }
 
-  if (!this->Build.empty()) {
+  if (!args.Build.empty()) {
     this->CTest->SetCTestConfiguration(
-      "BuildDirectory", cmSystemTools::CollapseFullPath(this->Build),
-      this->Quiet);
+      "BuildDirectory", cmSystemTools::CollapseFullPath(args.Build),
+      args.Quiet);
   } else {
-    std::string const& bdir =
-      this->Makefile->GetSafeDefinition("CTEST_BINARY_DIRECTORY");
+    std::string const& bdir = mf.GetSafeDefinition("CTEST_BINARY_DIRECTORY");
     if (!bdir.empty()) {
       this->CTest->SetCTestConfiguration(
-        "BuildDirectory", cmSystemTools::CollapseFullPath(bdir), this->Quiet);
+        "BuildDirectory", cmSystemTools::CollapseFullPath(bdir), args.Quiet);
     } else {
       cmCTestLog(this->CTest, ERROR_MESSAGE,
                  "CTEST_BINARY_DIRECTORY not set" << std::endl);
     }
   }
-  if (!this->Source.empty()) {
+  if (!args.Source.empty()) {
     cmCTestLog(this->CTest, DEBUG,
-               "Set source directory to: " << this->Source << std::endl);
+               "Set source directory to: " << args.Source << std::endl);
     this->CTest->SetCTestConfiguration(
-      "SourceDirectory", cmSystemTools::CollapseFullPath(this->Source),
-      this->Quiet);
+      "SourceDirectory", cmSystemTools::CollapseFullPath(args.Source),
+      args.Quiet);
   } else {
     this->CTest->SetCTestConfiguration(
       "SourceDirectory",
       cmSystemTools::CollapseFullPath(
-        this->Makefile->GetSafeDefinition("CTEST_SOURCE_DIRECTORY")),
-      this->Quiet);
+        mf.GetSafeDefinition("CTEST_SOURCE_DIRECTORY")),
+      args.Quiet);
   }
 
-  if (cmValue changeId = this->Makefile->GetDefinition("CTEST_CHANGE_ID")) {
-    this->CTest->SetCTestConfiguration("ChangeId", *changeId, this->Quiet);
+  if (cmValue changeId = mf.GetDefinition("CTEST_CHANGE_ID")) {
+    this->CTest->SetCTestConfiguration("ChangeId", *changeId, args.Quiet);
   }
 
   cmCTestLog(this->CTest, DEBUG, "Initialize handler" << std::endl);
-  cmCTestGenericHandler* handler = this->InitializeHandler();
+  auto handler = this->InitializeHandler(args, status);
   if (!handler) {
     cmCTestLog(this->CTest, ERROR_MESSAGE,
                "Cannot instantiate test handler " << this->GetName()
                                                   << std::endl);
-    if (captureCMakeError) {
-      this->Makefile->AddDefinition(this->CaptureCMakeError, "-1");
-      std::string const& err = status.GetError();
-      if (!cmSystemTools::FindLastString(err.c_str(), "unknown error.")) {
-        cmCTestLog(this->CTest, ERROR_MESSAGE, err << " error from command\n");
-      }
-      return true;
-    }
     return false;
   }
 
-  handler->SetAppendXML(this->Append);
+  handler->SetAppendXML(args.Append);
 
-  handler->PopulateCustomVectors(this->Makefile);
-  if (!this->SubmitIndex.empty()) {
-    handler->SetSubmitIndex(atoi(this->SubmitIndex.c_str()));
+  handler->PopulateCustomVectors(&mf);
+  if (!args.SubmitIndex.empty()) {
+    handler->SetSubmitIndex(atoi(args.SubmitIndex.c_str()));
   }
   cmWorkingDirectory workdir(
     this->CTest->GetCTestConfiguration("BuildDirectory"));
   if (workdir.Failed()) {
-    this->SetError("failed to change directory to " +
-                   this->CTest->GetCTestConfiguration("BuildDirectory") +
-                   " : " + std::strerror(workdir.GetLastResult()));
-    if (captureCMakeError) {
-      this->Makefile->AddDefinition(this->CaptureCMakeError, "-1");
-      cmCTestLog(this->CTest, ERROR_MESSAGE,
-                 this->GetName() << " " << status.GetError() << "\n");
-      // return success because failure is recorded in CAPTURE_CMAKE_ERROR
-      return true;
-    }
+    status.SetError(workdir.GetError());
     return false;
   }
 
+  // reread time limit, as the variable may have been modified.
+  this->CTest->SetTimeLimit(mf.GetDefinition("CTEST_TIME_LIMIT"));
+  handler->SetCMakeInstance(mf.GetCMakeInstance());
+
   int res = handler->ProcessHandler();
-  if (!this->ReturnValue.empty()) {
-    this->Makefile->AddDefinition(this->ReturnValue, std::to_string(res));
+  if (!args.ReturnValue.empty()) {
+    mf.AddDefinition(args.ReturnValue, std::to_string(res));
   }
-  this->ProcessAdditionalValues(handler);
-  // log the error message if there was an error
-  if (captureCMakeError) {
-    const char* returnString = "0";
-    if (cmSystemTools::GetErrorOccurredFlag()) {
-      returnString = "-1";
-      std::string const& err = status.GetError();
-      // print out the error if it is not "unknown error" which means
-      // there was no message
-      if (!cmSystemTools::FindLastString(err.c_str(), "unknown error.")) {
-        cmCTestLog(this->CTest, ERROR_MESSAGE, err);
-      }
-    }
-    // store the captured cmake error state 0 or -1
-    this->Makefile->AddDefinition(this->CaptureCMakeError, returnString);
-  }
+  this->ProcessAdditionalValues(handler.get(), args, status);
   return true;
 }
 
-void cmCTestHandlerCommand::ProcessAdditionalValues(cmCTestGenericHandler*)
+void cmCTestHandlerCommand::CheckArguments(HandlerArguments&,
+                                           cmExecutionStatus&) const
 {
 }
 
-void cmCTestHandlerCommand::BindArguments()
+std::unique_ptr<cmCTestGenericHandler>
+cmCTestHandlerCommand::InitializeHandler(HandlerArguments&,
+                                         cmExecutionStatus&) const
 {
-  this->BindParsedKeywords(this->ParsedKeywords);
-  this->Bind("APPEND"_s, this->Append);
-  this->Bind("QUIET"_s, this->Quiet);
-  this->Bind("RETURN_VALUE"_s, this->ReturnValue);
-  this->Bind("CAPTURE_CMAKE_ERROR"_s, this->CaptureCMakeError);
-  this->Bind("SOURCE"_s, this->Source);
-  this->Bind("BUILD"_s, this->Build);
-  this->Bind("SUBMIT_INDEX"_s, this->SubmitIndex);
-}
+  return nullptr;
+};
 
-void cmCTestHandlerCommand::CheckArguments()
+void cmCTestHandlerCommand::ProcessAdditionalValues(cmCTestGenericHandler*,
+                                                    HandlerArguments const&,
+                                                    cmExecutionStatus&) const
 {
 }
diff --git a/Source/CTest/cmCTestHandlerCommand.h b/Source/CTest/cmCTestHandlerCommand.h
index ed6d9af..c0a2fbc 100644
--- a/Source/CTest/cmCTestHandlerCommand.h
+++ b/Source/CTest/cmCTestHandlerCommand.h
@@ -4,59 +4,95 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
+#include <functional>
+#include <memory>
 #include <string>
+#include <type_traits>
 #include <vector>
 
 #include <cm/string_view>
+#include <cmext/string_view>
 
 #include "cmArgumentParser.h"
 #include "cmCTestCommand.h"
 
-class cmCTestGenericHandler;
 class cmExecutionStatus;
+class cmCTestGenericHandler;
 
-/** \class cmCTestHandler
- * \brief Run a ctest script
- *
- * cmCTestHandlerCommand defineds the command to test the project.
- */
-class cmCTestHandlerCommand
-  : public cmCTestCommand
-  , public cmArgumentParser<void>
+class cmCTestHandlerCommand : public cmCTestCommand
 {
 public:
-  /**
-   * The name of the command as specified in CMakeList.txt.
-   */
-  virtual std::string GetName() const = 0;
-
-  /**
-   * 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;
+  using cmCTestCommand::cmCTestCommand;
 
 protected:
-  virtual cmCTestGenericHandler* InitializeHandler() = 0;
+  struct BasicArguments : ArgumentParser::ParseResult
+  {
+    std::string CaptureCMakeError;
+    std::vector<cm::string_view> ParsedKeywords;
+  };
 
-  virtual void ProcessAdditionalValues(cmCTestGenericHandler* handler);
+  template <typename Args>
+  static auto MakeBasicParser() -> cmArgumentParser<Args>
+  {
+    static_assert(std::is_base_of<BasicArguments, Args>::value, "");
+    return cmArgumentParser<Args>{}
+      .Bind("CAPTURE_CMAKE_ERROR"_s, &BasicArguments::CaptureCMakeError)
+      .BindParsedKeywords(&BasicArguments::ParsedKeywords);
+  }
 
-  // Command argument handling.
-  virtual void BindArguments();
-  virtual void CheckArguments();
+  struct HandlerArguments : BasicArguments
+  {
+    bool Append = false;
+    bool Quiet = false;
+    std::string ReturnValue;
+    std::string Build;
+    std::string Source;
+    std::string SubmitIndex;
+  };
 
-  std::vector<cm::string_view> ParsedKeywords;
-  bool Append = false;
-  bool Quiet = false;
-  std::string CaptureCMakeError;
-  std::string ReturnValue;
-  std::string Build;
-  std::string Source;
-  std::string SubmitIndex;
+  template <typename Args>
+  static auto MakeHandlerParser() -> cmArgumentParser<Args>
+  {
+    static_assert(std::is_base_of<HandlerArguments, Args>::value, "");
+    return cmArgumentParser<Args>{ MakeBasicParser<Args>() }
+      .Bind("APPEND"_s, &HandlerArguments::Append)
+      .Bind("QUIET"_s, &HandlerArguments::Quiet)
+      .Bind("RETURN_VALUE"_s, &HandlerArguments::ReturnValue)
+      .Bind("SOURCE"_s, &HandlerArguments::Source)
+      .Bind("BUILD"_s, &HandlerArguments::Build)
+      .Bind("SUBMIT_INDEX"_s, &HandlerArguments::SubmitIndex);
+  }
+
+protected:
+  template <typename Args, typename Handler>
+  bool Invoke(cmArgumentParser<Args> const& parser,
+              std::vector<std::string> const& arguments,
+              cmExecutionStatus& status, Handler handler) const
+  {
+    std::vector<std::string> unparsed;
+    Args args = parser.Parse(arguments, &unparsed);
+    return this->InvokeImpl(args, unparsed, status,
+                            [&]() -> bool { return handler(args); });
+  };
+
+  bool ExecuteHandlerCommand(HandlerArguments& args,
+                             cmExecutionStatus& status) const;
+
+private:
+  bool InvokeImpl(BasicArguments& args,
+                  std::vector<std::string> const& unparsed,
+                  cmExecutionStatus& status,
+                  std::function<bool()> handler) const;
+
+  virtual std::string GetName() const = 0;
+
+  virtual void CheckArguments(HandlerArguments& arguments,
+                              cmExecutionStatus& status) const;
+
+  virtual std::unique_ptr<cmCTestGenericHandler> InitializeHandler(
+    HandlerArguments& arguments, cmExecutionStatus& status) const;
+
+  virtual void ProcessAdditionalValues(cmCTestGenericHandler*,
+                                       HandlerArguments const& arguments,
+                                       cmExecutionStatus& status) const;
 };
-
-#define CTEST_COMMAND_APPEND_OPTION_DOCS                                      \
-  "The APPEND option marks results for append to those previously "           \
-  "submitted to a dashboard server since the last ctest_start.  "             \
-  "Append semantics are defined by the dashboard server in use."
diff --git a/Source/CTest/cmCTestLaunchReporter.cxx b/Source/CTest/cmCTestLaunchReporter.cxx
index 4b4e5c5..4156294 100644
--- a/Source/CTest/cmCTestLaunchReporter.cxx
+++ b/Source/CTest/cmCTestLaunchReporter.cxx
@@ -25,7 +25,7 @@
   this->Passthru = true;
   this->Status.Finished = true;
   this->ExitCode = 1;
-  this->CWD = cmSystemTools::GetCurrentWorkingDirectory();
+  this->CWD = cmSystemTools::GetLogicalWorkingDirectory();
 
   this->ComputeFileNames();
 
diff --git a/Source/CTest/cmCTestMemCheckCommand.cxx b/Source/CTest/cmCTestMemCheckCommand.cxx
index 37b3628..28d7158 100644
--- a/Source/CTest/cmCTestMemCheckCommand.cxx
+++ b/Source/CTest/cmCTestMemCheckCommand.cxx
@@ -2,49 +2,65 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmCTestMemCheckCommand.h"
 
+#include <utility>
+
+#include <cm/memory>
 #include <cmext/string_view>
 
+#include "cmArgumentParser.h"
 #include "cmCTest.h"
 #include "cmCTestMemCheckHandler.h"
+#include "cmCTestTestHandler.h"
+#include "cmExecutionStatus.h"
 #include "cmMakefile.h"
 
-void cmCTestMemCheckCommand::BindArguments()
+std::unique_ptr<cmCTestTestHandler>
+cmCTestMemCheckCommand::InitializeActualHandler(
+  HandlerArguments& args, cmExecutionStatus& status) const
 {
-  this->cmCTestTestCommand::BindArguments();
-  this->Bind("DEFECT_COUNT"_s, this->DefectCount);
-}
-
-cmCTestTestHandler* cmCTestMemCheckCommand::InitializeActualHandler()
-{
-  cmCTestMemCheckHandler* handler = this->CTest->GetMemCheckHandler();
-  handler->Initialize();
+  cmMakefile& mf = status.GetMakefile();
+  auto handler = cm::make_unique<cmCTestMemCheckHandler>(this->CTest);
 
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "MemoryCheckType", "CTEST_MEMORYCHECK_TYPE", this->Quiet);
+    &mf, "MemoryCheckType", "CTEST_MEMORYCHECK_TYPE", args.Quiet);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "MemoryCheckSanitizerOptions",
-    "CTEST_MEMORYCHECK_SANITIZER_OPTIONS", this->Quiet);
+    &mf, "MemoryCheckSanitizerOptions", "CTEST_MEMORYCHECK_SANITIZER_OPTIONS",
+    args.Quiet);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "MemoryCheckCommand", "CTEST_MEMORYCHECK_COMMAND",
-    this->Quiet);
+    &mf, "MemoryCheckCommand", "CTEST_MEMORYCHECK_COMMAND", args.Quiet);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "MemoryCheckCommandOptions",
-    "CTEST_MEMORYCHECK_COMMAND_OPTIONS", this->Quiet);
+    &mf, "MemoryCheckCommandOptions", "CTEST_MEMORYCHECK_COMMAND_OPTIONS",
+    args.Quiet);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "MemoryCheckSuppressionFile",
-    "CTEST_MEMORYCHECK_SUPPRESSIONS_FILE", this->Quiet);
+    &mf, "MemoryCheckSuppressionFile", "CTEST_MEMORYCHECK_SUPPRESSIONS_FILE",
+    args.Quiet);
 
-  handler->SetQuiet(this->Quiet);
-  return handler;
+  handler->SetQuiet(args.Quiet);
+  return std::unique_ptr<cmCTestTestHandler>(std::move(handler));
 }
 
 void cmCTestMemCheckCommand::ProcessAdditionalValues(
-  cmCTestGenericHandler* handler)
+  cmCTestGenericHandler* handler, HandlerArguments const& arguments,
+  cmExecutionStatus& status) const
 {
-  if (!this->DefectCount.empty()) {
-    this->Makefile->AddDefinition(
-      this->DefectCount,
+  cmMakefile& mf = status.GetMakefile();
+  auto const& args = static_cast<MemCheckArguments const&>(arguments);
+  if (!args.DefectCount.empty()) {
+    mf.AddDefinition(
+      args.DefectCount,
       std::to_string(
         static_cast<cmCTestMemCheckHandler*>(handler)->GetDefectCount()));
   }
 }
+
+bool cmCTestMemCheckCommand::InitialPass(std::vector<std::string> const& args,
+                                         cmExecutionStatus& status) const
+{
+  static auto const parser =
+    cmArgumentParser<MemCheckArguments>{ MakeTestParser<MemCheckArguments>() }
+      .Bind("DEFECT_COUNT"_s, &MemCheckArguments::DefectCount);
+
+  return this->Invoke(parser, args, status, [&](MemCheckArguments& a) {
+    return this->ExecuteHandlerCommand(a, status);
+  });
+}
diff --git a/Source/CTest/cmCTestMemCheckCommand.h b/Source/CTest/cmCTestMemCheckCommand.h
index ee39e49..0f3a7fc 100644
--- a/Source/CTest/cmCTestMemCheckCommand.h
+++ b/Source/CTest/cmCTestMemCheckCommand.h
@@ -4,42 +4,37 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
+#include <memory>
 #include <string>
-#include <utility>
-
-#include <cm/memory>
+#include <vector>
 
 #include "cmCTestTestCommand.h"
-#include "cmCommand.h"
 
+class cmExecutionStatus;
 class cmCTestGenericHandler;
 class cmCTestTestHandler;
 
-/** \class cmCTestMemCheck
- * \brief Run a ctest script
- *
- * cmCTestMemCheckCommand defineds the command to test the project.
- */
 class cmCTestMemCheckCommand : public cmCTestTestCommand
 {
 public:
-  /**
-   * This is a virtual constructor for the command.
-   */
-  std::unique_ptr<cmCommand> Clone() override
-  {
-    auto ni = cm::make_unique<cmCTestMemCheckCommand>();
-    ni->CTest = this->CTest;
-    ni->CTestScriptHandler = this->CTestScriptHandler;
-    return std::unique_ptr<cmCommand>(std::move(ni));
-  }
+  using cmCTestTestCommand::cmCTestTestCommand;
 
 protected:
-  void BindArguments() override;
+  struct MemCheckArguments : TestArguments
+  {
+    std::string DefectCount;
+  };
 
-  cmCTestTestHandler* InitializeActualHandler() override;
+private:
+  std::string GetName() const override { return "ctest_memcheck"; }
 
-  void ProcessAdditionalValues(cmCTestGenericHandler* handler) override;
+  std::unique_ptr<cmCTestTestHandler> InitializeActualHandler(
+    HandlerArguments& arguments, cmExecutionStatus& status) const override;
 
-  std::string DefectCount;
+  void ProcessAdditionalValues(cmCTestGenericHandler* handler,
+                               HandlerArguments const& arguments,
+                               cmExecutionStatus& status) const override;
+
+  bool InitialPass(std::vector<std::string> const& args,
+                   cmExecutionStatus& status) const override;
 };
diff --git a/Source/CTest/cmCTestMemCheckHandler.cxx b/Source/CTest/cmCTestMemCheckHandler.cxx
index 8596ffa..e494163 100644
--- a/Source/CTest/cmCTestMemCheckHandler.cxx
+++ b/Source/CTest/cmCTestMemCheckHandler.cxx
@@ -118,26 +118,12 @@
 #define BOUNDS_CHECKER_MARKER                                                 \
   "******######*****Begin BOUNDS CHECKER XML******######******"
 
-cmCTestMemCheckHandler::cmCTestMemCheckHandler()
+cmCTestMemCheckHandler::cmCTestMemCheckHandler(cmCTest* ctest)
+  : Superclass(ctest)
 {
   this->MemCheck = true;
-  this->CustomMaximumPassedTestOutputSize = 0;
-  this->CustomMaximumFailedTestOutputSize = 0;
-  this->LogWithPID = false;
-}
-
-void cmCTestMemCheckHandler::Initialize()
-{
-  this->Superclass::Initialize();
-  this->LogWithPID = false;
-  this->CustomMaximumPassedTestOutputSize = 0;
-  this->CustomMaximumFailedTestOutputSize = 0;
-  this->MemoryTester.clear();
-  this->MemoryTesterDynamicOptions.clear();
-  this->MemoryTesterOptions.clear();
-  this->MemoryTesterStyle = UNKNOWN;
-  this->MemoryTesterOutputFile.clear();
-  this->DefectCount = 0;
+  this->TestOptions.OutputSizePassed = 0;
+  this->TestOptions.OutputSizeFailed = 0;
 }
 
 int cmCTestMemCheckHandler::PreProcessHandler()
@@ -311,7 +297,7 @@
   if (!this->CTest->GetProduceXML()) {
     return;
   }
-  this->CTest->StartXML(xml, this->AppendXML);
+  this->CTest->StartXML(xml, this->CMake, this->AppendXML);
   this->CTest->GenerateSubprojectsOutput(xml);
   xml.StartElement("DynamicAnalysis");
   switch (this->MemoryTesterStyle) {
@@ -371,9 +357,8 @@
       continue;
     }
     this->CleanTestOutput(
-      memcheckstr,
-      static_cast<size_t>(this->CustomMaximumFailedTestOutputSize),
-      this->TestOutputTruncation);
+      memcheckstr, static_cast<size_t>(this->TestOptions.OutputSizeFailed),
+      this->TestOptions.OutputTruncation);
     this->WriteTestResultHeader(xml, result);
     xml.StartElement("Results");
     int memoryErrors = 0;
@@ -929,7 +914,7 @@
   cmsys::SystemTools::Split(str, lines);
   bool unlimitedOutput = false;
   if (str.find("CTEST_FULL_OUTPUT") != std::string::npos ||
-      this->CustomMaximumFailedTestOutputSize == 0) {
+      this->TestOptions.OutputSizeFailed == 0) {
     unlimitedOutput = true;
   }
 
@@ -1029,7 +1014,7 @@
     ostr << lines[i] << std::endl;
     if (!unlimitedOutput &&
         totalOutputSize >
-          static_cast<size_t>(this->CustomMaximumFailedTestOutputSize)) {
+          static_cast<size_t>(this->TestOptions.OutputSizeFailed)) {
       ostr << "....\n";
       ostr << "Test Output for this test has been truncated see testing"
               " machine logs for full output,\n";
@@ -1143,7 +1128,7 @@
   cmsys::SystemTools::Split(str, lines);
   bool unlimitedOutput = false;
   if (str.find("CTEST_FULL_OUTPUT") != std::string::npos ||
-      this->CustomMaximumFailedTestOutputSize == 0) {
+      this->TestOptions.OutputSizeFailed == 0) {
     unlimitedOutput = true;
   }
 
@@ -1243,7 +1228,7 @@
     ostr << lines[i] << std::endl;
     if (!unlimitedOutput &&
         totalOutputSize >
-          static_cast<size_t>(this->CustomMaximumFailedTestOutputSize)) {
+          static_cast<size_t>(this->TestOptions.OutputSizeFailed)) {
       ostr << "....\n";
       ostr << "Test Output for this test has been truncated see testing"
               " machine logs for full output,\n";
diff --git a/Source/CTest/cmCTestMemCheckHandler.h b/Source/CTest/cmCTestMemCheckHandler.h
index a63a24d..1971330 100644
--- a/Source/CTest/cmCTestMemCheckHandler.h
+++ b/Source/CTest/cmCTestMemCheckHandler.h
@@ -11,6 +11,7 @@
 
 class cmMakefile;
 class cmXMLWriter;
+class cmCTest;
 
 /** \class cmCTestMemCheckHandler
  * \brief A class that handles ctest -S invocations
@@ -25,9 +26,7 @@
 
   void PopulateCustomVectors(cmMakefile* mf) override;
 
-  cmCTestMemCheckHandler();
-
-  void Initialize() override;
+  cmCTestMemCheckHandler(cmCTest* ctest);
 
   int GetDefectCount() const;
 
@@ -100,15 +99,15 @@
   std::string MemoryTester;
   std::vector<std::string> MemoryTesterDynamicOptions;
   std::vector<std::string> MemoryTesterOptions;
-  int MemoryTesterStyle;
+  int MemoryTesterStyle = UNKNOWN;
   std::string MemoryTesterOutputFile;
   std::string MemoryTesterEnvironmentVariable;
   // these are used to store the types of errors that can show up
   std::vector<std::string> ResultStrings;
   std::vector<std::string> ResultStringsLong;
   std::vector<int> GlobalResults;
-  bool LogWithPID; // does log file add pid
-  int DefectCount;
+  bool LogWithPID = false; // does log file add pid
+  int DefectCount = 0;
 
   std::vector<int>::size_type FindOrAddWarning(const std::string& warning);
   // initialize the ResultStrings and ResultStringsLong for
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/cmCTestReadCustomFilesCommand.cxx b/Source/CTest/cmCTestReadCustomFilesCommand.cxx
index a25cca4..992676f 100644
--- a/Source/CTest/cmCTestReadCustomFilesCommand.cxx
+++ b/Source/CTest/cmCTestReadCustomFilesCommand.cxx
@@ -3,19 +3,21 @@
 #include "cmCTestReadCustomFilesCommand.h"
 
 #include "cmCTest.h"
+#include "cmExecutionStatus.h"
 
-class cmExecutionStatus;
+class cmMakefile;
 
 bool cmCTestReadCustomFilesCommand::InitialPass(
-  std::vector<std::string> const& args, cmExecutionStatus& /*unused*/)
+  std::vector<std::string> const& args, cmExecutionStatus& status) const
 {
   if (args.empty()) {
-    this->SetError("called with incorrect number of arguments");
+    status.SetError("called with incorrect number of arguments");
     return false;
   }
 
+  cmMakefile& mf = status.GetMakefile();
   for (std::string const& arg : args) {
-    this->CTest->ReadCustomConfigurationFileTree(arg, this->Makefile);
+    this->CTest->ReadCustomConfigurationFileTree(arg, &mf);
   }
 
   return true;
diff --git a/Source/CTest/cmCTestReadCustomFilesCommand.h b/Source/CTest/cmCTestReadCustomFilesCommand.h
index 03714f6..bf12e9e 100644
--- a/Source/CTest/cmCTestReadCustomFilesCommand.h
+++ b/Source/CTest/cmCTestReadCustomFilesCommand.h
@@ -5,13 +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;
 
@@ -24,22 +20,12 @@
 class cmCTestReadCustomFilesCommand : public cmCTestCommand
 {
 public:
-  cmCTestReadCustomFilesCommand() {}
-
-  /**
-   * This is a virtual constructor for the command.
-   */
-  std::unique_ptr<cmCommand> Clone() override
-  {
-    auto ni = cm::make_unique<cmCTestReadCustomFilesCommand>();
-    ni->CTest = this->CTest;
-    return std::unique_ptr<cmCommand>(std::move(ni));
-  }
+  using cmCTestCommand::cmCTestCommand;
 
   /**
    * 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;
+                   cmExecutionStatus& status) const override;
 };
diff --git a/Source/CTest/cmCTestRunScriptCommand.cxx b/Source/CTest/cmCTestRunScriptCommand.cxx
index 7661d4d..74620a2 100644
--- a/Source/CTest/cmCTestRunScriptCommand.cxx
+++ b/Source/CTest/cmCTestRunScriptCommand.cxx
@@ -3,18 +3,19 @@
 #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) const
 {
   if (args.empty()) {
-    this->CTestScriptHandler->RunCurrentScript();
-    return true;
+    status.SetError("called with incorrect number of arguments");
+    return false;
   }
 
+  cmMakefile& mf = status.GetMakefile();
   bool np = false;
   unsigned int i = 0;
   if (args[i] == "NEW_PROCESS") {
@@ -37,9 +38,9 @@
       ++i;
     } else {
       int ret;
-      cmCTestScriptHandler::RunScript(this->CTest, this->Makefile, args[i],
-                                      !np, &ret);
-      this->Makefile->AddDefinition(returnVariable, std::to_string(ret));
+      cmCTestScriptHandler::RunScript(
+        this->CTest, &mf, cmSystemTools::CollapseFullPath(args[i]), !np, &ret);
+      mf.AddDefinition(returnVariable, std::to_string(ret));
     }
   }
   return true;
diff --git a/Source/CTest/cmCTestRunScriptCommand.h b/Source/CTest/cmCTestRunScriptCommand.h
index 510b748..2ccc0e7 100644
--- a/Source/CTest/cmCTestRunScriptCommand.h
+++ b/Source/CTest/cmCTestRunScriptCommand.h
@@ -5,13 +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;
 
@@ -24,23 +20,12 @@
 class cmCTestRunScriptCommand : public cmCTestCommand
 {
 public:
-  cmCTestRunScriptCommand() {}
-
-  /**
-   * This is a virtual constructor for the command.
-   */
-  std::unique_ptr<cmCommand> Clone() override
-  {
-    auto ni = cm::make_unique<cmCTestRunScriptCommand>();
-    ni->CTest = this->CTest;
-    ni->CTestScriptHandler = this->CTestScriptHandler;
-    return std::unique_ptr<cmCommand>(std::move(ni));
-  }
+  using cmCTestCommand::cmCTestCommand;
 
   /**
    * 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;
+                   cmExecutionStatus& status) const override;
 };
diff --git a/Source/CTest/cmCTestRunTest.cxx b/Source/CTest/cmCTestRunTest.cxx
index 483b3b4..1ca9807 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>
@@ -297,11 +296,11 @@
   if (!this->TestHandler->MemCheck && started) {
     this->TestHandler->CleanTestOutput(
       this->ProcessOutput,
-      static_cast<size_t>(
-        this->TestResult.Status == cmCTestTestHandler::COMPLETED
-          ? this->TestHandler->CustomMaximumPassedTestOutputSize
-          : this->TestHandler->CustomMaximumFailedTestOutputSize),
-      this->TestHandler->TestOutputTruncation);
+      static_cast<size_t>(this->TestResult.Status ==
+                              cmCTestTestHandler::COMPLETED
+                            ? this->TestHandler->TestOptions.OutputSizePassed
+                            : this->TestHandler->TestOptions.OutputSizeFailed),
+      this->TestHandler->TestOptions.OutputTruncation);
   }
   this->TestResult.Reason = reason;
   if (this->TestHandler->LogFile) {
@@ -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..7d7fa94 100644
--- a/Source/CTest/cmCTestScriptHandler.cxx
+++ b/Source/CTest/cmCTestScriptHandler.cxx
@@ -2,7 +2,7 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmCTestScriptHandler.h"
 
-#include <cstdio>
+#include <chrono>
 #include <cstdlib>
 #include <map>
 #include <ratio>
@@ -13,11 +13,8 @@
 
 #include <cm3p/uv.h>
 
-#include "cmsys/Directory.hxx"
-
 #include "cmCTest.h"
 #include "cmCTestBuildCommand.h"
-#include "cmCTestCommand.h"
 #include "cmCTestConfigureCommand.h"
 #include "cmCTestCoverageCommand.h"
 #include "cmCTestEmptyBinaryDirectoryCommand.h"
@@ -30,63 +27,20 @@
 #include "cmCTestTestCommand.h"
 #include "cmCTestUpdateCommand.h"
 #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()
+cmCTestScriptHandler::cmCTestScriptHandler(cmCTest* ctest)
+  : CTest(ctest)
 {
-  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();
-
-  this->Makefile.reset();
-  this->ParentMakefile = nullptr;
-
-  this->GlobalGenerator.reset();
-
-  this->CMake.reset();
 }
 
 cmCTestScriptHandler::~cmCTestScriptHandler() = default;
@@ -106,9 +60,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;
@@ -120,21 +73,12 @@
 {
   if (this->Makefile) {
     // set the current elapsed time
-    auto itime = cmDurationTo<unsigned int>(std::chrono::steady_clock::now() -
-                                            this->ScriptStartTime);
+    auto itime = cmDurationTo<unsigned int>(this->CTest->GetElapsedTime());
     auto timeString = std::to_string(itime);
     this->Makefile->AddDefinition("CTEST_ELAPSED_TIME", timeString);
   }
 }
 
-void cmCTestScriptHandler::AddCTestCommand(
-  std::string const& name, std::unique_ptr<cmCTestCommand> command)
-{
-  command->CTest = this->CTest;
-  command->CTestScriptHandler = this;
-  this->CMake->GetState()->AddBuiltinCommand(name, std::move(command));
-}
-
 int cmCTestScriptHandler::ExecuteScript(const std::string& total_script_arg)
 {
   // execute the script passing in the arguments to the script as well as the
@@ -235,7 +179,7 @@
     cm::make_unique<cmGlobalGenerator>(this->CMake.get());
 
   cmStateSnapshot snapshot = this->CMake->GetCurrentSnapshot();
-  std::string cwd = cmSystemTools::GetCurrentWorkingDirectory();
+  std::string cwd = cmSystemTools::GetLogicalWorkingDirectory();
   snapshot.GetDirectory().SetCurrentSource(cwd);
   snapshot.GetDirectory().SetCurrentBinary(cwd);
   this->Makefile =
@@ -252,28 +196,26 @@
       }
     });
 
-  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>());
-  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>());
-  this->AddCTestCommand("ctest_start", cm::make_unique<cmCTestStartCommand>());
-  this->AddCTestCommand("ctest_submit",
-                        cm::make_unique<cmCTestSubmitCommand>());
-  this->AddCTestCommand("ctest_test", cm::make_unique<cmCTestTestCommand>());
-  this->AddCTestCommand("ctest_update",
-                        cm::make_unique<cmCTestUpdateCommand>());
-  this->AddCTestCommand("ctest_upload",
-                        cm::make_unique<cmCTestUploadCommand>());
+  cmState* state = this->CMake->GetState();
+  state->AddBuiltinCommand("ctest_build", cmCTestBuildCommand(this->CTest));
+  state->AddBuiltinCommand("ctest_configure",
+                           cmCTestConfigureCommand(this->CTest));
+  state->AddBuiltinCommand("ctest_coverage",
+                           cmCTestCoverageCommand(this->CTest));
+  state->AddBuiltinCommand("ctest_empty_binary_directory",
+                           cmCTestEmptyBinaryDirectoryCommand);
+  state->AddBuiltinCommand("ctest_memcheck",
+                           cmCTestMemCheckCommand(this->CTest));
+  state->AddBuiltinCommand("ctest_read_custom_files",
+                           cmCTestReadCustomFilesCommand(this->CTest));
+  state->AddBuiltinCommand("ctest_run_script",
+                           cmCTestRunScriptCommand(this->CTest));
+  state->AddBuiltinCommand("ctest_sleep", cmCTestSleepCommand);
+  state->AddBuiltinCommand("ctest_start", cmCTestStartCommand(this->CTest));
+  state->AddBuiltinCommand("ctest_submit", cmCTestSubmitCommand(this->CTest));
+  state->AddBuiltinCommand("ctest_test", cmCTestTestCommand(this->CTest));
+  state->AddBuiltinCommand("ctest_update", cmCTestUpdateCommand(this->CTest));
+  state->AddBuiltinCommand("ctest_upload", cmCTestUploadCommand(this->CTest));
 }
 
 // this sets up some variables for the script to use, creates the required
@@ -314,8 +256,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 +306,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)
@@ -483,8 +316,6 @@
 
   int result;
 
-  this->ScriptStartTime = std::chrono::steady_clock::now();
-
   // read in the script
   if (pscope) {
     cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
@@ -495,361 +326,15 @@
                "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)
 {
-  auto sh = cm::make_unique<cmCTestScriptHandler>();
-  sh->SetCTestInstance(ctest);
+  auto sh = cm::make_unique<cmCTestScriptHandler>(ctest);
   sh->ParentMakefile = mf;
   sh->AddConfigurationScript(sname, InProcess);
   int res = sh->ProcessHandler();
@@ -858,94 +343,3 @@
   }
   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) {
-    return cmCTest::MaxDuration();
-  }
-
-  cmValue timelimitS = this->Makefile->GetDefinition("CTEST_TIME_LIMIT");
-
-  if (!timelimitS) {
-    return cmCTest::MaxDuration();
-  }
-
-  auto timelimit = cmDuration(atof(timelimitS->c_str()));
-
-  auto duration = std::chrono::duration_cast<cmDuration>(
-    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 7747750..bcb3933 100644
--- a/Source/CTest/cmCTestScriptHandler.h
+++ b/Source/CTest/cmCTestScriptHandler.h
@@ -4,63 +4,21 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
-#include <chrono>
 #include <memory>
 #include <string>
 #include <vector>
 
-#include "cmsys/Status.hxx"
-
-#include "cmCTestGenericHandler.h"
-#include "cmDuration.h"
-
 class cmCTest;
-class cmCTestCommand;
 class cmGlobalGenerator;
 class cmMakefile;
 class cmake;
 
 /** \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
+class cmCTestScriptHandler
 {
 public:
-  using Superclass = cmCTestGenericHandler;
-
   /**
    * Add a script to run, and if is should run in the current process
    */
@@ -69,7 +27,7 @@
   /**
    * Run a dashboard using a specified configuration script
    */
-  int ProcessHandler() override;
+  int ProcessHandler();
 
   /*
    * Run a script
@@ -77,104 +35,32 @@
   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();
 
-  /**
-   * Return the time remaianing that the script is allowed to run in
-   * seconds if the user has set the variable CTEST_TIME_LIMIT. If that has
-   * not been set it returns a very large value.
-   */
-  cmDuration GetRemainingTimeAllowed();
-
-  cmCTestScriptHandler();
+  cmCTestScriptHandler(cmCTest* ctest);
   cmCTestScriptHandler(const cmCTestScriptHandler&) = delete;
   const cmCTestScriptHandler& operator=(const cmCTestScriptHandler&) = delete;
-  ~cmCTestScriptHandler() override;
-
-  void Initialize() override;
+  ~cmCTestScriptHandler();
 
   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);
-
+  cmCTest* CTest = nullptr;
   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();
-
   std::unique_ptr<cmMakefile> Makefile;
   cmMakefile* ParentMakefile = nullptr;
   std::unique_ptr<cmGlobalGenerator> GlobalGenerator;
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..3f610d0 100644
--- a/Source/CTest/cmCTestStartCommand.cxx
+++ b/Source/CTest/cmCTestStartCommand.cxx
@@ -7,28 +7,24 @@
 
 #include "cmCTest.h"
 #include "cmCTestVC.h"
+#include "cmExecutionStatus.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*/)
+                                      cmExecutionStatus& status) const
 {
   if (args.empty()) {
-    this->SetError("called with incorrect number of arguments");
+    status.SetError("called with incorrect number of arguments");
     return false;
   }
 
   size_t cnt = 0;
+  bool append = false;
+  bool quiet = false;
   const char* smodel = nullptr;
   cmValue src_dir;
   cmValue bld_dir;
@@ -40,17 +36,17 @@
           args[cnt] == "QUIET") {
         std::ostringstream e;
         e << args[cnt - 1] << " argument missing group name";
-        this->SetError(e.str());
+        status.SetError(e.str());
         return false;
       }
       this->CTest->SetSpecificGroup(args[cnt].c_str());
       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++;
@@ -61,43 +57,42 @@
       bld_dir = cmValue(args[cnt]);
       cnt++;
     } else {
-      this->SetError("Too many arguments");
+      status.SetError("Too many arguments");
       return false;
     }
   }
 
-  if (!src_dir) {
-    src_dir = this->Makefile->GetDefinition("CTEST_SOURCE_DIRECTORY");
-  }
-  if (!bld_dir) {
-    bld_dir = this->Makefile->GetDefinition("CTEST_BINARY_DIRECTORY");
-  }
-  if (!src_dir) {
-    this->SetError("source directory not specified. Specify source directory "
-                   "as an argument or set CTEST_SOURCE_DIRECTORY");
-    return false;
-  }
-  if (!bld_dir) {
-    this->SetError("binary directory not specified. Specify binary directory "
-                   "as an argument or set CTEST_BINARY_DIRECTORY");
-    return false;
-  }
-  if (!smodel && this->CreateNewTag) {
-    this->SetError("no test model specified and APPEND not specified. Specify "
-                   "either a test model or the APPEND argument");
-    return false;
-  }
+  cmMakefile& mf = status.GetMakefile();
 
-  cmSystemTools::AddKeepPath(*src_dir);
-  cmSystemTools::AddKeepPath(*bld_dir);
+  if (!src_dir) {
+    src_dir = mf.GetDefinition("CTEST_SOURCE_DIRECTORY");
+  }
+  if (!bld_dir) {
+    bld_dir = mf.GetDefinition("CTEST_BINARY_DIRECTORY");
+  }
+  if (!src_dir) {
+    status.SetError("source directory not specified. Specify source directory "
+                    "as an argument or set CTEST_SOURCE_DIRECTORY");
+    return false;
+  }
+  if (!bld_dir) {
+    status.SetError("binary directory not specified. Specify binary directory "
+                    "as an argument or set CTEST_BINARY_DIRECTORY");
+    return false;
+  }
+  if (!smodel && !append) {
+    status.SetError(
+      "no test model specified and APPEND not specified. Specify "
+      "either a test model or the APPEND argument");
+    return false;
+  }
 
   this->CTest->EmptyCTestConfiguration();
 
   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 +100,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 +108,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.
@@ -131,7 +126,7 @@
   }
 
   // Make sure the source directory exists.
-  if (!this->InitialCheckout(ofs, sourceDir)) {
+  if (!this->InitialCheckout(ofs, sourceDir, status)) {
     return false;
   }
   if (!cmSystemTools::FileIsDirectory(sourceDir)) {
@@ -140,11 +135,10 @@
       << "  " << sourceDir << "\n"
       << "which is not an existing directory.  "
       << "Set CTEST_CHECKOUT_COMMAND to a command line to create it.";
-    this->SetError(e.str());
+    status.SetError(e.str());
     return false;
   }
 
-  this->CTest->SetRunCurrentScript(false);
   this->CTest->SetSuppressUpdatingCTestConfiguration(true);
   int model;
   if (smodel) {
@@ -155,18 +149,90 @@
   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 = mf.ReadDependentFile(fname);
+    if (!readit) {
+      std::string m = cmStrCat("Could not find include file: ", fname);
+      status.SetError(m);
+      return false;
+    }
+  }
+
+  this->CTest->SetCTestConfigurationFromCMakeVariable(
+    &mf, "NightlyStartTime", "CTEST_NIGHTLY_START_TIME", quiet);
+  this->CTest->SetCTestConfigurationFromCMakeVariable(&mf, "Site",
+                                                      "CTEST_SITE", quiet);
+  this->CTest->SetCTestConfigurationFromCMakeVariable(
+    &mf, "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, &mf);
+
+  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,
-                                          std::string const& sourceDir)
+                                          std::string const& sourceDir,
+                                          cmExecutionStatus& status) const
 {
+  cmMakefile& mf = status.GetMakefile();
   // Use the user-provided command to create the source tree.
-  cmValue initialCheckoutCommand =
-    this->Makefile->GetDefinition("CTEST_CHECKOUT_COMMAND");
+  cmValue initialCheckoutCommand = mf.GetDefinition("CTEST_CHECKOUT_COMMAND");
   if (!initialCheckoutCommand) {
-    initialCheckoutCommand =
-      this->Makefile->GetDefinition("CTEST_CVS_CHECKOUT");
+    initialCheckoutCommand = mf.GetDefinition("CTEST_CVS_CHECKOUT");
   }
   if (initialCheckoutCommand) {
     // Use a generic VC object to run and log the command.
diff --git a/Source/CTest/cmCTestStartCommand.h b/Source/CTest/cmCTestStartCommand.h
index b3d06a7..325471a 100644
--- a/Source/CTest/cmCTestStartCommand.h
+++ b/Source/CTest/cmCTestStartCommand.h
@@ -6,13 +6,9 @@
 
 #include <iosfwd>
 #include <string>
-#include <utility>
 #include <vector>
 
-#include <cm/memory>
-
 #include "cmCTestCommand.h"
-#include "cmCommand.h"
 
 class cmExecutionStatus;
 
@@ -24,40 +20,16 @@
 class cmCTestStartCommand : public cmCTestCommand
 {
 public:
-  cmCTestStartCommand();
-
-  /**
-   * This is a virtual constructor for the command.
-   */
-  std::unique_ptr<cmCommand> Clone() override
-  {
-    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));
-  }
+  using cmCTestCommand::cmCTestCommand;
 
   /**
    * 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;
-
-  /**
-   * 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; }
+                   cmExecutionStatus& status) const override;
 
 private:
-  bool InitialCheckout(std::ostream& ofs, std::string const& sourceDir);
-  bool CreateNewTag;
-  bool Quiet;
+  bool InitialCheckout(std::ostream& ofs, std::string const& sourceDir,
+                       cmExecutionStatus& status) const;
 };
diff --git a/Source/CTest/cmCTestSubmitCommand.cxx b/Source/CTest/cmCTestSubmitCommand.cxx
index 029f81f..8ae0f4c 100644
--- a/Source/CTest/cmCTestSubmitCommand.cxx
+++ b/Source/CTest/cmCTestSubmitCommand.cxx
@@ -10,9 +10,11 @@
 #include <cm/vector>
 #include <cmext/string_view>
 
+#include "cmArgumentParser.h"
 #include "cmCTest.h"
+#include "cmCTestGenericHandler.h"
 #include "cmCTestSubmitHandler.h"
-#include "cmCommand.h"
+#include "cmExecutionStatus.h"
 #include "cmList.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
@@ -20,117 +22,101 @@
 #include "cmSystemTools.h"
 #include "cmValue.h"
 
-class cmExecutionStatus;
-
-/**
- * This is a virtual constructor for the command.
- */
-std::unique_ptr<cmCommand> cmCTestSubmitCommand::Clone()
+std::unique_ptr<cmCTestGenericHandler> cmCTestSubmitCommand::InitializeHandler(
+  HandlerArguments& arguments, cmExecutionStatus& status) const
 {
-  auto ni = cm::make_unique<cmCTestSubmitCommand>();
-  ni->CTest = this->CTest;
-  ni->CTestScriptHandler = this->CTestScriptHandler;
-  return std::unique_ptr<cmCommand>(std::move(ni));
-}
-
-cmCTestGenericHandler* cmCTestSubmitCommand::InitializeHandler()
-{
-  cmValue submitURL = !this->SubmitURL.empty()
-    ? cmValue(this->SubmitURL)
-    : this->Makefile->GetDefinition("CTEST_SUBMIT_URL");
+  cmMakefile& mf = status.GetMakefile();
+  auto const& args = static_cast<SubmitArguments&>(arguments);
+  cmValue submitURL = !args.SubmitURL.empty()
+    ? cmValue(args.SubmitURL)
+    : mf.GetDefinition("CTEST_SUBMIT_URL");
 
   if (submitURL) {
-    this->CTest->SetCTestConfiguration("SubmitURL", *submitURL, this->Quiet);
+    this->CTest->SetCTestConfiguration("SubmitURL", *submitURL, args.Quiet);
   } else {
     this->CTest->SetCTestConfigurationFromCMakeVariable(
-      this->Makefile, "DropMethod", "CTEST_DROP_METHOD", this->Quiet);
+      &mf, "DropMethod", "CTEST_DROP_METHOD", args.Quiet);
     this->CTest->SetCTestConfigurationFromCMakeVariable(
-      this->Makefile, "DropSiteUser", "CTEST_DROP_SITE_USER", this->Quiet);
+      &mf, "DropSiteUser", "CTEST_DROP_SITE_USER", args.Quiet);
     this->CTest->SetCTestConfigurationFromCMakeVariable(
-      this->Makefile, "DropSitePassword", "CTEST_DROP_SITE_PASSWORD",
-      this->Quiet);
+      &mf, "DropSitePassword", "CTEST_DROP_SITE_PASSWORD", args.Quiet);
     this->CTest->SetCTestConfigurationFromCMakeVariable(
-      this->Makefile, "DropSite", "CTEST_DROP_SITE", this->Quiet);
+      &mf, "DropSite", "CTEST_DROP_SITE", args.Quiet);
     this->CTest->SetCTestConfigurationFromCMakeVariable(
-      this->Makefile, "DropLocation", "CTEST_DROP_LOCATION", this->Quiet);
+      &mf, "DropLocation", "CTEST_DROP_LOCATION", args.Quiet);
   }
 
   if (!this->CTest->SetCTestConfigurationFromCMakeVariable(
-        this->Makefile, "TLSVersion", "CTEST_TLS_VERSION", this->Quiet)) {
-    if (cmValue tlsVersionVar =
-          this->Makefile->GetDefinition("CMAKE_TLS_VERSION")) {
+        &mf, "TLSVersion", "CTEST_TLS_VERSION", args.Quiet)) {
+    if (cmValue tlsVersionVar = mf.GetDefinition("CMAKE_TLS_VERSION")) {
       cmCTestOptionalLog(
         this->CTest, HANDLER_VERBOSE_OUTPUT,
         "SetCTestConfiguration from CMAKE_TLS_VERSION:TLSVersion:"
           << *tlsVersionVar << std::endl,
-        this->Quiet);
+        args.Quiet);
       this->CTest->SetCTestConfiguration("TLSVersion", *tlsVersionVar,
-                                         this->Quiet);
+                                         args.Quiet);
     } else if (cm::optional<std::string> tlsVersionEnv =
                  cmSystemTools::GetEnvVar("CMAKE_TLS_VERSION")) {
       cmCTestOptionalLog(
         this->CTest, HANDLER_VERBOSE_OUTPUT,
         "SetCTestConfiguration from ENV{CMAKE_TLS_VERSION}:TLSVersion:"
           << *tlsVersionEnv << std::endl,
-        this->Quiet);
+        args.Quiet);
       this->CTest->SetCTestConfiguration("TLSVersion", *tlsVersionEnv,
-                                         this->Quiet);
+                                         args.Quiet);
     }
   }
   if (!this->CTest->SetCTestConfigurationFromCMakeVariable(
-        this->Makefile, "TLSVerify", "CTEST_TLS_VERIFY", this->Quiet)) {
-    if (cmValue tlsVerifyVar =
-          this->Makefile->GetDefinition("CMAKE_TLS_VERIFY")) {
+        &mf, "TLSVerify", "CTEST_TLS_VERIFY", args.Quiet)) {
+    if (cmValue tlsVerifyVar = mf.GetDefinition("CMAKE_TLS_VERIFY")) {
       cmCTestOptionalLog(
         this->CTest, HANDLER_VERBOSE_OUTPUT,
         "SetCTestConfiguration from CMAKE_TLS_VERIFY:TLSVerify:"
           << *tlsVerifyVar << std::endl,
-        this->Quiet);
+        args.Quiet);
       this->CTest->SetCTestConfiguration("TLSVerify", *tlsVerifyVar,
-                                         this->Quiet);
+                                         args.Quiet);
     } else if (cm::optional<std::string> tlsVerifyEnv =
                  cmSystemTools::GetEnvVar("CMAKE_TLS_VERIFY")) {
       cmCTestOptionalLog(
         this->CTest, HANDLER_VERBOSE_OUTPUT,
         "SetCTestConfiguration from ENV{CMAKE_TLS_VERIFY}:TLSVerify:"
           << *tlsVerifyEnv << std::endl,
-        this->Quiet);
+        args.Quiet);
       this->CTest->SetCTestConfiguration("TLSVerify", *tlsVerifyEnv,
-                                         this->Quiet);
+                                         args.Quiet);
     }
   }
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "CurlOptions", "CTEST_CURL_OPTIONS", this->Quiet);
+    &mf, "CurlOptions", "CTEST_CURL_OPTIONS", args.Quiet);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "SubmitInactivityTimeout",
-    "CTEST_SUBMIT_INACTIVITY_TIMEOUT", this->Quiet);
+    &mf, "SubmitInactivityTimeout", "CTEST_SUBMIT_INACTIVITY_TIMEOUT",
+    args.Quiet);
 
-  cmValue notesFilesVariable =
-    this->Makefile->GetDefinition("CTEST_NOTES_FILES");
+  cmValue notesFilesVariable = mf.GetDefinition("CTEST_NOTES_FILES");
   if (notesFilesVariable) {
     cmList notesFiles{ *notesFilesVariable };
-    this->CTest->GenerateNotesFile(notesFiles);
+    this->CTest->GenerateNotesFile(mf.GetCMakeInstance(), notesFiles);
   }
 
-  cmValue extraFilesVariable =
-    this->Makefile->GetDefinition("CTEST_EXTRA_SUBMIT_FILES");
+  cmValue extraFilesVariable = mf.GetDefinition("CTEST_EXTRA_SUBMIT_FILES");
   if (extraFilesVariable) {
     cmList extraFiles{ *extraFilesVariable };
     if (!this->CTest->SubmitExtraFiles(extraFiles)) {
-      this->SetError("problem submitting extra files.");
+      status.SetError("problem submitting extra files.");
       return nullptr;
     }
   }
 
-  cmCTestSubmitHandler* handler = this->CTest->GetSubmitHandler();
-  handler->Initialize();
+  auto handler = cm::make_unique<cmCTestSubmitHandler>(this->CTest);
 
   // If no FILES or PARTS given, *all* PARTS are submitted by default.
   //
   // If FILES are given, but not PARTS, only the FILES are submitted
   // and *no* PARTS are submitted.
   //  (This is why we select the empty "noParts" set in the
-  //   if(this->Files) block below...)
+  //   if(args.Files) block below...)
   //
   // If PARTS are given, only the selected PARTS are submitted.
   //
@@ -139,7 +125,7 @@
 
   // If given explicit FILES to submit, pass them to the handler.
   //
-  if (this->Files) {
+  if (args.Files) {
     // Intentionally select *no* PARTS. (Pass an empty set.) If PARTS
     // were also explicitly mentioned, they will be selected below...
     // But FILES with no PARTS mentioned should just submit the FILES
@@ -147,99 +133,111 @@
     //
     handler->SelectParts(std::set<cmCTest::Part>());
     handler->SelectFiles(
-      std::set<std::string>(this->Files->begin(), this->Files->end()));
+      std::set<std::string>(args.Files->begin(), args.Files->end()));
   }
 
   // If a PARTS option was given, select only the named parts for submission.
   //
-  if (this->Parts) {
+  if (args.Parts) {
     auto parts =
-      cmMakeRange(*(this->Parts)).transform([this](std::string const& arg) {
+      cmMakeRange(*(args.Parts)).transform([this](std::string const& arg) {
         return this->CTest->GetPartFromName(arg);
       });
     handler->SelectParts(std::set<cmCTest::Part>(parts.begin(), parts.end()));
   }
 
   // Pass along any HTTPHEADER to the handler if this option was given.
-  if (!this->HttpHeaders.empty()) {
-    handler->SetHttpHeaders(this->HttpHeaders);
+  if (!args.HttpHeaders.empty()) {
+    handler->SetHttpHeaders(args.HttpHeaders);
   }
 
-  handler->SetOption("RetryDelay", this->RetryDelay);
-  handler->SetOption("RetryCount", this->RetryCount);
-  handler->SetOption("InternalTest", this->InternalTest ? "ON" : "OFF");
+  handler->RetryDelay = args.RetryDelay;
+  handler->RetryCount = args.RetryCount;
+  handler->InternalTest = args.InternalTest;
 
-  handler->SetQuiet(this->Quiet);
+  handler->SetQuiet(args.Quiet);
 
-  if (this->CDashUpload) {
-    handler->SetOption("CDashUploadFile", this->CDashUploadFile);
-    handler->SetOption("CDashUploadType", this->CDashUploadType);
+  if (args.CDashUpload) {
+    handler->CDashUpload = true;
+    handler->CDashUploadFile = args.CDashUploadFile;
+    handler->CDashUploadType = args.CDashUploadType;
   }
-  return handler;
+  return std::unique_ptr<cmCTestGenericHandler>(std::move(handler));
 }
 
 bool cmCTestSubmitCommand::InitialPass(std::vector<std::string> const& args,
-                                       cmExecutionStatus& status)
+                                       cmExecutionStatus& status) const
 {
-  this->CDashUpload = !args.empty() && args[0] == "CDASH_UPLOAD";
-
-  bool ret = this->cmCTestHandlerCommand::InitialPass(args, status);
-
-  if (!this->BuildID.empty()) {
-    this->Makefile->AddDefinition(this->BuildID, this->CTest->GetBuildID());
-  }
-
-  return ret;
-}
-
-void cmCTestSubmitCommand::BindArguments()
-{
-  if (this->CDashUpload) {
-    // Arguments specific to the CDASH_UPLOAD signature.
-    this->Bind("CDASH_UPLOAD", this->CDashUploadFile);
-    this->Bind("CDASH_UPLOAD_TYPE", this->CDashUploadType);
-  } else {
-    // Arguments that cannot be used with CDASH_UPLOAD.
-    this->Bind("PARTS"_s, this->Parts);
-    this->Bind("FILES"_s, this->Files);
-  }
   // Arguments used by both modes.
-  this->Bind("BUILD_ID"_s, this->BuildID);
-  this->Bind("HTTPHEADER"_s, this->HttpHeaders);
-  this->Bind("RETRY_COUNT"_s, this->RetryCount);
-  this->Bind("RETRY_DELAY"_s, this->RetryDelay);
-  this->Bind("SUBMIT_URL"_s, this->SubmitURL);
-  this->Bind("INTERNAL_TEST_CHECKSUM", this->InternalTest);
+  static auto const parserBase =
+    cmArgumentParser<SubmitArguments>{ MakeHandlerParser<SubmitArguments>() }
+      .Bind("BUILD_ID"_s, &SubmitArguments::BuildID)
+      .Bind("HTTPHEADER"_s, &SubmitArguments::HttpHeaders)
+      .Bind("RETRY_COUNT"_s, &SubmitArguments::RetryCount)
+      .Bind("RETRY_DELAY"_s, &SubmitArguments::RetryDelay)
+      .Bind("SUBMIT_URL"_s, &SubmitArguments::SubmitURL)
+      .Bind("INTERNAL_TEST_CHECKSUM"_s, &SubmitArguments::InternalTest);
 
-  // Look for other arguments.
-  this->cmCTestHandlerCommand::BindArguments();
+  // Arguments specific to the CDASH_UPLOAD signature.
+  static auto const uploadParser =
+    cmArgumentParser<SubmitArguments>{ parserBase }
+      .Bind("CDASH_UPLOAD"_s, &SubmitArguments::CDashUploadFile)
+      .Bind("CDASH_UPLOAD_TYPE"_s, &SubmitArguments::CDashUploadType);
+
+  // Arguments that cannot be used with CDASH_UPLOAD.
+  static auto const partsParser =
+    cmArgumentParser<SubmitArguments>{ parserBase }
+      .Bind("PARTS"_s, &SubmitArguments::Parts)
+      .Bind("FILES"_s, &SubmitArguments::Files);
+
+  bool const cdashUpload = !args.empty() && args[0] == "CDASH_UPLOAD";
+  auto const& parser = cdashUpload ? uploadParser : partsParser;
+
+  return this->Invoke(parser, args, status, [&](SubmitArguments& a) -> bool {
+    a.CDashUpload = cdashUpload;
+    return this->ExecuteHandlerCommand(a, status);
+  });
 }
 
-void cmCTestSubmitCommand::CheckArguments()
+void cmCTestSubmitCommand::CheckArguments(HandlerArguments& arguments,
+                                          cmExecutionStatus& status) const
 {
-  if (this->Parts) {
-    cm::erase_if(*(this->Parts), [this](std::string const& arg) -> bool {
+  cmMakefile& mf = status.GetMakefile();
+  auto& args = static_cast<SubmitArguments&>(arguments);
+  if (args.Parts) {
+    cm::erase_if(*(args.Parts), [this, &mf](std::string const& arg) -> bool {
       cmCTest::Part p = this->CTest->GetPartFromName(arg);
       if (p == cmCTest::PartCount) {
         std::ostringstream e;
         e << "Part name \"" << arg << "\" is invalid.";
-        this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
+        mf.IssueMessage(MessageType::FATAL_ERROR, e.str());
         return true;
       }
       return false;
     });
   }
 
-  if (this->Files) {
-    cm::erase_if(*(this->Files), [this](std::string const& arg) -> bool {
+  if (args.Files) {
+    cm::erase_if(*(args.Files), [&mf](std::string const& arg) -> bool {
       if (!cmSystemTools::FileExists(arg)) {
         std::ostringstream e;
         e << "File \"" << arg << "\" does not exist. Cannot submit "
           << "a non-existent file.";
-        this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
+        mf.IssueMessage(MessageType::FATAL_ERROR, e.str());
         return true;
       }
       return false;
     });
   }
 }
+
+void cmCTestSubmitCommand::ProcessAdditionalValues(
+  cmCTestGenericHandler*, HandlerArguments const& arguments,
+  cmExecutionStatus& status) const
+{
+  cmMakefile& mf = status.GetMakefile();
+  auto const& args = static_cast<SubmitArguments const&>(arguments);
+  if (!args.BuildID.empty()) {
+    mf.AddDefinition(args.BuildID, this->CTest->GetBuildID());
+  }
+}
diff --git a/Source/CTest/cmCTestSubmitCommand.h b/Source/CTest/cmCTestSubmitCommand.h
index b67f182..9c02fc8 100644
--- a/Source/CTest/cmCTestSubmitCommand.h
+++ b/Source/CTest/cmCTestSubmitCommand.h
@@ -13,45 +13,45 @@
 #include "cmArgumentParserTypes.h"
 #include "cmCTestHandlerCommand.h"
 
-class cmCommand;
-class cmCTestGenericHandler;
 class cmExecutionStatus;
+class cmCTestGenericHandler;
 
-/** \class cmCTestSubmit
- * \brief Run a ctest script
- *
- * cmCTestSubmitCommand defineds the command to submit the test results for
- * the project.
- */
 class cmCTestSubmitCommand : public cmCTestHandlerCommand
 {
 public:
-  std::unique_ptr<cmCommand> Clone() override;
-
-  bool InitialPass(std::vector<std::string> const& args,
-                   cmExecutionStatus& status) override;
-
-  /**
-   * The name of the command as specified in CMakeList.txt.
-   */
-  std::string GetName() const override { return "ctest_submit"; }
+  using cmCTestHandlerCommand::cmCTestHandlerCommand;
 
 protected:
-  void BindArguments() override;
-  void CheckArguments() override;
-  cmCTestGenericHandler* InitializeHandler() override;
+  struct SubmitArguments : HandlerArguments
+  {
+    bool CDashUpload = false;
+    bool InternalTest = false;
 
-  bool CDashUpload = false;
-  bool InternalTest = false;
+    std::string BuildID;
+    std::string CDashUploadFile;
+    std::string CDashUploadType;
+    std::string RetryCount;
+    std::string RetryDelay;
+    std::string SubmitURL;
 
-  std::string BuildID;
-  std::string CDashUploadFile;
-  std::string CDashUploadType;
-  std::string RetryCount;
-  std::string RetryDelay;
-  std::string SubmitURL;
+    cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> Files;
+    ArgumentParser::MaybeEmpty<std::vector<std::string>> HttpHeaders;
+    cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> Parts;
+  };
 
-  cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> Files;
-  ArgumentParser::MaybeEmpty<std::vector<std::string>> HttpHeaders;
-  cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> Parts;
+private:
+  std::string GetName() const override { return "ctest_submit"; }
+
+  void CheckArguments(HandlerArguments& arguments,
+                      cmExecutionStatus& status) const override;
+
+  std::unique_ptr<cmCTestGenericHandler> InitializeHandler(
+    HandlerArguments& arguments, cmExecutionStatus& status) const override;
+
+  void ProcessAdditionalValues(cmCTestGenericHandler* handler,
+                               HandlerArguments const& arguments,
+                               cmExecutionStatus& status) const override;
+
+  bool InitialPass(std::vector<std::string> const& args,
+                   cmExecutionStatus& status) const override;
 };
diff --git a/Source/CTest/cmCTestSubmitHandler.cxx b/Source/CTest/cmCTestSubmitHandler.cxx
index 91dea55..1564c0e 100644
--- a/Source/CTest/cmCTestSubmitHandler.cxx
+++ b/Source/CTest/cmCTestSubmitHandler.cxx
@@ -19,7 +19,6 @@
 #include "cmAlgorithms.h"
 #include "cmCTest.h"
 #include "cmCTestCurl.h"
-#include "cmCTestScriptHandler.h"
 #include "cmCryptoHash.h"
 #include "cmCurl.h"
 #include "cmDuration.h"
@@ -117,40 +116,15 @@
   return 0;
 }
 
-cmCTestSubmitHandler::cmCTestSubmitHandler()
-{
-  this->Initialize();
-}
-
-void cmCTestSubmitHandler::Initialize()
+cmCTestSubmitHandler::cmCTestSubmitHandler(cmCTest* ctest)
+  : Superclass(ctest)
+  , HttpHeaders(ctest->GetCommandLineHttpHeaders())
 {
   // We submit all available parts by default.
   for (cmCTest::Part p = cmCTest::PartStart; p != cmCTest::PartCount;
        p = static_cast<cmCTest::Part>(p + 1)) {
     this->SubmitPart[p] = true;
   }
-  this->HasWarnings = false;
-  this->HasErrors = false;
-  this->Superclass::Initialize();
-  this->HTTPProxy.clear();
-  this->HTTPProxyType = 0;
-  this->HTTPProxyAuth.clear();
-  this->LogFile = nullptr;
-  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(
@@ -275,10 +249,8 @@
         upload_as += "&stamp=";
         upload_as += ctest_curl.Escape(this->CTest->GetCurrentTag());
         upload_as += "-";
-        upload_as += ctest_curl.Escape(this->CTest->GetTestModelString());
-        cmCTestScriptHandler* ch = this->CTest->GetScriptHandler();
-        cmake* cm = ch->GetCMake();
-        if (cm) {
+        upload_as += ctest_curl.Escape(this->CTest->GetTestGroupString());
+        if (cmake* cm = this->CMake) {
           cmValue subproject = cm->GetState()->GetGlobalProperty("SubProject");
           if (subproject) {
             upload_as += "&subproject=";
@@ -300,7 +272,7 @@
 
       upload_as += "&MD5=";
 
-      if (this->GetOption("InternalTest").IsOn()) {
+      if (this->InternalTest) {
         upload_as += "ffffffffffffffffffffffffffffffff";
       } else {
         cmCryptoHash hasher(cmCryptoHash::AlgoMD5);
@@ -382,8 +354,8 @@
       bool successful_submission = response_code == 200;
 
       if (!successful_submission || this->HasErrors) {
-        std::string retryDelay = *this->GetOption("RetryDelay");
-        std::string retryCount = *this->GetOption("RetryCount");
+        std::string retryDelay = this->RetryDelay;
+        std::string retryCount = this->RetryCount;
 
         auto delay = cmDuration(
           retryDelay.empty()
@@ -512,10 +484,6 @@
 int cmCTestSubmitHandler::HandleCDashUploadFile(std::string const& file,
                                                 std::string const& typeString)
 {
-  if (file.empty()) {
-    cmCTestLog(this->CTest, ERROR_MESSAGE, "Upload file not specified\n");
-    return -1;
-  }
   if (!cmSystemTools::FileExists(file)) {
     cmCTestLog(this->CTest, ERROR_MESSAGE,
                "Upload file not found: '" << file << "'\n");
@@ -542,11 +510,11 @@
     fields = url.substr(pos + 1);
     url.erase(pos);
   }
-  bool internalTest = this->GetOption("InternalTest").IsOn();
+  bool internalTest = this->InternalTest;
 
   // Get RETRY_COUNT and RETRY_DELAY values if they were set.
-  std::string retryDelayString = *this->GetOption("RetryDelay");
-  std::string retryCountString = *this->GetOption("RetryCount");
+  std::string retryDelayString = this->RetryDelay;
+  std::string retryCountString = this->RetryCount;
   auto retryDelay = std::chrono::seconds(0);
   if (!retryDelayString.empty()) {
     unsigned long retryDelayValue = 0;
@@ -573,9 +541,8 @@
   //    has already been uploaded
   // TODO I added support for subproject. You would need to add
   // a "&subproject=subprojectname" to the first POST.
-  cmCTestScriptHandler* ch = this->CTest->GetScriptHandler();
-  cmake* cm = ch->GetCMake();
-  cmValue subproject = cm->GetState()->GetGlobalProperty("SubProject");
+  cmValue subproject =
+    this->CMake->GetState()->GetGlobalProperty("SubProject");
   // TODO: Encode values for a URL instead of trusting caller.
   std::ostringstream str;
   if (subproject) {
@@ -584,18 +551,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 << "&"
@@ -735,10 +702,9 @@
 
 int cmCTestSubmitHandler::ProcessHandler()
 {
-  cmValue cdashUploadFile = this->GetOption("CDashUploadFile");
-  cmValue cdashUploadType = this->GetOption("CDashUploadType");
-  if (cdashUploadFile && cdashUploadType) {
-    return this->HandleCDashUploadFile(*cdashUploadFile, *cdashUploadType);
+  if (this->CDashUpload) {
+    return this->HandleCDashUploadFile(this->CDashUploadFile,
+                                       this->CDashUploadType);
   }
 
   const std::string& buildDirectory =
@@ -906,7 +872,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..86067f6 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>
@@ -24,7 +23,7 @@
 public:
   using Superclass = cmCTestGenericHandler;
 
-  cmCTestSubmitHandler();
+  cmCTestSubmitHandler(cmCTest* ctest);
   ~cmCTestSubmitHandler() override { this->LogFile = nullptr; }
 
   /*
@@ -32,13 +31,6 @@
    */
   int ProcessHandler() override;
 
-  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);
 
@@ -50,12 +42,7 @@
 
   void SetHttpHeaders(std::vector<std::string> const& v)
   {
-    if (this->CommandLineHttpHeaders.empty()) {
-      this->HttpHeaders = v;
-    } else {
-      this->HttpHeaders = this->CommandLineHttpHeaders;
-      this->HttpHeaders.insert(this->HttpHeaders.end(), v.begin(), v.end());
-    }
+    this->HttpHeaders.insert(this->HttpHeaders.end(), v.begin(), v.end());
   }
 
 private:
@@ -79,13 +66,22 @@
   class ResponseParser;
 
   std::string HTTPProxy;
-  int HTTPProxyType;
+  int HTTPProxyType = 0;
   std::string HTTPProxyAuth;
-  std::ostream* LogFile;
+  std::ostream* LogFile = nullptr;
   bool SubmitPart[cmCTest::PartCount];
-  bool HasWarnings;
-  bool HasErrors;
+  bool HasWarnings = false;
+  bool HasErrors = false;
   std::set<std::string> Files;
-  std::vector<std::string> CommandLineHttpHeaders;
   std::vector<std::string> HttpHeaders;
+
+  bool CDashUpload = false;
+  bool InternalTest = false;
+
+  std::string CDashUploadFile;
+  std::string CDashUploadType;
+  std::string RetryCount;
+  std::string RetryDelay;
+
+  friend class cmCTestSubmitCommand;
 };
diff --git a/Source/CTest/cmCTestTestCommand.cxx b/Source/CTest/cmCTestTestCommand.cxx
index 98ce862..ef3c80f 100644
--- a/Source/CTest/cmCTestTestCommand.cxx
+++ b/Source/CTest/cmCTestTestCommand.cxx
@@ -6,44 +6,26 @@
 #include <cstdlib>
 #include <ratio>
 #include <sstream>
+#include <utility>
+#include <vector>
 
-#include <cmext/string_view>
+#include <cm/memory>
 
 #include "cmCTest.h"
+#include "cmCTestGenericHandler.h"
 #include "cmCTestTestHandler.h"
 #include "cmDuration.h"
+#include "cmExecutionStatus.h"
 #include "cmMakefile.h"
 #include "cmStringAlgorithms.h"
 #include "cmValue.h"
 
-void cmCTestTestCommand::BindArguments()
+std::unique_ptr<cmCTestGenericHandler> cmCTestTestCommand::InitializeHandler(
+  HandlerArguments& arguments, cmExecutionStatus& status) const
 {
-  this->cmCTestHandlerCommand::BindArguments();
-  this->Bind("START"_s, this->Start);
-  this->Bind("END"_s, this->End);
-  this->Bind("STRIDE"_s, this->Stride);
-  this->Bind("EXCLUDE"_s, this->Exclude);
-  this->Bind("INCLUDE"_s, this->Include);
-  this->Bind("EXCLUDE_LABEL"_s, this->ExcludeLabel);
-  this->Bind("INCLUDE_LABEL"_s, this->IncludeLabel);
-  this->Bind("EXCLUDE_FROM_FILE"_s, this->ExcludeTestsFromFile);
-  this->Bind("INCLUDE_FROM_FILE"_s, this->IncludeTestsFromFile);
-  this->Bind("EXCLUDE_FIXTURE"_s, this->ExcludeFixture);
-  this->Bind("EXCLUDE_FIXTURE_SETUP"_s, this->ExcludeFixtureSetup);
-  this->Bind("EXCLUDE_FIXTURE_CLEANUP"_s, this->ExcludeFixtureCleanup);
-  this->Bind("PARALLEL_LEVEL"_s, this->ParallelLevel);
-  this->Bind("REPEAT"_s, this->Repeat);
-  this->Bind("SCHEDULE_RANDOM"_s, this->ScheduleRandom);
-  this->Bind("STOP_TIME"_s, this->StopTime);
-  this->Bind("TEST_LOAD"_s, this->TestLoad);
-  this->Bind("RESOURCE_SPEC_FILE"_s, this->ResourceSpecFile);
-  this->Bind("STOP_ON_FAILURE"_s, this->StopOnFailure);
-  this->Bind("OUTPUT_JUNIT"_s, this->OutputJUnit);
-}
-
-cmCTestGenericHandler* cmCTestTestCommand::InitializeHandler()
-{
-  cmValue ctestTimeout = this->Makefile->GetDefinition("CTEST_TEST_TIMEOUT");
+  cmMakefile& mf = status.GetMakefile();
+  auto& args = static_cast<TestArguments&>(arguments);
+  cmValue ctestTimeout = mf.GetDefinition("CTEST_TEST_TIMEOUT");
 
   cmDuration timeout;
   if (ctestTimeout) {
@@ -57,80 +39,77 @@
   }
   this->CTest->SetTimeOut(timeout);
 
-  cmValue resourceSpecFile =
-    this->Makefile->GetDefinition("CTEST_RESOURCE_SPEC_FILE");
-  if (this->ResourceSpecFile.empty() && resourceSpecFile) {
-    this->ResourceSpecFile = *resourceSpecFile;
+  cmValue resourceSpecFile = mf.GetDefinition("CTEST_RESOURCE_SPEC_FILE");
+  if (args.ResourceSpecFile.empty() && resourceSpecFile) {
+    args.ResourceSpecFile = *resourceSpecFile;
   }
 
-  cmCTestTestHandler* handler = this->InitializeActualHandler();
-  if (!this->Start.empty() || !this->End.empty() || !this->Stride.empty()) {
-    handler->SetOption(
-      "TestsToRunInformation",
-      cmStrCat(this->Start, ',', this->End, ',', this->Stride));
+  auto handler = this->InitializeActualHandler(args, status);
+  if (!args.Start.empty() || !args.End.empty() || !args.Stride.empty()) {
+    handler->TestOptions.TestsToRunInformation =
+      cmStrCat(args.Start, ',', args.End, ',', args.Stride);
   }
-  if (!this->Exclude.empty()) {
-    handler->SetOption("ExcludeRegularExpression", this->Exclude);
+  if (!args.Exclude.empty()) {
+    handler->TestOptions.ExcludeRegularExpression = args.Exclude;
   }
-  if (!this->Include.empty()) {
-    handler->SetOption("IncludeRegularExpression", this->Include);
+  if (!args.Include.empty()) {
+    handler->TestOptions.IncludeRegularExpression = args.Include;
   }
-  if (!this->ExcludeLabel.empty()) {
-    handler->AddMultiOption("ExcludeLabelRegularExpression",
-                            this->ExcludeLabel);
+  if (!args.ExcludeLabel.empty()) {
+    handler->TestOptions.ExcludeLabelRegularExpression.push_back(
+      args.ExcludeLabel);
   }
-  if (!this->IncludeLabel.empty()) {
-    handler->AddMultiOption("LabelRegularExpression", this->IncludeLabel);
+  if (!args.IncludeLabel.empty()) {
+    handler->TestOptions.LabelRegularExpression.push_back(args.IncludeLabel);
   }
 
-  if (!this->ExcludeTestsFromFile.empty()) {
-    handler->SetOption("ExcludeTestListFile", this->ExcludeTestsFromFile);
+  if (!args.ExcludeTestsFromFile.empty()) {
+    handler->TestOptions.ExcludeTestListFile = args.ExcludeTestsFromFile;
   }
-  if (!this->IncludeTestsFromFile.empty()) {
-    handler->SetOption("TestListFile", this->IncludeTestsFromFile);
+  if (!args.IncludeTestsFromFile.empty()) {
+    handler->TestOptions.TestListFile = args.IncludeTestsFromFile;
   }
 
-  if (!this->ExcludeFixture.empty()) {
-    handler->SetOption("ExcludeFixtureRegularExpression",
-                       this->ExcludeFixture);
+  if (!args.ExcludeFixture.empty()) {
+    handler->TestOptions.ExcludeFixtureRegularExpression = args.ExcludeFixture;
   }
-  if (!this->ExcludeFixtureSetup.empty()) {
-    handler->SetOption("ExcludeFixtureSetupRegularExpression",
-                       this->ExcludeFixtureSetup);
+  if (!args.ExcludeFixtureSetup.empty()) {
+    handler->TestOptions.ExcludeFixtureSetupRegularExpression =
+      args.ExcludeFixtureSetup;
   }
-  if (!this->ExcludeFixtureCleanup.empty()) {
-    handler->SetOption("ExcludeFixtureCleanupRegularExpression",
-                       this->ExcludeFixtureCleanup);
+  if (!args.ExcludeFixtureCleanup.empty()) {
+    handler->TestOptions.ExcludeFixtureCleanupRegularExpression =
+      args.ExcludeFixtureCleanup;
   }
-  if (this->StopOnFailure) {
-    handler->SetOption("StopOnFailure", "ON");
+  if (args.StopOnFailure) {
+    handler->TestOptions.StopOnFailure = true;
   }
-  if (this->ParallelLevel) {
-    handler->SetOption("ParallelLevel", *this->ParallelLevel);
+  if (args.ParallelLevel) {
+    handler->ParallelLevel = *args.ParallelLevel;
   }
-  if (!this->Repeat.empty()) {
-    handler->SetOption("Repeat", this->Repeat);
+  if (!args.Repeat.empty()) {
+    handler->Repeat = args.Repeat;
   }
-  if (!this->ScheduleRandom.empty()) {
-    handler->SetOption("ScheduleRandom", this->ScheduleRandom);
+  if (!args.ScheduleRandom.empty()) {
+    handler->TestOptions.ScheduleRandom = cmValue(args.ScheduleRandom).IsOn();
   }
-  if (!this->ResourceSpecFile.empty()) {
-    handler->SetOption("ResourceSpecFile", this->ResourceSpecFile);
+  if (!args.ResourceSpecFile.empty()) {
+    handler->TestOptions.ResourceSpecFile = args.ResourceSpecFile;
   }
-  if (!this->StopTime.empty()) {
-    this->CTest->SetStopTime(this->StopTime);
+  if (!args.StopTime.empty()) {
+    this->CTest->SetStopTime(args.StopTime);
   }
 
   // Test load is determined by: TEST_LOAD argument,
   // or CTEST_TEST_LOAD script variable, or ctest --test-load
   // command line argument... in that order.
   unsigned long testLoad;
-  cmValue ctestTestLoad = this->Makefile->GetDefinition("CTEST_TEST_LOAD");
-  if (!this->TestLoad.empty()) {
-    if (!cmStrToULong(this->TestLoad, &testLoad)) {
+  cmValue ctestTestLoad = mf.GetDefinition("CTEST_TEST_LOAD");
+  if (!args.TestLoad.empty()) {
+    if (!cmStrToULong(args.TestLoad, &testLoad)) {
       testLoad = 0;
       cmCTestLog(this->CTest, WARNING,
-                 "Invalid value for 'TEST_LOAD' : " << this->TestLoad
+                 "Invalid value for 'TEST_LOAD' : " << args.TestLoad
                                                     << std::endl);
     }
   } else if (cmNonempty(ctestTestLoad)) {
@@ -146,22 +125,32 @@
   handler->SetTestLoad(testLoad);
 
   if (cmValue labelsForSubprojects =
-        this->Makefile->GetDefinition("CTEST_LABELS_FOR_SUBPROJECTS")) {
+        mf.GetDefinition("CTEST_LABELS_FOR_SUBPROJECTS")) {
     this->CTest->SetCTestConfiguration("LabelsForSubprojects",
-                                       *labelsForSubprojects, this->Quiet);
+                                       *labelsForSubprojects, args.Quiet);
   }
 
-  if (!this->OutputJUnit.empty()) {
-    handler->SetJUnitXMLFileName(this->OutputJUnit);
+  if (!args.OutputJUnit.empty()) {
+    handler->SetJUnitXMLFileName(args.OutputJUnit);
   }
 
-  handler->SetQuiet(this->Quiet);
-  return handler;
+  handler->SetQuiet(args.Quiet);
+  return std::unique_ptr<cmCTestGenericHandler>(std::move(handler));
 }
 
-cmCTestTestHandler* cmCTestTestCommand::InitializeActualHandler()
+std::unique_ptr<cmCTestTestHandler>
+cmCTestTestCommand::InitializeActualHandler(HandlerArguments&,
+                                            cmExecutionStatus&) const
 {
-  cmCTestTestHandler* handler = this->CTest->GetTestHandler();
-  handler->Initialize();
-  return handler;
+  return cm::make_unique<cmCTestTestHandler>(this->CTest);
+}
+
+bool cmCTestTestCommand::InitialPass(std::vector<std::string> const& args,
+                                     cmExecutionStatus& status) const
+{
+  static auto const parser = MakeTestParser<TestArguments>();
+
+  return this->Invoke(parser, args, status, [&](TestArguments& a) {
+    return this->ExecuteHandlerCommand(a, status);
+  });
 }
diff --git a/Source/CTest/cmCTestTestCommand.h b/Source/CTest/cmCTestTestCommand.h
index 23661c5..038d219 100644
--- a/Source/CTest/cmCTestTestCommand.h
+++ b/Source/CTest/cmCTestTestCommand.h
@@ -4,66 +4,88 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
+#include <memory>
 #include <string>
-#include <utility>
+#include <type_traits>
+#include <vector>
 
-#include <cm/memory>
 #include <cm/optional>
+#include <cmext/string_view>
 
+#include "cmArgumentParser.h"
 #include "cmArgumentParserTypes.h"
 #include "cmCTestHandlerCommand.h"
-#include "cmCommand.h"
 
+class cmExecutionStatus;
 class cmCTestGenericHandler;
 class cmCTestTestHandler;
 
-/** \class cmCTestTest
- * \brief Run a ctest script
- *
- * cmCTestTestCommand defineds the command to test the project.
- */
 class cmCTestTestCommand : public cmCTestHandlerCommand
 {
 public:
-  /**
-   * This is a virtual constructor for the command.
-   */
-  std::unique_ptr<cmCommand> Clone() override
-  {
-    auto ni = cm::make_unique<cmCTestTestCommand>();
-    ni->CTest = this->CTest;
-    ni->CTestScriptHandler = this->CTestScriptHandler;
-    return std::unique_ptr<cmCommand>(std::move(ni));
-  }
-
-  /**
-   * The name of the command as specified in CMakeList.txt.
-   */
-  std::string GetName() const override { return "ctest_test"; }
+  using cmCTestHandlerCommand::cmCTestHandlerCommand;
 
 protected:
-  void BindArguments() override;
-  virtual cmCTestTestHandler* InitializeActualHandler();
-  cmCTestGenericHandler* InitializeHandler() override;
+  struct TestArguments : HandlerArguments
+  {
+    std::string Start;
+    std::string End;
+    std::string Stride;
+    std::string Exclude;
+    std::string Include;
+    std::string ExcludeLabel;
+    std::string IncludeLabel;
+    std::string IncludeTestsFromFile;
+    std::string ExcludeTestsFromFile;
+    std::string ExcludeFixture;
+    std::string ExcludeFixtureSetup;
+    std::string ExcludeFixtureCleanup;
+    cm::optional<ArgumentParser::Maybe<std::string>> ParallelLevel;
+    std::string Repeat;
+    std::string ScheduleRandom;
+    std::string StopTime;
+    std::string TestLoad;
+    std::string ResourceSpecFile;
+    std::string OutputJUnit;
+    bool StopOnFailure = false;
+  };
 
-  std::string Start;
-  std::string End;
-  std::string Stride;
-  std::string Exclude;
-  std::string Include;
-  std::string ExcludeLabel;
-  std::string IncludeLabel;
-  std::string IncludeTestsFromFile;
-  std::string ExcludeTestsFromFile;
-  std::string ExcludeFixture;
-  std::string ExcludeFixtureSetup;
-  std::string ExcludeFixtureCleanup;
-  cm::optional<ArgumentParser::Maybe<std::string>> ParallelLevel;
-  std::string Repeat;
-  std::string ScheduleRandom;
-  std::string StopTime;
-  std::string TestLoad;
-  std::string ResourceSpecFile;
-  std::string OutputJUnit;
-  bool StopOnFailure = false;
+  template <typename Args>
+  static auto MakeTestParser() -> cmArgumentParser<Args>
+  {
+    static_assert(std::is_base_of<TestArguments, Args>::value, "");
+    return cmArgumentParser<Args>{ MakeHandlerParser<Args>() }
+      .Bind("START"_s, &TestArguments::Start)
+      .Bind("END"_s, &TestArguments::End)
+      .Bind("STRIDE"_s, &TestArguments::Stride)
+      .Bind("EXCLUDE"_s, &TestArguments::Exclude)
+      .Bind("INCLUDE"_s, &TestArguments::Include)
+      .Bind("EXCLUDE_LABEL"_s, &TestArguments::ExcludeLabel)
+      .Bind("INCLUDE_LABEL"_s, &TestArguments::IncludeLabel)
+      .Bind("EXCLUDE_FROM_FILE"_s, &TestArguments::ExcludeTestsFromFile)
+      .Bind("INCLUDE_FROM_FILE"_s, &TestArguments::IncludeTestsFromFile)
+      .Bind("EXCLUDE_FIXTURE"_s, &TestArguments::ExcludeFixture)
+      .Bind("EXCLUDE_FIXTURE_SETUP"_s, &TestArguments::ExcludeFixtureSetup)
+      .Bind("EXCLUDE_FIXTURE_CLEANUP"_s, &TestArguments::ExcludeFixtureCleanup)
+      .Bind("PARALLEL_LEVEL"_s, &TestArguments::ParallelLevel)
+      .Bind("REPEAT"_s, &TestArguments::Repeat)
+      .Bind("SCHEDULE_RANDOM"_s, &TestArguments::ScheduleRandom)
+      .Bind("STOP_TIME"_s, &TestArguments::StopTime)
+      .Bind("TEST_LOAD"_s, &TestArguments::TestLoad)
+      .Bind("RESOURCE_SPEC_FILE"_s, &TestArguments::ResourceSpecFile)
+      .Bind("STOP_ON_FAILURE"_s, &TestArguments::StopOnFailure)
+      .Bind("OUTPUT_JUNIT"_s, &TestArguments::OutputJUnit);
+  }
+
+private:
+  std::string GetName() const override { return "ctest_test"; }
+
+  virtual std::unique_ptr<cmCTestTestHandler> InitializeActualHandler(
+    HandlerArguments& arguments, cmExecutionStatus& status) const;
+
+  std::unique_ptr<cmCTestGenericHandler> InitializeHandler(
+    HandlerArguments& arguments, cmExecutionStatus& status) const override;
+
+  bool InitialPass(std::vector<std::string> const& args,
+                   cmExecutionStatus& status) const override;
 };
diff --git a/Source/CTest/cmCTestTestHandler.cxx b/Source/CTest/cmCTestTestHandler.cxx
index c7875cd..f42d84e 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;
@@ -130,7 +128,7 @@
     status.SetError("called with incorrect number of arguments");
     return false;
   }
-  std::string cwd = cmSystemTools::GetCurrentWorkingDirectory();
+  std::string cwd = cmSystemTools::GetLogicalWorkingDirectory();
   for (std::string const& arg : args) {
     std::string fname;
 
@@ -156,7 +154,7 @@
   }
 
   std::string fname =
-    cmStrCat(cmSystemTools::GetCurrentWorkingDirectory(), '/', args[0]);
+    cmStrCat(cmSystemTools::GetLogicalWorkingDirectory(), '/', args[0]);
 
   return ReadSubdirectory(std::move(fname), status);
 }
@@ -276,25 +274,10 @@
 
 } // namespace
 
-cmCTestTestHandler::cmCTestTestHandler()
+cmCTestTestHandler::cmCTestTestHandler(cmCTest* ctest)
+  : Superclass(ctest)
+  , TestOptions(ctest->GetTestOptions())
 {
-  this->UseUnion = false;
-
-  this->UseIncludeRegExpFlag = false;
-  this->UseExcludeRegExpFlag = false;
-  this->UseExcludeRegExpFirst = false;
-
-  this->CustomMaximumPassedTestOutputSize = 1 * 1024;
-  this->CustomMaximumFailedTestOutputSize = 300 * 1024;
-  this->TestOutputTruncation = cmCTestTypes::TruncationMode::Tail;
-
-  this->MemCheck = false;
-
-  this->LogFile = nullptr;
-
-  // Support for JUnit XML output.
-  this->JUnitXMLFileName = "";
-
   // Regular expressions to scan test output for custom measurements.
 
   // Capture the whole section of test output from the first opening
@@ -315,46 +298,6 @@
   this->CustomLabelRegex.compile("<CTestLabel>(.*)</CTestLabel>");
 }
 
-void cmCTestTestHandler::Initialize()
-{
-  this->Superclass::Initialize();
-
-  this->ElapsedTestingTime = cmDuration();
-
-  this->TestResults.clear();
-
-  this->CustomTestsIgnore.clear();
-  this->StartTest.clear();
-  this->EndTest.clear();
-
-  this->CustomPreTest.clear();
-  this->CustomPostTest.clear();
-  this->CustomMaximumPassedTestOutputSize = 1 * 1024;
-  this->CustomMaximumFailedTestOutputSize = 300 * 1024;
-  this->TestOutputTruncation = cmCTestTypes::TruncationMode::Tail;
-
-  this->TestsToRun.clear();
-
-  this->UseIncludeRegExpFlag = false;
-  this->UseExcludeRegExpFlag = false;
-  this->UseExcludeRegExpFirst = false;
-  this->IncludeLabelRegularExpressions.clear();
-  this->ExcludeLabelRegularExpressions.clear();
-  this->IncludeRegExp.clear();
-  this->ExcludeRegExp.clear();
-  this->ExcludeFixtureRegExp.clear();
-  this->ExcludeFixtureSetupRegExp.clear();
-  this->ExcludeFixtureCleanupRegExp.clear();
-  this->TestListFile.clear();
-  this->ExcludeTestListFile.clear();
-  this->TestsToRunByName.reset();
-  this->TestsToExcludeByName.reset();
-
-  this->TestsToRunString.clear();
-  this->UseUnion = false;
-  this->TestList.clear();
-}
-
 void cmCTestTestHandler::PopulateCustomVectors(cmMakefile* mf)
 {
   this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_PRE_TEST",
@@ -365,14 +308,14 @@
                                     this->CustomTestsIgnore);
   this->CTest->PopulateCustomInteger(
     mf, "CTEST_CUSTOM_MAXIMUM_PASSED_TEST_OUTPUT_SIZE",
-    this->CustomMaximumPassedTestOutputSize);
+    this->TestOptions.OutputSizePassed);
   this->CTest->PopulateCustomInteger(
     mf, "CTEST_CUSTOM_MAXIMUM_FAILED_TEST_OUTPUT_SIZE",
-    this->CustomMaximumFailedTestOutputSize);
+    this->TestOptions.OutputSizeFailed);
 
   cmValue dval = mf->GetDefinition("CTEST_CUSTOM_TEST_OUTPUT_TRUNCATION");
   if (dval) {
-    if (!this->SetTestOutputTruncation(*dval)) {
+    if (!SetTruncationMode(this->TestOptions.OutputTruncation, *dval)) {
       cmCTestLog(this->CTest, ERROR_MESSAGE,
                  "Invalid value for CTEST_CUSTOM_TEST_OUTPUT_TRUNCATION: "
                    << *dval << std::endl);
@@ -411,7 +354,7 @@
   cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
                      (this->MemCheck ? "Memory check" : "Test")
                        << " project "
-                       << cmSystemTools::GetCurrentWorkingDirectory()
+                       << cmSystemTools::GetLogicalWorkingDirectory()
                        << std::endl,
                      this->Quiet);
   if (!this->PreProcessHandler()) {
@@ -521,12 +464,11 @@
 bool cmCTestTestHandler::ProcessOptions()
 {
   // Update internal data structure from generic one
-  this->SetTestsToRunInformation(this->GetOption("TestsToRunInformation"));
-  this->SetUseUnion(this->GetOption("UseUnion").IsOn());
-  if (this->GetOption("ScheduleRandom").IsOn()) {
+  this->SetTestsToRunInformation(this->TestOptions.TestsToRunInformation);
+  if (this->TestOptions.ScheduleRandom) {
     this->CTest->SetScheduleType("Random");
   }
-  if (cmValue repeat = this->GetOption("Repeat")) {
+  if (auto repeat = this->Repeat) {
     cmsys::RegularExpression repeatRegex(
       "^(UNTIL_FAIL|UNTIL_PASS|AFTER_TIMEOUT):([0-9]+)$");
     if (repeatRegex.find(*repeat)) {
@@ -550,8 +492,8 @@
       return false;
     }
   }
-  if (cmValue parallelLevel = this->GetOption("ParallelLevel")) {
-    if (parallelLevel.IsEmpty()) {
+  if (auto parallelLevel = this->ParallelLevel) {
+    if (parallelLevel->empty()) {
       // An empty value tells ctest to choose a default.
       this->CTest->SetParallelLevel(cm::nullopt);
     } else {
@@ -567,50 +509,20 @@
     }
   }
 
-  if (this->GetOption("StopOnFailure")) {
+  if (this->TestOptions.StopOnFailure) {
     this->CTest->SetStopOnFailure(true);
   }
 
-  BuildLabelRE(this->GetMultiOption("LabelRegularExpression"),
+  BuildLabelRE(this->TestOptions.LabelRegularExpression,
                this->IncludeLabelRegularExpressions);
-  BuildLabelRE(this->GetMultiOption("ExcludeLabelRegularExpression"),
+  BuildLabelRE(this->TestOptions.ExcludeLabelRegularExpression,
                this->ExcludeLabelRegularExpressions);
-  cmValue val = this->GetOption("IncludeRegularExpression");
-  if (val) {
+  if (!this->TestOptions.IncludeRegularExpression.empty()) {
     this->UseIncludeRegExp();
-    this->SetIncludeRegExp(*val);
   }
-  val = this->GetOption("ExcludeRegularExpression");
-  if (val) {
+  if (!this->TestOptions.ExcludeRegularExpression.empty()) {
     this->UseExcludeRegExp();
-    this->SetExcludeRegExp(*val);
   }
-  val = this->GetOption("ExcludeFixtureRegularExpression");
-  if (val) {
-    this->ExcludeFixtureRegExp = *val;
-  }
-  val = this->GetOption("ExcludeFixtureSetupRegularExpression");
-  if (val) {
-    this->ExcludeFixtureSetupRegExp = *val;
-  }
-  val = this->GetOption("ExcludeFixtureCleanupRegularExpression");
-  if (val) {
-    this->ExcludeFixtureCleanupRegExp = *val;
-  }
-  val = this->GetOption("ResourceSpecFile");
-  if (val) {
-    this->ResourceSpecFile = *val;
-  }
-  val = this->GetOption("TestListFile");
-  if (val) {
-    this->TestListFile = val;
-  }
-  val = this->GetOption("ExcludeTestListFile");
-  if (val) {
-    this->ExcludeTestListFile = val;
-  }
-  this->SetRerunFailed(this->GetOption("RerunFailed").IsOn());
-
   return true;
 }
 
@@ -922,7 +834,7 @@
     return false;
   }
 
-  if (this->RerunFailed) {
+  if (this->TestOptions.RerunFailed) {
     return this->ComputeTestListForRerunFailed();
   }
 
@@ -936,7 +848,7 @@
     }
   }
   // expand the test list based on the union flag
-  if (this->UseUnion) {
+  if (this->TestOptions.UseUnion) {
     this->ExpandTestsToRunInformation(static_cast<int>(tmsize));
   } else {
     this->ExpandTestsToRunInformation(inREcnt);
@@ -951,7 +863,7 @@
       inREcnt++;
     }
 
-    if (this->UseUnion) {
+    if (this->TestOptions.UseUnion) {
       // if it is not in the list and not in the regexp then skip
       if ((!this->TestsToRun.empty() &&
            !cm::contains(this->TestsToRun, cnt)) &&
@@ -1034,22 +946,24 @@
                      this->Quiet);
 
   // Prepare regular expression evaluators
-  std::string setupRegExp(this->ExcludeFixtureRegExp);
-  std::string cleanupRegExp(this->ExcludeFixtureRegExp);
-  if (!this->ExcludeFixtureSetupRegExp.empty()) {
+  std::string setupRegExp(this->TestOptions.ExcludeFixtureRegularExpression);
+  std::string cleanupRegExp(this->TestOptions.ExcludeFixtureRegularExpression);
+  if (!this->TestOptions.ExcludeFixtureSetupRegularExpression.empty()) {
     if (setupRegExp.empty()) {
-      setupRegExp = this->ExcludeFixtureSetupRegExp;
+      setupRegExp = this->TestOptions.ExcludeFixtureSetupRegularExpression;
     } else {
-      setupRegExp.append("(" + setupRegExp + ")|(" +
-                         this->ExcludeFixtureSetupRegExp + ")");
+      setupRegExp.append(
+        "(" + setupRegExp + ")|(" +
+        this->TestOptions.ExcludeFixtureSetupRegularExpression + ")");
     }
   }
-  if (!this->ExcludeFixtureCleanupRegExp.empty()) {
+  if (!this->TestOptions.ExcludeFixtureCleanupRegularExpression.empty()) {
     if (cleanupRegExp.empty()) {
-      cleanupRegExp = this->ExcludeFixtureCleanupRegExp;
+      cleanupRegExp = this->TestOptions.ExcludeFixtureCleanupRegularExpression;
     } else {
-      cleanupRegExp.append("(" + cleanupRegExp + ")|(" +
-                           this->ExcludeFixtureCleanupRegExp + ")");
+      cleanupRegExp.append(
+        "(" + cleanupRegExp + ")|(" +
+        this->TestOptions.ExcludeFixtureCleanupRegularExpression + ")");
     }
   }
   cmsys::RegularExpression excludeSetupRegex(setupRegExp);
@@ -1431,7 +1345,7 @@
     tests[p.Index].Depends = depends;
     properties[p.Index] = &p;
   }
-  parallel->SetResourceSpecFile(this->ResourceSpecFile);
+  parallel->SetResourceSpecFile(this->TestOptions.ResourceSpecFile);
   if (!parallel->SetTests(std::move(tests), std::move(properties))) {
     return false;
   }
@@ -1467,7 +1381,7 @@
     return;
   }
 
-  this->CTest->StartXML(xml, this->AppendXML);
+  this->CTest->StartXML(xml, this->CMake, this->AppendXML);
   this->CTest->GenerateSubprojectsOutput(xml);
   xml.StartElement("Testing");
   xml.Element("StartDateTime", this->StartTest);
@@ -1761,7 +1675,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 +1684,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);
@@ -1810,11 +1724,13 @@
 
 bool cmCTestTestHandler::GetListOfTests()
 {
-  if (!this->IncludeRegExp.empty()) {
-    this->IncludeTestsRegularExpression.compile(this->IncludeRegExp);
+  if (!this->TestOptions.IncludeRegularExpression.empty()) {
+    this->IncludeTestsRegularExpression.compile(
+      this->TestOptions.IncludeRegularExpression);
   }
-  if (!this->ExcludeRegExp.empty()) {
-    this->ExcludeTestsRegularExpression.compile(this->ExcludeRegExp);
+  if (!this->TestOptions.ExcludeRegularExpression.empty()) {
+    this->ExcludeTestsRegularExpression.compile(
+      this->TestOptions.ExcludeRegularExpression);
   }
   cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                      "Constructing a list of tests" << std::endl, this->Quiet);
@@ -1864,19 +1780,20 @@
     return false;
   }
   cmValue specFile = mf.GetDefinition("CTEST_RESOURCE_SPEC_FILE");
-  if (this->ResourceSpecFile.empty() && specFile) {
-    this->ResourceSpecFile = *specFile;
+  if (this->TestOptions.ResourceSpecFile.empty() && specFile) {
+    this->TestOptions.ResourceSpecFile = *specFile;
   }
 
-  if (!this->TestListFile.empty()) {
-    this->TestsToRunByName = this->ReadTestListFile(this->TestListFile);
+  if (!this->TestOptions.TestListFile.empty()) {
+    this->TestsToRunByName =
+      this->ReadTestListFile(this->TestOptions.TestListFile);
     if (!this->TestsToRunByName) {
       return false;
     }
   }
-  if (!this->ExcludeTestListFile.empty()) {
+  if (!this->TestOptions.ExcludeTestListFile.empty()) {
     this->TestsToExcludeByName =
-      this->ReadTestListFile(this->ExcludeTestListFile);
+      this->ReadTestListFile(this->TestOptions.ExcludeTestListFile);
     if (!this->TestsToExcludeByName) {
       return false;
     }
@@ -2153,41 +2070,14 @@
   }
 }
 
-void cmCTestTestHandler::SetIncludeRegExp(const std::string& arg)
+void cmCTestTestHandler::SetTestsToRunInformation(std::string const& in)
 {
-  this->IncludeRegExp = arg;
-}
-
-void cmCTestTestHandler::SetExcludeRegExp(const std::string& arg)
-{
-  this->ExcludeRegExp = arg;
-}
-
-bool cmCTestTestHandler::SetTestOutputTruncation(const std::string& mode)
-{
-  if (mode == "tail") {
-    this->TestOutputTruncation = cmCTestTypes::TruncationMode::Tail;
-  } else if (mode == "middle") {
-    this->TestOutputTruncation = cmCTestTypes::TruncationMode::Middle;
-  } else if (mode == "head") {
-    this->TestOutputTruncation = cmCTestTypes::TruncationMode::Head;
-  } else {
-    return false;
-  }
-  return true;
-}
-
-void cmCTestTestHandler::SetTestsToRunInformation(cmValue in)
-{
-  if (!in) {
-    return;
-  }
-  this->TestsToRunString = *in;
+  this->TestsToRunString = in;
   // if the argument is a file, then read it and use the contents as the
   // string
-  if (cmSystemTools::FileExists(*in)) {
-    cmsys::ifstream fin(in->c_str());
-    unsigned long filelen = cmSystemTools::FileLength(*in);
+  if (cmSystemTools::FileExists(in)) {
+    cmsys::ifstream fin(in.c_str());
+    unsigned long filelen = cmSystemTools::FileLength(in);
     auto buff = cm::make_unique<char[]>(filelen + 1);
     fin.getline(buff.get(), filelen);
     buff[fin.gcount()] = 0;
@@ -2495,7 +2385,7 @@
     }
     std::string const& val = *it;
     for (cmCTestTestProperties& rt : this->TestList) {
-      std::string cwd = cmSystemTools::GetCurrentWorkingDirectory();
+      std::string cwd = cmSystemTools::GetLogicalWorkingDirectory();
       if (cwd == rt.Directory) {
         if (key == "LABELS"_s) {
           cmList DirectoryLabels{ val };
@@ -2559,7 +2449,7 @@
   cmCTestTestProperties test;
   test.Name = testname;
   test.Args = args;
-  test.Directory = cmSystemTools::GetCurrentWorkingDirectory();
+  test.Directory = cmSystemTools::GetLogicalWorkingDirectory();
   cmCTestOptionalLog(this->CTest, DEBUG,
                      "Set test directory: " << test.Directory << std::endl,
                      this->Quiet);
@@ -2590,22 +2480,22 @@
 
 void cmCTestTestHandler::SetJUnitXMLFileName(const std::string& filename)
 {
-  this->JUnitXMLFileName = filename;
+  this->TestOptions.JUnitXMLFileName = filename;
 }
 
 bool cmCTestTestHandler::WriteJUnitXML()
 {
-  if (this->JUnitXMLFileName.empty()) {
+  if (this->TestOptions.JUnitXMLFileName.empty()) {
     return true;
   }
 
   // Open new XML file for writing.
   cmGeneratedFileStream xmlfile;
   xmlfile.SetTempExt("tmp");
-  xmlfile.Open(this->JUnitXMLFileName);
+  xmlfile.Open(this->TestOptions.JUnitXMLFileName);
   if (!xmlfile) {
     cmCTestLog(this->CTest, ERROR_MESSAGE,
-               "Problem opening file: " << this->JUnitXMLFileName
+               "Problem opening file: " << this->TestOptions.JUnitXMLFileName
                                         << std::endl);
     return false;
   }
diff --git a/Source/CTest/cmCTestTestHandler.h b/Source/CTest/cmCTestTestHandler.h
index c35af3f..a9e2d67 100644
--- a/Source/CTest/cmCTestTestHandler.h
+++ b/Source/CTest/cmCTestTestHandler.h
@@ -24,11 +24,39 @@
 #include "cmCTestTypes.h" // IWYU pragma: keep
 #include "cmDuration.h"
 #include "cmListFileCache.h"
-#include "cmValue.h"
 
 class cmMakefile;
 class cmXMLWriter;
 
+struct cmCTestTestOptions
+{
+  bool RerunFailed = false;
+  bool ScheduleRandom = false;
+  bool StopOnFailure = false;
+  bool UseUnion = false;
+
+  int OutputSizePassed = 1 * 1024;
+  int OutputSizeFailed = 300 * 1024;
+  cmCTestTypes::TruncationMode OutputTruncation =
+    cmCTestTypes::TruncationMode::Tail;
+
+  std::string TestsToRunInformation;
+  std::string IncludeRegularExpression;
+  std::string ExcludeRegularExpression;
+
+  std::vector<std::string> LabelRegularExpression;
+  std::vector<std::string> ExcludeLabelRegularExpression;
+
+  std::string ExcludeFixtureRegularExpression;
+  std::string ExcludeFixtureSetupRegularExpression;
+  std::string ExcludeFixtureCleanupRegularExpression;
+
+  std::string TestListFile;
+  std::string ExcludeTestListFile;
+  std::string ResourceSpecFile;
+  std::string JUnitXMLFileName;
+};
+
 /** \class cmCTestTestHandler
  * \brief A class that handles ctest -S invocations
  *
@@ -48,19 +76,6 @@
   int ProcessHandler() override;
 
   /**
-   * When both -R and -I are used should the resulting test list be the
-   * intersection or the union of the lists. By default it is the
-   * intersection.
-   */
-  void SetUseUnion(bool val) { this->UseUnion = val; }
-
-  /**
-   * Set whether or not CTest should only execute the tests that failed
-   * on the previous run.  By default this is false.
-   */
-  void SetRerunFailed(bool val) { this->RerunFailed = val; }
-
-  /**
    * This method is called when reading CTest custom file
    */
   void PopulateCustomVectors(cmMakefile* mf) override;
@@ -69,28 +84,14 @@
   /// them on
   void UseIncludeRegExp();
   void UseExcludeRegExp();
-  void SetIncludeRegExp(const std::string&);
-  void SetExcludeRegExp(const std::string&);
 
   void SetMaxIndex(int n) { this->MaxIndex = n; }
   int GetMaxIndex() { return this->MaxIndex; }
 
-  void SetTestOutputSizePassed(int n)
-  {
-    this->CustomMaximumPassedTestOutputSize = n;
-  }
-  void SetTestOutputSizeFailed(int n)
-  {
-    this->CustomMaximumFailedTestOutputSize = n;
-  }
-
-  //! Set test output truncation mode. Return false if unknown mode.
-  bool SetTestOutputTruncation(const std::string& mode);
-
   //! pass the -I argument down
-  void SetTestsToRunInformation(cmValue);
+  void SetTestsToRunInformation(std::string const& in);
 
-  cmCTestTestHandler();
+  cmCTestTestHandler(cmCTest* ctest);
 
   /*
    * Add the test to the list of tests to be executed
@@ -107,8 +108,6 @@
    */
   bool SetDirectoryProperties(const std::vector<std::string>& args);
 
-  void Initialize() override;
-
   struct cmCTestTestResourceRequirement
   {
     std::string ResourceType;
@@ -260,6 +259,8 @@
   void CleanTestOutput(std::string& output, size_t length,
                        cmCTestTypes::TruncationMode truncate);
 
+  cmCTestTestOptions TestOptions;
+
   cmDuration ElapsedTestingTime;
 
   using TestResultsVector = std::vector<cmCTestTestResult>;
@@ -270,10 +271,7 @@
   std::string EndTest;
   std::chrono::system_clock::time_point StartTestTime;
   std::chrono::system_clock::time_point EndTestTime;
-  bool MemCheck;
-  int CustomMaximumPassedTestOutputSize;
-  int CustomMaximumFailedTestOutputSize;
-  cmCTestTypes::TruncationMode TestOutputTruncation;
+  bool MemCheck = false;
   int MaxIndex;
 
 public:
@@ -350,24 +348,17 @@
 
   std::vector<int> TestsToRun;
 
-  bool UseIncludeRegExpFlag;
-  bool UseExcludeRegExpFlag;
-  bool UseExcludeRegExpFirst;
-  std::string IncludeRegExp;
-  std::string ExcludeRegExp;
-  std::string ExcludeFixtureRegExp;
-  std::string ExcludeFixtureSetupRegExp;
-  std::string ExcludeFixtureCleanupRegExp;
+  bool UseIncludeRegExpFlag = false;
+  bool UseExcludeRegExpFlag = false;
+  bool UseExcludeRegExpFirst = false;
   std::vector<cmsys::RegularExpression> IncludeLabelRegularExpressions;
   std::vector<cmsys::RegularExpression> ExcludeLabelRegularExpressions;
   cmsys::RegularExpression IncludeTestsRegularExpression;
   cmsys::RegularExpression ExcludeTestsRegularExpression;
-  std::string TestListFile;
-  std::string ExcludeTestListFile;
   cm::optional<std::set<std::string>> TestsToRunByName;
   cm::optional<std::set<std::string>> TestsToExcludeByName;
-
-  std::string ResourceSpecFile;
+  cm::optional<std::string> ParallelLevel;
+  cm::optional<std::string> Repeat;
 
   void RecordCustomTestMeasurements(cmXMLWriter& xml, std::string content);
   void CheckLabelFilter(cmCTestTestProperties& it);
@@ -375,7 +366,6 @@
   void CheckLabelFilterInclude(cmCTestTestProperties& it);
 
   std::string TestsToRunString;
-  bool UseUnion;
   ListOfTests TestList;
   size_t TotalNumberOfTests;
   cmsys::RegularExpression AllTestMeasurementsRegex;
@@ -383,11 +373,10 @@
   cmsys::RegularExpression CustomCompletionStatusRegex;
   cmsys::RegularExpression CustomLabelRegex;
 
-  std::ostream* LogFile;
+  std::ostream* LogFile = nullptr;
 
   cmCTest::Repeat RepeatMode = cmCTest::Repeat::Never;
   int RepeatCount = 1;
-  bool RerunFailed;
 
-  std::string JUnitXMLFileName;
+  friend class cmCTestTestCommand;
 };
diff --git a/Source/CTest/cmCTestTypes.cxx b/Source/CTest/cmCTestTypes.cxx
new file mode 100644
index 0000000..9a06cd9
--- /dev/null
+++ b/Source/CTest/cmCTestTypes.cxx
@@ -0,0 +1,24 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include "cmCTestTypes.h"
+
+#include <string>
+
+namespace cmCTestTypes {
+
+bool SetTruncationMode(TruncationMode& mode, cm::string_view str)
+{
+  if (str == "tail") {
+    mode = cmCTestTypes::TruncationMode::Tail;
+  } else if (str == "middle") {
+    mode = cmCTestTypes::TruncationMode::Middle;
+  } else if (str == "head") {
+    mode = cmCTestTypes::TruncationMode::Head;
+  } else {
+    return false;
+  }
+  return true;
+}
+
+} // namespace cmCTestTypes
diff --git a/Source/CTest/cmCTestTypes.h b/Source/CTest/cmCTestTypes.h
index 843d27a..0dfc4a5 100644
--- a/Source/CTest/cmCTestTypes.h
+++ b/Source/CTest/cmCTestTypes.h
@@ -5,12 +5,18 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
+#include <cm/string_view>
+
 namespace cmCTestTypes {
 
+// Test output truncation mode
 enum class TruncationMode
-{ // Test output truncation mode
+{
   Tail,
   Middle,
   Head
 };
-}
+
+bool SetTruncationMode(TruncationMode& mode, cm::string_view str);
+
+} // namespace cmCTestTypes
diff --git a/Source/CTest/cmCTestUpdateCommand.cxx b/Source/CTest/cmCTestUpdateCommand.cxx
index 6655bf7..a691088 100644
--- a/Source/CTest/cmCTestUpdateCommand.cxx
+++ b/Source/CTest/cmCTestUpdateCommand.cxx
@@ -2,84 +2,94 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmCTestUpdateCommand.h"
 
+#include <utility>
+
+#include <cm/memory>
+
 #include "cmCTest.h"
+#include "cmCTestGenericHandler.h"
 #include "cmCTestUpdateHandler.h"
+#include "cmExecutionStatus.h"
 #include "cmMakefile.h"
 #include "cmSystemTools.h"
 
-cmCTestGenericHandler* cmCTestUpdateCommand::InitializeHandler()
+std::unique_ptr<cmCTestGenericHandler> cmCTestUpdateCommand::InitializeHandler(
+  HandlerArguments& args, cmExecutionStatus& status) const
 {
-  if (!this->Source.empty()) {
+  cmMakefile& mf = status.GetMakefile();
+  if (!args.Source.empty()) {
     this->CTest->SetCTestConfiguration(
-      "SourceDirectory", cmSystemTools::CollapseFullPath(this->Source),
-      this->Quiet);
+      "SourceDirectory", cmSystemTools::CollapseFullPath(args.Source),
+      args.Quiet);
   } else {
     this->CTest->SetCTestConfiguration(
       "SourceDirectory",
       cmSystemTools::CollapseFullPath(
-        this->Makefile->GetSafeDefinition("CTEST_SOURCE_DIRECTORY")),
-      this->Quiet);
+        mf.GetSafeDefinition("CTEST_SOURCE_DIRECTORY")),
+      args.Quiet);
   }
   std::string source_dir =
     this->CTest->GetCTestConfiguration("SourceDirectory");
 
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "UpdateCommand", "CTEST_UPDATE_COMMAND", this->Quiet);
+    &mf, "UpdateCommand", "CTEST_UPDATE_COMMAND", args.Quiet);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "UpdateOptions", "CTEST_UPDATE_OPTIONS", this->Quiet);
+    &mf, "UpdateOptions", "CTEST_UPDATE_OPTIONS", args.Quiet);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "CVSCommand", "CTEST_CVS_COMMAND", this->Quiet);
+    &mf, "CVSCommand", "CTEST_CVS_COMMAND", args.Quiet);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "CVSUpdateOptions", "CTEST_CVS_UPDATE_OPTIONS",
-    this->Quiet);
+    &mf, "CVSUpdateOptions", "CTEST_CVS_UPDATE_OPTIONS", args.Quiet);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "SVNCommand", "CTEST_SVN_COMMAND", this->Quiet);
+    &mf, "SVNCommand", "CTEST_SVN_COMMAND", args.Quiet);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "SVNUpdateOptions", "CTEST_SVN_UPDATE_OPTIONS",
-    this->Quiet);
+    &mf, "SVNUpdateOptions", "CTEST_SVN_UPDATE_OPTIONS", args.Quiet);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "SVNOptions", "CTEST_SVN_OPTIONS", this->Quiet);
+    &mf, "SVNOptions", "CTEST_SVN_OPTIONS", args.Quiet);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "BZRCommand", "CTEST_BZR_COMMAND", this->Quiet);
+    &mf, "BZRCommand", "CTEST_BZR_COMMAND", args.Quiet);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "BZRUpdateOptions", "CTEST_BZR_UPDATE_OPTIONS",
-    this->Quiet);
+    &mf, "BZRUpdateOptions", "CTEST_BZR_UPDATE_OPTIONS", args.Quiet);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "GITCommand", "CTEST_GIT_COMMAND", this->Quiet);
+    &mf, "GITCommand", "CTEST_GIT_COMMAND", args.Quiet);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "GITUpdateOptions", "CTEST_GIT_UPDATE_OPTIONS",
-    this->Quiet);
+    &mf, "GITUpdateOptions", "CTEST_GIT_UPDATE_OPTIONS", args.Quiet);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "GITInitSubmodules", "CTEST_GIT_INIT_SUBMODULES",
-    this->Quiet);
+    &mf, "GITInitSubmodules", "CTEST_GIT_INIT_SUBMODULES", args.Quiet);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "GITUpdateCustom", "CTEST_GIT_UPDATE_CUSTOM", this->Quiet);
+    &mf, "GITUpdateCustom", "CTEST_GIT_UPDATE_CUSTOM", args.Quiet);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "UpdateVersionOnly", "CTEST_UPDATE_VERSION_ONLY",
-    this->Quiet);
+    &mf, "UpdateVersionOnly", "CTEST_UPDATE_VERSION_ONLY", args.Quiet);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "UpdateVersionOverride", "CTEST_UPDATE_VERSION_OVERRIDE",
-    this->Quiet);
+    &mf, "UpdateVersionOverride", "CTEST_UPDATE_VERSION_OVERRIDE", args.Quiet);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "HGCommand", "CTEST_HG_COMMAND", this->Quiet);
+    &mf, "HGCommand", "CTEST_HG_COMMAND", args.Quiet);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "HGUpdateOptions", "CTEST_HG_UPDATE_OPTIONS", this->Quiet);
+    &mf, "HGUpdateOptions", "CTEST_HG_UPDATE_OPTIONS", args.Quiet);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "P4Command", "CTEST_P4_COMMAND", this->Quiet);
+    &mf, "P4Command", "CTEST_P4_COMMAND", args.Quiet);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "P4UpdateOptions", "CTEST_P4_UPDATE_OPTIONS", this->Quiet);
+    &mf, "P4UpdateOptions", "CTEST_P4_UPDATE_OPTIONS", args.Quiet);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "P4Client", "CTEST_P4_CLIENT", this->Quiet);
+    &mf, "P4Client", "CTEST_P4_CLIENT", args.Quiet);
   this->CTest->SetCTestConfigurationFromCMakeVariable(
-    this->Makefile, "P4Options", "CTEST_P4_OPTIONS", this->Quiet);
+    &mf, "P4Options", "CTEST_P4_OPTIONS", args.Quiet);
 
-  cmCTestUpdateHandler* handler = this->CTest->GetUpdateHandler();
-  handler->Initialize();
+  auto handler = cm::make_unique<cmCTestUpdateHandler>(this->CTest);
   if (source_dir.empty()) {
-    this->SetError("source directory not specified. Please use SOURCE tag");
+    status.SetError("source directory not specified. Please use SOURCE tag");
     return nullptr;
   }
-  handler->SetOption("SourceDirectory", source_dir);
-  handler->SetQuiet(this->Quiet);
-  return handler;
+  handler->SourceDirectory = source_dir;
+  handler->SetQuiet(args.Quiet);
+  return std::unique_ptr<cmCTestGenericHandler>(std::move(handler));
+}
+
+bool cmCTestUpdateCommand::InitialPass(std::vector<std::string> const& args,
+                                       cmExecutionStatus& status) const
+{
+  static auto const parser = MakeHandlerParser<HandlerArguments>();
+
+  return this->Invoke(parser, args, status, [&](HandlerArguments& a) {
+    return this->ExecuteHandlerCommand(a, status);
+  });
 }
diff --git a/Source/CTest/cmCTestUpdateCommand.h b/Source/CTest/cmCTestUpdateCommand.h
index e4c3453..7635aa0 100644
--- a/Source/CTest/cmCTestUpdateCommand.h
+++ b/Source/CTest/cmCTestUpdateCommand.h
@@ -4,40 +4,26 @@
 
 #include "cmConfigure.h" // IWYU pragma: keep
 
+#include <memory>
 #include <string>
-#include <utility>
-
-#include <cm/memory>
+#include <vector>
 
 #include "cmCTestHandlerCommand.h"
-#include "cmCommand.h"
 
+class cmExecutionStatus;
 class cmCTestGenericHandler;
 
-/** \class cmCTestUpdate
- * \brief Run a ctest script
- *
- * cmCTestUpdateCommand defineds the command to updates the repository.
- */
 class cmCTestUpdateCommand : public cmCTestHandlerCommand
 {
 public:
-  /**
-   * This is a virtual constructor for the command.
-   */
-  std::unique_ptr<cmCommand> Clone() override
-  {
-    auto ni = cm::make_unique<cmCTestUpdateCommand>();
-    ni->CTest = this->CTest;
-    ni->CTestScriptHandler = this->CTestScriptHandler;
-    return std::unique_ptr<cmCommand>(std::move(ni));
-  }
+  using cmCTestHandlerCommand::cmCTestHandlerCommand;
 
-  /**
-   * The name of the command as specified in CMakeList.txt.
-   */
+private:
   std::string GetName() const override { return "ctest_update"; }
 
-protected:
-  cmCTestGenericHandler* InitializeHandler() override;
+  std::unique_ptr<cmCTestGenericHandler> InitializeHandler(
+    HandlerArguments& args, cmExecutionStatus& status) const override;
+
+  bool InitialPass(std::vector<std::string> const& args,
+                   cmExecutionStatus& status) const override;
 };
diff --git a/Source/CTest/cmCTestUpdateHandler.cxx b/Source/CTest/cmCTestUpdateHandler.cxx
index 045eb84..8bf71cc 100644
--- a/Source/CTest/cmCTestUpdateHandler.cxx
+++ b/Source/CTest/cmCTestUpdateHandler.cxx
@@ -19,7 +19,6 @@
 #include "cmGeneratedFileStream.h"
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
-#include "cmValue.h"
 #include "cmVersion.h"
 #include "cmXMLWriter.h"
 
@@ -36,13 +35,9 @@
   return cmCTestUpdateHandlerUpdateStrings[type];
 }
 
-cmCTestUpdateHandler::cmCTestUpdateHandler() = default;
-
-void cmCTestUpdateHandler::Initialize()
+cmCTestUpdateHandler::cmCTestUpdateHandler(cmCTest* ctest)
+  : Superclass(ctest)
 {
-  this->Superclass::Initialize();
-  this->UpdateCommand.clear();
-  this->UpdateType = e_CVS;
 }
 
 int cmCTestUpdateHandler::DetermineType(const char* cmd, const char* type)
@@ -109,8 +104,7 @@
   static_cast<void>(fixLocale);
 
   // Get source dir
-  cmValue sourceDirectory = this->GetOption("SourceDirectory");
-  if (!sourceDirectory) {
+  if (this->SourceDirectory.empty()) {
     cmCTestLog(this->CTest, ERROR_MESSAGE,
                "Cannot find SourceDirectory  key in the DartConfiguration.tcl"
                  << std::endl);
@@ -123,7 +117,7 @@
   }
 
   cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
-                     "   Updating the repository: " << *sourceDirectory
+                     "   Updating the repository: " << this->SourceDirectory
                                                     << std::endl,
                      this->Quiet);
 
@@ -163,7 +157,7 @@
       break;
   }
   vc->SetCommandLineTool(this->UpdateCommand);
-  vc->SetSourceDirectory(*sourceDirectory);
+  vc->SetSourceDirectory(this->SourceDirectory);
 
   // Cleanup the working tree.
   vc->Cleanup();
@@ -195,7 +189,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());
@@ -301,7 +295,7 @@
   this->UpdateCommand = this->CTest->GetCTestConfiguration("UpdateCommand");
 
   // Detect the VCS managing the source tree.
-  this->UpdateType = this->DetectVCS(this->GetOption("SourceDirectory"));
+  this->UpdateType = this->DetectVCS(this->SourceDirectory);
   if (this->UpdateType == e_UNKNOWN) {
     // The source tree does not have a recognized VCS.  Check the
     // configuration value or command name.
diff --git a/Source/CTest/cmCTestUpdateHandler.h b/Source/CTest/cmCTestUpdateHandler.h
index 70269ea..7797228 100644
--- a/Source/CTest/cmCTestUpdateHandler.h
+++ b/Source/CTest/cmCTestUpdateHandler.h
@@ -10,6 +10,8 @@
 
 #include "cmCTestGenericHandler.h"
 
+class cmCTest;
+
 /** \class cmCTestUpdateHandler
  * \brief A class that handles ctest -S invocations
  *
@@ -24,7 +26,7 @@
    */
   int ProcessHandler() override;
 
-  cmCTestUpdateHandler();
+  cmCTestUpdateHandler(cmCTest* ctest);
 
   enum
   {
@@ -38,11 +40,6 @@
     e_LAST
   };
 
-  /**
-   * Initialize handler
-   */
-  void Initialize() override;
-
 private:
   // Some structures needed for update
   struct StringPair : public std::pair<std::string, std::string>
@@ -57,8 +54,11 @@
 
   // The VCS command to update the working tree.
   std::string UpdateCommand;
-  int UpdateType;
+  std::string SourceDirectory;
+  int UpdateType = e_CVS;
 
   int DetectVCS(const std::string& dir);
   bool SelectVCS();
+
+  friend class cmCTestUpdateCommand;
 };
diff --git a/Source/CTest/cmCTestUploadCommand.cxx b/Source/CTest/cmCTestUploadCommand.cxx
index 2ed671c..4ec4201 100644
--- a/Source/CTest/cmCTestUploadCommand.cxx
+++ b/Source/CTest/cmCTestUploadCommand.cxx
@@ -2,45 +2,98 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmCTestUploadCommand.h"
 
-#include <set>
+#include <algorithm>
+#include <chrono>
 #include <sstream>
+#include <string>
 
 #include <cm/vector>
 #include <cmext/string_view>
 
+#include "cmArgumentParser.h"
 #include "cmCTest.h"
-#include "cmCTestUploadHandler.h"
+#include "cmExecutionStatus.h"
+#include "cmGeneratedFileStream.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
 #include "cmSystemTools.h"
+#include "cmVersion.h"
+#include "cmXMLWriter.h"
 
-void cmCTestUploadCommand::BindArguments()
+bool cmCTestUploadCommand::ExecuteUpload(UploadArguments& args,
+                                         cmExecutionStatus& status) const
 {
-  this->Bind("FILES"_s, this->Files);
-  this->Bind("QUIET"_s, this->Quiet);
-  this->Bind("CAPTURE_CMAKE_ERROR"_s, this->CaptureCMakeError);
-}
+  cmMakefile& mf = status.GetMakefile();
 
-void cmCTestUploadCommand::CheckArguments()
-{
-  cm::erase_if(this->Files, [this](std::string const& arg) -> bool {
+  std::sort(args.Files.begin(), args.Files.end());
+  args.Files.erase(std::unique(args.Files.begin(), args.Files.end()),
+                   args.Files.end());
+
+  cm::erase_if(args.Files, [&mf](std::string const& arg) -> bool {
     if (!cmSystemTools::FileExists(arg)) {
       std::ostringstream e;
       e << "File \"" << arg << "\" does not exist. Cannot submit "
         << "a non-existent file.";
-      this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
+      mf.IssueMessage(MessageType::FATAL_ERROR, e.str());
       return true;
     }
     return false;
   });
+
+  cmGeneratedFileStream ofs;
+  if (!this->CTest->OpenOutputFile(this->CTest->GetCurrentTag(), "Upload.xml",
+                                   ofs)) {
+    cmCTestLog(this->CTest, ERROR_MESSAGE,
+               "Cannot open Upload.xml file" << std::endl);
+    return false;
+  }
+  std::string buildname =
+    cmCTest::SafeBuildIdField(mf.GetSafeDefinition("CTEST_BUILD_NAME"));
+
+  cmXMLWriter xml(ofs);
+  xml.StartDocument();
+  xml.ProcessingInstruction("xml-stylesheet",
+                            "type=\"text/xsl\" "
+                            "href=\"Dart/Source/Server/XSL/Build.xsl "
+                            "<file:///Dart/Source/Server/XSL/Build.xsl> \"");
+  xml.StartElement("Site");
+  xml.Attribute("BuildName", buildname);
+  xml.Attribute("BuildStamp",
+                this->CTest->GetCurrentTag() + "-" +
+                  this->CTest->GetTestGroupString());
+  xml.Attribute("Name", mf.GetSafeDefinition("CTEST_SITE"));
+  xml.Attribute("Generator",
+                std::string("ctest-") + cmVersion::GetCMakeVersion());
+  this->CTest->AddSiteProperties(xml, mf.GetCMakeInstance());
+  xml.StartElement("Upload");
+  xml.Element("Time", std::chrono::system_clock::now());
+
+  for (std::string const& file : args.Files) {
+    cmCTestOptionalLog(this->CTest, OUTPUT,
+                       "\tUpload file: " << file << std::endl, args.Quiet);
+    xml.StartElement("File");
+    xml.Attribute("filename", file);
+    xml.StartElement("Content");
+    xml.Attribute("encoding", "base64");
+    xml.Content(this->CTest->Base64EncodeFile(file));
+    xml.EndElement(); // Content
+    xml.EndElement(); // File
+  }
+  xml.EndElement(); // Upload
+  xml.EndElement(); // Site
+  xml.EndDocument();
+  return true;
 }
 
-cmCTestGenericHandler* cmCTestUploadCommand::InitializeHandler()
+bool cmCTestUploadCommand::InitialPass(std::vector<std::string> const& args,
+                                       cmExecutionStatus& status) const
 {
-  cmCTestUploadHandler* handler = this->CTest->GetUploadHandler();
-  handler->Initialize();
-  handler->SetFiles(
-    std::set<std::string>(this->Files.begin(), this->Files.end()));
-  handler->SetQuiet(this->Quiet);
-  return handler;
+  static auto const parser =
+    cmArgumentParser<UploadArguments>{ MakeBasicParser<UploadArguments>() }
+      .Bind("FILES"_s, &UploadArguments::Files)
+      .Bind("QUIET"_s, &UploadArguments::Quiet);
+
+  return this->Invoke(parser, args, status, [&](UploadArguments& a) {
+    return this->ExecuteUpload(a, status);
+  });
 }
diff --git a/Source/CTest/cmCTestUploadCommand.h b/Source/CTest/cmCTestUploadCommand.h
index a9d1dd2..93fbc35 100644
--- a/Source/CTest/cmCTestUploadCommand.h
+++ b/Source/CTest/cmCTestUploadCommand.h
@@ -5,46 +5,30 @@
 #include "cmConfigure.h" // IWYU pragma: keep
 
 #include <string>
-#include <utility>
 #include <vector>
 
-#include <cm/memory>
-
 #include "cmArgumentParserTypes.h"
 #include "cmCTestHandlerCommand.h"
-#include "cmCommand.h"
 
-class cmCTestGenericHandler;
+class cmExecutionStatus;
 
-/** \class cmCTestUpload
- * \brief Run a ctest script
- *
- * cmCTestUploadCommand defines the command to upload result files for
- * the project.
- */
 class cmCTestUploadCommand : public cmCTestHandlerCommand
 {
 public:
-  /**
-   * This is a virtual constructor for the command.
-   */
-  std::unique_ptr<cmCommand> Clone() override
-  {
-    auto ni = cm::make_unique<cmCTestUploadCommand>();
-    ni->CTest = this->CTest;
-    ni->CTestScriptHandler = this->CTestScriptHandler;
-    return std::unique_ptr<cmCommand>(std::move(ni));
-  }
-
-  /**
-   * The name of the command as specified in CMakeList.txt.
-   */
-  std::string GetName() const override { return "ctest_upload"; }
+  using cmCTestHandlerCommand::cmCTestHandlerCommand;
 
 protected:
-  void BindArguments() override;
-  void CheckArguments() override;
-  cmCTestGenericHandler* InitializeHandler() override;
+  struct UploadArguments : BasicArguments
+  {
+    ArgumentParser::MaybeEmpty<std::vector<std::string>> Files;
+    bool Quiet = false;
+  };
 
-  ArgumentParser::MaybeEmpty<std::vector<std::string>> Files;
+private:
+  std::string GetName() const override { return "ctest_upload"; }
+
+  bool ExecuteUpload(UploadArguments& args, cmExecutionStatus& status) const;
+
+  bool InitialPass(std::vector<std::string> const& args,
+                   cmExecutionStatus& status) const override;
 };
diff --git a/Source/CTest/cmCTestUploadHandler.cxx b/Source/CTest/cmCTestUploadHandler.cxx
deleted file mode 100644
index 59e2b88..0000000
--- a/Source/CTest/cmCTestUploadHandler.cxx
+++ /dev/null
@@ -1,75 +0,0 @@
-/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-   file Copyright.txt or https://cmake.org/licensing for details.  */
-#include "cmCTestUploadHandler.h"
-
-#include <chrono>
-#include <ostream>
-#include <string>
-
-#include "cmCTest.h"
-#include "cmGeneratedFileStream.h"
-#include "cmVersion.h"
-#include "cmXMLWriter.h"
-
-cmCTestUploadHandler::cmCTestUploadHandler()
-{
-  this->Initialize();
-}
-
-void cmCTestUploadHandler::Initialize()
-{
-  this->Superclass::Initialize();
-  this->Files.clear();
-}
-
-void cmCTestUploadHandler::SetFiles(std::set<std::string> const& files)
-{
-  this->Files = files;
-}
-
-int cmCTestUploadHandler::ProcessHandler()
-{
-  cmGeneratedFileStream ofs;
-  if (!this->CTest->OpenOutputFile(this->CTest->GetCurrentTag(), "Upload.xml",
-                                   ofs)) {
-    cmCTestLog(this->CTest, ERROR_MESSAGE,
-               "Cannot open Upload.xml file" << std::endl);
-    return -1;
-  }
-  std::string buildname =
-    cmCTest::SafeBuildIdField(this->CTest->GetCTestConfiguration("BuildName"));
-
-  cmXMLWriter xml(ofs);
-  xml.StartDocument();
-  xml.ProcessingInstruction("xml-stylesheet",
-                            "type=\"text/xsl\" "
-                            "href=\"Dart/Source/Server/XSL/Build.xsl "
-                            "<file:///Dart/Source/Server/XSL/Build.xsl> \"");
-  xml.StartElement("Site");
-  xml.Attribute("BuildName", buildname);
-  xml.Attribute("BuildStamp",
-                this->CTest->GetCurrentTag() + "-" +
-                  this->CTest->GetTestModelString());
-  xml.Attribute("Name", this->CTest->GetCTestConfiguration("Site"));
-  xml.Attribute("Generator",
-                std::string("ctest-") + cmVersion::GetCMakeVersion());
-  this->CTest->AddSiteProperties(xml);
-  xml.StartElement("Upload");
-  xml.Element("Time", std::chrono::system_clock::now());
-
-  for (std::string const& file : this->Files) {
-    cmCTestOptionalLog(this->CTest, OUTPUT,
-                       "\tUpload file: " << file << std::endl, this->Quiet);
-    xml.StartElement("File");
-    xml.Attribute("filename", file);
-    xml.StartElement("Content");
-    xml.Attribute("encoding", "base64");
-    xml.Content(this->CTest->Base64EncodeFile(file));
-    xml.EndElement(); // Content
-    xml.EndElement(); // File
-  }
-  xml.EndElement(); // Upload
-  xml.EndElement(); // Site
-  xml.EndDocument();
-  return 0;
-}
diff --git a/Source/CTest/cmCTestUploadHandler.h b/Source/CTest/cmCTestUploadHandler.h
deleted file mode 100644
index 55d21c1..0000000
--- a/Source/CTest/cmCTestUploadHandler.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-   file Copyright.txt or https://cmake.org/licensing for details.  */
-#pragma once
-
-#include "cmConfigure.h" // IWYU pragma: keep
-
-#include <set>
-#include <string>
-
-#include "cmCTestGenericHandler.h"
-
-/** \class cmCTestUploadHandler
- * \brief Helper class for CTest
- *
- * Submit arbitrary files
- *
- */
-class cmCTestUploadHandler : public cmCTestGenericHandler
-{
-public:
-  using Superclass = cmCTestGenericHandler;
-
-  cmCTestUploadHandler();
-
-  /*
-   * The main entry point for this class
-   */
-  int ProcessHandler() override;
-
-  void Initialize() override;
-
-  /** Specify a set of files to submit.  */
-  void SetFiles(std::set<std::string> const& files);
-
-private:
-  std::set<std::string> Files;
-};
diff --git a/Source/CTest/cmProcess.cxx b/Source/CTest/cmProcess.cxx
index 2c809b6..7a89ffb 100644
--- a/Source/CTest/cmProcess.cxx
+++ b/Source/CTest/cmProcess.cxx
@@ -106,6 +106,11 @@
   options.stdio_count = 3; // in, out and err
   options.exit_cb = &cmProcess::OnExitCB;
   options.stdio = stdio;
+#if UV_VERSION_MAJOR > 1 || !defined(CMAKE_USE_SYSTEM_LIBUV)
+  if (!this->Runner->GetCTest()->GetInteractiveDebugMode()) {
+    options.flags = UV_PROCESS_WINDOWS_USE_PARENT_ERROR_MODE;
+  }
+#endif
 #if !defined(CMAKE_USE_SYSTEM_LIBUV)
   std::vector<char> cpumask;
   if (affinity && !affinity->empty()) {
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/CursesDialog/ccmake.cxx b/Source/CursesDialog/ccmake.cxx
index bc21cd0..8f7e515 100644
--- a/Source/CursesDialog/ccmake.cxx
+++ b/Source/CursesDialog/ccmake.cxx
@@ -101,7 +101,7 @@
     }
   }
 
-  std::string cacheDir = cmSystemTools::GetCurrentWorkingDirectory();
+  std::string cacheDir = cmSystemTools::GetLogicalWorkingDirectory();
   for (i = 1; i < args.size(); ++i) {
     std::string const& arg = args[i];
     if (cmHasPrefix(arg, "-B")) {
diff --git a/Source/QtDialog/CMakeSetup.cxx b/Source/QtDialog/CMakeSetup.cxx
index 21ed8c8..c3adae1 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::GetLogicalWorkingDirectory()));
       }
     }
   }
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..83f6eb3 100644
--- a/Source/cmCTest.cxx
+++ b/Source/cmCTest.cxx
@@ -10,6 +10,7 @@
 #include <cstdlib>
 #include <cstring>
 #include <ctime>
+#include <initializer_list>
 #include <iostream>
 #include <map>
 #include <ratio>
@@ -31,7 +32,6 @@
 #include "cmsys/Base64.h"
 #include "cmsys/Directory.hxx"
 #include "cmsys/FStream.hxx"
-#include "cmsys/Glob.hxx"
 #include "cmsys/RegularExpression.hxx"
 #include "cmsys/SystemInformation.hxx"
 #if defined(_WIN32)
@@ -41,23 +41,18 @@
 #endif
 
 #include "cmCMakePresetsGraph.h"
-#include "cmCTestBuildAndTestHandler.h"
-#include "cmCTestBuildHandler.h"
-#include "cmCTestConfigureHandler.h"
-#include "cmCTestCoverageHandler.h"
-#include "cmCTestGenericHandler.h"
-#include "cmCTestMemCheckHandler.h"
+#include "cmCTestBuildAndTest.h"
 #include "cmCTestScriptHandler.h"
-#include "cmCTestStartCommand.h"
-#include "cmCTestSubmitHandler.h"
 #include "cmCTestTestHandler.h"
-#include "cmCTestUpdateHandler.h"
-#include "cmCTestUploadHandler.h"
+#include "cmCTestTypes.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 +66,7 @@
 #include "cmValue.h"
 #include "cmVersion.h"
 #include "cmVersionConfig.h"
+#include "cmWorkingDirectory.h"
 #include "cmXMLWriter.h"
 #include "cmake.h"
 
@@ -80,6 +76,11 @@
 
 struct cmCTest::Private
 {
+  Private(cmCTest* ctest)
+    : BuildAndTest(ctest)
+  {
+  }
+
   /** Representation of one part.  */
   struct PartInfo
   {
@@ -114,44 +115,8 @@
 
   bool FlushTestProgressLine = false;
 
-  bool ForceNewCTestProcess = false;
-
-  bool RunConfigurationScript = false;
-
   // these are helper classes
-  cmCTestBuildHandler BuildHandler;
-  cmCTestBuildAndTestHandler BuildAndTestHandler;
-  cmCTestCoverageHandler CoverageHandler;
-  cmCTestScriptHandler ScriptHandler;
-  cmCTestTestHandler TestHandler;
-  cmCTestUpdateHandler UpdateHandler;
-  cmCTestConfigureHandler ConfigureHandler;
-  cmCTestMemCheckHandler MemCheckHandler;
-  cmCTestSubmitHandler SubmitHandler;
-  cmCTestUploadHandler UploadHandler;
-
-  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 };
-  }
-
-  std::map<std::string, cmCTestGenericHandler*> GetNamedTestingHandlers()
-  {
-    return { { "build", &this->BuildHandler },
-             { "buildtest", &this->BuildAndTestHandler },
-             { "coverage", &this->CoverageHandler },
-             { "script", &this->ScriptHandler },
-             { "test", &this->TestHandler },
-             { "update", &this->UpdateHandler },
-             { "configure", &this->ConfigureHandler },
-             { "memcheck", &this->MemCheckHandler },
-             { "submit", &this->SubmitHandler },
-             { "upload", &this->UploadHandler } };
-  }
+  cmCTestBuildAndTest BuildAndTest;
 
   bool ShowOnly = false;
   bool OutputAsJson = false;
@@ -177,6 +142,10 @@
 
   cmDuration GlobalTimeout = cmDuration::zero();
 
+  std::chrono::steady_clock::time_point StartTime =
+    std::chrono::steady_clock::now();
+  cmDuration TimeLimit = cmCTest::MaxDuration();
+
   int MaxTestNameWidth = 30;
 
   cm::optional<size_t> ParallelLevel = 1;
@@ -199,14 +168,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 +180,7 @@
   int SubmitIndex = 0;
 
   std::unique_ptr<cmGeneratedFileStream> OutputLogFile;
-  int OutputLogFileLastTag = -1;
+  cm::optional<cmCTest::LogType> OutputLogFileLastTag;
 
   bool OutputTestOutputOnTestFailure = false;
   bool OutputColorCode = cmCTest::ColoredOutputSupportedByConsole();
@@ -225,6 +189,9 @@
 
   cmCTest::NoTests NoTestsMode = cmCTest::NoTests::Legacy;
   bool NoTestsModeSetInCli = false;
+
+  cmCTestTestOptions TestOptions;
+  std::vector<std::string> CommandLineHttpHeaders;
 };
 
 struct tm* cmCTest::GetNightlyTime(std::string const& str, bool tomorrowtag)
@@ -340,7 +307,7 @@
 }
 
 cmCTest::cmCTest()
-  : Impl(new Private)
+  : Impl(cm::make_unique<Private>(this))
 {
   std::string envValue;
   if (cmSystemTools::GetEnv("CTEST_OUTPUT_ON_FAILURE", envValue)) {
@@ -370,10 +337,6 @@
       ->PartMap[cmSystemTools::LowerCase(this->Impl->Parts[p].GetName())] = p;
   }
 
-  for (auto& handler : this->Impl->GetTestingHandlers()) {
-    handler->SetCTestInstance(this);
-  }
-
   // Make sure we can capture the build tool output.
   cmSystemTools::EnableVSConsoleOutput();
 }
@@ -418,15 +381,13 @@
   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 {
@@ -435,269 +396,122 @@
 
   this->Impl->BinaryDir = binary_dir;
   cmSystemTools::ConvertToUnixSlashes(this->Impl->BinaryDir);
-
-  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;
-    }
-  }
-
-  cmake cm(cmake::RoleScript, cmState::CTest);
-  cm.SetHomeDirectory("");
-  cm.SetHomeOutputDirectory("");
-  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)
+bool cmCTest::CreateNewTag(bool quiet)
 {
-  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();
-  }
+  std::string const testingDir = this->Impl->BinaryDir + "/Testing";
+  std::string const tagfile = testingDir + "/TAG";
 
-  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)) {
+  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 " << this->GetTestModelString() << " tag: "
-                               << this->GetCurrentTag() << std::endl,
-                     command->ShouldBeQuiet());
+                     "  Use existing tag: " << tag << " - "
+                                            << this->GetTestGroupString()
+                                            << std::endl,
+                     quiet);
+
+  this->Impl->CurrentTag = tag;
   return true;
 }
 
@@ -760,7 +574,7 @@
   if (!this->GetCTestConfiguration("BuildDirectory").empty()) {
     this->Impl->BinaryDir = this->GetCTestConfiguration("BuildDirectory");
     if (this->Impl->TestDir.empty()) {
-      cmSystemTools::ChangeDirectory(this->Impl->BinaryDir);
+      cmSystemTools::SetLogicalWorkingDirectory(this->Impl->BinaryDir);
     }
   }
   this->Impl->TimeOut =
@@ -825,10 +639,6 @@
   return false;
 }
 
-void cmCTest::Finalize()
-{
-}
-
 bool cmCTest::OpenOutputFile(const std::string& path, const std::string& name,
                              cmGeneratedFileStream& stream, bool compress)
 {
@@ -889,166 +699,174 @@
   return cmSystemTools::FileExists(testingDir);
 }
 
-cmCTestBuildHandler* cmCTest::GetBuildHandler()
-{
-  return &this->Impl->BuildHandler;
-}
-
-cmCTestBuildAndTestHandler* cmCTest::GetBuildAndTestHandler()
-{
-  return &this->Impl->BuildAndTestHandler;
-}
-
-cmCTestCoverageHandler* cmCTest::GetCoverageHandler()
-{
-  return &this->Impl->CoverageHandler;
-}
-
-cmCTestScriptHandler* cmCTest::GetScriptHandler()
-{
-  return &this->Impl->ScriptHandler;
-}
-
-cmCTestTestHandler* cmCTest::GetTestHandler()
-{
-  return &this->Impl->TestHandler;
-}
-
-cmCTestUpdateHandler* cmCTest::GetUpdateHandler()
-{
-  return &this->Impl->UpdateHandler;
-}
-
-cmCTestConfigureHandler* cmCTest::GetConfigureHandler()
-{
-  return &this->Impl->ConfigureHandler;
-}
-
-cmCTestMemCheckHandler* cmCTest::GetMemCheckHandler()
-{
-  return &this->Impl->MemCheckHandler;
-}
-
-cmCTestSubmitHandler* cmCTest::GetSubmitHandler()
-{
-  return &this->Impl->SubmitHandler;
-}
-
-cmCTestUploadHandler* cmCTest::GetUploadHandler()
-{
-  return &this->Impl->UploadHandler;
-}
-
 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];
+  const std::string currDir = cmSystemTools::GetLogicalWorkingDirectory();
+  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(this);
+  script.CreateCMake();
+  cmMakefile& mf = *script.GetMakefile();
+  this->ReadCustomConfigurationFileTree(this->Impl->BinaryDir, &mf);
+  this->SetTimeLimit(mf.GetDefinition("CTEST_TIME_LIMIT"));
+  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();
       }
     }
   }
   if (this->Impl->Parts[PartNotes]) {
     this->UpdateCTestConfiguration();
     if (!this->Impl->NotesFiles.empty()) {
-      this->GenerateNotesFile(this->Impl->NotesFiles);
+      this->GenerateNotesFile(script.GetCMake(), this->Impl->NotesFiles);
     }
   }
   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 +876,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 +1011,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);
@@ -1386,7 +1041,7 @@
   return safevalue;
 }
 
-void cmCTest::StartXML(cmXMLWriter& xml, bool append)
+void cmCTest::StartXML(cmXMLWriter& xml, cmake* cm, bool append)
 {
   if (this->Impl->CurrentTag.empty()) {
     cmCTestLog(this, ERROR_MESSAGE,
@@ -1405,7 +1060,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"));
 
@@ -1448,25 +1103,17 @@
     xml.Attribute("ChangeId", changeId);
   }
 
-  this->AddSiteProperties(xml);
+  this->AddSiteProperties(xml, cm);
 }
 
-void cmCTest::AddSiteProperties(cmXMLWriter& xml)
+void cmCTest::AddSiteProperties(cmXMLWriter& xml, cmake* cm)
 {
-  cmCTestScriptHandler* ch = this->GetScriptHandler();
-  cmake* cm = ch->GetCMake();
-  // if no CMake then this is the old style script and props like
-  // this will not work anyway.
-  if (!cm) {
-    return;
-  }
   // This code should go when cdash is changed to use labels only
   cmValue subproject = cm->GetState()->GetGlobalProperty("SubProject");
   if (subproject) {
     xml.StartElement("Subproject");
     xml.Attribute("name", *subproject);
-    cmValue labels =
-      ch->GetCMake()->GetState()->GetGlobalProperty("SubProjectLabels");
+    cmValue labels = cm->GetState()->GetGlobalProperty("SubProjectLabels");
     if (labels) {
       xml.StartElement("Labels");
       cmList args{ *labels };
@@ -1518,7 +1165,7 @@
   xml.EndDocument();
 }
 
-int cmCTest::GenerateCTestNotesOutput(cmXMLWriter& xml,
+int cmCTest::GenerateCTestNotesOutput(cmXMLWriter& xml, cmake* cm,
                                       std::vector<std::string> const& files)
 {
   std::string buildname =
@@ -1531,11 +1178,11 @@
   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());
-  this->AddSiteProperties(xml);
+  this->AddSiteProperties(xml, cm);
   xml.StartElement("Notes");
 
   for (std::string const& file : files) {
@@ -1569,7 +1216,8 @@
   return 1;
 }
 
-int cmCTest::GenerateNotesFile(std::vector<std::string> const& files)
+int cmCTest::GenerateNotesFile(cmake* cm,
+                               std::vector<std::string> const& files)
 {
   cmGeneratedFileStream ofs;
   if (!this->OpenOutputFile(this->Impl->CurrentTag, "Notes.xml", ofs)) {
@@ -1577,11 +1225,11 @@
     return 1;
   }
   cmXMLWriter xml(ofs);
-  this->GenerateCTestNotesOutput(xml, files);
+  this->GenerateCTestNotesOutput(xml, cm, files);
   return 0;
 }
 
-int cmCTest::GenerateNotesFile(const std::string& cfiles)
+int cmCTest::GenerateNotesFile(cmake* cm, const std::string& cfiles)
 {
   if (cfiles.empty()) {
     return 1;
@@ -1595,7 +1243,7 @@
     return 1;
   }
 
-  return this->GenerateNotesFile(files);
+  return this->GenerateNotesFile(cm, files);
 }
 
 int cmCTest::GenerateDoneFile()
@@ -1616,31 +1264,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 +1288,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 +1346,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 +1469,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 +1494,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 +1538,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;
@@ -2350,28 +1552,10 @@
   return false;
 }
 
-void cmCTest::SetPersistentOptionIfNotEmpty(const std::string& value,
-                                            const std::string& optionName)
-{
-  if (!value.empty()) {
-    this->GetTestHandler()->SetPersistentOption(optionName, value);
-    this->GetMemCheckHandler()->SetPersistentOption(optionName, value);
-  }
-}
-
-void cmCTest::AddPersistentMultiOptionIfNotEmpty(const std::string& value,
-                                                 const std::string& optionName)
-{
-  if (!value.empty()) {
-    this->GetTestHandler()->AddPersistentMultiOption(optionName, value);
-    this->GetMemCheckHandler()->AddPersistentMultiOption(optionName, value);
-  }
-}
-
 bool cmCTest::SetArgsFromPreset(const std::string& presetName,
                                 bool listPresets)
 {
-  const auto workingDirectory = cmSystemTools::GetCurrentWorkingDirectory();
+  const auto workingDirectory = cmSystemTools::GetLogicalWorkingDirectory();
 
   cmCMakePresetsGraph settingsFile;
   auto result = settingsFile.ReadProjectPresets(workingDirectory);
@@ -2485,8 +1669,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);
@@ -2504,17 +1686,17 @@
       expandedPreset->Output->SubprojectSummary.value_or(true);
 
     if (expandedPreset->Output->MaxPassedTestOutputSize) {
-      this->Impl->TestHandler.SetTestOutputSizePassed(
-        *expandedPreset->Output->MaxPassedTestOutputSize);
+      this->Impl->TestOptions.OutputSizePassed =
+        *expandedPreset->Output->MaxPassedTestOutputSize;
     }
 
     if (expandedPreset->Output->MaxFailedTestOutputSize) {
-      this->Impl->TestHandler.SetTestOutputSizeFailed(
-        *expandedPreset->Output->MaxFailedTestOutputSize);
+      this->Impl->TestOptions.OutputSizeFailed =
+        *expandedPreset->Output->MaxFailedTestOutputSize;
     }
 
     if (expandedPreset->Output->TestOutputTruncation) {
-      this->Impl->TestHandler.TestOutputTruncation =
+      this->Impl->TestOptions.OutputTruncation =
         *expandedPreset->Output->TestOutputTruncation;
     }
 
@@ -2525,10 +1707,12 @@
 
   if (expandedPreset->Filter) {
     if (expandedPreset->Filter->Include) {
-      this->SetPersistentOptionIfNotEmpty(
-        expandedPreset->Filter->Include->Name, "IncludeRegularExpression");
-      this->AddPersistentMultiOptionIfNotEmpty(
-        expandedPreset->Filter->Include->Label, "LabelRegularExpression");
+      this->Impl->TestOptions.IncludeRegularExpression =
+        expandedPreset->Filter->Include->Name;
+      if (!expandedPreset->Filter->Include->Label.empty()) {
+        this->Impl->TestOptions.LabelRegularExpression.push_back(
+          expandedPreset->Filter->Include->Label);
+      }
 
       if (expandedPreset->Filter->Include->Index) {
         if (expandedPreset->Filter->Include->Index->IndexFile.empty()) {
@@ -2542,38 +1726,32 @@
           indexOptions +=
             cmJoin(expandedPreset->Filter->Include->Index->SpecificTests, ",");
 
-          this->SetPersistentOptionIfNotEmpty(indexOptions,
-                                              "TestsToRunInformation");
+          this->Impl->TestOptions.TestsToRunInformation = indexOptions;
         } else {
-          this->SetPersistentOptionIfNotEmpty(
-            expandedPreset->Filter->Include->Index->IndexFile,
-            "TestsToRunInformation");
+          this->Impl->TestOptions.TestsToRunInformation =
+            expandedPreset->Filter->Include->Index->IndexFile;
         }
       }
 
-      if (expandedPreset->Filter->Include->UseUnion.value_or(false)) {
-        this->GetTestHandler()->SetPersistentOption("UseUnion", "true");
-        this->GetMemCheckHandler()->SetPersistentOption("UseUnion", "true");
-      }
+      this->Impl->TestOptions.UseUnion =
+        expandedPreset->Filter->Include->UseUnion.value_or(false);
     }
 
     if (expandedPreset->Filter->Exclude) {
-      this->SetPersistentOptionIfNotEmpty(
-        expandedPreset->Filter->Exclude->Name, "ExcludeRegularExpression");
-      this->AddPersistentMultiOptionIfNotEmpty(
-        expandedPreset->Filter->Exclude->Label,
-        "ExcludeLabelRegularExpression");
+      this->Impl->TestOptions.ExcludeRegularExpression =
+        expandedPreset->Filter->Exclude->Name;
+      if (!expandedPreset->Filter->Exclude->Label.empty()) {
+        this->Impl->TestOptions.ExcludeLabelRegularExpression.push_back(
+          expandedPreset->Filter->Exclude->Label);
+      }
 
       if (expandedPreset->Filter->Exclude->Fixtures) {
-        this->SetPersistentOptionIfNotEmpty(
-          expandedPreset->Filter->Exclude->Fixtures->Any,
-          "ExcludeFixtureRegularExpression");
-        this->SetPersistentOptionIfNotEmpty(
-          expandedPreset->Filter->Exclude->Fixtures->Setup,
-          "ExcludeFixtureSetupRegularExpression");
-        this->SetPersistentOptionIfNotEmpty(
-          expandedPreset->Filter->Exclude->Fixtures->Cleanup,
-          "ExcludeFixtureCleanupRegularExpression");
+        this->Impl->TestOptions.ExcludeFixtureRegularExpression =
+          expandedPreset->Filter->Exclude->Fixtures->Any;
+        this->Impl->TestOptions.ExcludeFixtureSetupRegularExpression =
+          expandedPreset->Filter->Exclude->Fixtures->Setup;
+        this->Impl->TestOptions.ExcludeFixtureCleanupRegularExpression =
+          expandedPreset->Filter->Exclude->Fixtures->Cleanup;
       }
     }
   }
@@ -2590,8 +1768,8 @@
       this->Impl->ParallelLevelSetInCli = true;
     }
 
-    this->SetPersistentOptionIfNotEmpty(
-      expandedPreset->Execution->ResourceSpecFile, "ResourceSpecFile");
+    this->Impl->TestOptions.ResourceSpecFile =
+      expandedPreset->Execution->ResourceSpecFile;
 
     if (expandedPreset->Execution->TestLoad) {
       auto testLoad = *expandedPreset->Execution->TestLoad;
@@ -2675,12 +1853,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 +1881,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 +1901,678 @@
     }
   }
 
+  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->TestOptions.TestsToRunInformation = tests;
+    return true;
+  };
+  auto const dashU = [this](std::string const&) -> bool {
+    this->Impl->TestOptions.UseUnion = true;
+    return true;
+  };
+  auto const dashR = [this](std::string const& expr) -> bool {
+    this->Impl->TestOptions.IncludeRegularExpression = expr;
+    return true;
+  };
+  auto const dashE = [this](std::string const& expr) -> bool {
+    this->Impl->TestOptions.ExcludeRegularExpression = expr;
+    return true;
+  };
+  auto const dashL = [this](std::string const& expr) -> bool {
+    this->Impl->TestOptions.LabelRegularExpression.push_back(expr);
+    return true;
+  };
+  auto const dashLE = [this](std::string const& expr) -> bool {
+    this->Impl->TestOptions.ExcludeLabelRegularExpression.push_back(expr);
+    return true;
+  };
+  auto const dashFA = [this](std::string const& expr) -> bool {
+    this->Impl->TestOptions.ExcludeFixtureRegularExpression = expr;
+    return true;
+  };
+  auto const dashFS = [this](std::string const& expr) -> bool {
+    this->Impl->TestOptions.ExcludeFixtureSetupRegularExpression = expr;
+    return true;
+  };
+  auto const dashFC = [this](std::string const& expr) -> bool {
+    this->Impl->TestOptions.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->TestOptions.OutputSizePassed =
+                           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->TestOptions.OutputSizeFailed =
+                           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 (!SetTruncationMode(this->Impl->TestOptions.OutputTruncation,
+                               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->CommandLineHttpHeaders.push_back(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->TestOptions.ResourceSpecFile = file;
+                       return true;
+                     } },
+    CommandArgument{ "--tests-from-file", CommandArgument::Values::One,
+                     [this](std::string const& file) -> bool {
+                       this->Impl->TestOptions.TestListFile = file;
+                       return true;
+                     } },
+    CommandArgument{ "--exclude-from-file", CommandArgument::Values::One,
+                     [this](std::string const& file) -> bool {
+                       this->Impl->TestOptions.ExcludeTestListFile = file;
+                       return true;
+                     } },
+    CommandArgument{ "--schedule-random", CommandArgument::Values::Zero,
+                     [this](std::string const&) -> bool {
+                       this->Impl->TestOptions.ScheduleRandom = true;
+                       return true;
+                     } },
+    CommandArgument{ "--rerun-failed", CommandArgument::Values::Zero,
+                     [this](std::string const&) -> bool {
+                       this->Impl->TestOptions.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 +2626,111 @@
   // 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 */
-    }
+  auto ch = cm::make_unique<cmCTestScriptHandler>(this);
+  for (auto const& script : scripts) {
+    ch->AddConfigurationScript(script.first, script.second);
   }
-  return success;
+
+  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;
 
+  const std::string currDir = cmSystemTools::GetLogicalWorkingDirectory();
+  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;
+  }
+
+  cmCTestLog(this, DEBUG, "Here: " << __LINE__ << std::endl);
+  if (!this->Impl->InteractiveDebugMode) {
+    this->BlockTestErrorDiagnostics();
   } 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);
-    }
+    cmSystemTools::PutEnv("CTEST_INTERACTIVE_DEBUG_MODE=1");
+  }
 
-    const std::string currDir = cmSystemTools::GetCurrentWorkingDirectory();
-    std::string workDir = currDir;
-    if (!this->Impl->TestDir.empty()) {
-      workDir = cmSystemTools::CollapseFullPath(this->Impl->TestDir);
-    }
+  this->Impl->BinaryDir = workDir;
+  cmSystemTools::ConvertToUnixSlashes(this->Impl->BinaryDir);
 
-    if (currDir != workDir) {
-      if (!this->TryToChangeDirectory(workDir)) {
-        return 1;
-      }
-    }
+  this->UpdateCTestConfiguration();
 
-    if (!this->Initialize(workDir, nullptr)) {
-      res = 12;
+  cmCTestLog(this, DEBUG, "Here: " << __LINE__ << std::endl);
+
+  cmCTestTestHandler handler(this);
+
+  {
+    cmake cm(cmake::RoleScript, cmState::CTest);
+    cm.SetHomeDirectory("");
+    cm.SetHomeOutputDirectory("");
+    cm.GetCurrentSnapshot().SetDefaultDefinitions();
+    cmGlobalGenerator gg(&cm);
+    cmMakefile mf(&gg, cm.GetCurrentSnapshot());
+    this->ReadCustomConfigurationFileTree(this->Impl->BinaryDir, &mf);
+    handler.PopulateCustomVectors(&mf);
+  }
+
+  handler.SetVerbose(this->Impl->Verbose);
+  if (handler.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,17 +2802,25 @@
   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,
              "* Read custom CTest configuration directory: " << dir
                                                              << std::endl);
 
-  std::string fname = cmStrCat(dir, "/CTestCustom.cmake");
-  cmCTestLog(this, DEBUG, "* Check for file: " << fname << std::endl);
-  if (cmSystemTools::FileExists(fname)) {
+  auto const fname = [this, &dir]() -> std::string {
+    for (char const* ext : { ".cmake", ".ctest" }) {
+      std::string path = cmStrCat(dir, "/CTestCustom", ext);
+      cmCTestLog(this, DEBUG, "* Check for file: " << path << std::endl);
+      if (cmSystemTools::FileExists(path)) {
+        return path;
+      }
+    }
+    return "";
+  }();
+
+  if (!fname.empty()) {
     cmCTestLog(this, DEBUG,
                "* Read custom CTest configuration file: " << fname
                                                           << std::endl);
@@ -3116,43 +2832,10 @@
                  "Problem reading custom configuration: " << fname
                                                           << std::endl);
     }
-    found = true;
     if (erroroc) {
       cmSystemTools::SetErrorOccurred();
     }
   }
-
-  std::string rexpr = cmStrCat(dir, "/CTestCustom.ctest");
-  cmCTestLog(this, DEBUG, "* Check for file: " << rexpr << std::endl);
-  if (!found && cmSystemTools::FileExists(rexpr)) {
-    cmsys::Glob gl;
-    gl.RecurseOn();
-    gl.FindFiles(rexpr);
-    std::vector<std::string>& files = gl.GetFiles();
-    for (const std::string& file : files) {
-      cmCTestLog(this, DEBUG,
-                 "* Read custom CTest configuration file: " << file
-                                                            << std::endl);
-      if (!mf->ReadListFile(file) || cmSystemTools::GetErrorOccurredFlag()) {
-        cmCTestLog(this, ERROR_MESSAGE,
-                   "Problem reading custom configuration: " << file
-                                                            << std::endl);
-      }
-    }
-    found = true;
-  }
-
-  if (found) {
-    for (auto& handler : this->Impl->GetNamedTestingHandlers()) {
-      cmCTestLog(this, DEBUG,
-                 "* Read custom CTest configuration vectors for handler: "
-                   << handler.first << " (" << handler.second << ")"
-                   << std::endl);
-      handler.second->PopulateCustomVectors(mf);
-    }
-  }
-
-  return 1;
 }
 
 void cmCTest::PopulateCustomVector(cmMakefile* mf, const std::string& def,
@@ -3183,10 +2866,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,10 +3091,14 @@
   return this->Impl->ExtraVerbose;
 }
 
-void cmCTest::SetStreams(std::ostream* out, std::ostream* err)
+int cmCTest::GetSubmitIndex() const
 {
-  this->Impl->StreamOut = out;
-  this->Impl->StreamErr = err;
+  return this->Impl->SubmitIndex;
+}
+
+bool cmCTest::GetInteractiveDebugMode() const
+{
+  return this->Impl->InteractiveDebugMode;
 }
 
 bool cmCTest::GetLabelSummary() const
@@ -3460,6 +3146,16 @@
   return this->Impl->BuildID;
 }
 
+cmCTestTestOptions const& cmCTest::GetTestOptions() const
+{
+  return this->Impl->TestOptions;
+}
+
+std::vector<std::string> cmCTest::GetCommandLineHttpHeaders() const
+{
+  return this->Impl->CommandLineHttpHeaders;
+}
+
 void cmCTest::AddSubmitFile(Part part, const std::string& name)
 {
   this->Impl->Parts[part].SubmitFiles.emplace_back(name);
@@ -3520,6 +3216,79 @@
   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");
+}
+
 bool cmCTest::RunCommand(std::vector<std::string> const& args,
                          std::string* stdOut, std::string* stdErr, int* retVal,
                          const char* dir, cmDuration timeout,
@@ -3649,7 +3418,7 @@
 
 void cmCTest::SetOutputJUnitFileName(const std::string& name)
 {
-  this->Impl->TestHandler.SetJUnitXMLFileName(name);
+  this->Impl->TestOptions.JUnitXMLFileName = name;
   // Turn test output compression off.
   // This makes it easier to include test output in the resulting
   // JUnit XML report.
@@ -3663,20 +3432,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 +3456,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 +3496,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;
     }
   }
 }
@@ -3795,9 +3532,21 @@
   return "";
 }
 
-cmDuration cmCTest::GetRemainingTimeAllowed()
+void cmCTest::SetTimeLimit(cmValue val)
 {
-  return this->GetScriptHandler()->GetRemainingTimeAllowed();
+  this->Impl->TimeLimit =
+    val ? cmDuration(atof(val->c_str())) : cmCTest::MaxDuration();
+}
+
+cmDuration cmCTest::GetElapsedTime() const
+{
+  return std::chrono::duration_cast<cmDuration>(
+    std::chrono::steady_clock::now() - this->Impl->StartTime);
+}
+
+cmDuration cmCTest::GetRemainingTimeAllowed() const
+{
+  return this->Impl->TimeLimit - this->GetElapsedTime();
 }
 
 cmDuration cmCTest::MaxDuration()
@@ -3805,20 +3554,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..c8dc485 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>
@@ -18,20 +19,12 @@
 #include "cmDuration.h"
 #include "cmProcessOutput.h"
 
-class cmCTestBuildHandler;
-class cmCTestBuildAndTestHandler;
-class cmCTestCoverageHandler;
-class cmCTestScriptHandler;
-class cmCTestTestHandler;
-class cmCTestUpdateHandler;
-class cmCTestConfigureHandler;
-class cmCTestMemCheckHandler;
-class cmCTestSubmitHandler;
-class cmCTestUploadHandler;
-class cmCTestStartCommand;
+class cmake;
 class cmGeneratedFileStream;
 class cmMakefile;
+class cmValue;
 class cmXMLWriter;
+struct cmCTestTestOptions;
 
 /** \class cmCTest
  * \brief Represents a ctest invocation.
@@ -67,30 +60,16 @@
   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 and finalize testing
-   */
-  bool InitializeFromCommand(cmCTestStartCommand* command);
-  void Finalize();
+  /** Initialize a dashboard run in the given build tree. */
+  void Initialize(std::string const& binary_dir);
+
+  bool CreateNewTag(bool quiet);
+  bool ReadExistingTag(bool quiet);
 
   /**
    * 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 +119,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);
@@ -175,12 +155,15 @@
   /** base64 encode a file */
   std::string Base64EncodeFile(std::string const& file);
 
+  void SetTimeLimit(cmValue val);
+  cmDuration GetElapsedTime() const;
+
   /**
    * Return the time remaining that the script is allowed to run in
    * seconds if the user has set the variable CTEST_TIME_LIMIT. If that has
    * not been set it returns a very large duration.
    */
-  cmDuration GetRemainingTimeAllowed();
+  cmDuration GetRemainingTimeAllowed() const;
 
   static cmDuration MaxDuration();
 
@@ -246,7 +229,7 @@
   static std::string SafeBuildIdField(const std::string& value);
 
   /** Start CTest XML output file */
-  void StartXML(cmXMLWriter& xml, bool append);
+  void StartXML(cmXMLWriter& xml, cmake* cm, bool append);
 
   /** End CTest XML output file */
   void EndXML(cmXMLWriter& xml);
@@ -299,31 +282,6 @@
   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();
-  cmCTestUpdateHandler* GetUpdateHandler();
-  cmCTestConfigureHandler* GetConfigureHandler();
-  cmCTestMemCheckHandler* GetMemCheckHandler();
-  cmCTestSubmitHandler* GetSubmitHandler();
-  cmCTestUploadHandler* GetUploadHandler();
-
-  /**
    * Set the CTest variable from CMake variable
    */
   bool SetCTestConfigurationFromCMakeVariable(cmMakefile* mf,
@@ -331,6 +289,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&);
 
@@ -348,7 +311,7 @@
   void AddCTestConfigurationOverwrite(const std::string& encstr);
 
   /** Create XML file that contains all the notes specified */
-  int GenerateNotesFile(std::vector<std::string> const& files);
+  int GenerateNotesFile(cmake* cm, std::vector<std::string> const& files);
 
   /** Create XML file to indicate that build is complete */
   int GenerateDoneFile();
@@ -367,9 +330,9 @@
   void SetConfigType(const std::string& ct);
 
   /** Various log types */
-  enum
+  enum LogType
   {
-    DEBUG = 0,
+    DEBUG,
     OUTPUT,
     HANDLER_OUTPUT,
     HANDLER_PROGRESS_OUTPUT,
@@ -377,12 +340,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 +370,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();
 
@@ -424,11 +385,11 @@
 
   bool GetVerbose() const;
   bool GetExtraVerbose() const;
+  int GetSubmitIndex() const;
 
-  /** Direct process output to given streams.  */
-  void SetStreams(std::ostream* out, std::ostream* err);
+  void AddSiteProperties(cmXMLWriter& xml, cmake* cm);
 
-  void AddSiteProperties(cmXMLWriter& xml);
+  bool GetInteractiveDebugMode() const;
 
   bool GetLabelSummary() const;
   bool GetSubprojectSummary() const;
@@ -462,33 +423,22 @@
   void GenerateSubprojectsOutput(cmXMLWriter& xml);
   std::vector<std::string> GetLabelsForSubprojects();
 
-  void SetRunCurrentScript(bool value);
+  /** Reread the configuration file */
+  bool UpdateCTestConfiguration();
+
+  cmCTestTestOptions const& GetTestOptions() const;
+  std::vector<std::string> GetCommandLineHttpHeaders() const;
 
 private:
-  void SetPersistentOptionIfNotEmpty(const std::string& value,
-                                     const std::string& optionName);
-  void AddPersistentMultiOptionIfNotEmpty(const std::string& value,
-                                          const std::string& optionName);
-
-  int GenerateNotesFile(const std::string& files);
+  int GenerateNotesFile(cmake* cm, const std::string& files);
 
   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 +446,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,40 +457,18 @@
   /** 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,
+  int GenerateCTestNotesOutput(cmXMLWriter& xml, cmake* cm,
                                std::vector<std::string> const& files);
 
   /** Check if the argument is the one specified */
   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 +477,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/cmCommand.cxx b/Source/cmCommand.cxx
deleted file mode 100644
index 0c2734e..0000000
--- a/Source/cmCommand.cxx
+++ /dev/null
@@ -1,59 +0,0 @@
-/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
-   file Copyright.txt or https://cmake.org/licensing for details.  */
-#include "cmCommand.h"
-
-#include <utility>
-
-#include "cmExecutionStatus.h"
-#include "cmMakefile.h"
-
-struct cmListFileArgument;
-
-void cmCommand::SetExecutionStatus(cmExecutionStatus* status)
-{
-  this->Status = status;
-  this->Makefile = &status->GetMakefile();
-}
-
-bool cmCommand::InvokeInitialPass(const std::vector<cmListFileArgument>& args,
-                                  cmExecutionStatus& status)
-{
-  std::vector<std::string> expandedArguments;
-  if (!this->Makefile->ExpandArguments(args, expandedArguments)) {
-    // There was an error expanding arguments.  It was already
-    // reported, so we can skip this command without error.
-    return true;
-  }
-  return this->InitialPass(expandedArguments, status);
-}
-
-void cmCommand::SetError(const std::string& e)
-{
-  this->Status->SetError(e);
-}
-
-cmLegacyCommandWrapper::cmLegacyCommandWrapper(std::unique_ptr<cmCommand> cmd)
-  : Command(std::move(cmd))
-{
-}
-
-cmLegacyCommandWrapper::cmLegacyCommandWrapper(
-  cmLegacyCommandWrapper const& other)
-  : Command(other.Command->Clone())
-{
-}
-
-cmLegacyCommandWrapper& cmLegacyCommandWrapper::operator=(
-  cmLegacyCommandWrapper const& other)
-{
-  this->Command = other.Command->Clone();
-  return *this;
-}
-
-bool cmLegacyCommandWrapper::operator()(
-  std::vector<cmListFileArgument> const& args, cmExecutionStatus& status) const
-{
-  auto cmd = this->Command->Clone();
-  cmd->SetExecutionStatus(&status);
-  return cmd->InvokeInitialPass(args, status);
-}
diff --git a/Source/cmCommand.h b/Source/cmCommand.h
deleted file mode 100644
index f5a5190..0000000
--- a/Source/cmCommand.h
+++ /dev/null
@@ -1,97 +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 <memory>
-#include <string>
-#include <vector>
-
-class cmExecutionStatus;
-class cmMakefile;
-struct cmListFileArgument;
-
-/** \class cmCommand
- * \brief Superclass for all commands in CMake.
- *
- * cmCommand is the base class for all commands in CMake. A command
- * manifests as an entry in CMakeLists.txt and produces one or
- * more makefile rules. Commands are associated with a particular
- * makefile. This base class cmCommand defines the API for commands
- * to support such features as enable/disable, inheritance,
- * documentation, and construction.
- */
-class cmCommand
-{
-public:
-  /**
-   * Construct the command. By default it has no makefile.
-   */
-  cmCommand() = default;
-
-  /**
-   * Need virtual destructor to destroy real command type.
-   */
-  virtual ~cmCommand() = default;
-
-  cmCommand(cmCommand const&) = delete;
-  cmCommand& operator=(cmCommand const&) = delete;
-
-  /**
-   * Specify the makefile.
-   */
-  cmMakefile* GetMakefile() { return this->Makefile; }
-
-  void SetExecutionStatus(cmExecutionStatus* s);
-  cmExecutionStatus* GetExecutionStatus() { return this->Status; }
-
-  /**
-   * This is called by the cmMakefile when the command is first
-   * encountered in the CMakeLists.txt file.  It expands the command's
-   * arguments and then invokes the InitialPass.
-   */
-  bool InvokeInitialPass(const std::vector<cmListFileArgument>& args,
-                         cmExecutionStatus& status);
-
-  /**
-   * This is called when the command is first encountered in
-   * the CMakeLists.txt file.
-   */
-  virtual bool InitialPass(std::vector<std::string> const& args,
-                           cmExecutionStatus&) = 0;
-
-  /**
-   * This is a virtual constructor for the command.
-   */
-  virtual std::unique_ptr<cmCommand> Clone() = 0;
-
-  /**
-   * Set the error message
-   */
-  void SetError(const std::string& e);
-
-protected:
-  cmMakefile* Makefile = nullptr;
-
-private:
-  cmExecutionStatus* Status = nullptr;
-};
-
-class cmLegacyCommandWrapper
-{
-public:
-  explicit cmLegacyCommandWrapper(std::unique_ptr<cmCommand> cmd);
-
-  cmLegacyCommandWrapper(cmLegacyCommandWrapper const& other);
-  cmLegacyCommandWrapper& operator=(cmLegacyCommandWrapper const& other);
-
-  cmLegacyCommandWrapper(cmLegacyCommandWrapper&&) = default;
-  cmLegacyCommandWrapper& operator=(cmLegacyCommandWrapper&&) = default;
-
-  bool operator()(std::vector<cmListFileArgument> const& args,
-                  cmExecutionStatus& status) const;
-
-private:
-  std::unique_ptr<cmCommand> Command;
-};
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/cmComputeLinkDepends.cxx b/Source/cmComputeLinkDepends.cxx
index 551a45b..fde673a 100644
--- a/Source/cmComputeLinkDepends.cxx
+++ b/Source/cmComputeLinkDepends.cxx
@@ -257,7 +257,7 @@
         if (processingOption.match(1) == "LIBRARY_TYPE") {
           featureAttributes.LibraryTypes.clear();
           for (auto const& value :
-               cmTokenize(processingOption.match(2), ","_s)) {
+               cmTokenize(processingOption.match(2), ',')) {
             if (value == "STATIC") {
               featureAttributes.LibraryTypes.emplace(
                 cmStateEnums::STATIC_LIBRARY);
@@ -292,7 +292,8 @@
           }
         } else if (processingOption.match(1) == "OVERRIDE") {
           featureAttributes.Override.clear();
-          auto values = cmTokenize(processingOption.match(2), ","_s);
+          std::vector<std::string> values =
+            cmTokenize(processingOption.match(2), ',');
           featureAttributes.Override.insert(values.begin(), values.end());
         }
       } else {
@@ -668,13 +669,14 @@
       *linkLibraryOverride, target->GetLocalGenerator(), config, target, &dag,
       target, linkLanguage);
 
-    auto overrideList = cmTokenize(overrideValue, ","_s);
+    std::vector<std::string> overrideList =
+      cmTokenize(overrideValue, ',', cmTokenizerMode::New);
     if (overrideList.size() >= 2) {
       auto const& feature = overrideList.front();
-      for_each(overrideList.cbegin() + 1, overrideList.cend(),
-               [this, &feature](std::string const& item) {
-                 this->LinkLibraryOverride.emplace(item, feature);
-               });
+      std::for_each(overrideList.cbegin() + 1, overrideList.cend(),
+                    [this, &feature](std::string const& item) {
+                      this->LinkLibraryOverride.emplace(item, feature);
+                    });
     }
   }
 }
diff --git a/Source/cmComputeLinkInformation.cxx b/Source/cmComputeLinkInformation.cxx
index c2c210b..5d3cbb4 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/cmCoreTryCompile.cxx b/Source/cmCoreTryCompile.cxx
index 4d739ff..6ed3ad4 100644
--- a/Source/cmCoreTryCompile.cxx
+++ b/Source/cmCoreTryCompile.cxx
@@ -910,6 +910,14 @@
         ? "OLD"
         : "NEW");
 
+    /* Set the appropriate policy information for the LINKER: prefix expansion
+     */
+    fprintf(fout, "cmake_policy(SET CMP0181 %s)\n",
+            this->Makefile->GetPolicyStatus(cmPolicies::CMP0181) ==
+                cmPolicies::NEW
+              ? "NEW"
+              : "OLD");
+
     // Workaround for -Wl,-headerpad_max_install_names issue until we can avoid
     // adding that flag in the platform and compiler language files
     fprintf(fout,
diff --git a/Source/cmDebuggerAdapter.cxx b/Source/cmDebuggerAdapter.cxx
index c2e0d4f..c3587a1 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) {
@@ -148,6 +148,7 @@
     SupportsVariableType = req.supportsVariableType.value(false);
     dap::CMakeInitializeResponse response;
     response.supportsConfigurationDoneRequest = true;
+    response.supportsValueFormattingOptions = true;
     response.cmakeVersion.major = CMake_VERSION_MAJOR;
     response.cmakeVersion.minor = CMake_VERSION_MINOR;
     response.cmakeVersion.patch = CMake_VERSION_PATCH;
@@ -186,7 +187,7 @@
     std::unique_lock<std::mutex> lock(Mutex);
 
     cm::optional<dap::StackTraceResponse> response =
-      ThreadManager->GetThreadStackTraceResponse(request.threadId);
+      ThreadManager->GetThreadStackTraceResponse(request);
     if (response.has_value()) {
       return response.value();
     }
diff --git a/Source/cmDebuggerStackFrame.cxx b/Source/cmDebuggerStackFrame.cxx
index 789b0a5..89cdbc6 100644
--- a/Source/cmDebuggerStackFrame.cxx
+++ b/Source/cmDebuggerStackFrame.cxx
@@ -25,4 +25,10 @@
   return this->Function.Line();
 }
 
+std::vector<cmListFileArgument> const& cmDebuggerStackFrame::GetArguments()
+  const noexcept
+{
+  return this->Function.Arguments();
+}
+
 } // namespace cmDebugger
diff --git a/Source/cmDebuggerStackFrame.h b/Source/cmDebuggerStackFrame.h
index f4e6612..2133bcd 100644
--- a/Source/cmDebuggerStackFrame.h
+++ b/Source/cmDebuggerStackFrame.h
@@ -7,8 +7,10 @@
 #include <atomic>
 #include <cstdint>
 #include <string>
+#include <vector>
 
 class cmListFileFunction;
+struct cmListFileArgument;
 class cmMakefile;
 
 namespace cmDebugger {
@@ -32,6 +34,7 @@
   {
     return this->Function;
   }
+  std::vector<cmListFileArgument> const& GetArguments() const noexcept;
 };
 
 } // namespace cmDebugger
diff --git a/Source/cmDebuggerThread.cxx b/Source/cmDebuggerThread.cxx
index f7a1778..047dd2d 100644
--- a/Source/cmDebuggerThread.cxx
+++ b/Source/cmDebuggerThread.cxx
@@ -14,6 +14,7 @@
 #include "cmDebuggerVariablesHelper.h"
 #include "cmDebuggerVariablesManager.h"
 #include "cmListFileCache.h"
+#include "cmStringAlgorithms.h"
 
 namespace cmDebugger {
 
@@ -117,8 +118,27 @@
 }
 
 dap::StackTraceResponse GetStackTraceResponse(
-  std::shared_ptr<cmDebuggerThread> const& thread)
+  std::shared_ptr<cmDebuggerThread> const& thread,
+  dap::optional<dap::StackFrameFormat> format)
 {
+  dap::boolean showParameters = false;
+  dap::boolean showParameterValues = false;
+  dap::boolean showLine = false;
+  if (format.has_value()) {
+    auto formatValue = format.value();
+    if (formatValue.parameters.has_value()) {
+      showParameters = formatValue.parameters.value();
+    }
+
+    if (formatValue.parameterValues.has_value()) {
+      showParameterValues = formatValue.parameterValues.value();
+    }
+
+    if (formatValue.line.has_value()) {
+      showLine = formatValue.line.value();
+    }
+  }
+
   dap::StackTraceResponse response;
   std::unique_lock<std::mutex> lock(thread->Mutex);
   for (int i = static_cast<int>(thread->Frames.size()) - 1; i >= 0; --i) {
@@ -136,10 +156,29 @@
 #endif
     stackFrame.line = thread->Frames[i]->GetLine();
     stackFrame.column = 1;
-    stackFrame.name = thread->Frames[i]->GetFunction().OriginalName();
     stackFrame.id = thread->Frames[i]->GetId();
     stackFrame.source = source;
 
+    auto stackName = thread->Frames[i]->GetFunction().OriginalName();
+    if (showParameters) {
+      stackName.push_back('(');
+      if (showParameterValues && !thread->Frames[i]->GetArguments().empty()) {
+        for (auto const& arg : thread->Frames[i]->GetArguments()) {
+          stackName = cmStrCat(stackName, arg.Value, ", ");
+        }
+
+        stackName.erase(stackName.end() - 2, stackName.end());
+      }
+
+      stackName.push_back(')');
+    }
+
+    if (showLine) {
+      stackName =
+        cmStrCat(stackName, " Line: ", static_cast<int64_t>(stackFrame.line));
+    }
+
+    stackFrame.name = stackName;
     response.stackFrames.push_back(stackFrame);
   }
 
diff --git a/Source/cmDebuggerThread.h b/Source/cmDebuggerThread.h
index 65ee2cf..81664b5 100644
--- a/Source/cmDebuggerThread.h
+++ b/Source/cmDebuggerThread.h
@@ -23,6 +23,11 @@
 class cmDebuggerVariablesManager;
 }
 
+namespace dap {
+template <typename T>
+class optional;
+}
+
 namespace cmDebugger {
 
 class cmDebuggerThread
@@ -53,7 +58,8 @@
   dap::VariablesResponse GetVariablesResponse(
     dap::VariablesRequest const& request);
   friend dap::StackTraceResponse GetStackTraceResponse(
-    std::shared_ptr<cmDebuggerThread> const& thread);
+    std::shared_ptr<cmDebuggerThread> const& thread,
+    dap::optional<dap::StackFrameFormat> format);
 };
 
 } // namespace cmDebugger
diff --git a/Source/cmDebuggerThreadManager.cxx b/Source/cmDebuggerThreadManager.cxx
index 0eb443b..0f15b6b 100644
--- a/Source/cmDebuggerThreadManager.cxx
+++ b/Source/cmDebuggerThreadManager.cxx
@@ -6,6 +6,7 @@
 #include <algorithm>
 
 #include <cm3p/cppdap/protocol.h>
+#include <cm3p/cppdap/types.h>
 
 #include "cmDebuggerThread.h"
 
@@ -30,18 +31,19 @@
 }
 
 cm::optional<dap::StackTraceResponse>
-cmDebuggerThreadManager::GetThreadStackTraceResponse(int64_t id)
+cmDebuggerThreadManager::GetThreadStackTraceResponse(
+  const dap::StackTraceRequest& request)
 {
   auto it = find_if(Threads.begin(), Threads.end(),
                     [&](const std::shared_ptr<cmDebuggerThread>& t) {
-                      return t->GetId() == id;
+                      return t->GetId() == request.threadId;
                     });
 
   if (it == Threads.end()) {
     return {};
   }
 
-  return GetStackTraceResponse(*it);
+  return GetStackTraceResponse(*it, request.format);
 }
 
 } // namespace cmDebugger
diff --git a/Source/cmDebuggerThreadManager.h b/Source/cmDebuggerThreadManager.h
index 934cf85..6d27a5c 100644
--- a/Source/cmDebuggerThreadManager.h
+++ b/Source/cmDebuggerThreadManager.h
@@ -17,6 +17,7 @@
 }
 
 namespace dap {
+struct StackTraceRequest;
 struct StackTraceResponse;
 }
 
@@ -32,7 +33,7 @@
   std::shared_ptr<cmDebuggerThread> StartThread(std::string const& name);
   void EndThread(std::shared_ptr<cmDebuggerThread> const& thread);
   cm::optional<dap::StackTraceResponse> GetThreadStackTraceResponse(
-    std::int64_t id);
+    const dap::StackTraceRequest& request);
 };
 
 } // namespace cmDebugger
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/cmDependsCompiler.cxx b/Source/cmDependsCompiler.cxx
index 17ec43b..408f19f 100644
--- a/Source/cmDependsCompiler.cxx
+++ b/Source/cmDependsCompiler.cxx
@@ -4,7 +4,6 @@
 #include "cmDependsCompiler.h"
 
 #include <algorithm>
-#include <iterator>
 #include <map>
 #include <string>
 #include <unordered_set>
@@ -111,13 +110,9 @@
           // copy depends for each target, except first one, which can be
           // moved
           for (auto index = entry.rules.size() - 1; index > 0; --index) {
-            auto& rule_deps = dependencies[entry.rules[index]];
-            rule_deps.insert(rule_deps.end(), depends.cbegin(),
-                             depends.cend());
+            dependencies[entry.rules[index]] = depends;
           }
-          auto& rule_deps = dependencies[entry.rules.front()];
-          std::move(depends.cbegin(), depends.cend(),
-                    std::back_inserter(rule_deps));
+          dependencies[entry.rules.front()] = std::move(depends);
         }
       } else {
         if (format == "msvc"_s) {
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..0c22259 100644
--- a/Source/cmDocumentationFormatter.cxx
+++ b/Source/cmDocumentationFormatter.cxx
@@ -5,175 +5,144 @@
 #include <algorithm> // IWYU pragma: keep
 #include <cassert>
 #include <iomanip>
+#include <iterator>
 #include <ostream>
 #include <string>
 #include <vector>
 
+#include <cm/string_view>
+#include <cmext/string_view>
+
 #include "cmDocumentationEntry.h"
 #include "cmDocumentationSection.h"
+#include "cmStringAlgorithms.h"
 
 namespace {
-const char* skipSpaces(const char* ptr)
-{
-  assert(ptr);
-  for (; *ptr == ' '; ++ptr) {
-    ;
-  }
-  return ptr;
-}
-const char* skipToSpace(const char* ptr)
-{
-  assert(ptr);
-  for (; *ptr && (*ptr != '\n') && (*ptr != ' '); ++ptr) {
-    ;
-  }
-  return ptr;
-}
-}
+const auto EOL = "\n"_s;
+const auto SPACE = " "_s;
+const auto TWO_SPACES = "  "_s;
+const auto MAX_WIDTH_PADDING =
+  std::string(cmDocumentationFormatter::TEXT_WIDTH, ' ');
 
-void cmDocumentationFormatter::PrintFormatted(std::ostream& os,
-                                              std::string const& text) const
+void FormatLine(std::back_insert_iterator<std::vector<cm::string_view>> outIt,
+                const cm::string_view text, const cm::string_view padding)
 {
-  if (text.empty()) {
+  auto tokens = cmTokenizedView(text, ' ', cmTokenizerMode::New);
+  if (tokens.empty()) {
     return;
   }
 
-  struct Buffer
-  {
-    // clang-format off
-    using PrinterFn = void (cmDocumentationFormatter::*)(
-        std::ostream&, std::string const&
-      ) const;
-    // clang-format on
-    std::string collected;
-    const PrinterFn printer;
-  };
-  // const auto NORMAL_IDX = 0u;
-  const auto PREFORMATTED_IDX = 1u;
-  const auto HANDLERS_SIZE = 2u;
-  Buffer buffers[HANDLERS_SIZE] = {
-    { {}, &cmDocumentationFormatter::PrintParagraph },
-    { {}, &cmDocumentationFormatter::PrintPreformatted }
-  };
-
-  const auto padding = std::string(this->TextIndent, ' ');
-
-  for (std::size_t pos = 0u, eol = 0u; pos < text.size(); pos = eol) {
-    const auto current_idx = std::size_t(text[pos] == ' ');
-    // size_t(!bool(current_idx))
-    const auto other_idx = current_idx ^ 1u;
-
-    // Flush the other buffer if anything has been collected
-    if (!buffers[other_idx].collected.empty()) {
-      // NOTE Whatever the other index is, the current buffered
-      // string expected to be empty.
-      assert(buffers[current_idx].collected.empty());
-
-      (this->*buffers[other_idx].printer)(os, buffers[other_idx].collected);
-      buffers[other_idx].collected.clear();
-    }
-
-    // ATTENTION The previous implementation had called `PrintParagraph()`
-    // **for every processed (char by char) input line**.
-    // The method unconditionally append the `\n' character after the
-    // printed text. To keep the backward-compatible behavior it's needed to
-    // add the '\n' character to the previously collected line...
-    if (!buffers[current_idx].collected.empty() &&
-        current_idx != PREFORMATTED_IDX) {
-      buffers[current_idx].collected += '\n';
-    }
-
-    // Lookup EOL
-    eol = text.find('\n', pos);
-    if (current_idx == PREFORMATTED_IDX) {
-      buffers[current_idx].collected.append(padding);
-    }
-    buffers[current_idx].collected.append(
-      text, pos, eol == std::string::npos ? eol : ++eol - pos);
+  // Push padding in front of a first line
+  if (!padding.empty()) {
+    outIt = padding;
   }
 
-  for (auto& buf : buffers) {
-    if (!buf.collected.empty()) {
-      (this->*buf.printer)(os, buf.collected);
-    }
-  }
-}
+  auto currentWidth = padding.size();
+  auto newSentence = false;
 
-void cmDocumentationFormatter::PrintPreformatted(std::ostream& os,
-                                                 std::string const& text) const
-{
-  os << text << '\n';
-}
-
-void cmDocumentationFormatter::PrintParagraph(std::ostream& os,
-                                              std::string const& text) const
-{
-  if (this->TextIndent) {
-    os << std::string(this->TextIndent, ' ');
-  }
-  this->PrintColumn(os, text);
-  os << '\n';
-}
-
-void cmDocumentationFormatter::PrintColumn(std::ostream& os,
-                                           std::string const& text) const
-{
-  // Print text arranged in an indented column of fixed width.
-  bool newSentence = false;
-  bool firstLine = true;
-
-  assert(this->TextIndent < this->TextWidth);
-  const std::ptrdiff_t width = this->TextWidth - this->TextIndent;
-  std::ptrdiff_t column = 0;
-
-  // 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)) {
-    // Does it fit on this line?
-    if (r - l < width - column - std::ptrdiff_t(newSentence)) {
-      // Word fits on this line.
-      if (r > l) {
-        if (column) {
-          // Not first word on line.  Separate from the previous word
-          // by a space, or two if this is a new sentence.
-          os << &("  "[std::size_t(!newSentence)]);
-          column += 1u + std::ptrdiff_t(newSentence);
-        } else if (!firstLine && this->TextIndent) {
-          // First word on line.  Print indentation unless this is the
-          // first line.
-          os << std::string(this->TextIndent, ' ');
-        }
-
-        // Print the word.
-        os.write(l, r - l);
-        newSentence = (*(r - 1) == '.');
+  for (auto token : tokens) {
+    // It's no need to add a space if this is a very first
+    // word on a line.
+    const auto needSpace = currentWidth > padding.size();
+    // Evaluate the size of a current token + possibly spaces before it.
+    const auto tokenWithSpaceSize = token.size() + std::size_t(needSpace) +
+      std::size_t(needSpace && newSentence);
+    // Check if a current word fits on a line.
+    // Also, take in account:
+    //  - extra space if not a first word on a line
+    //  - extra space if last token ends w/ a period
+    if (currentWidth + tokenWithSpaceSize <=
+        cmDocumentationFormatter::TEXT_WIDTH) {
+      // If not a first word on a line...
+      if (needSpace) {
+        // ... add a space after the last token +
+        // possibly one more space if the last token
+        // ends with a period (means, end of a sentence).
+        outIt = newSentence ? TWO_SPACES : SPACE;
       }
-
-      if (*r == '\n') {
-        // Text provided a newline.  Start a new line.
-        os << '\n';
-        ++r;
-        column = 0;
-        firstLine = false;
-      } else {
-        // No provided newline.  Continue this line.
-        column += r - l;
-      }
+      outIt = token;
+      currentWidth += tokenWithSpaceSize;
     } else {
-      // Word does not fit on this line.  Start a new line.
-      os << '\n';
-      firstLine = false;
-      if (r > l) {
-        os << std::string(this->TextIndent, ' ');
-        os.write(l, r - l);
-        column = r - l;
-        newSentence = (*(r - 1) == '.');
-      } else {
-        column = 0;
+      // Start a new line!
+      outIt = EOL;
+      if (!padding.empty()) {
+        outIt = padding;
       }
+      outIt = token;
+      currentWidth = padding.size() + token.size();
     }
-    // Move to beginning of next word.  Skip over whitespace.
+
+    // Start a new sentence if the current word ends with period
+    newSentence = token.back() == '.';
   }
+  // Always add EOL at the end of formatted text
+  outIt = EOL;
+}
+} // anonymous namespace
+
+std::string cmDocumentationFormatter::Format(std::string text) const
+{
+  // Exit early on empty text
+  if (text.empty()) {
+    return {};
+  }
+
+  assert(this->TextIndent < this->TEXT_WIDTH);
+
+  const auto padding =
+    cm::string_view(MAX_WIDTH_PADDING.c_str(), this->TextIndent);
+
+  std::vector<cm::string_view> tokens;
+  auto outIt = std::back_inserter(tokens);
+  auto prevWasPreFormatted = false;
+
+  // NOTE Can't use `cmTokenizedView()` cuz every sequential EOL does matter
+  // (and `cmTokenizedView()` will squeeze 'em)
+  for ( // clang-format off
+      std::string::size_type start = 0
+    , end = text.find('\n')
+    ; start < text.size()
+    ; start = end + ((end != std::string::npos) ? 1 : 0)
+    , end = text.find('\n', start)
+    ) // clang-format on
+  {
+    const auto isLastLine = end == std::string::npos;
+    const auto line = isLastLine
+      ? cm::string_view{ text.c_str() + start }
+      : cm::string_view{ text.c_str() + start, end - start };
+
+    if (!line.empty() && line.front() == ' ') {
+      // Preformatted lines go as is w/ a leading padding
+      if (!padding.empty()) {
+        outIt = padding;
+      }
+      outIt = line;
+      prevWasPreFormatted = true;
+    } else {
+      // Separate a normal paragraph from a pre-formatted
+      // w/ an extra EOL
+      if (prevWasPreFormatted) {
+        outIt = EOL;
+      }
+      if (line.empty()) {
+        if (!isLastLine) {
+          outIt = EOL;
+        }
+      } else {
+        FormatLine(outIt, line, padding);
+      }
+      prevWasPreFormatted = false;
+    }
+    if (!isLastLine) {
+      outIt = EOL;
+    }
+  }
+
+  if (prevWasPreFormatted) {
+    outIt = EOL;
+  }
+
+  return cmJoinStrings(tokens, {}, {});
 }
 
 void cmDocumentationFormatter::PrintSection(
@@ -202,13 +171,10 @@
       if (entry.Name.size() > NAME_SIZE) {
         os << '\n' << std::setw(int(this->TextIndent - PREFIX_SIZE)) << ' ';
       }
-      os << "= ";
-      this->PrintColumn(os, entry.Brief);
-      os << '\n';
+      os << "= " << this->Format(entry.Brief).substr(this->TextIndent);
     } else {
-      os << '\n';
       this->TextIndent = 0u;
-      this->PrintFormatted(os, entry.Brief);
+      os << '\n' << this->Format(entry.Brief);
     }
   }
 
diff --git a/Source/cmDocumentationFormatter.h b/Source/cmDocumentationFormatter.h
index 9d35e0a..77606cf 100644
--- a/Source/cmDocumentationFormatter.h
+++ b/Source/cmDocumentationFormatter.h
@@ -14,15 +14,20 @@
 class cmDocumentationFormatter
 {
 public:
-  void SetIndent(std::size_t indent) { this->TextIndent = indent; }
-  void PrintFormatted(std::ostream& os, std::string const& text) const;
+  std::string Format(std::string text) const;
   void PrintSection(std::ostream& os, cmDocumentationSection const& section);
+  void PrintFormatted(std::ostream& os, std::string const& text) const
+  {
+    os << this->Format(text);
+  }
+  void SetIndent(std::size_t indent) { this->TextIndent = indent; }
+
+  static constexpr std::size_t TEXT_WIDTH = 77u;
 
 private:
   void PrintPreformatted(std::ostream& os, std::string const&) const;
   void PrintParagraph(std::ostream& os, std::string const&) const;
   void PrintColumn(std::ostream& os, std::string const&) const;
 
-  std::size_t TextWidth = 77u;
   std::size_t TextIndent = 0u;
 };
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..e6fe94b 100644
--- a/Source/cmFileCommand.cxx
+++ b/Source/cmFileCommand.cxx
@@ -1409,8 +1409,7 @@
       auto basePath = cmCMakePath{ *arguments.BaseDirectory };
       path = basePath.Append(path);
     }
-    result = cmSystemTools::GetActualCaseForPath(
-      cmSystemTools::GetRealPath(path.String()));
+    result = cmSystemTools::GetRealPath(path.String());
   };
 
   std::string realPath;
@@ -3846,14 +3845,13 @@
 
       if (!cmSystemTools::FileIsFullPath(inFile)) {
         inFile =
-          cmStrCat(cmSystemTools::GetCurrentWorkingDirectory(), "/", inFile);
+          cmStrCat(cmSystemTools::GetLogicalWorkingDirectory(), "/", inFile);
       }
     }
 
     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/cmFileLock.cxx b/Source/cmFileLock.cxx
index 548e327..64fbc1f 100644
--- a/Source/cmFileLock.cxx
+++ b/Source/cmFileLock.cxx
@@ -14,6 +14,9 @@
   this->File = other.File;
   other.File = (decltype(other.File))-1;
   this->Filename = std::move(other.Filename);
+#if defined(_WIN32)
+  this->Overlapped = std::move(other.Overlapped);
+#endif
 }
 
 cmFileLock::~cmFileLock()
@@ -30,6 +33,9 @@
   this->File = other.File;
   other.File = (decltype(other.File))-1;
   this->Filename = std::move(other.Filename);
+#if defined(_WIN32)
+  this->Overlapped = std::move(other.Overlapped);
+#endif
 
   return *this;
 }
diff --git a/Source/cmFileLock.h b/Source/cmFileLock.h
index 0f2e7d9..e17f8bc 100644
--- a/Source/cmFileLock.h
+++ b/Source/cmFileLock.h
@@ -7,6 +7,7 @@
 #include <string>
 
 #if defined(_WIN32)
+#  include <memory>
 using HANDLE = void*;
 #endif
 
@@ -54,6 +55,7 @@
 
 #if defined(_WIN32)
   HANDLE File = (HANDLE)-1;
+  std::unique_ptr<struct _OVERLAPPED> Overlapped;
   int LockFile(int flags);
 #else
   int File = -1;
diff --git a/Source/cmFileLockWin32.cxx b/Source/cmFileLockWin32.cxx
index 244ade2..8a4dadc 100644
--- a/Source/cmFileLockWin32.cxx
+++ b/Source/cmFileLockWin32.cxx
@@ -1,12 +1,18 @@
 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
    file Copyright.txt or https://cmake.org/licensing for details.  */
-#include <windows.h> // CreateFileW
+#include <cm/memory>
+
+#include <windows.h>
 
 #include "cmFileLock.h"
 #include "cmSystemTools.h"
 
+static const unsigned long LOCK_LEN = static_cast<unsigned long>(-1);
+
 cmFileLock::cmFileLock()
+  : Overlapped(cm::make_unique<OVERLAPPED>())
 {
+  ZeroMemory(this->Overlapped.get(), sizeof(*this->Overlapped));
 }
 
 cmFileLockResult cmFileLock::Release()
@@ -14,15 +20,16 @@
   if (this->Filename.empty()) {
     return cmFileLockResult::MakeOk();
   }
-  const unsigned long len = static_cast<unsigned long>(-1);
-  static OVERLAPPED overlapped;
   const DWORD reserved = 0;
+  ZeroMemory(this->Overlapped.get(), sizeof(*this->Overlapped));
+
   const BOOL unlockResult =
-    UnlockFileEx(File, reserved, len, len, &overlapped);
+    UnlockFileEx(File, reserved, LOCK_LEN, LOCK_LEN, this->Overlapped.get());
 
   this->Filename = "";
 
   CloseHandle(this->File);
+
   this->File = INVALID_HANDLE_VALUE;
 
   if (unlockResult) {
@@ -51,37 +58,63 @@
 
 cmFileLockResult cmFileLock::LockWithoutTimeout()
 {
+  cmFileLockResult lock_result = cmFileLockResult::MakeOk();
   if (!this->LockFile(LOCKFILE_EXCLUSIVE_LOCK)) {
-    return cmFileLockResult::MakeSystem();
-  } else {
-    return cmFileLockResult::MakeOk();
+    lock_result = cmFileLockResult::MakeSystem();
   }
+  CloseHandle(this->Overlapped->hEvent);
+  return lock_result;
 }
 
 cmFileLockResult cmFileLock::LockWithTimeout(unsigned long seconds)
 {
+  cmFileLockResult lock_result = cmFileLockResult::MakeOk();
   const DWORD flags = LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY;
-  while (true) {
-    const BOOL result = this->LockFile(flags);
-    if (result) {
-      return cmFileLockResult::MakeOk();
+  bool in_time = true;
+  while (in_time && !this->LockFile(flags)) {
+    switch (GetLastError()) {
+      case ERROR_INVALID_HANDLE:
+        lock_result = cmFileLockResult::MakeSystem();
+        break;
+      case ERROR_LOCK_VIOLATION:
+        if (seconds == 0) {
+          in_time = false;
+          lock_result = cmFileLockResult::MakeTimeout();
+          continue;
+        }
+        --seconds;
+        cmSystemTools::Delay(1000);
+        continue;
+      case ERROR_IO_PENDING:
+        switch (
+          WaitForSingleObject(this->Overlapped->hEvent, seconds * 1000)) {
+          case WAIT_OBJECT_0:
+            break;
+          case WAIT_TIMEOUT:
+            lock_result = cmFileLockResult::MakeTimeout();
+            break;
+          default:
+            lock_result = cmFileLockResult::MakeSystem();
+            break;
+        }
+        break;
+      default:
+        lock_result = cmFileLockResult::MakeSystem();
+        break;
     }
-    const DWORD error = GetLastError();
-    if (error != ERROR_LOCK_VIOLATION) {
-      return cmFileLockResult::MakeSystem();
-    }
-    if (seconds == 0) {
-      return cmFileLockResult::MakeTimeout();
-    }
-    --seconds;
-    cmSystemTools::Delay(1000);
   }
+  CloseHandle(this->Overlapped->hEvent);
+  return lock_result;
 }
 
 int cmFileLock::LockFile(int flags)
 {
   const DWORD reserved = 0;
-  const unsigned long len = static_cast<unsigned long>(-1);
-  static OVERLAPPED overlapped;
-  return LockFileEx(this->File, flags, reserved, len, len, &overlapped);
+
+  this->Overlapped->hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
+  if (this->Overlapped->hEvent == nullptr) {
+    return false;
+  }
+  return LockFileEx(this->File, flags, reserved, LOCK_LEN, LOCK_LEN,
+                    this->Overlapped.get());
 }
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..4f97a18 100644
--- a/Source/cmFindPackageCommand.cxx
+++ b/Source/cmFindPackageCommand.cxx
@@ -12,11 +12,11 @@
 #include <utility>
 
 #include <cm/optional>
+#include <cmext/algorithm>
 #include <cmext/string_view>
 
 #include "cmsys/Directory.hxx"
 #include "cmsys/FStream.hxx"
-#include "cmsys/Glob.hxx"
 #include "cmsys/RegularExpression.hxx"
 #include "cmsys/String.h"
 
@@ -164,8 +164,7 @@
 {
 public:
   cmCaseInsensitiveDirectoryListGenerator(cm::string_view name)
-    : DirectoryLister{}
-    , DirName{ name }
+    : DirName{ name }
   {
   }
 
@@ -207,9 +206,10 @@
 class cmDirectoryListGenerator
 {
 public:
-  cmDirectoryListGenerator(std::vector<std::string> const& names)
+  cmDirectoryListGenerator(std::vector<std::string> const& names,
+                           bool exactMatch)
     : Names{ names }
-    , Matches{}
+    , ExactMatch{ exactMatch }
     , Current{ this->Matches.cbegin() }
   {
   }
@@ -233,20 +233,28 @@
       // `isDirentryToIgnore(i)` condition to check.
       for (auto i = 0ul; i < directoryLister.GetNumberOfFiles(); ++i) {
         const char* const fname = directoryLister.GetFile(i);
-        if (isDirentryToIgnore(fname)) {
+        // Skip entries to ignore or that aren't directories.
+        if (isDirentryToIgnore(fname) || !directoryLister.FileIsDirectory(i)) {
           continue;
         }
 
-        for (const auto& n : this->Names.get()) {
-          // NOTE Customization point for `cmMacProjectDirectoryListGenerator`
-          const auto name = this->TransformNameBeforeCmp(n);
-          // Skip entries that don't match and non-directories.
-          // ATTENTION BTW, original code also didn't check if it's a symlink
-          // to a directory!
-          const auto equal =
-            (cmsysString_strncasecmp(fname, name.c_str(), name.length()) == 0);
-          if (equal && directoryLister.FileIsDirectory(i)) {
-            this->Matches.emplace_back(fname);
+        if (!this->ExactMatch && this->Names.get().empty()) {
+          this->Matches.emplace_back(fname);
+        } else {
+          for (const auto& n : this->Names.get()) {
+            // NOTE Customization point for
+            // `cmMacProjectDirectoryListGenerator`
+            const auto name = this->TransformNameBeforeCmp(n);
+            // Skip entries that don't match.
+            const auto equal =
+              ((this->ExactMatch
+                  ? cmsysString_strcasecmp(fname, name.c_str())
+                  : cmsysString_strncasecmp(fname, name.c_str(),
+                                            name.length())) == 0);
+            if (equal) {
+              this->Matches.emplace_back(fname);
+              break;
+            }
           }
         }
       }
@@ -275,6 +283,7 @@
   virtual std::string TransformNameBeforeCmp(std::string same) { return same; }
 
   std::reference_wrapper<const std::vector<std::string>> Names;
+  bool const ExactMatch;
   std::vector<std::string> Matches;
   std::vector<std::string>::const_iterator Current;
 };
@@ -284,8 +293,9 @@
 public:
   cmProjectDirectoryListGenerator(std::vector<std::string> const& names,
                                   cmFindPackageCommand::SortOrderType so,
-                                  cmFindPackageCommand::SortDirectionType sd)
-    : cmDirectoryListGenerator{ names }
+                                  cmFindPackageCommand::SortDirectionType sd,
+                                  bool exactMatch)
+    : cmDirectoryListGenerator{ names, exactMatch }
     , SortOrder{ so }
     , SortDirection{ sd }
   {
@@ -312,7 +322,7 @@
 public:
   cmMacProjectDirectoryListGenerator(const std::vector<std::string>& names,
                                      cm::string_view ext)
-    : cmDirectoryListGenerator{ names }
+    : cmDirectoryListGenerator{ names, true }
     , Extension{ ext }
   {
   }
@@ -327,49 +337,18 @@
   const cm::string_view Extension;
 };
 
-class cmFileListGeneratorGlob
+class cmAnyDirectoryListGenerator : public cmProjectDirectoryListGenerator
 {
 public:
-  cmFileListGeneratorGlob(cm::string_view pattern)
-    : Pattern(pattern)
-    , Files{}
-    , Current{}
+  cmAnyDirectoryListGenerator(cmFindPackageCommand::SortOrderType so,
+                              cmFindPackageCommand::SortDirectionType sd)
+    : cmProjectDirectoryListGenerator(this->EmptyNamesList, so, sd, false)
   {
   }
 
-  std::string GetNextCandidate(const std::string& parent)
-  {
-    if (this->Files.empty()) {
-      // Glob the set of matching files.
-      std::string expr = cmStrCat(parent, this->Pattern);
-      cmsys::Glob g;
-      if (!g.FindFiles(expr)) {
-        return {};
-      }
-      this->Files = g.GetFiles();
-      this->Current = this->Files.cbegin();
-    }
-
-    // Skip non-directories
-    for (; this->Current != this->Files.cend() &&
-         !cmSystemTools::FileIsDirectory(*this->Current);
-         ++this->Current) {
-    }
-
-    return (this->Current != this->Files.cend()) ? *this->Current++
-                                                 : std::string{};
-  }
-
-  void Reset()
-  {
-    this->Files.clear();
-    this->Current = this->Files.cbegin();
-  }
-
 private:
-  cm::string_view Pattern;
-  std::vector<std::string> Files;
-  std::vector<std::string>::const_iterator Current;
+  // NOTE `cmDirectoryListGenerator` needs to hold a reference to this
+  std::vector<std::string> EmptyNamesList;
 };
 
 #if defined(__LCC__)
@@ -1965,11 +1944,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 +2101,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));
@@ -2442,8 +2423,12 @@
 {
   assert(!dir.empty() && dir.back() == '/');
 
-  // Look for the file in this directory.
   std::string const d = dir.substr(0, dir.size() - 1);
+  if (cm::contains(this->IgnoredPaths, d)) {
+    return false;
+  }
+
+  // Look for the file in this directory.
   if (this->FindConfigFile(d, this->FileFound)) {
     // Remove duplicate slashes.
     cmSystemTools::ConvertToUnixSlashes(this->FileFound);
@@ -2455,10 +2440,6 @@
 bool cmFindPackageCommand::FindConfigFile(std::string const& dir,
                                           std::string& file)
 {
-  if (this->IgnoredPaths.count(dir)) {
-    return false;
-  }
-
   for (std::string const& c : this->Configs) {
     file = cmStrCat(dir, '/', c);
     if (this->DebugMode) {
@@ -2668,7 +2649,7 @@
   auto iCMakeGen = cmCaseInsensitiveDirectoryListGenerator{ "cmake"_s };
   auto firstPkgDirGen =
     cmProjectDirectoryListGenerator{ this->Names, this->SortOrder,
-                                     this->SortDirection };
+                                     this->SortDirection, false };
 
   // PREFIX/(cmake|CMake)/ (useful on windows or in build trees)
   if (TryGeneratedPaths(searchFn, prefix, iCMakeGen)) {
@@ -2687,7 +2668,7 @@
 
   auto secondPkgDirGen =
     cmProjectDirectoryListGenerator{ this->Names, this->SortOrder,
-                                     this->SortDirection };
+                                     this->SortDirection, false };
 
   // PREFIX/(Foo|foo|FOO).*/(cmake|CMake)/(Foo|foo|FOO).*/
   if (TryGeneratedPaths(searchFn, prefix, firstPkgDirGen, iCMakeGen,
@@ -2766,7 +2747,8 @@
     cmMacProjectDirectoryListGenerator{ this->Names, ".framework"_s };
   auto rGen = cmAppendPathSegmentGenerator{ "Resources"_s };
   auto vGen = cmAppendPathSegmentGenerator{ "Versions"_s };
-  auto grGen = cmFileListGeneratorGlob{ "/*/Resources"_s };
+  auto anyGen =
+    cmAnyDirectoryListGenerator{ this->SortOrder, this->SortDirection };
 
   // <prefix>/Foo.framework/Resources/
   if (TryGeneratedPaths(searchFn, prefix, fwGen, rGen)) {
@@ -2779,12 +2761,13 @@
   }
 
   // <prefix>/Foo.framework/Versions/*/Resources/
-  if (TryGeneratedPaths(searchFn, prefix, fwGen, vGen, grGen)) {
+  if (TryGeneratedPaths(searchFn, prefix, fwGen, vGen, anyGen, rGen)) {
     return true;
   }
 
   // <prefix>/Foo.framework/Versions/*/Resources/CMake/
-  return TryGeneratedPaths(searchFn, prefix, fwGen, vGen, grGen, iCMakeGen);
+  return TryGeneratedPaths(searchFn, prefix, fwGen, vGen, anyGen, rGen,
+                           iCMakeGen);
 }
 
 bool cmFindPackageCommand::SearchAppBundlePrefix(std::string const& prefix_in)
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/cmGeneratorOptions.h b/Source/cmGeneratorOptions.h
new file mode 100644
index 0000000..0e17f56
--- /dev/null
+++ b/Source/cmGeneratorOptions.h
@@ -0,0 +1,35 @@
+/* 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
+
+/** Flag if byproducts shall also be considered.  */
+enum class cmSourceOutputKind
+{
+  OutputOnly,
+  OutputOrByproduct
+};
+
+/** What scanner to use for dependencies lookup.  */
+enum class cmDependencyScannerKind
+{
+  CMake,
+  Compiler
+};
+
+/** What to compute language flags for */
+enum class cmBuildStep
+{
+  Compile,
+  Link
+};
+
+/** What compilation mode the swift files are in */
+enum class cmSwiftCompileMode
+{
+  Wholemodule,
+  Incremental,
+  Singlefile,
+  Unknown,
+};
diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx
index fb92771..a99cd20 100644
--- a/Source/cmGeneratorTarget.cxx
+++ b/Source/cmGeneratorTarget.cxx
@@ -30,6 +30,7 @@
 #include "cmGeneratedFileStream.h"
 #include "cmGeneratorExpression.h"
 #include "cmGeneratorExpressionDAGChecker.h"
+#include "cmGeneratorOptions.h"
 #include "cmGlobalGenerator.h"
 #include "cmList.h"
 #include "cmLocalGenerator.h"
@@ -2516,8 +2517,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 +2696,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/cmGeneratorTarget_Options.cxx b/Source/cmGeneratorTarget_Options.cxx
index d8b3eb3..8b978c5 100644
--- a/Source/cmGeneratorTarget_Options.cxx
+++ b/Source/cmGeneratorTarget_Options.cxx
@@ -565,11 +565,11 @@
       cmSystemTools::ParseUnixCommandLine(
         value.c_str() + LINKER_SHELL.length(), linkerOptions);
     } else {
-      linkerOptions = cmTokenize(value.substr(LINKER.length()), ",");
+      linkerOptions =
+        cmTokenize(value.substr(LINKER.length()), ',', cmTokenizerMode::New);
     }
 
-    if (linkerOptions.empty() ||
-        (linkerOptions.size() == 1 && linkerOptions.front().empty())) {
+    if (linkerOptions.empty()) {
       continue;
     }
 
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/cmGhsMultiTargetGenerator.cxx b/Source/cmGhsMultiTargetGenerator.cxx
index a92faef..888dfd3 100644
--- a/Source/cmGhsMultiTargetGenerator.cxx
+++ b/Source/cmGhsMultiTargetGenerator.cxx
@@ -15,6 +15,7 @@
 #include "cmCustomCommand.h"
 #include "cmCustomCommandGenerator.h"
 #include "cmGeneratedFileStream.h"
+#include "cmGeneratorOptions.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGhsMultiGenerator.h"
 #include "cmLinkLineComputer.h" // IWYU pragma: keep
diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx
index e3a38b0..382b656 100644
--- a/Source/cmGlobalGenerator.cxx
+++ b/Source/cmGlobalGenerator.cxx
@@ -1503,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;
 }
@@ -1777,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());
@@ -1798,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());
@@ -2218,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;
 }
@@ -2249,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();
@@ -2261,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;
@@ -2274,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?
@@ -2303,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
@@ -2334,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;
@@ -2811,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:
@@ -2825,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.";
     }
@@ -2984,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);
@@ -3020,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:
@@ -3127,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";
       }
@@ -3743,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';
     }
   }
 }
@@ -3761,7 +3763,7 @@
         continue;
       }
       this->WriteSummary(tgt.get());
-      fout << tgt->GetSupportDirectory() << "\n";
+      fout << tgt->GetSupportDirectory() << '\n';
     }
   }
 }
@@ -3799,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);
         }
       }
@@ -3822,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);
     }
 
@@ -3844,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);
         }
       }
@@ -4021,3 +4023,16 @@
 {
   this->InstallScripts.push_back(file);
 }
+
+void cmGlobalGenerator::AddTestFile(std::string const& file)
+{
+  this->TestFiles.push_back(file);
+}
+
+void cmGlobalGenerator::AddCMakeFilesToRebuild(
+  std::vector<std::string>& files) const
+{
+  files.insert(files.end(), this->InstallScripts.begin(),
+               this->InstallScripts.end());
+  files.insert(files.end(), this->TestFiles.begin(), this->TestFiles.end());
+}
diff --git a/Source/cmGlobalGenerator.h b/Source/cmGlobalGenerator.h
index a865adb..92e974c 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.
@@ -676,6 +675,8 @@
   bool CheckCMP0171() const;
 
   void AddInstallScript(std::string const& file);
+  void AddTestFile(std::string const& file);
+  void AddCMakeFilesToRebuild(std::vector<std::string>& files) const;
 
 protected:
   // for a project collect all its targets by following depend
@@ -917,6 +918,7 @@
     RuntimeDependencySetsByName;
 
   std::vector<std::string> InstallScripts;
+  std::vector<std::string> TestFiles;
 
 #if !defined(CMAKE_BOOTSTRAP)
   // Pool of file locks
diff --git a/Source/cmGlobalNinjaGenerator.cxx b/Source/cmGlobalNinjaGenerator.cxx
index 17e9c44..d46ec81 100644
--- a/Source/cmGlobalNinjaGenerator.cxx
+++ b/Source/cmGlobalNinjaGenerator.cxx
@@ -3293,6 +3293,7 @@
   if (!this->DefaultFileConfig.empty()) {
     outputs.push_back(this->NinjaOutputPath(NINJA_BUILD_FILE));
   }
+  this->AddCMakeFilesToRebuild(outputs);
 }
 
 void cmGlobalNinjaMultiGenerator::GetQtAutoGenConfigs(
diff --git a/Source/cmGlobalNinjaGenerator.h b/Source/cmGlobalNinjaGenerator.h
index 69b2361..7bd7206 100644
--- a/Source/cmGlobalNinjaGenerator.h
+++ b/Source/cmGlobalNinjaGenerator.h
@@ -382,6 +382,7 @@
   virtual void AddRebuildManifestOutputs(cmNinjaDeps& outputs) const
   {
     outputs.push_back(this->NinjaOutputPath(NINJA_BUILD_FILE));
+    this->AddCMakeFilesToRebuild(outputs);
   }
 
   int GetRuleCmdLength(const std::string& name)
diff --git a/Source/cmGlobalVisualStudio10Generator.cxx b/Source/cmGlobalVisualStudio10Generator.cxx
index f9abe29..3d911bd 100644
--- a/Source/cmGlobalVisualStudio10Generator.cxx
+++ b/Source/cmGlobalVisualStudio10Generator.cxx
@@ -339,12 +339,13 @@
 bool cmGlobalVisualStudio10Generator::ParseGeneratorToolset(
   std::string const& ts, cmMakefile* mf)
 {
-  std::vector<std::string> const fields = cmTokenize(ts, ",");
-  auto fi = fields.begin();
-  if (fi == fields.end()) {
+  std::vector<std::string> const fields =
+    cmTokenize(ts, ',', cmTokenizerMode::New);
+  if (fields.empty()) {
     return true;
   }
 
+  auto fi = fields.begin();
   // The first field may be the VS platform toolset.
   if (fi->find('=') == fi->npos) {
     this->GeneratorToolset = *fi;
diff --git a/Source/cmGlobalVisualStudio8Generator.cxx b/Source/cmGlobalVisualStudio8Generator.cxx
index b1fba8f..d9accad 100644
--- a/Source/cmGlobalVisualStudio8Generator.cxx
+++ b/Source/cmGlobalVisualStudio8Generator.cxx
@@ -128,12 +128,13 @@
 {
   this->GeneratorPlatform.clear();
 
-  std::vector<std::string> const fields = cmTokenize(p, ",");
-  auto fi = fields.begin();
-  if (fi == fields.end()) {
+  std::vector<std::string> const fields =
+    cmTokenize(p, ',', cmTokenizerMode::New);
+  if (fields.empty()) {
     return true;
   }
 
+  auto fi = fields.begin();
   // The first field may be the VS platform.
   if (fi->find('=') == fi->npos) {
     this->GeneratorPlatform = *fi;
diff --git a/Source/cmGlobalVisualStudioVersionedGenerator.cxx b/Source/cmGlobalVisualStudioVersionedGenerator.cxx
index 14460fd..ba51e36 100644
--- a/Source/cmGlobalVisualStudioVersionedGenerator.cxx
+++ b/Source/cmGlobalVisualStudioVersionedGenerator.cxx
@@ -584,12 +584,13 @@
   this->GeneratorInstance.clear();
   this->GeneratorInstanceVersion.clear();
 
-  std::vector<std::string> const fields = cmTokenize(is, ",");
-  auto fi = fields.begin();
-  if (fi == fields.end()) {
+  std::vector<std::string> const fields =
+    cmTokenize(is, ',', cmTokenizerMode::New);
+  if (fields.empty()) {
     return true;
   }
 
+  auto fi = fields.begin();
   // The first field may be the VS instance.
   if (fi->find('=') == fi->npos) {
     this->GeneratorInstance = *fi;
diff --git a/Source/cmGlobalXCodeGenerator.cxx b/Source/cmGlobalXCodeGenerator.cxx
index 7fa21d0..a45e23f 100644
--- a/Source/cmGlobalXCodeGenerator.cxx
+++ b/Source/cmGlobalXCodeGenerator.cxx
@@ -27,6 +27,7 @@
 #include "cmCustomCommandTypes.h"
 #include "cmGeneratedFileStream.h"
 #include "cmGeneratorExpression.h"
+#include "cmGeneratorOptions.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGeneratorFactory.h"
 #include "cmLinkItem.h"
@@ -354,12 +355,13 @@
 bool cmGlobalXCodeGenerator::ParseGeneratorToolset(std::string const& ts,
                                                    cmMakefile* mf)
 {
-  std::vector<std::string> const fields = cmTokenize(ts, ",");
-  auto fi = fields.cbegin();
-  if (fi == fields.cend()) {
+  std::vector<std::string> const fields =
+    cmTokenize(ts, ',', cmTokenizerMode::New);
+  if (fields.empty()) {
     return true;
   }
 
+  auto fi = fields.cbegin();
   // The first field may be the Xcode GCC_VERSION.
   if (fi->find('=') == fi->npos) {
     this->GeneratorToolset = *fi;
@@ -1362,12 +1364,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;
   }
 };
@@ -2530,7 +2540,8 @@
   }
   if (!extraLinkOptionsVar.empty()) {
     this->CurrentLocalGenerator->AddConfigVariableFlags(
-      extraLinkOptions, extraLinkOptionsVar, configName);
+      extraLinkOptions, extraLinkOptionsVar, gtgt, cmBuildStep::Link, llang,
+      configName);
   }
 
   if (gtgt->GetType() == cmStateEnums::OBJECT_LIBRARY ||
@@ -2685,7 +2696,7 @@
         // in many ways as an application bundle, as far as
         // link flags go
         std::string createFlags = this->LookupFlags(
-          "CMAKE_SHARED_MODULE_CREATE_", llang, "_FLAGS", "-bundle");
+          "CMAKE_SHARED_MODULE_CREATE_", llang, "_FLAGS", gtgt, "-bundle");
         if (!createFlags.empty()) {
           extraLinkOptions += ' ';
           extraLinkOptions += createFlags;
@@ -2711,7 +2722,7 @@
                                     this->CreateString("NO"));
         // Add the flags to create an executable.
         std::string createFlags =
-          this->LookupFlags("CMAKE_", llang, "_LINK_FLAGS", "");
+          this->LookupFlags("CMAKE_", llang, "_LINK_FLAGS", gtgt, "");
         if (!createFlags.empty()) {
           extraLinkOptions += ' ';
           extraLinkOptions += createFlags;
@@ -2740,8 +2751,9 @@
                                     this->CreateString(plist));
       } else {
         // Add the flags to create a shared library.
-        std::string createFlags = this->LookupFlags(
-          "CMAKE_SHARED_LIBRARY_CREATE_", llang, "_FLAGS", "-dynamiclib");
+        std::string createFlags =
+          this->LookupFlags("CMAKE_SHARED_LIBRARY_CREATE_", llang, "_FLAGS",
+                            gtgt, "-dynamiclib");
         if (!createFlags.empty()) {
           extraLinkOptions += ' ';
           extraLinkOptions += createFlags;
@@ -2761,7 +2773,7 @@
     case cmStateEnums::EXECUTABLE: {
       // Add the flags to create an executable.
       std::string createFlags =
-        this->LookupFlags("CMAKE_", llang, "_LINK_FLAGS", "");
+        this->LookupFlags("CMAKE_", llang, "_LINK_FLAGS", gtgt, "");
       if (!createFlags.empty()) {
         extraLinkOptions += ' ';
         extraLinkOptions += createFlags;
@@ -4446,7 +4458,7 @@
   if (it != this->TargetGroup.end()) {
     tgroup = it->second;
   } else {
-    std::vector<std::string> tgt_folders = cmTokenize(target, "/");
+    std::vector<std::string> const tgt_folders = cmTokenize(target, '/');
     std::string curr_tgt_folder;
     for (std::vector<std::string>::size_type i = 0; i < tgt_folders.size();
          i++) {
@@ -4479,7 +4491,7 @@
   // It's a recursive folder structure, let's find the real parent group
   if (sg->GetFullName() != sg->GetName()) {
     std::string curr_folder = cmStrCat(target, '/');
-    for (auto const& folder : cmTokenize(sg->GetFullName(), "\\")) {
+    for (auto const& folder : cmTokenize(sg->GetFullName(), '\\')) {
       curr_folder += folder;
       auto const i_folder = this->GroupNameMap.find(curr_folder);
       // Create new folder
@@ -5199,13 +5211,17 @@
 
 std::string cmGlobalXCodeGenerator::LookupFlags(
   const std::string& varNamePrefix, const std::string& varNameLang,
-  const std::string& varNameSuffix, const std::string& default_flags)
+  const std::string& varNameSuffix, cmGeneratorTarget const* gt,
+  const std::string& default_flags)
 {
   if (!varNameLang.empty()) {
     std::string varName = cmStrCat(varNamePrefix, varNameLang, varNameSuffix);
     if (cmValue varValue = this->CurrentMakefile->GetDefinition(varName)) {
       if (!varValue->empty()) {
-        return *varValue;
+        std::string flags;
+        this->CurrentLocalGenerator->AppendFlags(
+          flags, *varValue, varName, gt, cmBuildStep::Link, varNameLang);
+        return flags;
       }
     }
   }
diff --git a/Source/cmGlobalXCodeGenerator.h b/Source/cmGlobalXCodeGenerator.h
index 14e9308..22743e5 100644
--- a/Source/cmGlobalXCodeGenerator.h
+++ b/Source/cmGlobalXCodeGenerator.h
@@ -304,6 +304,7 @@
   std::string LookupFlags(const std::string& varNamePrefix,
                           const std::string& varNameLang,
                           const std::string& varNameSuffix,
+                          cmGeneratorTarget const* gt,
                           const std::string& default_flags);
 
   class Factory;
diff --git a/Source/cmInstallScriptHandler.cxx b/Source/cmInstallScriptHandler.cxx
index 9a4e70f..a7dfb0c 100644
--- a/Source/cmInstallScriptHandler.cxx
+++ b/Source/cmInstallScriptHandler.cxx
@@ -133,7 +133,7 @@
 InstallScript::InstallScript(const std::vector<std::string>& cmd)
 {
   this->name = cmSystemTools::RelativePath(
-    cmSystemTools::GetCurrentWorkingDirectory(), cmd.back());
+    cmSystemTools::GetLogicalWorkingDirectory(), cmd.back());
   this->command = cmd;
 }
 
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/cmLoadCommandCommand.cxx b/Source/cmLoadCommandCommand.cxx
index 81edea5..44eb029 100644
--- a/Source/cmLoadCommandCommand.cxx
+++ b/Source/cmLoadCommandCommand.cxx
@@ -18,12 +18,10 @@
 #include <cstdio>
 #include <cstdlib>
 #include <cstring>
-#include <utility>
 
 #include <cm/memory>
 
 #include "cmCPluginAPI.h"
-#include "cmCommand.h"
 #include "cmDynamicLoader.h"
 #include "cmExecutionStatus.h"
 #include "cmListFileCache.h"
@@ -124,40 +122,32 @@
 };
 
 // a class for loadabple commands
-class cmLoadedCommand : public cmCommand
+class cmLoadedCommand
 {
 public:
-  cmLoadedCommand() = default;
   explicit cmLoadedCommand(CM_INIT_FUNCTION init)
     : Impl(std::make_shared<LoadedCommandImpl>(init))
   {
   }
 
-  /**
-   * This is a virtual constructor for the command.
-   */
-  std::unique_ptr<cmCommand> Clone() override
-  {
-    auto newC = cm::make_unique<cmLoadedCommand>();
-    // we must copy when we clone
-    newC->Impl = this->Impl;
-    return std::unique_ptr<cmCommand>(std::move(newC));
-  }
-
-  /**
-   * This is called when the command is first encountered in
-   * the CMakeLists.txt file.
-   */
-  bool InitialPass(std::vector<std::string> const& args,
-                   cmExecutionStatus&) override;
+  bool operator()(std::vector<cmListFileArgument> const& args,
+                  cmExecutionStatus& status) const;
 
 private:
   std::shared_ptr<LoadedCommandImpl> Impl;
 };
 
-bool cmLoadedCommand::InitialPass(std::vector<std::string> const& args,
-                                  cmExecutionStatus&)
+bool cmLoadedCommand::operator()(
+  std::vector<cmListFileArgument> const& arguments,
+  cmExecutionStatus& status) const
 {
+  cmMakefile& mf = status.GetMakefile();
+
+  std::vector<std::string> args;
+  if (!mf.ExpandArguments(arguments, args)) {
+    return true;
+  }
+
   if (!this->Impl->InitialPass) {
     return true;
   }
@@ -177,13 +167,13 @@
   for (i = 0; i < argc; ++i) {
     argv[i] = strdup(args[i].c_str());
   }
-  int result = this->Impl->DoInitialPass(this->Makefile, argc, argv);
+  int result = this->Impl->DoInitialPass(&mf, argc, argv);
   cmFreeArguments(argc, argv);
 
   if (result) {
     if (this->Impl->FinalPass) {
       auto impl = this->Impl;
-      this->Makefile->AddGeneratorAction(
+      mf.AddGeneratorAction(
         [impl](cmLocalGenerator& lg, const cmListFileBacktrace&) {
           impl->DoFinalPass(lg.GetMakefile());
         });
@@ -193,7 +183,7 @@
 
   /* Initial Pass must have failed so set the error string */
   if (this->Impl->Error) {
-    this->SetError(this->Impl->Error);
+    status.SetError(this->Impl->Error);
   }
   return false;
 }
@@ -270,9 +260,8 @@
   if (initFunction) {
     return status.GetMakefile().GetState()->AddScriptedCommand(
       args[0],
-      BT<cmState::Command>(
-        cmLegacyCommandWrapper(cm::make_unique<cmLoadedCommand>(initFunction)),
-        status.GetMakefile().GetBacktrace()),
+      BT<cmState::Command>(cmLoadedCommand(initFunction),
+                           status.GetMakefile().GetBacktrace()),
       status.GetMakefile());
   }
   status.SetError("Attempt to load command failed. "
diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx
index 42b517e..b011747 100644
--- a/Source/cmLocalGenerator.cxx
+++ b/Source/cmLocalGenerator.cxx
@@ -208,12 +208,62 @@
 }
 
 std::unique_ptr<cmRulePlaceholderExpander>
-cmLocalGenerator::CreateRulePlaceholderExpander() const
+cmLocalGenerator::CreateRulePlaceholderExpander(cmBuildStep buildStep) const
 {
   return cm::make_unique<cmRulePlaceholderExpander>(
-    this->Compilers, this->VariableMappings, this->CompilerSysroot,
+    buildStep, this->Compilers, this->VariableMappings, this->CompilerSysroot,
     this->LinkerSysroot);
 }
+std::unique_ptr<cmRulePlaceholderExpander>
+cmLocalGenerator::CreateRulePlaceholderExpander(
+  cmBuildStep buildStep, cmGeneratorTarget const* target,
+  std::string const& language)
+{
+  auto targetType = target->GetType();
+  if (buildStep == cmBuildStep::Link &&
+      (targetType == cmStateEnums::EXECUTABLE ||
+       targetType == cmStateEnums::SHARED_LIBRARY ||
+       targetType == cmStateEnums::MODULE_LIBRARY)) {
+    auto mappings = this->VariableMappings;
+    auto updateMapping = [buildStep, target, &language, &mappings,
+                          this](std::string const& variable) {
+      auto search = this->VariableMappings.find(variable);
+      if (search != this->VariableMappings.end()) {
+        std::string finalFlags;
+        this->AppendFlags(finalFlags, search->second, variable, target,
+                          buildStep, language);
+        mappings[variable] = std::move(finalFlags);
+      }
+    };
+
+    switch (targetType) {
+      // FALLTHROUGH is used because, depending of the compiler and/or
+      // platform, the wrong variable is used. For example
+      // CMAKE_SHARED_LIBRARY_CREATE_<LANG>_FLAGS is used to generate a module,
+      // and the variable CMAKE_SHARED_MODULE_CREATE_<LANG>_FLAGS is ignored.
+      case cmStateEnums::MODULE_LIBRARY:
+        updateMapping(
+          cmStrCat("CMAKE_SHARED_MODULE_CREATE_", language, "_FLAGS"));
+        CM_FALLTHROUGH;
+      case cmStateEnums::SHARED_LIBRARY:
+        updateMapping(
+          cmStrCat("CMAKE_SHARED_LIBRARY_CREATE_", language, "_FLAGS"));
+        CM_FALLTHROUGH;
+      case cmStateEnums::EXECUTABLE:
+        updateMapping(cmStrCat("CMAKE_", language, "_LINK_FLAGS"));
+        break;
+      default:
+        // no action needed
+        ;
+    }
+
+    return cm::make_unique<cmRulePlaceholderExpander>(
+      buildStep, this->Compilers, std::move(mappings), this->CompilerSysroot,
+      this->LinkerSysroot);
+  }
+
+  return this->CreateRulePlaceholderExpander(buildStep);
+}
 
 cmLocalGenerator::~cmLocalGenerator() = default;
 
@@ -316,9 +366,9 @@
   std::string file =
     cmStrCat(this->StateSnapshot.GetDirectory().GetCurrentBinary(),
              "/CTestTestfile.cmake");
+  this->GlobalGenerator->AddTestFile(file);
 
   cmGeneratedFileStream fout(file);
-  fout.SetCopyIfDifferent(true);
 
   fout << "# CMake generated Testfile for \n"
           "# Source directory: "
@@ -502,7 +552,6 @@
   file += "/cmake_install.cmake";
   this->GetGlobalGenerator()->AddInstallScript(file);
   cmGeneratedFileStream fout(file);
-  fout.SetCopyIfDifferent(true);
 
   // Write the header.
   /* clang-format off */
@@ -1495,17 +1544,16 @@
       libraryLinkVariable = "CMAKE_MODULE_LINKER_FLAGS";
       CM_FALLTHROUGH;
     case cmStateEnums::SHARED_LIBRARY: {
-      std::string sharedLibFlags;
       if (this->IsSplitSwiftBuild() || linkLanguage != "Swift") {
-        sharedLibFlags = cmStrCat(
-          this->Makefile->GetSafeDefinition(libraryLinkVariable), ' ');
-        if (!configUpper.empty()) {
-          std::string build = cmStrCat(libraryLinkVariable, '_', configUpper);
-          sharedLibFlags += this->Makefile->GetSafeDefinition(build);
-          sharedLibFlags += " ";
+        std::string libFlags;
+        this->AddConfigVariableFlags(libFlags, libraryLinkVariable, target,
+                                     cmBuildStep::Link, linkLanguage, config);
+        if (!libFlags.empty()) {
+          linkFlags.emplace_back(std::move(libFlags));
         }
       }
 
+      std::string sharedLibFlags;
       cmValue targetLinkFlags = target->GetProperty("LINK_FLAGS");
       if (targetLinkFlags) {
         sharedLibFlags += *targetLinkFlags;
@@ -1538,7 +1586,6 @@
       }
     } break;
     case cmStateEnums::EXECUTABLE: {
-      std::string exeFlags;
       if (linkLanguage.empty()) {
         cmSystemTools::Error(
           "CMake can not determine linker language for target: " +
@@ -1547,25 +1594,28 @@
       }
 
       if (linkLanguage != "Swift") {
-        exeFlags = this->Makefile->GetSafeDefinition("CMAKE_EXE_LINKER_FLAGS");
-        exeFlags += " ";
-        if (!configUpper.empty()) {
-          exeFlags += this->Makefile->GetSafeDefinition(
-            cmStrCat("CMAKE_EXE_LINKER_FLAGS_", configUpper));
-          exeFlags += " ";
+        std::string exeFlags;
+        this->AddConfigVariableFlags(exeFlags, "CMAKE_EXE_LINKER_FLAGS",
+                                     target, cmBuildStep::Link, linkLanguage,
+                                     config);
+        if (!exeFlags.empty()) {
+          linkFlags.emplace_back(std::move(exeFlags));
         }
       }
 
-      if (target->IsWin32Executable(config)) {
-        exeFlags += this->Makefile->GetSafeDefinition(
-          cmStrCat("CMAKE_", linkLanguage, "_CREATE_WIN32_EXE"));
-        exeFlags += " ";
-      } else {
-        exeFlags += this->Makefile->GetSafeDefinition(
-          cmStrCat("CMAKE_", linkLanguage, "_CREATE_CONSOLE_EXE"));
-        exeFlags += " ";
+      {
+        auto exeType = cmStrCat(
+          "CMAKE_", linkLanguage, "_CREATE_",
+          (target->IsWin32Executable(config) ? "WIN32" : "CONSOLE"), "_EXE");
+        std::string exeFlags;
+        this->AppendFlags(exeFlags, this->Makefile->GetDefinition(exeType),
+                          exeType, target, cmBuildStep::Link, linkLanguage);
+        if (!exeFlags.empty()) {
+          linkFlags.emplace_back(std::move(exeFlags));
+        }
       }
 
+      std::string exeFlags;
       if (target->IsExecutableWithExports()) {
         exeFlags += this->Makefile->GetSafeDefinition(
           cmStrCat("CMAKE_EXE_EXPORTS_", linkLanguage, "_FLAG"));
@@ -2380,7 +2430,7 @@
     dep = cmStrCat(this->GetCurrentBinaryDirectory(), '/', inName);
   }
 
-  dep = cmSystemTools::CollapseFullPath(dep, this->GetBinaryDirectory());
+  dep = cmSystemTools::CollapseFullPath(dep);
 
   return true;
 }
@@ -2621,6 +2671,19 @@
     this->AppendFlags(flags, this->Makefile->GetSafeDefinition(flagsVar));
   }
 }
+void cmLocalGenerator::AddConfigVariableFlags(std::string& flags,
+                                              const std::string& var,
+                                              cmGeneratorTarget const* target,
+                                              cmBuildStep compileOrLink,
+                                              const std::string& lang,
+                                              const std::string& config)
+{
+  std::string newFlags;
+  this->AddConfigVariableFlags(newFlags, var, config);
+  if (!newFlags.empty()) {
+    this->AppendFlags(flags, newFlags, var, target, compileOrLink, lang);
+  }
+}
 
 void cmLocalGenerator::AppendFlags(std::string& flags,
                                    const std::string& newFlags) const
@@ -2651,6 +2714,59 @@
     this->EscapeForShell(rawFlag, false, false, false, this->IsNinjaMulti()));
 }
 
+void cmLocalGenerator::AppendFlags(std::string& flags,
+                                   std::string const& newFlags,
+                                   const std::string& name,
+                                   const cmGeneratorTarget* target,
+                                   cmBuildStep compileOrLink,
+                                   const std::string& language)
+{
+  switch (target->GetPolicyStatusCMP0181()) {
+    case cmPolicies::WARN:
+      if (!this->Makefile->GetCMakeInstance()->GetIsInTryCompile() &&
+          this->Makefile->PolicyOptionalWarningEnabled(
+            "CMAKE_POLICY_WARNING_CMP0181")) {
+        this->Makefile->GetCMakeInstance()->IssueMessage(
+          MessageType::AUTHOR_WARNING,
+          cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0181),
+                   "\nSince the policy is not set, the contents of variable '",
+                   name,
+                   "' will "
+                   "be used as is."),
+          target->GetBacktrace());
+      }
+      CM_FALLTHROUGH;
+    case cmPolicies::OLD:
+      this->AppendFlags(flags, newFlags);
+      break;
+    case cmPolicies::REQUIRED_IF_USED:
+    case cmPolicies::REQUIRED_ALWAYS:
+      this->Makefile->GetCMakeInstance()->IssueMessage(
+        MessageType::FATAL_ERROR,
+        cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0181),
+        target->GetBacktrace());
+      CM_FALLTHROUGH;
+    case cmPolicies::NEW:
+      if (compileOrLink == cmBuildStep::Link) {
+        std::vector<std::string> options;
+        cmSystemTools::ParseUnixCommandLine(newFlags.c_str(), options);
+        this->SetLinkScriptShell(this->GlobalGenerator->GetUseLinkScript());
+        std::vector<BT<std::string>> optionsWithBT{ options.size() };
+        std::transform(options.cbegin(), options.cend(), optionsWithBT.begin(),
+                       [](const std::string& item) -> BT<std::string> {
+                         return BT<std::string>{ item };
+                       });
+        target->ResolveLinkerWrapper(optionsWithBT, language);
+        for (const auto& item : optionsWithBT) {
+          this->AppendFlagEscape(flags, item.Value);
+        }
+        this->SetLinkScriptShell(false);
+      } else {
+        this->AppendFlags(flags, newFlags);
+      }
+  }
+}
+
 void cmLocalGenerator::AddISPCDependencies(cmGeneratorTarget* target)
 {
   std::vector<std::string> enabledLanguages =
@@ -3087,7 +3203,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 +3227,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 +3237,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 +3281,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 +3321,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 +3339,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 +3350,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 +3376,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 +3434,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 +3457,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 +3480,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..0305947 100644
--- a/Source/cmLocalGenerator.h
+++ b/Source/cmLocalGenerator.h
@@ -19,6 +19,7 @@
 #include <cm3p/kwiml/int.h>
 
 #include "cmCustomCommandTypes.h"
+#include "cmGeneratorOptions.h"
 #include "cmGeneratorTarget.h"
 #include "cmListFileCache.h"
 #include "cmMessageType.h" // IWYU pragma: keep
@@ -46,36 +47,6 @@
 template <typename Iter>
 class cmRange;
 
-/** Flag if byproducts shall also be considered.  */
-enum class cmSourceOutputKind
-{
-  OutputOnly,
-  OutputOrByproduct
-};
-
-/** What scanner to use for dependencies lookup.  */
-enum class cmDependencyScannerKind
-{
-  CMake,
-  Compiler
-};
-
-/** What to compute language flags for */
-enum class cmBuildStep
-{
-  Compile,
-  Link
-};
-
-/** What compilation mode the swift files are in */
-enum class cmSwiftCompileMode
-{
-  Wholemodule,
-  Incremental,
-  Singlefile,
-  Unknown,
-};
-
 /** Target and source file which have a specific output.  */
 struct cmSourcesWithOutput
 {
@@ -147,7 +118,12 @@
   }
 
   virtual std::unique_ptr<cmRulePlaceholderExpander>
-  CreateRulePlaceholderExpander() const;
+  CreateRulePlaceholderExpander(
+    cmBuildStep buildStep = cmBuildStep::Compile) const;
+  virtual std::unique_ptr<cmRulePlaceholderExpander>
+  CreateRulePlaceholderExpander(cmBuildStep buildStep,
+                                cmGeneratorTarget const* target,
+                                std::string const& language);
 
   std::string GetLinkLibsCMP0065(std::string const& linkLanguage,
                                  cmGeneratorTarget& tgt) const;
@@ -174,6 +150,12 @@
                                 const std::string& lang);
   void AddConfigVariableFlags(std::string& flags, const std::string& var,
                               const std::string& config);
+  // Handle prefixes processing (like LINKER:)
+  void AddConfigVariableFlags(std::string& flags, const std::string& var,
+                              cmGeneratorTarget const* target,
+                              cmBuildStep compileOrLink,
+                              const std::string& lang,
+                              const std::string& config);
   void AddColorDiagnosticsFlags(std::string& flags, const std::string& lang);
   //! Append flags to a string.
   virtual void AppendFlags(std::string& flags,
@@ -182,6 +164,13 @@
                            const std::vector<BT<std::string>>& newFlags) const;
   virtual void AppendFlagEscape(std::string& flags,
                                 const std::string& rawFlag) const;
+  /**
+   * Append flags after parsing, prefixes processing (like LINKER:) and
+   * escaping
+   */
+  void AppendFlags(std::string& flags, std::string const& newFlags,
+                   const std::string& name, const cmGeneratorTarget* target,
+                   cmBuildStep compileOrLink, const std::string& lang);
   void AddISPCDependencies(cmGeneratorTarget* target);
   void AddPchDependencies(cmGeneratorTarget* target);
   void AddUnityBuild(cmGeneratorTarget* target);
@@ -699,28 +688,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/cmLocalNinjaGenerator.cxx b/Source/cmLocalNinjaGenerator.cxx
index 46a95af..a566a46 100644
--- a/Source/cmLocalNinjaGenerator.cxx
+++ b/Source/cmLocalNinjaGenerator.cxx
@@ -50,9 +50,20 @@
 // Virtual public methods.
 
 std::unique_ptr<cmRulePlaceholderExpander>
-cmLocalNinjaGenerator::CreateRulePlaceholderExpander() const
+cmLocalNinjaGenerator::CreateRulePlaceholderExpander(
+  cmBuildStep buildStep) const
 {
-  auto ret = this->cmLocalGenerator::CreateRulePlaceholderExpander();
+  auto ret = this->cmLocalGenerator::CreateRulePlaceholderExpander(buildStep);
+  ret->SetTargetImpLib("$TARGET_IMPLIB");
+  return std::unique_ptr<cmRulePlaceholderExpander>(std::move(ret));
+}
+std::unique_ptr<cmRulePlaceholderExpander>
+cmLocalNinjaGenerator::CreateRulePlaceholderExpander(
+  cmBuildStep buildStep, cmGeneratorTarget const* target,
+  std::string const& language)
+{
+  auto ret = this->cmLocalGenerator::CreateRulePlaceholderExpander(
+    buildStep, target, language);
   ret->SetTargetImpLib("$TARGET_IMPLIB");
   return std::unique_ptr<cmRulePlaceholderExpander>(std::move(ret));
 }
diff --git a/Source/cmLocalNinjaGenerator.h b/Source/cmLocalNinjaGenerator.h
index 74b8b3b..1abc410 100644
--- a/Source/cmLocalNinjaGenerator.h
+++ b/Source/cmLocalNinjaGenerator.h
@@ -11,6 +11,7 @@
 #include <string>
 #include <vector>
 
+#include "cmGeneratorOptions.h"
 #include "cmLocalCommonGenerator.h"
 #include "cmNinjaTypes.h"
 #include "cmOutputConverter.h"
@@ -42,8 +43,11 @@
 
   void Generate() override;
 
-  std::unique_ptr<cmRulePlaceholderExpander> CreateRulePlaceholderExpander()
-    const override;
+  std::unique_ptr<cmRulePlaceholderExpander> CreateRulePlaceholderExpander(
+    cmBuildStep buildStep = cmBuildStep::Compile) const override;
+  std::unique_ptr<cmRulePlaceholderExpander> CreateRulePlaceholderExpander(
+    cmBuildStep buildStep, cmGeneratorTarget const* target,
+    std::string const& language) override;
 
   std::string GetTargetDirectory(
     cmGeneratorTarget const* target) const override;
diff --git a/Source/cmLocalUnixMakefileGenerator3.h b/Source/cmLocalUnixMakefileGenerator3.h
index 7d5a922..6db410b 100644
--- a/Source/cmLocalUnixMakefileGenerator3.h
+++ b/Source/cmLocalUnixMakefileGenerator3.h
@@ -12,8 +12,8 @@
 #include <vector>
 
 #include "cmDepends.h"
+#include "cmGeneratorOptions.h"
 #include "cmLocalCommonGenerator.h"
-#include "cmLocalGenerator.h"
 
 class cmCustomCommand;
 class cmCustomCommandGenerator;
diff --git a/Source/cmLocalVisualStudio7Generator.cxx b/Source/cmLocalVisualStudio7Generator.cxx
index 8be21c2..97df2a5 100644
--- a/Source/cmLocalVisualStudio7Generator.cxx
+++ b/Source/cmLocalVisualStudio7Generator.cxx
@@ -25,6 +25,7 @@
 #include "cmCustomCommandLines.h"
 #include "cmGeneratedFileStream.h"
 #include "cmGeneratorExpression.h"
+#include "cmGeneratorOptions.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
 #include "cmGlobalVisualStudio7Generator.h"
@@ -664,16 +665,16 @@
   }
   std::string flags;
   std::string langForClCompile;
+  const std::string& linkLanguage =
+    (this->FortranProject ? std::string("Fortran")
+                          : target->GetLinkerLanguage(configName));
+  if (linkLanguage.empty()) {
+    cmSystemTools::Error(
+      cmStrCat("CMake can not determine linker language for target: ",
+               target->GetName()));
+    return;
+  }
   if (target->GetType() <= cmStateEnums::OBJECT_LIBRARY) {
-    const std::string& linkLanguage =
-      (this->FortranProject ? std::string("Fortran")
-                            : target->GetLinkerLanguage(configName));
-    if (linkLanguage.empty()) {
-      cmSystemTools::Error(
-        cmStrCat("CMake can not determine linker language for target: ",
-                 target->GetName()));
-      return;
-    }
     langForClCompile = linkLanguage;
     if (langForClCompile == "C" || langForClCompile == "CXX" ||
         langForClCompile == "Fortran") {
@@ -957,26 +958,14 @@
   }
 
   this->OutputTargetRules(fout, configName, target, libName);
-  this->OutputBuildTool(fout, configName, target, targetOptions);
+  this->OutputBuildTool(fout, linkLanguage, configName, target, targetOptions);
   this->OutputDeploymentDebuggerTool(fout, configName, target);
   fout << "\t\t</Configuration>\n";
 }
 
-std::string cmLocalVisualStudio7Generator::GetBuildTypeLinkerFlags(
-  std::string const& rootLinkerFlags, const std::string& configName)
-{
-  std::string configTypeUpper = cmSystemTools::UpperCase(configName);
-  std::string extraLinkOptionsBuildTypeDef =
-    cmStrCat(rootLinkerFlags, '_', configTypeUpper);
-
-  const std::string& extraLinkOptionsBuildType =
-    this->Makefile->GetRequiredDefinition(extraLinkOptionsBuildTypeDef);
-
-  return extraLinkOptionsBuildType;
-}
-
 void cmLocalVisualStudio7Generator::OutputBuildTool(
-  std::ostream& fout, const std::string& configName, cmGeneratorTarget* target,
+  std::ostream& fout, const std::string& linkLanguage,
+  const std::string& configName, cmGeneratorTarget* target,
   const Options& targetOptions)
 {
   cmGlobalVisualStudio7Generator* gg =
@@ -984,19 +973,19 @@
   std::string temp;
   std::string extraLinkOptions;
   if (target->GetType() == cmStateEnums::EXECUTABLE) {
-    extraLinkOptions = cmStrCat(
-      this->Makefile->GetRequiredDefinition("CMAKE_EXE_LINKER_FLAGS"), ' ',
-      GetBuildTypeLinkerFlags("CMAKE_EXE_LINKER_FLAGS", configName));
+    this->AddConfigVariableFlags(extraLinkOptions, "CMAKE_EXE_LINKER_FLAGS",
+                                 target, cmBuildStep::Link, linkLanguage,
+                                 configName);
   }
   if (target->GetType() == cmStateEnums::SHARED_LIBRARY) {
-    extraLinkOptions = cmStrCat(
-      this->Makefile->GetRequiredDefinition("CMAKE_SHARED_LINKER_FLAGS"), ' ',
-      GetBuildTypeLinkerFlags("CMAKE_SHARED_LINKER_FLAGS", configName));
+    this->AddConfigVariableFlags(extraLinkOptions, "CMAKE_SHARED_LINKER_FLAGS",
+                                 target, cmBuildStep::Link, linkLanguage,
+                                 configName);
   }
   if (target->GetType() == cmStateEnums::MODULE_LIBRARY) {
-    extraLinkOptions = cmStrCat(
-      this->Makefile->GetRequiredDefinition("CMAKE_MODULE_LINKER_FLAGS"), ' ',
-      GetBuildTypeLinkerFlags("CMAKE_MODULE_LINKER_FLAGS", configName));
+    this->AddConfigVariableFlags(extraLinkOptions, "CMAKE_MODULE_LINKER_FLAGS",
+                                 target, cmBuildStep::Link, linkLanguage,
+                                 configName);
   }
 
   cmValue targetLinkFlags = target->GetProperty("LINK_FLAGS");
@@ -1089,7 +1078,6 @@
         return;
       }
       cmComputeLinkInformation& cli = *pcli;
-      std::string linkLanguage = cli.GetLinkLanguage();
 
       if (!target->GetLinkerTypeProperty(linkLanguage, configName).empty()) {
         // Visual Studio 10 or upper is required for this feature
@@ -1173,7 +1161,6 @@
         return;
       }
       cmComputeLinkInformation& cli = *pcli;
-      std::string linkLanguage = cli.GetLinkLanguage();
 
       if (!target->GetLinkerTypeProperty(linkLanguage, configName).empty()) {
         // Visual Studio 10 or upper is required for this feature
diff --git a/Source/cmLocalVisualStudio7Generator.h b/Source/cmLocalVisualStudio7Generator.h
index 7c395d6..baf85fb 100644
--- a/Source/cmLocalVisualStudio7Generator.h
+++ b/Source/cmLocalVisualStudio7Generator.h
@@ -99,8 +99,6 @@
 private:
   using Options = cmVS7GeneratorOptions;
   using FCInfo = cmLocalVisualStudio7GeneratorFCInfo;
-  std::string GetBuildTypeLinkerFlags(std::string const& rootLinkerFlags,
-                                      const std::string& configName);
   void FixGlobalTargets();
   void WriteVCProjHeader(std::ostream& fout, const std::string& libName,
                          cmGeneratorTarget* tgt,
@@ -119,8 +117,9 @@
   void OutputTargetRules(std::ostream& fout, const std::string& configName,
                          cmGeneratorTarget* target,
                          const std::string& libName);
-  void OutputBuildTool(std::ostream& fout, const std::string& configName,
-                       cmGeneratorTarget* t, const Options& targetOptions);
+  void OutputBuildTool(std::ostream& fout, const std::string& linkLanguage,
+                       const std::string& configName, cmGeneratorTarget* t,
+                       const Options& targetOptions);
   void OutputDeploymentDebuggerTool(std::ostream& fout,
                                     std::string const& config,
                                     cmGeneratorTarget* target);
diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx
index 98da10d..c6a831b 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();
@@ -2387,13 +2387,9 @@
 
 cmSourceGroup* cmMakefile::GetOrCreateSourceGroup(const std::string& name)
 {
-  std::string delimiters;
-  if (cmValue p = this->GetDefinition("SOURCE_GROUP_DELIMITER")) {
-    delimiters = *p;
-  } else {
-    delimiters = "/\\";
-  }
-  return this->GetOrCreateSourceGroup(cmTokenize(name, delimiters));
+  auto p = this->GetDefinition("SOURCE_GROUP_DELIMITER");
+  return this->GetOrCreateSourceGroup(
+    cmTokenize(name, p ? cm::string_view(*p) : R"(\/)"_s));
 }
 
 /**
@@ -3722,10 +3718,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 +4638,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/cmMakefileExecutableTargetGenerator.cxx b/Source/cmMakefileExecutableTargetGenerator.cxx
index 96a0d5c..e24f2b7 100644
--- a/Source/cmMakefileExecutableTargetGenerator.cxx
+++ b/Source/cmMakefileExecutableTargetGenerator.cxx
@@ -12,6 +12,7 @@
 #include <cmext/algorithm>
 
 #include "cmGeneratedFileStream.h"
+#include "cmGeneratorOptions.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalUnixMakefileGenerator3.h"
 #include "cmLinkLineComputer.h"
@@ -233,7 +234,8 @@
     }
 
     auto rulePlaceholderExpander =
-      this->LocalGenerator->CreateRulePlaceholderExpander();
+      this->LocalGenerator->CreateRulePlaceholderExpander(
+        cmBuildStep::Link, this->GeneratorTarget, linkLanguage);
 
     // Expand placeholders in the commands.
     rulePlaceholderExpander->SetTargetImpLib(targetOutput);
@@ -370,19 +372,20 @@
 
   // Add flags to create an executable.
   this->LocalGenerator->AddConfigVariableFlags(
-    linkFlags, "CMAKE_EXE_LINKER_FLAGS", this->GetConfigName());
+    linkFlags, "CMAKE_EXE_LINKER_FLAGS", this->GeneratorTarget,
+    cmBuildStep::Link, linkLanguage, this->GetConfigName());
 
-  if (this->GeneratorTarget->IsWin32Executable(
-        this->Makefile->GetSafeDefinition("CMAKE_BUILD_TYPE"))) {
+  {
+    auto exeType =
+      cmStrCat("CMAKE_", linkLanguage, "_CREATE_",
+               (this->GeneratorTarget->IsWin32Executable(
+                  this->Makefile->GetDefinition("CMAKE_BUILD_TYPE"))
+                  ? "WIN32"
+                  : "CONSOLE"),
+               "_EXE");
     this->LocalGenerator->AppendFlags(
-      linkFlags,
-      this->Makefile->GetSafeDefinition(
-        cmStrCat("CMAKE_", linkLanguage, "_CREATE_WIN32_EXE")));
-  } else {
-    this->LocalGenerator->AppendFlags(
-      linkFlags,
-      this->Makefile->GetSafeDefinition(
-        cmStrCat("CMAKE_", linkLanguage, "_CREATE_CONSOLE_EXE")));
+      linkFlags, this->Makefile->GetDefinition(exeType), exeType,
+      this->GeneratorTarget, cmBuildStep::Link, linkLanguage);
   }
 
   // Add symbol export flags if necessary.
@@ -602,7 +605,8 @@
     }
 
     auto rulePlaceholderExpander =
-      this->LocalGenerator->CreateRulePlaceholderExpander();
+      this->LocalGenerator->CreateRulePlaceholderExpander(
+        cmBuildStep::Link, this->GeneratorTarget, linkLanguage);
 
     // Expand placeholders in the commands.
     rulePlaceholderExpander->SetTargetImpLib(targetOutPathImport);
diff --git a/Source/cmMakefileLibraryTargetGenerator.cxx b/Source/cmMakefileLibraryTargetGenerator.cxx
index 225e63b..746ee50 100644
--- a/Source/cmMakefileLibraryTargetGenerator.cxx
+++ b/Source/cmMakefileLibraryTargetGenerator.cxx
@@ -12,6 +12,7 @@
 #include <cmext/algorithm>
 
 #include "cmGeneratedFileStream.h"
+#include "cmGeneratorOptions.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalUnixMakefileGenerator3.h"
 #include "cmLinkLineComputer.h"
@@ -177,7 +178,8 @@
   std::string extraFlags;
   this->GetTargetLinkFlags(extraFlags, linkLanguage);
   this->LocalGenerator->AddConfigVariableFlags(
-    extraFlags, "CMAKE_SHARED_LINKER_FLAGS", this->GetConfigName());
+    extraFlags, "CMAKE_SHARED_LINKER_FLAGS", this->GeneratorTarget,
+    cmBuildStep::Link, linkLanguage, this->GetConfigName());
 
   std::unique_ptr<cmLinkLineComputer> linkLineComputer =
     this->CreateLinkLineComputer(
@@ -212,7 +214,8 @@
   std::string extraFlags;
   this->GetTargetLinkFlags(extraFlags, linkLanguage);
   this->LocalGenerator->AddConfigVariableFlags(
-    extraFlags, "CMAKE_MODULE_LINKER_FLAGS", this->GetConfigName());
+    extraFlags, "CMAKE_MODULE_LINKER_FLAGS", this->GeneratorTarget,
+    cmBuildStep::Link, linkLanguage, this->GetConfigName());
 
   std::unique_ptr<cmLinkLineComputer> linkLineComputer =
     this->CreateLinkLineComputer(
@@ -239,7 +242,8 @@
   std::string extraFlags;
   this->GetTargetLinkFlags(extraFlags, linkLanguage);
   this->LocalGenerator->AddConfigVariableFlags(
-    extraFlags, "CMAKE_MACOSX_FRAMEWORK_LINKER_FLAGS", this->GetConfigName());
+    extraFlags, "CMAKE_MACOSX_FRAMEWORK_LINKER_FLAGS", this->GeneratorTarget,
+    cmBuildStep::Link, linkLanguage, this->GetConfigName());
 
   this->WriteLibraryRules(linkRuleVar, extraFlags, relink);
 }
@@ -382,7 +386,8 @@
     }
 
     auto rulePlaceholderExpander =
-      this->LocalGenerator->CreateRulePlaceholderExpander();
+      this->LocalGenerator->CreateRulePlaceholderExpander(
+        cmBuildStep::Link, this->GeneratorTarget, linkLanguage);
 
     // Construct the main link rule and expand placeholders.
     rulePlaceholderExpander->SetTargetImpLib(targetOutput);
@@ -707,7 +712,8 @@
 
   // Expand the rule variables.
   auto rulePlaceholderExpander =
-    this->LocalGenerator->CreateRulePlaceholderExpander();
+    this->LocalGenerator->CreateRulePlaceholderExpander(
+      cmBuildStep::Link, this->GeneratorTarget, linkLanguage);
   bool useWatcomQuote =
     this->Makefile->IsOn(linkRuleVar + "_USE_WATCOM_QUOTE");
   cmList real_link_commands;
diff --git a/Source/cmMakefileTargetGenerator.cxx b/Source/cmMakefileTargetGenerator.cxx
index 9d0d466..e27e7fd 100644
--- a/Source/cmMakefileTargetGenerator.cxx
+++ b/Source/cmMakefileTargetGenerator.cxx
@@ -25,6 +25,7 @@
 #include "cmFileSet.h"
 #include "cmGeneratedFileStream.h"
 #include "cmGeneratorExpression.h"
+#include "cmGeneratorOptions.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalUnixMakefileGenerator3.h"
 #include "cmLinkLineComputer.h" // IWYU pragma: keep
@@ -683,9 +684,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 +1559,7 @@
               cmOutputConverter::SHELL)
          << " "
          << this->LocalGenerator->ConvertToOutputFormat(
-              cmSystemTools::CollapseFullPath(this->InfoFileNameFull),
-              cmOutputConverter::SHELL);
+              this->InfoFileNameFull, cmOutputConverter::SHELL);
   if (this->LocalGenerator->GetColorMakefile()) {
     depCmd << " \"--color=$(COLOR)\"";
   }
@@ -1710,7 +1708,8 @@
   vars.Flags = flags.c_str();
 
   std::string compileCmd = this->GetLinkRule("CMAKE_CUDA_DEVICE_LINK_COMPILE");
-  auto rulePlaceholderExpander = localGen->CreateRulePlaceholderExpander();
+  auto rulePlaceholderExpander = localGen->CreateRulePlaceholderExpander(
+    cmBuildStep::Link, this->GetGeneratorTarget(), "CUDA");
   rulePlaceholderExpander->ExpandRuleVariables(localGen, compileCmd, vars);
 
   commands.emplace_back(compileCmd);
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/cmNinjaNormalTargetGenerator.cxx b/Source/cmNinjaNormalTargetGenerator.cxx
index 891187a..ff514ff 100644
--- a/Source/cmNinjaNormalTargetGenerator.cxx
+++ b/Source/cmNinjaNormalTargetGenerator.cxx
@@ -18,6 +18,7 @@
 #include "cmCustomCommand.h" // IWYU pragma: keep
 #include "cmCustomCommandGenerator.h"
 #include "cmGeneratedFileStream.h"
+#include "cmGeneratorOptions.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalNinjaGenerator.h"
 #include "cmLinkLineComputer.h"
@@ -350,7 +351,9 @@
     }
 
     auto rulePlaceholderExpander =
-      this->GetLocalGenerator()->CreateRulePlaceholderExpander();
+      this->GetLocalGenerator()->CreateRulePlaceholderExpander(
+        cmBuildStep::Link, this->GetGeneratorTarget(),
+        this->TargetLinkLanguage(config));
 
     // Rule for linking library/executable.
     std::vector<std::string> linkCmds = this->ComputeDeviceLinkCmd();
@@ -413,7 +416,9 @@
   std::string compileCmd = this->GetMakefile()->GetRequiredDefinition(
     "CMAKE_CUDA_DEVICE_LINK_COMPILE");
   auto rulePlaceholderExpander =
-    this->GetLocalGenerator()->CreateRulePlaceholderExpander();
+    this->GetLocalGenerator()->CreateRulePlaceholderExpander(
+      cmBuildStep::Link, this->GetGeneratorTarget(),
+      this->TargetLinkLanguage(config));
   rulePlaceholderExpander->ExpandRuleVariables(this->GetLocalGenerator(),
                                                compileCmd, vars);
 
@@ -566,7 +571,9 @@
     }
 
     auto rulePlaceholderExpander =
-      this->GetLocalGenerator()->CreateRulePlaceholderExpander();
+      this->GetLocalGenerator()->CreateRulePlaceholderExpander(
+        cmBuildStep::Link, this->GetGeneratorTarget(),
+        this->TargetLinkLanguage(config));
 
     // Rule for linking library/executable.
     std::vector<std::string> linkCmds = this->ComputeLinkCmd(config);
diff --git a/Source/cmNinjaTargetGenerator.cxx b/Source/cmNinjaTargetGenerator.cxx
index 720020d..144fa35 100644
--- a/Source/cmNinjaTargetGenerator.cxx
+++ b/Source/cmNinjaTargetGenerator.cxx
@@ -29,6 +29,7 @@
 #include "cmFileSet.h"
 #include "cmGeneratedFileStream.h"
 #include "cmGeneratorExpression.h"
+#include "cmGeneratorOptions.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalCommonGenerator.h"
 #include "cmGlobalNinjaGenerator.h"
@@ -1289,11 +1290,9 @@
     if (cmValue name = target->GetProperty("Swift_DEPENDENCIES_FILE")) {
       return *name;
     }
-    return this->GetLocalGenerator()->ConvertToOutputFormat(
-      this->ConvertToNinjaPath(cmStrCat(target->GetSupportDirectory(), '/',
-                                        config, '/', target->GetName(),
-                                        ".swiftdeps")),
-      cmOutputConverter::SHELL);
+    return this->ConvertToNinjaPath(cmStrCat(target->GetSupportDirectory(),
+                                             '/', config, '/',
+                                             target->GetName(), ".swiftdeps"));
   }();
 
   std::string mapFilePath =
@@ -1311,7 +1310,7 @@
 
   // Add flag
   this->LocalGenerator->AppendFlags(flags, "-output-file-map");
-  this->LocalGenerator->AppendFlagEscape(
+  this->LocalGenerator->AppendFlags(
     flags,
     this->GetLocalGenerator()->ConvertToOutputFormat(
       ConvertToNinjaPath(mapFilePath), cmOutputConverter::SHELL));
@@ -2014,7 +2013,10 @@
     std::string const emitModuleFlag = "-emit-module";
     std::string const modulePathFlag = "-emit-module-path";
     this->LocalGenerator->AppendFlags(
-      vars["FLAGS"], { emitModuleFlag, modulePathFlag, moduleFilepath });
+      vars["FLAGS"],
+      { emitModuleFlag, modulePathFlag,
+        this->LocalGenerator->ConvertToOutputFormat(
+          moduleFilepath, cmOutputConverter::SHELL) });
     objBuild.Outputs.push_back(moduleFilepath);
   }
   this->LocalGenerator->AppendFlags(vars["FLAGS"],
@@ -2273,7 +2275,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/cmPathResolver.cxx b/Source/cmPathResolver.cxx
new file mode 100644
index 0000000..93457a5
--- /dev/null
+++ b/Source/cmPathResolver.cxx
@@ -0,0 +1,541 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmPathResolver.h"
+
+#include <algorithm>
+#include <cerrno>
+#include <cstddef>
+#include <string>
+#include <utility>
+
+#include <cm/optional>
+#include <cm/string_view>
+#include <cmext/string_view>
+
+#ifdef _WIN32
+#  include <cctype>
+
+#  include <windows.h>
+#endif
+
+#define MAX_SYMBOLIC_LINKS 32
+
+namespace cm {
+namespace PathResolver {
+
+namespace {
+
+namespace Options {
+
+enum class ActualCase
+{
+  No,
+  Yes,
+};
+
+enum class Symlinks
+{
+  None,
+  Lazy,
+  Eager,
+};
+
+enum class Existence
+{
+  Agnostic,
+  Required,
+};
+}
+
+enum class Root
+{
+  None,
+  POSIX,
+#ifdef _WIN32
+  Drive,
+  Network,
+#endif
+};
+
+struct Control
+{
+  enum class Tag
+  {
+    Continue,
+    Restart,
+    Error,
+  };
+  Tag tag;
+  union
+  {
+    std::string::size_type slash; // data for Continue
+    cmsys::Status error;          // data for Error
+  };
+  static Control Continue(std::string::size_type s)
+  {
+    Control c{ Tag::Continue };
+    c.slash = s;
+    return c;
+  }
+  static Control Restart() { return Control{ Tag::Restart }; }
+  static Control Error(cmsys::Status e)
+  {
+    Control c{ Tag::Error };
+    c.error = e;
+    return c;
+  }
+
+private:
+  Control(Tag t)
+    : tag(t)
+  {
+  }
+};
+
+Root ClassifyRoot(cm::string_view p)
+{
+#ifdef _WIN32
+  if (p.size() >= 2 && std::isalpha(p[0]) && p[1] == ':') {
+    return Root::Drive;
+  }
+  if (p.size() >= 3 && p[0] == '/' && p[1] == '/' && p[2] != '/') {
+    return Root::Network;
+  }
+#endif
+  if (!p.empty() && p[0] == '/') {
+    return Root::POSIX;
+  }
+  return Root::None;
+}
+
+class ImplBase
+{
+protected:
+  ImplBase(System& os)
+    : OS(os)
+  {
+  }
+
+  System& OS;
+  std::string P;
+  std::size_t SymlinkDepth = 0;
+
+#ifdef _WIN32
+  std::string GetWorkingDirectoryOnDrive(char letter);
+  Control ResolveRootRelative();
+#endif
+  cm::optional<std::string> ReadSymlink(std::string const& path,
+                                        cmsys::Status& status);
+  Control ResolveSymlink(Root root, std::string::size_type slash,
+                         std::string::size_type next_slash,
+                         std::string symlink_target);
+};
+
+template <class Policy>
+class Impl : public ImplBase
+{
+  Control ResolveRelativePath();
+  Control ResolveRoot(Root root);
+  Control ResolveComponent(Root root, std::string::size_type root_slash,
+                           std::string::size_type slash);
+  Control ResolvePath();
+
+public:
+  Impl(System& os)
+    : ImplBase(os)
+  {
+  }
+  cmsys::Status Resolve(std::string in, std::string& out);
+};
+
+template <class Policy>
+Control Impl<Policy>::ResolveRelativePath()
+{
+  // This is a relative path.  Convert it to absolute and restart.
+  std::string p = this->OS.GetWorkingDirectory();
+  std::replace(p.begin(), p.end(), '\\', '/');
+  if (ClassifyRoot(p) == Root::None) {
+    p.insert(0, 1, '/');
+  }
+  if (p.back() != '/') {
+    p.push_back('/');
+  }
+  P.insert(0, p);
+  return Control::Restart();
+}
+
+#ifdef _WIN32
+std::string ImplBase::GetWorkingDirectoryOnDrive(char letter)
+{
+  // Use the drive's working directory, if any.
+  std::string d = this->OS.GetWorkingDirectoryOnDrive(letter);
+  std::replace(d.begin(), d.end(), '\\', '/');
+  if (d.size() >= 3 && std::toupper(d[0]) == std::toupper(letter) &&
+      d[1] == ':' && d[2] == '/') {
+    d[0] = letter;
+    d.push_back('/');
+    return d;
+  }
+
+  // Use the current working directory if the drive matches.
+  d = this->OS.GetWorkingDirectory();
+  if (d.size() >= 3 && std::toupper(d[0]) == std::toupper(letter) &&
+      d[1] == ':' && d[2] == '/') {
+    d[0] = letter;
+    d.push_back('/');
+    return d;
+  }
+
+  // Fall back to the root directory on the drive.
+  d = "_:/";
+  d[0] = letter;
+  return d;
+}
+
+Control ImplBase::ResolveRootRelative()
+{
+  // This is a root-relative path.  Resolve the root drive and restart.
+  P.replace(0, 2, this->GetWorkingDirectoryOnDrive(P[0]));
+  return Control::Restart();
+}
+#endif
+
+cm::optional<std::string> ImplBase::ReadSymlink(std::string const& path,
+                                                cmsys::Status& status)
+{
+  cm::optional<std::string> result;
+  std::string target;
+  status = this->OS.ReadSymlink(path, target);
+  if (status && ++this->SymlinkDepth >= MAX_SYMBOLIC_LINKS) {
+    status = cmsys::Status::POSIX(ELOOP);
+  }
+  if (status) {
+    if (!target.empty()) {
+      result = std::move(target);
+    }
+  } else if (status.GetPOSIX() == EINVAL
+#ifdef _WIN32
+             || status.GetWindows() == ERROR_NOT_A_REPARSE_POINT
+#endif
+  ) {
+    // The path was not a symlink.
+    status = cmsys::Status::Success();
+  }
+  return result;
+}
+
+Control ImplBase::ResolveSymlink(Root root, std::string::size_type slash,
+                                 std::string::size_type next_slash,
+                                 std::string symlink_target)
+{
+  std::replace(symlink_target.begin(), symlink_target.end(), '\\', '/');
+  Root const symlink_target_root = ClassifyRoot(symlink_target);
+  if (symlink_target_root == Root::None) {
+    // This is a symlink to a relative path.
+    // Resolve the symlink, while preserving the leading and
+    // trailing (if any) slash:
+    //   "*/link/" => "*/dest/"
+    //     ^slash       ^slash
+    P.replace(slash + 1, next_slash - slash - 1, symlink_target);
+    return Control::Continue(slash);
+  }
+
+#ifdef _WIN32
+  if (root == Root::Drive && symlink_target_root == Root::POSIX) {
+    // This is a symlink to a POSIX absolute path,
+    // but the current path is on a drive letter.  Resolve the
+    // symlink while preserving the drive letter, and start over:
+    //   "C:/*/link/" => "C:/dest/"
+    //        ^slash      (restart)
+    P.replace(2, next_slash - 2, symlink_target);
+    return Control::Restart();
+  }
+#else
+  static_cast<void>(root);
+#endif
+
+  // This is a symlink to an absolute path.
+  // Resolve it and start over:
+  //   "*/link/" => "/dest/"
+  //     ^slash      (restart)
+  P.replace(0, next_slash, symlink_target);
+  return Control::Restart();
+}
+
+template <class Policy>
+Control Impl<Policy>::ResolveRoot(Root root)
+{
+  if (root == Root::None) {
+    return this->ResolveRelativePath();
+  }
+
+  // POSIX absolute paths always start with a '/'.
+  std::string::size_type root_slash = 0;
+
+#ifdef _WIN32
+  if (root == Root::Drive) {
+    if (P.size() == 2 || P[2] != '/') {
+      return this->ResolveRootRelative();
+    }
+
+    if (Policy::ActualCase == Options::ActualCase::Yes) {
+      // Normalize the drive letter to upper-case.
+      P[0] = static_cast<char>(std::toupper(P[0]));
+    }
+
+    // The root is a drive letter.  The root '/' immediately follows.
+    root_slash = 2;
+  } else if (root == Root::Network) {
+    // The root is a network name.  Find the root '/' after it.
+    root_slash = P.find('/', 2);
+    if (root_slash == std::string::npos) {
+      root_slash = P.size();
+      P.push_back('/');
+    }
+  }
+#endif
+
+  if (Policy::Existence == Options::Existence::Required
+#ifdef _WIN32
+      && root != Root::Network
+#endif
+  ) {
+    std::string path = P.substr(0, root_slash + 1);
+    if (!this->OS.PathExists(path)) {
+      P = std::move(path);
+      return Control::Error(cmsys::Status::POSIX(ENOENT));
+    }
+  }
+
+  return Control::Continue(root_slash);
+}
+
+template <class Policy>
+Control Impl<Policy>::ResolveComponent(Root root,
+                                       std::string::size_type root_slash,
+                                       std::string::size_type slash)
+{
+  // Look for the '/' or end-of-input that ends this component.
+  // The sample paths in comments below show the trailing slash
+  // even if it is actually beyond the end of the path.
+  std::string::size_type next_slash = P.find('/', slash + 1);
+  if (next_slash == std::string::npos) {
+    next_slash = P.size();
+  }
+  cm::string_view c =
+    cm::string_view(P).substr(slash + 1, next_slash - (slash + 1));
+
+  if (slash == root_slash) {
+    if (c.empty() || c == "."_s || c == ".."_s) {
+      // This is an empty, '.', or '..' component at the root.
+      // Drop the component and its trailing slash, if any,
+      // while preserving the root slash:
+      //   "//"   => "/"
+      //   "/./"  => "/"
+      //   "/../" => "/"
+      //    ^slash    ^slash
+      P.erase(slash + 1, next_slash - slash);
+      return Control::Continue(slash);
+    }
+  } else {
+    if (c.empty() || c == "."_s) {
+      // This is an empty or '.' component not at the root.
+      // Drop the component and its leading slash:
+      //   "*//"  => "*/"
+      //   "*/./" => "*/"
+      //     ^slash    ^slash
+      P.erase(slash, next_slash - slash);
+      return Control::Continue(slash);
+    }
+
+    if (c == ".."_s) {
+      // This is a '..' component not at the root.
+      // Rewind to the previous component:
+      //   "*/prev/../" => "*/prev/../"
+      //          ^slash     ^slash
+      next_slash = slash;
+      slash = P.rfind('/', slash - 1);
+
+      if (Policy::Symlinks == Options::Symlinks::Lazy) {
+        cmsys::Status status;
+        std::string path = P.substr(0, next_slash);
+        if (cm::optional<std::string> maybe_symlink_target =
+              this->ReadSymlink(path, status)) {
+          return this->ResolveSymlink(root, slash, next_slash,
+                                      std::move(*maybe_symlink_target));
+        }
+        if (!status && Policy::Existence == Options::Existence::Required) {
+          P = std::move(path);
+          return Control::Error(status);
+        }
+      }
+
+      // This is not a symlink.
+      // Drop the component, the following '..', and its trailing slash,
+      // if any, while preserving the (possibly root) leading slash:
+      //   "*/dir/../" => "*/"
+      //     ^slash         ^slash
+      P.erase(slash + 1, next_slash + 3 - slash);
+      return Control::Continue(slash);
+    }
+  }
+
+  // This is a named component.
+
+  if (Policy::Symlinks == Options::Symlinks::Eager) {
+    cmsys::Status status;
+    std::string path = P.substr(0, next_slash);
+    if (cm::optional<std::string> maybe_symlink_target =
+          this->ReadSymlink(path, status)) {
+      return this->ResolveSymlink(root, slash, next_slash,
+                                  std::move(*maybe_symlink_target));
+    }
+    if (!status && Policy::Existence == Options::Existence::Required) {
+      P = std::move(path);
+      return Control::Error(status);
+    }
+  }
+
+#ifdef _WIN32
+  bool exists = false;
+  if (Policy::ActualCase == Options::ActualCase::Yes) {
+    std::string name;
+    std::string path = P.substr(0, next_slash);
+    if (cmsys::Status status = this->OS.ReadName(path, name)) {
+      exists = true;
+      if (!name.empty()) {
+        // Rename this component:
+        //   "*/name/" => "*/Name/"
+        //     ^slash       ^slash
+        P.replace(slash + 1, next_slash - slash - 1, name);
+        next_slash = slash + 1 + name.length();
+      }
+    } else if (Policy::Existence == Options::Existence::Required) {
+      P = std::move(path);
+      return Control::Error(status);
+    }
+  }
+#endif
+
+  if (Policy::Existence == Options::Existence::Required
+#ifdef _WIN32
+      && !exists
+#endif
+  ) {
+    std::string path = P.substr(0, next_slash);
+    if (!this->OS.PathExists(path)) {
+      P = std::move(path);
+      return Control::Error(cmsys::Status::POSIX(ENOENT));
+    }
+  }
+
+  // Keep this component:
+  //   "*/name/" => "*/name/"
+  //     ^slash            ^slash
+  return Control::Continue(next_slash);
+}
+
+template <class Policy>
+Control Impl<Policy>::ResolvePath()
+{
+  Root const root = ClassifyRoot(P);
+
+  // Resolve the root component.  It always ends in a slash.
+  Control control = this->ResolveRoot(root);
+  if (control.tag != Control::Tag::Continue) {
+    return control;
+  }
+  std::string::size_type const root_slash = control.slash;
+
+  // Resolve later components.  Every iteration that finishes
+  // the loop body makes progress either by removing a component
+  // or advancing the slash past it.
+  for (std::string::size_type slash = root_slash;
+       P.size() > root_slash + 1 && slash < P.size();) {
+    control = this->ResolveComponent(root, root_slash, slash);
+    if (control.tag != Control::Tag::Continue) {
+      return control;
+    }
+    slash = control.slash;
+  }
+  return Control::Continue(P.size());
+}
+
+template <class Policy>
+cmsys::Status Impl<Policy>::Resolve(std::string in, std::string& out)
+{
+  P = std::move(in);
+  std::replace(P.begin(), P.end(), '\\', '/');
+  for (;;) {
+    Control control = this->ResolvePath();
+    switch (control.tag) {
+      case Control::Tag::Continue:
+        out = std::move(P);
+        return cmsys::Status::Success();
+      case Control::Tag::Restart:
+        continue;
+      case Control::Tag::Error:
+        out = std::move(P);
+        return control.error;
+    };
+  }
+}
+
+}
+
+namespace Policies {
+struct NaivePath
+{
+#ifdef _WIN32
+  static constexpr Options::ActualCase ActualCase = Options::ActualCase::No;
+#endif
+  static constexpr Options::Symlinks Symlinks = Options::Symlinks::None;
+  static constexpr Options::Existence Existence = Options::Existence::Agnostic;
+};
+struct RealPath
+{
+#ifdef _WIN32
+  static constexpr Options::ActualCase ActualCase = Options::ActualCase::Yes;
+#endif
+  static constexpr Options::Symlinks Symlinks = Options::Symlinks::Eager;
+  static constexpr Options::Existence Existence = Options::Existence::Required;
+};
+struct LogicalPath
+{
+#ifdef _WIN32
+  static constexpr Options::ActualCase ActualCase = Options::ActualCase::Yes;
+#endif
+  static constexpr Options::Symlinks Symlinks = Options::Symlinks::Lazy;
+  static constexpr Options::Existence Existence = Options::Existence::Agnostic;
+};
+
+#if defined(__SUNPRO_CC)
+constexpr Options::Symlinks NaivePath::Symlinks;
+constexpr Options::Existence NaivePath::Existence;
+constexpr Options::Symlinks RealPath::Symlinks;
+constexpr Options::Existence RealPath::Existence;
+constexpr Options::Symlinks LogicalPath::Symlinks;
+constexpr Options::Existence LogicalPath::Existence;
+#endif
+}
+
+template <class Policy>
+Resolver<Policy>::Resolver(System& os)
+  : OS(os)
+{
+}
+template <class Policy>
+cmsys::Status Resolver<Policy>::Resolve(std::string in, std::string& out) const
+{
+  return Impl<Policy>(OS).Resolve(std::move(in), out);
+}
+
+System::System() = default;
+System::~System() = default;
+
+template class Resolver<Policies::LogicalPath>;
+template class Resolver<Policies::RealPath>;
+template class Resolver<Policies::NaivePath>;
+
+}
+}
diff --git a/Source/cmPathResolver.h b/Source/cmPathResolver.h
new file mode 100644
index 0000000..0f8baf8
--- /dev/null
+++ b/Source/cmPathResolver.h
@@ -0,0 +1,98 @@
+/* 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 "cmsys/Status.hxx"
+
+namespace cm {
+namespace PathResolver {
+
+class System;
+
+/** Normalize filesystem paths according to a Policy.
+ *
+ * Resolved paths are always absolute, have no '..', '.', or empty
+ * components, and have a trailing '/' if and only if the entire
+ * path is a root component.
+ *
+ * The Policy determines behavior w.r.t. symbolic links, existence,
+ * and matching the on-disk case (upper/lower) of existing paths.
+ */
+template <class Policy>
+class Resolver
+{
+  System& OS;
+
+public:
+  /** Construct with a concrete filesystem access implementation.  */
+  Resolver(System& os);
+
+  /** Resolve the input path according to the Policy, if possible.
+      On success, the resolved path is stored in 'out'.
+      On failure, the non-existent path is stored in 'out'.  */
+  cmsys::Status Resolve(std::string in, std::string& out) const;
+};
+
+/** Access the filesystem via runtime dispatch.
+    This allows unit tests to work without accessing a real filesystem,
+    which is particularly important on Windows where symbolic links
+    may not be something we can create without administrator privileges.
+    */
+class System
+{
+public:
+  System();
+  virtual ~System() = 0;
+
+  /** If the given path is a symbolic link, read its target.
+      If the path exists but is not a symbolic link, fail
+      with EINVAL or ERROR_NOT_A_REPARSE_POINT.  */
+  virtual cmsys::Status ReadSymlink(std::string const& path,
+                                    std::string& symlink_target) = 0;
+
+  /** Return whether the given path exists on disk.  */
+  virtual bool PathExists(std::string const& path) = 0;
+
+  /** Get the process's working directory.  */
+  virtual std::string GetWorkingDirectory() = 0;
+
+#ifdef _WIN32
+  /** Get the process's working directory on a Windows drive letter.
+      This is a legacy DOS concept supported by 'cmd' shells.  */
+  virtual std::string GetWorkingDirectoryOnDrive(char drive_letter) = 0;
+
+  /** Read the on-disk spelling of the last component of a file path.  */
+  virtual cmsys::Status ReadName(std::string const& path,
+                                 std::string& name) = 0;
+#endif
+};
+
+namespace Policies {
+// IWYU pragma: begin_exports
+
+/** Normalizes paths while resolving symlinks only when followed
+    by '..' components.  Does not require paths to exist, but
+    reads on-disk case of paths that do exist (on Windows).  */
+struct LogicalPath;
+
+/** Normalizes paths while resolving all symlinks.
+    Requires paths to exist, and reads their on-disk case (on Windows).  */
+struct RealPath;
+
+/** Normalizes paths in memory without disk access.
+    Assumes components followed by '..' components are not symlinks.  */
+struct NaivePath;
+
+// IWYU pragma: end_exports
+}
+
+extern template class Resolver<Policies::LogicalPath>;
+extern template class Resolver<Policies::RealPath>;
+extern template class Resolver<Policies::NaivePath>;
+
+}
+}
diff --git a/Source/cmPolicies.h b/Source/cmPolicies.h
index dbd6ce8..bc2aace 100644
--- a/Source/cmPolicies.h
+++ b/Source/cmPolicies.h
@@ -552,7 +552,10 @@
          3, 31, 0, cmPolicies::WARN)                                          \
   SELECT(POLICY, CMP0180,                                                     \
          "project() always sets <PROJECT-NAME>_* as normal variables.", 3,    \
-         31, 0, cmPolicies::WARN)
+         31, 0, cmPolicies::WARN)                                             \
+  SELECT(POLICY, CMP0181,                                                     \
+         "Link command-line fragment variables are parsed and re-quoted.", 3, \
+         32, 0, cmPolicies::WARN)
 
 #define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
 #define CM_FOR_EACH_POLICY_ID(POLICY)                                         \
@@ -597,7 +600,8 @@
   F(CMP0157)                                                                  \
   F(CMP0160)                                                                  \
   F(CMP0162)                                                                  \
-  F(CMP0179)
+  F(CMP0179)                                                                  \
+  F(CMP0181)
 
 #define CM_FOR_EACH_CUSTOM_COMMAND_POLICY(F)                                  \
   F(CMP0116)                                                                  \
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/cmQtAutoMocUic.cxx b/Source/cmQtAutoMocUic.cxx
index 251ef3a..8bedc11 100644
--- a/Source/cmQtAutoMocUic.cxx
+++ b/Source/cmQtAutoMocUic.cxx
@@ -7,7 +7,6 @@
 #include <cstddef>
 #include <limits>
 #include <map>
-#include <mutex>
 #include <set>
 #include <string>
 #include <unordered_map>
@@ -561,7 +560,6 @@
   std::string AbsoluteIncludePath(cm::string_view relativePath) const;
   template <class JOBTYPE>
   void CreateParseJobs(SourceFileMapT const& sourceMap);
-  std::string CollapseFullPathTS(std::string const& path) const;
 
 private:
   // -- Abstract processing interface
@@ -595,8 +593,6 @@
   // -- Worker thread pool
   std::atomic<bool> JobError_{ false };
   cmWorkerPool WorkerPool_;
-  // -- Concurrent processing
-  mutable std::mutex CMakeLibMutex_;
 };
 
 cmQtAutoMocUicT::IncludeKeyT::IncludeKeyT(std::string const& key,
@@ -1494,8 +1490,9 @@
                      &headerHandle](std::string const& basePath) -> bool {
     bool found = false;
     for (std::string const& ext : this->BaseConst().HeaderExtensions) {
-      std::string const testPath =
-        this->Gen()->CollapseFullPathTS(cmStrCat(basePath, '.', ext));
+      std::string const testPath = cmSystemTools::CollapseFullPath(
+        cmStrCat(basePath, '.', ext),
+        this->Gen()->ProjectDirs().CurrentSource);
       cmFileTime fileTime;
       if (!fileTime.Load(testPath)) {
         // File not found
@@ -1682,7 +1679,8 @@
   this->SearchLocations.clear();
 
   auto findUi = [this](std::string const& testPath) -> bool {
-    std::string const fullPath = this->Gen()->CollapseFullPathTS(testPath);
+    std::string const fullPath = cmSystemTools::CollapseFullPath(
+      testPath, this->Gen()->ProjectDirs().CurrentSource);
     cmFileTime fileTime;
     if (!fileTime.Load(fullPath)) {
       this->SearchLocations.emplace_back(cmQtAutoGen::ParentDir(fullPath));
@@ -2885,17 +2883,6 @@
   }
 }
 
-/** Concurrently callable implementation of cmSystemTools::CollapseFullPath */
-std::string cmQtAutoMocUicT::CollapseFullPathTS(std::string const& path) const
-{
-  std::lock_guard<std::mutex> guard(this->CMakeLibMutex_);
-#if defined(__NVCOMPILER) || defined(__LCC__)
-  static_cast<void>(guard); // convince compiler var is used
-#endif
-  return cmSystemTools::CollapseFullPath(path,
-                                         this->ProjectDirs().CurrentSource);
-}
-
 void cmQtAutoMocUicT::InitJobs()
 {
   // Add moc_predefs.h job
@@ -3130,10 +3117,6 @@
 std::vector<std::string> cmQtAutoMocUicT::dependenciesFromDepFile(
   const char* filePath)
 {
-  std::lock_guard<std::mutex> guard(this->CMakeLibMutex_);
-#if defined(__NVCOMPILER) || defined(__LCC__)
-  static_cast<void>(guard); // convince compiler var is used
-#endif
   auto const content = cmReadGccDepfile(filePath);
   if (!content || content->empty()) {
     return {};
diff --git a/Source/cmRulePlaceholderExpander.cxx b/Source/cmRulePlaceholderExpander.cxx
index a8c81d0..a2435a3 100644
--- a/Source/cmRulePlaceholderExpander.cxx
+++ b/Source/cmRulePlaceholderExpander.cxx
@@ -9,10 +9,11 @@
 #include "cmSystemTools.h"
 
 cmRulePlaceholderExpander::cmRulePlaceholderExpander(
-  std::map<std::string, std::string> compilers,
+  cmBuildStep buildStep, std::map<std::string, std::string> compilers,
   std::map<std::string, std::string> variableMappings,
   std::string compilerSysroot, std::string linkerSysroot)
-  : Compilers(std::move(compilers))
+  : BuildStep(buildStep)
+  , Compilers(std::move(compilers))
   , VariableMappings(std::move(variableMappings))
   , CompilerSysroot(std::move(compilerSysroot))
   , LinkerSysroot(std::move(linkerSysroot))
@@ -320,9 +321,8 @@
     }
     std::string sysroot;
     // Some platforms may use separate sysroots for compiling and linking.
-    // If we detect link flags, then we pass the link sysroot instead.
-    // FIXME: Use a more robust way to detect link line expansion.
-    if (this->ReplaceValues->LinkFlags) {
+    // When the build step is link, pass the link sysroot instead.
+    if (this->BuildStep == cmBuildStep::Link) {
       sysroot = this->LinkerSysroot;
     } else {
       sysroot = this->CompilerSysroot;
diff --git a/Source/cmRulePlaceholderExpander.h b/Source/cmRulePlaceholderExpander.h
index 225abd4..4858b08 100644
--- a/Source/cmRulePlaceholderExpander.h
+++ b/Source/cmRulePlaceholderExpander.h
@@ -8,6 +8,7 @@
 #include <map>
 #include <string>
 
+#include "cmGeneratorOptions.h"
 #include "cmPlaceholderExpander.h"
 
 class cmOutputConverter;
@@ -16,7 +17,7 @@
 {
 public:
   cmRulePlaceholderExpander(
-    std::map<std::string, std::string> compilers,
+    cmBuildStep buildStep, std::map<std::string, std::string> compilers,
     std::map<std::string, std::string> variableMappings,
     std::string compilerSysroot, std::string linkerSysroot);
 
@@ -84,6 +85,7 @@
 
   std::string TargetImpLib;
 
+  cmBuildStep BuildStep = cmBuildStep::Compile;
   std::map<std::string, std::string> Compilers;
   std::map<std::string, std::string> VariableMappings;
   std::string CompilerSysroot;
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/cmSourceGroupCommand.cxx b/Source/cmSourceGroupCommand.cxx
index bb92856..31f541e 100644
--- a/Source/cmSourceGroupCommand.cxx
+++ b/Source/cmSourceGroupCommand.cxx
@@ -29,11 +29,6 @@
 const std::string kRegexOptionName = "REGULAR_EXPRESSION";
 const std::string kSourceGroupOptionName = "<sg_name>";
 
-std::vector<std::string> tokenizePath(const std::string& path)
-{
-  return cmTokenize(path, "\\/");
-}
-
 std::set<std::string> getSourceGroupFilesPaths(
   const std::string& root, const std::vector<std::string>& files)
 {
@@ -95,31 +90,28 @@
   cmSourceGroup* sg;
 
   for (std::string const& sgFilesPath : sgFilesPaths) {
+    std::vector<std::string> tokenizedPath = cmTokenize(
+      prefix.empty() ? sgFilesPath : cmStrCat(prefix, '/', sgFilesPath),
+      R"(\/)", cmTokenizerMode::New);
 
-    std::vector<std::string> tokenizedPath;
-    if (!prefix.empty()) {
-      tokenizedPath = tokenizePath(cmStrCat(prefix, '/', sgFilesPath));
-    } else {
-      tokenizedPath = tokenizePath(sgFilesPath);
+    if (tokenizedPath.empty()) {
+      continue;
+    }
+    tokenizedPath.pop_back();
+
+    if (tokenizedPath.empty()) {
+      tokenizedPath.emplace_back();
     }
 
-    if (!tokenizedPath.empty()) {
-      tokenizedPath.pop_back();
+    sg = makefile.GetOrCreateSourceGroup(tokenizedPath);
 
-      if (tokenizedPath.empty()) {
-        tokenizedPath.emplace_back();
-      }
-
-      sg = makefile.GetOrCreateSourceGroup(tokenizedPath);
-
-      if (!sg) {
-        errorMsg = "Could not create source group for file: " + sgFilesPath;
-        return false;
-      }
-      const std::string fullPath =
-        cmSystemTools::CollapseFullPath(sgFilesPath, root);
-      sg->AddGroupFile(fullPath);
+    if (!sg) {
+      errorMsg = "Could not create source group for file: " + sgFilesPath;
+      return false;
     }
+    const std::string fullPath =
+      cmSystemTools::CollapseFullPath(sgFilesPath, root);
+    sg->AddGroupFile(fullPath);
   }
 
   return true;
diff --git a/Source/cmState.cxx b/Source/cmState.cxx
index b716dc7..706f4e6 100644
--- a/Source/cmState.cxx
+++ b/Source/cmState.cxx
@@ -13,7 +13,6 @@
 #include "cmsys/RegularExpression.hxx"
 
 #include "cmCacheManager.h"
-#include "cmCommand.h"
 #include "cmDefinitions.h"
 #include "cmExecutionStatus.h"
 #include "cmGlobCacheEntry.h"
@@ -397,12 +396,6 @@
   this->IsGeneratorMultiConfig = b;
 }
 
-void cmState::AddBuiltinCommand(std::string const& name,
-                                std::unique_ptr<cmCommand> command)
-{
-  this->AddBuiltinCommand(name, cmLegacyCommandWrapper(std::move(command)));
-}
-
 void cmState::AddBuiltinCommand(std::string const& name, Command command)
 {
   assert(name == cmSystemTools::LowerCase(name));
diff --git a/Source/cmState.h b/Source/cmState.h
index 4dc982f..6239511 100644
--- a/Source/cmState.h
+++ b/Source/cmState.h
@@ -27,7 +27,6 @@
 #include "cmValue.h"
 
 class cmCacheManager;
-class cmCommand;
 class cmGlobVerificationManager;
 class cmMakefile;
 class cmStateSnapshot;
@@ -177,8 +176,6 @@
   // Returns a command from its name, or nullptr
   Command GetCommandByExactName(std::string const& name) const;
 
-  void AddBuiltinCommand(std::string const& name,
-                         std::unique_ptr<cmCommand> command);
   void AddBuiltinCommand(std::string const& name, Command command);
   void AddBuiltinCommand(std::string const& name, BuiltinCommand command);
   void AddFlowControlCommand(std::string const& name, Command command);
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/cmStringAlgorithms.cxx b/Source/cmStringAlgorithms.cxx
index 332bd8d..724ca75 100644
--- a/Source/cmStringAlgorithms.cxx
+++ b/Source/cmStringAlgorithms.cxx
@@ -55,30 +55,6 @@
   return result;
 }
 
-std::vector<std::string> cmTokenize(cm::string_view str, cm::string_view sep)
-{
-  std::vector<std::string> tokens;
-  cm::string_view::size_type tokend = 0;
-
-  do {
-    cm::string_view::size_type tokstart = str.find_first_not_of(sep, tokend);
-    if (tokstart == cm::string_view::npos) {
-      break; // no more tokens
-    }
-    tokend = str.find_first_of(sep, tokstart);
-    if (tokend == cm::string_view::npos) {
-      tokens.emplace_back(str.substr(tokstart));
-    } else {
-      tokens.emplace_back(str.substr(tokstart, tokend - tokstart));
-    }
-  } while (tokend != cm::string_view::npos);
-
-  if (tokens.empty()) {
-    tokens.emplace_back();
-  }
-  return tokens;
-}
-
 namespace {
 template <std::size_t N, typename T>
 inline void MakeDigits(cm::string_view& view, char (&digits)[N],
diff --git a/Source/cmStringAlgorithms.h b/Source/cmStringAlgorithms.h
index 2bd615a..72557d5 100644
--- a/Source/cmStringAlgorithms.h
+++ b/Source/cmStringAlgorithms.h
@@ -24,7 +24,7 @@
 
 /** Returns length of a literal string.  */
 template <size_t N>
-constexpr size_t cmStrLen(const char (&/*str*/)[N])
+constexpr size_t cmStrLen(const char (&)[N])
 {
   return N - 1;
 }
@@ -91,12 +91,12 @@
   }
 
   std::string result;
-  result.reserve(
-    std::accumulate(std::begin(rng), std::end(rng),
-                    initial.size() + (rng.size() - 1) * separator.size(),
-                    [](std::size_t sum, const std::string& item) {
-                      return sum + item.size();
-                    }));
+  result.reserve(std::accumulate(
+    std::begin(rng), std::end(rng),
+    initial.size() + (rng.size() - 1) * separator.size(),
+    [](std::size_t sum, typename Range::value_type const& item) {
+      return sum + item.size();
+    }));
   result.append(std::begin(initial), std::end(initial));
 
   auto begin = std::begin(rng);
@@ -122,8 +122,81 @@
 std::string cmJoin(cmStringRange const& rng, cm::string_view separator,
                    cm::string_view initial = {});
 
-/** Extract tokens that are separated by any of the characters in @a sep.  */
-std::vector<std::string> cmTokenize(cm::string_view str, cm::string_view sep);
+enum class cmTokenizerMode
+{
+  /// A backward-compatible behavior when in the case of no
+  /// tokens have found in an input text it'll return one empty
+  /// token in the result container (vector).
+  Legacy,
+  /// The new behavior is to return an empty vector.
+  New
+};
+
+/**
+ * \brief A generic version of a tokenizer.
+ *
+ * Extract tokens from the input string separated by any
+ * of the characters in `sep` and assign them to the
+ * given output iterator.
+ *
+ * The `mode` parameter defines the behavior in the case when
+ * no tokens have found in the input text.
+ *
+ */
+template <typename StringT, typename OutIt, typename Sep = char>
+void cmTokenize(OutIt outIt, cm::string_view str, Sep sep,
+                cmTokenizerMode mode)
+{
+  auto hasTokens = false;
+  // clang-format off
+  for (auto start = str.find_first_not_of(sep)
+    , end = str.find_first_of(sep, start)
+    ; start != cm::string_view::npos
+    ; start = str.find_first_not_of(sep, end)
+    , end = str.find_first_of(sep, start)
+    , hasTokens = true
+    ) {
+    *outIt++ = StringT{ str.substr(start, end - start) };
+  }
+  // clang-format on
+  if (!hasTokens && mode == cmTokenizerMode::Legacy) {
+    *outIt = {};
+  }
+}
+
+/**
+ * \brief Extract tokens that are separated by any of the
+ * characters in `sep`.
+ *
+ * Backward compatible signature.
+ *
+ * \return A vector of strings.
+ */
+template <typename Sep = char>
+std::vector<std::string> cmTokenize(
+  cm::string_view str, Sep sep, cmTokenizerMode mode = cmTokenizerMode::Legacy)
+{
+  using StringType = std::string;
+  std::vector<StringType> tokens;
+  cmTokenize<StringType>(std::back_inserter(tokens), str, sep, mode);
+  return tokens;
+}
+
+/**
+ * \brief Extract tokens that are separated by any of the
+ * characters in `sep`.
+ *
+ * \return A vector of string views.
+ */
+template <typename Sep = char>
+std::vector<cm::string_view> cmTokenizedView(
+  cm::string_view str, Sep sep, cmTokenizerMode mode = cmTokenizerMode::Legacy)
+{
+  using StringType = cm::string_view;
+  std::vector<StringType> tokens;
+  cmTokenize<StringType>(std::back_inserter(tokens), str, sep, mode);
+  return tokens;
+}
 
 /** Concatenate string pieces into a single string.  */
 std::string cmCatViews(
diff --git a/Source/cmSystemTools.cxx b/Source/cmSystemTools.cxx
index 5ad0439..cdb661c 100644
--- a/Source/cmSystemTools.cxx
+++ b/Source/cmSystemTools.cxx
@@ -20,6 +20,12 @@
 
 #include "cmSystemTools.h"
 
+#include <iterator>
+
+#ifdef _WIN32
+#  include <unordered_map>
+#endif
+
 #include <cm/optional>
 #include <cmext/algorithm>
 #include <cmext/string_view>
@@ -29,6 +35,7 @@
 #include "cmDuration.h"
 #include "cmELF.h"
 #include "cmMessageMetadata.h"
+#include "cmPathResolver.h"
 #include "cmProcessOutput.h"
 #include "cmRange.h"
 #include "cmStringAlgorithms.h"
@@ -126,6 +133,116 @@
 cmSystemTools::OutputCallback s_StderrCallback;
 cmSystemTools::OutputCallback s_StdoutCallback;
 
+#ifdef _WIN32
+std::string GetDosDriveWorkingDirectory(char letter)
+{
+  // The Windows command processor tracks a per-drive working
+  // directory for compatibility with MS-DOS by using special
+  // environment variables named "=C:".
+  // https://web.archive.org/web/20100522040616/
+  // https://blogs.msdn.com/oldnewthing/archive/2010/05/06/10008132.aspx
+  return cmSystemTools::GetEnvVar(cmStrCat('=', letter, ':'))
+    .value_or(std::string());
+}
+
+cmsys::Status ReadNameOnDisk(std::string const& path, std::string& name)
+{
+  std::wstring wp = cmsys::Encoding::ToWide(path);
+  HANDLE h = CreateFileW(
+    wp.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
+    FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr);
+  if (h == INVALID_HANDLE_VALUE) {
+    return cmsys::Status::Windows_GetLastError();
+  }
+
+  WCHAR local_fni[((sizeof(FILE_NAME_INFO) - 1) / sizeof(WCHAR)) + 1024];
+  size_t fni_size = sizeof(local_fni);
+  auto* fni = reinterpret_cast<FILE_NAME_INFO*>(local_fni);
+  if (!GetFileInformationByHandleEx(h, FileNameInfo, fni, fni_size)) {
+    DWORD e = GetLastError();
+    if (e != ERROR_MORE_DATA) {
+      CloseHandle(h);
+      return cmsys::Status::Windows(e);
+    }
+    fni_size = fni->FileNameLength;
+    fni = static_cast<FILE_NAME_INFO*>(malloc(fni_size));
+    if (!fni) {
+      e = ERROR_NOT_ENOUGH_MEMORY;
+      CloseHandle(h);
+      return cmsys::Status::Windows(e);
+    }
+    if (!GetFileInformationByHandleEx(h, FileNameInfo, fni, fni_size)) {
+      e = GetLastError();
+      free(fni);
+      CloseHandle(h);
+      return cmsys::Status::Windows(e);
+    }
+  }
+
+  std::wstring wn{ fni->FileName, fni->FileNameLength / sizeof(WCHAR) };
+  std::string nn = cmsys::Encoding::ToNarrow(wn);
+  std::string::size_type last_slash = nn.find_last_of("/\\");
+  if (last_slash != std::string::npos) {
+    name = nn.substr(last_slash + 1);
+  }
+  if (fni != reinterpret_cast<FILE_NAME_INFO*>(local_fni)) {
+    free(fni);
+  }
+  CloseHandle(h);
+  return cmsys::Status::Success();
+}
+#endif
+
+class RealSystem : public cm::PathResolver::System
+{
+public:
+  ~RealSystem() override = default;
+  cmsys::Status ReadSymlink(std::string const& path,
+                            std::string& link) override
+  {
+    return cmSystemTools::ReadSymlink(path, link);
+  }
+  bool PathExists(std::string const& path) override
+  {
+    return cmSystemTools::PathExists(path);
+  }
+  std::string GetWorkingDirectory() override
+  {
+    return cmSystemTools::GetLogicalWorkingDirectory();
+  }
+#ifdef _WIN32
+  std::string GetWorkingDirectoryOnDrive(char letter) override
+  {
+    return GetDosDriveWorkingDirectory(letter);
+  }
+
+  struct NameOnDisk
+  {
+    cmsys::Status Status;
+    std::string Name;
+  };
+  using NameOnDiskMap = std::unordered_map<std::string, NameOnDisk>;
+  NameOnDiskMap CachedNameOnDisk;
+
+  cmsys::Status ReadName(std::string const& path, std::string& name) override
+  {
+    // Cache results to avoid repeated filesystem access.
+    // We assume any files created by our own process keep their case.
+    // Index the cache by lower-case paths to make it case-insensitive.
+    std::string path_lower = cmSystemTools::LowerCase(path);
+    auto i = this->CachedNameOnDisk.find(path_lower);
+    if (i == this->CachedNameOnDisk.end()) {
+      i = this->CachedNameOnDisk.emplace(path_lower, NameOnDisk()).first;
+      i->second.Status = ReadNameOnDisk(path, i->second.Name);
+    }
+    name = i->second.Name;
+    return i->second.Status;
+  }
+#endif
+};
+
+RealSystem RealOS;
+
 } // namespace
 
 #if !defined(HAVE_ENVIRON_NOT_REQUIRE_PROTOTYPE)
@@ -133,7 +250,7 @@
 #  if defined(_WIN32)
 extern __declspec(dllimport) char** environ;
 #  else
-extern char** environ;
+extern char** environ; // NOLINT(readability-redundant-declaration)
 #  endif
 #endif
 
@@ -984,36 +1101,22 @@
 
 #endif
 
-std::string cmSystemTools::GetRealPathResolvingWindowsSubst(
-  const std::string& path, std::string* errorMessage)
+std::string cmSystemTools::GetRealPath(const std::string& path,
+                                       std::string* errorMessage)
 {
 #ifdef _WIN32
-  // uv_fs_realpath uses Windows Vista API so fallback to kwsys if not found
   std::string resolved_path;
-  uv_fs_t req;
-  int err = uv_fs_realpath(nullptr, &req, path.c_str(), nullptr);
-  if (!err) {
-    resolved_path = std::string((char*)req.ptr);
-    cmSystemTools::ConvertToUnixSlashes(resolved_path);
-    // Normalize to upper-case drive letter as GetActualCaseForPath does.
-    if (resolved_path.size() > 1 && resolved_path[1] == ':') {
-      resolved_path[0] = toupper(resolved_path[0]);
+  using namespace cm::PathResolver;
+  // IWYU pragma: no_forward_declare cm::PathResolver::Policies::RealPath
+  static const Resolver<Policies::RealPath> resolver(RealOS);
+  cmsys::Status status = resolver.Resolve(path, resolved_path);
+  if (!status) {
+    if (errorMessage) {
+      *errorMessage = status.GetString();
+      resolved_path.clear();
+    } else {
+      resolved_path = path;
     }
-  } else if (err == UV_ENOSYS) {
-    resolved_path = cmsys::SystemTools::GetRealPath(path, errorMessage);
-  } else if (errorMessage) {
-    LPSTR message = nullptr;
-    DWORD size = FormatMessageA(
-      FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
-        FORMAT_MESSAGE_IGNORE_INSERTS,
-      nullptr, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&message,
-      0, nullptr);
-    *errorMessage = std::string(message, size);
-    LocalFree(message);
-
-    resolved_path = "";
-  } else {
-    resolved_path = path;
   }
   return resolved_path;
 #else
@@ -1614,20 +1717,59 @@
   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)
+{
+  using namespace cm::PathResolver;
+  // IWYU pragma: no_forward_declare cm::PathResolver::Policies::LogicalPath
+  static const Resolver<Policies::LogicalPath> resolver(RealOS);
+  resolver.Resolve(std::move(p), p);
+  return p;
+}
+
 #ifndef CMAKE_BOOTSTRAP
 bool cmSystemTools::UnsetEnv(const char* value)
 {
@@ -1874,12 +2016,12 @@
                               std::string const& format, int compressionLevel)
 {
 #if !defined(CMAKE_BOOTSTRAP)
-  cmWorkingDirectory workdir(cmSystemTools::GetCurrentWorkingDirectory());
+  cmWorkingDirectory workdir(cmSystemTools::GetLogicalWorkingDirectory());
   if (!workingDirectory.empty()) {
     workdir.SetDirectory(workingDirectory);
   }
 
-  const std::string cwd = cmSystemTools::GetCurrentWorkingDirectory();
+  const std::string cwd = cmSystemTools::GetLogicalWorkingDirectory();
   cmsys::ofstream fout(outFileName.c_str(), std::ios::out | std::ios::binary);
   if (!fout) {
     std::string e = cmStrCat("Cannot open output file \"", outFileName,
@@ -2513,30 +2655,48 @@
 #endif
 }
 
-static std::string cmSystemToolsCMakeCommand;
-static std::string cmSystemToolsCTestCommand;
-static std::string cmSystemToolsCPackCommand;
-static std::string cmSystemToolsCMakeCursesCommand;
-static std::string cmSystemToolsCMakeGUICommand;
-static std::string cmSystemToolsCMClDepsCommand;
-static std::string cmSystemToolsCMakeRoot;
-static std::string cmSystemToolsHTMLDoc;
-void cmSystemTools::FindCMakeResources(const char* argv0)
+namespace {
+std::string InitLogicalWorkingDirectory()
 {
-  std::string exe_dir;
+  std::string cwd = cmsys::SystemTools::GetCurrentWorkingDirectory();
+  std::string pwd;
+  if (cmSystemTools::GetEnv("PWD", pwd)) {
+    std::string const pwd_real = cmSystemTools::GetRealPath(pwd);
+    if (pwd_real == cwd) {
+      cwd = std::move(pwd);
+    }
+  }
+  return cwd;
+}
+
+std::string cmSystemToolsLogicalWorkingDirectory =
+  InitLogicalWorkingDirectory();
+
+std::string cmSystemToolsCMakeCommand;
+std::string cmSystemToolsCTestCommand;
+std::string cmSystemToolsCPackCommand;
+std::string cmSystemToolsCMakeCursesCommand;
+std::string cmSystemToolsCMakeGUICommand;
+std::string cmSystemToolsCMClDepsCommand;
+std::string cmSystemToolsCMakeRoot;
+std::string cmSystemToolsHTMLDoc;
+
+#if defined(__APPLE__)
+bool IsCMakeAppBundleExe(std::string const& exe)
+{
+  return cmHasLiteralSuffix(cmSystemTools::LowerCase(exe), "/macos/cmake");
+}
+#endif
+
+std::string FindOwnExecutable(const char* argv0)
+{
 #if defined(_WIN32) && !defined(__CYGWIN__)
-  (void)argv0; // ignore this on windows
+  static_cast<void>(argv0);
   wchar_t modulepath[_MAX_PATH];
   ::GetModuleFileNameW(nullptr, modulepath, sizeof(modulepath));
-  std::string path = cmsys::Encoding::ToNarrow(modulepath);
-  std::string realPath =
-    cmSystemTools::GetRealPathResolvingWindowsSubst(path, nullptr);
-  if (realPath.empty()) {
-    realPath = path;
-  }
-  exe_dir = cmSystemTools::GetFilenamePath(realPath);
+  std::string exe = cmsys::Encoding::ToNarrow(modulepath);
 #elif defined(__APPLE__)
-  (void)argv0; // ignore this on OS X
+  static_cast<void>(argv0);
 #  define CM_EXE_PATH_LOCAL_SIZE 16384
   char exe_path_local[CM_EXE_PATH_LOCAL_SIZE];
 #  if defined(MAC_OS_X_VERSION_10_3) && !defined(MAC_OS_X_VERSION_10_4)
@@ -2550,41 +2710,133 @@
     exe_path = static_cast<char*>(malloc(exe_path_size));
     _NSGetExecutablePath(exe_path, &exe_path_size);
   }
-  exe_dir =
-    cmSystemTools::GetFilenamePath(cmSystemTools::GetRealPath(exe_path));
+  std::string exe = exe_path;
   if (exe_path != exe_path_local) {
     free(exe_path);
   }
-  if (cmSystemTools::GetFilenameName(exe_dir) == "MacOS") {
+  if (IsCMakeAppBundleExe(exe)) {
     // The executable is inside an application bundle.
-    // Look for ..<CMAKE_BIN_DIR> (install tree) and then fall back to
-    // ../../../bin (build tree).
-    exe_dir = cmSystemTools::GetFilenamePath(exe_dir);
-    if (cmSystemTools::FileExists(exe_dir + CMAKE_BIN_DIR "/cmake")) {
-      exe_dir += CMAKE_BIN_DIR;
-    } else {
-      exe_dir = cmSystemTools::GetFilenamePath(exe_dir);
-      exe_dir = cmSystemTools::GetFilenamePath(exe_dir);
+    // The install tree has "..<CMAKE_BIN_DIR>/cmake-gui".
+    // The build tree has '../../../cmake-gui".
+    std::string dir = cmSystemTools::GetFilenamePath(exe);
+    dir = cmSystemTools::GetFilenamePath(dir);
+    exe = cmStrCat(dir, CMAKE_BIN_DIR "/cmake-gui");
+    if (!cmSystemTools::PathExists(exe)) {
+      dir = cmSystemTools::GetFilenamePath(dir);
+      dir = cmSystemTools::GetFilenamePath(dir);
+      exe = cmStrCat(dir, "/cmake-gui");
     }
   }
 #else
   std::string errorMsg;
   std::string exe;
-  if (cmSystemTools::FindProgramPath(argv0, exe, errorMsg)) {
-    // remove symlinks
-    exe = cmSystemTools::GetRealPath(exe);
-    exe_dir = cmSystemTools::GetFilenamePath(exe);
-  } else {
+  if (!cmSystemTools::FindProgramPath(argv0, exe, errorMsg)) {
     // ???
   }
 #endif
-  exe_dir = cmSystemTools::GetActualCaseForPath(exe_dir);
-  cmSystemToolsCMakeCommand =
-    cmStrCat(exe_dir, "/cmake", cmSystemTools::GetExecutableExtension());
+  exe = cmSystemTools::ToNormalizedPathOnDisk(std::move(exe));
+  return exe;
+}
+
+#ifndef CMAKE_BOOTSTRAP
+bool ResolveSymlinkToOwnExecutable(std::string& exe, std::string& exe_dir)
+{
+  std::string linked_exe;
+  if (!cmSystemTools::ReadSymlink(exe, linked_exe)) {
+    return false;
+  }
+#  if defined(__APPLE__)
+  // Ignore "cmake-gui -> ../MacOS/CMake".
+  if (IsCMakeAppBundleExe(linked_exe)) {
+    return false;
+  }
+#  endif
+  if (cmSystemTools::FileIsFullPath(linked_exe)) {
+    exe = std::move(linked_exe);
+  } else {
+    exe = cmStrCat(exe_dir, '/', std::move(linked_exe));
+  }
+  exe = cmSystemTools::ToNormalizedPathOnDisk(std::move(exe));
+  exe_dir = cmSystemTools::GetFilenamePath(exe);
+  return true;
+}
+
+bool FindCMakeResourcesInInstallTree(std::string const& exe_dir)
+{
+  // Install tree has
+  // - "<prefix><CMAKE_BIN_DIR>/cmake"
+  // - "<prefix><CMAKE_DATA_DIR>"
+  // - "<prefix><CMAKE_DOC_DIR>"
+  if (cmHasLiteralSuffix(exe_dir, CMAKE_BIN_DIR)) {
+    std::string const prefix =
+      exe_dir.substr(0, exe_dir.size() - cmStrLen(CMAKE_BIN_DIR));
+    // Set cmSystemToolsCMakeRoot set to the location expected in an
+    // install tree, even if it does not exist, so that
+    // cmake::AddCMakePaths can print the location in its error message.
+    cmSystemToolsCMakeRoot = cmStrCat(prefix, CMAKE_DATA_DIR);
+    if (cmSystemTools::FileExists(
+          cmStrCat(cmSystemToolsCMakeRoot, "/Modules/CMake.cmake"))) {
+      if (cmSystemTools::FileExists(
+            cmStrCat(prefix, CMAKE_DOC_DIR "/html/index.html"))) {
+        cmSystemToolsHTMLDoc = cmStrCat(prefix, CMAKE_DOC_DIR "/html");
+      }
+      return true;
+    }
+  }
+  return false;
+}
+
+void FindCMakeResourcesInBuildTree(std::string const& exe_dir)
+{
+  // Build tree has "<build>/bin[/<config>]/cmake" and
+  // "<build>/CMakeFiles/CMakeSourceDir.txt".
+  std::string dir = cmSystemTools::GetFilenamePath(exe_dir);
+  std::string src_dir_txt = cmStrCat(dir, "/CMakeFiles/CMakeSourceDir.txt");
+  cmsys::ifstream fin(src_dir_txt.c_str());
+  std::string src_dir;
+  if (fin && cmSystemTools::GetLineFromStream(fin, src_dir) &&
+      cmSystemTools::FileIsDirectory(src_dir)) {
+    cmSystemToolsCMakeRoot = src_dir;
+  } else {
+    dir = cmSystemTools::GetFilenamePath(dir);
+    src_dir_txt = cmStrCat(dir, "/CMakeFiles/CMakeSourceDir.txt");
+    cmsys::ifstream fin2(src_dir_txt.c_str());
+    if (fin2 && cmSystemTools::GetLineFromStream(fin2, src_dir) &&
+        cmSystemTools::FileIsDirectory(src_dir)) {
+      cmSystemToolsCMakeRoot = src_dir;
+    }
+  }
+  if (!cmSystemToolsCMakeRoot.empty() && cmSystemToolsHTMLDoc.empty() &&
+      cmSystemTools::FileExists(
+        cmStrCat(dir, "/Utilities/Sphinx/html/index.html"))) {
+    cmSystemToolsHTMLDoc = cmStrCat(dir, "/Utilities/Sphinx/html");
+  }
+}
+#endif
+}
+
+void cmSystemTools::FindCMakeResources(const char* argv0)
+{
+  std::string exe = FindOwnExecutable(argv0);
 #ifdef CMAKE_BOOTSTRAP
+  // The bootstrap cmake knows its resource locations.
+  cmSystemToolsCMakeRoot = CMAKE_BOOTSTRAP_SOURCE_DIR;
+  cmSystemToolsCMakeCommand = exe;
   // The bootstrap cmake does not provide the other tools,
   // so use the directory where they are about to be built.
-  exe_dir = CMAKE_BOOTSTRAP_BINARY_DIR "/bin";
+  std::string exe_dir = CMAKE_BOOTSTRAP_BINARY_DIR "/bin";
+#else
+  // Find resources relative to our own executable.
+  std::string exe_dir = cmSystemTools::GetFilenamePath(exe);
+  bool found = false;
+  do {
+    found = FindCMakeResourcesInInstallTree(exe_dir);
+  } while (!found && ResolveSymlinkToOwnExecutable(exe, exe_dir));
+  if (!found) {
+    FindCMakeResourcesInBuildTree(exe_dir);
+  }
+  cmSystemToolsCMakeCommand =
+    cmStrCat(exe_dir, "/cmake", cmSystemTools::GetExecutableExtension());
 #endif
   cmSystemToolsCTestCommand =
     cmStrCat(exe_dir, "/ctest", cmSystemTools::GetExecutableExtension());
@@ -2605,52 +2857,6 @@
   if (!cmSystemTools::FileExists(cmSystemToolsCMClDepsCommand)) {
     cmSystemToolsCMClDepsCommand.clear();
   }
-
-#ifndef CMAKE_BOOTSTRAP
-  // Install tree has
-  // - "<prefix><CMAKE_BIN_DIR>/cmake"
-  // - "<prefix><CMAKE_DATA_DIR>"
-  // - "<prefix><CMAKE_DOC_DIR>"
-  if (cmHasLiteralSuffix(exe_dir, CMAKE_BIN_DIR)) {
-    std::string const prefix =
-      exe_dir.substr(0, exe_dir.size() - cmStrLen(CMAKE_BIN_DIR));
-    cmSystemToolsCMakeRoot = cmStrCat(prefix, CMAKE_DATA_DIR);
-    if (cmSystemTools::FileExists(
-          cmStrCat(prefix, CMAKE_DOC_DIR "/html/index.html"))) {
-      cmSystemToolsHTMLDoc = cmStrCat(prefix, CMAKE_DOC_DIR "/html");
-    }
-  }
-  if (cmSystemToolsCMakeRoot.empty() ||
-      !cmSystemTools::FileExists(
-        cmStrCat(cmSystemToolsCMakeRoot, "/Modules/CMake.cmake"))) {
-    // Build tree has "<build>/bin[/<config>]/cmake" and
-    // "<build>/CMakeFiles/CMakeSourceDir.txt".
-    std::string dir = cmSystemTools::GetFilenamePath(exe_dir);
-    std::string src_dir_txt = cmStrCat(dir, "/CMakeFiles/CMakeSourceDir.txt");
-    cmsys::ifstream fin(src_dir_txt.c_str());
-    std::string src_dir;
-    if (fin && cmSystemTools::GetLineFromStream(fin, src_dir) &&
-        cmSystemTools::FileIsDirectory(src_dir)) {
-      cmSystemToolsCMakeRoot = src_dir;
-    } else {
-      dir = cmSystemTools::GetFilenamePath(dir);
-      src_dir_txt = cmStrCat(dir, "/CMakeFiles/CMakeSourceDir.txt");
-      cmsys::ifstream fin2(src_dir_txt.c_str());
-      if (fin2 && cmSystemTools::GetLineFromStream(fin2, src_dir) &&
-          cmSystemTools::FileIsDirectory(src_dir)) {
-        cmSystemToolsCMakeRoot = src_dir;
-      }
-    }
-    if (!cmSystemToolsCMakeRoot.empty() && cmSystemToolsHTMLDoc.empty() &&
-        cmSystemTools::FileExists(
-          cmStrCat(dir, "/Utilities/Sphinx/html/index.html"))) {
-      cmSystemToolsHTMLDoc = cmStrCat(dir, "/Utilities/Sphinx/html");
-    }
-  }
-#else
-  // Bootstrap build knows its source.
-  cmSystemToolsCMakeRoot = CMAKE_BOOTSTRAP_SOURCE_DIR;
-#endif
 }
 
 std::string const& cmSystemTools::GetCMakeCommand()
@@ -2737,10 +2943,18 @@
   return config;
 }
 
-std::string cmSystemTools::GetCurrentWorkingDirectory()
+std::string const& cmSystemTools::GetLogicalWorkingDirectory()
 {
-  return cmSystemTools::CollapseFullPath(
-    cmsys::SystemTools::GetCurrentWorkingDirectory());
+  return cmSystemToolsLogicalWorkingDirectory;
+}
+
+cmsys::Status cmSystemTools::SetLogicalWorkingDirectory(std::string const& lwd)
+{
+  cmsys::Status status = cmSystemTools::ChangeDirectory(lwd);
+  if (status) {
+    cmSystemToolsLogicalWorkingDirectory = lwd;
+  }
+  return status;
 }
 
 void cmSystemTools::MakefileColorEcho(int color, const char* message,
diff --git a/Source/cmSystemTools.h b/Source/cmSystemTools.h
index 0531f63..4f62aee 100644
--- a/Source/cmSystemTools.h
+++ b/Source/cmSystemTools.h
@@ -399,7 +399,18 @@
                                      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);
+
+  /** Convert an input path to an absolute path with no '/..' components.
+      Backslashes in the input path are converted to forward slashes.
+      Relative paths are interpreted w.r.t. GetLogicalWorkingDirectory.
+      On Windows, the on-disk capitalization is loaded for existing paths.
+      This is similar to 'realpath', but preserves symlinks that are
+      not erased by '../' components.  */
+  static std::string ToNormalizedPathOnDisk(std::string p);
 
 #ifndef CMAKE_BOOTSTRAP
   /** Remove an environment variable */
@@ -529,8 +540,11 @@
   static cm::optional<std::string> GetSystemConfigDirectory();
   static cm::optional<std::string> GetCMakeConfigDirectory();
 
-  /** Get the CWD mapped through the KWSys translation map.  */
-  static std::string GetCurrentWorkingDirectory();
+  static std::string const& GetLogicalWorkingDirectory();
+
+  /** The logical working directory may contain symlinks but must not
+      contain any '../' path components.  */
+  static cmsys::Status SetLogicalWorkingDirectory(std::string const& lwd);
 
   /** Echo a message in color using KWSys's Terminal cprintf.  */
   static void MakefileColorEcho(int color, const char* message, bool newLine,
@@ -591,11 +605,9 @@
   static std::string GetComspec();
 #endif
 
-  /** Get the real path for a given path, removing all symlinks.
-      This variant of GetRealPath also works on Windows but will
-      resolve subst drives too.  */
-  static std::string GetRealPathResolvingWindowsSubst(
-    const std::string& path, std::string* errorMessage = nullptr);
+  /** Get the real path for a given path, removing all symlinks.  */
+  static std::string GetRealPath(const std::string& path,
+                                 std::string* errorMessage = nullptr);
 
   /** Perform one-time initialization of libuv.  */
   static void InitializeLibUV();
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/cmTimestamp.cxx b/Source/cmTimestamp.cxx
index 4d8bc02..cb6eb6f 100644
--- a/Source/cmTimestamp.cxx
+++ b/Source/cmTimestamp.cxx
@@ -62,8 +62,7 @@
                                               const std::string& formatString,
                                               bool utcFlag) const
 {
-  std::string real_path =
-    cmSystemTools::GetRealPathResolvingWindowsSubst(path);
+  std::string real_path = cmSystemTools::GetRealPath(path);
 
   if (!cmsys::SystemTools::FileExists(real_path)) {
     return std::string();
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/cmUVProcessChain.cxx b/Source/cmUVProcessChain.cxx
index b787f19..4060a3d 100644
--- a/Source/cmUVProcessChain.cxx
+++ b/Source/cmUVProcessChain.cxx
@@ -342,6 +342,9 @@
   !defined(CMAKE_USE_SYSTEM_LIBUV)
   options.flags |= UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME;
 #endif
+#if UV_VERSION_MAJOR > 1 || !defined(CMAKE_USE_SYSTEM_LIBUV)
+  options.flags |= UV_PROCESS_WINDOWS_USE_PARENT_ERROR_MODE;
+#endif
   if (!this->Builder->WorkingDirectory.empty()) {
     options.cwd = this->Builder->WorkingDirectory.c_str();
   }
diff --git a/Source/cmVisualStudio10TargetGenerator.cxx b/Source/cmVisualStudio10TargetGenerator.cxx
index 694976e..8d9946f 100644
--- a/Source/cmVisualStudio10TargetGenerator.cxx
+++ b/Source/cmVisualStudio10TargetGenerator.cxx
@@ -30,6 +30,7 @@
 #include "cmFileSet.h"
 #include "cmGeneratedFileStream.h"
 #include "cmGeneratorExpression.h"
+#include "cmGeneratorOptions.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
 #include "cmGlobalVisualStudio10Generator.h"
@@ -1870,7 +1871,9 @@
       BuildInParallel buildInParallel = BuildInParallel::No;
       if (command.GetCMP0147Status() == cmPolicies::NEW &&
           !command.GetUsesTerminal() &&
-          !(command.HasMainDependency() && source->GetIsGenerated())) {
+          !(command.HasMainDependency() && source->GetIsGenerated()) &&
+          !source->GetPropertyAsBool(
+            "VS_CUSTOM_COMMAND_DISABLE_PARALLEL_BUILD")) {
         buildInParallel = BuildInParallel::Yes;
       }
       this->WriteCustomRuleCpp(*spe2, c, script, additional_inputs.str(),
@@ -4426,12 +4429,9 @@
     linkType = "EXE";
   }
   std::string flags;
-  std::string linkFlagVarBase = cmStrCat("CMAKE_", linkType, "_LINKER_FLAGS");
-  flags += ' ';
-  flags += this->Makefile->GetRequiredDefinition(linkFlagVarBase);
-  std::string linkFlagVar = cmStrCat(linkFlagVarBase, '_', CONFIG);
-  flags += ' ';
-  flags += this->Makefile->GetRequiredDefinition(linkFlagVar);
+  this->LocalGenerator->AddConfigVariableFlags(
+    flags, cmStrCat("CMAKE_", linkType, "_LINKER_FLAGS"),
+    this->GeneratorTarget, cmBuildStep::Link, linkLanguage, config);
   cmValue targetLinkFlags = this->GeneratorTarget->GetProperty("LINK_FLAGS");
   if (targetLinkFlags) {
     flags += ' ';
diff --git a/Source/cmWorkerPool.cxx b/Source/cmWorkerPool.cxx
index 2fbf657..69ebdab 100644
--- a/Source/cmWorkerPool.cxx
+++ b/Source/cmWorkerPool.cxx
@@ -250,6 +250,9 @@
     this->UVOptions_.args = const_cast<char**>(this->CommandPtr_.data());
     this->UVOptions_.cwd = this->Setup_.WorkingDirectory.c_str();
     this->UVOptions_.flags = UV_PROCESS_WINDOWS_HIDE;
+#if UV_VERSION_MAJOR > 1 || !defined(CMAKE_USE_SYSTEM_LIBUV)
+    this->UVOptions_.flags |= UV_PROCESS_WINDOWS_USE_PARENT_ERROR_MODE;
+#endif
     this->UVOptions_.stdio_count =
       static_cast<int>(this->UVOptionsStdIO_.size());
     this->UVOptions_.stdio = this->UVOptionsStdIO_.data();
diff --git a/Source/cmWorkingDirectory.cxx b/Source/cmWorkingDirectory.cxx
index 12fae12..ec10fb1 100644
--- a/Source/cmWorkingDirectory.cxx
+++ b/Source/cmWorkingDirectory.cxx
@@ -2,13 +2,12 @@
    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)
 {
-  this->OldDir = cmSystemTools::GetCurrentWorkingDirectory();
+  this->OldDir = cmSystemTools::GetLogicalWorkingDirectory();
   this->SetDirectory(newdir);
 }
 
@@ -19,11 +18,13 @@
 
 bool cmWorkingDirectory::SetDirectory(std::string const& newdir)
 {
-  if (cmSystemTools::ChangeDirectory(newdir)) {
-    this->ResultCode = 0;
+  cmsys::Status status = cmSystemTools::SetLogicalWorkingDirectory(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..2817819 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>
@@ -287,7 +286,7 @@
 };
 
 cmake::cmake(Role role, cmState::Mode mode, cmState::ProjectKind projectKind)
-  : CMakeWorkingDirectory(cmSystemTools::GetCurrentWorkingDirectory())
+  : CMakeWorkingDirectory(cmSystemTools::GetLogicalWorkingDirectory())
   , FileTimeCache(cm::make_unique<cmFileTimeCache>())
 #ifndef CMAKE_BOOTSTRAP
   , VariableWatch(cm::make_unique<cmVariableWatch>())
@@ -638,8 +637,8 @@
     // Documented behavior of CMAKE{,_CURRENT}_{SOURCE,BINARY}_DIR is to be
     // set to $PWD for -P mode.
     state->SetWorkingMode(SCRIPT_MODE);
-    state->SetHomeDirectory(cmSystemTools::GetCurrentWorkingDirectory());
-    state->SetHomeOutputDirectory(cmSystemTools::GetCurrentWorkingDirectory());
+    state->SetHomeDirectory(cmSystemTools::GetLogicalWorkingDirectory());
+    state->SetHomeOutputDirectory(cmSystemTools::GetLogicalWorkingDirectory());
     state->ReadListFile(args, path);
     return true;
   };
@@ -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)) {
@@ -796,16 +792,16 @@
 
 bool cmake::FindPackage(const std::vector<std::string>& args)
 {
-  this->SetHomeDirectory(cmSystemTools::GetCurrentWorkingDirectory());
-  this->SetHomeOutputDirectory(cmSystemTools::GetCurrentWorkingDirectory());
+  this->SetHomeDirectory(cmSystemTools::GetLogicalWorkingDirectory());
+  this->SetHomeOutputDirectory(cmSystemTools::GetLogicalWorkingDirectory());
 
   this->SetGlobalGenerator(cm::make_unique<cmGlobalGenerator>(this));
 
   cmStateSnapshot snapshot = this->GetCurrentSnapshot();
   snapshot.GetDirectory().SetCurrentBinary(
-    cmSystemTools::GetCurrentWorkingDirectory());
+    cmSystemTools::GetLogicalWorkingDirectory());
   snapshot.GetDirectory().SetCurrentSource(
-    cmSystemTools::GetCurrentWorkingDirectory());
+    cmSystemTools::GetLogicalWorkingDirectory());
   // read in the list file to fill the cache
   snapshot.SetDefaultDefinitions();
   auto mfu = cm::make_unique<cmMakefile>(this->GetGlobalGenerator(), snapshot);
@@ -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;
                      } },
 
@@ -1137,7 +1131,7 @@
       "--debug-find-pkg", "Provide a package argument for --debug-find-pkg",
       CommandArgument::Values::One, CommandArgument::RequiresSeparator::Yes,
       [](std::string const& value, cmake* state) -> bool {
-        std::vector<std::string> find_pkgs(cmTokenize(value, ","));
+        std::vector<std::string> find_pkgs(cmTokenize(value, ','));
         std::cout << "Running with debug output on for the 'find' commands "
                      "for package(s)";
         for (auto const& v : find_pkgs) {
@@ -1151,7 +1145,7 @@
       "--debug-find-var", CommandArgument::Values::One,
       CommandArgument::RequiresSeparator::Yes,
       [](std::string const& value, cmake* state) -> bool {
-        std::vector<std::string> find_vars(cmTokenize(value, ","));
+        std::vector<std::string> find_vars(cmTokenize(value, ','));
         std::cout << "Running with debug output on for the variable(s)";
         for (auto const& v : find_vars) {
           std::cout << ' ' << v;
@@ -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",
@@ -1479,10 +1471,10 @@
   }
 
   if (!haveSourceDir) {
-    this->SetHomeDirectory(cmSystemTools::GetCurrentWorkingDirectory());
+    this->SetHomeDirectory(cmSystemTools::GetLogicalWorkingDirectory());
   }
   if (!haveBinaryDir) {
-    this->SetHomeOutputDirectory(cmSystemTools::GetCurrentWorkingDirectory());
+    this->SetHomeOutputDirectory(cmSystemTools::GetLogicalWorkingDirectory());
   }
 
 #if !defined(CMAKE_BOOTSTRAP)
@@ -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::GetLogicalWorkingDirectory());
       }
     } else if (no_source_tree && no_build_tree) {
       this->SetHomeDirectory(listPath);
-
-      std::string cwd = cmSystemTools::GetCurrentWorkingDirectory();
-      this->SetHomeOutputDirectory(cwd);
+      this->SetHomeOutputDirectory(
+        cmSystemTools::GetLogicalWorkingDirectory());
     } 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::GetLogicalWorkingDirectory());
       used_provided_path = false;
     }
   }
@@ -2373,7 +2362,6 @@
 int cmake::ActualConfigure()
 {
   // Construct right now our path conversion table before it's too late:
-  this->UpdateConversionPathTable();
   this->CleanupCommandsAndMacros();
 
   cmSystemTools::RemoveADirectory(this->GetHomeOutputDirectory() +
@@ -3214,31 +3202,6 @@
 #endif
 }
 
-void cmake::UpdateConversionPathTable()
-{
-  // Update the path conversion table with any specified file:
-  cmValue tablepath =
-    this->State->GetInitializedCacheValue("CMAKE_PATH_TRANSLATION_FILE");
-
-  if (tablepath) {
-    cmsys::ifstream table(tablepath->c_str());
-    if (!table) {
-      cmSystemTools::Error("CMAKE_PATH_TRANSLATION_FILE set to " + *tablepath +
-                           ". CMake can not open file.");
-      cmSystemTools::ReportLastSystemError("CMake can not open file.");
-    } else {
-      std::string a;
-      std::string b;
-      while (!table.eof()) {
-        // two entries per line
-        table >> a;
-        table >> b;
-        cmSystemTools::AddTranslationPath(a, b);
-      }
-    }
-  }
-}
-
 int cmake::CheckBuildSystem()
 {
   // We do not need to rerun CMake.  Check dependency integrity.
@@ -3479,7 +3442,7 @@
 {
   // so create the directory
   std::string resultFile;
-  std::string cwd = cmSystemTools::GetCurrentWorkingDirectory();
+  std::string cwd = cmSystemTools::GetLogicalWorkingDirectory();
   std::string destPath = cwd + "/__cmake_systeminformation";
   cmSystemTools::RemoveADirectory(destPath);
   if (!cmSystemTools::MakeDirectory(destPath)) {
@@ -3548,8 +3511,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;
@@ -3618,8 +3580,8 @@
 
 #if !defined(CMAKE_BOOTSTRAP)
   if (!presetName.empty() || listPresets) {
-    this->SetHomeDirectory(cmSystemTools::GetCurrentWorkingDirectory());
-    this->SetHomeOutputDirectory(cmSystemTools::GetCurrentWorkingDirectory());
+    this->SetHomeDirectory(cmSystemTools::GetLogicalWorkingDirectory());
+    this->SetHomeOutputDirectory(cmSystemTools::GetLogicalWorkingDirectory());
 
     cmCMakePresetsGraph settingsFile;
     auto result = settingsFile.ReadProjectPresets(this->GetHomeDirectory());
@@ -3964,8 +3926,8 @@
                     WorkflowListPresets listPresets, WorkflowFresh fresh)
 {
 #ifndef CMAKE_BOOTSTRAP
-  this->SetHomeDirectory(cmSystemTools::GetCurrentWorkingDirectory());
-  this->SetHomeOutputDirectory(cmSystemTools::GetCurrentWorkingDirectory());
+  this->SetHomeDirectory(cmSystemTools::GetLogicalWorkingDirectory());
+  this->SetHomeOutputDirectory(cmSystemTools::GetLogicalWorkingDirectory());
 
   cmCMakePresetsGraph settingsFile;
   auto result = settingsFile.ReadProjectPresets(this->GetHomeDirectory());
diff --git a/Source/cmake.h b/Source/cmake.h
index cfe4edd..d5c8bbb 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
   {
@@ -811,8 +805,6 @@
 
   std::unique_ptr<cmGlobalGenerator> GlobalGenerator;
 
-  void UpdateConversionPathTable();
-
   //! Print a list of valid generators to stderr.
   void PrintGeneratorList();
 
diff --git a/Source/cmakemain.cxx b/Source/cmakemain.cxx
index b57cdb3..985362a 100644
--- a/Source/cmakemain.cxx
+++ b/Source/cmakemain.cxx
@@ -219,7 +219,7 @@
 
 int do_cmake(int ac, char const* const* av)
 {
-  if (cmSystemTools::GetCurrentWorkingDirectory().empty()) {
+  if (cmSystemTools::GetLogicalWorkingDirectory().empty()) {
     std::cerr << "Current working directory cannot be established."
               << std::endl;
     return 1;
@@ -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/cmcldeps.cxx b/Source/cmcldeps.cxx
index 5310166..aa4fd76 100644
--- a/Source/cmcldeps.cxx
+++ b/Source/cmcldeps.cxx
@@ -24,6 +24,7 @@
 #include <windows.h>
 
 #include "cmsys/Encoding.hxx"
+#include "cmsys/SystemTools.hxx"
 
 #include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
@@ -151,7 +152,7 @@
   // FIXME should this be fatal or not? delete obj? delete d?
   if (!out)
     return;
-  std::string cwd = cmSystemTools::GetCurrentWorkingDirectory();
+  std::string cwd = cmsys::SystemTools::GetCurrentWorkingDirectory();
   replaceAll(cwd, "/", "\\");
   cwd += "\\";
 
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..388e96b 100644
--- a/Source/ctest.cxx
+++ b/Source/ctest.cxx
@@ -15,7 +15,6 @@
 #include "cmSystemTools.h"
 
 #include "CTest/cmCTestLaunch.h"
-#include "CTest/cmCTestScriptHandler.h"
 
 namespace {
 const cmDocumentationEntry cmDocumentationName = {
@@ -147,8 +146,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" },
@@ -185,11 +182,8 @@
     return cmCTestLaunch::Main(argc, argv);
   }
 
-  cmCTest inst;
-
-  if (cmSystemTools::GetCurrentWorkingDirectory().empty()) {
-    cmCTestLog(&inst, ERROR_MESSAGE,
-               "Current working directory cannot be established.\n");
+  if (cmSystemTools::GetLogicalWorkingDirectory().empty()) {
+    std::cerr << "Current working directory cannot be established.\n";
     return 1;
   }
 
@@ -200,18 +194,13 @@
       !(cmSystemTools::FileExists("CTestTestfile.cmake") ||
         cmSystemTools::FileExists("DartTestfile.txt"))) {
     if (argc == 1) {
-      cmCTestLog(&inst, ERROR_MESSAGE,
-                 "*********************************\n"
-                 "No test configuration file found!\n"
-                 "*********************************\n");
+      std::cerr << "*********************************\n"
+                   "No test configuration file found!\n"
+                   "*********************************\n";
     }
     cmDocumentation doc;
     doc.addCTestStandardDocSections();
     if (doc.CheckOptions(argc, argv)) {
-      // Construct and print requested documentation.
-      cmCTestScriptHandler* ch = inst.GetScriptHandler();
-      ch->CreateCMake();
-
       doc.SetShowGenerators(false);
       doc.SetName("ctest");
       doc.SetSection("Name", cmDocumentationName);
@@ -222,15 +211,8 @@
   }
 
   // copy the args to a vector
-  std::vector<std::string> args;
-  args.reserve(argc);
-  for (int i = 0; i < argc; ++i) {
-    args.emplace_back(argv[i]);
-  }
-  // run ctest
-  std::string output;
-  int res = inst.Run(args, &output);
-  cmCTestLog(&inst, OUTPUT, output);
+  auto args = std::vector<std::string>(argv, argv + argc);
 
-  return res;
+  // run ctest
+  return cmCTest{}.Run(args);
 }
diff --git a/Source/kwsys/CMakeLists.txt b/Source/kwsys/CMakeLists.txt
index 562d5e6..be510b1 100644
--- a/Source/kwsys/CMakeLists.txt
+++ b/Source/kwsys/CMakeLists.txt
@@ -410,14 +410,6 @@
 endif()
 
 if(KWSYS_USE_SystemTools)
-  if (NOT DEFINED KWSYS_SYSTEMTOOLS_USE_TRANSLATION_MAP)
-    set(KWSYS_SYSTEMTOOLS_USE_TRANSLATION_MAP 1)
-  endif ()
-  if (KWSYS_SYSTEMTOOLS_USE_TRANSLATION_MAP)
-    set(KWSYS_SYSTEMTOOLS_USE_TRANSLATION_MAP 1)
-  else ()
-    set(KWSYS_SYSTEMTOOLS_USE_TRANSLATION_MAP 0)
-  endif ()
   KWSYS_PLATFORM_CXX_TEST(KWSYS_CXX_HAS_SETENV
     "Checking whether CXX compiler has setenv" DIRECT)
   KWSYS_PLATFORM_CXX_TEST(KWSYS_CXX_HAS_UNSETENV
diff --git a/Source/kwsys/Configure.hxx.in b/Source/kwsys/Configure.hxx.in
index 8d47340..b4b0efa 100644
--- a/Source/kwsys/Configure.hxx.in
+++ b/Source/kwsys/Configure.hxx.in
@@ -11,9 +11,6 @@
 /* Whether <ext/stdio_filebuf.h> is available. */
 #define @KWSYS_NAMESPACE@_CXX_HAS_EXT_STDIO_FILEBUF_H                         \
   @KWSYS_CXX_HAS_EXT_STDIO_FILEBUF_H@
-/* Whether the translation map is available or not. */
-#define @KWSYS_NAMESPACE@_SYSTEMTOOLS_USE_TRANSLATION_MAP                     \
-  @KWSYS_SYSTEMTOOLS_USE_TRANSLATION_MAP@
 
 #if defined(__SUNPRO_CC) && __SUNPRO_CC > 0x5130 && defined(__has_attribute)
 #  define @KWSYS_NAMESPACE@_has_cpp_attribute(x) __has_attribute(x)
@@ -58,8 +55,6 @@
 #  define KWSYS_CXX_HAS_EXT_STDIO_FILEBUF_H                                   \
     @KWSYS_NAMESPACE@_CXX_HAS_EXT_STDIO_FILEBUF_H
 #  define KWSYS_FALLTHROUGH @KWSYS_NAMESPACE@_FALLTHROUGH
-#  define KWSYS_SYSTEMTOOLS_USE_TRANSLATION_MAP                               \
-    @KWSYS_NAMESPACE@_SYSTEMTOOLS_USE_TRANSLATION_MAP
 #endif
 
 #endif
diff --git a/Source/kwsys/ProcessWin32.c b/Source/kwsys/ProcessWin32.c
index 0b43b4a..c1a566f 100644
--- a/Source/kwsys/ProcessWin32.c
+++ b/Source/kwsys/ProcessWin32.c
@@ -666,14 +666,14 @@
   if (dir && dir[0]) {
     wchar_t* wdir = kwsysEncoding_DupToWide(dir);
     /* We must convert the working directory to a full path.  */
-    DWORD length = GetFullPathNameW(wdir, 0, 0, 0);
+    DWORD length = GetFullPathNameW(wdir, 0, NULL, NULL);
     if (length > 0) {
       wchar_t* work_dir = malloc(length * sizeof(wchar_t));
       if (!work_dir) {
         free(wdir);
         return 0;
       }
-      if (!GetFullPathNameW(wdir, length, work_dir, 0)) {
+      if (!GetFullPathNameW(wdir, length, work_dir, NULL)) {
         free(work_dir);
         free(wdir);
         return 0;
diff --git a/Source/kwsys/SystemTools.cxx b/Source/kwsys/SystemTools.cxx
index 6cc103d..5b57900 100644
--- a/Source/kwsys/SystemTools.cxx
+++ b/Source/kwsys/SystemTools.cxx
@@ -59,9 +59,6 @@
 
 #include <cctype>
 #include <cerrno>
-#ifdef __QNX__
-#  include <malloc.h> /* for malloc/free on QNX */
-#endif
 #include <cstdio>
 #include <cstdlib>
 #include <cstring>
@@ -338,10 +335,9 @@
                      std::string* errorMessage = nullptr)
 {
   std::wstring tmp = KWSYS_NAMESPACE::Encoding::ToWide(path);
-  wchar_t* ptemp;
   wchar_t fullpath[MAX_PATH];
   DWORD bufferLen = GetFullPathNameW(
-    tmp.c_str(), sizeof(fullpath) / sizeof(fullpath[0]), fullpath, &ptemp);
+    tmp.c_str(), sizeof(fullpath) / sizeof(fullpath[0]), fullpath, nullptr);
   if (bufferLen < sizeof(fullpath) / sizeof(fullpath[0])) {
     resolved_path = KWSYS_NAMESPACE::Encoding::ToNarrow(fullpath);
     KWSYS_NAMESPACE::SystemTools::ConvertToUnixSlashes(resolved_path);
@@ -494,75 +490,17 @@
   }
 };
 
-#ifdef _WIN32
-#  if defined(_WIN64)
-static constexpr size_t FNV_OFFSET_BASIS = 14695981039346656037ULL;
-static constexpr size_t FNV_PRIME = 1099511628211ULL;
-#  else
-static constexpr size_t FNV_OFFSET_BASIS = 2166136261U;
-static constexpr size_t FNV_PRIME = 16777619U;
-#  endif
-
-// Case insensitive Fnv1a hash
-struct SystemToolsPathCaseHash
-{
-  size_t operator()(std::string const& path) const
-  {
-    size_t hash = FNV_OFFSET_BASIS;
-    for (auto c : path) {
-      hash ^= static_cast<size_t>(std::tolower(c));
-      hash *= FNV_PRIME;
-    }
-
-    return hash;
-  }
-};
-
-struct SystemToolsPathCaseEqual
-{
-  bool operator()(std::string const& l, std::string const& r) const
-  {
-#  ifdef _MSC_VER
-    return _stricmp(l.c_str(), r.c_str()) == 0;
-#  elif defined(__GNUC__)
-    return strcasecmp(l.c_str(), r.c_str()) == 0;
-#  else
-    return SystemTools::Strucmp(l.c_str(), r.c_str()) == 0;
-#  endif
-  }
-};
-#endif
-
 /**
  * SystemTools static variables singleton class.
  */
 class SystemToolsStatic
 {
 public:
-  using StringMap = std::map<std::string, std::string>;
-#if KWSYS_SYSTEMTOOLS_USE_TRANSLATION_MAP
-  /**
-   * Path translation table from dir to refdir
-   * Each time 'dir' will be found it will be replace by 'refdir'
-   */
-  StringMap TranslationMap;
-#endif
 #ifdef _WIN32
-  static std::string GetCasePathName(std::string const& pathIn,
-                                     bool const cache);
-  static std::string GetActualCaseForPathCached(std::string const& path);
+  static std::string GetCasePathName(std::string const& pathIn);
   static const char* GetEnvBuffered(const char* key);
-  std::unordered_map<std::string, std::string, SystemToolsPathCaseHash,
-                     SystemToolsPathCaseEqual>
-    FindFileMap;
-  std::unordered_map<std::string, std::string, SystemToolsPathCaseHash,
-                     SystemToolsPathCaseEqual>
-    PathCaseMap;
   std::map<std::string, std::string> EnvMap;
 #endif
-#ifdef __CYGWIN__
-  StringMap Cyg2Win32Map;
-#endif
 
   /**
    * Actual implementation of ReplaceString.
@@ -589,8 +527,7 @@
 static SystemToolsStatic* SystemToolsStatics;
 
 #ifdef _WIN32
-std::string SystemToolsStatic::GetCasePathName(std::string const& pathIn,
-                                               bool const cache)
+std::string SystemToolsStatic::GetCasePathName(std::string const& pathIn)
 {
   std::string casePath;
 
@@ -643,30 +580,15 @@
         std::string test_str = casePath;
         test_str += path_components[idx];
 
-        bool found_in_cache = false;
-        if (cache) {
-          auto const it = SystemToolsStatics->FindFileMap.find(test_str);
-          if (it != SystemToolsStatics->FindFileMap.end()) {
-            path_components[idx] = it->second;
-            found_in_cache = true;
-          }
-        }
-
-        if (!found_in_cache) {
-          WIN32_FIND_DATAW findData;
-          HANDLE hFind =
-            ::FindFirstFileW(Encoding::ToWide(test_str).c_str(), &findData);
-          if (INVALID_HANDLE_VALUE != hFind) {
-            auto case_file_name = Encoding::ToNarrow(findData.cFileName);
-            if (cache) {
-              SystemToolsStatics->FindFileMap.emplace(test_str,
-                                                      case_file_name);
-            }
-            path_components[idx] = std::move(case_file_name);
-            ::FindClose(hFind);
-          } else {
-            converting = false;
-          }
+        WIN32_FIND_DATAW findData;
+        HANDLE hFind =
+          ::FindFirstFileW(Encoding::ToWide(test_str).c_str(), &findData);
+        if (INVALID_HANDLE_VALUE != hFind) {
+          auto case_file_name = Encoding::ToNarrow(findData.cFileName);
+          path_components[idx] = std::move(case_file_name);
+          ::FindClose(hFind);
+        } else {
+          converting = false;
         }
       }
     }
@@ -675,21 +597,6 @@
   }
   return casePath;
 }
-
-std::string SystemToolsStatic::GetActualCaseForPathCached(std::string const& p)
-{
-  std::string casePath;
-
-  auto it = SystemToolsStatics->PathCaseMap.find(p);
-  if (it != SystemToolsStatics->PathCaseMap.end()) {
-    casePath = it->second;
-  } else {
-    casePath = SystemToolsStatic::GetCasePathName(p, true);
-    SystemToolsStatics->PathCaseMap.emplace(p, casePath);
-  }
-
-  return casePath;
-}
 #endif
 
 // adds the elements of the env variable path to the arg passed in
@@ -824,25 +731,14 @@
 #elif defined(__CYGWIN__) || defined(__GLIBC__)
 /* putenv("A") removes A from the environment.  It must not put the
    memory in the environment because it does not have any "=" syntax.  */
+
 static int kwsysUnPutEnv(const std::string& env)
 {
   int err = 0;
-  size_t pos = env.find('=');
-  size_t const len = pos == std::string::npos ? env.size() : pos;
-  size_t const sz = len + 1;
-  char local_buf[256];
-  char* buf = sz > sizeof(local_buf) ? (char*)malloc(sz) : local_buf;
-  if (!buf) {
-    return -1;
-  }
-  strncpy(buf, env.c_str(), len);
-  buf[len] = 0;
-  if (putenv(buf) < 0 && errno != EINVAL) {
+  std::string buf = env.substr(0, env.find('='));
+  if (putenv(&buf[0]) < 0 && errno != EINVAL) {
     err = errno;
   }
-  if (buf != local_buf) {
-    free(buf);
-  }
   if (err) {
     errno = err;
     return -1;
@@ -3407,6 +3303,14 @@
   }
   std::wstring substituteName(substituteNameData, substituteNameLength);
   origName = Encoding::ToNarrow(substituteName);
+  // Symbolic links to absolute paths may use a NT Object Path prefix.
+  // If the path begins with "\??\UNC\", replace it with "\\".
+  // Otherwise, if the path begins with "\??\", remove the prefix.
+  if (origName.compare(0, 8, "\\??\\UNC\\") == 0) {
+    origName.erase(1, 6);
+  } else if (origName.compare(0, 4, "\\??\\") == 0) {
+    origName.erase(0, 4);
+  }
 #else
   char buf[KWSYS_SYSTEMTOOLS_MAXPATH + 1];
   int count = static_cast<int>(
@@ -3501,72 +3405,6 @@
   return true;
 }
 
-#if KWSYS_SYSTEMTOOLS_USE_TRANSLATION_MAP
-void SystemTools::AddTranslationPath(const std::string& a,
-                                     const std::string& b)
-{
-  std::string path_a = a;
-  std::string path_b = b;
-  SystemTools::ConvertToUnixSlashes(path_a);
-  SystemTools::ConvertToUnixSlashes(path_b);
-  // First check this is a directory path, since we don't want the table to
-  // grow too fat
-  if (SystemTools::FileIsDirectory(path_a)) {
-    // Make sure the path is a full path and does not contain no '..'
-    // Ken--the following code is incorrect. .. can be in a valid path
-    // for example  /home/martink/MyHubba...Hubba/Src
-    if (SystemTools::FileIsFullPath(path_b) &&
-        path_b.find("..") == std::string::npos) {
-      // Before inserting make sure path ends with '/'
-      if (!path_a.empty() && path_a.back() != '/') {
-        path_a += '/';
-      }
-      if (!path_b.empty() && path_b.back() != '/') {
-        path_b += '/';
-      }
-      if (!(path_a == path_b)) {
-        SystemToolsStatics->TranslationMap.insert(
-          SystemToolsStatic::StringMap::value_type(std::move(path_a),
-                                                   std::move(path_b)));
-      }
-    }
-  }
-}
-
-void SystemTools::AddKeepPath(const std::string& dir)
-{
-  std::string cdir;
-  Realpath(SystemTools::CollapseFullPath(dir), cdir);
-  SystemTools::AddTranslationPath(cdir, dir);
-}
-
-void SystemTools::CheckTranslationPath(std::string& path)
-{
-  // Do not translate paths that are too short to have meaningful
-  // translations.
-  if (path.size() < 2) {
-    return;
-  }
-
-  // Always add a trailing slash before translation.  It does not
-  // matter if this adds an extra slash, but we do not want to
-  // translate part of a directory (like the foo part of foo-dir).
-  path += '/';
-
-  // In case a file was specified we still have to go through this:
-  // Now convert any path found in the table back to the one desired:
-  for (auto const& pair : SystemToolsStatics->TranslationMap) {
-    // We need to check of the path is a substring of the other path
-    if (path.compare(0, pair.first.size(), pair.first) == 0) {
-      path = path.replace(0, pair.first.size(), pair.second);
-    }
-  }
-
-  // Remove the trailing slash we added before.
-  path.pop_back();
-}
-#endif
-
 static void SystemToolsAppendComponents(
   std::vector<std::string>& out_components,
   std::vector<std::string>::iterator first,
@@ -3629,25 +3467,7 @@
   // Transform the path back to a string.
   std::string newPath = SystemTools::JoinPath(out_components);
 
-#if KWSYS_SYSTEMTOOLS_USE_TRANSLATION_MAP
-  // Update the translation table with this potentially new path.  I am not
-  // sure why this line is here, it seems really questionable, but yet I
-  // would put good money that if I remove it something will break, basically
-  // from what I can see it created a mapping from the collapsed path, to be
-  // replaced by the input path, which almost completely does the opposite of
-  // this function, the only thing preventing this from happening a lot is
-  // that if the in_path has a .. in it, then it is not added to the
-  // translation table. So for most calls this either does nothing due to the
-  // ..  or it adds a translation between identical paths as nothing was
-  // collapsed, so I am going to try to comment it out, and see what hits the
-  // fan, hopefully quickly.
-  // Commented out line below:
-  // SystemTools::AddTranslationPath(newPath, in_path);
-
-  SystemTools::CheckTranslationPath(newPath);
-#endif
 #ifdef _WIN32
-  newPath = SystemToolsStatics->GetActualCaseForPathCached(newPath);
   SystemTools::ConvertToUnixSlashes(newPath);
 #endif
   // Return the reconstructed path.
@@ -3756,7 +3576,7 @@
 std::string SystemTools::GetActualCaseForPath(const std::string& p)
 {
 #ifdef _WIN32
-  return SystemToolsStatic::GetCasePathName(p, false);
+  return SystemToolsStatic::GetCasePathName(p);
 #else
   return p;
 #endif
@@ -4968,51 +4788,6 @@
 
   // Create statics singleton instance
   SystemToolsStatics = new SystemToolsStatic;
-
-#if KWSYS_SYSTEMTOOLS_USE_TRANSLATION_MAP
-// Add some special translation paths for unix.  These are not added
-// for windows because drive letters need to be maintained.  Also,
-// there are not sym-links and mount points on windows anyway.
-#  if !defined(_WIN32) || defined(__CYGWIN__)
-  // The tmp path is frequently a logical path so always keep it:
-  SystemTools::AddKeepPath("/tmp/");
-
-  // If the current working directory is a logical path then keep the
-  // logical name.
-  std::string pwd_str;
-  if (SystemTools::GetEnv("PWD", pwd_str)) {
-    char buf[2048];
-    if (const char* cwd = Getcwd(buf, 2048)) {
-      // The current working directory may be a logical path.  Find
-      // the shortest logical path that still produces the correct
-      // physical path.
-      std::string cwd_changed;
-      std::string pwd_changed;
-
-      // Test progressively shorter logical-to-physical mappings.
-      std::string cwd_str = cwd;
-      std::string pwd_path;
-      Realpath(pwd_str, pwd_path);
-      while (cwd_str == pwd_path && cwd_str != pwd_str) {
-        // The current pair of paths is a working logical mapping.
-        cwd_changed = cwd_str;
-        pwd_changed = pwd_str;
-
-        // Strip off one directory level and see if the logical
-        // mapping still works.
-        pwd_str = SystemTools::GetFilenamePath(pwd_str);
-        cwd_str = SystemTools::GetFilenamePath(cwd_str);
-        Realpath(pwd_str, pwd_path);
-      }
-
-      // Add the translation to keep the logical path name.
-      if (!cwd_changed.empty() && !pwd_changed.empty()) {
-        SystemTools::AddTranslationPath(cwd_changed, pwd_changed);
-      }
-    }
-  }
-#  endif
-#endif
 }
 
 void SystemTools::ClassFinalize()
diff --git a/Source/kwsys/SystemTools.hxx.in b/Source/kwsys/SystemTools.hxx.in
index 294ffca..03fa3bc 100644
--- a/Source/kwsys/SystemTools.hxx.in
+++ b/Source/kwsys/SystemTools.hxx.in
@@ -984,25 +984,6 @@
    */
   static int GetTerminalWidth();
 
-#if @KWSYS_NAMESPACE@_SYSTEMTOOLS_USE_TRANSLATION_MAP
-  /**
-   * Add an entry in the path translation table.
-   */
-  static void AddTranslationPath(const std::string& dir,
-                                 const std::string& refdir);
-
-  /**
-   * If dir is different after CollapseFullPath is called,
-   * Then insert it into the path translation table
-   */
-  static void AddKeepPath(const std::string& dir);
-
-  /**
-   * Update path by going through the Path Translation table;
-   */
-  static void CheckTranslationPath(std::string& path);
-#endif
-
   /**
    * Delay the execution for a specified amount of time specified
    * in milliseconds
@@ -1052,14 +1033,7 @@
   static std::string DecodeURL(const std::string& url);
 
 private:
-  /**
-   * Allocate the stl map that serve as the Path Translation table.
-   */
   static void ClassInitialize();
-
-  /**
-   * Deallocate the stl map that serve as the Path Translation table.
-   */
   static void ClassFinalize();
 
   /**
diff --git a/Tests/CMakeLib/CMakeLists.txt b/Tests/CMakeLib/CMakeLists.txt
index 5b189e7..de08d56 100644
--- a/Tests/CMakeLib/CMakeLists.txt
+++ b/Tests/CMakeLib/CMakeLists.txt
@@ -16,12 +16,14 @@
   testCTestResourceSpec.cxx
   testCTestResourceGroups.cxx
   testDebug.cxx
+  testDocumentationFormatter.cxx
   testGccDepfileReader.cxx
   testGeneratedFileStream.cxx
   testJSONHelpers.cxx
   testRST.cxx
   testRange.cxx
   testOptional.cxx
+  testPathResolver.cxx
   testString.cxx
   testStringAlgorithms.cxx
   testSystemTools.cxx
diff --git a/Tests/CMakeLib/testCommon.h b/Tests/CMakeLib/testCommon.h
index de4a689..56e5ef4 100644
--- a/Tests/CMakeLib/testCommon.h
+++ b/Tests/CMakeLib/testCommon.h
@@ -40,10 +40,9 @@
         break;
       }
     }
-    std::cout << '.';
   }
-  if (!result) {
-    std::cout << " Passed\n";
+  if (result == 0) {
+    std::cout << "Passed\n";
   }
   return result;
 }
diff --git a/Tests/CMakeLib/testDebuggerAdapter.cxx b/Tests/CMakeLib/testDebuggerAdapter.cxx
index a055cb7..b2c9458 100644
--- a/Tests/CMakeLib/testDebuggerAdapter.cxx
+++ b/Tests/CMakeLib/testDebuggerAdapter.cxx
@@ -132,6 +132,7 @@
   ASSERT_TRUE(initializeResponse.response.supportsExceptionInfoRequest);
   ASSERT_TRUE(
     initializeResponse.response.exceptionBreakpointFilters.has_value());
+  ASSERT_TRUE(initializeResponse.response.supportsValueFormattingOptions);
 
   dap::LaunchRequest launchRequest;
   auto launchResponse = client->send(launchRequest).get();
diff --git a/Tests/CMakeLib/testDebuggerAdapterPipe.cxx b/Tests/CMakeLib/testDebuggerAdapterPipe.cxx
index 3647088..312b0e2 100644
--- a/Tests/CMakeLib/testDebuggerAdapterPipe.cxx
+++ b/Tests/CMakeLib/testDebuggerAdapterPipe.cxx
@@ -20,9 +20,10 @@
 #include "cmVersionConfig.h"
 
 #ifdef _WIN32
+#  include "cmsys/SystemTools.hxx"
+
 #  include "cmCryptoHash.h"
 #  include "cmDebuggerWindowsPipeConnection.h"
-#  include "cmSystemTools.h"
 #else
 #  include "cmDebuggerPosixPipeConnection.h"
 #endif
@@ -69,7 +70,7 @@
 #ifdef _WIN32
   std::string namedPipe = R"(\\.\pipe\LOCAL\CMakeDebuggerPipe2_)" +
     cmCryptoHash(cmCryptoHash::AlgoSHA256)
-      .HashString(cmSystemTools::GetCurrentWorkingDirectory());
+      .HashString(cmsys::SystemTools::GetCurrentWorkingDirectory());
 #else
   std::string namedPipe = "CMakeDebuggerPipe2";
 #endif
diff --git a/Tests/CMakeLib/testDebuggerThread.cxx b/Tests/CMakeLib/testDebuggerThread.cxx
index 3b7fe6e..4eed247 100644
--- a/Tests/CMakeLib/testDebuggerThread.cxx
+++ b/Tests/CMakeLib/testDebuggerThread.cxx
@@ -2,6 +2,7 @@
 #include <string>
 #include <vector>
 
+#include <cm3p/cppdap/optional.h>
 #include <cm3p/cppdap/protocol.h>
 #include <cm3p/cppdap/types.h>
 
@@ -10,21 +11,52 @@
 
 #include "testCommon.h"
 
-static bool testStackFrameFunctionName()
+static bool testStackFrameFunctionName(
+  dap::optional<dap::StackFrameFormat> format, const char* expectedName)
 {
   auto thread = std::make_shared<cmDebugger::cmDebuggerThread>(0, "name");
   const auto* functionName = "function_name";
-  auto arguments = std::vector<cmListFileArgument>{};
+  auto arguments = std::vector<cmListFileArgument>{ cmListFileArgument(
+    "arg", cmListFileArgument::Delimiter::Unquoted, 0) };
   cmListFileFunction func(functionName, 10, 20, arguments);
   thread->PushStackFrame(nullptr, "CMakeLists.txt", func);
 
-  auto stackTrace = GetStackTraceResponse(thread);
+  auto stackTrace = GetStackTraceResponse(thread, format);
 
-  ASSERT_TRUE(stackTrace.stackFrames[0].name == functionName);
+  ASSERT_TRUE(stackTrace.stackFrames[0].name == expectedName);
   return true;
 }
 
+bool testStackFrameNoFormatting()
+{
+  return testStackFrameFunctionName({}, "function_name");
+}
+
+bool testStackFrameFormatParameters()
+{
+  dap::StackFrameFormat format;
+  format.parameters = true;
+  return testStackFrameFunctionName(format, "function_name()");
+}
+
+bool testStackFrameFormatParameterValues()
+{
+  dap::StackFrameFormat format;
+  format.parameters = true;
+  format.parameterValues = true;
+  return testStackFrameFunctionName(format, "function_name(arg)");
+}
+
+bool testStackFrameFormatLine()
+{
+  dap::StackFrameFormat format;
+  format.line = true;
+  return testStackFrameFunctionName(format, "function_name Line: 10");
+}
+
 int testDebuggerThread(int, char*[])
 {
-  return runTests({ testStackFrameFunctionName });
+  return runTests({ testStackFrameNoFormatting, testStackFrameFormatParameters,
+                    testStackFrameFormatParameterValues,
+                    testStackFrameFormatLine });
 }
diff --git a/Tests/CMakeLib/testDocumentationFormatter.cxx b/Tests/CMakeLib/testDocumentationFormatter.cxx
new file mode 100644
index 0000000..e332535
--- /dev/null
+++ b/Tests/CMakeLib/testDocumentationFormatter.cxx
@@ -0,0 +1,184 @@
+/* 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 = std::initializer_list<std::pair<std::string, std::string>>;
+
+bool testPrintFormattedNoIndent()
+{
+  const TestCases testCases = {
+    { "", "" },
+    { "\n\n", "\n\n\n\n" },
+    { "\n  \n\n", "\n\n  \n\n\n\n" },
+    { "One line no EOL text", "One line no EOL text\n" },
+    { "One line with trailing spaces and no EOL   ",
+      "One line with trailing spaces and no EOL\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" },
+    { " Two pre-formatted\n paragraphs w/o EOL",
+      " Two pre-formatted\n paragraphs w/o EOL\n" },
+    { " Two pre-formatted\n paragraphs w/ EOL\n",
+      " Two pre-formatted\n paragraphs w/ EOL\n\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" },
+    { "A normal paragraph,  followed by ...   \n Pre-formatted\n paragraphs "
+      "with trailing whitespaces     ",
+      "A normal paragraph, followed by ...\n\n Pre-formatted\n paragraphs "
+      "with trailing whitespaces     \n" },
+    { "A normal paragraph,  followed by ...   \n Pre-formatted\n paragraphs "
+      "with trailing whitespaces     \nAnd another paragraph w/o EOL",
+      "A normal paragraph, followed by ...\n\n Pre-formatted\n paragraphs "
+      "with trailing whitespaces     \n\nAnd another paragraph w/o EOL\n" },
+    { "A normal paragraph,  followed by ...   \n Pre-formatted\n paragraphs "
+      "with trailing whitespaces     \nAnd another paragraph w/ EOL\n",
+      "A normal paragraph, followed by ...\n\n Pre-formatted\n paragraphs "
+      "with trailing whitespaces     \n\nAnd another paragraph w/ EOL\n\n" },
+    { "A normal paragraph,  followed by ...   \n Pre-formatted\n paragraphs "
+      "with trailing whitespaces     \nAnd another two\nparagraphs w/ EOL\n",
+      "A normal paragraph, followed by ...\n\n Pre-formatted\n paragraphs "
+      "with trailing whitespaces     \n\nAnd another two\n\nparagraphs w/ "
+      "EOL\n\n" }
+  };
+
+  cmDocumentationFormatter formatter;
+
+  for (auto& test : testCases) {
+    std::ostringstream out;
+    formatter.PrintFormatted(out, test.first);
+    auto actual = out.str();
+    ASSERT_EQUAL(actual, test.second);
+  }
+
+  return true;
+}
+
+bool testPrintFormattedIndent2()
+{
+  const TestCases testCases = {
+    { "", "" },
+    // BEGIN NOTE Empty lines are not indented.
+    { "\n\n", "\n\n\n\n" },
+    { "\n  \n\n", "\n\n    \n\n\n\n" },
+    // END NOTE
+    { "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" },
+    { "A normal paragraph,  followed by ...   \n Pre-formatted\n paragraphs "
+      "with trailing whitespaces     ",
+      "  A normal paragraph, followed by ...\n\n   Pre-formatted\n   "
+      "paragraphs "
+      "with trailing whitespaces     \n" },
+    { "A normal paragraph,  followed by ...   \n Pre-formatted\n paragraphs "
+      "with trailing whitespaces     \nAnd another paragraph w/o EOL",
+      "  A normal paragraph, followed by ...\n\n   Pre-formatted\n   "
+      "paragraphs with trailing whitespaces     \n\n  And another paragraph "
+      "w/o EOL\n" },
+    { "A normal paragraph,  followed by ...   \n Pre-formatted\n paragraphs "
+      "with trailing whitespaces     \nAnd another paragraph w/ EOL\n",
+      "  A normal paragraph, followed by ...\n\n   Pre-formatted\n   "
+      "paragraphs with trailing whitespaces     \n\n  And another paragraph "
+      "w/ EOL\n\n" }
+  };
+
+  cmDocumentationFormatter formatter;
+  formatter.SetIndent(2);
+
+  for (auto& test : testCases) {
+    std::ostringstream out;
+    formatter.PrintFormatted(out, test.first);
+    auto actual = out.str();
+    ASSERT_EQUAL(actual, 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);
+    auto actual = out.str();
+    ASSERT_EQUAL(actual, test.second);
+  }
+
+  return true;
+}
+}
+
+int testDocumentationFormatter(int /*unused*/, char* /*unused*/[])
+{
+  return runTests({ testPrintFormattedNoIndent, testPrintFormattedIndent2,
+                    testPrintFormattedIndent10 },
+                  false);
+}
diff --git a/Tests/CMakeLib/testPathResolver.cxx b/Tests/CMakeLib/testPathResolver.cxx
new file mode 100644
index 0000000..4c4ff87
--- /dev/null
+++ b/Tests/CMakeLib/testPathResolver.cxx
@@ -0,0 +1,476 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include <cmConfigure.h> // IWYU pragma: keep
+
+#include <cerrno>
+#include <map>
+#include <string>
+#include <utility>
+
+#include <cmsys/Status.hxx>
+
+#include "cmPathResolver.h"
+
+#ifdef _WIN32
+#  include <cctype>
+
+#  include "cmSystemTools.h"
+#endif
+
+#include "testCommon.h"
+
+// IWYU pragma: no_forward_declare cm::PathResolver::Policies::LogicalPath
+// IWYU pragma: no_forward_declare cm::PathResolver::Policies::NaivePath
+// IWYU pragma: no_forward_declare cm::PathResolver::Policies::RealPath
+
+namespace {
+
+class MockSystem : public cm::PathResolver::System
+{
+public:
+  ~MockSystem() override = default;
+
+  struct Path
+  {
+    std::string Name;
+    std::string Link;
+  };
+
+  std::map<std::string, Path> Paths;
+
+  void SetPaths(std::map<std::string, Path> paths)
+  {
+    this->Paths = std::move(paths);
+  }
+
+  static std::string AdjustCase(std::string const& path)
+  {
+#ifdef _WIN32
+    return cmSystemTools::LowerCase(path);
+#else
+    return path;
+#endif
+  }
+
+  cmsys::Status ReadSymlink(std::string const& path,
+                            std::string& link) override
+  {
+    auto i = this->Paths.find(AdjustCase(path));
+    if (i == this->Paths.end()) {
+      return cmsys::Status::POSIX(ENOENT);
+    }
+    if (i->second.Link.empty()) {
+      return cmsys::Status::POSIX(EINVAL);
+    }
+    link = i->second.Link;
+    return cmsys::Status::Success();
+  }
+
+  bool PathExists(std::string const& path) override
+  {
+    return this->Paths.find(AdjustCase(path)) != this->Paths.end();
+  }
+
+  std::string WorkDir;
+
+  void SetWorkDir(std::string wd) { this->WorkDir = std::move(wd); }
+
+  std::string GetWorkingDirectory() override { return this->WorkDir; }
+
+#ifdef _WIN32
+  std::map<char, std::string> WorkDirOnDrive;
+
+  void SetWorkDirOnDrive(std::map<char, std::string> wd)
+  {
+    this->WorkDirOnDrive = std::move(wd);
+  }
+
+  std::string GetWorkingDirectoryOnDrive(char letter) override
+  {
+    std::string result;
+    auto i = this->WorkDirOnDrive.find(std::tolower(letter));
+    if (i != this->WorkDirOnDrive.end()) {
+      result = i->second;
+    }
+    return result;
+  }
+
+  cmsys::Status ReadName(std::string const& path, std::string& name) override
+  {
+    auto i = this->Paths.find(AdjustCase(path));
+    if (i == this->Paths.end()) {
+      return cmsys::Status::POSIX(ENOENT);
+    }
+    name = i->second.Name;
+    return cmsys::Status::Success();
+  }
+#endif
+};
+
+#define EXPECT_RESOLVE(_in, _expect)                                          \
+  do {                                                                        \
+    std::string out;                                                          \
+    ASSERT_TRUE(r.Resolve(_in, out));                                         \
+    ASSERT_EQUAL(out, _expect);                                               \
+  } while (false)
+
+#define EXPECT_ENOENT(_in, _expect)                                           \
+  do {                                                                        \
+    std::string out;                                                          \
+    ASSERT_EQUAL(r.Resolve(_in, out).GetPOSIX(), ENOENT);                     \
+    ASSERT_EQUAL(out, _expect);                                               \
+  } while (false)
+
+using namespace cm::PathResolver;
+
+bool posixRoot()
+{
+  std::cout << "posixRoot()\n";
+  MockSystem os;
+  os.SetPaths({
+    { "/", { {}, {} } },
+  });
+  Resolver<Policies::RealPath> const r(os);
+  EXPECT_RESOLVE("/", "/");
+  EXPECT_RESOLVE("//", "/");
+  EXPECT_RESOLVE("/.", "/");
+  EXPECT_RESOLVE("/./", "/");
+  EXPECT_RESOLVE("/..", "/");
+  EXPECT_RESOLVE("/../", "/");
+  return true;
+}
+
+bool posixAbsolutePath()
+{
+  std::cout << "posixAbsolutePath()\n";
+  MockSystem os;
+  os.SetPaths({
+    { "/", { {}, {} } },
+    { "/a", { {}, {} } },
+  });
+  Resolver<Policies::RealPath> const r(os);
+  EXPECT_RESOLVE("/a", "/a");
+  EXPECT_RESOLVE("/a/", "/a");
+  EXPECT_RESOLVE("/a//", "/a");
+  EXPECT_RESOLVE("/a/.", "/a");
+  EXPECT_RESOLVE("/a/./", "/a");
+  EXPECT_RESOLVE("/a/..", "/");
+  EXPECT_RESOLVE("/a/../", "/");
+  EXPECT_RESOLVE("/a/../..", "/");
+#ifndef _WIN32
+  EXPECT_RESOLVE("//a", "/a");
+#endif
+  return true;
+}
+
+bool posixWorkingDirectory()
+{
+  std::cout << "posixWorkingDirectory()\n";
+  MockSystem os;
+  os.SetPaths({
+    { "/", { {}, {} } },
+    { "/a", { {}, {} } },
+    { "/cwd", { {}, {} } },
+    { "/cwd/a", { {}, {} } },
+  });
+  Resolver<Policies::RealPath> const r(os);
+  EXPECT_RESOLVE("", "/");
+  EXPECT_RESOLVE(".", "/");
+  EXPECT_RESOLVE("..", "/");
+  EXPECT_RESOLVE("a", "/a");
+  os.SetWorkDir("/cwd");
+  EXPECT_RESOLVE("", "/cwd");
+  EXPECT_RESOLVE(".", "/cwd");
+  EXPECT_RESOLVE("..", "/");
+  EXPECT_RESOLVE("a", "/cwd/a");
+  return true;
+}
+
+bool posixSymlink()
+{
+  std::cout << "posixSymlink()\n";
+  MockSystem os;
+  os.SetPaths({
+    { "/", { {}, {} } },
+    { "/link-a", { {}, "a" } },
+    { "/link-a-excess", { {}, "a//." } },
+    { "/link-broken", { {}, "link-broken-dest" } },
+    { "/a", { {}, {} } },
+    { "/a/b", { {}, {} } },
+    { "/a/link-b", { {}, "b" } },
+    { "/a/b/link-c", { {}, "c" } },
+    { "/a/b/c", { {}, {} } },
+    { "/a/b/c/link-..|..", { {}, "../.." } },
+    { "/a/link-|1|2", { {}, "/1/2" } },
+    { "/1", { {}, {} } },
+    { "/1/2", { {}, {} } },
+    { "/1/2/3", { {}, {} } },
+  });
+
+  {
+    Resolver<Policies::LogicalPath> const r(os);
+    EXPECT_RESOLVE("/link-a", "/link-a");
+    EXPECT_RESOLVE("/link-a-excess", "/link-a-excess");
+    EXPECT_RESOLVE("/link-a-excess/b", "/link-a-excess/b");
+    EXPECT_RESOLVE("/link-broken", "/link-broken");
+    EXPECT_RESOLVE("/link-a/../missing", "/missing");
+    EXPECT_RESOLVE("/a/b/link-c", "/a/b/link-c");
+    EXPECT_RESOLVE("/a/link-b/c", "/a/link-b/c");
+    EXPECT_RESOLVE("/a/link-b/link-c/..", "/a/link-b");
+    EXPECT_RESOLVE("/a/b/c/link-..|..", "/a/b/c/link-..|..");
+    EXPECT_RESOLVE("/a/b/c/link-..|../link-b", "/a/b/c/link-..|../link-b");
+    EXPECT_RESOLVE("/a/link-|1|2/3", "/a/link-|1|2/3");
+    EXPECT_RESOLVE("/a/link-|1|2/../2/3", "/1/2/3");
+  }
+
+  {
+    Resolver<Policies::RealPath> const r(os);
+    EXPECT_RESOLVE("/link-a", "/a");
+    EXPECT_RESOLVE("/link-a-excess", "/a");
+    EXPECT_RESOLVE("/link-a-excess/b", "/a/b");
+    EXPECT_ENOENT("/link-broken", "/link-broken-dest");
+    EXPECT_ENOENT("/link-a/../missing", "/missing");
+    EXPECT_RESOLVE("/a/b/link-c", "/a/b/c");
+    EXPECT_RESOLVE("/a/link-b/c", "/a/b/c");
+    EXPECT_RESOLVE("/a/link-b/link-c/..", "/a/b");
+    EXPECT_RESOLVE("/a/b/c/link-..|..", "/a");
+    EXPECT_RESOLVE("/a/b/c/link-..|../link-b", "/a/b");
+    EXPECT_RESOLVE("/a/link-|1|2/3", "/1/2/3");
+  }
+
+  return true;
+}
+
+#ifdef _WIN32
+bool windowsRoot()
+{
+  std::cout << "windowsRoot()\n";
+  MockSystem os;
+  {
+    Resolver<Policies::NaivePath> const r(os);
+    EXPECT_RESOLVE("c:/", "c:/");
+    EXPECT_RESOLVE("C:/", "C:/");
+    EXPECT_RESOLVE("c://", "c:/");
+    EXPECT_RESOLVE("C:/.", "C:/");
+    EXPECT_RESOLVE("c:/./", "c:/");
+    EXPECT_RESOLVE("C:/..", "C:/");
+    EXPECT_RESOLVE("c:/../", "c:/");
+  }
+  os.SetPaths({
+    { "c:/", { {}, {} } },
+    { "//host/", { {}, {} } },
+  });
+  {
+    Resolver<Policies::RealPath> const r(os);
+    EXPECT_RESOLVE("c:/", "C:/");
+    EXPECT_RESOLVE("C:/", "C:/");
+    EXPECT_RESOLVE("c://", "C:/");
+    EXPECT_RESOLVE("C:/.", "C:/");
+    EXPECT_RESOLVE("c:/./", "C:/");
+    EXPECT_RESOLVE("C:/..", "C:/");
+    EXPECT_RESOLVE("c:/../", "C:/");
+    EXPECT_RESOLVE("//host", "//host/");
+    EXPECT_RESOLVE("//host/.", "//host/");
+    EXPECT_RESOLVE("//host/./", "//host/");
+    EXPECT_RESOLVE("//host/..", "//host/");
+    EXPECT_RESOLVE("//host/../", "//host/");
+  }
+  return true;
+}
+
+bool windowsAbsolutePath()
+{
+  std::cout << "windowsAbsolutePath()\n";
+  MockSystem os;
+  os.SetPaths({
+    { "c:/", { {}, {} } },
+    { "c:/a", { {}, {} } },
+  });
+  Resolver<Policies::RealPath> const r(os);
+  EXPECT_RESOLVE("c:/a", "C:/a");
+  EXPECT_RESOLVE("c:/a/", "C:/a");
+  EXPECT_RESOLVE("c:/a//", "C:/a");
+  EXPECT_RESOLVE("c:/a/.", "C:/a");
+  EXPECT_RESOLVE("c:/a/./", "C:/a");
+  EXPECT_RESOLVE("c:/a/..", "C:/");
+  EXPECT_RESOLVE("c:/a/../", "C:/");
+  EXPECT_RESOLVE("c:/a/../..", "C:/");
+  return true;
+}
+
+bool windowsActualCase()
+{
+  std::cout << "windowsActualCase()\n";
+  MockSystem os;
+  os.SetPaths({
+    { "c:/", { {}, {} } },
+    { "c:/mixed", { "MiXeD", {} } },
+    { "c:/mixed/link-mixed", { "LiNk-MiXeD", "mixed" } },
+    { "c:/mixed/mixed", { "MiXeD", {} } },
+    { "c:/mixed/link-c-mixed", { "LiNk-C-MiXeD", "C:/mIxEd" } },
+    { "c:/upper", { "UPPER", {} } },
+    { "c:/upper/link-upper", { "LINK-UPPER", "upper" } },
+    { "c:/upper/upper", { "UPPER", {} } },
+    { "c:/upper/link-c-upper", { "LINK-C-UPPER", "c:/upper" } },
+  });
+
+  {
+    Resolver<Policies::LogicalPath> const r(os);
+    EXPECT_RESOLVE("c:/mIxEd/MiSsInG", "C:/MiXeD/MiSsInG");
+    EXPECT_RESOLVE("c:/mIxEd/link-MiXeD", "C:/MiXeD/LiNk-MiXeD");
+    EXPECT_RESOLVE("c:/mIxEd/link-c-MiXeD", "C:/MiXeD/LiNk-C-MiXeD");
+    EXPECT_RESOLVE("c:/upper/mIsSiNg", "C:/UPPER/mIsSiNg");
+    EXPECT_RESOLVE("c:/upper/link-upper", "C:/UPPER/LINK-UPPER");
+    EXPECT_RESOLVE("c:/upper/link-c-upper", "C:/UPPER/LINK-C-UPPER");
+  }
+
+  {
+    Resolver<Policies::RealPath> const r(os);
+    EXPECT_ENOENT("c:/mIxEd/MiSsInG", "C:/MiXeD/MiSsInG");
+    EXPECT_RESOLVE("c:/mIxEd/link-MiXeD", "C:/MiXeD/MiXeD");
+    EXPECT_RESOLVE("c:/mIxEd/link-c-MiXeD", "C:/MiXeD");
+    EXPECT_ENOENT("c:/upper/mIsSiNg", "C:/UPPER/mIsSiNg");
+    EXPECT_RESOLVE("c:/upper/link-upper", "C:/UPPER/UPPER");
+    EXPECT_RESOLVE("c:/upper/link-c-upper", "C:/UPPER");
+  }
+
+  return true;
+}
+
+bool windowsWorkingDirectory()
+{
+  std::cout << "windowsWorkingDirectory()\n";
+  MockSystem os;
+  os.SetPaths({
+    { "c:/", { {}, {} } },
+    { "c:/a", { {}, {} } },
+    { "c:/cwd", { {}, {} } },
+    { "c:/cwd/a", { {}, {} } },
+  });
+  {
+    Resolver<Policies::LogicalPath> const r(os);
+    EXPECT_RESOLVE("", "/");
+    EXPECT_RESOLVE(".", "/");
+    EXPECT_RESOLVE("..", "/");
+    EXPECT_RESOLVE("a", "/a");
+  }
+  {
+    Resolver<Policies::RealPath> const r(os);
+    os.SetWorkDir("c:/cwd");
+    EXPECT_RESOLVE("", "C:/cwd");
+    EXPECT_RESOLVE(".", "C:/cwd");
+    EXPECT_RESOLVE("..", "C:/");
+    EXPECT_RESOLVE("a", "C:/cwd/a");
+    EXPECT_ENOENT("missing", "C:/cwd/missing");
+  }
+  return true;
+}
+
+bool windowsWorkingDirectoryOnDrive()
+{
+  std::cout << "windowsWorkingDirectoryOnDrive()\n";
+  MockSystem os;
+  os.SetWorkDir("c:/cwd");
+  os.SetWorkDirOnDrive({
+    { 'd', "d:/cwd-d" },
+  });
+  {
+    Resolver<Policies::NaivePath> const r(os);
+    EXPECT_RESOLVE("c:", "c:/cwd");
+    EXPECT_RESOLVE("c:.", "c:/cwd");
+    EXPECT_RESOLVE("c:..", "c:/");
+    EXPECT_RESOLVE("C:", "C:/cwd");
+    EXPECT_RESOLVE("C:.", "C:/cwd");
+    EXPECT_RESOLVE("C:..", "C:/");
+    EXPECT_RESOLVE("d:", "d:/cwd-d");
+    EXPECT_RESOLVE("d:.", "d:/cwd-d");
+    EXPECT_RESOLVE("d:..", "d:/");
+    EXPECT_RESOLVE("D:", "D:/cwd-d");
+    EXPECT_RESOLVE("D:.", "D:/cwd-d");
+    EXPECT_RESOLVE("D:..", "D:/");
+    EXPECT_RESOLVE("e:", "e:/");
+    EXPECT_RESOLVE("e:.", "e:/");
+    EXPECT_RESOLVE("e:..", "e:/");
+    EXPECT_RESOLVE("E:", "E:/");
+    EXPECT_RESOLVE("E:.", "E:/");
+    EXPECT_RESOLVE("E:..", "E:/");
+  }
+  os.SetPaths({
+    { "c:/", { {}, {} } },
+    { "c:/cwd", { {}, {} } },
+    { "c:/cwd/existing", { {}, {} } },
+    { "d:/", { {}, {} } },
+    { "d:/cwd-d", { {}, {} } },
+    { "d:/cwd-d/existing", { {}, {} } },
+    { "e:/", { {}, {} } },
+  });
+  {
+    Resolver<Policies::RealPath> const r(os);
+    EXPECT_RESOLVE("c:existing", "C:/cwd/existing");
+    EXPECT_ENOENT("c:missing", "C:/cwd/missing");
+    EXPECT_RESOLVE("C:existing", "C:/cwd/existing");
+    EXPECT_ENOENT("C:missing", "C:/cwd/missing");
+    EXPECT_RESOLVE("d:existing", "D:/cwd-d/existing");
+    EXPECT_ENOENT("d:missing", "D:/cwd-d/missing");
+    EXPECT_ENOENT("e:missing", "E:/missing");
+    EXPECT_ENOENT("f:", "F:/");
+  }
+  return true;
+}
+
+bool windowsNetworkShare()
+{
+  std::cout << "windowsNetworkShare()\n";
+  MockSystem os;
+  os.SetPaths({
+    { "c:/", { {}, {} } },
+    { "c:/cwd", { {}, {} } },
+    { "c:/cwd/link-to-host-share", { {}, "//host/share" } },
+    { "//host/", { {}, {} } },
+    { "//host/share", { {}, {} } },
+  });
+  os.SetWorkDir("c:/cwd");
+  {
+    Resolver<Policies::RealPath> const r(os);
+    EXPECT_RESOLVE("//host/share", "//host/share");
+    EXPECT_RESOLVE("//host/share/", "//host/share");
+    EXPECT_RESOLVE("//host/share/.", "//host/share");
+    EXPECT_RESOLVE("//host/share/./", "//host/share");
+    EXPECT_RESOLVE("//host/share/..", "//host/");
+    EXPECT_RESOLVE("//host/share/../", "//host/");
+    EXPECT_RESOLVE("//host/share/../..", "//host/");
+    EXPECT_RESOLVE("link-to-host-share", "//host/share");
+    EXPECT_RESOLVE("link-to-host-share/..", "//host/");
+    EXPECT_ENOENT("link-to-host-share/../missing", "//host/missing");
+  }
+
+  {
+    Resolver<Policies::LogicalPath> const r(os);
+    EXPECT_RESOLVE("link-to-host-share", "C:/cwd/link-to-host-share");
+    EXPECT_RESOLVE("link-to-host-share/..", "//host/");
+    EXPECT_RESOLVE("link-to-host-share/../missing", "//host/missing");
+  }
+  return true;
+}
+#endif
+
+}
+
+int testPathResolver(int /*unused*/, char* /*unused*/[])
+{
+  return runTests({
+    posixRoot,
+    posixAbsolutePath,
+    posixWorkingDirectory,
+    posixSymlink,
+#ifdef _WIN32
+    windowsRoot,
+    windowsAbsolutePath,
+    windowsActualCase,
+    windowsWorkingDirectory,
+    windowsWorkingDirectoryOnDrive,
+    windowsNetworkShare,
+#endif
+  });
+}
diff --git a/Tests/CMakeLib/testStringAlgorithms.cxx b/Tests/CMakeLib/testStringAlgorithms.cxx
index 78442ba..4d418b4 100644
--- a/Tests/CMakeLib/testStringAlgorithms.cxx
+++ b/Tests/CMakeLib/testStringAlgorithms.cxx
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include <cm/string_view>
+#include <cmext/string_view>
 
 #include "cmStringAlgorithms.h"
 
@@ -103,7 +104,9 @@
   {
     typedef std::vector<std::string> VT;
     assert_ok(cmTokenize("", ";") == VT{ "" }, "cmTokenize empty");
-    assert_ok(cmTokenize(";", ";") == VT{ "" }, "cmTokenize sep");
+    assert_ok(cmTokenize(";", ";") == VT{ "" }, "cmTokenize sep (char*)");
+    assert_ok(cmTokenize(";", ";"_s) == VT{ "" },
+              "cmTokenize sep (string_view)");
     assert_ok(cmTokenize("abc", ";") == VT{ "abc" }, "cmTokenize item");
     assert_ok(cmTokenize("abc;", ";") == VT{ "abc" }, "cmTokenize item sep");
     assert_ok(cmTokenize(";abc", ";") == VT{ "abc" }, "cmTokenize sep item");
@@ -112,6 +115,22 @@
     assert_ok(cmTokenize("a1;a2;a3;a4", ";") == VT{ "a1", "a2", "a3", "a4" },
               "cmTokenize multiple items");
   }
+  {
+    typedef std::vector<cm::string_view> VT;
+    assert_ok(cmTokenizedView("", ';') == VT{ "" }, "cmTokenizedView empty");
+    assert_ok(cmTokenizedView(";", ';') == VT{ "" }, "cmTokenizedView sep");
+    assert_ok(cmTokenizedView("abc", ';') == VT{ "abc" },
+              "cmTokenizedView item");
+    assert_ok(cmTokenizedView("abc;", ';') == VT{ "abc" },
+              "cmTokenizedView item sep");
+    assert_ok(cmTokenizedView(";abc", ';') == VT{ "abc" },
+              "cmTokenizedView sep item");
+    assert_ok(cmTokenizedView("abc;;efg", ';') == VT{ "abc", "efg" },
+              "cmTokenizedView item sep sep item");
+    assert_ok(cmTokenizedView("a1;a2;a3;a4", ';') ==
+                VT{ "a1", "a2", "a3", "a4" },
+              "cmTokenizedView multiple items");
+  }
 
   // ----------------------------------------------------------------------
   // Test cmStrCat
diff --git a/Tests/CMakeLib/testUVProcessChainHelper.cxx b/Tests/CMakeLib/testUVProcessChainHelper.cxx
index 1b4adb7..29ee765 100644
--- a/Tests/CMakeLib/testUVProcessChainHelper.cxx
+++ b/Tests/CMakeLib/testUVProcessChainHelper.cxx
@@ -7,7 +7,7 @@
 #include <string>
 #include <thread>
 
-#include "cmSystemTools.h"
+#include "cmsys/SystemTools.hxx"
 
 #ifdef _WIN32
 #  include <windows.h>
@@ -73,7 +73,7 @@
 #endif
   }
   if (command == "pwd") {
-    std::string cwd = cmSystemTools::GetCurrentWorkingDirectory();
+    std::string cwd = cmsys::SystemTools::GetCurrentWorkingDirectory();
     std::cout << cwd << std::flush;
     return 0;
   }
diff --git a/Tests/CMakeLib/testUVStreambuf.cxx b/Tests/CMakeLib/testUVStreambuf.cxx
index af06a8e..1f42727 100644
--- a/Tests/CMakeLib/testUVStreambuf.cxx
+++ b/Tests/CMakeLib/testUVStreambuf.cxx
@@ -106,6 +106,9 @@
   options.file = cmakeCommand;
   options.args = const_cast<char**>(processArgs.data());
   options.flags = UV_PROCESS_WINDOWS_HIDE;
+#if UV_VERSION_MAJOR > 1 || !defined(CMAKE_USE_SYSTEM_LIBUV)
+  options.flags |= UV_PROCESS_WINDOWS_USE_PARENT_ERROR_MODE;
+#endif
   options.stdio = stdio.data();
   options.stdio_count = static_cast<int>(stdio.size());
   options.exit_cb = [](uv_process_t* handle, int64_t exitStatus,
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/CTestTest/test.cmake.in b/Tests/CTestTest/test.cmake.in
index 23166a7..61d30a6 100644
--- a/Tests/CTestTest/test.cmake.in
+++ b/Tests/CTestTest/test.cmake.in
@@ -43,12 +43,6 @@
 CMAKE_GENERATOR:INTERNAL=@CMAKE_GENERATOR@
 CMAKE_GENERATOR_PLATFORM:INTERNAL=@CMAKE_GENERATOR_PLATFORM@
 CMAKE_GENERATOR_TOOLSET:INTERNAL=@CMAKE_GENERATOR_TOOLSET@
-CMAKE_CXX_FLAGS:STRING=@CMAKE_CXX_FLAGS@
-CMAKE_C_FLAGS:STRING=@CMAKE_C_FLAGS@
-CMAKE_C_COMPILER:STRING=@CMAKE_C_COMPILER@
-CMAKE_CXX_COMPILER:STRING=@CMAKE_CXX_COMPILER@
-CMAKE_C_COMPILER_ARG1:STRING=@CMAKE_C_COMPILER_ARG1@
-CMAKE_CXX_COMPILER_ARG1:STRING=@CMAKE_CXX_COMPILER_ARG1@
 DART_ROOT:PATH=
 MEMORYCHECK_COMMAND:STRING=@MEMORYCHECK_COMMAND@
 MEMORYCHECK_SUPPRESSIONS_FILE:FILEPATH=@MEMORYCHECK_SUPPRESSIONS_FILE@
diff --git a/Tests/CTestTest2/test.cmake.in b/Tests/CTestTest2/test.cmake.in
index 6d833fe..48fd82a 100644
--- a/Tests/CTestTest2/test.cmake.in
+++ b/Tests/CTestTest2/test.cmake.in
@@ -28,12 +28,6 @@
 #CTEST_EMPTY_BINARY_DIRECTORY(${CTEST_BINARY_DIRECTORY})
 
 file(WRITE "${CTEST_BINARY_DIRECTORY}/CMakeCache.txt" "
-CMAKE_CXX_FLAGS:STRING=@CMAKE_CXX_FLAGS@
-CMAKE_C_FLAGS:STRING=@CMAKE_C_FLAGS@
-CMAKE_C_COMPILER:STRING=@CMAKE_C_COMPILER@
-CMAKE_CXX_COMPILER:STRING=@CMAKE_CXX_COMPILER@
-CMAKE_C_COMPILER_ARG1:STRING=@CMAKE_C_COMPILER_ARG1@
-CMAKE_CXX_COMPILER_ARG1:STRING=@CMAKE_CXX_COMPILER_ARG1@
 KWSYS_ENCODING_DEFAULT_CODEPAGE:STRING=CP_UTF8
 
 # This one is needed for testing advanced ctest features
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/CTestTestLaunchers/test.cmake.in b/Tests/CTestTestLaunchers/test.cmake.in
index 21a3ed4..3c99857 100644
--- a/Tests/CTestTestLaunchers/test.cmake.in
+++ b/Tests/CTestTestLaunchers/test.cmake.in
@@ -22,15 +22,6 @@
 
   ctest_empty_binary_directory(${CTEST_BINARY_DIRECTORY})
 
-  file(WRITE "${CTEST_BINARY_DIRECTORY}/CMakeCache.txt" "
-  CMAKE_CXX_FLAGS:STRING=@CMAKE_CXX_FLAGS@
-  CMAKE_C_FLAGS:STRING=@CMAKE_C_FLAGS@
-  CMAKE_C_COMPILER:STRING=@CMAKE_C_COMPILER@
-  CMAKE_CXX_COMPILER:STRING=@CMAKE_CXX_COMPILER@
-  CMAKE_C_COMPILER_ARG1:STRING=@CMAKE_C_COMPILER_ARG1@
-  CMAKE_CXX_COMPILER_ARG1:STRING=@CMAKE_CXX_COMPILER_ARG1@
-  ")
-
   ctest_start(Experimental)
   ctest_configure(OPTIONS "-DCTEST_USE_LAUNCHERS=1")
   ctest_build(NUMBER_ERRORS error_count)
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/CTestTestUpload/sleep.c b/Tests/CTestTestUpload/sleep.c
index b9b6e89..2d69f7f 100644
--- a/Tests/CTestTestUpload/sleep.c
+++ b/Tests/CTestTestUpload/sleep.c
@@ -1,3 +1,5 @@
+#include <stdlib.h>
+
 #if defined(_WIN32)
 #  include <windows.h>
 #else
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..46940a3 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)
@@ -550,10 +550,10 @@
 ############################################################################
 ##Test FIND_PACKAGE using sorting
 set(CMAKE_PREFIX_PATH ${CMAKE_CURRENT_SOURCE_DIR})
-SET(CMAKE_FIND_PACKAGE_SORT_ORDER NAME)
-SET(CMAKE_FIND_PACKAGE_SORT_DIRECTION ASC)
 
 set(SortLib_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
+SET(CMAKE_FIND_PACKAGE_SORT_ORDER NAME)
+SET(CMAKE_FIND_PACKAGE_SORT_DIRECTION ASC)
 FIND_PACKAGE(SortLib CONFIG)
 IF (NOT "${SortLib_VERSION}" STREQUAL "3.1.1")
   message(SEND_ERROR "FIND_PACKAGE_SORT_ORDER Name Asc! ${SortLib_VERSION}")
@@ -579,6 +579,28 @@
 endif()
 unset(SortLib_VERSION)
 
+
+set(SortFramework_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
+SET(CMAKE_FIND_PACKAGE_SORT_ORDER NAME)
+SET(CMAKE_FIND_PACKAGE_SORT_DIRECTION ASC)
+FIND_PACKAGE(SortFramework CONFIG)
+IF (NOT "${SortFramework_VERSION}" STREQUAL "3.1.1")
+  message(SEND_ERROR "FIND_PACKAGE_SORT_ORDER Framework Name Asc! ${SortFramework_VERSION}")
+endif()
+set(SortLib_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
+unset(SortFramework_VERSION)
+
+
+set(SortFramework_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
+SET(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL)
+SET(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC)
+FIND_PACKAGE(SortFramework CONFIG)
+IF (NOT "${SortFramework_VERSION}" STREQUAL "3.10.1")
+  message(SEND_ERROR "FIND_PACKAGE_SORT_ORDER Framework Natural! Dec ${SortFramework_VERSION}")
+endif()
+set(SortLib_DIR "" CACHE FILEPATH "Wipe out find results for testing." FORCE)
+unset(SortFramework_VERSION)
+
 unset(CMAKE_FIND_PACKAGE_SORT_ORDER)
 unset(CMAKE_FIND_PACKAGE_SORT_DIRECTION)
 set(CMAKE_PREFIX_PATH )
diff --git a/Tests/FindPackageTest/SortFramework.framework/Versions/3.1.1/Resources/CMake/SortFrameworkConfig.cmake b/Tests/FindPackageTest/SortFramework.framework/Versions/3.1.1/Resources/CMake/SortFrameworkConfig.cmake
new file mode 100644
index 0000000..f51edb2
--- /dev/null
+++ b/Tests/FindPackageTest/SortFramework.framework/Versions/3.1.1/Resources/CMake/SortFrameworkConfig.cmake
@@ -0,0 +1,2 @@
+set(SORT_FRAMEWORK_VERSION 3.1.1)
+message("SortFramework 3.1.1 config reached")
diff --git a/Tests/FindPackageTest/SortFramework.framework/Versions/3.1.1/Resources/CMake/SortFrameworkConfigVersion.cmake b/Tests/FindPackageTest/SortFramework.framework/Versions/3.1.1/Resources/CMake/SortFrameworkConfigVersion.cmake
new file mode 100644
index 0000000..fa927c7
--- /dev/null
+++ b/Tests/FindPackageTest/SortFramework.framework/Versions/3.1.1/Resources/CMake/SortFrameworkConfigVersion.cmake
@@ -0,0 +1,9 @@
+set(PACKAGE_VERSION 3.1.1)
+if(PACKAGE_FIND_VERSION_MAJOR EQUAL 3)
+  if(PACKAGE_FIND_VERSION_MINOR EQUAL 1)
+    set(PACKAGE_VERSION_COMPATIBLE 1)
+    if(PACKAGE_FIND_VERSION_PATCH EQUAL 1)
+      set(PACKAGE_VERSION_EXACT 1)
+    endif()
+  endif()
+endif()
diff --git a/Tests/FindPackageTest/SortFramework.framework/Versions/3.10.1/Resources/CMake/SortFrameworkConfig.cmake b/Tests/FindPackageTest/SortFramework.framework/Versions/3.10.1/Resources/CMake/SortFrameworkConfig.cmake
new file mode 100644
index 0000000..f7f05ef
--- /dev/null
+++ b/Tests/FindPackageTest/SortFramework.framework/Versions/3.10.1/Resources/CMake/SortFrameworkConfig.cmake
@@ -0,0 +1,2 @@
+set(SORT_FRAMEWORK_VERSION 3.10.1)
+message("SortFramework 3.10.1 config reached")
diff --git a/Tests/FindPackageTest/SortFramework.framework/Versions/3.10.1/Resources/CMake/SortFrameworkConfigVersion.cmake b/Tests/FindPackageTest/SortFramework.framework/Versions/3.10.1/Resources/CMake/SortFrameworkConfigVersion.cmake
new file mode 100644
index 0000000..6f44c2d
--- /dev/null
+++ b/Tests/FindPackageTest/SortFramework.framework/Versions/3.10.1/Resources/CMake/SortFrameworkConfigVersion.cmake
@@ -0,0 +1,9 @@
+set(PACKAGE_VERSION 3.10.1)
+if(PACKAGE_FIND_VERSION_MAJOR EQUAL 3)
+  if(PACKAGE_FIND_VERSION_MINOR EQUAL 10)
+    set(PACKAGE_VERSION_COMPATIBLE 1)
+    if(PACKAGE_FIND_VERSION_PATCH EQUAL 1)
+      set(PACKAGE_VERSION_EXACT 1)
+    endif()
+  endif()
+endif()
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/FortranC/Flags.cmake.in b/Tests/FortranC/Flags.cmake.in
index cf361a5..83ceef0 100644
--- a/Tests/FortranC/Flags.cmake.in
+++ b/Tests/FortranC/Flags.cmake.in
@@ -5,6 +5,9 @@
 # flags, remove them, and invoke the real compiler.
 set(ID "CC")
 set(COMMAND "@CMAKE_C_COMPILER@")
+if("x${COMMAND}" MATCHES "xctoolchain(/usr/bin/cc)$")
+  set(COMMAND "${CMAKE_MATCH_1}")
+endif()
 configure_file("${src}/test_opt.sh.in" "${bld}/cc.sh" @ONLY)
 set(ID "FC")
 set(COMMAND "@CMAKE_Fortran_COMPILER@")
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/BuildDepends/CustomCommandDepfile.cmake b/Tests/RunCMake/BuildDepends/CustomCommandDepfile.cmake
index c3725a4..331d21d 100644
--- a/Tests/RunCMake/BuildDepends/CustomCommandDepfile.cmake
+++ b/Tests/RunCMake/BuildDepends/CustomCommandDepfile.cmake
@@ -77,5 +77,14 @@
     \"${CMAKE_BINARY_DIR}/step3.timestamp|$<TARGET_FILE:subexe>\"
     \"${CMAKE_BINARY_DIR}/step3.timestamp|$<TARGET_FILE:sublib>\"
     )
+
+  if (RunCMake_GENERATOR MATCHES \"Make\")
+    file(STRINGS \"${CMAKE_BINARY_DIR}/CMakeFiles/topcc.dir/compiler_depend.internal\" deps REGEX \"^.*topccdep\\\\.txt$\")
+    list(LENGTH deps count)
+    if (NOT count EQUAL 1)
+       string(APPEND RunCMake_TEST_FAILED \"dependencies are duplicated\\n\")
+       set(RunCMake_TEST_FAILED \"\${RunCMake_TEST_FAILED}\" PARENT_SCOPE)
+    endif()
+  endif()
 endif()
 ")
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..c118be1 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/Tests/RunCMake/CMakeLists.txt
@@ -875,6 +875,9 @@
                                                    -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_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}
+                                                      -DCMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID}
+                                                      -DCMAKE_C_COMPILER_FRONTEND_VARIANT=${CMAKE_C_COMPILER_FRONTEND_VARIANT})
 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/CMakePresets/RunCMakeTest.cmake b/Tests/RunCMake/CMakePresets/RunCMakeTest.cmake
index a92a4c4..b0833d3 100644
--- a/Tests/RunCMake/CMakePresets/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CMakePresets/RunCMakeTest.cmake
@@ -76,11 +76,16 @@
     set(_preset)
   endif()
 
+  set(_make_program)
+  if(RunCMake_MAKE_PROGRAM)
+    set(_make_program -DCMAKE_MAKE_PROGRAM=${RunCMake_MAKE_PROGRAM})
+  endif()
+
   set(RunCMake_TEST_COMMAND ${CMAKE_COMMAND}
     ${_source_args}
     -DRunCMake_TEST=${name}
     -DRunCMake_GENERATOR=${RunCMake_GENERATOR}
-    -DCMAKE_MAKE_PROGRAM=${RunCMake_MAKE_PROGRAM}
+    ${_make_program}
     ${_unused_cli}
     ${_preset}
     ${ARGN}
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/CTestResourceAllocation/test.cmake.in b/Tests/RunCMake/CTestResourceAllocation/test.cmake.in
index 319ebf1..3237ef0 100644
--- a/Tests/RunCMake/CTestResourceAllocation/test.cmake.in
+++ b/Tests/RunCMake/CTestResourceAllocation/test.cmake.in
@@ -31,10 +31,18 @@
   list(APPEND config_options "-DCTEST_RESOURCE_SPEC_FILE=@RunCMake_SOURCE_DIR@/resspec.json")
 endif()
 
+set(test_args)
+if(DEFINED CTEST_PARALLEL)
+  list(APPEND test_args PARALLEL_LEVEL ${CTEST_PARALLEL})
+endif()
+if(DEFINED CTEST_RANDOM)
+  list(APPEND test_args SCHEDULE_RANDOM ${CTEST_RANDOM})
+endif()
+
 ctest_start(Experimental QUIET)
 ctest_configure(OPTIONS "${config_options}")
 ctest_build()
-ctest_test(${resspec} RETURN_VALUE retval PARALLEL_LEVEL ${CTEST_PARALLEL} SCHEDULE_RANDOM ${CTEST_RANDOM})
+ctest_test(${resspec} RETURN_VALUE retval ${test_args})
 if(retval)
   message(FATAL_ERROR "Tests did not pass")
 endif()
diff --git a/Tests/RunCMake/CTestTimeout/RunCMakeTest.cmake b/Tests/RunCMake/CTestTimeout/RunCMakeTest.cmake
index 470bbd8..6b4b96a 100644
--- a/Tests/RunCMake/CTestTimeout/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CTestTimeout/RunCMakeTest.cmake
@@ -34,7 +34,7 @@
     target_compile_definitions(TestTimeout PRIVATE SIGNAL)
     set_tests_properties(TestTimeout PROPERTIES
       TIMEOUT_SIGNAL_NAME SIGUSR1
-      TIMEOUT_SIGNAL_GRACE_PERIOD 1.2
+      TIMEOUT_SIGNAL_GRACE_PERIOD 32.1
       )
 ]])
   run_ctest_timeout(Signal)
@@ -91,7 +91,7 @@
 block()
   set(TIMEOUT 4)
   set(CASE_TEST_PREFIX_CODE "set(CTEST_TEST_TIMEOUT 2)")
-  set(CASE_CMAKELISTS_SUFFIX_CODE "set_property(TEST TestTimeout PROPERTY TIMEOUT 10)\n")
+  set(CASE_CMAKELISTS_SUFFIX_CODE "set_property(TEST TestTimeout PROPERTY TIMEOUT 60)\n")
   run_ctest_timeout(PropertyOverridesVar)
 endblock()
 
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/Configure/RerunCMake-build5-check.cmake b/Tests/RunCMake/Configure/RerunCMake-build5-check.cmake
new file mode 100644
index 0000000..d740671
--- /dev/null
+++ b/Tests/RunCMake/Configure/RerunCMake-build5-check.cmake
@@ -0,0 +1,4 @@
+file(READ ${stamp} content)
+if(NOT content STREQUAL 5)
+  set(RunCMake_TEST_FAILED "Expected stamp '5' but got: '${content}'")
+endif()
diff --git a/Tests/RunCMake/Configure/RunCMakeTest.cmake b/Tests/RunCMake/Configure/RunCMakeTest.cmake
index 00d3272..9b686e4 100644
--- a/Tests/RunCMake/Configure/RunCMakeTest.cmake
+++ b/Tests/RunCMake/Configure/RunCMakeTest.cmake
@@ -37,6 +37,15 @@
     set(RunCMake_TEST_OUTPUT_MERGE 0)
     run_cmake_command(RerunCMake-build4 ${CMAKE_COMMAND} --build .)
   endif()
+  if(RunCMake_GENERATOR MATCHES "^Ninja")
+    file(REMOVE "${error}")
+    run_cmake(RerunCMake)
+    execute_process(COMMAND ${CMAKE_COMMAND} -E sleep 1) # handle 1s resolution
+    # remove cmake_install.cmake to trigger rerun
+    file(REMOVE "${RunCMake_TEST_BINARY_DIR}/cmake_install.cmake")
+    file(WRITE "${input}" "5")
+    run_cmake_command(RerunCMake-build5 ${CMAKE_COMMAND} --build .)
+  endif()
 endblock()
 
 block()
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/apple_exe_framework.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/apple_exe_framework.json
index a4c13a8..dd49b39 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/apple_exe_framework.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/apple_exe_framework.json
@@ -26,7 +26,7 @@
                     "backtrace": null
                 }
             ],
-            "compileCommandFragments": []
+            "compileCommandFragments": null
         }
     ],
     "link": {
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_cross_emulator.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_cross_emulator.json
index bbd973b..a1b15ca 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_cross_emulator.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_cross_emulator.json
@@ -87,13 +87,7 @@
     "link": {
         "language": "CXX",
         "lto": null,
-        "commandFragments": [
-            {
-                "fragment" : ".*",
-                "role" : "flags",
-                "backtrace": null
-            }
-        ]
+        "commandFragments": null
     },
     "archive": null,
     "dependencies": [
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_cross_emulator_args.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_cross_emulator_args.json
index c1a8b0c..24a7550 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_cross_emulator_args.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_cross_emulator_args.json
@@ -91,13 +91,7 @@
     "link": {
         "language": "CXX",
         "lto": null,
-        "commandFragments": [
-            {
-                "fragment" : ".*",
-                "role" : "flags",
-                "backtrace": null
-            }
-        ]
+        "commandFragments": null
     },
     "archive": null,
     "dependencies": [
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_test_launcher.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_test_launcher.json
index 9002368..f5e365c 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_test_launcher.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_test_launcher.json
@@ -87,13 +87,7 @@
     "link": {
         "language": "CXX",
         "lto": null,
-        "commandFragments": [
-            {
-                "fragment" : ".*",
-                "role" : "flags",
-                "backtrace": null
-            }
-        ]
+        "commandFragments": null
     },
     "archive": null,
     "dependencies": [
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_test_launcher_and_cross_emulator.json b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_test_launcher_and_cross_emulator.json
index 06e7a7b..8facf86 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_test_launcher_and_cross_emulator.json
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-data/targets/cxx_exe_test_launcher_and_cross_emulator.json
@@ -91,13 +91,7 @@
     "link": {
         "language": "CXX",
         "lto": null,
-        "commandFragments": [
-            {
-                "fragment" : ".*",
-                "role" : "flags",
-                "backtrace": null
-            }
-        ]
+        "commandFragments": null
     },
     "archive": null,
     "dependencies": [
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/TargetPolicies/PolicyList-stderr.txt b/Tests/RunCMake/TargetPolicies/PolicyList-stderr.txt
index 4015ae8..4f65b85 100644
--- a/Tests/RunCMake/TargetPolicies/PolicyList-stderr.txt
+++ b/Tests/RunCMake/TargetPolicies/PolicyList-stderr.txt
@@ -44,6 +44,7 @@
    \* CMP0160
    \* CMP0162
    \* CMP0179
+   \* CMP0181
 
 Call Stack \(most recent call first\):
   CMakeLists.txt:3 \(include\)
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/CustomCommandParallelDisable-check.cmake b/Tests/RunCMake/VS10Project/CustomCommandParallelDisable-check.cmake
new file mode 100644
index 0000000..e0608f6
--- /dev/null
+++ b/Tests/RunCMake/VS10Project/CustomCommandParallelDisable-check.cmake
@@ -0,0 +1,42 @@
+# Check whether the 'BuildInParallel' setting is set as expected for a specified project file.
+# Note: if the setting is not present in the project file then it is assumed to be implicitly 'false'.
+function(check_build_in_parallel_setting projectFile expectedEnabled)
+  set(SettingEnabledRegex "<BuildInParallel.*>true</BuildInParallel>")
+  set(SettingDisabledRegex "<BuildInParallel.*>false</BuildInParallel>")
+
+  if(NOT EXISTS "${projectFile}")
+    set(RunCMake_TEST_FAILED "Project file '${projectFile}' does not exist." PARENT_SCOPE)
+    return()
+  endif()
+
+  set(settingEnabled FALSE)
+  set(settingExplicitlyDisabled FALSE)
+
+  file(STRINGS "${projectFile}" lines)
+
+  foreach(line IN LISTS lines)
+    if(line MATCHES "${SettingEnabledRegex}")
+      set(settingEnabled TRUE)
+    elseif(line MATCHES "${SettingDisabledRegex}")
+      set(settingExplicitlyDisabled TRUE)
+    endif()
+  endforeach()
+
+  if(expectedEnabled)
+    if(NOT settingEnabled)
+      set(RunCMake_TEST_FAILED "Expected 'BuildInParallel' to be enabled for projectFile '${projectFile}' but it was not!" PARENT_SCOPE)
+    endif()
+    if(settingExplicitlyDisabled)
+      set(RunCMake_TEST_FAILED "Expected 'BuildInParallel' to be enabled for projectFile '${projectFile}' but instead found it explicitly disabled!" PARENT_SCOPE)
+    endif()
+  else()
+    if(settingEnabled)
+      set(RunCMake_TEST_FAILED "Expected 'BuildInParallel' to be disabled for projectFile '${projectFile}' but it was not!")
+    endif()
+  endif()
+endfunction()
+
+check_build_in_parallel_setting("${RunCMake_TEST_BINARY_DIR}/foo1.vcxproj" TRUE)
+check_build_in_parallel_setting("${RunCMake_TEST_BINARY_DIR}/bar1.vcxproj" FALSE)
+check_build_in_parallel_setting("${RunCMake_TEST_BINARY_DIR}/foo2.vcxproj" FALSE)
+check_build_in_parallel_setting("${RunCMake_TEST_BINARY_DIR}/bar2.vcxproj" FALSE)
diff --git a/Tests/RunCMake/VS10Project/CustomCommandParallelDisable.cmake b/Tests/RunCMake/VS10Project/CustomCommandParallelDisable.cmake
new file mode 100644
index 0000000..b9ea3ab
--- /dev/null
+++ b/Tests/RunCMake/VS10Project/CustomCommandParallelDisable.cmake
@@ -0,0 +1,21 @@
+block()
+  cmake_policy(SET CMP0147 NEW) # Build custom commands in parallel by default
+
+  add_custom_command(OUTPUT "foo.out.txt" COMMAND echo Foo > foo.out.txt MAIN_DEPENDENCY "foo.txt")
+  add_custom_command(OUTPUT "bar.out.txt" COMMAND echo Bar > bar.out.txt MAIN_DEPENDENCY "bar.txt")
+  set_property(SOURCE "bar.txt" PROPERTY VS_CUSTOM_COMMAND_DISABLE_PARALLEL_BUILD TRUE)
+
+  add_custom_target(foo1 SOURCES foo.txt)
+  add_custom_target(bar1 SOURCES bar.txt)
+endblock()
+
+block()
+  cmake_policy(SET CMP0147 OLD) # Don't build custom commands in parallel by default
+
+  add_custom_command(OUTPUT "foo.out.cpp" COMMAND echo Foo > foo.out.txt MAIN_DEPENDENCY "foo.cpp")
+  add_custom_command(OUTPUT "bar.out.cpp" COMMAND echo Bar > bar.out.txt MAIN_DEPENDENCY "bar.cpp")
+  set_property(SOURCE "bar.cpp" PROPERTY VS_CUSTOM_COMMAND_DISABLE_PARALLEL_BUILD TRUE)
+
+  add_custom_target(foo2 SOURCES foo.cpp)
+  add_custom_target(bar2 SOURCES bar.cpp)
+endblock()
diff --git a/Tests/RunCMake/VS10Project/RunCMakeTest.cmake b/Tests/RunCMake/VS10Project/RunCMakeTest.cmake
index bbd8c8b..49c345c 100644
--- a/Tests/RunCMake/VS10Project/RunCMakeTest.cmake
+++ b/Tests/RunCMake/VS10Project/RunCMakeTest.cmake
@@ -10,6 +10,7 @@
 run_cmake(CustomCommandGenex)
 if(NOT RunCMake_GENERATOR MATCHES "^Visual Studio 1[1-5] ")
   run_cmake(CustomCommandParallel)
+  run_cmake(CustomCommandParallelDisable)
 endif()
 run_cmake_with_options(VsCharacterSet -DSET_CHARSET=MultiByte)
 run_cmake_with_options(VsCharacterSet -DSET_CHARSET=Unicode)
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/XcFramework/RunCMakeTest.cmake b/Tests/RunCMake/XcFramework/RunCMakeTest.cmake
index 64505ff..8c8d970 100644
--- a/Tests/RunCMake/XcFramework/RunCMakeTest.cmake
+++ b/Tests/RunCMake/XcFramework/RunCMakeTest.cmake
@@ -104,17 +104,17 @@
 create_xcframework(incomplete framework "tvos;watchos")
 create_executables(library library)
 create_executables(framework framework)
-run_cmake_with_options(create-executable-incomplete -DCMAKE_SYSTEM_NAME=Darwin "-DCMAKE_OSX_ARCHITECTURES=${macos_archs_1}" -DMYLIB_LIBRARY=${RunCMake_BINARY_DIR}/create-xcframework-incomplete-build/mylib.xcframework)
+run_cmake_with_options(create-executable-incomplete -DCMAKE_SYSTEM_NAME=Darwin "-DCMAKE_OSX_ARCHITECTURES=${macos_archs_1}" -DCMAKE_OSX_SYSROOT=macosx -DMYLIB_LIBRARY=${RunCMake_BINARY_DIR}/create-xcframework-incomplete-build/mylib.xcframework)
 create_executables(target-library library)
 create_executables(target-framework framework)
-run_cmake_with_options(create-executable-target-incomplete -DCMAKE_SYSTEM_NAME=Darwin "-DCMAKE_OSX_ARCHITECTURES=${macos_archs_1}" -DMYLIB_LIBRARY=${RunCMake_BINARY_DIR}/create-xcframework-incomplete-build/mylib.xcframework)
+run_cmake_with_options(create-executable-target-incomplete -DCMAKE_SYSTEM_NAME=Darwin "-DCMAKE_OSX_ARCHITECTURES=${macos_archs_1}" -DCMAKE_OSX_SYSROOT=macosx -DMYLIB_LIBRARY=${RunCMake_BINARY_DIR}/create-xcframework-incomplete-build/mylib.xcframework)
 if(RunCMake_GENERATOR STREQUAL "Xcode" AND CMake_TEST_XCODE_VERSION VERSION_GREATER_EQUAL 12)
   create_executables(library-link-phase library)
   create_executables(framework-link-phase framework)
-  run_cmake_with_options(create-executable-incomplete-link-phase -DCMAKE_SYSTEM_NAME=Darwin "-DCMAKE_OSX_ARCHITECTURES=${macos_archs_1}" -DMYLIB_LIBRARY=${RunCMake_BINARY_DIR}/create-xcframework-incomplete-build/mylib.xcframework)
+  run_cmake_with_options(create-executable-incomplete-link-phase -DCMAKE_SYSTEM_NAME=Darwin "-DCMAKE_OSX_ARCHITECTURES=${macos_archs_1}" -DCMAKE_OSX_SYSROOT=macosx -DMYLIB_LIBRARY=${RunCMake_BINARY_DIR}/create-xcframework-incomplete-build/mylib.xcframework)
   create_executables(target-library-link-phase library)
   create_executables(target-framework-link-phase framework)
-  run_cmake_with_options(create-executable-target-incomplete-link-phase -DCMAKE_SYSTEM_NAME=Darwin "-DCMAKE_OSX_ARCHITECTURES=${macos_archs_1}" -DMYLIB_LIBRARY=${RunCMake_BINARY_DIR}/create-xcframework-incomplete-build/mylib.xcframework)
+  run_cmake_with_options(create-executable-target-incomplete-link-phase -DCMAKE_SYSTEM_NAME=Darwin "-DCMAKE_OSX_ARCHITECTURES=${macos_archs_1}" -DCMAKE_OSX_SYSROOT=macosx -DMYLIB_LIBRARY=${RunCMake_BINARY_DIR}/create-xcframework-incomplete-build/mylib.xcframework)
 endif()
 
 # Ensure that .xcframework is found before .framework
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/ctest_submit/CDashUploadNone-stderr.txt b/Tests/RunCMake/ctest_submit/CDashUploadNone-stderr.txt
index af95b5c..9e27918 100644
--- a/Tests/RunCMake/ctest_submit/CDashUploadNone-stderr.txt
+++ b/Tests/RunCMake/ctest_submit/CDashUploadNone-stderr.txt
@@ -1 +1,4 @@
-Upload file not specified
+^CMake Error at .*Tests/RunCMake/ctest_submit/CDashUploadNone/test.cmake:[0-9]+ \(ctest_submit\):
+  Error after keyword "CDASH_UPLOAD":
+
+    missing required value
diff --git a/Tests/RunCMake/ctest_submit/RepeatRETURN_VALUE-stderr.txt b/Tests/RunCMake/ctest_submit/RepeatRETURN_VALUE-stderr.txt
index 6e17c75..3951524 100644
--- a/Tests/RunCMake/ctest_submit/RepeatRETURN_VALUE-stderr.txt
+++ b/Tests/RunCMake/ctest_submit/RepeatRETURN_VALUE-stderr.txt
@@ -1,2 +1,2 @@
 CMake Error at .*/Tests/RunCMake/ctest_submit/RepeatRETURN_VALUE/test.cmake:[0-9]+ \(ctest_submit\):
-  Called with more than one value for RETURN_VALUE
+  ctest_submit called with more than one value for RETURN_VALUE
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-CMP0181-OLD-validation.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion-CMP0181-OLD-validation.cmake
new file mode 100644
index 0000000..45855bc
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion-CMP0181-OLD-validation.cmake
@@ -0,0 +1,5 @@
+
+if (NOT actual_stdout MATCHES "LINKER:-foo,bar")
+  set (RunCMake_TEST_FAILED "LINKER: prefix was expanded.")
+  return()
+endif()
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/LINKER_expansion2-CMP0181-NEW-EXE_LINKER_FLAGS-check.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-NEW-EXE_LINKER_FLAGS-check.cmake
new file mode 100644
index 0000000..99f5aa3
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-NEW-EXE_LINKER_FLAGS-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_expansion2-CMP0181-NEW-EXE_LINKER_FLAGS-result.txt b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-NEW-EXE_LINKER_FLAGS-result.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-NEW-EXE_LINKER_FLAGS-result.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-NEW-MODULE_LINKER_FLAGS-check.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-NEW-MODULE_LINKER_FLAGS-check.cmake
new file mode 100644
index 0000000..99f5aa3
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-NEW-MODULE_LINKER_FLAGS-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_expansion2-CMP0181-NEW-MODULE_LINKER_FLAGS-result.txt b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-NEW-MODULE_LINKER_FLAGS-result.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-NEW-MODULE_LINKER_FLAGS-result.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-NEW-SHARED_LINKER_FLAGS-check.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-NEW-SHARED_LINKER_FLAGS-check.cmake
new file mode 100644
index 0000000..99f5aa3
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-NEW-SHARED_LINKER_FLAGS-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_expansion2-CMP0181-NEW-SHARED_LINKER_FLAGS-result.txt b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-NEW-SHARED_LINKER_FLAGS-result.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-NEW-SHARED_LINKER_FLAGS-result.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-OLD-EXE_LINKER_FLAGS-check.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-OLD-EXE_LINKER_FLAGS-check.cmake
new file mode 100644
index 0000000..e0d406f
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-OLD-EXE_LINKER_FLAGS-check.cmake
@@ -0,0 +1,2 @@
+
+include ("${CMAKE_CURRENT_LIST_DIR}/LINKER_expansion-CMP0181-OLD-validation.cmake")
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-OLD-EXE_LINKER_FLAGS-result.txt b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-OLD-EXE_LINKER_FLAGS-result.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-OLD-EXE_LINKER_FLAGS-result.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-OLD-MODULE_LINKER_FLAGS-check.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-OLD-MODULE_LINKER_FLAGS-check.cmake
new file mode 100644
index 0000000..e0d406f
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-OLD-MODULE_LINKER_FLAGS-check.cmake
@@ -0,0 +1,2 @@
+
+include ("${CMAKE_CURRENT_LIST_DIR}/LINKER_expansion-CMP0181-OLD-validation.cmake")
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-OLD-MODULE_LINKER_FLAGS-result.txt b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-OLD-MODULE_LINKER_FLAGS-result.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-OLD-MODULE_LINKER_FLAGS-result.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-OLD-SHARED_LINKER_FLAGS-check.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-OLD-SHARED_LINKER_FLAGS-check.cmake
new file mode 100644
index 0000000..e0d406f
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-OLD-SHARED_LINKER_FLAGS-check.cmake
@@ -0,0 +1,2 @@
+
+include ("${CMAKE_CURRENT_LIST_DIR}/LINKER_expansion-CMP0181-OLD-validation.cmake")
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-OLD-SHARED_LINKER_FLAGS-result.txt b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-OLD-SHARED_LINKER_FLAGS-result.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2-CMP0181-OLD-SHARED_LINKER_FLAGS-result.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2.cmake
new file mode 100644
index 0000000..7cf333a
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion2.cmake
@@ -0,0 +1,56 @@
+
+enable_language(C)
+
+cmake_policy(SET CMP0181 ${CMP0181})
+
+# ensure command line is always displayed and do not use any response file
+set(CMAKE_VERBOSE_MAKEFILE TRUE)
+
+if (CMAKE_GENERATOR MATCHES "Borland|NMake")
+  string(REPLACE "${CMAKE_START_TEMP_FILE}" "" CMAKE_C_LINK_EXECUTABLE "${CMAKE_C_LINK_EXECUTABLE}")
+  string(REPLACE "${CMAKE_END_TEMP_FILE}" "" CMAKE_C_LINK_EXECUTABLE "${CMAKE_C_LINK_EXECUTABLE}")
+
+  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}")
+
+  string(REPLACE "${CMAKE_START_TEMP_FILE}" "" CMAKE_C_CREATE_SHARED_MODULE "${CMAKE_C_CREATE_SHARED_MODULE}")
+  string(REPLACE "${CMAKE_END_TEMP_FILE}" "" CMAKE_C_CREATE_SHARED_MODULE "${CMAKE_C_CREATE_SHARED_MODULE}")
+endif()
+
+
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} LINKER:-foo,bar")
+add_executable(exe_linker_flags main.c)
+
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} LINKER:-foo,bar")
+add_library(shared_linker_flags SHARED LinkOptionsLib.c)
+
+set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} LINKER:-foo,bar")
+add_library(module_linker_flags MODULE LinkOptionsLib.c)
+
+
+# generate reference for LINKER flag
+if (CMP0181 STREQUAL "NEW")
+  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}")
+endif()
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-NEW-C_EXE_CREATE_LINK_FLAGS-check.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-NEW-C_EXE_CREATE_LINK_FLAGS-check.cmake
new file mode 100644
index 0000000..99f5aa3
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-NEW-C_EXE_CREATE_LINK_FLAGS-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_expansion3-CMP0181-NEW-C_EXE_CREATE_LINK_FLAGS-result.txt b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-NEW-C_EXE_CREATE_LINK_FLAGS-result.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-NEW-C_EXE_CREATE_LINK_FLAGS-result.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-NEW-C_MODULE_CREATE_LINK_FLAGS-check.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-NEW-C_MODULE_CREATE_LINK_FLAGS-check.cmake
new file mode 100644
index 0000000..99f5aa3
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-NEW-C_MODULE_CREATE_LINK_FLAGS-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_expansion3-CMP0181-NEW-C_MODULE_CREATE_LINK_FLAGS-result.txt b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-NEW-C_MODULE_CREATE_LINK_FLAGS-result.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-NEW-C_MODULE_CREATE_LINK_FLAGS-result.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-NEW-C_SHARED_CREATE_LINK_FLAGS-check.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-NEW-C_SHARED_CREATE_LINK_FLAGS-check.cmake
new file mode 100644
index 0000000..99f5aa3
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-NEW-C_SHARED_CREATE_LINK_FLAGS-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_expansion3-CMP0181-NEW-C_SHARED_CREATE_LINK_FLAGS-result.txt b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-NEW-C_SHARED_CREATE_LINK_FLAGS-result.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-NEW-C_SHARED_CREATE_LINK_FLAGS-result.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-OLD-C_EXE_CREATE_LINK_FLAGS-check.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-OLD-C_EXE_CREATE_LINK_FLAGS-check.cmake
new file mode 100644
index 0000000..e0d406f
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-OLD-C_EXE_CREATE_LINK_FLAGS-check.cmake
@@ -0,0 +1,2 @@
+
+include ("${CMAKE_CURRENT_LIST_DIR}/LINKER_expansion-CMP0181-OLD-validation.cmake")
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-OLD-C_EXE_CREATE_LINK_FLAGS-result.txt b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-OLD-C_EXE_CREATE_LINK_FLAGS-result.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-OLD-C_EXE_CREATE_LINK_FLAGS-result.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-OLD-C_MODULE_CREATE_LINK_FLAGS-check.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-OLD-C_MODULE_CREATE_LINK_FLAGS-check.cmake
new file mode 100644
index 0000000..e0d406f
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-OLD-C_MODULE_CREATE_LINK_FLAGS-check.cmake
@@ -0,0 +1,2 @@
+
+include ("${CMAKE_CURRENT_LIST_DIR}/LINKER_expansion-CMP0181-OLD-validation.cmake")
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-OLD-C_MODULE_CREATE_LINK_FLAGS-result.txt b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-OLD-C_MODULE_CREATE_LINK_FLAGS-result.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-OLD-C_MODULE_CREATE_LINK_FLAGS-result.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-OLD-C_SHARED_CREATE_LINK_FLAGS-check.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-OLD-C_SHARED_CREATE_LINK_FLAGS-check.cmake
new file mode 100644
index 0000000..e0d406f
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-OLD-C_SHARED_CREATE_LINK_FLAGS-check.cmake
@@ -0,0 +1,2 @@
+
+include ("${CMAKE_CURRENT_LIST_DIR}/LINKER_expansion-CMP0181-OLD-validation.cmake")
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-OLD-C_SHARED_CREATE_LINK_FLAGS-result.txt b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-OLD-C_SHARED_CREATE_LINK_FLAGS-result.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3-CMP0181-OLD-C_SHARED_CREATE_LINK_FLAGS-result.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3.cmake
new file mode 100644
index 0000000..301570b
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion3.cmake
@@ -0,0 +1,55 @@
+
+enable_language(C)
+
+cmake_policy(SET CMP0181 ${CMP0181})
+
+# ensure command line is always displayed and do not use any response file
+set(CMAKE_VERBOSE_MAKEFILE TRUE)
+
+if (CMAKE_GENERATOR MATCHES "Borland|NMake")
+  string(REPLACE "${CMAKE_START_TEMP_FILE}" "" CMAKE_C_LINK_EXECUTABLE "${CMAKE_C_LINK_EXECUTABLE}")
+  string(REPLACE "${CMAKE_END_TEMP_FILE}" "" CMAKE_C_LINK_EXECUTABLE "${CMAKE_C_LINK_EXECUTABLE}")
+
+  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}")
+
+  string(REPLACE "${CMAKE_START_TEMP_FILE}" "" CMAKE_C_CREATE_SHARED_MODULE "${CMAKE_C_CREATE_SHARED_MODULE}")
+  string(REPLACE "${CMAKE_END_TEMP_FILE}" "" CMAKE_C_CREATE_SHARED_MODULE "${CMAKE_C_CREATE_SHARED_MODULE}")
+endif()
+
+
+set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} LINKER:-foo,bar")
+add_executable(c_exe_create_link_flags main.c)
+
+set(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS "${CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS} LINKER:-foo,bar")
+add_library(c_shared_create_link_flags SHARED LinkOptionsLib.c)
+
+set(CMAKE_SHARED_MODULE_CREATE_C_FLAGS "${CMAKE_SHARED_MODULE_CREATE_C_FLAGS} LINKER:-foo,bar")
+add_library(c_module_create_link_flags MODULE LinkOptionsLib.c)
+
+# generate reference for LINKER flag
+if (CMP0181 STREQUAL "NEW")
+  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}")
+endif()
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion4-CMP0181-NEW-C_CREATE_CONSOLE_EXE-check.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion4-CMP0181-NEW-C_CREATE_CONSOLE_EXE-check.cmake
new file mode 100644
index 0000000..99f5aa3
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion4-CMP0181-NEW-C_CREATE_CONSOLE_EXE-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_expansion4-CMP0181-NEW-C_CREATE_CONSOLE_EXE-result.txt b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion4-CMP0181-NEW-C_CREATE_CONSOLE_EXE-result.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion4-CMP0181-NEW-C_CREATE_CONSOLE_EXE-result.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion4-CMP0181-NEW-C_CREATE_WIN32_EXE-check.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion4-CMP0181-NEW-C_CREATE_WIN32_EXE-check.cmake
new file mode 100644
index 0000000..99f5aa3
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion4-CMP0181-NEW-C_CREATE_WIN32_EXE-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_expansion4-CMP0181-NEW-C_CREATE_WIN32_EXE-result.txt b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion4-CMP0181-NEW-C_CREATE_WIN32_EXE-result.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion4-CMP0181-NEW-C_CREATE_WIN32_EXE-result.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion4-CMP0181-OLD-C_CREATE_CONSOLE_EXE-check.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion4-CMP0181-OLD-C_CREATE_CONSOLE_EXE-check.cmake
new file mode 100644
index 0000000..e0d406f
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion4-CMP0181-OLD-C_CREATE_CONSOLE_EXE-check.cmake
@@ -0,0 +1,2 @@
+
+include ("${CMAKE_CURRENT_LIST_DIR}/LINKER_expansion-CMP0181-OLD-validation.cmake")
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion4-CMP0181-OLD-C_CREATE_CONSOLE_EXE-result.txt b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion4-CMP0181-OLD-C_CREATE_CONSOLE_EXE-result.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion4-CMP0181-OLD-C_CREATE_CONSOLE_EXE-result.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion4-CMP0181-OLD-C_CREATE_WIN32_EXE-check.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion4-CMP0181-OLD-C_CREATE_WIN32_EXE-check.cmake
new file mode 100644
index 0000000..e0d406f
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion4-CMP0181-OLD-C_CREATE_WIN32_EXE-check.cmake
@@ -0,0 +1,2 @@
+
+include ("${CMAKE_CURRENT_LIST_DIR}/LINKER_expansion-CMP0181-OLD-validation.cmake")
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion4-CMP0181-OLD-C_CREATE_WIN32_EXE-result.txt b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion4-CMP0181-OLD-C_CREATE_WIN32_EXE-result.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion4-CMP0181-OLD-C_CREATE_WIN32_EXE-result.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion4.cmake b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion4.cmake
new file mode 100644
index 0000000..4e88e91
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/LINKER_expansion4.cmake
@@ -0,0 +1,47 @@
+
+enable_language(C)
+
+cmake_policy(SET CMP0181 ${CMP0181})
+
+# ensure command line is always displayed and do not use any response file
+set(CMAKE_VERBOSE_MAKEFILE TRUE)
+
+if (CMAKE_GENERATOR MATCHES "Borland|NMake")
+  string(REPLACE "${CMAKE_START_TEMP_FILE}" "" CMAKE_C_LINK_EXECUTABLE "${CMAKE_C_LINK_EXECUTABLE}")
+  string(REPLACE "${CMAKE_END_TEMP_FILE}" "" CMAKE_C_LINK_EXECUTABLE "${CMAKE_C_LINK_EXECUTABLE}")
+endif()
+
+
+set(CMAKE_C_CREATE_WIN32_EXE "${CMAKE_C_CREATE_WIN32_EXE} LINKER:-foo,bar")
+add_executable (c_create_win32_exe WIN32 main.c)
+
+set(CMAKE_C_CREATE_CONSOLE_EXE "${CMAKE_C_CREATE_CONSOLE_EXE} LINKER:-foo,bar")
+add_executable(c_create_console_exe main.c)
+
+
+# generate reference for LINKER flag
+if (CMP0181 STREQUAL "NEW")
+  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}")
+endif()
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..5cfd519
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/RunCMakeTest.cmake
@@ -0,0 +1,59 @@
+
+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()
+
+# Some environments are excluded because they are not able to honor verbose mode
+if (RunCMake_GENERATOR MATCHES "Makefiles|Ninja|Xcode|Visual Studio"
+    AND NOT CMAKE_C_COMPILER_ID STREQUAL "Intel")
+  set(RunCMake_TEST_OUTPUT_MERGE TRUE)
+
+  foreach(policy IN ITEMS OLD NEW)
+    set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/LINKER_expansion2-CMP0181-${policy}-build)
+    run_cmake_with_options(LINKER_expansion2 -DCMP0181=${policy})
+
+    run_cmake_target(LINKER_expansion2-CMP0181-${policy} EXE_LINKER_FLAGS exe_linker_flags --verbose)
+    run_cmake_target(LINKER_expansion2-CMP0181-${policy} SHARED_LINKER_FLAGS shared_linker_flags --verbose)
+    run_cmake_target(LINKER_expansion2-CMP0181-${policy} MODULE_LINKER_FLAGS module_linker_flags --verbose)
+
+
+    if (NOT (RunCMake_GENERATOR MATCHES "Visual Studio" OR CMAKE_C_COMPILER_ID MATCHES "Borland|Embarcadero"))
+      # Visual Studio generator and Borland, Embarcadero compilers do not use these variables
+      set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/LINKER_expansion3-CMP0181-${policy}-build)
+      run_cmake_with_options(LINKER_expansion3 -DCMP0181=${policy})
+
+      run_cmake_target(LINKER_expansion3-CMP0181-${policy} C_EXE_CREATE_LINK_FLAGS c_exe_create_link_flags --verbose)
+      if (NOT (CMAKE_C_COMPILER_ID STREQUAL "MSVC" OR CMAKE_C_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC"))
+         # MSVC compiler does not use these variables
+        run_cmake_target(LINKER_expansion3-CMP0181-${policy} C_SHARED_CREATE_LINK_FLAGS c_shared_create_link_flags --verbose)
+        run_cmake_target(LINKER_expansion3-CMP0181-${policy} C_MODULE_CREATE_LINK_FLAGS c_module_create_link_flags --verbose)
+      endif()
+
+      if (CMAKE_SYSTEM_NAME MATCHES "Windows")
+        set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/LINKER_expansion4-CMP0181-${policy}-build)
+        run_cmake_with_options(LINKER_expansion4 -DCMP0181=${policy})
+
+        run_cmake_target(LINKER_expansion4-CMP0181-${policy} C_CREATE_WIN32_EXE c_create_win32_exe --verbose)
+        run_cmake_target(LINKER_expansion4-CMP0181-${policy} C_CREATE_CONSOLE_EXE c_create_console_exe --verbose)
+      endif()
+    endif()
+  endforeach()
+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/target_link_libraries-LINKER-prefix/main.c b/Tests/RunCMake/target_link_libraries-LINKER-prefix/main.c
new file mode 100644
index 0000000..8488f4e
--- /dev/null
+++ b/Tests/RunCMake/target_link_libraries-LINKER-prefix/main.c
@@ -0,0 +1,4 @@
+int main(void)
+{
+  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/Tests/SwiftOnly/CMakeLists.txt b/Tests/SwiftOnly/CMakeLists.txt
index ba36c10..26f75f3 100644
--- a/Tests/SwiftOnly/CMakeLists.txt
+++ b/Tests/SwiftOnly/CMakeLists.txt
@@ -32,6 +32,8 @@
 add_subdirectory(SubD)
 add_subdirectory(SubE)
 
+add_subdirectory("Sub Space")
+
 set(CMAKE_Swift_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/swift)
 
 add_executable(SwiftOnly main.swift)
diff --git a/Tests/SwiftOnly/Sub Space/CMakeLists.txt b/Tests/SwiftOnly/Sub Space/CMakeLists.txt
new file mode 100644
index 0000000..ac4cd18
--- /dev/null
+++ b/Tests/SwiftOnly/Sub Space/CMakeLists.txt
@@ -0,0 +1 @@
+add_library(SubSpace SubSpace.swift)
diff --git a/Tests/SwiftOnly/Sub Space/SubSpace.swift b/Tests/SwiftOnly/Sub Space/SubSpace.swift
new file mode 100644
index 0000000..d17aa41
--- /dev/null
+++ b/Tests/SwiftOnly/Sub Space/SubSpace.swift
@@ -0,0 +1 @@
+public func space() { print("space") }
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)
diff --git a/Utilities/Sphinx/conf.py.in b/Utilities/Sphinx/conf.py.in
index dca6794..8ff9b0e 100644
--- a/Utilities/Sphinx/conf.py.in
+++ b/Utilities/Sphinx/conf.py.in
@@ -91,7 +91,8 @@
 linkcheck_ignore = [
     r'about:',
     r'https://gitlab\.kitware\.com/cmake/community/-/wikis/doc/cpack',
-    r'https://www.intel.com/',
+    r'https://www\.intel\.com/',
+    r'https://www\.tasking\.com($|/)',
 ]
 
 linkcheck_allowed_redirects = {
diff --git a/Utilities/cmlibuv/include/uv.h b/Utilities/cmlibuv/include/uv.h
index 42e3446..94113d1 100644
--- a/Utilities/cmlibuv/include/uv.h
+++ b/Utilities/cmlibuv/include/uv.h
@@ -1087,7 +1087,13 @@
    * search for the exact file name before trying variants with
    * extensions like '.exe' or '.cmd'.
    */
-  UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME = (1 << 7)
+  UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME = (1 << 7),
+  /*
+   * Spawn the child process with the error mode of its parent.
+   * This option is only meaningful on Windows systems. On Unix
+   * it is silently ignored.
+   */
+  UV_PROCESS_WINDOWS_USE_PARENT_ERROR_MODE = (1 << 8)
 };
 
 /*
diff --git a/Utilities/cmlibuv/src/unix/process.c b/Utilities/cmlibuv/src/unix/process.c
index ebe185d..729a44b 100644
--- a/Utilities/cmlibuv/src/unix/process.c
+++ b/Utilities/cmlibuv/src/unix/process.c
@@ -1033,7 +1033,8 @@
                               UV_PROCESS_WINDOWS_HIDE |
                               UV_PROCESS_WINDOWS_HIDE_CONSOLE |
                               UV_PROCESS_WINDOWS_HIDE_GUI |
-                              UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS)));
+                              UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS |
+                              UV_PROCESS_WINDOWS_USE_PARENT_ERROR_MODE)));
 
   uv__handle_init(loop, (uv_handle_t*)process, UV_PROCESS);
   QUEUE_INIT(&process->queue);
diff --git a/Utilities/cmlibuv/src/win/process.c b/Utilities/cmlibuv/src/win/process.c
index 5cf9fb8..59db8f8 100644
--- a/Utilities/cmlibuv/src/win/process.c
+++ b/Utilities/cmlibuv/src/win/process.c
@@ -90,7 +90,6 @@
   info.BasicLimitInformation.LimitFlags =
       JOB_OBJECT_LIMIT_BREAKAWAY_OK |
       JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK |
-      JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION |
       JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
 
   uv_global_job_handle_ = CreateJobObjectW(&attr, NULL);
@@ -993,7 +992,8 @@
                               UV_PROCESS_WINDOWS_HIDE |
                               UV_PROCESS_WINDOWS_HIDE_CONSOLE |
                               UV_PROCESS_WINDOWS_HIDE_GUI |
-                              UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS)));
+                              UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS |
+                              UV_PROCESS_WINDOWS_USE_PARENT_ERROR_MODE)));
 
   err = uv__utf8_to_utf16_alloc(options->file, &application);
   if (err)
@@ -1097,7 +1097,10 @@
   startup.hStdOutput = uv__stdio_handle(process->child_stdio_buffer, 1);
   startup.hStdError = uv__stdio_handle(process->child_stdio_buffer, 2);
 
-  process_flags = CREATE_UNICODE_ENVIRONMENT;
+  process_flags = CREATE_UNICODE_ENVIRONMENT | CREATE_DEFAULT_ERROR_MODE;
+  if (options->flags & UV_PROCESS_WINDOWS_USE_PARENT_ERROR_MODE) {
+    process_flags &= ~(CREATE_DEFAULT_ERROR_MODE);
+  }
 
   if ((options->flags & UV_PROCESS_WINDOWS_HIDE_CONSOLE) ||
       (options->flags & UV_PROCESS_WINDOWS_HIDE)) {
diff --git a/bootstrap b/bootstrap
index 53358d5..b41197f 100755
--- a/bootstrap
+++ b/bootstrap
@@ -315,7 +315,6 @@
   cmCMakePolicyCommand \
   cmCPackPropertiesGenerator \
   cmCacheManager \
-  cmCommand \
   cmCommandArgumentParserHelper \
   cmCommands \
   cmCommonTargetGenerator \
@@ -459,6 +458,7 @@
   cmOutputConverter \
   cmParseArgumentsCommand \
   cmPathLabel \
+  cmPathResolver \
   cmPolicies \
   cmProcessOutput \
   cmProjectCommand \
@@ -843,7 +843,6 @@
               s/@KWSYS_NAME_IS_KWSYS@/${KWSYS_NAME_IS_KWSYS}/g;
               s/@KWSYS_STL_HAS_WSTRING@/${KWSYS_STL_HAS_WSTRING}/g;
               s/@KWSYS_CXX_HAS_EXT_STDIO_FILEBUF_H@/${KWSYS_CXX_HAS_EXT_STDIO_FILEBUF_H}/g;
-              s/@KWSYS_SYSTEMTOOLS_USE_TRANSLATION_MAP@/${KWSYS_SYSTEMTOOLS_USE_TRANSLATION_MAP}/g;
              }" "${INFILE}" >> "${OUTFILE}${_tmp}"
     if test -f "${OUTFILE}${_tmp}"; then
       if "${_diff}" "${OUTFILE}" "${OUTFILE}${_tmp}" > /dev/null 2> /dev/null ; then
@@ -1563,7 +1562,6 @@
 KWSYS_CXX_HAS_ENVIRON_IN_STDLIB_H=0
 KWSYS_CXX_HAS_UTIMENSAT=0
 KWSYS_CXX_HAS_UTIMES=0
-KWSYS_SYSTEMTOOLS_USE_TRANSLATION_MAP=1
 
 if cmake_try_run "${cmake_cxx_compiler}" \
   "${cmake_cxx_flags} ${cmake_ld_flags} -DTEST_KWSYS_CXX_HAS_SETENV" \