Merge topic 'FindPython-virtual-env'

7ed84b1e8f FindPython: ensure virtual environments are correctly handled

Acked-by: Kitware Robot <kwrobot@kitware.com>
Merge-request: !3649
diff --git a/Help/command/if.rst b/Help/command/if.rst
index d8e3a45..be992df 100644
--- a/Help/command/if.rst
+++ b/Help/command/if.rst
@@ -29,6 +29,8 @@
 repeat of the argument of the opening
 ``if`` command.
 
+.. _`Condition Syntax`:
+
 Condition Syntax
 ^^^^^^^^^^^^^^^^
 
diff --git a/Help/variable/CMAKE_MESSAGE_INDENT.rst b/Help/variable/CMAKE_MESSAGE_INDENT.rst
index f7975ab..7e44a4c 100644
--- a/Help/variable/CMAKE_MESSAGE_INDENT.rst
+++ b/Help/variable/CMAKE_MESSAGE_INDENT.rst
@@ -23,8 +23,10 @@
 
 Which results in the following output:
 
+.. code-block:: none
+
   -- Collected items in the "listVar":
   --   one
   --   two
-  --   tree
+  --   three
   -- No more indent
diff --git a/Modules/CMakeDependentOption.cmake b/Modules/CMakeDependentOption.cmake
index 6046d85..99d5070 100644
--- a/Modules/CMakeDependentOption.cmake
+++ b/Modules/CMakeDependentOption.cmake
@@ -12,7 +12,7 @@
 is used, but any value set by the user is preserved for when the
 option is presented again.  Example invocation:
 
-::
+.. code-block:: cmake
 
   CMAKE_DEPENDENT_OPTION(USE_FOO "Use Foo" ON
                          "USE_BAR;NOT USE_ZOT" OFF)
@@ -21,7 +21,8 @@
 called USE_FOO that defaults to ON.  Otherwise, it sets USE_FOO to
 OFF.  If the status of USE_BAR or USE_ZOT ever changes, any value for
 the USE_FOO option is saved so that when the option is re-enabled it
-retains its old value.
+retains its old value.  Each element in the fourth parameter is
+evaluated as an if-condition, so :ref:`Condition Syntax` can be used.
 #]=======================================================================]
 
 macro(CMAKE_DEPENDENT_OPTION option doc default depends force)
diff --git a/Modules/CMakeDetermineCCompiler.cmake b/Modules/CMakeDetermineCCompiler.cmake
index 8be781a..037c33b 100644
--- a/Modules/CMakeDetermineCCompiler.cmake
+++ b/Modules/CMakeDetermineCCompiler.cmake
@@ -124,6 +124,22 @@
   elseif(CMAKE_C_PLATFORM_ID MATCHES "Cygwin")
     set(CMAKE_COMPILER_IS_CYGWIN 1)
   endif()
+else()
+  if(NOT DEFINED CMAKE_C_COMPILER_FRONTEND_VARIANT)
+    # Some toolchain files set our internal CMAKE_C_COMPILER_ID_RUN
+    # variable but are not aware of CMAKE_C_COMPILER_FRONTEND_VARIANT.
+    # They pre-date our support for the GNU-like variant targeting the
+    # MSVC ABI so we do not consider that here.
+    if(CMAKE_C_COMPILER_ID STREQUAL "Clang")
+      if("x${CMAKE_C_SIMULATE_ID}" STREQUAL "xMSVC")
+        set(CMAKE_C_COMPILER_FRONTEND_VARIANT "MSVC")
+      else()
+        set(CMAKE_C_COMPILER_FRONTEND_VARIANT "GNU")
+      endif()
+    else()
+      set(CMAKE_C_COMPILER_FRONTEND_VARIANT "")
+    endif()
+  endif()
 endif()
 
 if (NOT _CMAKE_TOOLCHAIN_LOCATION)
diff --git a/Modules/CMakeDetermineCXXCompiler.cmake b/Modules/CMakeDetermineCXXCompiler.cmake
index 00ef5b9..7274eec 100644
--- a/Modules/CMakeDetermineCXXCompiler.cmake
+++ b/Modules/CMakeDetermineCXXCompiler.cmake
@@ -119,6 +119,22 @@
   elseif(CMAKE_CXX_PLATFORM_ID MATCHES "Cygwin")
     set(CMAKE_COMPILER_IS_CYGWIN 1)
   endif()
+else()
+  if(NOT DEFINED CMAKE_CXX_COMPILER_FRONTEND_VARIANT)
+    # Some toolchain files set our internal CMAKE_CXX_COMPILER_ID_RUN
+    # variable but are not aware of CMAKE_CXX_COMPILER_FRONTEND_VARIANT.
+    # They pre-date our support for the GNU-like variant targeting the
+    # MSVC ABI so we do not consider that here.
+    if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
+      if("x${CMAKE_CXX_SIMULATE_ID}" STREQUAL "xMSVC")
+        set(CMAKE_CXX_COMPILER_FRONTEND_VARIANT "MSVC")
+      else()
+        set(CMAKE_CXX_COMPILER_FRONTEND_VARIANT "GNU")
+      endif()
+    else()
+      set(CMAKE_CXX_COMPILER_FRONTEND_VARIANT "")
+    endif()
+  endif()
 endif()
 
 if (NOT _CMAKE_TOOLCHAIN_LOCATION)
diff --git a/Modules/FindGLEW.cmake b/Modules/FindGLEW.cmake
index 2e9a052..bd69819 100644
--- a/Modules/FindGLEW.cmake
+++ b/Modules/FindGLEW.cmake
@@ -70,11 +70,27 @@
   message(STATUS "FindGLEW: did not find GLEW CMake config file. Searching for libraries.")
 endif()
 
+if(APPLE)
+  find_package(OpenGL QUIET)
+
+  if(OpenGL_FOUND)
+    if(GLEW_VERBOSE)
+      message(STATUS "FindGLEW: Found OpenGL Framework.")
+      message(STATUS "FindGLEW: OPENGL_LIBRARIES: ${OPENGL_LIBRARIES}")
+    endif()
+  else()
+    if(GLEW_VERBOSE)
+      message(STATUS "FindGLEW: could not find GLEW library.")
+    endif()
+    return()
+  endif()
+endif()
+
 
 function(__glew_set_find_library_suffix shared_or_static)
-  if(UNIX AND "${shared_or_static}" MATCHES "SHARED")
+  if((UNIX AND NOT APPLE) AND "${shared_or_static}" MATCHES "SHARED")
     set(CMAKE_FIND_LIBRARY_SUFFIXES ".so" PARENT_SCOPE)
-  elseif(UNIX AND "${shared_or_static}" MATCHES "STATIC")
+  elseif((UNIX AND NOT APPLE) AND "${shared_or_static}" MATCHES "STATIC")
     set(CMAKE_FIND_LIBRARY_SUFFIXES ".a" PARENT_SCOPE)
   elseif(APPLE AND "${shared_or_static}" MATCHES "SHARED")
     set(CMAKE_FIND_LIBRARY_SUFFIXES ".dylib;.so" PARENT_SCOPE)
@@ -194,7 +210,7 @@
 
 if(NOT GLEW_FOUND)
   if(GLEW_VERBOSE)
-    message(STATUS "FindGLEW: could not found GLEW library.")
+    message(STATUS "FindGLEW: could not find GLEW library.")
   endif()
   return()
 endif()
@@ -210,6 +226,11 @@
   set_target_properties(GLEW::glew
                         PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${GLEW_INCLUDE_DIRS}")
 
+  if(APPLE)
+    set_target_properties(GLEW::glew
+                          PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL)
+  endif()
+
   if(GLEW_SHARED_LIBRARY_RELEASE)
     set_property(TARGET GLEW::glew
                  APPEND
@@ -238,6 +259,11 @@
   set_target_properties(GLEW::glew_s
                         PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${GLEW_INCLUDE_DIRS}")
 
+  if(APPLE)
+    set_target_properties(GLEW::glew_s
+                          PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL)
+  endif()
+
   if(GLEW_STATIC_LIBRARY_RELEASE)
     set_property(TARGET GLEW::glew_s
                  APPEND
@@ -267,6 +293,11 @@
   set_target_properties(GLEW::GLEW
                         PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${GLEW_INCLUDE_DIRS}")
 
+  if(APPLE)
+    set_target_properties(GLEW::GLEW
+                          PROPERTIES INTERFACE_LINK_LIBRARIES OpenGL::GL)
+  endif()
+
   if(TARGET GLEW::glew)
     if(GLEW_SHARED_LIBRARY_RELEASE)
       set_property(TARGET GLEW::GLEW
diff --git a/Modules/Platform/Windows-OpenWatcom.cmake b/Modules/Platform/Windows-OpenWatcom.cmake
index d38d616..76cd28b 100644
--- a/Modules/Platform/Windows-OpenWatcom.cmake
+++ b/Modules/Platform/Windows-OpenWatcom.cmake
@@ -10,7 +10,7 @@
 
 set(CMAKE_LIBRARY_PATH_FLAG "libpath ")
 set(CMAKE_LINK_LIBRARY_FLAG "library ")
-set(CMAKE_LINK_LIBRARY_FILE_FLAG "library")
+set(CMAKE_LINK_LIBRARY_FILE_FLAG "library ")
 
 if(CMAKE_VERBOSE_MAKEFILE)
   set(CMAKE_WCL_QUIET)
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index fe40af3..b1f4ca5 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -532,6 +532,8 @@
   cmFindProgramCommand.h
   cmForEachCommand.cxx
   cmForEachCommand.h
+  cmFunctionBlocker.cxx
+  cmFunctionBlocker.h
   cmFunctionCommand.cxx
   cmFunctionCommand.h
   cmGetCMakePropertyCommand.cxx
diff --git a/Source/CMakeVersion.cmake b/Source/CMakeVersion.cmake
index 04564a0..96e5ee5 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 15)
-set(CMake_VERSION_PATCH 20190803)
+set(CMake_VERSION_PATCH 20190807)
 #set(CMake_VERSION_RC 0)
 set(CMake_VERSION_IS_DIRTY 0)
 
diff --git a/Source/CPack/IFW/cmCPackIFWInstaller.cxx b/Source/CPack/IFW/cmCPackIFWInstaller.cxx
index a075a17..f130e05 100644
--- a/Source/CPack/IFW/cmCPackIFWInstaller.cxx
+++ b/Source/CPack/IFW/cmCPackIFWInstaller.cxx
@@ -8,6 +8,7 @@
 #include "cmCPackIFWRepository.h"
 #include "cmCPackLog.h" // IWYU pragma: keep
 #include "cmGeneratedFileStream.h"
+#include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmXMLParser.h"
 #include "cmXMLWriter.h"
@@ -292,7 +293,7 @@
   {
     if (this->file) {
       std::string content(data, data + length);
-      content = cmSystemTools::TrimWhitespace(content);
+      content = cmTrimWhitespace(content);
       std::string source = this->basePath + "/" + content;
       std::string destination = this->path + "/" + content;
       if (!cmSystemTools::CopyFileIfDifferent(source, destination)) {
diff --git a/Source/CPack/WiX/cmCMakeToWixPath.cxx b/Source/CPack/WiX/cmCMakeToWixPath.cxx
index b3889cf..630a8f8 100644
--- a/Source/CPack/WiX/cmCMakeToWixPath.cxx
+++ b/Source/CPack/WiX/cmCMakeToWixPath.cxx
@@ -2,7 +2,7 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmCMakeToWixPath.h"
 
-#include "cmSystemTools.h"
+#include "cmStringAlgorithms.h"
 
 #include <string>
 #include <vector>
@@ -29,7 +29,7 @@
     return cygpath;
   }
 
-  return cmSystemTools::TrimWhitespace(winpath_chars.data());
+  return cmTrimWhitespace(winpath_chars.data());
 }
 #else
 std::string CMakeToWixPath(const std::string& path)
diff --git a/Source/CPack/WiX/cmWIXAccessControlList.cxx b/Source/CPack/WiX/cmWIXAccessControlList.cxx
index 563de02..b5e287d 100644
--- a/Source/CPack/WiX/cmWIXAccessControlList.cxx
+++ b/Source/CPack/WiX/cmWIXAccessControlList.cxx
@@ -4,6 +4,7 @@
 
 #include "cmCPackGenerator.h"
 
+#include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 
 cmWIXAccessControlList::cmWIXAccessControlList(
@@ -48,8 +49,7 @@
     user = user_and_domain;
   }
 
-  std::vector<std::string> permissions =
-    cmSystemTools::tokenize(permission_string, ",");
+  std::vector<std::string> permissions = cmTokenize(permission_string, ",");
 
   this->SourceWriter.BeginElement("Permission");
   this->SourceWriter.AddAttribute("User", user);
@@ -57,8 +57,7 @@
     this->SourceWriter.AddAttribute("Domain", domain);
   }
   for (std::string const& permission : permissions) {
-    this->EmitBooleanAttribute(entry,
-                               cmSystemTools::TrimWhitespace(permission));
+    this->EmitBooleanAttribute(entry, cmTrimWhitespace(permission));
   }
   this->SourceWriter.EndElement("Permission");
 }
diff --git a/Source/CTest/cmCTestScriptHandler.cxx b/Source/CTest/cmCTestScriptHandler.cxx
index 7a5b8d1..f8d9f1b 100644
--- a/Source/CTest/cmCTestScriptHandler.cxx
+++ b/Source/CTest/cmCTestScriptHandler.cxx
@@ -24,7 +24,6 @@
 #include "cmCTestUploadCommand.h"
 #include "cmCommand.h"
 #include "cmDuration.h"
-#include "cmFunctionBlocker.h"
 #include "cmGeneratedFileStream.h"
 #include "cmGlobalGenerator.h"
 #include "cmMakefile.h"
@@ -49,32 +48,8 @@
 #  include <unistd.h>
 #endif
 
-class cmExecutionStatus;
-struct cmListFileFunction;
-
 #define CTEST_INITIAL_CMAKE_OUTPUT_FILE_NAME "CTestInitialCMakeOutput.log"
 
-// used to keep elapsed time up to date
-class cmCTestScriptFunctionBlocker : public cmFunctionBlocker
-{
-public:
-  bool IsFunctionBlocked(const cmListFileFunction& lff, cmMakefile& mf,
-                         cmExecutionStatus& /*status*/) override;
-  // virtual bool ShouldRemove(const cmListFileFunction& lff, cmMakefile &mf);
-  // virtual void ScopeEnded(cmMakefile &mf);
-
-  cmCTestScriptHandler* CTestScriptHandler;
-};
-
-// simply update the time and don't block anything
-bool cmCTestScriptFunctionBlocker::IsFunctionBlocked(
-  const cmListFileFunction& /*lff*/, cmMakefile& /*mf*/,
-  cmExecutionStatus& /*status*/)
-{
-  this->CTestScriptHandler->UpdateElapsedTime();
-  return false;
-}
-
 cmCTestScriptHandler::cmCTestScriptHandler()
 {
   this->Backup = false;
@@ -373,12 +348,8 @@
   this->Makefile->AddDefinition("CMAKE_LEGACY_CYGWIN_WIN32", "0");
 #endif
 
-  // always add a function blocker to update the elapsed time
-  {
-    auto fb = cm::make_unique<cmCTestScriptFunctionBlocker>();
-    fb->CTestScriptHandler = this;
-    this->Makefile->AddFunctionBlocker(std::move(fb));
-  }
+  // set a callback function to update the elapsed time
+  this->Makefile->OnExecuteCommand([this] { this->UpdateElapsedTime(); });
 
   /* Execute CTestScriptMode.cmake, which loads CMakeDetermineSystem and
   CMakeSystemSpecificInformation, so
diff --git a/Source/cmCommandArgumentParserHelper.cxx b/Source/cmCommandArgumentParserHelper.cxx
index ca29967..5583520 100644
--- a/Source/cmCommandArgumentParserHelper.cxx
+++ b/Source/cmCommandArgumentParserHelper.cxx
@@ -5,6 +5,7 @@
 #include "cmCommandArgumentLexer.h"
 #include "cmMakefile.h"
 #include "cmState.h"
+#include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 
 #include <iostream>
@@ -58,7 +59,7 @@
     std::string str;
     if (cmSystemTools::GetEnv(var, str)) {
       if (this->EscapeQuotes) {
-        return this->AddString(cmSystemTools::EscapeQuotes(str));
+        return this->AddString(cmEscapeQuotes(str));
       }
       return this->AddString(str);
     }
@@ -68,7 +69,7 @@
     if (const std::string* c =
           this->Makefile->GetState()->GetInitializedCacheValue(var)) {
       if (this->EscapeQuotes) {
-        return this->AddString(cmSystemTools::EscapeQuotes(*c));
+        return this->AddString(cmEscapeQuotes(*c));
       }
       return this->AddString(*c);
     }
@@ -99,7 +100,7 @@
     }
   }
   if (this->EscapeQuotes && value) {
-    return this->AddString(cmSystemTools::EscapeQuotes(value));
+    return this->AddString(cmEscapeQuotes(value));
   }
   return this->AddString(value ? value : "");
 }
diff --git a/Source/cmComputeLinkInformation.cxx b/Source/cmComputeLinkInformation.cxx
index 78cddf0..5f46631 100644
--- a/Source/cmComputeLinkInformation.cxx
+++ b/Source/cmComputeLinkInformation.cxx
@@ -990,11 +990,6 @@
     return;
   }
 
-  // If this platform wants a flag before the full path, add it.
-  if (!this->LibLinkFileFlag.empty()) {
-    this->Items.emplace_back(this->LibLinkFileFlag, false);
-  }
-
   // For compatibility with CMake 2.4 include the item's directory in
   // the linker search path.
   if (this->OldLinkDirMode && !target->IsFrameworkOnApple() &&
@@ -1057,11 +1052,6 @@
     this->OldLinkDirItems.push_back(item);
   }
 
-  // If this platform wants a flag before the full path, add it.
-  if (!this->LibLinkFileFlag.empty()) {
-    this->Items.emplace_back(this->LibLinkFileFlag, false);
-  }
-
   // Now add the full path to the library.
   this->Items.emplace_back(item, true);
 }
diff --git a/Source/cmComputeLinkInformation.h b/Source/cmComputeLinkInformation.h
index 3be2c7f..784d3fa 100644
--- a/Source/cmComputeLinkInformation.h
+++ b/Source/cmComputeLinkInformation.h
@@ -56,6 +56,11 @@
   std::string GetChrpathString() const;
   std::set<cmGeneratorTarget const*> const& GetSharedLibrariesLinked() const;
 
+  std::string const& GetLibLinkFileFlag() const
+  {
+    return this->LibLinkFileFlag;
+  }
+
   std::string const& GetRPathLinkFlag() const { return this->RPathLinkFlag; }
   std::string GetRPathLinkString() const;
 
diff --git a/Source/cmCoreTryCompile.cxx b/Source/cmCoreTryCompile.cxx
index d2a4148..e286ed7 100644
--- a/Source/cmCoreTryCompile.cxx
+++ b/Source/cmCoreTryCompile.cxx
@@ -238,7 +238,7 @@
     } else if (doing == DoingLinkOptions) {
       linkOptions.push_back(argv[i]);
     } else if (doing == DoingLinkLibraries) {
-      libsToLink += "\"" + cmSystemTools::TrimWhitespace(argv[i]) + "\" ";
+      libsToLink += "\"" + cmTrimWhitespace(argv[i]) + "\" ";
       if (cmTarget* tgt = this->Makefile->FindTargetToUse(argv[i])) {
         switch (tgt->GetType()) {
           case cmStateEnums::SHARED_LIBRARY:
diff --git a/Source/cmFileAPICodemodel.cxx b/Source/cmFileAPICodemodel.cxx
index e9ee7f5..e4b7670 100644
--- a/Source/cmFileAPICodemodel.cxx
+++ b/Source/cmFileAPICodemodel.cxx
@@ -21,6 +21,7 @@
 #include "cmStateDirectory.h"
 #include "cmStateSnapshot.h"
 #include "cmStateTypes.h"
+#include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmTarget.h"
 #include "cmTargetDepend.h"
@@ -1144,12 +1145,9 @@
 Json::Value Target::DumpInstallDestinations()
 {
   Json::Value destinations = Json::arrayValue;
-  auto installGens = this->GT->Makefile->GetInstallGenerators();
-  for (auto iGen : installGens) {
-    auto itGen = dynamic_cast<cmInstallTargetGenerator*>(iGen);
-    if (itGen != nullptr && itGen->GetTarget() == this->GT) {
-      destinations.append(this->DumpInstallDestination(itGen));
-    }
+  auto installGens = this->GT->Target->GetInstallGenerators();
+  for (auto itGen : installGens) {
+    destinations.append(this->DumpInstallDestination(itGen));
   }
   return destinations;
 }
@@ -1273,11 +1271,11 @@
   lg->GetTargetFlags(&linkLineComputer, this->Config, linkLibs,
                      linkLanguageFlags, linkFlags, frameworkPath, linkPath,
                      this->GT);
-  linkLanguageFlags = cmSystemTools::TrimWhitespace(linkLanguageFlags);
-  linkFlags = cmSystemTools::TrimWhitespace(linkFlags);
-  frameworkPath = cmSystemTools::TrimWhitespace(frameworkPath);
-  linkPath = cmSystemTools::TrimWhitespace(linkPath);
-  linkLibs = cmSystemTools::TrimWhitespace(linkLibs);
+  linkLanguageFlags = cmTrimWhitespace(linkLanguageFlags);
+  linkFlags = cmTrimWhitespace(linkFlags);
+  frameworkPath = cmTrimWhitespace(frameworkPath);
+  linkPath = cmTrimWhitespace(linkPath);
+  linkLibs = cmTrimWhitespace(linkLibs);
 
   if (!linkLanguageFlags.empty()) {
     linkFragments.append(
diff --git a/Source/cmFindPackageCommand.cxx b/Source/cmFindPackageCommand.cxx
index 04fbbad..0159ca5 100644
--- a/Source/cmFindPackageCommand.cxx
+++ b/Source/cmFindPackageCommand.cxx
@@ -517,7 +517,9 @@
         loadedPackage = true;
       } else {
         // The package was not loaded. Report errors.
-        HandlePackageMode(HandlePackageModeType::Module);
+        if (HandlePackageMode(HandlePackageModeType::Module)) {
+          loadedPackage = true;
+        }
       }
     }
   } else {
diff --git a/Source/cmForEachCommand.cxx b/Source/cmForEachCommand.cxx
index 06dce2c..1d961be 100644
--- a/Source/cmForEachCommand.cxx
+++ b/Source/cmForEachCommand.cxx
@@ -8,16 +8,40 @@
 #include <utility>
 
 #include "cm_memory.hxx"
+#include "cm_static_string_view.hxx"
+#include "cm_string_view.hxx"
 
 #include "cmExecutionStatus.h"
+#include "cmFunctionBlocker.h"
+#include "cmListFileCache.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
 #include "cmRange.h"
 #include "cmSystemTools.h"
 
+class cmForEachFunctionBlocker : public cmFunctionBlocker
+{
+public:
+  cmForEachFunctionBlocker(cmMakefile* mf);
+  ~cmForEachFunctionBlocker() override;
+
+  cm::string_view StartCommandName() const override { return "foreach"_s; }
+  cm::string_view EndCommandName() const override { return "endforeach"_s; }
+
+  bool ArgumentsMatch(cmListFileFunction const& lff,
+                      cmMakefile& mf) const override;
+
+  bool Replay(std::vector<cmListFileFunction> functions,
+              cmExecutionStatus& inStatus) override;
+
+  std::vector<std::string> Args;
+
+private:
+  cmMakefile* Makefile;
+};
+
 cmForEachFunctionBlocker::cmForEachFunctionBlocker(cmMakefile* mf)
   : Makefile(mf)
-  , Depth(0)
 {
   this->Makefile->PushLoopBlock();
 }
@@ -27,87 +51,56 @@
   this->Makefile->PopLoopBlock();
 }
 
-bool cmForEachFunctionBlocker::IsFunctionBlocked(const cmListFileFunction& lff,
-                                                 cmMakefile& mf,
-                                                 cmExecutionStatus& inStatus)
+bool cmForEachFunctionBlocker::ArgumentsMatch(cmListFileFunction const& lff,
+                                              cmMakefile& mf) const
 {
-  if (lff.Name.Lower == "foreach") {
-    // record the number of nested foreach commands
-    this->Depth++;
-  } else if (lff.Name.Lower == "endforeach") {
-    // if this is the endofreach for this statement
-    if (!this->Depth) {
-      // Remove the function blocker for this scope or bail.
-      std::unique_ptr<cmFunctionBlocker> fb(
-        mf.RemoveFunctionBlocker(this, lff));
-      if (!fb) {
-        return false;
-      }
-
-      // at end of for each execute recorded commands
-      // store the old value
-      std::string oldDef;
-      if (mf.GetDefinition(this->Args[0])) {
-        oldDef = mf.GetDefinition(this->Args[0]);
-      }
-
-      for (std::string const& arg : cmMakeRange(this->Args).advance(1)) {
-        // set the variable to the loop value
-        mf.AddDefinition(this->Args[0], arg);
-        // Invoke all the functions that were collected in the block.
-        cmExecutionStatus status(mf);
-        for (cmListFileFunction const& func : this->Functions) {
-          status.Clear();
-          mf.ExecuteCommand(func, status);
-          if (status.GetReturnInvoked()) {
-            inStatus.SetReturnInvoked();
-            // restore the variable to its prior value
-            mf.AddDefinition(this->Args[0], oldDef);
-            return true;
-          }
-          if (status.GetBreakInvoked()) {
-            // restore the variable to its prior value
-            mf.AddDefinition(this->Args[0], oldDef);
-            return true;
-          }
-          if (status.GetContinueInvoked()) {
-            break;
-          }
-          if (cmSystemTools::GetFatalErrorOccured()) {
-            return true;
-          }
-        }
-      }
-
-      // restore the variable to its prior value
-      mf.AddDefinition(this->Args[0], oldDef);
-      return true;
-    }
-    // close out a nested foreach
-    this->Depth--;
-  }
-
-  // record the command
-  this->Functions.push_back(lff);
-
-  // always return true
-  return true;
+  std::vector<std::string> expandedArguments;
+  mf.ExpandArguments(lff.Arguments, expandedArguments);
+  return expandedArguments.empty() || expandedArguments[0] == this->Args[0];
 }
 
-bool cmForEachFunctionBlocker::ShouldRemove(const cmListFileFunction& lff,
-                                            cmMakefile& mf)
+bool cmForEachFunctionBlocker::Replay(
+  std::vector<cmListFileFunction> functions, cmExecutionStatus& inStatus)
 {
-  if (lff.Name.Lower == "endforeach") {
-    std::vector<std::string> expandedArguments;
-    mf.ExpandArguments(lff.Arguments, expandedArguments);
-    // if the endforeach has arguments then make sure
-    // they match the begin foreach arguments
-    if ((expandedArguments.empty() ||
-         (expandedArguments[0] == this->Args[0]))) {
-      return true;
+  cmMakefile& mf = inStatus.GetMakefile();
+  // at end of for each execute recorded commands
+  // store the old value
+  std::string oldDef;
+  if (mf.GetDefinition(this->Args[0])) {
+    oldDef = mf.GetDefinition(this->Args[0]);
+  }
+
+  for (std::string const& arg : cmMakeRange(this->Args).advance(1)) {
+    // set the variable to the loop value
+    mf.AddDefinition(this->Args[0], arg);
+    // Invoke all the functions that were collected in the block.
+    cmExecutionStatus status(mf);
+    for (cmListFileFunction const& func : functions) {
+      status.Clear();
+      mf.ExecuteCommand(func, status);
+      if (status.GetReturnInvoked()) {
+        inStatus.SetReturnInvoked();
+        // restore the variable to its prior value
+        mf.AddDefinition(this->Args[0], oldDef);
+        return true;
+      }
+      if (status.GetBreakInvoked()) {
+        // restore the variable to its prior value
+        mf.AddDefinition(this->Args[0], oldDef);
+        return true;
+      }
+      if (status.GetContinueInvoked()) {
+        break;
+      }
+      if (cmSystemTools::GetFatalErrorOccured()) {
+        return true;
+      }
     }
   }
-  return false;
+
+  // restore the variable to its prior value
+  mf.AddDefinition(this->Args[0], oldDef);
+  return true;
 }
 
 bool cmForEachCommand::InitialPass(std::vector<std::string> const& args,
diff --git a/Source/cmForEachCommand.h b/Source/cmForEachCommand.h
index cd112b8..135abf0 100644
--- a/Source/cmForEachCommand.h
+++ b/Source/cmForEachCommand.h
@@ -11,28 +11,8 @@
 #include "cm_memory.hxx"
 
 #include "cmCommand.h"
-#include "cmFunctionBlocker.h"
-#include "cmListFileCache.h"
 
 class cmExecutionStatus;
-class cmMakefile;
-
-class cmForEachFunctionBlocker : public cmFunctionBlocker
-{
-public:
-  cmForEachFunctionBlocker(cmMakefile* mf);
-  ~cmForEachFunctionBlocker() override;
-  bool IsFunctionBlocked(const cmListFileFunction& lff, cmMakefile& mf,
-                         cmExecutionStatus&) override;
-  bool ShouldRemove(const cmListFileFunction& lff, cmMakefile& mf) override;
-
-  std::vector<std::string> Args;
-  std::vector<cmListFileFunction> Functions;
-
-private:
-  cmMakefile* Makefile;
-  int Depth;
-};
 
 /// Starts foreach() ... endforeach() block
 class cmForEachCommand : public cmCommand
diff --git a/Source/cmFunctionBlocker.cxx b/Source/cmFunctionBlocker.cxx
new file mode 100644
index 0000000..5778a71
--- /dev/null
+++ b/Source/cmFunctionBlocker.cxx
@@ -0,0 +1,46 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmFunctionBlocker.h"
+
+#include <cassert>
+#include <sstream>
+#include <utility>
+
+#include "cmExecutionStatus.h"
+#include "cmMakefile.h"
+#include "cmMessageType.h"
+
+bool cmFunctionBlocker::IsFunctionBlocked(const cmListFileFunction& lff,
+                                          cmExecutionStatus& status)
+{
+  if (lff.Name.Lower == this->StartCommandName()) {
+    this->ScopeDepth++;
+  } else if (lff.Name.Lower == this->EndCommandName()) {
+    this->ScopeDepth--;
+    if (this->ScopeDepth == 0U) {
+      cmMakefile& mf = status.GetMakefile();
+      auto self = mf.RemoveFunctionBlocker();
+      assert(self.get() == this);
+
+      if (!this->ArgumentsMatch(lff, mf)) {
+        cmListFileContext const& lfc = this->GetStartingContext();
+        cmListFileContext closingContext =
+          cmListFileContext::FromCommandContext(lff, lfc.FilePath);
+        std::ostringstream e;
+        /* clang-format off */
+        e << "A logical block opening on the line\n"
+          << "  " << lfc << "\n"
+          << "closes on the line\n"
+          << "  " << closingContext << "\n"
+          << "with mis-matching arguments.";
+        /* clang-format on */
+        mf.IssueMessage(MessageType::AUTHOR_WARNING, e.str());
+      }
+
+      return this->Replay(std::move(this->Functions), status);
+    }
+  }
+
+  this->Functions.push_back(lff);
+  return true;
+}
diff --git a/Source/cmFunctionBlocker.h b/Source/cmFunctionBlocker.h
index cd6b05d..87bdccd 100644
--- a/Source/cmFunctionBlocker.h
+++ b/Source/cmFunctionBlocker.h
@@ -3,6 +3,12 @@
 #ifndef cmFunctionBlocker_h
 #define cmFunctionBlocker_h
 
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <vector>
+
+#include "cm_string_view.hxx"
+
 #include "cmListFileCache.h"
 
 class cmExecutionStatus;
@@ -14,17 +20,8 @@
   /**
    * should a function be blocked
    */
-  virtual bool IsFunctionBlocked(const cmListFileFunction& lff, cmMakefile& mf,
-                                 cmExecutionStatus& status) = 0;
-
-  /**
-   * should this function blocker be removed, useful when one function adds a
-   * blocker and another must remove it
-   */
-  virtual bool ShouldRemove(const cmListFileFunction&, cmMakefile&)
-  {
-    return false;
-  }
+  bool IsFunctionBlocked(cmListFileFunction const& lff,
+                         cmExecutionStatus& status);
 
   virtual ~cmFunctionBlocker() = default;
 
@@ -39,7 +36,19 @@
   }
 
 private:
+  virtual cm::string_view StartCommandName() const = 0;
+  virtual cm::string_view EndCommandName() const = 0;
+
+  virtual bool ArgumentsMatch(cmListFileFunction const& lff,
+                              cmMakefile& mf) const = 0;
+
+  virtual bool Replay(std::vector<cmListFileFunction> functions,
+                      cmExecutionStatus& status) = 0;
+
+private:
   cmListFileContext StartingContext;
+  std::vector<cmListFileFunction> Functions;
+  unsigned int ScopeDepth = 1;
 };
 
 #endif
diff --git a/Source/cmFunctionCommand.cxx b/Source/cmFunctionCommand.cxx
index 2809cf7..610f516 100644
--- a/Source/cmFunctionCommand.cxx
+++ b/Source/cmFunctionCommand.cxx
@@ -5,8 +5,13 @@
 #include <sstream>
 #include <utility>
 
+#include "cm_static_string_view.hxx"
+#include "cm_string_view.hxx"
+
 #include "cmAlgorithms.h"
 #include "cmExecutionStatus.h"
+#include "cmFunctionBlocker.h"
+#include "cmListFileCache.h"
 #include "cmMakefile.h"
 #include "cmPolicies.h"
 #include "cmRange.h"
@@ -102,53 +107,42 @@
   return true;
 }
 
-bool cmFunctionFunctionBlocker::IsFunctionBlocked(
-  const cmListFileFunction& lff, cmMakefile& mf, cmExecutionStatus&)
+class cmFunctionFunctionBlocker : public cmFunctionBlocker
 {
-  // record commands until we hit the ENDFUNCTION
-  // at the ENDFUNCTION call we shift gears and start looking for invocations
-  if (lff.Name.Lower == "function") {
-    this->Depth++;
-  } else if (lff.Name.Lower == "endfunction") {
-    // if this is the endfunction for this function then execute
-    if (!this->Depth) {
-      // create a new command and add it to cmake
-      cmFunctionHelperCommand f;
-      f.Args = this->Args;
-      f.Functions = this->Functions;
-      f.FilePath = this->GetStartingContext().FilePath;
-      mf.RecordPolicies(f.Policies);
-      mf.GetState()->AddScriptedCommand(this->Args[0], std::move(f));
-      // remove the function blocker now that the function is defined
-      mf.RemoveFunctionBlocker(this, lff);
-      return true;
-    }
-    // decrement for each nested function that ends
-    this->Depth--;
-  }
+public:
+  cm::string_view StartCommandName() const override { return "function"_s; }
+  cm::string_view EndCommandName() const override { return "endfunction"_s; }
 
-  // if it wasn't an endfunction and we are not executing then we must be
-  // recording
-  this->Functions.push_back(lff);
-  return true;
+  bool ArgumentsMatch(cmListFileFunction const&,
+                      cmMakefile& mf) const override;
+
+  bool Replay(std::vector<cmListFileFunction> functions,
+              cmExecutionStatus& status) override;
+
+  std::vector<std::string> Args;
+};
+
+bool cmFunctionFunctionBlocker::ArgumentsMatch(cmListFileFunction const& lff,
+                                               cmMakefile& mf) const
+{
+  std::vector<std::string> expandedArguments;
+  mf.ExpandArguments(lff.Arguments, expandedArguments,
+                     this->GetStartingContext().FilePath.c_str());
+  return expandedArguments.empty() || expandedArguments[0] == this->Args[0];
 }
 
-bool cmFunctionFunctionBlocker::ShouldRemove(const cmListFileFunction& lff,
-                                             cmMakefile& mf)
+bool cmFunctionFunctionBlocker::Replay(
+  std::vector<cmListFileFunction> functions, cmExecutionStatus& status)
 {
-  if (lff.Name.Lower == "endfunction") {
-    std::vector<std::string> expandedArguments;
-    mf.ExpandArguments(lff.Arguments, expandedArguments,
-                       this->GetStartingContext().FilePath.c_str());
-    // if the endfunction has arguments then make sure
-    // they match the ones in the opening function command
-    if ((expandedArguments.empty() ||
-         (expandedArguments[0] == this->Args[0]))) {
-      return true;
-    }
-  }
-
-  return false;
+  cmMakefile& mf = status.GetMakefile();
+  // create a new command and add it to cmake
+  cmFunctionHelperCommand f;
+  f.Args = this->Args;
+  f.Functions = std::move(functions);
+  f.FilePath = this->GetStartingContext().FilePath;
+  mf.RecordPolicies(f.Policies);
+  mf.GetState()->AddScriptedCommand(this->Args[0], std::move(f));
+  return true;
 }
 
 bool cmFunctionCommand::InitialPass(std::vector<std::string> const& args,
diff --git a/Source/cmFunctionCommand.h b/Source/cmFunctionCommand.h
index 449a180..b334525 100644
--- a/Source/cmFunctionCommand.h
+++ b/Source/cmFunctionCommand.h
@@ -11,23 +11,8 @@
 #include "cm_memory.hxx"
 
 #include "cmCommand.h"
-#include "cmFunctionBlocker.h"
-#include "cmListFileCache.h"
 
 class cmExecutionStatus;
-class cmMakefile;
-
-class cmFunctionFunctionBlocker : public cmFunctionBlocker
-{
-public:
-  bool IsFunctionBlocked(const cmListFileFunction&, cmMakefile& mf,
-                         cmExecutionStatus&) override;
-  bool ShouldRemove(const cmListFileFunction&, cmMakefile& mf) override;
-
-  std::vector<std::string> Args;
-  std::vector<cmListFileFunction> Functions;
-  int Depth = 0;
-};
 
 /// Starts function() ... endfunction() block
 class cmFunctionCommand : public cmCommand
diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx
index 7340bc2..03fc5ae 100644
--- a/Source/cmGeneratorTarget.cxx
+++ b/Source/cmGeneratorTarget.cxx
@@ -3410,8 +3410,7 @@
       cmSystemTools::ParseUnixCommandLine(
         value.c_str() + LINKER_SHELL.length(), linkerOptions);
     } else {
-      linkerOptions =
-        cmSystemTools::tokenize(value.substr(LINKER.length()), ",");
+      linkerOptions = cmTokenize(value.substr(LINKER.length()), ",");
     }
 
     if (linkerOptions.empty() ||
@@ -5219,7 +5218,7 @@
   const std::string& config, cmOptionalLinkInterface& iface,
   cmGeneratorTarget const* headTarget) const
 {
-  if (iface.ExplicitLibraries) {
+  if (iface.Explicit) {
     if (this->GetType() == cmStateEnums::SHARED_LIBRARY ||
         this->GetType() == cmStateEnums::STATIC_LIBRARY ||
         this->GetType() == cmStateEnums::INTERFACE_LIBRARY) {
@@ -5603,8 +5602,9 @@
   // libraries and executables that export symbols.
   const char* explicitLibraries = nullptr;
   std::string linkIfaceProp;
-  if (this->GetPolicyStatusCMP0022() != cmPolicies::OLD &&
-      this->GetPolicyStatusCMP0022() != cmPolicies::WARN) {
+  bool const cmp0022NEW = (this->GetPolicyStatusCMP0022() != cmPolicies::OLD &&
+                           this->GetPolicyStatusCMP0022() != cmPolicies::WARN);
+  if (cmp0022NEW) {
     // CMP0022 NEW behavior is to use INTERFACE_LINK_LIBRARIES.
     linkIfaceProp = "INTERFACE_LINK_LIBRARIES";
     explicitLibraries = this->GetProperty(linkIfaceProp);
@@ -5659,15 +5659,14 @@
     return;
   }
   iface.Exists = true;
-  iface.ExplicitLibraries = explicitLibraries;
+  iface.Explicit = cmp0022NEW || explicitLibraries != nullptr;
 
   if (explicitLibraries) {
     // The interface libraries have been explicitly set.
     this->ExpandLinkItems(linkIfaceProp, explicitLibraries, config, headTarget,
                           usage_requirements_only, iface.Libraries,
                           iface.HadHeadSensitiveCondition);
-  } else if (this->GetPolicyStatusCMP0022() == cmPolicies::WARN ||
-             this->GetPolicyStatusCMP0022() == cmPolicies::OLD)
+  } else if (!cmp0022NEW)
   // If CMP0022 is NEW then the plain tll signature sets the
   // INTERFACE_LINK_LIBRARIES, so if we get here then the project
   // cleared the property explicitly and we should not fall back
diff --git a/Source/cmGetFilenameComponentCommand.cxx b/Source/cmGetFilenameComponentCommand.cxx
index fc82535..c948b2a 100644
--- a/Source/cmGetFilenameComponentCommand.cxx
+++ b/Source/cmGetFilenameComponentCommand.cxx
@@ -4,6 +4,7 @@
 
 #include "cmMakefile.h"
 #include "cmStateTypes.h"
+#include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 
 class cmExecutionStatus;
@@ -64,7 +65,7 @@
     // First assume the path to the program was specified with no
     // arguments and with no quoting or escaping for spaces.
     // Only bother doing this if there is non-whitespace.
-    if (!cmSystemTools::TrimWhitespace(filename).empty()) {
+    if (!cmTrimWhitespace(filename).empty()) {
       result = cmSystemTools::FindProgram(filename);
     }
 
diff --git a/Source/cmGlobalNinjaGenerator.cxx b/Source/cmGlobalNinjaGenerator.cxx
index 0b68966..9914d15 100644
--- a/Source/cmGlobalNinjaGenerator.cxx
+++ b/Source/cmGlobalNinjaGenerator.cxx
@@ -383,7 +383,7 @@
   }
 
   // Do not add a variable if the value is empty.
-  std::string val = cmSystemTools::TrimWhitespace(value);
+  std::string val = cmTrimWhitespace(value);
   if (val.empty()) {
     return;
   }
@@ -528,7 +528,7 @@
       cmSystemTools::SetFatalErrorOccured();
       return false;
     }
-    this->NinjaVersion = cmSystemTools::TrimWhitespace(version);
+    this->NinjaVersion = cmTrimWhitespace(version);
     this->CheckNinjaFeatures();
   }
   return true;
diff --git a/Source/cmGlobalVisualStudio10Generator.cxx b/Source/cmGlobalVisualStudio10Generator.cxx
index 4a3cadd..720b6c5 100644
--- a/Source/cmGlobalVisualStudio10Generator.cxx
+++ b/Source/cmGlobalVisualStudio10Generator.cxx
@@ -325,7 +325,7 @@
 bool cmGlobalVisualStudio10Generator::ParseGeneratorToolset(
   std::string const& ts, cmMakefile* mf)
 {
-  std::vector<std::string> const fields = cmSystemTools::tokenize(ts, ",");
+  std::vector<std::string> const fields = cmTokenize(ts, ",");
   std::vector<std::string>::const_iterator fi = fields.begin();
   if (fi == fields.end()) {
     return true;
diff --git a/Source/cmGlobalVisualStudio7Generator.cxx b/Source/cmGlobalVisualStudio7Generator.cxx
index bead0e3..ca80d3b 100644
--- a/Source/cmGlobalVisualStudio7Generator.cxx
+++ b/Source/cmGlobalVisualStudio7Generator.cxx
@@ -518,9 +518,9 @@
           const std::string::size_type posEqual = itPair.find('=');
           if (posEqual != std::string::npos) {
             const std::string key =
-              cmSystemTools::TrimWhitespace(itPair.substr(0, posEqual));
+              cmTrimWhitespace(itPair.substr(0, posEqual));
             const std::string value =
-              cmSystemTools::TrimWhitespace(itPair.substr(posEqual + 1));
+              cmTrimWhitespace(itPair.substr(posEqual + 1));
             fout << "\t\t" << key << " = " << value << "\n";
             if (key == "SolutionGuid") {
               addGuid = false;
diff --git a/Source/cmGlobalXCodeGenerator.cxx b/Source/cmGlobalXCodeGenerator.cxx
index 8f4ae62..f675c8e 100644
--- a/Source/cmGlobalXCodeGenerator.cxx
+++ b/Source/cmGlobalXCodeGenerator.cxx
@@ -2945,8 +2945,7 @@
   if (it != this->TargetGroup.end()) {
     tgroup = it->second;
   } else {
-    std::vector<std::string> tgt_folders =
-      cmSystemTools::tokenize(target, "/");
+    std::vector<std::string> tgt_folders = cmTokenize(target, "/");
     std::string curr_tgt_folder;
     for (std::vector<std::string>::size_type i = 0; i < tgt_folders.size();
          i++) {
@@ -2980,8 +2979,7 @@
   if (sg->GetFullName() != sg->GetName()) {
     std::string curr_folder = target;
     curr_folder += "/";
-    for (auto const& folder :
-         cmSystemTools::tokenize(sg->GetFullName(), "\\")) {
+    for (auto const& folder : cmTokenize(sg->GetFullName(), "\\")) {
       curr_folder += folder;
       std::map<std::string, cmXCodeObject*>::iterator i_folder =
         this->GroupNameMap.find(curr_folder);
diff --git a/Source/cmIfCommand.cxx b/Source/cmIfCommand.cxx
index 385022c..c5cfd8c 100644
--- a/Source/cmIfCommand.cxx
+++ b/Source/cmIfCommand.cxx
@@ -3,10 +3,14 @@
 #include "cmIfCommand.h"
 
 #include "cm_memory.hxx"
+#include "cm_static_string_view.hxx"
+#include "cm_string_view.hxx"
 
 #include "cmConditionEvaluator.h"
 #include "cmExecutionStatus.h"
 #include "cmExpandedCommandArgument.h"
+#include "cmFunctionBlocker.h"
+#include "cmListFileCache.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
 #include "cmOutputConverter.h"
@@ -28,152 +32,138 @@
   return err;
 }
 
-//=========================================================================
-bool cmIfFunctionBlocker::IsFunctionBlocked(const cmListFileFunction& lff,
-                                            cmMakefile& mf,
-                                            cmExecutionStatus& inStatus)
+class cmIfFunctionBlocker : public cmFunctionBlocker
 {
-  // we start by recording all the functions
-  if (lff.Name.Lower == "if") {
-    this->ScopeDepth++;
-  } else if (lff.Name.Lower == "endif") {
-    this->ScopeDepth--;
-    // if this is the endif for this if statement, then start executing
-    if (!this->ScopeDepth) {
-      // Remove the function blocker for this scope or bail.
-      std::unique_ptr<cmFunctionBlocker> fb(
-        mf.RemoveFunctionBlocker(this, lff));
-      if (!fb) {
-        return false;
-      }
+public:
+  cm::string_view StartCommandName() const override { return "if"_s; }
+  cm::string_view EndCommandName() const override { return "endif"_s; }
 
-      // execute the functions for the true parts of the if statement
-      cmExecutionStatus status(mf);
-      int scopeDepth = 0;
-      for (cmListFileFunction const& func : this->Functions) {
-        // keep track of scope depth
-        if (func.Name.Lower == "if") {
-          scopeDepth++;
-        }
-        if (func.Name.Lower == "endif") {
-          scopeDepth--;
-        }
-        // watch for our state change
-        if (scopeDepth == 0 && func.Name.Lower == "else") {
+  bool ArgumentsMatch(cmListFileFunction const& lff,
+                      cmMakefile&) const override;
 
-          if (this->ElseSeen) {
-            cmListFileBacktrace bt = mf.GetBacktrace(func);
-            mf.GetCMakeInstance()->IssueMessage(
-              MessageType::FATAL_ERROR,
-              "A duplicate ELSE command was found inside an IF block.", bt);
-            cmSystemTools::SetFatalErrorOccured();
-            return true;
-          }
+  bool Replay(std::vector<cmListFileFunction> functions,
+              cmExecutionStatus& inStatus) override;
 
-          this->IsBlocking = this->HasRun;
-          this->HasRun = true;
-          this->ElseSeen = true;
+  std::vector<cmListFileArgument> Args;
+  bool IsBlocking;
+  bool HasRun = false;
+  bool ElseSeen = false;
+};
 
-          // if trace is enabled, print a (trivially) evaluated "else"
-          // statement
-          if (!this->IsBlocking && mf.GetCMakeInstance()->GetTrace()) {
-            mf.PrintCommandTrace(func);
-          }
-        } else if (scopeDepth == 0 && func.Name.Lower == "elseif") {
-          if (this->ElseSeen) {
-            cmListFileBacktrace bt = mf.GetBacktrace(func);
-            mf.GetCMakeInstance()->IssueMessage(
-              MessageType::FATAL_ERROR,
-              "An ELSEIF command was found after an ELSE command.", bt);
-            cmSystemTools::SetFatalErrorOccured();
-            return true;
-          }
-
-          if (this->HasRun) {
-            this->IsBlocking = true;
-          } else {
-            // if trace is enabled, print the evaluated "elseif" statement
-            if (mf.GetCMakeInstance()->GetTrace()) {
-              mf.PrintCommandTrace(func);
-            }
-
-            std::string errorString;
-
-            std::vector<cmExpandedCommandArgument> expandedArguments;
-            mf.ExpandArguments(func.Arguments, expandedArguments);
-
-            MessageType messType;
-
-            cmListFileContext conditionContext =
-              cmListFileContext::FromCommandContext(
-                func, this->GetStartingContext().FilePath);
-
-            cmConditionEvaluator conditionEvaluator(mf, conditionContext,
-                                                    mf.GetBacktrace(func));
-
-            bool isTrue = conditionEvaluator.IsTrue(expandedArguments,
-                                                    errorString, messType);
-
-            if (!errorString.empty()) {
-              std::string err = cmIfCommandError(expandedArguments);
-              err += errorString;
-              cmListFileBacktrace bt = mf.GetBacktrace(func);
-              mf.GetCMakeInstance()->IssueMessage(messType, err, bt);
-              if (messType == MessageType::FATAL_ERROR) {
-                cmSystemTools::SetFatalErrorOccured();
-                return true;
-              }
-            }
-
-            if (isTrue) {
-              this->IsBlocking = false;
-              this->HasRun = true;
-            }
-          }
-        }
-
-        // should we execute?
-        else if (!this->IsBlocking) {
-          status.Clear();
-          mf.ExecuteCommand(func, status);
-          if (status.GetReturnInvoked()) {
-            inStatus.SetReturnInvoked();
-            return true;
-          }
-          if (status.GetBreakInvoked()) {
-            inStatus.SetBreakInvoked();
-            return true;
-          }
-          if (status.GetContinueInvoked()) {
-            inStatus.SetContinueInvoked();
-            return true;
-          }
-        }
-      }
-      return true;
-    }
-  }
-
-  // record the command
-  this->Functions.push_back(lff);
-
-  // always return true
-  return true;
+bool cmIfFunctionBlocker::ArgumentsMatch(cmListFileFunction const& lff,
+                                         cmMakefile&) const
+{
+  return lff.Arguments.empty() || lff.Arguments == this->Args;
 }
 
-//=========================================================================
-bool cmIfFunctionBlocker::ShouldRemove(const cmListFileFunction& lff,
-                                       cmMakefile&)
+bool cmIfFunctionBlocker::Replay(std::vector<cmListFileFunction> functions,
+                                 cmExecutionStatus& inStatus)
 {
-  if (lff.Name.Lower == "endif") {
-    // if the endif has arguments, then make sure
-    // they match the arguments of the matching if
-    if (lff.Arguments.empty() || lff.Arguments == this->Args) {
-      return true;
+  cmMakefile& mf = inStatus.GetMakefile();
+  // execute the functions for the true parts of the if statement
+  cmExecutionStatus status(mf);
+  int scopeDepth = 0;
+  for (cmListFileFunction const& func : functions) {
+    // keep track of scope depth
+    if (func.Name.Lower == "if") {
+      scopeDepth++;
+    }
+    if (func.Name.Lower == "endif") {
+      scopeDepth--;
+    }
+    // watch for our state change
+    if (scopeDepth == 0 && func.Name.Lower == "else") {
+
+      if (this->ElseSeen) {
+        cmListFileBacktrace bt = mf.GetBacktrace(func);
+        mf.GetCMakeInstance()->IssueMessage(
+          MessageType::FATAL_ERROR,
+          "A duplicate ELSE command was found inside an IF block.", bt);
+        cmSystemTools::SetFatalErrorOccured();
+        return true;
+      }
+
+      this->IsBlocking = this->HasRun;
+      this->HasRun = true;
+      this->ElseSeen = true;
+
+      // if trace is enabled, print a (trivially) evaluated "else"
+      // statement
+      if (!this->IsBlocking && mf.GetCMakeInstance()->GetTrace()) {
+        mf.PrintCommandTrace(func);
+      }
+    } else if (scopeDepth == 0 && func.Name.Lower == "elseif") {
+      if (this->ElseSeen) {
+        cmListFileBacktrace bt = mf.GetBacktrace(func);
+        mf.GetCMakeInstance()->IssueMessage(
+          MessageType::FATAL_ERROR,
+          "An ELSEIF command was found after an ELSE command.", bt);
+        cmSystemTools::SetFatalErrorOccured();
+        return true;
+      }
+
+      if (this->HasRun) {
+        this->IsBlocking = true;
+      } else {
+        // if trace is enabled, print the evaluated "elseif" statement
+        if (mf.GetCMakeInstance()->GetTrace()) {
+          mf.PrintCommandTrace(func);
+        }
+
+        std::string errorString;
+
+        std::vector<cmExpandedCommandArgument> expandedArguments;
+        mf.ExpandArguments(func.Arguments, expandedArguments);
+
+        MessageType messType;
+
+        cmListFileContext conditionContext =
+          cmListFileContext::FromCommandContext(
+            func, this->GetStartingContext().FilePath);
+
+        cmConditionEvaluator conditionEvaluator(mf, conditionContext,
+                                                mf.GetBacktrace(func));
+
+        bool isTrue =
+          conditionEvaluator.IsTrue(expandedArguments, errorString, messType);
+
+        if (!errorString.empty()) {
+          std::string err = cmIfCommandError(expandedArguments);
+          err += errorString;
+          cmListFileBacktrace bt = mf.GetBacktrace(func);
+          mf.GetCMakeInstance()->IssueMessage(messType, err, bt);
+          if (messType == MessageType::FATAL_ERROR) {
+            cmSystemTools::SetFatalErrorOccured();
+            return true;
+          }
+        }
+
+        if (isTrue) {
+          this->IsBlocking = false;
+          this->HasRun = true;
+        }
+      }
+    }
+
+    // should we execute?
+    else if (!this->IsBlocking) {
+      status.Clear();
+      mf.ExecuteCommand(func, status);
+      if (status.GetReturnInvoked()) {
+        inStatus.SetReturnInvoked();
+        return true;
+      }
+      if (status.GetBreakInvoked()) {
+        inStatus.SetBreakInvoked();
+        return true;
+      }
+      if (status.GetContinueInvoked()) {
+        inStatus.SetContinueInvoked();
+        return true;
+      }
     }
   }
-
-  return false;
+  return true;
 }
 
 //=========================================================================
@@ -208,7 +198,6 @@
   {
     auto fb = cm::make_unique<cmIfFunctionBlocker>();
     // if is isn't true block the commands
-    fb->ScopeDepth = 1;
     fb->IsBlocking = !isTrue;
     if (isTrue) {
       fb->HasRun = true;
diff --git a/Source/cmIfCommand.h b/Source/cmIfCommand.h
index 775e609..820ffa4 100644
--- a/Source/cmIfCommand.h
+++ b/Source/cmIfCommand.h
@@ -7,26 +7,8 @@
 
 #include <vector>
 
-#include "cmFunctionBlocker.h"
-#include "cmListFileCache.h"
-
 class cmExecutionStatus;
-class cmMakefile;
-
-class cmIfFunctionBlocker : public cmFunctionBlocker
-{
-public:
-  bool IsFunctionBlocked(const cmListFileFunction& lff, cmMakefile& mf,
-                         cmExecutionStatus&) override;
-  bool ShouldRemove(const cmListFileFunction& lff, cmMakefile& mf) override;
-
-  std::vector<cmListFileArgument> Args;
-  std::vector<cmListFileFunction> Functions;
-  bool IsBlocking;
-  bool HasRun = false;
-  bool ElseSeen = false;
-  unsigned int ScopeDepth = 0;
-};
+struct cmListFileArgument;
 
 /// Starts an if block
 bool cmIfCommand(std::vector<cmListFileArgument> const& args,
diff --git a/Source/cmInstallCommand.cxx b/Source/cmInstallCommand.cxx
index aca7268..5349a9d 100644
--- a/Source/cmInstallCommand.cxx
+++ b/Source/cmInstallCommand.cxx
@@ -43,11 +43,13 @@
   target.SetHaveInstallRule(true);
   const char* component = namelink ? args.GetNamelinkComponent().c_str()
                                    : args.GetComponent().c_str();
-  return new cmInstallTargetGenerator(
+  auto g = new cmInstallTargetGenerator(
     target.GetName(), destination.c_str(), impLib,
     args.GetPermissions().c_str(), args.GetConfigurations(), component,
     message, args.GetExcludeFromAll(), args.GetOptional() || forceOpt,
     backtrace);
+  target.AddInstallGenerator(g);
+  return g;
 }
 
 static cmInstallTargetGenerator* CreateInstallTargetGenerator(
diff --git a/Source/cmJsonObjects.cxx b/Source/cmJsonObjects.cxx
index 2423faf..953333c 100644
--- a/Source/cmJsonObjects.cxx
+++ b/Source/cmJsonObjects.cxx
@@ -20,6 +20,7 @@
 #include "cmStateDirectory.h"
 #include "cmStateSnapshot.h"
 #include "cmStateTypes.h"
+#include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmTarget.h"
 #include "cmTest.h"
@@ -541,19 +542,19 @@
     lg->GetTargetFlags(&linkLineComputer, config, linkLibs, linkLanguageFlags,
                        linkFlags, frameworkPath, linkPath, target);
 
-    linkLibs = cmSystemTools::TrimWhitespace(linkLibs);
-    linkFlags = cmSystemTools::TrimWhitespace(linkFlags);
-    linkLanguageFlags = cmSystemTools::TrimWhitespace(linkLanguageFlags);
-    frameworkPath = cmSystemTools::TrimWhitespace(frameworkPath);
-    linkPath = cmSystemTools::TrimWhitespace(linkPath);
+    linkLibs = cmTrimWhitespace(linkLibs);
+    linkFlags = cmTrimWhitespace(linkFlags);
+    linkLanguageFlags = cmTrimWhitespace(linkLanguageFlags);
+    frameworkPath = cmTrimWhitespace(frameworkPath);
+    linkPath = cmTrimWhitespace(linkPath);
 
-    if (!cmSystemTools::TrimWhitespace(linkLibs).empty()) {
+    if (!cmTrimWhitespace(linkLibs).empty()) {
       result[kLINK_LIBRARIES_KEY] = linkLibs;
     }
-    if (!cmSystemTools::TrimWhitespace(linkFlags).empty()) {
+    if (!cmTrimWhitespace(linkFlags).empty()) {
       result[kLINK_FLAGS_KEY] = linkFlags;
     }
-    if (!cmSystemTools::TrimWhitespace(linkLanguageFlags).empty()) {
+    if (!cmTrimWhitespace(linkLanguageFlags).empty()) {
       result[kLINK_LANGUAGE_FLAGS_KEY] = linkLanguageFlags;
     }
     if (!frameworkPath.empty()) {
diff --git a/Source/cmLinkItem.h b/Source/cmLinkItem.h
index 6450c62..d71ff49 100644
--- a/Source/cmLinkItem.h
+++ b/Source/cmLinkItem.h
@@ -87,7 +87,7 @@
   bool LibrariesDone = false;
   bool AllDone = false;
   bool Exists = false;
-  const char* ExplicitLibraries = nullptr;
+  bool Explicit = false;
 };
 
 struct cmHeadToLinkInterfaceMap
diff --git a/Source/cmLinkLineComputer.cxx b/Source/cmLinkLineComputer.cxx
index 8746b35..4430f97 100644
--- a/Source/cmLinkLineComputer.cxx
+++ b/Source/cmLinkLineComputer.cxx
@@ -63,6 +63,7 @@
       continue;
     }
     if (item.IsPath) {
+      linkLibs += cli.GetLibLinkFileFlag();
       linkLibs +=
         this->ConvertToOutputFormat(this->ConvertToLinkReference(item.Value));
     } else {
diff --git a/Source/cmListCommand.cxx b/Source/cmListCommand.cxx
index 8c14596..1f748ca 100644
--- a/Source/cmListCommand.cxx
+++ b/Source/cmListCommand.cxx
@@ -855,7 +855,7 @@
                   { "STRIP", 0,
                     [&command](const std::string& s) -> std::string {
                       if (command.Selector->InSelection(s)) {
-                        return cmSystemTools::TrimWhitespace(s);
+                        return cmTrimWhitespace(s);
                       }
 
                       return s;
diff --git a/Source/cmMacroCommand.cxx b/Source/cmMacroCommand.cxx
index 1f2b5b2..8689c8f 100644
--- a/Source/cmMacroCommand.cxx
+++ b/Source/cmMacroCommand.cxx
@@ -7,9 +7,13 @@
 #include <utility>
 
 #include "cm_memory.hxx"
+#include "cm_static_string_view.hxx"
+#include "cm_string_view.hxx"
 
 #include "cmAlgorithms.h"
 #include "cmExecutionStatus.h"
+#include "cmFunctionBlocker.h"
+#include "cmListFileCache.h"
 #include "cmMakefile.h"
 #include "cmPolicies.h"
 #include "cmRange.h"
@@ -136,55 +140,43 @@
   return true;
 }
 
-bool cmMacroFunctionBlocker::IsFunctionBlocked(const cmListFileFunction& lff,
-                                               cmMakefile& mf,
-                                               cmExecutionStatus&)
+class cmMacroFunctionBlocker : public cmFunctionBlocker
 {
-  // record commands until we hit the ENDMACRO
-  // at the ENDMACRO call we shift gears and start looking for invocations
-  if (lff.Name.Lower == "macro") {
-    this->Depth++;
-  } else if (lff.Name.Lower == "endmacro") {
-    // if this is the endmacro for this macro then execute
-    if (!this->Depth) {
-      mf.AppendProperty("MACROS", this->Args[0].c_str());
-      // create a new command and add it to cmake
-      cmMacroHelperCommand f;
-      f.Args = this->Args;
-      f.Functions = this->Functions;
-      f.FilePath = this->GetStartingContext().FilePath;
-      mf.RecordPolicies(f.Policies);
-      mf.GetState()->AddScriptedCommand(this->Args[0], std::move(f));
-      // remove the function blocker now that the macro is defined
-      mf.RemoveFunctionBlocker(this, lff);
-      return true;
-    }
-    // decrement for each nested macro that ends
-    this->Depth--;
-  }
+public:
+  cm::string_view StartCommandName() const override { return "macro"_s; }
+  cm::string_view EndCommandName() const override { return "endmacro"_s; }
 
-  // if it wasn't an endmacro and we are not executing then we must be
-  // recording
-  this->Functions.push_back(lff);
-  return true;
+  bool ArgumentsMatch(cmListFileFunction const&,
+                      cmMakefile& mf) const override;
+
+  bool Replay(std::vector<cmListFileFunction> functions,
+              cmExecutionStatus& status) override;
+
+  std::vector<std::string> Args;
+};
+
+bool cmMacroFunctionBlocker::ArgumentsMatch(cmListFileFunction const& lff,
+                                            cmMakefile& mf) const
+{
+  std::vector<std::string> expandedArguments;
+  mf.ExpandArguments(lff.Arguments, expandedArguments,
+                     this->GetStartingContext().FilePath.c_str());
+  return expandedArguments.empty() || expandedArguments[0] == this->Args[0];
 }
 
-bool cmMacroFunctionBlocker::ShouldRemove(const cmListFileFunction& lff,
-                                          cmMakefile& mf)
+bool cmMacroFunctionBlocker::Replay(std::vector<cmListFileFunction> functions,
+                                    cmExecutionStatus& status)
 {
-  if (lff.Name.Lower == "endmacro") {
-    std::vector<std::string> expandedArguments;
-    mf.ExpandArguments(lff.Arguments, expandedArguments,
-                       this->GetStartingContext().FilePath.c_str());
-    // if the endmacro has arguments make sure they
-    // match the arguments of the macro
-    if ((expandedArguments.empty() ||
-         (expandedArguments[0] == this->Args[0]))) {
-      return true;
-    }
-  }
-
-  return false;
+  cmMakefile& mf = status.GetMakefile();
+  mf.AppendProperty("MACROS", this->Args[0].c_str());
+  // create a new command and add it to cmake
+  cmMacroHelperCommand f;
+  f.Args = this->Args;
+  f.Functions = std::move(functions);
+  f.FilePath = this->GetStartingContext().FilePath;
+  mf.RecordPolicies(f.Policies);
+  mf.GetState()->AddScriptedCommand(this->Args[0], std::move(f));
+  return true;
 }
 
 bool cmMacroCommand::InitialPass(std::vector<std::string> const& args,
diff --git a/Source/cmMacroCommand.h b/Source/cmMacroCommand.h
index 3ebd959..0d7083a 100644
--- a/Source/cmMacroCommand.h
+++ b/Source/cmMacroCommand.h
@@ -11,23 +11,8 @@
 #include "cm_memory.hxx"
 
 #include "cmCommand.h"
-#include "cmFunctionBlocker.h"
-#include "cmListFileCache.h"
 
 class cmExecutionStatus;
-class cmMakefile;
-
-class cmMacroFunctionBlocker : public cmFunctionBlocker
-{
-public:
-  bool IsFunctionBlocked(const cmListFileFunction&, cmMakefile& mf,
-                         cmExecutionStatus&) override;
-  bool ShouldRemove(const cmListFileFunction&, cmMakefile& mf) override;
-
-  std::vector<std::string> Args;
-  std::vector<cmListFileFunction> Functions;
-  int Depth = 0;
-};
 
 /// Starts macro() ... endmacro() block
 class cmMacroCommand : public cmCommand
diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx
index 8188ffa..86a002f 100644
--- a/Source/cmMakefile.cxx
+++ b/Source/cmMakefile.cxx
@@ -353,6 +353,11 @@
   cmMakefile* Makefile;
 };
 
+void cmMakefile::OnExecuteCommand(std::function<void()> callback)
+{
+  this->ExecuteCommandCallback = std::move(callback);
+}
+
 bool cmMakefile::ExecuteCommand(const cmListFileFunction& lff,
                                 cmExecutionStatus& status)
 {
@@ -364,6 +369,10 @@
     return result;
   }
 
+  if (this->ExecuteCommandCallback) {
+    this->ExecuteCommandCallback();
+  }
+
   // Place this call on the call stack.
   cmMakefileCall stack_manager(this, lff, status);
   static_cast<void>(stack_manager);
@@ -2166,8 +2175,7 @@
   if (delimiter == nullptr) {
     delimiter = "\\";
   }
-  return this->GetOrCreateSourceGroup(
-    cmSystemTools::tokenize(name, delimiter));
+  return this->GetOrCreateSourceGroup(cmTokenize(name, delimiter));
 }
 
 /**
@@ -2659,7 +2667,7 @@
       if (const char* val = this->GetDefinition(var)) {
         // Store the value in the output escaping as requested.
         if (escapeQuotes) {
-          source.append(cmSystemTools::EscapeQuotes(val));
+          source.append(cmEscapeQuotes(val));
         } else {
           source.append(val);
         }
@@ -2823,7 +2831,7 @@
           // Get the string we're meant to append to.
           if (value) {
             if (escapeQuotes) {
-              varresult = cmSystemTools::EscapeQuotes(value);
+              varresult = cmEscapeQuotes(value);
             } else {
               varresult = value;
             }
@@ -2949,7 +2957,7 @@
             }
 
             if (escapeQuotes) {
-              varresult = cmSystemTools::EscapeQuotes(varresult);
+              varresult = cmEscapeQuotes(varresult);
             }
             // Skip over the variable.
             result.append(last, in - last);
@@ -3061,7 +3069,7 @@
     return false;
   }
 
-  return this->FunctionBlockers.top()->IsFunctionBlocked(lff, *this, status);
+  return this->FunctionBlockers.top()->IsFunctionBlocked(lff, status);
 }
 
 void cmMakefile::PushFunctionBlockerBarrier()
@@ -3211,30 +3219,12 @@
   this->FunctionBlockers.push(std::move(fb));
 }
 
-std::unique_ptr<cmFunctionBlocker> cmMakefile::RemoveFunctionBlocker(
-  cmFunctionBlocker* fb, const cmListFileFunction& lff)
+std::unique_ptr<cmFunctionBlocker> cmMakefile::RemoveFunctionBlocker()
 {
   assert(!this->FunctionBlockers.empty());
-  assert(this->FunctionBlockers.top().get() == fb);
   assert(this->FunctionBlockerBarriers.empty() ||
          this->FunctionBlockers.size() > this->FunctionBlockerBarriers.back());
 
-  // Warn if the arguments do not match, but always remove.
-  if (!fb->ShouldRemove(lff, *this)) {
-    cmListFileContext const& lfc = fb->GetStartingContext();
-    cmListFileContext closingContext =
-      cmListFileContext::FromCommandContext(lff, lfc.FilePath);
-    std::ostringstream e;
-    /* clang-format off */
-    e << "A logical block opening on the line\n"
-      << "  " << lfc << "\n"
-      << "closes on the line\n"
-      << "  " << closingContext << "\n"
-      << "with mis-matching arguments.";
-    /* clang-format on */
-    this->IssueMessage(MessageType::AUTHOR_WARNING, e.str());
-  }
-
   auto b = std::move(this->FunctionBlockers.top());
   this->FunctionBlockers.pop();
   return b;
diff --git a/Source/cmMakefile.h b/Source/cmMakefile.h
index dc196ac..4d61c05 100644
--- a/Source/cmMakefile.h
+++ b/Source/cmMakefile.h
@@ -107,8 +107,7 @@
    * Remove the function blocker whose scope ends with the given command.
    * This returns ownership of the function blocker object.
    */
-  std::unique_ptr<cmFunctionBlocker> RemoveFunctionBlocker(
-    cmFunctionBlocker* fb, const cmListFileFunction& lff);
+  std::unique_ptr<cmFunctionBlocker> RemoveFunctionBlocker();
 
   /**
    * Try running cmake and building a file. This is used for dynalically
@@ -628,6 +627,11 @@
   void PrintCommandTrace(const cmListFileFunction& lff) const;
 
   /**
+   * Set a callback that is invoked whenever ExecuteCommand is called.
+   */
+  void OnExecuteCommand(std::function<void()> callback);
+
+  /**
    * Execute a single CMake command.  Returns true if the command
    * succeeded or false if it failed.
    */
@@ -964,6 +968,7 @@
   bool EnforceUniqueDir(const std::string& srcPath,
                         const std::string& binPath) const;
 
+  std::function<void()> ExecuteCommandCallback;
   using FunctionBlockerPtr = std::unique_ptr<cmFunctionBlocker>;
   using FunctionBlockersType =
     std::stack<FunctionBlockerPtr, std::vector<FunctionBlockerPtr>>;
diff --git a/Source/cmSourceGroupCommand.cxx b/Source/cmSourceGroupCommand.cxx
index ffdd0ce..0f69c84 100644
--- a/Source/cmSourceGroupCommand.cxx
+++ b/Source/cmSourceGroupCommand.cxx
@@ -21,7 +21,7 @@
 
 std::vector<std::string> tokenizePath(const std::string& path)
 {
-  return cmSystemTools::tokenize(path, "\\/");
+  return cmTokenize(path, "\\/");
 }
 
 std::string getFullFilePath(const std::string& currentPath,
diff --git a/Source/cmStringAlgorithms.cxx b/Source/cmStringAlgorithms.cxx
index 5deb9b0..5867a44 100644
--- a/Source/cmStringAlgorithms.cxx
+++ b/Source/cmStringAlgorithms.cxx
@@ -5,6 +5,59 @@
 #include <algorithm>
 #include <cstdio>
 
+std::string cmTrimWhitespace(cm::string_view str)
+{
+  auto start = str.begin();
+  while (start != str.end() && cmIsSpace(*start)) {
+    ++start;
+  }
+  if (start == str.end()) {
+    return std::string();
+  }
+  auto stop = str.end() - 1;
+  while (cmIsSpace(*stop)) {
+    --stop;
+  }
+  return std::string(start, stop + 1);
+}
+
+std::string cmEscapeQuotes(cm::string_view str)
+{
+  std::string result;
+  result.reserve(str.size());
+  for (const char ch : str) {
+    if (ch == '"') {
+      result += '\\';
+    }
+    result += ch;
+  }
+  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 cdb494f..1898649 100644
--- a/Source/cmStringAlgorithms.h
+++ b/Source/cmStringAlgorithms.h
@@ -7,6 +7,7 @@
 
 #include "cmRange.h"
 #include "cm_string_view.hxx"
+#include <cctype>
 #include <initializer_list>
 #include <sstream>
 #include <string.h>
@@ -31,6 +32,18 @@
   std::string const Test_;
 };
 
+/** Returns true if the character @a ch is a whitespace character.  **/
+inline bool cmIsSpace(char ch)
+{
+  return ((ch & 0x80) == 0) && std::isspace(ch);
+}
+
+/** Returns a string that has whitespace removed from the start and the end. */
+std::string cmTrimWhitespace(cm::string_view str);
+
+/** Escape quotes in a string.  */
+std::string cmEscapeQuotes(cm::string_view str);
+
 /** Joins elements of a range with separator into a single string.  */
 template <typename Range>
 std::string cmJoin(Range const& rng, cm::string_view separator)
@@ -49,6 +62,9 @@
   return os.str();
 }
 
+/** 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);
+
 /** Concatenate string pieces into a single string.  */
 std::string cmCatViews(std::initializer_list<cm::string_view> views);
 
diff --git a/Source/cmSystemTools.cxx b/Source/cmSystemTools.cxx
index 5f4e1fc..aed787e 100644
--- a/Source/cmSystemTools.cxx
+++ b/Source/cmSystemTools.cxx
@@ -84,11 +84,6 @@
 
 } // namespace
 
-static bool cm_isspace(char c)
-{
-  return ((c & 0x80) == 0) && isspace(c);
-}
-
 #if !defined(HAVE_ENVIRON_NOT_REQUIRE_PROTOTYPE)
 // For GetEnvironmentVariables
 #  if defined(_WIN32)
@@ -177,19 +172,6 @@
 }
 #endif
 
-std::string cmSystemTools::EscapeQuotes(cm::string_view str)
-{
-  std::string result;
-  result.reserve(str.size());
-  for (const char ch : str) {
-    if (ch == '"') {
-      result += '\\';
-    }
-    result += ch;
-  }
-  return result;
-}
-
 std::string cmSystemTools::HelpFileName(cm::string_view str)
 {
   std::string name(str);
@@ -198,22 +180,6 @@
   return name;
 }
 
-std::string cmSystemTools::TrimWhitespace(cm::string_view str)
-{
-  auto start = str.begin();
-  while (start != str.end() && cm_isspace(*start)) {
-    ++start;
-  }
-  if (start == str.end()) {
-    return std::string();
-  }
-  auto stop = str.end() - 1;
-  while (cm_isspace(*stop)) {
-    --stop;
-  }
-  return std::string(start, stop + 1);
-}
-
 void cmSystemTools::Error(const std::string& m)
 {
   std::string message = "CMake Error: " + m;
@@ -396,7 +362,7 @@
     } else {
       arg.append(backslashes, '\\');
       backslashes = 0;
-      if (cm_isspace(*c)) {
+      if (cmIsSpace(*c)) {
         if (in_quotes) {
           arg.append(1, *c);
         } else if (in_argument) {
@@ -2882,31 +2848,6 @@
   return false;
 }
 
-std::vector<std::string> cmSystemTools::tokenize(const std::string& str,
-                                                 const std::string& sep)
-{
-  std::vector<std::string> tokens;
-  std::string::size_type tokend = 0;
-
-  do {
-    std::string::size_type tokstart = str.find_first_not_of(sep, tokend);
-    if (tokstart == std::string::npos) {
-      break; // no more tokens
-    }
-    tokend = str.find_first_of(sep, tokstart);
-    if (tokend == std::string::npos) {
-      tokens.push_back(str.substr(tokstart));
-    } else {
-      tokens.push_back(str.substr(tokstart, tokend - tokstart));
-    }
-  } while (tokend != std::string::npos);
-
-  if (tokens.empty()) {
-    tokens.emplace_back();
-  }
-  return tokens;
-}
-
 bool cmSystemTools::StringToLong(const char* str, long* value)
 {
   errno = 0;
diff --git a/Source/cmSystemTools.h b/Source/cmSystemTools.h
index ac1aa80..d3fcb64 100644
--- a/Source/cmSystemTools.h
+++ b/Source/cmSystemTools.h
@@ -77,17 +77,9 @@
   static void ExpandRegistryValues(std::string& source,
                                    KeyWOW64 view = KeyWOW64_Default);
 
-  //! Escape quotes in a string.
-  static std::string EscapeQuotes(cm::string_view str);
-
   /** Map help document name to file name.  */
   static std::string HelpFileName(cm::string_view);
 
-  /**
-   * Returns a string that has whitespace removed from the start and the end.
-   */
-  static std::string TrimWhitespace(cm::string_view str);
-
   using MessageCallback = std::function<void(const std::string&, const char*)>;
   /**
    *  Set the function used by GUIs to display error messages
@@ -508,10 +500,6 @@
   /** Remove a directory; repeat a few times in case of locked files.  */
   static bool RepeatedRemoveDirectory(const std::string& dir);
 
-  /** Tokenize a string */
-  static std::vector<std::string> tokenize(const std::string& str,
-                                           const std::string& sep);
-
   /** Convert string to long. Expected that the whole string is an integer */
   static bool StringToLong(const char* str, long* value);
   static bool StringToULong(const char* str, unsigned long* value);
diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx
index beccfce..7ca2391 100644
--- a/Source/cmTarget.cxx
+++ b/Source/cmTarget.cxx
@@ -180,6 +180,7 @@
   std::vector<cmCustomCommand> PreBuildCommands;
   std::vector<cmCustomCommand> PreLinkCommands;
   std::vector<cmCustomCommand> PostBuildCommands;
+  std::vector<cmInstallTargetGenerator*> InstallGenerators;
   std::set<std::string> SystemIncludeDirectories;
   cmTarget::LinkLibraryVectorType OriginalLinkLibraries;
   std::vector<std::string> IncludeDirectoriesEntries;
@@ -873,6 +874,17 @@
   impl->HaveInstallRule = hir;
 }
 
+void cmTarget::AddInstallGenerator(cmInstallTargetGenerator* g)
+{
+  impl->InstallGenerators.emplace_back(g);
+}
+
+std::vector<cmInstallTargetGenerator*> const& cmTarget::GetInstallGenerators()
+  const
+{
+  return impl->InstallGenerators;
+}
+
 bool cmTarget::GetIsGeneratorProvided() const
 {
   return impl->IsGeneratorProvided;
diff --git a/Source/cmTarget.h b/Source/cmTarget.h
index 4e5141b..2b75879 100644
--- a/Source/cmTarget.h
+++ b/Source/cmTarget.h
@@ -21,6 +21,7 @@
 
 class cmCustomCommand;
 class cmGlobalGenerator;
+class cmInstallTargetGenerator;
 class cmMakefile;
 class cmMessenger;
 class cmPropertyMap;
@@ -147,6 +148,9 @@
   bool GetHaveInstallRule() const;
   void SetHaveInstallRule(bool hir);
 
+  void AddInstallGenerator(cmInstallTargetGenerator* g);
+  std::vector<cmInstallTargetGenerator*> const& GetInstallGenerators() const;
+
   /**
    * Get/Set whether this target was auto-created by a generator.
    */
diff --git a/Source/cmVSSetupHelper.cxx b/Source/cmVSSetupHelper.cxx
index c78361e..20f5e2f 100644
--- a/Source/cmVSSetupHelper.cxx
+++ b/Source/cmVSSetupHelper.cxx
@@ -2,6 +2,7 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmVSSetupHelper.h"
 
+#include "cmStringAlgorithms.h"
 #include "cmSystemTools.h"
 #include "cmsys/Encoding.hxx"
 #include "cmsys/FStream.hxx"
@@ -195,7 +196,7 @@
     if (!fin || !cmSystemTools::GetLineFromStream(fin, vcToolsVersion)) {
       return false;
     }
-    vcToolsVersion = cmSystemTools::TrimWhitespace(vcToolsVersion);
+    vcToolsVersion = cmTrimWhitespace(vcToolsVersion);
     std::string const vcToolsDir = vcRoot + "/VC/Tools/MSVC/" + vcToolsVersion;
     if (!cmSystemTools::FileIsDirectory(vcToolsDir)) {
       return false;
diff --git a/Source/cmVisualStudioGeneratorOptions.cxx b/Source/cmVisualStudioGeneratorOptions.cxx
index e1b0c70..6c28996 100644
--- a/Source/cmVisualStudioGeneratorOptions.cxx
+++ b/Source/cmVisualStudioGeneratorOptions.cxx
@@ -193,7 +193,7 @@
     std::string arch_name = arch[0];
     std::vector<std::string> codes;
     if (!code.empty()) {
-      codes = cmSystemTools::tokenize(code[0], ",");
+      codes = cmTokenize(code[0], ",");
     }
     if (codes.empty()) {
       codes.push_back(arch_name);
@@ -220,7 +220,7 @@
     cmSystemTools::ReplaceString(entry, "]", "");
     cmSystemTools::ReplaceString(entry, "\"", "");
 
-    std::vector<std::string> codes = cmSystemTools::tokenize(entry, ",");
+    std::vector<std::string> codes = cmTokenize(entry, ",");
     if (codes.size() >= 2) {
       auto gencode_arch = cm::cbegin(codes);
       for (auto ci = gencode_arch + 1; ci != cm::cend(codes); ++ci) {
diff --git a/Source/cmVisualStudioSlnParser.cxx b/Source/cmVisualStudioSlnParser.cxx
index 3e7e142..9eaee11 100644
--- a/Source/cmVisualStudioSlnParser.cxx
+++ b/Source/cmVisualStudioSlnParser.cxx
@@ -463,7 +463,7 @@
   if (!this->ParseBOM(input, line, state))
     return false;
   do {
-    line = cmSystemTools::TrimWhitespace(line);
+    line = cmTrimWhitespace(line);
     if (line.empty())
       continue;
     ParsedLine parsedLine;
@@ -579,9 +579,9 @@
     return true;
   }
   const std::string& key = line.substr(0, idxEqualSign);
-  parsedLine.SetTag(cmSystemTools::TrimWhitespace(key));
+  parsedLine.SetTag(cmTrimWhitespace(key));
   const std::string& value = line.substr(idxEqualSign + 1);
-  parsedLine.AddValue(cmSystemTools::TrimWhitespace(value));
+  parsedLine.AddValue(cmTrimWhitespace(value));
   return true;
 }
 
@@ -590,18 +590,17 @@
 {
   size_t idxLeftParen = fullTag.find('(');
   if (idxLeftParen == fullTag.npos) {
-    parsedLine.SetTag(cmSystemTools::TrimWhitespace(fullTag));
+    parsedLine.SetTag(cmTrimWhitespace(fullTag));
     return true;
   }
-  parsedLine.SetTag(
-    cmSystemTools::TrimWhitespace(fullTag.substr(0, idxLeftParen)));
+  parsedLine.SetTag(cmTrimWhitespace(fullTag.substr(0, idxLeftParen)));
   size_t idxRightParen = fullTag.rfind(')');
   if (idxRightParen == fullTag.npos) {
     this->LastResult.SetError(ResultErrorInputStructure,
                               state.GetCurrentLine());
     return false;
   }
-  const std::string& arg = cmSystemTools::TrimWhitespace(
+  const std::string& arg = cmTrimWhitespace(
     fullTag.substr(idxLeftParen + 1, idxRightParen - idxLeftParen - 1));
   if (arg.front() == '"') {
     if (arg.back() != '"') {
@@ -618,7 +617,7 @@
 bool cmVisualStudioSlnParser::ParseValue(const std::string& value,
                                          ParsedLine& parsedLine)
 {
-  const std::string& trimmed = cmSystemTools::TrimWhitespace(value);
+  const std::string& trimmed = cmTrimWhitespace(value);
   if (trimmed.empty())
     parsedLine.AddValue(trimmed);
   else if (trimmed.front() == '"' && trimmed.back() == '"')
diff --git a/Source/cmWhileCommand.cxx b/Source/cmWhileCommand.cxx
index 37d1c74..a396852 100644
--- a/Source/cmWhileCommand.cxx
+++ b/Source/cmWhileCommand.cxx
@@ -3,10 +3,14 @@
 #include "cmWhileCommand.h"
 
 #include "cm_memory.hxx"
+#include "cm_static_string_view.hxx"
+#include "cm_string_view.hxx"
 
 #include "cmConditionEvaluator.h"
 #include "cmExecutionStatus.h"
 #include "cmExpandedCommandArgument.h"
+#include "cmFunctionBlocker.h"
+#include "cmListFileCache.h"
 #include "cmMakefile.h"
 #include "cmMessageType.h"
 #include "cmSystemTools.h"
@@ -14,9 +18,29 @@
 #include <string>
 #include <utility>
 
+class cmWhileFunctionBlocker : public cmFunctionBlocker
+{
+public:
+  cmWhileFunctionBlocker(cmMakefile* mf);
+  ~cmWhileFunctionBlocker() override;
+
+  cm::string_view StartCommandName() const override { return "while"_s; }
+  cm::string_view EndCommandName() const override { return "endwhile"_s; }
+
+  bool ArgumentsMatch(cmListFileFunction const& lff,
+                      cmMakefile& mf) const override;
+
+  bool Replay(std::vector<cmListFileFunction> functions,
+              cmExecutionStatus& inStatus) override;
+
+  std::vector<cmListFileArgument> Args;
+
+private:
+  cmMakefile* Makefile;
+};
+
 cmWhileFunctionBlocker::cmWhileFunctionBlocker(cmMakefile* mf)
   : Makefile(mf)
-  , Depth(0)
 {
   this->Makefile->PushLoopBlock();
 }
@@ -26,108 +50,77 @@
   this->Makefile->PopLoopBlock();
 }
 
-bool cmWhileFunctionBlocker::IsFunctionBlocked(const cmListFileFunction& lff,
-                                               cmMakefile& mf,
-                                               cmExecutionStatus& inStatus)
+bool cmWhileFunctionBlocker::ArgumentsMatch(cmListFileFunction const& lff,
+                                            cmMakefile&) const
 {
-  // at end of for each execute recorded commands
-  if (lff.Name.Lower == "while") {
-    // record the number of while commands past this one
-    this->Depth++;
-  } else if (lff.Name.Lower == "endwhile") {
-    // if this is the endwhile for this while loop then execute
-    if (!this->Depth) {
-      // Remove the function blocker for this scope or bail.
-      std::unique_ptr<cmFunctionBlocker> fb(
-        mf.RemoveFunctionBlocker(this, lff));
-      if (!fb) {
-        return false;
-      }
-
-      std::string errorString;
-
-      std::vector<cmExpandedCommandArgument> expandedArguments;
-      mf.ExpandArguments(this->Args, expandedArguments);
-      MessageType messageType;
-
-      cmListFileContext execContext = this->GetStartingContext();
-
-      cmCommandContext commandContext;
-      commandContext.Line = execContext.Line;
-      commandContext.Name = execContext.Name;
-
-      cmConditionEvaluator conditionEvaluator(mf, this->GetStartingContext(),
-                                              mf.GetBacktrace(commandContext));
-
-      bool isTrue =
-        conditionEvaluator.IsTrue(expandedArguments, errorString, messageType);
-
-      while (isTrue) {
-        if (!errorString.empty()) {
-          std::string err = "had incorrect arguments: ";
-          for (cmListFileArgument const& arg : this->Args) {
-            err += (arg.Delim ? "\"" : "");
-            err += arg.Value;
-            err += (arg.Delim ? "\"" : "");
-            err += " ";
-          }
-          err += "(";
-          err += errorString;
-          err += ").";
-          mf.IssueMessage(messageType, err);
-          if (messageType == MessageType::FATAL_ERROR) {
-            cmSystemTools::SetFatalErrorOccured();
-            return true;
-          }
-        }
-
-        // Invoke all the functions that were collected in the block.
-        for (cmListFileFunction const& fn : this->Functions) {
-          cmExecutionStatus status(mf);
-          mf.ExecuteCommand(fn, status);
-          if (status.GetReturnInvoked()) {
-            inStatus.SetReturnInvoked();
-            return true;
-          }
-          if (status.GetBreakInvoked()) {
-            return true;
-          }
-          if (status.GetContinueInvoked()) {
-            break;
-          }
-          if (cmSystemTools::GetFatalErrorOccured()) {
-            return true;
-          }
-        }
-        expandedArguments.clear();
-        mf.ExpandArguments(this->Args, expandedArguments);
-        isTrue = conditionEvaluator.IsTrue(expandedArguments, errorString,
-                                           messageType);
-      }
-      return true;
-    }
-    // decrement for each nested while that ends
-    this->Depth--;
-  }
-
-  // record the command
-  this->Functions.push_back(lff);
-
-  // always return true
-  return true;
+  return lff.Arguments.empty() || lff.Arguments == this->Args;
 }
 
-bool cmWhileFunctionBlocker::ShouldRemove(const cmListFileFunction& lff,
-                                          cmMakefile&)
+bool cmWhileFunctionBlocker::Replay(std::vector<cmListFileFunction> functions,
+                                    cmExecutionStatus& inStatus)
 {
-  if (lff.Name.Lower == "endwhile") {
-    // if the endwhile has arguments, then make sure
-    // they match the arguments of the matching while
-    if (lff.Arguments.empty() || lff.Arguments == this->Args) {
-      return true;
+  cmMakefile& mf = inStatus.GetMakefile();
+  std::string errorString;
+
+  std::vector<cmExpandedCommandArgument> expandedArguments;
+  mf.ExpandArguments(this->Args, expandedArguments);
+  MessageType messageType;
+
+  cmListFileContext execContext = this->GetStartingContext();
+
+  cmCommandContext commandContext;
+  commandContext.Line = execContext.Line;
+  commandContext.Name = execContext.Name;
+
+  cmConditionEvaluator conditionEvaluator(mf, this->GetStartingContext(),
+                                          mf.GetBacktrace(commandContext));
+
+  bool isTrue =
+    conditionEvaluator.IsTrue(expandedArguments, errorString, messageType);
+
+  while (isTrue) {
+    if (!errorString.empty()) {
+      std::string err = "had incorrect arguments: ";
+      for (cmListFileArgument const& arg : this->Args) {
+        err += (arg.Delim ? "\"" : "");
+        err += arg.Value;
+        err += (arg.Delim ? "\"" : "");
+        err += " ";
+      }
+      err += "(";
+      err += errorString;
+      err += ").";
+      mf.IssueMessage(messageType, err);
+      if (messageType == MessageType::FATAL_ERROR) {
+        cmSystemTools::SetFatalErrorOccured();
+        return true;
+      }
     }
+
+    // Invoke all the functions that were collected in the block.
+    for (cmListFileFunction const& fn : functions) {
+      cmExecutionStatus status(mf);
+      mf.ExecuteCommand(fn, status);
+      if (status.GetReturnInvoked()) {
+        inStatus.SetReturnInvoked();
+        return true;
+      }
+      if (status.GetBreakInvoked()) {
+        return true;
+      }
+      if (status.GetContinueInvoked()) {
+        break;
+      }
+      if (cmSystemTools::GetFatalErrorOccured()) {
+        return true;
+      }
+    }
+    expandedArguments.clear();
+    mf.ExpandArguments(this->Args, expandedArguments);
+    isTrue =
+      conditionEvaluator.IsTrue(expandedArguments, errorString, messageType);
   }
-  return false;
+  return true;
 }
 
 bool cmWhileCommand(std::vector<cmListFileArgument> const& args,
diff --git a/Source/cmWhileCommand.h b/Source/cmWhileCommand.h
index 2257799..beca652 100644
--- a/Source/cmWhileCommand.h
+++ b/Source/cmWhileCommand.h
@@ -7,28 +7,8 @@
 
 #include <vector>
 
-#include "cmFunctionBlocker.h"
-#include "cmListFileCache.h"
-
 class cmExecutionStatus;
-class cmMakefile;
-
-class cmWhileFunctionBlocker : public cmFunctionBlocker
-{
-public:
-  cmWhileFunctionBlocker(cmMakefile* mf);
-  ~cmWhileFunctionBlocker() override;
-  bool IsFunctionBlocked(const cmListFileFunction& lff, cmMakefile& mf,
-                         cmExecutionStatus&) override;
-  bool ShouldRemove(const cmListFileFunction& lff, cmMakefile& mf) override;
-
-  std::vector<cmListFileArgument> Args;
-  std::vector<cmListFileFunction> Functions;
-
-private:
-  cmMakefile* Makefile;
-  int Depth;
-};
+struct cmListFileArgument;
 
 /// \brief Starts a while loop
 bool cmWhileCommand(std::vector<cmListFileArgument> const& args,
diff --git a/Source/cmake.cxx b/Source/cmake.cxx
index 309efd3..a81b7e4 100644
--- a/Source/cmake.cxx
+++ b/Source/cmake.cxx
@@ -28,6 +28,7 @@
 #include "cmUtils.hxx"
 #include "cmVersionConfig.h"
 #include "cmWorkingDirectory.h"
+#include "cm_string_view.hxx"
 #include "cm_sys_stat.h"
 
 #if defined(CMAKE_BUILD_WITH_CMAKE)
@@ -132,22 +133,15 @@
 }
 
 cmake::cmake(Role role, cmState::Mode mode)
+  : FileTimeCache(cm::make_unique<cmFileTimeCache>())
+#ifdef CMAKE_BUILD_WITH_CMAKE
+  , VariableWatch(cm::make_unique<cmVariableWatch>())
+#endif
+  , State(cm::make_unique<cmState>())
+  , Messenger(cm::make_unique<cmMessenger>())
 {
-  this->Trace = false;
-  this->TraceExpand = false;
-  this->WarnUninitialized = false;
-  this->WarnUnused = false;
-  this->WarnUnusedCli = true;
-  this->CheckSystemVars = false;
-  this->DebugOutput = false;
-  this->DebugTryCompile = false;
-  this->ClearBuildSystem = false;
-  this->FileTimeCache = cm::make_unique<cmFileTimeCache>();
-
-  this->State = cm::make_unique<cmState>();
   this->State->SetMode(mode);
   this->CurrentSnapshot = this->State->CreateBaseSnapshot();
-  this->Messenger = cm::make_unique<cmMessenger>();
 
 #ifdef __APPLE__
   struct rlimit rlp;
@@ -159,16 +153,6 @@
   }
 #endif
 
-  this->GlobalGenerator = nullptr;
-  this->GeneratorInstanceSet = false;
-  this->GeneratorPlatformSet = false;
-  this->GeneratorToolsetSet = false;
-  this->CurrentWorkingMode = NORMAL_MODE;
-
-#ifdef CMAKE_BUILD_WITH_CMAKE
-  this->VariableWatch = cm::make_unique<cmVariableWatch>();
-#endif
-
   this->AddDefaultGenerators();
   this->AddDefaultExtraGenerators();
   if (role == RoleScript || role == RoleProject) {
@@ -188,32 +172,25 @@
   // Set up a list of source and header extensions.
   // These are used to find files when the extension is not given.
   {
-    auto fillExts = [](FileExtensions& exts,
-                       std::initializer_list<const char*> extList) {
+    auto setupExts = [](FileExtensions& exts,
+                        std::initializer_list<cm::string_view> extList) {
       // Fill ordered vector
       exts.ordered.reserve(extList.size());
-      for (const char* ext : extList) {
+      for (cm::string_view ext : extList) {
         exts.ordered.emplace_back(ext);
       };
       // Fill unordered set
       exts.unordered.insert(exts.ordered.begin(), exts.ordered.end());
     };
 
-    // Source extensions
     // The "c" extension MUST precede the "C" extension.
-    fillExts(this->SourceFileExtensions,
-             { "c", "C", "c++", "cc", "cpp", "cxx", "cu", "m", "M", "mm" });
-
-    // Header extensions
-    fillExts(this->HeaderFileExtensions,
-             { "h", "hh", "h++", "hm", "hpp", "hxx", "in", "txx" });
-
-    // Cuda extensions
-    fillExts(this->CudaFileExtensions, { "cu" });
-
-    // Fortran extensions
-    fillExts(this->FortranFileExtensions,
-             { "f", "F", "for", "f77", "f90", "f95", "f03" });
+    setupExts(this->SourceFileExtensions,
+              { "c", "C", "c++", "cc", "cpp", "cxx", "cu", "m", "M", "mm" });
+    setupExts(this->HeaderFileExtensions,
+              { "h", "hh", "h++", "hm", "hpp", "hxx", "in", "txx" });
+    setupExts(this->CudaFileExtensions, { "cu" });
+    setupExts(this->FortranFileExtensions,
+              { "f", "F", "for", "f77", "f90", "f95", "f03" });
   }
 }
 
diff --git a/Source/cmake.h b/Source/cmake.h
index 6aa00e1..92494ae 100644
--- a/Source/cmake.h
+++ b/Source/cmake.h
@@ -509,14 +509,14 @@
   void AddDefaultGenerators();
   void AddDefaultExtraGenerators();
 
-  cmGlobalGenerator* GlobalGenerator;
+  cmGlobalGenerator* GlobalGenerator = nullptr;
   std::map<std::string, DiagLevel> DiagLevels;
   std::string GeneratorInstance;
   std::string GeneratorPlatform;
   std::string GeneratorToolset;
-  bool GeneratorInstanceSet;
-  bool GeneratorPlatformSet;
-  bool GeneratorToolsetSet;
+  bool GeneratorInstanceSet = false;
+  bool GeneratorPlatformSet = false;
+  bool GeneratorToolsetSet = false;
 
   //! read in a cmake list file to initialize the cache
   void ReadListFile(const std::vector<std::string>& args,
@@ -543,14 +543,14 @@
 
 private:
   ProgressCallbackType ProgressCallback;
-  WorkingMode CurrentWorkingMode;
-  bool DebugOutput;
-  bool Trace;
-  bool TraceExpand;
-  bool WarnUninitialized;
-  bool WarnUnused;
-  bool WarnUnusedCli;
-  bool CheckSystemVars;
+  WorkingMode CurrentWorkingMode = NORMAL_MODE;
+  bool DebugOutput = false;
+  bool Trace = false;
+  bool TraceExpand = false;
+  bool WarnUninitialized = false;
+  bool WarnUnused = false;
+  bool WarnUnusedCli = true;
+  bool CheckSystemVars = false;
   std::map<std::string, bool> UsedCliVariables;
   std::string CMakeEditCommand;
   std::string CXXEnvironment;
@@ -564,8 +564,8 @@
   FileExtensions HeaderFileExtensions;
   FileExtensions CudaFileExtensions;
   FileExtensions FortranFileExtensions;
-  bool ClearBuildSystem;
-  bool DebugTryCompile;
+  bool ClearBuildSystem = false;
+  bool DebugTryCompile = false;
   std::unique_ptr<cmFileTimeCache> FileTimeCache;
   std::string GraphVizFile;
   InstalledFilesMap InstalledFiles;
diff --git a/Tests/CMakeLib/testStringAlgorithms.cxx b/Tests/CMakeLib/testStringAlgorithms.cxx
index 95616ff..55d2a8f 100644
--- a/Tests/CMakeLib/testStringAlgorithms.cxx
+++ b/Tests/CMakeLib/testStringAlgorithms.cxx
@@ -38,6 +38,28 @@
   };
 
   // ----------------------------------------------------------------------
+  // Test cmTrimWhitespace
+  {
+    std::string base = "base";
+    std::string spaces = "  \f\f\n\n\r\r\t\t\v\v";
+    assert_string(cmTrimWhitespace(spaces + base), base,
+                  "cmTrimWhitespace front");
+    assert_string(cmTrimWhitespace(base + spaces), base,
+                  "cmTrimWhitespace back");
+    assert_string(cmTrimWhitespace(spaces + base + spaces), base,
+                  "cmTrimWhitespace front and back");
+  }
+
+  // ----------------------------------------------------------------------
+  // Test cmEscapeQuotes
+  {
+    assert_string(cmEscapeQuotes("plain"), "plain", "cmEscapeQuotes plain");
+    std::string base = "\"base\"\"";
+    std::string result = "\\\"base\\\"\\\"";
+    assert_string(cmEscapeQuotes(base), result, "cmEscapeQuotes escaped");
+  }
+
+  // ----------------------------------------------------------------------
   // Test cmJoin
   {
     typedef std::string ST;
@@ -52,6 +74,21 @@
   }
 
   // ----------------------------------------------------------------------
+  // Test cmTokenize
+  {
+    typedef std::vector<std::string> VT;
+    assert_ok(cmTokenize("", ";") == VT{ "" }, "cmTokenize empty");
+    assert_ok(cmTokenize(";", ";") == VT{ "" }, "cmTokenize sep");
+    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");
+    assert_ok(cmTokenize("abc;;efg", ";") == VT{ "abc", "efg" },
+              "cmTokenize item sep sep item");
+    assert_ok(cmTokenize("a1;a2;a3;a4", ";") == VT{ "a1", "a2", "a3", "a4" },
+              "cmTokenize multiple items");
+  }
+
+  // ----------------------------------------------------------------------
   // Test cmStrCat
   {
     int ni = -1100;
diff --git a/Tests/FindPackageTest/CMakeLists.txt b/Tests/FindPackageTest/CMakeLists.txt
index 519608c..7217f43 100644
--- a/Tests/FindPackageTest/CMakeLists.txt
+++ b/Tests/FindPackageTest/CMakeLists.txt
@@ -653,3 +653,9 @@
 if(ACME_FOUND)
     message(SEND_ERROR "Should not find ACME package")
 endif()
+
+############################################################################
+##Test find_package CMAKE_FIND_PACKAGE_PREFER_CONFIG with unknown package
+
+set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON)
+find_package(DoesNotExist)
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2-check.py b/Tests/RunCMake/FileAPI/codemodel-v2-check.py
index 3b0ec6e..89f63d0 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2-check.py
+++ b/Tests/RunCMake/FileAPI/codemodel-v2-check.py
@@ -2092,7 +2092,40 @@
             ],
             "build": "^cxx$",
             "source": "^cxx$",
-            "install": None,
+            "install": {
+                "prefix": "^(/usr/local|[A-Za-z]:.*/codemodel-v2)$",
+                "destinations": [
+                    {
+                        "path": "bin",
+                        "backtrace": [
+                            {
+                                "file": "^codemodel-v2\\.cmake$",
+                                "line": 37,
+                                "command": "install",
+                                "hasParent": True,
+                            },
+                            {
+                                "file": "^codemodel-v2\\.cmake$",
+                                "line": None,
+                                "command": None,
+                                "hasParent": True,
+                            },
+                            {
+                                "file": "^CMakeLists\\.txt$",
+                                "line": 3,
+                                "command": "include",
+                                "hasParent": True,
+                            },
+                            {
+                                "file": "^CMakeLists\\.txt$",
+                                "line": None,
+                                "command": None,
+                                "hasParent": False,
+                            },
+                        ],
+                    },
+                ],
+            },
             "link": {
                 "language": "CXX",
                 "lto": None,
diff --git a/Tests/RunCMake/FileAPI/codemodel-v2.cmake b/Tests/RunCMake/FileAPI/codemodel-v2.cmake
index 72073d5..c98a84c 100644
--- a/Tests/RunCMake/FileAPI/codemodel-v2.cmake
+++ b/Tests/RunCMake/FileAPI/codemodel-v2.cmake
@@ -33,3 +33,5 @@
   set_property(TARGET c_static_lib PROPERTY INTERPROCEDURAL_OPTIMIZATION ON)
   file(WRITE "${CMAKE_BINARY_DIR}/ipo_enabled.txt" "")
 endif()
+
+install(TARGETS cxx_exe)
diff --git a/Tests/RuntimePath/CMakeLists.txt b/Tests/RuntimePath/CMakeLists.txt
index 6583a87..bb87440 100644
--- a/Tests/RuntimePath/CMakeLists.txt
+++ b/Tests/RuntimePath/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 2.6)
+cmake_minimum_required (VERSION 3.15)
 project(RuntimePath C)
 
 # Add a simple chain of shared libraries that must be found.
@@ -31,3 +31,14 @@
   set_property(TARGET bar2 PROPERTY LIBRARY_OUTPUT_DIRECTORY A)
   target_link_libraries(bar2 foo2)
 endif()
+
+# Add a library that is missing the rpath for its dependency.
+add_library(bar1_no_rpath SHARED bar1.c)
+set_property(TARGET bar1_no_rpath PROPERTY LIBRARY_OUTPUT_DIRECTORY B)
+set_property(TARGET bar1_no_rpath PROPERTY SKIP_BUILD_RPATH 1)
+target_link_libraries(bar1_no_rpath PRIVATE foo1)
+
+# Add an executable linking to the library with a missing dependency rpath.
+# CMake should generate the proper rpath-link flag to find it at build time.
+add_executable(main_with_bar1_no_rpath main.c)
+target_link_libraries(main_with_bar1_no_rpath bar1_no_rpath)
diff --git a/bootstrap b/bootstrap
index ac5adc8..06e11b5 100755
--- a/bootstrap
+++ b/bootstrap
@@ -326,6 +326,7 @@
   cmFindPathCommand \
   cmFindProgramCommand \
   cmForEachCommand \
+  cmFunctionBlocker \
   cmFunctionCommand \
   cmFSPermissions \
   cmGeneratedFileStream \