Merge topic 'cmFileCommand-refactoring'

a7a5f376bc cmFileCommand: Use cmSubcommandTable
b66b7464ab Introduce cmSubcommandTable
2b785875fb cmFileCommand: turn into free function
9703c65718 cmFileCommand: put subcommands in unnamed namespace
64f987c174 cmFileCommand: port to cmExecutionStatus

Acked-by: Kitware Robot <kwrobot@kitware.com>
Merge-request: !3657
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index b1f4ca5..ee82ff5 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -639,6 +639,8 @@
   cmStringReplaceHelper.cxx
   cmStringCommand.cxx
   cmStringCommand.h
+  cmSubcommandTable.cxx
+  cmSubcommandTable.h
   cmSubdirCommand.cxx
   cmSubdirCommand.h
   cmSubdirDependsCommand.cxx
diff --git a/Source/cmCommands.cxx b/Source/cmCommands.cxx
index f351ff8..a9be445 100644
--- a/Source/cmCommands.cxx
+++ b/Source/cmCommands.cxx
@@ -128,7 +128,7 @@
                            cm::make_unique<cmExecProgramCommand>());
   state->AddBuiltinCommand("execute_process",
                            cm::make_unique<cmExecuteProcessCommand>());
-  state->AddBuiltinCommand("file", cm::make_unique<cmFileCommand>());
+  state->AddBuiltinCommand("file", cmFileCommand);
   state->AddBuiltinCommand("find_file", cm::make_unique<cmFindFileCommand>());
   state->AddBuiltinCommand("find_library",
                            cm::make_unique<cmFindLibraryCommand>());
diff --git a/Source/cmFileCommand.cxx b/Source/cmFileCommand.cxx
index 22f0d1f..ec03191 100644
--- a/Source/cmFileCommand.cxx
+++ b/Source/cmFileCommand.cxx
@@ -25,6 +25,7 @@
 #include "cmAlgorithms.h"
 #include "cmArgumentParser.h"
 #include "cmCryptoHash.h"
+#include "cmExecutionStatus.h"
 #include "cmFileCopier.h"
 #include "cmFileInstaller.h"
 #include "cmFileLockPool.h"
@@ -40,6 +41,7 @@
 #include "cmRuntimeDependencyArchive.h"
 #include "cmState.h"
 #include "cmStringAlgorithms.h"
+#include "cmSubcommandTable.h"
 #include "cmSystemTools.h"
 #include "cmTimestamp.h"
 #include "cm_sys_stat.h"
@@ -59,10 +61,12 @@
 #  include <windows.h>
 #endif
 
+namespace {
+
 #if defined(_WIN32)
 // libcurl doesn't support file:// urls for unicode filenames on Windows.
 // Convert string from UTF-8 to ACP if this is a file:// URL.
-static std::string fix_file_url_windows(const std::string& url)
+std::string fix_file_url_windows(const std::string& url)
 {
   std::string ret = url;
   if (strncmp(url.c_str(), "file://", 7) == 0) {
@@ -84,123 +88,8 @@
 }
 #endif
 
-// cmLibraryCommand
-bool cmFileCommand::InitialPass(std::vector<std::string> const& args,
-                                cmExecutionStatus&)
-{
-  if (args.size() < 2) {
-    this->SetError("must be called with at least two arguments.");
-    return false;
-  }
-  std::string const& subCommand = args[0];
-  if (subCommand == "WRITE") {
-    return this->HandleWriteCommand(args, false);
-  }
-  if (subCommand == "APPEND") {
-    return this->HandleWriteCommand(args, true);
-  }
-  if (subCommand == "DOWNLOAD") {
-    return this->HandleDownloadCommand(args);
-  }
-  if (subCommand == "UPLOAD") {
-    return this->HandleUploadCommand(args);
-  }
-  if (subCommand == "READ") {
-    return this->HandleReadCommand(args);
-  }
-  if (subCommand == "MD5" || subCommand == "SHA1" || subCommand == "SHA224" ||
-      subCommand == "SHA256" || subCommand == "SHA384" ||
-      subCommand == "SHA512" || subCommand == "SHA3_224" ||
-      subCommand == "SHA3_256" || subCommand == "SHA3_384" ||
-      subCommand == "SHA3_512") {
-    return this->HandleHashCommand(args);
-  }
-  if (subCommand == "STRINGS") {
-    return this->HandleStringsCommand(args);
-  }
-  if (subCommand == "GLOB") {
-    return this->HandleGlobCommand(args, false);
-  }
-  if (subCommand == "GLOB_RECURSE") {
-    return this->HandleGlobCommand(args, true);
-  }
-  if (subCommand == "MAKE_DIRECTORY") {
-    return this->HandleMakeDirectoryCommand(args);
-  }
-  if (subCommand == "RENAME") {
-    return this->HandleRename(args);
-  }
-  if (subCommand == "REMOVE") {
-    return this->HandleRemove(args, false);
-  }
-  if (subCommand == "REMOVE_RECURSE") {
-    return this->HandleRemove(args, true);
-  }
-  if (subCommand == "COPY") {
-    return this->HandleCopyCommand(args);
-  }
-  if (subCommand == "INSTALL") {
-    return this->HandleInstallCommand(args);
-  }
-  if (subCommand == "DIFFERENT") {
-    return this->HandleDifferentCommand(args);
-  }
-  if (subCommand == "RPATH_CHANGE" || subCommand == "CHRPATH") {
-    return this->HandleRPathChangeCommand(args);
-  }
-  if (subCommand == "RPATH_CHECK") {
-    return this->HandleRPathCheckCommand(args);
-  }
-  if (subCommand == "RPATH_REMOVE") {
-    return this->HandleRPathRemoveCommand(args);
-  }
-  if (subCommand == "READ_ELF") {
-    return this->HandleReadElfCommand(args);
-  }
-  if (subCommand == "RELATIVE_PATH") {
-    return this->HandleRelativePathCommand(args);
-  }
-  if (subCommand == "TO_CMAKE_PATH") {
-    return this->HandleCMakePathCommand(args, false);
-  }
-  if (subCommand == "TO_NATIVE_PATH") {
-    return this->HandleCMakePathCommand(args, true);
-  }
-  if (subCommand == "TOUCH") {
-    return this->HandleTouchCommand(args, true);
-  }
-  if (subCommand == "TOUCH_NOCREATE") {
-    return this->HandleTouchCommand(args, false);
-  }
-  if (subCommand == "TIMESTAMP") {
-    return this->HandleTimestampCommand(args);
-  }
-  if (subCommand == "GENERATE") {
-    return this->HandleGenerateCommand(args);
-  }
-  if (subCommand == "LOCK") {
-    return this->HandleLockCommand(args);
-  }
-  if (subCommand == "SIZE") {
-    return this->HandleSizeCommand(args);
-  }
-  if (subCommand == "READ_SYMLINK") {
-    return this->HandleReadSymlinkCommand(args);
-  }
-  if (subCommand == "CREATE_LINK") {
-    return this->HandleCreateLinkCommand(args);
-  }
-  if (subCommand == "GET_RUNTIME_DEPENDENCIES") {
-    return this->HandleGetRuntimeDependenciesCommand(args);
-  }
-
-  std::string e = "does not recognize sub-command " + subCommand;
-  this->SetError(e);
-  return false;
-}
-
-bool cmFileCommand::HandleWriteCommand(std::vector<std::string> const& args,
-                                       bool append)
+bool HandleWriteImpl(std::vector<std::string> const& args, bool append,
+                     cmExecutionStatus& status)
 {
   std::vector<std::string>::const_iterator i = args.begin();
 
@@ -208,16 +97,16 @@
 
   std::string fileName = *i;
   if (!cmsys::SystemTools::FileIsFullPath(*i)) {
-    fileName = this->Makefile->GetCurrentSourceDirectory();
+    fileName = status.GetMakefile().GetCurrentSourceDirectory();
     fileName += "/" + *i;
   }
 
   i++;
 
-  if (!this->Makefile->CanIWriteThisFile(fileName)) {
+  if (!status.GetMakefile().CanIWriteThisFile(fileName)) {
     std::string e =
       "attempted to write a file: " + fileName + " into a source directory.";
-    this->SetError(e);
+    status.SetError(e);
     cmSystemTools::SetFatalErrorOccured();
     return false;
   }
@@ -249,7 +138,7 @@
     error += cmSystemTools::GetLastSystemError();
     error += "):\n  ";
     error += fileName;
-    this->SetError(error);
+    status.SetError(error);
     return false;
   }
   std::string message = cmJoin(cmMakeRange(i, args.end()), std::string());
@@ -259,7 +148,7 @@
     error += cmSystemTools::GetLastSystemError();
     error += "):\n  ";
     error += fileName;
-    this->SetError(error);
+    status.SetError(error);
     return false;
   }
   file.close();
@@ -269,11 +158,24 @@
   return true;
 }
 
-bool cmFileCommand::HandleReadCommand(std::vector<std::string> const& args)
+bool HandleWriteCommand(std::vector<std::string> const& args,
+                        cmExecutionStatus& status)
+{
+  return HandleWriteImpl(args, false, status);
+}
+
+bool HandleAppendCommand(std::vector<std::string> const& args,
+                         cmExecutionStatus& status)
+{
+  return HandleWriteImpl(args, true, status);
+}
+
+bool HandleReadCommand(std::vector<std::string> const& args,
+                       cmExecutionStatus& status)
 {
   if (args.size() < 3) {
-    this->SetError("READ must be called with at least two additional "
-                   "arguments");
+    status.SetError("READ must be called with at least two additional "
+                    "arguments");
     return false;
   }
 
@@ -296,7 +198,7 @@
 
   std::string fileName = fileNameArg;
   if (!cmsys::SystemTools::FileIsFullPath(fileName)) {
-    fileName = this->Makefile->GetCurrentSourceDirectory();
+    fileName = status.GetMakefile().GetCurrentSourceDirectory();
     fileName += "/" + fileNameArg;
   }
 
@@ -314,7 +216,7 @@
     error += cmSystemTools::GetLastSystemError();
     error += "):\n  ";
     error += fileName;
-    this->SetError(error);
+    status.SetError(error);
     return false;
   }
 
@@ -366,17 +268,18 @@
       }
     }
   }
-  this->Makefile->AddDefinition(variable, output);
+  status.GetMakefile().AddDefinition(variable, output);
   return true;
 }
 
-bool cmFileCommand::HandleHashCommand(std::vector<std::string> const& args)
+bool HandleHashCommand(std::vector<std::string> const& args,
+                       cmExecutionStatus& status)
 {
 #if defined(CMAKE_BUILD_WITH_CMAKE)
   if (args.size() != 3) {
     std::ostringstream e;
     e << args[0] << " requires a file name and output variable";
-    this->SetError(e.str());
+    status.SetError(e.str());
     return false;
   }
 
@@ -384,34 +287,35 @@
   if (hash) {
     std::string out = hash->HashFile(args[1]);
     if (!out.empty()) {
-      this->Makefile->AddDefinition(args[2], out);
+      status.GetMakefile().AddDefinition(args[2], out);
       return true;
     }
     std::ostringstream e;
     e << args[0] << " failed to read file \"" << args[1]
       << "\": " << cmSystemTools::GetLastSystemError();
-    this->SetError(e.str());
+    status.SetError(e.str());
   }
   return false;
 #else
   std::ostringstream e;
   e << args[0] << " not available during bootstrap";
-  this->SetError(e.str());
+  status.SetError(e.str());
   return false;
 #endif
 }
 
-bool cmFileCommand::HandleStringsCommand(std::vector<std::string> const& args)
+bool HandleStringsCommand(std::vector<std::string> const& args,
+                          cmExecutionStatus& status)
 {
   if (args.size() < 3) {
-    this->SetError("STRINGS requires a file name and output variable");
+    status.SetError("STRINGS requires a file name and output variable");
     return false;
   }
 
   // Get the file to read.
   std::string fileName = args[1];
   if (!cmsys::SystemTools::FileIsFullPath(fileName)) {
-    fileName = this->Makefile->GetCurrentSourceDirectory();
+    fileName = status.GetMakefile().GetCurrentSourceDirectory();
     fileName += "/" + args[1];
   }
 
@@ -478,7 +382,7 @@
         std::ostringstream e;
         e << "STRINGS option LIMIT_INPUT value \"" << args[i]
           << "\" is not an unsigned integer.";
-        this->SetError(e.str());
+        status.SetError(e.str());
         return false;
       }
       arg_mode = arg_none;
@@ -488,7 +392,7 @@
         std::ostringstream e;
         e << "STRINGS option LIMIT_OUTPUT value \"" << args[i]
           << "\" is not an unsigned integer.";
-        this->SetError(e.str());
+        status.SetError(e.str());
         return false;
       }
       arg_mode = arg_none;
@@ -498,7 +402,7 @@
         std::ostringstream e;
         e << "STRINGS option LIMIT_COUNT value \"" << args[i]
           << "\" is not an unsigned integer.";
-        this->SetError(e.str());
+        status.SetError(e.str());
         return false;
       }
       limit_count = count;
@@ -509,7 +413,7 @@
         std::ostringstream e;
         e << "STRINGS option LENGTH_MINIMUM value \"" << args[i]
           << "\" is not an unsigned integer.";
-        this->SetError(e.str());
+        status.SetError(e.str());
         return false;
       }
       minlen = len;
@@ -520,7 +424,7 @@
         std::ostringstream e;
         e << "STRINGS option LENGTH_MAXIMUM value \"" << args[i]
           << "\" is not an unsigned integer.";
-        this->SetError(e.str());
+        status.SetError(e.str());
         return false;
       }
       maxlen = len;
@@ -530,7 +434,7 @@
         std::ostringstream e;
         e << "STRINGS option REGEX value \"" << args[i]
           << "\" could not be compiled.";
-        this->SetError(e.str());
+        status.SetError(e.str());
         return false;
       }
       have_regex = true;
@@ -549,21 +453,22 @@
       } else {
         std::ostringstream e;
         e << "STRINGS option ENCODING \"" << args[i] << "\" not recognized.";
-        this->SetError(e.str());
+        status.SetError(e.str());
         return false;
       }
       arg_mode = arg_none;
     } else {
       std::ostringstream e;
       e << "STRINGS given unknown argument \"" << args[i] << "\"";
-      this->SetError(e.str());
+      status.SetError(e.str());
       return false;
     }
   }
 
   if (hex_conversion_enabled) {
     // TODO: should work without temp file, but just on a memory buffer
-    std::string binaryFileName = this->Makefile->GetCurrentBinaryDirectory();
+    std::string binaryFileName =
+      status.GetMakefile().GetCurrentBinaryDirectory();
     binaryFileName += "/CMakeFiles";
     binaryFileName += "/FileCommandStringsBinaryFile";
     if (cmHexFileConverter::TryConvert(fileName, binaryFileName)) {
@@ -580,7 +485,7 @@
   if (!fin) {
     std::ostringstream e;
     e << "STRINGS file \"" << fileName << "\" cannot be read.";
-    this->SetError(e.str());
+    status.SetError(e.str());
     return false;
   }
 
@@ -752,12 +657,12 @@
   }
 
   // Save the output in a makefile variable.
-  this->Makefile->AddDefinition(outVar, output);
+  status.GetMakefile().AddDefinition(outVar, output);
   return true;
 }
 
-bool cmFileCommand::HandleGlobCommand(std::vector<std::string> const& args,
-                                      bool recurse)
+bool HandleGlobImpl(std::vector<std::string> const& args, bool recurse,
+                    cmExecutionStatus& status)
 {
   // File commands has at least one argument
   assert(args.size() > 1);
@@ -772,10 +677,10 @@
   g.SetRecurse(recurse);
 
   bool explicitFollowSymlinks = false;
-  cmPolicies::PolicyStatus status =
-    this->Makefile->GetPolicyStatus(cmPolicies::CMP0009);
+  cmPolicies::PolicyStatus policyStatus =
+    status.GetMakefile().GetPolicyStatus(cmPolicies::CMP0009);
   if (recurse) {
-    switch (status) {
+    switch (policyStatus) {
       case cmPolicies::REQUIRED_IF_USED:
       case cmPolicies::REQUIRED_ALWAYS:
       case cmPolicies::NEW:
@@ -793,7 +698,7 @@
   bool warnConfigureLate = false;
   bool warnFollowedSymlinks = false;
   const cmake::WorkingMode workingMode =
-    this->Makefile->GetCMakeInstance()->GetWorkingMode();
+    status.GetMakefile().GetCMakeInstance()->GetWorkingMode();
   while (i != args.end()) {
     if (*i == "LIST_DIRECTORIES") {
       ++i; // skip LIST_DIRECTORIES
@@ -805,12 +710,12 @@
           g.SetListDirs(false);
           g.SetRecurseListDirs(false);
         } else {
-          this->SetError("LIST_DIRECTORIES missing bool value.");
+          status.SetError("LIST_DIRECTORIES missing bool value.");
           return false;
         }
         ++i;
       } else {
-        this->SetError("LIST_DIRECTORIES missing bool value.");
+        status.SetError("LIST_DIRECTORIES missing bool value.");
         return false;
       }
     } else if (*i == "FOLLOW_SYMLINKS") {
@@ -819,7 +724,7 @@
         explicitFollowSymlinks = true;
         g.RecurseThroughSymlinksOn();
         if (i == args.end()) {
-          this->SetError(
+          status.SetError(
             "GLOB_RECURSE requires a glob expression after FOLLOW_SYMLINKS.");
           return false;
         }
@@ -827,25 +732,26 @@
     } else if (*i == "RELATIVE") {
       ++i; // skip RELATIVE
       if (i == args.end()) {
-        this->SetError("GLOB requires a directory after the RELATIVE tag.");
+        status.SetError("GLOB requires a directory after the RELATIVE tag.");
         return false;
       }
       g.SetRelative(i->c_str());
       ++i;
       if (i == args.end()) {
-        this->SetError("GLOB requires a glob expression after the directory.");
+        status.SetError(
+          "GLOB requires a glob expression after the directory.");
         return false;
       }
     } else if (*i == "CONFIGURE_DEPENDS") {
       // Generated build system depends on glob results
       if (!configureDepends && warnConfigureLate) {
-        this->Makefile->IssueMessage(
+        status.GetMakefile().IssueMessage(
           MessageType::AUTHOR_WARNING,
           "CONFIGURE_DEPENDS flag was given after a glob expression was "
           "already evaluated.");
       }
       if (workingMode != cmake::NORMAL_MODE) {
-        this->Makefile->IssueMessage(
+        status.GetMakefile().IssueMessage(
           MessageType::FATAL_ERROR,
           "CONFIGURE_DEPENDS is invalid for script and find package modes.");
         return false;
@@ -853,14 +759,14 @@
       configureDepends = true;
       ++i;
       if (i == args.end()) {
-        this->SetError(
+        status.SetError(
           "GLOB requires a glob expression after CONFIGURE_DEPENDS.");
         return false;
       }
     } else {
       std::string expr = *i;
       if (!cmsys::SystemTools::FileIsFullPath(*i)) {
-        expr = this->Makefile->GetCurrentSourceDirectory();
+        expr = status.GetMakefile().GetCurrentSourceDirectory();
         // Handle script mode
         if (!expr.empty()) {
           expr += "/" + *i;
@@ -876,12 +782,12 @@
         bool shouldExit = false;
         for (cmsys::Glob::Message const& globMessage : globMessages) {
           if (globMessage.type == cmsys::Glob::cyclicRecursion) {
-            this->Makefile->IssueMessage(
+            status.GetMakefile().IssueMessage(
               MessageType::AUTHOR_WARNING,
               "Cyclic recursion detected while globbing for '" + *i + "':\n" +
                 globMessage.content);
           } else {
-            this->Makefile->IssueMessage(
+            status.GetMakefile().IssueMessage(
               MessageType::FATAL_ERROR,
               "Error has occurred while globbing for '" + *i + "' - " +
                 globMessage.content);
@@ -905,11 +811,11 @@
         std::sort(foundFiles.begin(), foundFiles.end());
         foundFiles.erase(std::unique(foundFiles.begin(), foundFiles.end()),
                          foundFiles.end());
-        this->Makefile->GetCMakeInstance()->AddGlobCacheEntry(
+        status.GetMakefile().GetCMakeInstance()->AddGlobCacheEntry(
           recurse, (recurse ? g.GetRecurseListDirs() : g.GetListDirs()),
           (recurse ? g.GetRecurseThroughSymlinks() : false),
           (g.GetRelative() ? g.GetRelative() : ""), expr, foundFiles, variable,
-          this->Makefile->GetBacktrace());
+          status.GetMakefile().GetBacktrace());
       } else {
         warnConfigureLate = true;
       }
@@ -917,7 +823,7 @@
     }
   }
 
-  switch (status) {
+  switch (policyStatus) {
     case cmPolicies::REQUIRED_IF_USED:
     case cmPolicies::REQUIRED_ALWAYS:
     case cmPolicies::NEW:
@@ -930,7 +836,7 @@
       // Possibly unexpected old behavior *and* we actually traversed
       // symlinks without being explicitly asked to: warn the author.
       if (warnFollowedSymlinks) {
-        this->Makefile->IssueMessage(
+        status.GetMakefile().IssueMessage(
           MessageType::AUTHOR_WARNING,
           cmPolicies::GetPolicyWarning(cmPolicies::CMP0009));
       }
@@ -939,12 +845,24 @@
 
   std::sort(files.begin(), files.end());
   files.erase(std::unique(files.begin(), files.end()), files.end());
-  this->Makefile->AddDefinition(variable, cmJoin(files, ";"));
+  status.GetMakefile().AddDefinition(variable, cmJoin(files, ";"));
   return true;
 }
 
-bool cmFileCommand::HandleMakeDirectoryCommand(
-  std::vector<std::string> const& args)
+bool HandleGlobCommand(std::vector<std::string> const& args,
+                       cmExecutionStatus& status)
+{
+  return HandleGlobImpl(args, false, status);
+}
+
+bool HandleGlobRecurseCommand(std::vector<std::string> const& args,
+                              cmExecutionStatus& status)
+{
+  return HandleGlobImpl(args, true, status);
+}
+
+bool HandleMakeDirectoryCommand(std::vector<std::string> const& args,
+                                cmExecutionStatus& status)
 {
   // File command has at least one argument
   assert(args.size() > 1);
@@ -955,28 +873,28 @@
   {
     const std::string* cdir = &arg;
     if (!cmsys::SystemTools::FileIsFullPath(arg)) {
-      expr = this->Makefile->GetCurrentSourceDirectory();
+      expr = status.GetMakefile().GetCurrentSourceDirectory();
       expr += "/" + arg;
       cdir = &expr;
     }
-    if (!this->Makefile->CanIWriteThisFile(*cdir)) {
+    if (!status.GetMakefile().CanIWriteThisFile(*cdir)) {
       std::string e = "attempted to create a directory: " + *cdir +
         " into a source directory.";
-      this->SetError(e);
+      status.SetError(e);
       cmSystemTools::SetFatalErrorOccured();
       return false;
     }
     if (!cmSystemTools::MakeDirectory(*cdir)) {
       std::string error = "problem creating directory: " + *cdir;
-      this->SetError(error);
+      status.SetError(error);
       return false;
     }
   }
   return true;
 }
 
-bool cmFileCommand::HandleTouchCommand(std::vector<std::string> const& args,
-                                       bool create)
+bool HandleTouchImpl(std::vector<std::string> const& args, bool create,
+                     cmExecutionStatus& status)
 {
   // File command has at least one argument
   assert(args.size() > 1);
@@ -986,27 +904,39 @@
   {
     std::string tfile = arg;
     if (!cmsys::SystemTools::FileIsFullPath(tfile)) {
-      tfile = this->Makefile->GetCurrentSourceDirectory();
+      tfile = status.GetMakefile().GetCurrentSourceDirectory();
       tfile += "/" + arg;
     }
-    if (!this->Makefile->CanIWriteThisFile(tfile)) {
+    if (!status.GetMakefile().CanIWriteThisFile(tfile)) {
       std::string e =
         "attempted to touch a file: " + tfile + " in a source directory.";
-      this->SetError(e);
+      status.SetError(e);
       cmSystemTools::SetFatalErrorOccured();
       return false;
     }
     if (!cmSystemTools::Touch(tfile, create)) {
       std::string error = "problem touching file: " + tfile;
-      this->SetError(error);
+      status.SetError(error);
       return false;
     }
   }
   return true;
 }
 
-bool cmFileCommand::HandleDifferentCommand(
-  std::vector<std::string> const& args)
+bool HandleTouchCommand(std::vector<std::string> const& args,
+                        cmExecutionStatus& status)
+{
+  return HandleTouchImpl(args, true, status);
+}
+
+bool HandleTouchNocreateCommand(std::vector<std::string> const& args,
+                                cmExecutionStatus& status)
+{
+  return HandleTouchImpl(args, false, status);
+}
+
+bool HandleDifferentCommand(std::vector<std::string> const& args,
+                            cmExecutionStatus& status)
 {
   /*
     FILE(DIFFERENT <variable> FILES <lhs> <rhs>)
@@ -1039,34 +969,35 @@
     } else {
       std::ostringstream e;
       e << "DIFFERENT given unknown argument " << args[i];
-      this->SetError(e.str());
+      status.SetError(e.str());
       return false;
     }
   }
   if (!var) {
-    this->SetError("DIFFERENT not given result variable name.");
+    status.SetError("DIFFERENT not given result variable name.");
     return false;
   }
   if (!file_lhs || !file_rhs) {
-    this->SetError("DIFFERENT not given FILES option with two file names.");
+    status.SetError("DIFFERENT not given FILES option with two file names.");
     return false;
   }
 
   // Compare the files.
   const char* result =
     cmSystemTools::FilesDiffer(file_lhs, file_rhs) ? "1" : "0";
-  this->Makefile->AddDefinition(var, result);
+  status.GetMakefile().AddDefinition(var, result);
   return true;
 }
 
-bool cmFileCommand::HandleCopyCommand(std::vector<std::string> const& args)
+bool HandleCopyCommand(std::vector<std::string> const& args,
+                       cmExecutionStatus& status)
 {
-  cmFileCopier copier(this);
+  cmFileCopier copier(status);
   return copier.Run(args);
 }
 
-bool cmFileCommand::HandleRPathChangeCommand(
-  std::vector<std::string> const& args)
+bool HandleRPathChangeCommand(std::vector<std::string> const& args,
+                              cmExecutionStatus& status)
 {
   // Evaluate arguments.
   std::string file;
@@ -1102,26 +1033,26 @@
     } else {
       std::ostringstream e;
       e << "RPATH_CHANGE given unknown argument " << args[i];
-      this->SetError(e.str());
+      status.SetError(e.str());
       return false;
     }
   }
   if (file.empty()) {
-    this->SetError("RPATH_CHANGE not given FILE option.");
+    status.SetError("RPATH_CHANGE not given FILE option.");
     return false;
   }
   if (!oldRPath) {
-    this->SetError("RPATH_CHANGE not given OLD_RPATH option.");
+    status.SetError("RPATH_CHANGE not given OLD_RPATH option.");
     return false;
   }
   if (!newRPath) {
-    this->SetError("RPATH_CHANGE not given NEW_RPATH option.");
+    status.SetError("RPATH_CHANGE not given NEW_RPATH option.");
     return false;
   }
   if (!cmSystemTools::FileExists(file, true)) {
     std::ostringstream e;
     e << "RPATH_CHANGE given FILE \"" << file << "\" that does not exist.";
-    this->SetError(e.str());
+    status.SetError(e.str());
     return false;
   }
   bool success = true;
@@ -1139,7 +1070,7 @@
       << "  " << file << "\n"
       << emsg;
     /* clang-format on */
-    this->SetError(e.str());
+    status.SetError(e.str());
     success = false;
   }
   if (success) {
@@ -1149,15 +1080,15 @@
       message += "\" to \"";
       message += newRPath;
       message += "\"";
-      this->Makefile->DisplayStatus(message, -1);
+      status.GetMakefile().DisplayStatus(message, -1);
     }
     ft.Store(file);
   }
   return success;
 }
 
-bool cmFileCommand::HandleRPathRemoveCommand(
-  std::vector<std::string> const& args)
+bool HandleRPathRemoveCommand(std::vector<std::string> const& args,
+                              cmExecutionStatus& status)
 {
   // Evaluate arguments.
   std::string file;
@@ -1176,18 +1107,18 @@
     } else {
       std::ostringstream e;
       e << "RPATH_REMOVE given unknown argument " << args[i];
-      this->SetError(e.str());
+      status.SetError(e.str());
       return false;
     }
   }
   if (file.empty()) {
-    this->SetError("RPATH_REMOVE not given FILE option.");
+    status.SetError("RPATH_REMOVE not given FILE option.");
     return false;
   }
   if (!cmSystemTools::FileExists(file, true)) {
     std::ostringstream e;
     e << "RPATH_REMOVE given FILE \"" << file << "\" that does not exist.";
-    this->SetError(e.str());
+    status.SetError(e.str());
     return false;
   }
   bool success = true;
@@ -1201,7 +1132,7 @@
       << "  " << file << "\n"
       << emsg;
     /* clang-format on */
-    this->SetError(e.str());
+    status.SetError(e.str());
     success = false;
   }
   if (success) {
@@ -1209,15 +1140,15 @@
       std::string message = "Removed runtime path from \"";
       message += file;
       message += "\"";
-      this->Makefile->DisplayStatus(message, -1);
+      status.GetMakefile().DisplayStatus(message, -1);
     }
     ft.Store(file);
   }
   return success;
 }
 
-bool cmFileCommand::HandleRPathCheckCommand(
-  std::vector<std::string> const& args)
+bool HandleRPathCheckCommand(std::vector<std::string> const& args,
+                             cmExecutionStatus& status)
 {
   // Evaluate arguments.
   std::string file;
@@ -1243,16 +1174,16 @@
     } else {
       std::ostringstream e;
       e << "RPATH_CHECK given unknown argument " << args[i];
-      this->SetError(e.str());
+      status.SetError(e.str());
       return false;
     }
   }
   if (file.empty()) {
-    this->SetError("RPATH_CHECK not given FILE option.");
+    status.SetError("RPATH_CHECK not given FILE option.");
     return false;
   }
   if (!rpath) {
-    this->SetError("RPATH_CHECK not given RPATH option.");
+    status.SetError("RPATH_CHECK not given RPATH option.");
     return false;
   }
 
@@ -1267,11 +1198,12 @@
   return true;
 }
 
-bool cmFileCommand::HandleReadElfCommand(std::vector<std::string> const& args)
+bool HandleReadElfCommand(std::vector<std::string> const& args,
+                          cmExecutionStatus& status)
 {
   if (args.size() < 4) {
-    this->SetError("READ_ELF must be called with at least three additional "
-                   "arguments.");
+    status.SetError("READ_ELF must be called with at least three additional "
+                    "arguments.");
     return false;
   }
 
@@ -1293,7 +1225,7 @@
   if (!cmSystemTools::FileExists(fileNameArg, true)) {
     std::ostringstream e;
     e << "READ_ELF given FILE \"" << fileNameArg << "\" that does not exist.";
-    this->SetError(e.str());
+    status.SetError(e.str());
     return false;
   }
 
@@ -1304,14 +1236,14 @@
     if (cmELF::StringEntry const* se_rpath = elf.GetRPath()) {
       std::string rpath(se_rpath->Value);
       std::replace(rpath.begin(), rpath.end(), ':', ';');
-      this->Makefile->AddDefinition(arguments.RPath, rpath);
+      status.GetMakefile().AddDefinition(arguments.RPath, rpath);
     }
   }
   if (!arguments.RunPath.empty()) {
     if (cmELF::StringEntry const* se_runpath = elf.GetRunPath()) {
       std::string runpath(se_runpath->Value);
       std::replace(runpath.begin(), runpath.end(), ':', ';');
-      this->Makefile->AddDefinition(arguments.RunPath, runpath);
+      status.GetMakefile().AddDefinition(arguments.RunPath, runpath);
     }
   }
 
@@ -1319,25 +1251,26 @@
 #else
   std::string error = "ELF parser not available on this platform.";
   if (arguments.Error.empty()) {
-    this->SetError(error);
+    status.SetError(error);
     return false;
   }
-  this->Makefile->AddDefinition(arguments.Error, error);
+  status.GetMakefile().AddDefinition(arguments.Error, error);
   return true;
 #endif
 }
 
-bool cmFileCommand::HandleInstallCommand(std::vector<std::string> const& args)
+bool HandleInstallCommand(std::vector<std::string> const& args,
+                          cmExecutionStatus& status)
 {
-  cmFileInstaller installer(this);
+  cmFileInstaller installer(status);
   return installer.Run(args);
 }
 
-bool cmFileCommand::HandleRelativePathCommand(
-  std::vector<std::string> const& args)
+bool HandleRelativePathCommand(std::vector<std::string> const& args,
+                               cmExecutionStatus& status)
 {
   if (args.size() != 4) {
-    this->SetError("RELATIVE_PATH called with incorrect number of arguments");
+    status.SetError("RELATIVE_PATH called with incorrect number of arguments");
     return false;
   }
 
@@ -1349,37 +1282,38 @@
     std::string errstring =
       "RELATIVE_PATH must be passed a full path to the directory: " +
       directoryName;
-    this->SetError(errstring);
+    status.SetError(errstring);
     return false;
   }
   if (!cmSystemTools::FileIsFullPath(fileName)) {
     std::string errstring =
       "RELATIVE_PATH must be passed a full path to the file: " + fileName;
-    this->SetError(errstring);
+    status.SetError(errstring);
     return false;
   }
 
   std::string res = cmSystemTools::RelativePath(directoryName, fileName);
-  this->Makefile->AddDefinition(outVar, res);
+  status.GetMakefile().AddDefinition(outVar, res);
   return true;
 }
 
-bool cmFileCommand::HandleRename(std::vector<std::string> const& args)
+bool HandleRename(std::vector<std::string> const& args,
+                  cmExecutionStatus& status)
 {
   if (args.size() != 3) {
-    this->SetError("RENAME given incorrect number of arguments.");
+    status.SetError("RENAME given incorrect number of arguments.");
     return false;
   }
 
   // Compute full path for old and new names.
   std::string oldname = args[1];
   if (!cmsys::SystemTools::FileIsFullPath(oldname)) {
-    oldname = this->Makefile->GetCurrentSourceDirectory();
+    oldname = status.GetMakefile().GetCurrentSourceDirectory();
     oldname += "/" + args[1];
   }
   std::string newname = args[2];
   if (!cmsys::SystemTools::FileIsFullPath(newname)) {
-    newname = this->Makefile->GetCurrentSourceDirectory();
+    newname = status.GetMakefile().GetCurrentSourceDirectory();
     newname += "/" + args[2];
   }
 
@@ -1393,14 +1327,14 @@
       << "  " << newname << "\n"
       << "because: " << err << "\n";
     /* clang-format on */
-    this->SetError(e.str());
+    status.SetError(e.str());
     return false;
   }
   return true;
 }
 
-bool cmFileCommand::HandleRemove(std::vector<std::string> const& args,
-                                 bool recurse)
+bool HandleRemoveImpl(std::vector<std::string> const& args, bool recurse,
+                      cmExecutionStatus& status)
 {
 
   std::string message;
@@ -1411,12 +1345,12 @@
     std::string fileName = arg;
     if (fileName.empty()) {
       std::string const r = recurse ? "REMOVE_RECURSE" : "REMOVE";
-      this->Makefile->IssueMessage(MessageType::AUTHOR_WARNING,
-                                   "Ignoring empty file name in " + r + ".");
+      status.GetMakefile().IssueMessage(
+        MessageType::AUTHOR_WARNING, "Ignoring empty file name in " + r + ".");
       continue;
     }
     if (!cmsys::SystemTools::FileIsFullPath(fileName)) {
-      fileName = this->Makefile->GetCurrentSourceDirectory();
+      fileName = status.GetMakefile().GetCurrentSourceDirectory();
       fileName += "/" + arg;
     }
 
@@ -1430,7 +1364,18 @@
   return true;
 }
 
-namespace {
+bool HandleRemove(std::vector<std::string> const& args,
+                  cmExecutionStatus& status)
+{
+  return HandleRemoveImpl(args, false, status);
+}
+
+bool HandleRemoveRecurse(std::vector<std::string> const& args,
+                         cmExecutionStatus& status)
+{
+  return HandleRemoveImpl(args, true, status);
+}
+
 std::string ToNativePath(const std::string& path)
 {
   const auto& outPath = cmSystemTools::ConvertToOutputPath(path);
@@ -1447,14 +1392,14 @@
   cmSystemTools::ConvertToUnixSlashes(temp);
   return temp;
 }
-}
 
-bool cmFileCommand::HandleCMakePathCommand(
-  std::vector<std::string> const& args, bool nativePath)
+bool HandlePathCommand(std::vector<std::string> const& args,
+                       std::string (*convert)(std::string const&),
+                       cmExecutionStatus& status)
 {
   if (args.size() != 3) {
-    this->SetError("FILE([TO_CMAKE_PATH|TO_NATIVE_PATH] path result) must be "
-                   "called with exactly three arguments.");
+    status.SetError("FILE([TO_CMAKE_PATH|TO_NATIVE_PATH] path result) must be "
+                    "called with exactly three arguments.");
     return false;
   }
 #if defined(_WIN32) && !defined(__CYGWIN__)
@@ -1464,19 +1409,28 @@
 #endif
   std::vector<std::string> path = cmSystemTools::SplitString(args[1], pathSep);
 
-  std::string value = cmJoin(
-    cmMakeRange(path).transform(nativePath ? ToNativePath : ToCMakePath), ";");
-  this->Makefile->AddDefinition(args[2], value);
+  std::string value = cmJoin(cmMakeRange(path).transform(convert), ";");
+  status.GetMakefile().AddDefinition(args[2], value);
   return true;
 }
 
+bool HandleCMakePathCommand(std::vector<std::string> const& args,
+                            cmExecutionStatus& status)
+{
+  return HandlePathCommand(args, ToCMakePath, status);
+}
+
+bool HandleNativePathCommand(std::vector<std::string> const& args,
+                             cmExecutionStatus& status)
+{
+  return HandlePathCommand(args, ToNativePath, status);
+}
+
 #if defined(CMAKE_BUILD_WITH_CMAKE)
 
 // Stuff for curl download/upload
 typedef std::vector<char> cmFileCommandVectorOfChar;
 
-namespace {
-
 size_t cmWriteToFileCallback(void* ptr, size_t size, size_t nmemb, void* data)
 {
   int realsize = static_cast<int>(size * nmemb);
@@ -1527,11 +1481,10 @@
 class cURLProgressHelper
 {
 public:
-  cURLProgressHelper(cmFileCommand* fc, const char* text)
+  cURLProgressHelper(cmMakefile* mf, const char* text)
+    : Makefile(mf)
+    , Text(text)
   {
-    this->CurrentPercentage = -1;
-    this->FileCommand = fc;
-    this->Text = text;
   }
 
   bool UpdatePercentage(double value, double total, std::string& status)
@@ -1558,11 +1511,11 @@
     return updated;
   }
 
-  cmFileCommand* GetFileCommand() { return this->FileCommand; }
+  cmMakefile* GetMakefile() { return this->Makefile; }
 
 private:
-  long CurrentPercentage;
-  cmFileCommand* FileCommand;
+  long CurrentPercentage = -1;
+  cmMakefile* Makefile;
   std::string Text;
 };
 
@@ -1576,8 +1529,7 @@
 
   std::string status;
   if (helper->UpdatePercentage(dlnow, dltotal, status)) {
-    cmFileCommand* fc = helper->GetFileCommand();
-    cmMakefile* mf = fc->GetMakefile();
+    cmMakefile* mf = helper->GetMakefile();
     mf->DisplayStatus(status, -1);
   }
 
@@ -1594,16 +1546,12 @@
 
   std::string status;
   if (helper->UpdatePercentage(ulnow, ultotal, status)) {
-    cmFileCommand* fc = helper->GetFileCommand();
-    cmMakefile* mf = fc->GetMakefile();
+    cmMakefile* mf = helper->GetMakefile();
     mf->DisplayStatus(status, -1);
   }
 
   return 0;
 }
-}
-
-namespace {
 
 class cURLEasyGuard
 {
@@ -1628,7 +1576,7 @@
 private:
   ::CURL* Easy;
 };
-}
+
 #endif
 
 #define check_curl_result(result, errstr)                                     \
@@ -1636,17 +1584,18 @@
     if (result != CURLE_OK) {                                                 \
       std::string e(errstr);                                                  \
       e += ::curl_easy_strerror(result);                                      \
-      this->SetError(e);                                                      \
+      status.SetError(e);                                                     \
       return false;                                                           \
     }                                                                         \
   } while (false)
 
-bool cmFileCommand::HandleDownloadCommand(std::vector<std::string> const& args)
+bool HandleDownloadCommand(std::vector<std::string> const& args,
+                           cmExecutionStatus& status)
 {
 #if defined(CMAKE_BUILD_WITH_CMAKE)
   std::vector<std::string>::const_iterator i = args.begin();
   if (args.size() < 3) {
-    this->SetError("DOWNLOAD must be called with at least three arguments.");
+    status.SetError("DOWNLOAD must be called with at least three arguments.");
     return false;
   }
   ++i; // Get rid of subcommand
@@ -1659,11 +1608,12 @@
   long inactivity_timeout = 0;
   std::string logVar;
   std::string statusVar;
-  bool tls_verify = this->Makefile->IsOn("CMAKE_TLS_VERIFY");
-  const char* cainfo = this->Makefile->GetDefinition("CMAKE_TLS_CAINFO");
-  std::string netrc_level = this->Makefile->GetSafeDefinition("CMAKE_NETRC");
+  bool tls_verify = status.GetMakefile().IsOn("CMAKE_TLS_VERIFY");
+  const char* cainfo = status.GetMakefile().GetDefinition("CMAKE_TLS_CAINFO");
+  std::string netrc_level =
+    status.GetMakefile().GetSafeDefinition("CMAKE_NETRC");
   std::string netrc_file =
-    this->Makefile->GetSafeDefinition("CMAKE_NETRC_FILE");
+    status.GetMakefile().GetSafeDefinition("CMAKE_NETRC_FILE");
   std::string expectedHash;
   std::string hashMatchMSG;
   std::unique_ptr<cmCryptoHash> hash;
@@ -1678,7 +1628,7 @@
       if (i != args.end()) {
         timeout = atol(i->c_str());
       } else {
-        this->SetError("DOWNLOAD missing time for TIMEOUT.");
+        status.SetError("DOWNLOAD missing time for TIMEOUT.");
         return false;
       }
     } else if (*i == "INACTIVITY_TIMEOUT") {
@@ -1686,20 +1636,20 @@
       if (i != args.end()) {
         inactivity_timeout = atol(i->c_str());
       } else {
-        this->SetError("DOWNLOAD missing time for INACTIVITY_TIMEOUT.");
+        status.SetError("DOWNLOAD missing time for INACTIVITY_TIMEOUT.");
         return false;
       }
     } else if (*i == "LOG") {
       ++i;
       if (i == args.end()) {
-        this->SetError("DOWNLOAD missing VAR for LOG.");
+        status.SetError("DOWNLOAD missing VAR for LOG.");
         return false;
       }
       logVar = *i;
     } else if (*i == "STATUS") {
       ++i;
       if (i == args.end()) {
-        this->SetError("DOWNLOAD missing VAR for STATUS.");
+        status.SetError("DOWNLOAD missing VAR for STATUS.");
         return false;
       }
       statusVar = *i;
@@ -1708,7 +1658,7 @@
       if (i != args.end()) {
         tls_verify = cmSystemTools::IsOn(*i);
       } else {
-        this->SetError("TLS_VERIFY missing bool value.");
+        status.SetError("TLS_VERIFY missing bool value.");
         return false;
       }
     } else if (*i == "TLS_CAINFO") {
@@ -1716,7 +1666,7 @@
       if (i != args.end()) {
         cainfo = i->c_str();
       } else {
-        this->SetError("TLS_CAFILE missing file value.");
+        status.SetError("TLS_CAFILE missing file value.");
         return false;
       }
     } else if (*i == "NETRC_FILE") {
@@ -1724,7 +1674,7 @@
       if (i != args.end()) {
         netrc_file = *i;
       } else {
-        this->SetError("DOWNLOAD missing file value for NETRC_FILE.");
+        status.SetError("DOWNLOAD missing file value for NETRC_FILE.");
         return false;
       }
     } else if (*i == "NETRC") {
@@ -1732,13 +1682,13 @@
       if (i != args.end()) {
         netrc_level = *i;
       } else {
-        this->SetError("DOWNLOAD missing level value for NETRC.");
+        status.SetError("DOWNLOAD missing level value for NETRC.");
         return false;
       }
     } else if (*i == "EXPECTED_MD5") {
       ++i;
       if (i == args.end()) {
-        this->SetError("DOWNLOAD missing sum value for EXPECTED_MD5.");
+        status.SetError("DOWNLOAD missing sum value for EXPECTED_MD5.");
         return false;
       }
       hash = cm::make_unique<cmCryptoHash>(cmCryptoHash::AlgoMD5);
@@ -1749,7 +1699,7 @@
     } else if (*i == "EXPECTED_HASH") {
       ++i;
       if (i == args.end()) {
-        this->SetError("DOWNLOAD missing ALGO=value for EXPECTED_HASH.");
+        status.SetError("DOWNLOAD missing ALGO=value for EXPECTED_HASH.");
         return false;
       }
       std::string::size_type pos = i->find("=");
@@ -1757,7 +1707,7 @@
         std::string err =
           "DOWNLOAD EXPECTED_HASH expects ALGO=value but got: ";
         err += *i;
-        this->SetError(err);
+        status.SetError(err);
         return false;
       }
       std::string algo = i->substr(0, pos);
@@ -1766,21 +1716,21 @@
       if (!hash) {
         std::string err = "DOWNLOAD EXPECTED_HASH given unknown ALGO: ";
         err += algo;
-        this->SetError(err);
+        status.SetError(err);
         return false;
       }
       hashMatchMSG = algo + " hash";
     } else if (*i == "USERPWD") {
       ++i;
       if (i == args.end()) {
-        this->SetError("DOWNLOAD missing string for USERPWD.");
+        status.SetError("DOWNLOAD missing string for USERPWD.");
         return false;
       }
       userpwd = *i;
     } else if (*i == "HTTPHEADER") {
       ++i;
       if (i == args.end()) {
-        this->SetError("DOWNLOAD missing string for HTTPHEADER.");
+        status.SetError("DOWNLOAD missing string for HTTPHEADER.");
         return false;
       }
       curl_headers.push_back(*i);
@@ -1788,7 +1738,7 @@
       // Do not return error for compatibility reason.
       std::string err = "Unexpected argument: ";
       err += *i;
-      this->Makefile->IssueMessage(MessageType::AUTHOR_WARNING, err);
+      status.GetMakefile().IssueMessage(MessageType::AUTHOR_WARNING, err);
     }
     ++i;
   }
@@ -1806,7 +1756,7 @@
       if (!statusVar.empty()) {
         std::ostringstream result;
         result << 0 << ";\"" << msg;
-        this->Makefile->AddDefinition(statusVar, result.str());
+        status.GetMakefile().AddDefinition(statusVar, result.str());
       }
       return true;
     }
@@ -1819,13 +1769,13 @@
     std::string errstring = "DOWNLOAD error: cannot create directory '" + dir +
       "' - Specify file by full path name and verify that you "
       "have directory creation and file write privileges.";
-    this->SetError(errstring);
+    status.SetError(errstring);
     return false;
   }
 
   cmsys::ofstream fout(file.c_str(), std::ios::binary);
   if (!fout) {
-    this->SetError("DOWNLOAD cannot open file for write.");
+    status.SetError("DOWNLOAD cannot open file for write.");
     return false;
   }
 
@@ -1837,7 +1787,7 @@
   ::curl_global_init(CURL_GLOBAL_DEFAULT);
   curl = ::curl_easy_init();
   if (!curl) {
-    this->SetError("DOWNLOAD error initializing curl.");
+    status.SetError("DOWNLOAD error initializing curl.");
     return false;
   }
 
@@ -1871,7 +1821,7 @@
   // command arg comes first
   std::string const& cainfo_err = cmCurlSetCAInfo(curl, cainfo);
   if (!cainfo_err.empty()) {
-    this->SetError(cainfo_err);
+    status.SetError(cainfo_err);
     return false;
   }
 
@@ -1881,7 +1831,7 @@
   std::string const& netrc_option_err =
     cmCurlSetNETRCOption(curl, netrc_level, netrc_file);
   if (!netrc_option_err.empty()) {
-    this->SetError(netrc_option_err);
+    status.SetError(netrc_option_err);
     return false;
   }
 
@@ -1917,7 +1867,7 @@
   // scope intentionally, rather than inside the "if(showProgress)"
   // block...
   //
-  cURLProgressHelper helper(this, "download");
+  cURLProgressHelper helper(&status.GetMakefile(), "download");
 
   if (showProgress) {
     res = ::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
@@ -1955,7 +1905,7 @@
     std::ostringstream result;
     result << static_cast<int>(res) << ";\"" << ::curl_easy_strerror(res)
            << "\"";
-    this->Makefile->AddDefinition(statusVar, result.str());
+    status.GetMakefile().AddDefinition(statusVar, result.str());
   }
 
   ::curl_global_cleanup();
@@ -1970,7 +1920,7 @@
   if (hash) {
     std::string actualHash = hash->HashFile(file);
     if (actualHash.empty()) {
-      this->SetError("DOWNLOAD cannot compute hash on downloaded file");
+      status.SetError("DOWNLOAD cannot compute hash on downloaded file");
       return false;
     }
 
@@ -1984,34 +1934,36 @@
           << ::curl_easy_strerror(res) << "\"]" << std::endl;
 
       if (!statusVar.empty() && res == 0) {
-        std::string status = "1;HASH mismatch: "
-                             "expected: " +
-          expectedHash + " actual: " + actualHash;
-        this->Makefile->AddDefinition(statusVar, status);
+        status.GetMakefile().AddDefinition(statusVar,
+                                           "1;HASH mismatch: "
+                                           "expected: " +
+                                             expectedHash +
+                                             " actual: " + actualHash);
       }
 
-      this->SetError(oss.str());
+      status.SetError(oss.str());
       return false;
     }
   }
 
   if (!logVar.empty()) {
     chunkDebug.push_back(0);
-    this->Makefile->AddDefinition(logVar, chunkDebug.data());
+    status.GetMakefile().AddDefinition(logVar, chunkDebug.data());
   }
 
   return true;
 #else
-  this->SetError("DOWNLOAD not supported by bootstrap cmake.");
+  status.SetError("DOWNLOAD not supported by bootstrap cmake.");
   return false;
 #endif
 }
 
-bool cmFileCommand::HandleUploadCommand(std::vector<std::string> const& args)
+bool HandleUploadCommand(std::vector<std::string> const& args,
+                         cmExecutionStatus& status)
 {
 #if defined(CMAKE_BUILD_WITH_CMAKE)
   if (args.size() < 3) {
-    this->SetError("UPLOAD must be called with at least three arguments.");
+    status.SetError("UPLOAD must be called with at least three arguments.");
     return false;
   }
   std::vector<std::string>::const_iterator i = args.begin();
@@ -2027,9 +1979,10 @@
   std::string statusVar;
   bool showProgress = false;
   std::string userpwd;
-  std::string netrc_level = this->Makefile->GetSafeDefinition("CMAKE_NETRC");
+  std::string netrc_level =
+    status.GetMakefile().GetSafeDefinition("CMAKE_NETRC");
   std::string netrc_file =
-    this->Makefile->GetSafeDefinition("CMAKE_NETRC_FILE");
+    status.GetMakefile().GetSafeDefinition("CMAKE_NETRC_FILE");
 
   std::vector<std::string> curl_headers;
 
@@ -2039,7 +1992,7 @@
       if (i != args.end()) {
         timeout = atol(i->c_str());
       } else {
-        this->SetError("UPLOAD missing time for TIMEOUT.");
+        status.SetError("UPLOAD missing time for TIMEOUT.");
         return false;
       }
     } else if (*i == "INACTIVITY_TIMEOUT") {
@@ -2047,20 +2000,20 @@
       if (i != args.end()) {
         inactivity_timeout = atol(i->c_str());
       } else {
-        this->SetError("UPLOAD missing time for INACTIVITY_TIMEOUT.");
+        status.SetError("UPLOAD missing time for INACTIVITY_TIMEOUT.");
         return false;
       }
     } else if (*i == "LOG") {
       ++i;
       if (i == args.end()) {
-        this->SetError("UPLOAD missing VAR for LOG.");
+        status.SetError("UPLOAD missing VAR for LOG.");
         return false;
       }
       logVar = *i;
     } else if (*i == "STATUS") {
       ++i;
       if (i == args.end()) {
-        this->SetError("UPLOAD missing VAR for STATUS.");
+        status.SetError("UPLOAD missing VAR for STATUS.");
         return false;
       }
       statusVar = *i;
@@ -2071,7 +2024,7 @@
       if (i != args.end()) {
         netrc_file = *i;
       } else {
-        this->SetError("UPLOAD missing file value for NETRC_FILE.");
+        status.SetError("UPLOAD missing file value for NETRC_FILE.");
         return false;
       }
     } else if (*i == "NETRC") {
@@ -2079,20 +2032,20 @@
       if (i != args.end()) {
         netrc_level = *i;
       } else {
-        this->SetError("UPLOAD missing level value for NETRC.");
+        status.SetError("UPLOAD missing level value for NETRC.");
         return false;
       }
     } else if (*i == "USERPWD") {
       ++i;
       if (i == args.end()) {
-        this->SetError("UPLOAD missing string for USERPWD.");
+        status.SetError("UPLOAD missing string for USERPWD.");
         return false;
       }
       userpwd = *i;
     } else if (*i == "HTTPHEADER") {
       ++i;
       if (i == args.end()) {
-        this->SetError("UPLOAD missing string for HTTPHEADER.");
+        status.SetError("UPLOAD missing string for HTTPHEADER.");
         return false;
       }
       curl_headers.push_back(*i);
@@ -2100,7 +2053,7 @@
       // Do not return error for compatibility reason.
       std::string err = "Unexpected argument: ";
       err += *i;
-      this->Makefile->IssueMessage(MessageType::AUTHOR_WARNING, err);
+      status.GetMakefile().IssueMessage(MessageType::AUTHOR_WARNING, err);
     }
 
     ++i;
@@ -2112,7 +2065,7 @@
   if (!fin) {
     std::string errStr = "UPLOAD cannot open file '";
     errStr += filename + "' for reading.";
-    this->SetError(errStr);
+    status.SetError(errStr);
     return false;
   }
 
@@ -2126,7 +2079,7 @@
   ::curl_global_init(CURL_GLOBAL_DEFAULT);
   curl = ::curl_easy_init();
   if (!curl) {
-    this->SetError("UPLOAD error initializing curl.");
+    status.SetError("UPLOAD error initializing curl.");
     fclose(fin);
     return false;
   }
@@ -2185,7 +2138,7 @@
   // scope intentionally, rather than inside the "if(showProgress)"
   // block...
   //
-  cURLProgressHelper helper(this, "upload");
+  cURLProgressHelper helper(&status.GetMakefile(), "upload");
 
   if (showProgress) {
     res = ::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
@@ -2220,7 +2173,7 @@
   std::string const& netrc_option_err =
     cmCurlSetNETRCOption(curl, netrc_level, netrc_file);
   if (!netrc_option_err.empty()) {
-    this->SetError(netrc_option_err);
+    status.SetError(netrc_option_err);
     return false;
   }
 
@@ -2242,7 +2195,7 @@
     std::ostringstream result;
     result << static_cast<int>(res) << ";\"" << ::curl_easy_strerror(res)
            << "\"";
-    this->Makefile->AddDefinition(statusVar, result.str());
+    status.GetMakefile().AddDefinition(statusVar, result.str());
   }
 
   ::curl_global_cleanup();
@@ -2267,22 +2220,22 @@
       log += "\n";
     }
 
-    this->Makefile->AddDefinition(logVar, log);
+    status.GetMakefile().AddDefinition(logVar, log);
   }
 
   return true;
 #else
-  this->SetError("UPLOAD not supported by bootstrap cmake.");
+  status.SetError("UPLOAD not supported by bootstrap cmake.");
   return false;
 #endif
 }
 
-void cmFileCommand::AddEvaluationFile(const std::string& inputName,
-                                      const std::string& outputExpr,
-                                      const std::string& condition,
-                                      bool inputIsContent)
+void AddEvaluationFile(const std::string& inputName,
+                       const std::string& outputExpr,
+                       const std::string& condition, bool inputIsContent,
+                       cmExecutionStatus& status)
 {
-  cmListFileBacktrace lfbt = this->Makefile->GetBacktrace();
+  cmListFileBacktrace lfbt = status.GetMakefile().GetBacktrace();
 
   cmGeneratorExpression outputGe(lfbt);
   std::unique_ptr<cmCompiledGeneratorExpression> outputCge =
@@ -2292,50 +2245,52 @@
   std::unique_ptr<cmCompiledGeneratorExpression> conditionCge =
     conditionGe.Parse(condition);
 
-  this->Makefile->AddEvaluationFile(inputName, std::move(outputCge),
-                                    std::move(conditionCge), inputIsContent);
+  status.GetMakefile().AddEvaluationFile(
+    inputName, std::move(outputCge), std::move(conditionCge), inputIsContent);
 }
 
-bool cmFileCommand::HandleGenerateCommand(std::vector<std::string> const& args)
+bool HandleGenerateCommand(std::vector<std::string> const& args,
+                           cmExecutionStatus& status)
 {
   if (args.size() < 5) {
-    this->SetError("Incorrect arguments to GENERATE subcommand.");
+    status.SetError("Incorrect arguments to GENERATE subcommand.");
     return false;
   }
   if (args[1] != "OUTPUT") {
-    this->SetError("Incorrect arguments to GENERATE subcommand.");
+    status.SetError("Incorrect arguments to GENERATE subcommand.");
     return false;
   }
   std::string condition;
   if (args.size() > 5) {
     if (args[5] != "CONDITION") {
-      this->SetError("Incorrect arguments to GENERATE subcommand.");
+      status.SetError("Incorrect arguments to GENERATE subcommand.");
       return false;
     }
     if (args.size() != 7) {
-      this->SetError("Incorrect arguments to GENERATE subcommand.");
+      status.SetError("Incorrect arguments to GENERATE subcommand.");
       return false;
     }
     condition = args[6];
     if (condition.empty()) {
-      this->SetError("CONDITION of sub-command GENERATE must not be empty if "
-                     "specified.");
+      status.SetError("CONDITION of sub-command GENERATE must not be empty if "
+                      "specified.");
       return false;
     }
   }
   std::string output = args[2];
   const bool inputIsContent = args[3] != "INPUT";
   if (inputIsContent && args[3] != "CONTENT") {
-    this->SetError("Incorrect arguments to GENERATE subcommand.");
+    status.SetError("Incorrect arguments to GENERATE subcommand.");
     return false;
   }
   std::string input = args[4];
 
-  this->AddEvaluationFile(input, output, condition, inputIsContent);
+  AddEvaluationFile(input, output, condition, inputIsContent, status);
   return true;
 }
 
-bool cmFileCommand::HandleLockCommand(std::vector<std::string> const& args)
+bool HandleLockCommand(std::vector<std::string> const& args,
+                       cmExecutionStatus& status)
 {
 #if defined(CMAKE_BUILD_WITH_CMAKE)
   // Default values
@@ -2353,7 +2308,7 @@
 
   // Parse arguments
   if (args.size() < 2) {
-    this->Makefile->IssueMessage(
+    status.GetMakefile().IssueMessage(
       MessageType::FATAL_ERROR,
       "sub-command LOCK requires at least two arguments.");
     return false;
@@ -2369,7 +2324,7 @@
       ++i;
       const char* merr = "expected FUNCTION, FILE or PROCESS after GUARD";
       if (i >= args.size()) {
-        this->Makefile->IssueMessage(MessageType::FATAL_ERROR, merr);
+        status.GetMakefile().IssueMessage(MessageType::FATAL_ERROR, merr);
         return false;
       }
       if (args[i] == "FUNCTION") {
@@ -2381,14 +2336,14 @@
       } else {
         std::ostringstream e;
         e << merr << ", but got:\n  \"" << args[i] << "\".";
-        this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
+        status.GetMakefile().IssueMessage(MessageType::FATAL_ERROR, e.str());
         return false;
       }
 
     } else if (args[i] == "RESULT_VARIABLE") {
       ++i;
       if (i >= args.size()) {
-        this->Makefile->IssueMessage(
+        status.GetMakefile().IssueMessage(
           MessageType::FATAL_ERROR,
           "expected variable name after RESULT_VARIABLE");
         return false;
@@ -2397,8 +2352,8 @@
     } else if (args[i] == "TIMEOUT") {
       ++i;
       if (i >= args.size()) {
-        this->Makefile->IssueMessage(MessageType::FATAL_ERROR,
-                                     "expected timeout value after TIMEOUT");
+        status.GetMakefile().IssueMessage(
+          MessageType::FATAL_ERROR, "expected timeout value after TIMEOUT");
         return false;
       }
       long scanned;
@@ -2406,7 +2361,7 @@
           scanned < 0) {
         std::ostringstream e;
         e << "TIMEOUT value \"" << args[i] << "\" is not an unsigned integer.";
-        this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
+        status.GetMakefile().IssueMessage(MessageType::FATAL_ERROR, e.str());
         return false;
       }
       timeout = static_cast<unsigned long>(scanned);
@@ -2414,7 +2369,7 @@
       std::ostringstream e;
       e << "expected DIRECTORY, RELEASE, GUARD, RESULT_VARIABLE or TIMEOUT\n";
       e << "but got: \"" << args[i] << "\".";
-      this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
+      status.GetMakefile().IssueMessage(MessageType::FATAL_ERROR, e.str());
       return false;
     }
   }
@@ -2424,7 +2379,7 @@
   }
 
   if (!cmsys::SystemTools::FileIsFullPath(path)) {
-    path = this->Makefile->GetCurrentSourceDirectory() + "/" + path;
+    path = status.GetMakefile().GetCurrentSourceDirectory() + "/" + path;
   }
 
   // Unify path (remove '//', '/../', ...)
@@ -2436,7 +2391,7 @@
     std::ostringstream e;
     e << "directory\n  \"" << parentDir << "\"\ncreation failed ";
     e << "(check permissions).";
-    this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
+    status.GetMakefile().IssueMessage(MessageType::FATAL_ERROR, e.str());
     cmSystemTools::SetFatalErrorOccured();
     return false;
   }
@@ -2444,7 +2399,7 @@
   if (!file) {
     std::ostringstream e;
     e << "file\n  \"" << path << "\"\ncreation failed (check permissions).";
-    this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
+    status.GetMakefile().IssueMessage(MessageType::FATAL_ERROR, e.str());
     cmSystemTools::SetFatalErrorOccured();
     return false;
   }
@@ -2452,7 +2407,7 @@
 
   // Actual lock/unlock
   cmFileLockPool& lockPool =
-    this->Makefile->GetGlobalGenerator()->GetFileLockPool();
+    status.GetMakefile().GetGlobalGenerator()->GetFileLockPool();
 
   cmFileLockResult fileLockResult(cmFileLockResult::MakeOk());
   if (release) {
@@ -2479,32 +2434,32 @@
   if (resultVariable.empty() && !fileLockResult.IsOk()) {
     std::ostringstream e;
     e << "error locking file\n  \"" << path << "\"\n" << result << ".";
-    this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
+    status.GetMakefile().IssueMessage(MessageType::FATAL_ERROR, e.str());
     cmSystemTools::SetFatalErrorOccured();
     return false;
   }
 
   if (!resultVariable.empty()) {
-    this->Makefile->AddDefinition(resultVariable, result);
+    status.GetMakefile().AddDefinition(resultVariable, result);
   }
 
   return true;
 #else
   static_cast<void>(args);
-  this->SetError("sub-command LOCK not implemented in bootstrap cmake");
+  status.SetError("sub-command LOCK not implemented in bootstrap cmake");
   return false;
 #endif
 }
 
-bool cmFileCommand::HandleTimestampCommand(
-  std::vector<std::string> const& args)
+bool HandleTimestampCommand(std::vector<std::string> const& args,
+                            cmExecutionStatus& status)
 {
   if (args.size() < 3) {
-    this->SetError("sub-command TIMESTAMP requires at least two arguments.");
+    status.SetError("sub-command TIMESTAMP requires at least two arguments.");
     return false;
   }
   if (args.size() > 5) {
-    this->SetError("sub-command TIMESTAMP takes at most four arguments.");
+    status.SetError("sub-command TIMESTAMP takes at most four arguments.");
     return false;
   }
 
@@ -2526,7 +2481,7 @@
     } else {
       std::string e = " TIMESTAMP sub-command does not recognize option " +
         args[argsIndex] + ".";
-      this->SetError(e);
+      status.SetError(e);
       return false;
     }
   }
@@ -2534,17 +2489,18 @@
   cmTimestamp timestamp;
   std::string result =
     timestamp.FileModificationTime(filename.c_str(), formatString, utcFlag);
-  this->Makefile->AddDefinition(outputVariable, result);
+  status.GetMakefile().AddDefinition(outputVariable, result);
 
   return true;
 }
 
-bool cmFileCommand::HandleSizeCommand(std::vector<std::string> const& args)
+bool HandleSizeCommand(std::vector<std::string> const& args,
+                       cmExecutionStatus& status)
 {
   if (args.size() != 3) {
     std::ostringstream e;
     e << args[0] << " requires a file name and output variable";
-    this->SetError(e.str());
+    status.SetError(e.str());
     return false;
   }
 
@@ -2557,23 +2513,23 @@
   if (!cmSystemTools::FileExists(filename, true)) {
     std::ostringstream e;
     e << "SIZE requested of path that is not readable:\n  " << filename;
-    this->SetError(e.str());
+    status.SetError(e.str());
     return false;
   }
 
-  this->Makefile->AddDefinition(
+  status.GetMakefile().AddDefinition(
     outputVariable, std::to_string(cmSystemTools::FileLength(filename)));
 
   return true;
 }
 
-bool cmFileCommand::HandleReadSymlinkCommand(
-  std::vector<std::string> const& args)
+bool HandleReadSymlinkCommand(std::vector<std::string> const& args,
+                              cmExecutionStatus& status)
 {
   if (args.size() != 3) {
     std::ostringstream e;
     e << args[0] << " requires a file name and output variable";
-    this->SetError(e.str());
+    status.SetError(e.str());
     return false;
   }
 
@@ -2585,21 +2541,21 @@
     std::ostringstream e;
     e << "READ_SYMLINK requested of path that is not a symlink:\n  "
       << filename;
-    this->SetError(e.str());
+    status.SetError(e.str());
     return false;
   }
 
-  this->Makefile->AddDefinition(outputVariable, result);
+  status.GetMakefile().AddDefinition(outputVariable, result);
 
   return true;
 }
 
-bool cmFileCommand::HandleCreateLinkCommand(
-  std::vector<std::string> const& args)
+bool HandleCreateLinkCommand(std::vector<std::string> const& args,
+                             cmExecutionStatus& status)
 {
   if (args.size() < 3) {
-    this->SetError("CREATE_LINK must be called with at least two additional "
-                   "arguments");
+    status.SetError("CREATE_LINK must be called with at least two additional "
+                    "arguments");
     return false;
   }
 
@@ -2624,7 +2580,7 @@
     parser.Parse(cmMakeRange(args).advance(3), &unconsumedArgs);
 
   if (!unconsumedArgs.empty()) {
-    this->SetError("unknown argument: \"" + unconsumedArgs.front() + '\"');
+    status.SetError("unknown argument: \"" + unconsumedArgs.front() + '\"');
     return false;
   }
 
@@ -2635,10 +2591,10 @@
   if (fileName == newFileName) {
     result = "CREATE_LINK cannot use same file and newfile";
     if (!arguments.Result.empty()) {
-      this->Makefile->AddDefinition(arguments.Result, result);
+      status.GetMakefile().AddDefinition(arguments.Result, result);
       return true;
     }
-    this->SetError(result);
+    status.SetError(result);
     return false;
   }
 
@@ -2646,10 +2602,10 @@
   if (!arguments.Symbolic && !cmSystemTools::FileExists(fileName)) {
     result = "Cannot hard link \'" + fileName + "\' as it does not exist.";
     if (!arguments.Result.empty()) {
-      this->Makefile->AddDefinition(arguments.Result, result);
+      status.GetMakefile().AddDefinition(arguments.Result, result);
       return true;
     }
-    this->SetError(result);
+    status.SetError(result);
     return false;
   }
 
@@ -2663,10 +2619,10 @@
       << cmSystemTools::GetLastSystemError() << "\n";
 
     if (!arguments.Result.empty()) {
-      this->Makefile->AddDefinition(arguments.Result, e.str());
+      status.GetMakefile().AddDefinition(arguments.Result, e.str());
       return true;
     }
-    this->SetError(e.str());
+    status.SetError(e.str());
     return false;
   }
 
@@ -2693,45 +2649,46 @@
     result = "0";
   } else if (arguments.Result.empty()) {
     // The operation failed and the result is not reported in a variable.
-    this->SetError(result);
+    status.SetError(result);
     return false;
   }
 
   if (!arguments.Result.empty()) {
-    this->Makefile->AddDefinition(arguments.Result, result);
+    status.GetMakefile().AddDefinition(arguments.Result, result);
   }
 
   return true;
 }
 
-bool cmFileCommand::HandleGetRuntimeDependenciesCommand(
-  std::vector<std::string> const& args)
+bool HandleGetRuntimeDependenciesCommand(std::vector<std::string> const& args,
+                                         cmExecutionStatus& status)
 {
   static const std::set<std::string> supportedPlatforms = { "Windows", "Linux",
                                                             "Darwin" };
   std::string platform =
-    this->Makefile->GetSafeDefinition("CMAKE_HOST_SYSTEM_NAME");
+    status.GetMakefile().GetSafeDefinition("CMAKE_HOST_SYSTEM_NAME");
   if (!supportedPlatforms.count(platform)) {
     std::ostringstream e;
     e << "GET_RUNTIME_DEPENDENCIES is not supported on system \"" << platform
       << "\"";
-    this->SetError(e.str());
+    status.SetError(e.str());
     cmSystemTools::SetFatalErrorOccured();
     return false;
   }
 
-  if (this->Makefile->GetState()->GetMode() == cmState::Project) {
-    this->Makefile->IssueMessage(MessageType::AUTHOR_WARNING,
-                                 "You have used file(GET_RUNTIME_DEPENDENCIES)"
-                                 " in project mode. This is probably not what "
-                                 "you intended to do. Instead, please consider"
-                                 " using it in an install(CODE) or "
-                                 "install(SCRIPT) command. For example:"
-                                 "\n  install(CODE [["
-                                 "\n    file(GET_RUNTIME_DEPENDENCIES"
-                                 "\n      # ..."
-                                 "\n      )"
-                                 "\n    ]])");
+  if (status.GetMakefile().GetState()->GetMode() == cmState::Project) {
+    status.GetMakefile().IssueMessage(
+      MessageType::AUTHOR_WARNING,
+      "You have used file(GET_RUNTIME_DEPENDENCIES)"
+      " in project mode. This is probably not what "
+      "you intended to do. Instead, please consider"
+      " using it in an install(CODE) or "
+      "install(SCRIPT) command. For example:"
+      "\n  install(CODE [["
+      "\n    file(GET_RUNTIME_DEPENDENCIES"
+      "\n      # ..."
+      "\n      )"
+      "\n    ]])");
   }
 
   struct Arguments
@@ -2776,7 +2733,7 @@
   if (argIt != unrecognizedArguments.end()) {
     std::ostringstream e;
     e << "Unrecognized argument: \"" << *argIt << "\"";
-    this->SetError(e.str());
+    status.SetError(e.str());
     cmSystemTools::SetFatalErrorOccured();
     return false;
   }
@@ -2784,13 +2741,13 @@
   if (argIt != keywordsMissingValues.end()) {
     std::ostringstream e;
     e << "Keyword missing value: " << *argIt;
-    this->SetError(e.str());
+    status.SetError(e.str());
     cmSystemTools::SetFatalErrorOccured();
     return false;
   }
 
   cmRuntimeDependencyArchive archive(
-    this, parsedArgs.Directories, parsedArgs.BundleExecutable,
+    status, parsedArgs.Directories, parsedArgs.BundleExecutable,
     parsedArgs.PreIncludeRegexes, parsedArgs.PreExcludeRegexes,
     parsedArgs.PostIncludeRegexes, parsedArgs.PostExcludeRegexes);
   if (!archive.Prepare()) {
@@ -2826,14 +2783,14 @@
       std::string varName =
         parsedArgs.ConflictingDependenciesPrefix + "_" + val.first;
       std::string pathsStr = cmJoin(paths, ";");
-      this->Makefile->AddDefinition(varName, pathsStr);
+      status.GetMakefile().AddDefinition(varName, pathsStr);
     } else {
       std::ostringstream e;
       e << "Multiple conflicting paths found for " << val.first << ":";
       for (auto const& path : val.second) {
         e << "\n  " << path;
       }
-      this->SetError(e.str());
+      status.SetError(e.str());
       cmSystemTools::SetFatalErrorOccured();
       return false;
     }
@@ -2848,7 +2805,7 @@
       assert(it != archive.GetUnresolvedPaths().end());
       std::ostringstream e;
       e << "Could not resolve file " << *it;
-      this->SetError(e.str());
+      status.SetError(e.str());
       cmSystemTools::SetFatalErrorOccured();
       return false;
     }
@@ -2856,16 +2813,76 @@
 
   if (!parsedArgs.ResolvedDependenciesVar.empty()) {
     std::string val = cmJoin(deps, ";");
-    this->Makefile->AddDefinition(parsedArgs.ResolvedDependenciesVar, val);
+    status.GetMakefile().AddDefinition(parsedArgs.ResolvedDependenciesVar,
+                                       val);
   }
   if (!parsedArgs.UnresolvedDependenciesVar.empty()) {
     std::string val = cmJoin(unresolvedDeps, ";");
-    this->Makefile->AddDefinition(parsedArgs.UnresolvedDependenciesVar, val);
+    status.GetMakefile().AddDefinition(parsedArgs.UnresolvedDependenciesVar,
+                                       val);
   }
   if (!parsedArgs.ConflictingDependenciesPrefix.empty()) {
     std::string val = cmJoin(conflictingDeps, ";");
-    this->Makefile->AddDefinition(
+    status.GetMakefile().AddDefinition(
       parsedArgs.ConflictingDependenciesPrefix + "_FILENAMES", val);
   }
   return true;
 }
+
+} // namespace
+
+bool cmFileCommand(std::vector<std::string> const& args,
+                   cmExecutionStatus& status)
+{
+  if (args.size() < 2) {
+    status.SetError("must be called with at least two arguments.");
+    return false;
+  }
+
+  static cmSubcommandTable const subcommand{
+    { "WRITE"_s, HandleWriteCommand },
+    { "APPEND"_s, HandleAppendCommand },
+    { "DOWNLOAD"_s, HandleDownloadCommand },
+    { "UPLOAD"_s, HandleUploadCommand },
+    { "READ"_s, HandleReadCommand },
+    { "MD5"_s, HandleHashCommand },
+    { "SHA1"_s, HandleHashCommand },
+    { "SHA224"_s, HandleHashCommand },
+    { "SHA256"_s, HandleHashCommand },
+    { "SHA384"_s, HandleHashCommand },
+    { "SHA512"_s, HandleHashCommand },
+    { "SHA3_224"_s, HandleHashCommand },
+    { "SHA3_256"_s, HandleHashCommand },
+    { "SHA3_384"_s, HandleHashCommand },
+    { "SHA3_512"_s, HandleHashCommand },
+    { "STRINGS"_s, HandleStringsCommand },
+    { "GLOB"_s, HandleGlobCommand },
+    { "GLOB_RECURSE"_s, HandleGlobRecurseCommand },
+    { "MAKE_DIRECTORY"_s, HandleMakeDirectoryCommand },
+    { "RENAME"_s, HandleRename },
+    { "REMOVE"_s, HandleRemove },
+    { "REMOVE_RECURSE"_s, HandleRemoveRecurse },
+    { "COPY"_s, HandleCopyCommand },
+    { "INSTALL"_s, HandleInstallCommand },
+    { "DIFFERENT"_s, HandleDifferentCommand },
+    { "RPATH_CHANGE"_s, HandleRPathChangeCommand },
+    { "CHRPATH"_s, HandleRPathChangeCommand },
+    { "RPATH_CHECK"_s, HandleRPathCheckCommand },
+    { "RPATH_REMOVE"_s, HandleRPathRemoveCommand },
+    { "READ_ELF"_s, HandleReadElfCommand },
+    { "RELATIVE_PATH"_s, HandleRelativePathCommand },
+    { "TO_CMAKE_PATH"_s, HandleCMakePathCommand },
+    { "TO_NATIVE_PATH"_s, HandleNativePathCommand },
+    { "TOUCH"_s, HandleTouchCommand },
+    { "TOUCH_NOCREATE"_s, HandleTouchNocreateCommand },
+    { "TIMESTAMP"_s, HandleTimestampCommand },
+    { "GENERATE"_s, HandleGenerateCommand },
+    { "LOCK"_s, HandleLockCommand },
+    { "SIZE"_s, HandleSizeCommand },
+    { "READ_SYMLINK"_s, HandleReadSymlinkCommand },
+    { "CREATE_LINK"_s, HandleCreateLinkCommand },
+    { "GET_RUNTIME_DEPENDENCIES"_s, HandleGetRuntimeDependenciesCommand },
+  };
+
+  return subcommand(args[0], args, status);
+}
diff --git a/Source/cmFileCommand.h b/Source/cmFileCommand.h
index d4b980e..8c9b219 100644
--- a/Source/cmFileCommand.h
+++ b/Source/cmFileCommand.h
@@ -8,72 +8,9 @@
 #include <string>
 #include <vector>
 
-#include "cm_memory.hxx"
-
-#include "cmCommand.h"
-
 class cmExecutionStatus;
 
-/** \class cmFileCommand
- * \brief Command for manipulation of files
- *
- */
-class cmFileCommand : public cmCommand
-{
-public:
-  /**
-   * This is a virtual constructor for the command.
-   */
-  std::unique_ptr<cmCommand> Clone() override
-  {
-    return cm::make_unique<cmFileCommand>();
-  }
-
-  /**
-   * This is called when the command is first encountered in
-   * the CMakeLists.txt file.
-   */
-  bool InitialPass(std::vector<std::string> const& args,
-                   cmExecutionStatus& status) override;
-
-protected:
-  bool HandleRename(std::vector<std::string> const& args);
-  bool HandleRemove(std::vector<std::string> const& args, bool recurse);
-  bool HandleWriteCommand(std::vector<std::string> const& args, bool append);
-  bool HandleReadCommand(std::vector<std::string> const& args);
-  bool HandleHashCommand(std::vector<std::string> const& args);
-  bool HandleStringsCommand(std::vector<std::string> const& args);
-  bool HandleGlobCommand(std::vector<std::string> const& args, bool recurse);
-  bool HandleTouchCommand(std::vector<std::string> const& args, bool create);
-  bool HandleMakeDirectoryCommand(std::vector<std::string> const& args);
-
-  bool HandleRelativePathCommand(std::vector<std::string> const& args);
-  bool HandleCMakePathCommand(std::vector<std::string> const& args,
-                              bool nativePath);
-  bool HandleReadElfCommand(std::vector<std::string> const& args);
-  bool HandleRPathChangeCommand(std::vector<std::string> const& args);
-  bool HandleRPathCheckCommand(std::vector<std::string> const& args);
-  bool HandleRPathRemoveCommand(std::vector<std::string> const& args);
-  bool HandleDifferentCommand(std::vector<std::string> const& args);
-
-  bool HandleCopyCommand(std::vector<std::string> const& args);
-  bool HandleInstallCommand(std::vector<std::string> const& args);
-  bool HandleDownloadCommand(std::vector<std::string> const& args);
-  bool HandleUploadCommand(std::vector<std::string> const& args);
-
-  bool HandleTimestampCommand(std::vector<std::string> const& args);
-  bool HandleGenerateCommand(std::vector<std::string> const& args);
-  bool HandleLockCommand(std::vector<std::string> const& args);
-  bool HandleSizeCommand(std::vector<std::string> const& args);
-  bool HandleReadSymlinkCommand(std::vector<std::string> const& args);
-  bool HandleCreateLinkCommand(std::vector<std::string> const& args);
-  bool HandleGetRuntimeDependenciesCommand(
-    std::vector<std::string> const& args);
-
-private:
-  void AddEvaluationFile(const std::string& inputName,
-                         const std::string& outputExpr,
-                         const std::string& condition, bool inputIsContent);
-};
+bool cmFileCommand(std::vector<std::string> const& args,
+                   cmExecutionStatus& status);
 
 #endif
diff --git a/Source/cmFileCopier.cxx b/Source/cmFileCopier.cxx
index 71493bb..62f132d 100644
--- a/Source/cmFileCopier.cxx
+++ b/Source/cmFileCopier.cxx
@@ -3,8 +3,8 @@
 
 #include "cmFileCopier.h"
 
+#include "cmExecutionStatus.h"
 #include "cmFSPermissions.h"
-#include "cmFileCommand.h"
 #include "cmFileTimes.h"
 #include "cmMakefile.h"
 #include "cmStringAlgorithms.h"
@@ -21,9 +21,9 @@
 
 using namespace cmFSPermissions;
 
-cmFileCopier::cmFileCopier(cmFileCommand* command, const char* name)
-  : FileCommand(command)
-  , Makefile(command->GetMakefile())
+cmFileCopier::cmFileCopier(cmExecutionStatus& status, const char* name)
+  : Status(status)
+  , Makefile(&status.GetMakefile())
   , Name(name)
   , Always(false)
   , MatchlessFiles(true)
@@ -92,7 +92,7 @@
     if (!cmSystemTools::SetPermissions(toFile, permissions)) {
       std::ostringstream e;
       e << this->Name << " cannot set permissions on \"" << toFile << "\"";
-      this->FileCommand->SetError(e.str());
+      this->Status.SetError(e.str());
       return false;
     }
   }
@@ -106,7 +106,7 @@
   if (!cmFSPermissions::stringToModeT(arg, permissions)) {
     std::ostringstream e;
     e << this->Name << " given invalid permission \"" << arg << "\".";
-    this->FileCommand->SetError(e.str());
+    this->Status.SetError(e.str());
     return false;
   }
   return true;
@@ -122,7 +122,7 @@
   // The input file does not exist and installation is not optional.
   std::ostringstream e;
   e << this->Name << " cannot find \"" << fromFile << "\".";
-  this->FileCommand->SetError(e.str());
+  this->Status.SetError(e.str());
   return false;
 }
 
@@ -130,7 +130,7 @@
 {
   std::ostringstream e;
   e << "option " << arg << " may not appear before PATTERN or REGEX.";
-  this->FileCommand->SetError(e.str());
+  this->Status.SetError(e.str());
   this->Doing = DoingError;
 }
 
@@ -138,7 +138,7 @@
 {
   std::ostringstream e;
   e << "option " << arg << " may not appear after PATTERN or REGEX.";
-  this->FileCommand->SetError(e.str());
+  this->Status.SetError(e.str());
   this->Doing = DoingError;
 }
 
@@ -175,7 +175,7 @@
     cmSystemTools::ExpandListArgument(default_dir_install_permissions, items);
     for (const auto& arg : items) {
       if (!this->CheckPermissions(arg, **mode)) {
-        this->FileCommand->SetError(
+        this->Status.SetError(
           " Set with CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS variable.");
         return false;
       }
@@ -195,7 +195,7 @@
     if (!this->CheckKeyword(args[i]) && !this->CheckValue(args[i])) {
       std::ostringstream e;
       e << "called with unknown argument \"" << args[i] << "\".";
-      this->FileCommand->SetError(e.str());
+      this->Status.SetError(e.str());
       return false;
     }
 
@@ -209,7 +209,7 @@
   if (this->Destination.empty()) {
     std::ostringstream e;
     e << this->Name << " given no DESTINATION";
-    this->FileCommand->SetError(e.str());
+    this->Status.SetError(e.str());
     return false;
   }
 
@@ -342,7 +342,7 @@
       } else {
         std::ostringstream e;
         e << "could not compile PATTERN \"" << arg << "\".";
-        this->FileCommand->SetError(e.str());
+        this->Status.SetError(e.str());
         this->Doing = DoingError;
       }
     } break;
@@ -354,7 +354,7 @@
       } else {
         std::ostringstream e;
         e << "could not compile REGEX \"" << arg << "\".";
-        this->FileCommand->SetError(e.str());
+        this->Status.SetError(e.str());
         this->Doing = DoingError;
       }
       break;
@@ -397,8 +397,8 @@
       file += "/";
       file += f;
     } else if (!this->FilesFromDir.empty()) {
-      this->FileCommand->SetError("option FILES_FROM_DIR requires all files "
-                                  "to be specified as relative paths.");
+      this->Status.SetError("option FILES_FROM_DIR requires all files "
+                            "to be specified as relative paths.");
       return false;
     } else {
       file = f;
@@ -447,7 +447,7 @@
   if (fromFile.empty()) {
     std::ostringstream e;
     e << "INSTALL encountered an empty string input file name.";
-    this->FileCommand->SetError(e.str());
+    this->Status.SetError(e.str());
     return false;
   }
 
@@ -515,7 +515,7 @@
       if (!cmSystemTools::CreateSymlink(symlinkTarget, toFile)) {
         std::ostringstream e;
         e << this->Name << " cannot create symlink \"" << toFile << "\".";
-        this->FileCommand->SetError(e.str());
+        this->Status.SetError(e.str());
         return false;
       }
     }
@@ -536,7 +536,7 @@
     std::ostringstream e;
     e << this->Name << " cannot read symlink \"" << fromFile
       << "\" to duplicate at \"" << toFile << "\".";
-    this->FileCommand->SetError(e.str());
+    this->Status.SetError(e.str());
     return false;
   }
 
@@ -567,7 +567,7 @@
       std::ostringstream e;
       e << this->Name << " cannot duplicate symlink \"" << fromFile
         << "\" at \"" << toFile << "\".";
-      this->FileCommand->SetError(e.str());
+      this->Status.SetError(e.str());
       return false;
     }
   }
@@ -596,7 +596,7 @@
     std::ostringstream e;
     e << this->Name << " cannot copy file \"" << fromFile << "\" to \""
       << toFile << "\".";
-    this->FileCommand->SetError(e.str());
+    this->Status.SetError(e.str());
     return false;
   }
 
@@ -612,7 +612,7 @@
       std::ostringstream e;
       e << this->Name << " cannot set modification time on \"" << toFile
         << "\"";
-      this->FileCommand->SetError(e.str());
+      this->Status.SetError(e.str());
       return false;
     }
   }
@@ -649,7 +649,7 @@
     std::ostringstream e;
     e << this->Name << " cannot make directory \"" << destination
       << "\": " << cmSystemTools::GetLastSystemError();
-    this->FileCommand->SetError(e.str());
+    this->Status.SetError(e.str());
     return false;
   }
 
diff --git a/Source/cmFileCopier.h b/Source/cmFileCopier.h
index a79a60b..263a365 100644
--- a/Source/cmFileCopier.h
+++ b/Source/cmFileCopier.h
@@ -12,19 +12,19 @@
 #include <string>
 #include <vector>
 
-class cmFileCommand;
+class cmExecutionStatus;
 class cmMakefile;
 
 // File installation helper class.
 struct cmFileCopier
 {
-  cmFileCopier(cmFileCommand* command, const char* name = "COPY");
+  cmFileCopier(cmExecutionStatus& status, const char* name = "COPY");
   virtual ~cmFileCopier();
 
   bool Run(std::vector<std::string> const& args);
 
 protected:
-  cmFileCommand* FileCommand;
+  cmExecutionStatus& Status;
   cmMakefile* Makefile;
   const char* Name;
   bool Always;
diff --git a/Source/cmFileInstaller.cxx b/Source/cmFileInstaller.cxx
index 9378439..d28ef41 100644
--- a/Source/cmFileInstaller.cxx
+++ b/Source/cmFileInstaller.cxx
@@ -3,8 +3,8 @@
 
 #include "cmFileInstaller.h"
 
+#include "cmExecutionStatus.h"
 #include "cmFSPermissions.h"
-#include "cmFileCommand.h"
 #include "cmMakefile.h"
 #include "cmSystemTools.h"
 
@@ -14,8 +14,8 @@
 
 using namespace cmFSPermissions;
 
-cmFileInstaller::cmFileInstaller(cmFileCommand* command)
-  : cmFileCopier(command, "INSTALL")
+cmFileInstaller::cmFileInstaller(cmExecutionStatus& status)
+  : cmFileCopier(status, "INSTALL")
   , InstallType(cmInstallType_FILES)
   , Optional(false)
   , MessageAlways(false)
@@ -111,19 +111,19 @@
 
   if (!this->Rename.empty()) {
     if (!this->FilesFromDir.empty()) {
-      this->FileCommand->SetError("INSTALL option RENAME may not be "
-                                  "combined with FILES_FROM_DIR.");
+      this->Status.SetError("INSTALL option RENAME may not be "
+                            "combined with FILES_FROM_DIR.");
       return false;
     }
     if (this->InstallType != cmInstallType_FILES &&
         this->InstallType != cmInstallType_PROGRAMS) {
-      this->FileCommand->SetError("INSTALL option RENAME may be used "
-                                  "only with FILES or PROGRAMS.");
+      this->Status.SetError("INSTALL option RENAME may be used "
+                            "only with FILES or PROGRAMS.");
       return false;
     }
     if (this->Files.size() > 1) {
-      this->FileCommand->SetError("INSTALL option RENAME may be used "
-                                  "only with one file.");
+      this->Status.SetError("INSTALL option RENAME may be used "
+                            "only with one file.");
       return false;
     }
   }
@@ -134,9 +134,9 @@
 
   if (((this->MessageAlways ? 1 : 0) + (this->MessageLazy ? 1 : 0) +
        (this->MessageNever ? 1 : 0)) > 1) {
-    this->FileCommand->SetError("INSTALL options MESSAGE_ALWAYS, "
-                                "MESSAGE_LAZY, and MESSAGE_NEVER "
-                                "are mutually exclusive.");
+    this->Status.SetError("INSTALL options MESSAGE_ALWAYS, "
+                          "MESSAGE_LAZY, and MESSAGE_NEVER "
+                          "are mutually exclusive.");
     return false;
   }
 
@@ -213,7 +213,7 @@
     e << "INSTALL called with old-style " << arg << " argument.  "
       << "This script was generated with an older version of CMake.  "
       << "Re-run this cmake version on your build tree.";
-    this->FileCommand->SetError(e.str());
+    this->Status.SetError(e.str());
     this->Doing = DoingError;
   } else {
     return this->cmFileCopier::CheckKeyword(arg);
@@ -257,7 +257,7 @@
   } else {
     std::ostringstream e;
     e << "Option TYPE given unknown value \"" << stype << "\".";
-    this->FileCommand->SetError(e.str());
+    this->Status.SetError(e.str());
     return false;
   }
   return true;
@@ -269,8 +269,8 @@
 
   // allow for / to be a valid destination
   if (destination.size() < 2 && destination != "/") {
-    this->FileCommand->SetError("called with inappropriate arguments. "
-                                "No DESTINATION provided or .");
+    this->Status.SetError("called with inappropriate arguments. "
+                          "No DESTINATION provided or .");
     return false;
   }
 
@@ -300,7 +300,7 @@
       if (relative) {
         // This is relative path on unix or windows. Since we are doing
         // destdir, this case does not make sense.
-        this->FileCommand->SetError(
+        this->Status.SetError(
           "called with relative DESTINATION. This "
           "does not make sense when using DESTDIR. Specify "
           "absolute path or remove DESTDIR environment variable.");
@@ -315,7 +315,7 @@
           "absolute path or remove DESTDIR environment variable."
           "\nDESTINATION=\n";
         message += destination;
-        this->FileCommand->SetError(message);
+        this->Status.SetError(message);
         return false;
       }
     }
@@ -335,14 +335,14 @@
       if (!cmSystemTools::MakeDirectory(destination, default_dir_mode)) {
         std::string errstring = "cannot create directory: " + destination +
           ". Maybe need administrative privileges.";
-        this->FileCommand->SetError(errstring);
+        this->Status.SetError(errstring);
         return false;
       }
     }
     if (!cmSystemTools::FileIsDirectory(destination)) {
       std::string errstring =
         "INSTALL destination: " + destination + " is not a directory.";
-      this->FileCommand->SetError(errstring);
+      this->Status.SetError(errstring);
       return false;
     }
   }
diff --git a/Source/cmFileInstaller.h b/Source/cmFileInstaller.h
index 312529a..fd883ea 100644
--- a/Source/cmFileInstaller.h
+++ b/Source/cmFileInstaller.h
@@ -12,11 +12,11 @@
 #include <string>
 #include <vector>
 
-class cmFileCommand;
+class cmExecutionStatus;
 
 struct cmFileInstaller : public cmFileCopier
 {
-  cmFileInstaller(cmFileCommand* command);
+  cmFileInstaller(cmExecutionStatus& status);
   ~cmFileInstaller() override;
 
 protected:
diff --git a/Source/cmRuntimeDependencyArchive.cxx b/Source/cmRuntimeDependencyArchive.cxx
index 45aff69..ca85287 100644
--- a/Source/cmRuntimeDependencyArchive.cxx
+++ b/Source/cmRuntimeDependencyArchive.cxx
@@ -6,7 +6,7 @@
 #include "cmBinUtilsLinuxELFLinker.h"
 #include "cmBinUtilsMacOSMachOLinker.h"
 #include "cmBinUtilsWindowsPELinker.h"
-#include "cmCommand.h"
+#include "cmExecutionStatus.h"
 #include "cmMakefile.h"
 #include "cmStateTypes.h"
 #include "cmSystemTools.h"
@@ -108,13 +108,13 @@
 }
 
 cmRuntimeDependencyArchive::cmRuntimeDependencyArchive(
-  cmCommand* command, std::vector<std::string> searchDirectories,
+  cmExecutionStatus& status, std::vector<std::string> searchDirectories,
   std::string bundleExecutable,
   const std::vector<std::string>& preIncludeRegexes,
   const std::vector<std::string>& preExcludeRegexes,
   const std::vector<std::string>& postIncludeRegexes,
   const std::vector<std::string>& postExcludeRegexes)
-  : Command(command)
+  : Status(status)
   , SearchDirectories(std::move(searchDirectories))
   , BundleExecutable(std::move(bundleExecutable))
   , PreIncludeRegexes(preIncludeRegexes.size())
@@ -190,7 +190,7 @@
 
 void cmRuntimeDependencyArchive::SetError(const std::string& e)
 {
-  this->Command->SetError(e);
+  this->Status.SetError(e);
 }
 
 std::string cmRuntimeDependencyArchive::GetBundleExecutable()
@@ -361,7 +361,7 @@
 
 cmMakefile* cmRuntimeDependencyArchive::GetMakefile()
 {
-  return this->Command->GetMakefile();
+  return &this->Status.GetMakefile();
 }
 
 const std::map<std::string, std::set<std::string>>&
diff --git a/Source/cmRuntimeDependencyArchive.h b/Source/cmRuntimeDependencyArchive.h
index 67efec7..e063121 100644
--- a/Source/cmRuntimeDependencyArchive.h
+++ b/Source/cmRuntimeDependencyArchive.h
@@ -14,14 +14,14 @@
 #include <string>
 #include <vector>
 
-class cmCommand;
+class cmExecutionStatus;
 class cmMakefile;
 
 class cmRuntimeDependencyArchive
 {
 public:
   explicit cmRuntimeDependencyArchive(
-    cmCommand* command, std::vector<std::string> searchDirectories,
+    cmExecutionStatus& status, std::vector<std::string> searchDirectories,
     std::string bundleExecutable,
     const std::vector<std::string>& preIncludeRegexes,
     const std::vector<std::string>& preExcludeRegexes,
@@ -51,7 +51,7 @@
   const std::set<std::string>& GetUnresolvedPaths();
 
 private:
-  cmCommand* Command;
+  cmExecutionStatus& Status;
   std::unique_ptr<cmBinUtilsLinker> Linker;
 
   std::string GetRuntimeDependenciesTool;
diff --git a/Source/cmSubcommandTable.cxx b/Source/cmSubcommandTable.cxx
new file mode 100644
index 0000000..f6194f8
--- /dev/null
+++ b/Source/cmSubcommandTable.cxx
@@ -0,0 +1,31 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmSubcommandTable.h"
+
+#include <algorithm>
+
+#include "cmExecutionStatus.h"
+#include "cmStringAlgorithms.h"
+
+cmSubcommandTable::cmSubcommandTable(std::initializer_list<InitElem> init)
+  : Impl(init.begin(), init.end())
+{
+  std::sort(this->Impl.begin(), this->Impl.end(),
+            [](Elem const& left, Elem const& right) {
+              return left.first < right.first;
+            });
+}
+
+bool cmSubcommandTable::operator()(cm::string_view key,
+                                   std::vector<std::string> const& args,
+                                   cmExecutionStatus& status) const
+{
+  auto const it = std::lower_bound(
+    this->Impl.begin(), this->Impl.end(), key,
+    [](Elem const& elem, cm::string_view k) { return elem.first < k; });
+  if (it != this->Impl.end() && it->first == key) {
+    return it->second(args, status);
+  }
+  status.SetError(cmStrCat("does not recognize sub-command ", key));
+  return false;
+}
diff --git a/Source/cmSubcommandTable.h b/Source/cmSubcommandTable.h
new file mode 100644
index 0000000..21342bb
--- /dev/null
+++ b/Source/cmSubcommandTable.h
@@ -0,0 +1,36 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#ifndef cmSubcommandTable_h
+#define cmSubcommandTable_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cm_static_string_view.hxx"
+#include "cm_string_view.hxx"
+
+#include <initializer_list>
+#include <string>
+#include <utility>
+#include <vector>
+
+class cmExecutionStatus;
+
+class cmSubcommandTable
+{
+public:
+  using Command = bool (*)(std::vector<std::string> const&,
+                           cmExecutionStatus&);
+
+  using Elem = std::pair<cm::string_view, Command>;
+  using InitElem = std::pair<cm::static_string_view, Command>;
+
+  cmSubcommandTable(std::initializer_list<InitElem> init);
+
+  bool operator()(cm::string_view key, std::vector<std::string> const& args,
+                  cmExecutionStatus& status) const;
+
+private:
+  std::vector<Elem> Impl;
+};
+
+#endif
diff --git a/bootstrap b/bootstrap
index 06e11b5..c97a5d4 100755
--- a/bootstrap
+++ b/bootstrap
@@ -426,6 +426,7 @@
   cmStringAlgorithms \
   cmStringReplaceHelper \
   cmStringCommand \
+  cmSubcommandTable \
   cmSubdirCommand \
   cmSystemTools \
   cmTarget \