Imported Upstream version 3.25.0
[platform/upstream/cmake.git] / Source / cmStringCommand.cxx
index 68ba13f..c12d1fe 100644 (file)
-/*============================================================================
-  CMake - Cross Platform Makefile Generator
-  Copyright 2000-2009 Kitware, Inc., Insight Software Consortium
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+// NOLINTNEXTLINE(bugprone-reserved-identifier)
+#define _SCL_SECURE_NO_WARNINGS
 
-  Distributed under the OSI-approved BSD License (the "License");
-  see accompanying file Copyright.txt for details.
-
-  This software is distributed WITHOUT ANY WARRANTY; without even the
-  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-  See the License for more information.
-============================================================================*/
 #include "cmStringCommand.h"
-#include "cmCryptoHash.h"
 
-#include <cmsys/RegularExpression.hxx>
-#include <cmsys/SystemTools.hxx>
+#include <algorithm>
+#include <cctype>
+#include <cstdio>
+#include <cstdlib>
+#include <initializer_list>
+#include <limits>
+#include <memory>
+#include <stdexcept>
+#include <utility>
 
-#include <stdlib.h> // required for atoi
-#include <ctype.h>
-#include <time.h>
+#include <cm/iterator>
+#include <cm/optional>
+#include <cm/string_view>
+#include <cmext/string_view>
 
-#include <cmTimestamp.h>
+#include <cm3p/json/reader.h>
+#include <cm3p/json/value.h>
+#include <cm3p/json/writer.h>
 
-//----------------------------------------------------------------------------
-bool cmStringCommand
-::InitialPass(std::vector<std::string> const& args, cmExecutionStatus &)
-{
-  if(args.size() < 1)
-    {
-    this->SetError("must be called with at least one argument.");
-    return false;
-    }
+#include "cmsys/RegularExpression.hxx"
 
-  const std::string &subCommand = args[0];
-  if(subCommand == "REGEX")
-    {
-    return this->HandleRegexCommand(args);
-    }
-  else if(subCommand == "REPLACE")
-    {
-    return this->HandleReplaceCommand(args);
-    }
-  else if ( subCommand == "MD5" ||
-            subCommand == "SHA1" ||
-            subCommand == "SHA224" ||
-            subCommand == "SHA256" ||
-            subCommand == "SHA384" ||
-            subCommand == "SHA512" )
-    {
-    return this->HandleHashCommand(args);
-    }
-  else if(subCommand == "TOLOWER")
-    {
-    return this->HandleToUpperLowerCommand(args, false);
-    }
-  else if(subCommand == "TOUPPER")
-    {
-    return this->HandleToUpperLowerCommand(args, true);
-    }
-  else if(subCommand == "COMPARE")
-    {
-    return this->HandleCompareCommand(args);
-    }
-  else if(subCommand == "ASCII")
-    {
-    return this->HandleAsciiCommand(args);
-    }
-  else if(subCommand == "CONFIGURE")
-    {
-    return this->HandleConfigureCommand(args);
-    }
-  else if(subCommand == "LENGTH")
-    {
-    return this->HandleLengthCommand(args);
-    }
-  else if(subCommand == "SUBSTRING")
-    {
-    return this->HandleSubstringCommand(args);
-    }
-  else if(subCommand == "STRIP")
-    {
-    return this->HandleStripCommand(args);
-    }
-  else if(subCommand == "RANDOM")
-    {
-    return this->HandleRandomCommand(args);
-    }
-  else if(subCommand == "FIND")
-    {
-    return this->HandleFindCommand(args);
-    }
-  else if(subCommand == "TIMESTAMP")
-    {
-    return this->HandleTimestampCommand(args);
-    }
-  else if(subCommand == "MAKE_C_IDENTIFIER")
-    {
-    return this->HandleMakeCIdentifierCommand(args);
-    }
-
-  std::string e = "does not recognize sub-command "+subCommand;
-  this->SetError(e.c_str());
-  return false;
-}
-
-//----------------------------------------------------------------------------
-bool cmStringCommand::HandleHashCommand(std::vector<std::string> const& args)
+#include "cmCryptoHash.h"
+#include "cmExecutionStatus.h"
+#include "cmGeneratorExpression.h"
+#include "cmMakefile.h"
+#include "cmMessageType.h"
+#include "cmRange.h"
+#include "cmStringAlgorithms.h"
+#include "cmStringReplaceHelper.h"
+#include "cmSubcommandTable.h"
+#include "cmSystemTools.h"
+#include "cmTimestamp.h"
+#include "cmUuid.h"
+#include "cmValue.h"
+
+namespace {
+
+bool RegexMatch(std::vector<std::string> const& args,
+                cmExecutionStatus& status);
+bool RegexMatchAll(std::vector<std::string> const& args,
+                   cmExecutionStatus& status);
+bool RegexReplace(std::vector<std::string> const& args,
+                  cmExecutionStatus& status);
+
+bool joinImpl(std::vector<std::string> const& args, std::string const& glue,
+              size_t varIdx, cmMakefile& makefile);
+
+bool HandleHashCommand(std::vector<std::string> const& args,
+                       cmExecutionStatus& status)
 {
-#if defined(CMAKE_BUILD_WITH_CMAKE)
-  if(args.size() != 3)
-    {
-    cmOStringStream e;
-    e << args[0] << " requires an output variable and an input string";
-    this->SetError(e.str().c_str());
+  if (args.size() != 3) {
+    status.SetError(
+      cmStrCat(args[0], " requires an output variable and an input string"));
     return false;
-    }
+  }
 
-  cmsys::auto_ptr<cmCryptoHash> hash(cmCryptoHash::New(args[0].c_str()));
-  if(hash.get())
-    {
-    std::string out = hash->HashString(args[2].c_str());
-    this->Makefile->AddDefinition(args[1].c_str(), out.c_str());
+  std::unique_ptr<cmCryptoHash> hash(cmCryptoHash::New(args[0]));
+  if (hash) {
+    std::string out = hash->HashString(args[2]);
+    status.GetMakefile().AddDefinition(args[1], out);
     return true;
-    }
-  return false;
-#else
-  cmOStringStream e;
-  e << args[0] << " not available during bootstrap";
-  this->SetError(e.str().c_str());
+  }
   return false;
-#endif
 }
 
-//----------------------------------------------------------------------------
-bool cmStringCommand::HandleToUpperLowerCommand(
-  std::vector<std::string> const& args, bool toUpper)
+bool HandleToUpperLowerCommand(std::vector<std::string> const& args,
+                               bool toUpper, cmExecutionStatus& status)
 {
-  if ( args.size() < 3 )
-    {
-    this->SetError("no output variable specified");
+  if (args.size() < 3) {
+    status.SetError("no output variable specified");
     return false;
-    }
+  }
 
-  std::string outvar = args[2];
+  std::string const& outvar = args[2];
   std::string output;
 
-  if (toUpper)
-    {
+  if (toUpper) {
     output = cmSystemTools::UpperCase(args[1]);
-    }
-  else
-    {
+  } else {
     output = cmSystemTools::LowerCase(args[1]);
-    }
+  }
 
   // Store the output in the provided variable.
-  this->Makefile->AddDefinition(outvar.c_str(), output.c_str());
+  status.GetMakefile().AddDefinition(outvar, output);
   return true;
 }
 
-//----------------------------------------------------------------------------
-bool cmStringCommand::HandleAsciiCommand(std::vector<std::string> const& args)
+bool HandleToUpperCommand(std::vector<std::string> const& args,
+                          cmExecutionStatus& status)
 {
-  if ( args.size() < 3 )
-    {
-    this->SetError("No output variable specified");
+  return HandleToUpperLowerCommand(args, true, status);
+}
+
+bool HandleToLowerCommand(std::vector<std::string> const& args,
+                          cmExecutionStatus& status)
+{
+  return HandleToUpperLowerCommand(args, false, status);
+}
+
+bool HandleAsciiCommand(std::vector<std::string> const& args,
+                        cmExecutionStatus& status)
+{
+  if (args.size() < 3) {
+    status.SetError("No output variable specified");
     return false;
-    }
+  }
   std::string::size_type cc;
-  std::string outvar = args[args.size()-1];
-  std::string output = "";
-  for ( cc = 1; cc < args.size()-1; cc ++ )
-    {
+  std::string const& outvar = args.back();
+  std::string output;
+  for (cc = 1; cc < args.size() - 1; cc++) {
     int ch = atoi(args[cc].c_str());
-    if ( ch > 0 && ch < 256 )
-      {
+    if (ch > 0 && ch < 256) {
       output += static_cast<char>(ch);
-      }
-    else
-      {
-      std::string error = "Character with code ";
-      error += args[cc];
-      error += " does not exist.";
-      this->SetError(error.c_str());
+    } else {
+      std::string error =
+        cmStrCat("Character with code ", args[cc], " does not exist.");
+      status.SetError(error);
       return false;
-      }
     }
+  }
   // Store the output in the provided variable.
-  this->Makefile->AddDefinition(outvar.c_str(), output.c_str());
+  status.GetMakefile().AddDefinition(outvar, output);
   return true;
 }
 
-//----------------------------------------------------------------------------
-bool cmStringCommand::HandleConfigureCommand(
-  std::vector<std::string> const& args)
+bool HandleHexCommand(std::vector<std::string> const& args,
+                      cmExecutionStatus& status)
 {
-  if ( args.size() < 2 )
-    {
-    this->SetError("No input string specified.");
+  if (args.size() != 3) {
+    status.SetError("Incorrect number of arguments");
     return false;
-    }
-  else if ( args.size() < 3 )
-    {
-    this->SetError("No output variable specified.");
+  }
+  auto const& instr = args[1];
+  auto const& outvar = args[2];
+  std::string output(instr.size() * 2, ' ');
+
+  std::string::size_type hexIndex = 0;
+  for (auto const& c : instr) {
+    snprintf(&output[hexIndex], 3, "%.2x",
+             static_cast<unsigned char>(c) & 0xFF);
+    hexIndex += 2;
+  }
+
+  status.GetMakefile().AddDefinition(outvar, output);
+  return true;
+}
+
+bool HandleConfigureCommand(std::vector<std::string> const& args,
+                            cmExecutionStatus& status)
+{
+  if (args.size() < 2) {
+    status.SetError("No input string specified.");
     return false;
-    }
+  }
+  if (args.size() < 3) {
+    status.SetError("No output variable specified.");
+    return false;
+  }
 
   // Parse options.
   bool escapeQuotes = false;
   bool atOnly = false;
-  for(unsigned int i = 3; i < args.size(); ++i)
-    {
-    if(args[i] == "@ONLY")
-      {
+  for (unsigned int i = 3; i < args.size(); ++i) {
+    if (args[i] == "@ONLY") {
       atOnly = true;
-      }
-    else if(args[i] == "ESCAPE_QUOTES")
-      {
+    } else if (args[i] == "ESCAPE_QUOTES") {
       escapeQuotes = true;
-      }
-    else
-      {
-      cmOStringStream err;
-      err << "Unrecognized argument \"" << args[i] << "\"";
-      this->SetError(err.str().c_str());
+    } else {
+      status.SetError(cmStrCat("Unrecognized argument \"", args[i], "\""));
       return false;
-      }
     }
+  }
 
   // Configure the string.
   std::string output;
-  this->Makefile->ConfigureString(args[1], output, atOnly, escapeQuotes);
+  status.GetMakefile().ConfigureString(args[1], output, atOnly, escapeQuotes);
 
   // Store the output in the provided variable.
-  this->Makefile->AddDefinition(args[2].c_str(), output.c_str());
+  status.GetMakefile().AddDefinition(args[2], output);
 
   return true;
 }
 
-//----------------------------------------------------------------------------
-bool cmStringCommand::HandleRegexCommand(std::vector<std::string> const& args)
+bool HandleRegexCommand(std::vector<std::string> const& args,
+                        cmExecutionStatus& status)
 {
-  if(args.size() < 2)
-    {
-    this->SetError("sub-command REGEX requires a mode to be specified.");
+  if (args.size() < 2) {
+    status.SetError("sub-command REGEX requires a mode to be specified.");
     return false;
-    }
-  std::string mode = args[1];
-  if(mode == "MATCH")
-    {
-    if(args.size() < 5)
-      {
-      this->SetError("sub-command REGEX, mode MATCH needs "
-                     "at least 5 arguments total to command.");
+  }
+  std::string const& mode = args[1];
+  if (mode == "MATCH") {
+    if (args.size() < 5) {
+      status.SetError("sub-command REGEX, mode MATCH needs "
+                      "at least 5 arguments total to command.");
       return false;
-      }
-    return this->RegexMatch(args);
     }
-  else if(mode == "MATCHALL")
-    {
-    if(args.size() < 5)
-      {
-      this->SetError("sub-command REGEX, mode MATCHALL needs "
-                     "at least 5 arguments total to command.");
+    return RegexMatch(args, status);
+  }
+  if (mode == "MATCHALL") {
+    if (args.size() < 5) {
+      status.SetError("sub-command REGEX, mode MATCHALL needs "
+                      "at least 5 arguments total to command.");
       return false;
-      }
-    return this->RegexMatchAll(args);
     }
-  else if(mode == "REPLACE")
-    {
-    if(args.size() < 6)
-      {
-      this->SetError("sub-command REGEX, mode REPLACE needs "
-                     "at least 6 arguments total to command.");
+    return RegexMatchAll(args, status);
+  }
+  if (mode == "REPLACE") {
+    if (args.size() < 6) {
+      status.SetError("sub-command REGEX, mode REPLACE needs "
+                      "at least 6 arguments total to command.");
       return false;
-      }
-    return this->RegexReplace(args);
     }
+    return RegexReplace(args, status);
+  }
 
-  std::string e = "sub-command REGEX does not recognize mode "+mode;
-  this->SetError(e.c_str());
+  std::string e = "sub-command REGEX does not recognize mode " + mode;
+  status.SetError(e);
   return false;
 }
 
-//----------------------------------------------------------------------------
-bool cmStringCommand::RegexMatch(std::vector<std::string> const& args)
+bool RegexMatch(std::vector<std::string> const& args,
+                cmExecutionStatus& status)
 {
   //"STRING(REGEX MATCH <regular_expression> <output variable>
   // <input> [<input>...])\n";
-  std::string regex = args[2];
-  std::string outvar = args[3];
-
-  // Concatenate all the last arguments together.
-  std::string input = args[4];
-  for(unsigned int i=5; i < args.size(); ++i)
-    {
-    input += args[i];
-    }
+  std::string const& regex = args[2];
+  std::string const& outvar = args[3];
 
-  this->ClearMatches(this->Makefile);
+  status.GetMakefile().ClearMatches();
   // Compile the regular expression.
   cmsys::RegularExpression re;
-  if(!re.compile(regex.c_str()))
-    {
+  if (!re.compile(regex)) {
     std::string e =
-      "sub-command REGEX, mode MATCH failed to compile regex \""+regex+"\".";
-    this->SetError(e.c_str());
+      "sub-command REGEX, mode MATCH failed to compile regex \"" + regex +
+      "\".";
+    status.SetError(e);
     return false;
-    }
+  }
+
+  // Concatenate all the last arguments together.
+  std::string input = cmJoin(cmMakeRange(args).advance(4), std::string());
 
   // Scan through the input for all matches.
   std::string output;
-  if(re.find(input.c_str()))
-    {
-    this->StoreMatches(this->Makefile, re);
+  if (re.find(input)) {
+    status.GetMakefile().StoreMatches(re);
     std::string::size_type l = re.start();
     std::string::size_type r = re.end();
-    if(r-l == 0)
-      {
-      std::string e =
-        "sub-command REGEX, mode MATCH regex \""+regex+
+    if (r - l == 0) {
+      std::string e = "sub-command REGEX, mode MATCH regex \"" + regex +
         "\" matched an empty string.";
-      this->SetError(e.c_str());
+      status.SetError(e);
       return false;
-      }
-    output = input.substr(l, r-l);
     }
+    output = input.substr(l, r - l);
+  }
 
   // Store the output in the provided variable.
-  this->Makefile->AddDefinition(outvar.c_str(), output.c_str());
+  status.GetMakefile().AddDefinition(outvar, output);
   return true;
 }
 
-//----------------------------------------------------------------------------
-bool cmStringCommand::RegexMatchAll(std::vector<std::string> const& args)
+bool RegexMatchAll(std::vector<std::string> const& args,
+                   cmExecutionStatus& status)
 {
   //"STRING(REGEX MATCHALL <regular_expression> <output variable> <input>
   // [<input>...])\n";
-  std::string regex = args[2];
-  std::string outvar = args[3];
+  std::string const& regex = args[2];
+  std::string const& outvar = args[3];
 
-  // Concatenate all the last arguments together.
-  std::string input = args[4];
-  for(unsigned int i=5; i < args.size(); ++i)
-    {
-    input += args[i];
-    }
-
-  this->ClearMatches(this->Makefile);
+  status.GetMakefile().ClearMatches();
   // Compile the regular expression.
   cmsys::RegularExpression re;
-  if(!re.compile(regex.c_str()))
-    {
+  if (!re.compile(regex)) {
     std::string e =
-      "sub-command REGEX, mode MATCHALL failed to compile regex \""+
-      regex+"\".";
-    this->SetError(e.c_str());
+      "sub-command REGEX, mode MATCHALL failed to compile regex \"" + regex +
+      "\".";
+    status.SetError(e);
     return false;
-    }
+  }
+
+  // Concatenate all the last arguments together.
+  std::string input = cmJoin(cmMakeRange(args).advance(4), std::string());
 
   // Scan through the input for all matches.
   std::string output;
   const char* p = input.c_str();
-  while(re.find(p))
-    {
-    this->StoreMatches(this->Makefile, re);
+  while (re.find(p)) {
+    status.GetMakefile().ClearMatches();
+    status.GetMakefile().StoreMatches(re);
     std::string::size_type l = re.start();
     std::string::size_type r = re.end();
-    if(r-l == 0)
-      {
-      std::string e = "sub-command REGEX, mode MATCHALL regex \""+
-        regex+"\" matched an empty string.";
-      this->SetError(e.c_str());
+    if (r - l == 0) {
+      std::string e = "sub-command REGEX, mode MATCHALL regex \"" + regex +
+        "\" matched an empty string.";
+      status.SetError(e);
       return false;
-      }
-    if(output.length() > 0)
-      {
+    }
+    if (!output.empty()) {
       output += ";";
-      }
-    output += std::string(p+l, r-l);
-    p += r;
     }
+    output += std::string(p + l, r - l);
+    p += r;
+  }
 
   // Store the output in the provided variable.
-  this->Makefile->AddDefinition(outvar.c_str(), output.c_str());
+  status.GetMakefile().AddDefinition(outvar, output);
   return true;
 }
 
-//----------------------------------------------------------------------------
-bool cmStringCommand::RegexReplace(std::vector<std::string> const& args)
+bool RegexReplace(std::vector<std::string> const& args,
+                  cmExecutionStatus& status)
 {
   //"STRING(REGEX REPLACE <regular_expression> <replace_expression>
   // <output variable> <input> [<input>...])\n"
-  std::string regex = args[2];
-  std::string replace = args[3];
-  std::string outvar = args[4];
-
-  // Pull apart the replace expression to find the escaped [0-9] values.
-  std::vector<RegexReplacement> replacement;
-  std::string::size_type l = 0;
-  while(l < replace.length())
-    {
-    std::string::size_type r = replace.find("\\", l);
-    if(r == std::string::npos)
-      {
-      r = replace.length();
-      replacement.push_back(replace.substr(l, r-l));
-      }
-    else
-      {
-      if(r-l > 0)
-        {
-        replacement.push_back(replace.substr(l, r-l));
-        }
-      if(r == (replace.length()-1))
-        {
-        this->SetError("sub-command REGEX, mode REPLACE: "
-                       "replace-expression ends in a backslash.");
-        return false;
-        }
-      if((replace[r+1] >= '0') && (replace[r+1] <= '9'))
-        {
-        replacement.push_back(replace[r+1]-'0');
-        }
-      else if(replace[r+1] == 'n')
-        {
-        replacement.push_back("\n");
-        }
-      else if(replace[r+1] == '\\')
-        {
-        replacement.push_back("\\");
-        }
-      else
-        {
-        std::string e = "sub-command REGEX, mode REPLACE: Unknown escape \"";
-        e += replace.substr(r, 2);
-        e += "\" in replace-expression.";
-        this->SetError(e.c_str());
-        return false;
-        }
-      r += 2;
-      }
-    l = r;
-    }
+  std::string const& regex = args[2];
+  std::string const& replace = args[3];
+  std::string const& outvar = args[4];
+  cmStringReplaceHelper replaceHelper(regex, replace, &status.GetMakefile());
+
+  if (!replaceHelper.IsReplaceExpressionValid()) {
+    status.SetError(
+      "sub-command REGEX, mode REPLACE: " + replaceHelper.GetError() + ".");
+    return false;
+  }
 
-  // Concatenate all the last arguments together.
-  std::string input = args[5];
-  for(unsigned int i=6; i < args.size(); ++i)
-    {
-    input += args[i];
-    }
+  status.GetMakefile().ClearMatches();
 
-  this->ClearMatches(this->Makefile);
-  // Compile the regular expression.
-  cmsys::RegularExpression re;
-  if(!re.compile(regex.c_str()))
-    {
+  if (!replaceHelper.IsRegularExpressionValid()) {
     std::string e =
-      "sub-command REGEX, mode REPLACE failed to compile regex \""+
-      regex+"\".";
-    this->SetError(e.c_str());
+      "sub-command REGEX, mode REPLACE failed to compile regex \"" + regex +
+      "\".";
+    status.SetError(e);
     return false;
-    }
+  }
 
-  // Scan through the input for all matches.
+  // Concatenate all the last arguments together.
+  const std::string input =
+    cmJoin(cmMakeRange(args).advance(5), std::string());
   std::string output;
-  std::string::size_type base = 0;
-  while(re.find(input.c_str()+base))
-    {
-    this->StoreMatches(this->Makefile, re);
-    std::string::size_type l2 = re.start();
-    std::string::size_type r = re.end();
-
-    // Concatenate the part of the input that was not matched.
-    output += input.substr(base, l2);
-
-    // Make sure the match had some text.
-    if(r-l2 == 0)
-      {
-      std::string e = "sub-command REGEX, mode REPLACE regex \""+
-        regex+"\" matched an empty string.";
-      this->SetError(e.c_str());
-      return false;
-      }
-
-    // Concatenate the replacement for the match.
-    for(unsigned int i=0; i < replacement.size(); ++i)
-      {
-      if(replacement[i].number < 0)
-        {
-        // This is just a plain-text part of the replacement.
-        output += replacement[i].value;
-        }
-      else
-        {
-        // Replace with part of the match.
-        int n = replacement[i].number;
-        std::string::size_type start = re.start(n);
-        std::string::size_type end = re.end(n);
-        std::string::size_type len = input.length()-base;
-        if((start != std::string::npos) && (end != std::string::npos) &&
-           (start <= len) && (end <= len))
-          {
-          output += input.substr(base+start, end-start);
-          }
-        else
-          {
-          std::string e =
-            "sub-command REGEX, mode REPLACE: replace expression \""+
-            replace+"\" contains an out-of-range escape for regex \""+
-            regex+"\".";
-          this->SetError(e.c_str());
-          return false;
-          }
-        }
-      }
-
-    // Move past the match.
-    base += r;
-    }
 
-  // Concatenate the text after the last match.
-  output += input.substr(base, input.length()-base);
+  if (!replaceHelper.Replace(input, output)) {
+    status.SetError(
+      "sub-command REGEX, mode REPLACE: " + replaceHelper.GetError() + ".");
+    return false;
+  }
 
   // Store the output in the provided variable.
-  this->Makefile->AddDefinition(outvar.c_str(), output.c_str());
+  status.GetMakefile().AddDefinition(outvar, output);
   return true;
 }
 
-//----------------------------------------------------------------------------
-void cmStringCommand::ClearMatches(cmMakefile* mf)
-{
-  for (unsigned int i=0; i<10; i++)
-    {
-    char name[128];
-    sprintf(name, "CMAKE_MATCH_%d", i);
-    const char* s = mf->GetDefinition(name);
-    if(s && *s != 0)
-      {
-      mf->AddDefinition(name, "");
-      mf->MarkVariableAsUsed(name);
-      }
-    }
-}
-
-//----------------------------------------------------------------------------
-void cmStringCommand::StoreMatches(cmMakefile* mf,cmsys::RegularExpression& re)
-{
-  for (unsigned int i=0; i<10; i++)
-    {
-    std::string m = re.match(i);
-    if(m.size() > 0)
-      {
-      char name[128];
-      sprintf(name, "CMAKE_MATCH_%d", i);
-      mf->AddDefinition(name, re.match(i).c_str());
-      mf->MarkVariableAsUsed(name);
-      }
-    }
-}
-
-//----------------------------------------------------------------------------
-bool cmStringCommand::HandleFindCommand(std::vector<std::string> const&
-                                           args)
+bool HandleFindCommand(std::vector<std::string> const& args,
+                       cmExecutionStatus& status)
 {
   // check if all required parameters were passed
-  if(args.size() < 4 || args.size() > 5)
-    {
-    this->SetError("sub-command FIND requires 3 or 4 parameters.");
+  if (args.size() < 4 || args.size() > 5) {
+    status.SetError("sub-command FIND requires 3 or 4 parameters.");
     return false;
-    }
+  }
 
   // check if the reverse flag was set or not
   bool reverseMode = false;
-  if(args.size() == 5 && args[4] == "REVERSE")
-    {
+  if (args.size() == 5 && args[4] == "REVERSE") {
     reverseMode = true;
-    }
+  }
 
   // if we have 5 arguments the last one must be REVERSE
-  if(args.size() == 5 && args[4] != "REVERSE")
-    {
-    this->SetError("sub-command FIND: unknown last parameter");
+  if (args.size() == 5 && args[4] != "REVERSE") {
+    status.SetError("sub-command FIND: unknown last parameter");
     return false;
-    }
+  }
 
   // local parameter names.
   const std::string& sstring = args[1];
@@ -590,130 +385,107 @@ bool cmStringCommand::HandleFindCommand(std::vector<std::string> const&
   const std::string& outvar = args[3];
 
   // ensure that the user cannot accidentally specify REVERSE as a variable
-  if(outvar == "REVERSE")
-    {
-    this->SetError("sub-command FIND does not allow to select REVERSE as "
-                   "the output variable.  "
-                   "Maybe you missed the actual output variable?");
+  if (outvar == "REVERSE") {
+    status.SetError("sub-command FIND does not allow one to select REVERSE as "
+                    "the output variable.  "
+                    "Maybe you missed the actual output variable?");
     return false;
-    }
+  }
 
   // try to find the character and return its position
   size_t pos;
-  if(!reverseMode)
-    {
+  if (!reverseMode) {
     pos = sstring.find(schar);
-    }
-  else
-    {
+  } else {
     pos = sstring.rfind(schar);
-    }
-  if(std::string::npos != pos)
-    {
-    cmOStringStream s;
-    s << pos;
-    this->Makefile->AddDefinition(outvar.c_str(), s.str().c_str());
+  }
+  if (std::string::npos != pos) {
+    status.GetMakefile().AddDefinition(outvar, std::to_string(pos));
     return true;
-    }
+  }
 
   // the character was not found, but this is not really an error
-  this->Makefile->AddDefinition(outvar.c_str(), "-1");
+  status.GetMakefile().AddDefinition(outvar, "-1");
   return true;
 }
 
-//----------------------------------------------------------------------------
-bool cmStringCommand::HandleCompareCommand(std::vector<std::string> const&
-                                           args)
+bool HandleCompareCommand(std::vector<std::string> const& args,
+                          cmExecutionStatus& status)
 {
-  if(args.size() < 2)
-    {
-    this->SetError("sub-command COMPARE requires a mode to be specified.");
+  if (args.size() < 2) {
+    status.SetError("sub-command COMPARE requires a mode to be specified.");
     return false;
-    }
-  std::string mode = args[1];
-  if((mode == "EQUAL") || (mode == "NOTEQUAL") ||
-     (mode == "LESS") || (mode == "GREATER"))
-    {
-    if(args.size() < 5)
-      {
-      std::string e = "sub-command COMPARE, mode ";
-      e += mode;
-      e += " needs at least 5 arguments total to command.";
-      this->SetError(e.c_str());
+  }
+  std::string const& mode = args[1];
+  if ((mode == "EQUAL") || (mode == "NOTEQUAL") || (mode == "LESS") ||
+      (mode == "LESS_EQUAL") || (mode == "GREATER") ||
+      (mode == "GREATER_EQUAL")) {
+    if (args.size() < 5) {
+      std::string e =
+        cmStrCat("sub-command COMPARE, mode ", mode,
+                 " needs at least 5 arguments total to command.");
+      status.SetError(e);
       return false;
-      }
+    }
 
     const std::string& left = args[2];
     const std::string& right = args[3];
     const std::string& outvar = args[4];
     bool result;
-    if(mode == "LESS")
-      {
+    if (mode == "LESS") {
       result = (left < right);
-      }
-    else if(mode == "GREATER")
-      {
+    } else if (mode == "LESS_EQUAL") {
+      result = (left <= right);
+    } else if (mode == "GREATER") {
       result = (left > right);
-      }
-    else if(mode == "EQUAL")
-      {
+    } else if (mode == "GREATER_EQUAL") {
+      result = (left >= right);
+    } else if (mode == "EQUAL") {
       result = (left == right);
-      }
-    else // if(mode == "NOTEQUAL")
-      {
+    } else // if(mode == "NOTEQUAL")
+    {
       result = !(left == right);
-      }
-    if(result)
-      {
-      this->Makefile->AddDefinition(outvar.c_str(), "1");
-      }
-    else
-      {
-      this->Makefile->AddDefinition(outvar.c_str(), "0");
-      }
-    return true;
     }
-  std::string e = "sub-command COMPARE does not recognize mode "+mode;
-  this->SetError(e.c_str());
+    if (result) {
+      status.GetMakefile().AddDefinition(outvar, "1");
+    } else {
+      status.GetMakefile().AddDefinition(outvar, "0");
+    }
+    return true;
+  }
+  std::string e = "sub-command COMPARE does not recognize mode " + mode;
+  status.SetError(e);
   return false;
 }
 
-//----------------------------------------------------------------------------
-bool cmStringCommand::HandleReplaceCommand(std::vector<std::string> const&
-                                           args)
+bool HandleReplaceCommand(std::vector<std::string> const& args,
+                          cmExecutionStatus& status)
 {
-  if(args.size() < 5)
-    {
-    this->SetError("sub-command REPLACE requires at least four arguments.");
+  if (args.size() < 5) {
+    status.SetError("sub-command REPLACE requires at least four arguments.");
     return false;
-    }
+  }
 
   const std::string& matchExpression = args[1];
   const std::string& replaceExpression = args[2];
   const std::string& variableName = args[3];
 
-  std::string input = args[4];
-  for(unsigned int i=5; i < args.size(); ++i)
-    {
-    input += args[i];
-    }
+  std::string input = cmJoin(cmMakeRange(args).advance(4), std::string());
 
   cmsys::SystemTools::ReplaceString(input, matchExpression.c_str(),
                                     replaceExpression.c_str());
 
-  this->Makefile->AddDefinition(variableName.c_str(), input.c_str());
+  status.GetMakefile().AddDefinition(variableName, input);
   return true;
 }
 
-//----------------------------------------------------------------------------
-bool cmStringCommand::HandleSubstringCommand(std::vector<std::string> const&
-                                             args)
+bool HandleSubstringCommand(std::vector<std::string> const& args,
+                            cmExecutionStatus& status)
 {
-  if(args.size() != 5)
-    {
-    this->SetError("sub-command SUBSTRING requires four arguments.");
+  if (args.size() != 5) {
+    status.SetError("sub-command SUBSTRING requires four arguments.");
     return false;
-    }
+  }
 
   const std::string& stringValue = args[1];
   int begin = atoi(args[2].c_str());
@@ -722,77 +494,164 @@ bool cmStringCommand::HandleSubstringCommand(std::vector<std::string> const&
 
   size_t stringLength = stringValue.size();
   int intStringLength = static_cast<int>(stringLength);
-  if ( begin < 0 || begin > intStringLength )
-    {
-    cmOStringStream ostr;
-    ostr << "begin index: " << begin << " is out of range 0 - "
-         << stringLength;
-    this->SetError(ostr.str().c_str());
+  if (begin < 0 || begin > intStringLength) {
+    status.SetError(
+      cmStrCat("begin index: ", begin, " is out of range 0 - ", stringLength));
     return false;
-    }
-  int leftOverLength = intStringLength - begin;
-  if ( end < -1 || end > leftOverLength )
-    {
-    cmOStringStream ostr;
-    ostr << "end index: " << end << " is out of range -1 - "
-         << leftOverLength;
-    this->SetError(ostr.str().c_str());
+  }
+  if (end < -1) {
+    status.SetError(cmStrCat("end index: ", end, " should be -1 or greater"));
     return false;
-    }
+  }
 
-  this->Makefile->AddDefinition(variableName.c_str(),
-                                stringValue.substr(begin, end).c_str());
+  status.GetMakefile().AddDefinition(variableName,
+                                     stringValue.substr(begin, end));
   return true;
 }
 
-//----------------------------------------------------------------------------
-bool cmStringCommand
-::HandleLengthCommand(std::vector<std::string> const& args)
+bool HandleLengthCommand(std::vector<std::string> const& args,
+                         cmExecutionStatus& status)
 {
-  if(args.size() != 3)
-    {
-    this->SetError("sub-command LENGTH requires two arguments.");
+  if (args.size() != 3) {
+    status.SetError("sub-command LENGTH requires two arguments.");
     return false;
-    }
+  }
 
   const std::string& stringValue = args[1];
   const std::string& variableName = args[2];
 
   size_t length = stringValue.size();
   char buffer[1024];
-  sprintf(buffer, "%d", static_cast<int>(length));
+  snprintf(buffer, sizeof(buffer), "%d", static_cast<int>(length));
 
-  this->Makefile->AddDefinition(variableName.c_str(), buffer);
+  status.GetMakefile().AddDefinition(variableName, buffer);
   return true;
 }
 
-//----------------------------------------------------------------------------
-bool cmStringCommand
-::HandleMakeCIdentifierCommand(std::vector<std::string> const& args)
+bool HandleAppendCommand(std::vector<std::string> const& args,
+                         cmExecutionStatus& status)
 {
-  if(args.size() != 3)
-    {
-    this->SetError("sub-command MAKE_C_IDENTIFIER requires two arguments.");
+  if (args.size() < 2) {
+    status.SetError("sub-command APPEND requires at least one argument.");
     return false;
-    }
+  }
+
+  // Skip if nothing to append.
+  if (args.size() < 3) {
+    return true;
+  }
+
+  auto const& variableName = args[1];
+
+  cm::string_view oldView{ status.GetMakefile().GetSafeDefinition(
+    variableName) };
+
+  auto const newValue = cmJoin(cmMakeRange(args).advance(2), {}, oldView);
+  status.GetMakefile().AddDefinition(variableName, newValue);
+
+  return true;
+}
+
+bool HandlePrependCommand(std::vector<std::string> const& args,
+                          cmExecutionStatus& status)
+{
+  if (args.size() < 2) {
+    status.SetError("sub-command PREPEND requires at least one argument.");
+    return false;
+  }
+
+  // Skip if nothing to prepend.
+  if (args.size() < 3) {
+    return true;
+  }
+
+  const std::string& variable = args[1];
+
+  std::string value = cmJoin(cmMakeRange(args).advance(2), std::string());
+  cmValue oldValue = status.GetMakefile().GetDefinition(variable);
+  if (oldValue) {
+    value += *oldValue;
+  }
+  status.GetMakefile().AddDefinition(variable, value);
+  return true;
+}
+
+bool HandleConcatCommand(std::vector<std::string> const& args,
+                         cmExecutionStatus& status)
+{
+  if (args.size() < 2) {
+    status.SetError("sub-command CONCAT requires at least one argument.");
+    return false;
+  }
+
+  return joinImpl(args, std::string(), 1, status.GetMakefile());
+}
+
+bool HandleJoinCommand(std::vector<std::string> const& args,
+                       cmExecutionStatus& status)
+{
+  if (args.size() < 3) {
+    status.SetError("sub-command JOIN requires at least two arguments.");
+    return false;
+  }
+
+  return joinImpl(args, args[1], 2, status.GetMakefile());
+}
+
+bool joinImpl(std::vector<std::string> const& args, std::string const& glue,
+              const size_t varIdx, cmMakefile& makefile)
+{
+  std::string const& variableName = args[varIdx];
+  // NOTE Items to concat/join placed right after the variable for
+  // both `CONCAT` and `JOIN` sub-commands.
+  std::string value = cmJoin(cmMakeRange(args).advance(varIdx + 1), glue);
+
+  makefile.AddDefinition(variableName, value);
+  return true;
+}
+
+bool HandleMakeCIdentifierCommand(std::vector<std::string> const& args,
+                                  cmExecutionStatus& status)
+{
+  if (args.size() != 3) {
+    status.SetError("sub-command MAKE_C_IDENTIFIER requires two arguments.");
+    return false;
+  }
 
   const std::string& input = args[1];
   const std::string& variableName = args[2];
 
-  this->Makefile->AddDefinition(variableName.c_str(),
-                      cmSystemTools::MakeCidentifier(input.c_str()).c_str());
+  status.GetMakefile().AddDefinition(variableName,
+                                     cmSystemTools::MakeCidentifier(input));
   return true;
 }
 
-//----------------------------------------------------------------------------
-bool cmStringCommand::HandleStripCommand(
-  std::vector<std::string> const& args)
+bool HandleGenexStripCommand(std::vector<std::string> const& args,
+                             cmExecutionStatus& status)
 {
- if(args.size() != 3)
-    {
-    this->SetError("sub-command STRIP requires two arguments.");
+  if (args.size() != 3) {
+    status.SetError("sub-command GENEX_STRIP requires two arguments.");
     return false;
-    }
+  }
+
+  const std::string& input = args[1];
+
+  std::string result = cmGeneratorExpression::Preprocess(
+    input, cmGeneratorExpression::StripAllGeneratorExpressions);
+
+  const std::string& variableName = args[2];
+
+  status.GetMakefile().AddDefinition(variableName, result);
+  return true;
+}
+
+bool HandleStripCommand(std::vector<std::string> const& args,
+                        cmExecutionStatus& status)
+{
+  if (args.size() != 3) {
+    status.SetError("sub-command STRIP requires two arguments.");
+    return false;
+  }
 
   const std::string& stringValue = args[1];
   const std::string& variableName = args[2];
@@ -801,165 +660,617 @@ bool cmStringCommand::HandleStripCommand(
   size_t endPos = 0;
   const char* ptr = stringValue.c_str();
   size_t cc;
-  for ( cc = 0; cc < inStringLength; ++ cc )
-    {
-    if ( !isspace(*ptr) )
-      {
-      if ( startPos > inStringLength )
-        {
+  for (cc = 0; cc < inStringLength; ++cc) {
+    if (!isspace(*ptr)) {
+      if (startPos > inStringLength) {
         startPos = cc;
-        }
-      endPos = cc;
       }
-    ++ ptr;
+      endPos = cc;
     }
+    ++ptr;
+  }
 
   size_t outLength = 0;
 
   // if the input string didn't contain any non-space characters, return
   // an empty string
-  if (startPos > inStringLength)
-    {
+  if (startPos > inStringLength) {
     outLength = 0;
     startPos = 0;
-    }
-  else
-    {
-    outLength=endPos - startPos + 1;
-    }
+  } else {
+    outLength = endPos - startPos + 1;
+  }
 
-  this->Makefile->AddDefinition(variableName.c_str(),
-    stringValue.substr(startPos, outLength).c_str());
+  status.GetMakefile().AddDefinition(variableName,
+                                     stringValue.substr(startPos, outLength));
   return true;
 }
 
-//----------------------------------------------------------------------------
-bool cmStringCommand
-::HandleRandomCommand(std::vector<std::string> const& args)
+bool HandleRepeatCommand(std::vector<std::string> const& args,
+                         cmExecutionStatus& status)
 {
-  if(args.size() < 2 || args.size() == 3 || args.size() == 5)
-    {
-    this->SetError("sub-command RANDOM requires at least one argument.");
+  cmMakefile& makefile = status.GetMakefile();
+
+  // `string(REPEAT "<str>" <times> OUTPUT_VARIABLE)`
+  enum ArgPos : std::size_t
+  {
+    SUB_COMMAND,
+    VALUE,
+    TIMES,
+    OUTPUT_VARIABLE,
+    TOTAL_ARGS
+  };
+
+  if (args.size() != ArgPos::TOTAL_ARGS) {
+    makefile.IssueMessage(MessageType::FATAL_ERROR,
+                          "sub-command REPEAT requires three arguments.");
+    return true;
+  }
+
+  unsigned long times;
+  if (!cmStrToULong(args[ArgPos::TIMES], &times)) {
+    makefile.IssueMessage(MessageType::FATAL_ERROR,
+                          "repeat count is not a positive number.");
+    return true;
+  }
+
+  const auto& stringValue = args[ArgPos::VALUE];
+  const auto& variableName = args[ArgPos::OUTPUT_VARIABLE];
+  const auto inStringLength = stringValue.size();
+
+  std::string result;
+  switch (inStringLength) {
+    case 0u:
+      // Nothing to do for zero length input strings
+      break;
+    case 1u:
+      // NOTE If the string to repeat consists of the only character,
+      // use the appropriate constructor.
+      result = std::string(times, stringValue[0]);
+      break;
+    default:
+      result = std::string(inStringLength * times, char{});
+      for (auto i = 0u; i < times; ++i) {
+        std::copy(cm::cbegin(stringValue), cm::cend(stringValue),
+                  &result[i * inStringLength]);
+      }
+      break;
+  }
+
+  makefile.AddDefinition(variableName, result);
+  return true;
+}
+
+bool HandleRandomCommand(std::vector<std::string> const& args,
+                         cmExecutionStatus& status)
+{
+  if (args.size() < 2 || args.size() == 3 || args.size() == 5) {
+    status.SetError("sub-command RANDOM requires at least one argument.");
     return false;
-    }
+  }
 
   static bool seeded = false;
   bool force_seed = false;
   unsigned int seed = 0;
   int length = 5;
   const char cmStringCommandDefaultAlphabet[] = "qwertyuiopasdfghjklzxcvbnm"
-    "QWERTYUIOPASDFGHJKLZXCVBNM"
-    "0123456789";
+                                                "QWERTYUIOPASDFGHJKLZXCVBNM"
+                                                "0123456789";
   std::string alphabet;
 
-  if ( args.size() > 3 )
-    {
+  if (args.size() > 3) {
     size_t i = 1;
     size_t stopAt = args.size() - 2;
 
-    for ( ; i < stopAt; ++i )
-      {
-      if ( args[i] == "LENGTH" )
-        {
+    for (; i < stopAt; ++i) {
+      if (args[i] == "LENGTH") {
         ++i;
         length = atoi(args[i].c_str());
-        }
-      else if ( args[i] == "ALPHABET" )
-        {
+      } else if (args[i] == "ALPHABET") {
         ++i;
         alphabet = args[i];
-        }
-      else if ( args[i] == "RANDOM_SEED" )
-        {
+      } else if (args[i] == "RANDOM_SEED") {
         ++i;
         seed = static_cast<unsigned int>(atoi(args[i].c_str()));
         force_seed = true;
-        }
       }
     }
-  if ( !alphabet.size() )
-    {
+  }
+  if (alphabet.empty()) {
     alphabet = cmStringCommandDefaultAlphabet;
-    }
+  }
 
   double sizeofAlphabet = static_cast<double>(alphabet.size());
-  if ( sizeofAlphabet < 1 )
-    {
-    this->SetError("sub-command RANDOM invoked with bad alphabet.");
+  if (sizeofAlphabet < 1) {
+    status.SetError("sub-command RANDOM invoked with bad alphabet.");
     return false;
-    }
-  if ( length < 1 )
-    {
-    this->SetError("sub-command RANDOM invoked with bad length.");
+  }
+  if (length < 1) {
+    status.SetError("sub-command RANDOM invoked with bad length.");
     return false;
-    }
-  const std::string& variableName = args[args.size()-1];
+  }
+  const std::string& variableName = args.back();
 
   std::vector<char> result;
 
-  if (!seeded || force_seed)
-    {
+  if (!seeded || force_seed) {
     seeded = true;
-    srand(force_seed? seed : cmSystemTools::RandomSeed());
-    }
+    srand(force_seed ? seed : cmSystemTools::RandomSeed());
+  }
 
   const char* alphaPtr = alphabet.c_str();
-  int cc;
-  for ( cc = 0; cc < length; cc ++ )
-    {
-    int idx=(int) (sizeofAlphabet* rand()/(RAND_MAX+1.0));
+  for (int cc = 0; cc < length; cc++) {
+    int idx = static_cast<int>(sizeofAlphabet * rand() / (RAND_MAX + 1.0));
     result.push_back(*(alphaPtr + idx));
-    }
+  }
   result.push_back(0);
 
-  this->Makefile->AddDefinition(variableName.c_str(), &*result.begin());
+  status.GetMakefile().AddDefinition(variableName, result.data());
   return true;
 }
 
-//----------------------------------------------------------------------------
-bool cmStringCommand
-::HandleTimestampCommand(std::vector<std::string> const& args)
+bool HandleTimestampCommand(std::vector<std::string> const& args,
+                            cmExecutionStatus& status)
 {
-  if(args.size() < 2)
-    {
-    this->SetError("sub-command TIMESTAMP requires at least one argument.");
+  if (args.size() < 2) {
+    status.SetError("sub-command TIMESTAMP requires at least one argument.");
     return false;
-    }
-  else if(args.size() > 4)
-    {
-    this->SetError("sub-command TIMESTAMP takes at most three arguments.");
+  }
+  if (args.size() > 4) {
+    status.SetError("sub-command TIMESTAMP takes at most three arguments.");
     return false;
-    }
+  }
 
   unsigned int argsIndex = 1;
 
-  const std::string &outputVariable = args[argsIndex++];
+  const std::stringoutputVariable = args[argsIndex++];
 
   std::string formatString;
-  if(args.size() > argsIndex && args[argsIndex] != "UTC")
-    {
+  if (args.size() > argsIndex && args[argsIndex] != "UTC") {
     formatString = args[argsIndex++];
-    }
+  }
 
   bool utcFlag = false;
-  if(args.size() > argsIndex)
-    {
-    if(args[argsIndex] == "UTC")
-      {
+  if (args.size() > argsIndex) {
+    if (args[argsIndex] == "UTC") {
       utcFlag = true;
-      }
-    else
-      {
+    } else {
       std::string e = " TIMESTAMP sub-command does not recognize option " +
-          args[argsIndex] + ".";
-      this->SetError(e.c_str());
+        args[argsIndex] + ".";
+      status.SetError(e);
       return false;
-      }
     }
+  }
 
   cmTimestamp timestamp;
   std::string result = timestamp.CurrentTime(formatString, utcFlag);
-  this->Makefile->AddDefinition(outputVariable.c_str(), result.c_str());
+  status.GetMakefile().AddDefinition(outputVariable, result);
 
   return true;
 }
+
+bool HandleUuidCommand(std::vector<std::string> const& args,
+                       cmExecutionStatus& status)
+{
+#if !defined(CMAKE_BOOTSTRAP)
+  unsigned int argsIndex = 1;
+
+  if (args.size() < 2) {
+    status.SetError("UUID sub-command requires an output variable.");
+    return false;
+  }
+
+  const std::string& outputVariable = args[argsIndex++];
+
+  std::string uuidNamespaceString;
+  std::string uuidName;
+  std::string uuidType;
+  bool uuidUpperCase = false;
+
+  while (args.size() > argsIndex) {
+    if (args[argsIndex] == "NAMESPACE") {
+      ++argsIndex;
+      if (argsIndex >= args.size()) {
+        status.SetError("UUID sub-command, NAMESPACE requires a value.");
+        return false;
+      }
+      uuidNamespaceString = args[argsIndex++];
+    } else if (args[argsIndex] == "NAME") {
+      ++argsIndex;
+      if (argsIndex >= args.size()) {
+        status.SetError("UUID sub-command, NAME requires a value.");
+        return false;
+      }
+      uuidName = args[argsIndex++];
+    } else if (args[argsIndex] == "TYPE") {
+      ++argsIndex;
+      if (argsIndex >= args.size()) {
+        status.SetError("UUID sub-command, TYPE requires a value.");
+        return false;
+      }
+      uuidType = args[argsIndex++];
+    } else if (args[argsIndex] == "UPPER") {
+      ++argsIndex;
+      uuidUpperCase = true;
+    } else {
+      std::string e =
+        "UUID sub-command does not recognize option " + args[argsIndex] + ".";
+      status.SetError(e);
+      return false;
+    }
+  }
+
+  std::string uuid;
+  cmUuid uuidGenerator;
+
+  std::vector<unsigned char> uuidNamespace;
+  if (!uuidGenerator.StringToBinary(uuidNamespaceString, uuidNamespace)) {
+    status.SetError("UUID sub-command, malformed NAMESPACE UUID.");
+    return false;
+  }
+
+  if (uuidType == "MD5") {
+    uuid = uuidGenerator.FromMd5(uuidNamespace, uuidName);
+  } else if (uuidType == "SHA1") {
+    uuid = uuidGenerator.FromSha1(uuidNamespace, uuidName);
+  } else {
+    std::string e = "UUID sub-command, unknown TYPE '" + uuidType + "'.";
+    status.SetError(e);
+    return false;
+  }
+
+  if (uuid.empty()) {
+    status.SetError("UUID sub-command, generation failed.");
+    return false;
+  }
+
+  if (uuidUpperCase) {
+    uuid = cmSystemTools::UpperCase(uuid);
+  }
+
+  status.GetMakefile().AddDefinition(outputVariable, uuid);
+  return true;
+#else
+  status.SetError(cmStrCat(args[0], " not available during bootstrap"));
+  return false;
+#endif
+}
+
+#if !defined(CMAKE_BOOTSTRAP)
+
+// Helpers for string(JSON ...)
+struct Args : cmRange<typename std::vector<std::string>::const_iterator>
+{
+  using cmRange<typename std::vector<std::string>::const_iterator>::cmRange;
+
+  auto PopFront(cm::string_view error) -> const std::string&;
+  auto PopBack(cm::string_view error) -> const std::string&;
+};
+
+class json_error : public std::runtime_error
+{
+public:
+  json_error(std::initializer_list<cm::string_view> message,
+             cm::optional<Args> errorPath = cm::nullopt)
+    : std::runtime_error(cmCatViews(message))
+    , ErrorPath{
+      std::move(errorPath) // NOLINT(performance-move-const-arg)
+    }
+  {
+  }
+  cm::optional<Args> ErrorPath;
+};
+
+const std::string& Args::PopFront(cm::string_view error)
+{
+  if (this->empty()) {
+    throw json_error({ error });
+  }
+  const std::string& res = *this->begin();
+  this->advance(1);
+  return res;
+}
+
+const std::string& Args::PopBack(cm::string_view error)
+{
+  if (this->empty()) {
+    throw json_error({ error });
+  }
+  const std::string& res = *(this->end() - 1);
+  this->retreat(1);
+  return res;
+}
+
+cm::string_view JsonTypeToString(Json::ValueType type)
+{
+  switch (type) {
+    case Json::ValueType::nullValue:
+      return "NULL"_s;
+    case Json::ValueType::intValue:
+    case Json::ValueType::uintValue:
+    case Json::ValueType::realValue:
+      return "NUMBER"_s;
+    case Json::ValueType::stringValue:
+      return "STRING"_s;
+    case Json::ValueType::booleanValue:
+      return "BOOLEAN"_s;
+    case Json::ValueType::arrayValue:
+      return "ARRAY"_s;
+    case Json::ValueType::objectValue:
+      return "OBJECT"_s;
+  }
+  throw json_error({ "invalid JSON type found"_s });
+}
+
+int ParseIndex(
+  const std::string& str, cm::optional<Args> const& progress = cm::nullopt,
+  Json::ArrayIndex max = std::numeric_limits<Json::ArrayIndex>::max())
+{
+  unsigned long lindex;
+  if (!cmStrToULong(str, &lindex)) {
+    throw json_error({ "expected an array index, got: '"_s, str, "'"_s },
+                     progress);
+  }
+  Json::ArrayIndex index = static_cast<Json::ArrayIndex>(lindex);
+  if (index >= max) {
+    cmAlphaNum sizeStr{ max };
+    throw json_error({ "expected an index less than "_s, sizeStr.View(),
+                       " got '"_s, str, "'"_s },
+                     progress);
+  }
+  return index;
+}
+
+Json::Value& ResolvePath(Json::Value& json, Args path)
+{
+  Json::Value* search = &json;
+
+  for (auto curr = path.begin(); curr != path.end(); ++curr) {
+    const std::string& field = *curr;
+    Args progress{ path.begin(), curr + 1 };
+
+    if (search->isArray()) {
+      auto index = ParseIndex(field, progress, search->size());
+      search = &(*search)[index];
+
+    } else if (search->isObject()) {
+      if (!search->isMember(field)) {
+        const auto progressStr = cmJoin(progress, " "_s);
+        throw json_error({ "member '"_s, progressStr, "' not found"_s },
+                         progress);
+      }
+      search = &(*search)[field];
+    } else {
+      const auto progressStr = cmJoin(progress, " "_s);
+      throw json_error(
+        { "invalid path '"_s, progressStr,
+          "', need element of OBJECT or ARRAY type to lookup '"_s, field,
+          "' got "_s, JsonTypeToString(search->type()) },
+        progress);
+    }
+  }
+  return *search;
+}
+
+Json::Value ReadJson(const std::string& jsonstr)
+{
+  Json::CharReaderBuilder builder;
+  builder["collectComments"] = false;
+  auto jsonReader = std::unique_ptr<Json::CharReader>(builder.newCharReader());
+  Json::Value json;
+  std::string error;
+  if (!jsonReader->parse(jsonstr.data(), jsonstr.data() + jsonstr.size(),
+                         &json, &error)) {
+    throw json_error({ "failed parsing json string: "_s, error });
+  }
+  return json;
+}
+std::string WriteJson(const Json::Value& value)
+{
+  Json::StreamWriterBuilder writer;
+  writer["indentation"] = "  ";
+  writer["commentStyle"] = "None";
+  return Json::writeString(writer, value);
+}
+
+#endif
+
+bool HandleJSONCommand(std::vector<std::string> const& arguments,
+                       cmExecutionStatus& status)
+{
+#if !defined(CMAKE_BOOTSTRAP)
+
+  auto& makefile = status.GetMakefile();
+  Args args{ arguments.begin() + 1, arguments.end() };
+
+  const std::string* errorVariable = nullptr;
+  const std::string* outputVariable = nullptr;
+  bool success = true;
+
+  try {
+    outputVariable = &args.PopFront("missing out-var argument"_s);
+
+    if (!args.empty() && *args.begin() == "ERROR_VARIABLE"_s) {
+      args.PopFront("");
+      errorVariable = &args.PopFront("missing error-var argument"_s);
+      makefile.AddDefinition(*errorVariable, "NOTFOUND"_s);
+    }
+
+    const auto& mode = args.PopFront("missing mode argument"_s);
+    if (mode != "GET"_s && mode != "TYPE"_s && mode != "MEMBER"_s &&
+        mode != "LENGTH"_s && mode != "REMOVE"_s && mode != "SET"_s &&
+        mode != "EQUAL"_s) {
+      throw json_error(
+        { "got an invalid mode '"_s, mode,
+          "', expected one of GET, TYPE, MEMBER, LENGTH, REMOVE, SET, "
+          " EQUAL"_s });
+    }
+
+    const auto& jsonstr = args.PopFront("missing json string argument"_s);
+    Json::Value json = ReadJson(jsonstr);
+
+    if (mode == "GET"_s) {
+      const auto& value = ResolvePath(json, args);
+      if (value.isObject() || value.isArray()) {
+        makefile.AddDefinition(*outputVariable, WriteJson(value));
+      } else if (value.isBool()) {
+        makefile.AddDefinitionBool(*outputVariable, value.asBool());
+      } else {
+        makefile.AddDefinition(*outputVariable, value.asString());
+      }
+
+    } else if (mode == "TYPE"_s) {
+      const auto& value = ResolvePath(json, args);
+      makefile.AddDefinition(*outputVariable, JsonTypeToString(value.type()));
+
+    } else if (mode == "MEMBER"_s) {
+      const auto& indexStr = args.PopBack("missing member index"_s);
+      const auto& value = ResolvePath(json, args);
+      if (!value.isObject()) {
+        throw json_error({ "MEMBER needs to be called with an element of "
+                           "type OBJECT, got "_s,
+                           JsonTypeToString(value.type()) },
+                         args);
+      }
+      const auto index = ParseIndex(
+        indexStr, Args{ args.begin(), args.end() + 1 }, value.size());
+      const auto memIt = std::next(value.begin(), index);
+      makefile.AddDefinition(*outputVariable, memIt.name());
+
+    } else if (mode == "LENGTH"_s) {
+      const auto& value = ResolvePath(json, args);
+      if (!value.isArray() && !value.isObject()) {
+        throw json_error({ "LENGTH needs to be called with an "
+                           "element of type ARRAY or OBJECT, got "_s,
+                           JsonTypeToString(value.type()) },
+                         args);
+      }
+
+      cmAlphaNum sizeStr{ value.size() };
+      makefile.AddDefinition(*outputVariable, sizeStr.View());
+
+    } else if (mode == "REMOVE"_s) {
+      const auto& toRemove =
+        args.PopBack("missing member or index to remove"_s);
+      auto& value = ResolvePath(json, args);
+
+      if (value.isArray()) {
+        const auto index = ParseIndex(
+          toRemove, Args{ args.begin(), args.end() + 1 }, value.size());
+        Json::Value removed;
+        value.removeIndex(index, &removed);
+
+      } else if (value.isObject()) {
+        Json::Value removed;
+        value.removeMember(toRemove, &removed);
+
+      } else {
+        throw json_error({ "REMOVE needs to be called with an "
+                           "element of type ARRAY or OBJECT, got "_s,
+                           JsonTypeToString(value.type()) },
+                         args);
+      }
+      makefile.AddDefinition(*outputVariable, WriteJson(json));
+
+    } else if (mode == "SET"_s) {
+      const auto& newValueStr = args.PopBack("missing new value remove"_s);
+      const auto& toAdd = args.PopBack("missing member name to add"_s);
+      auto& value = ResolvePath(json, args);
+
+      Json::Value newValue = ReadJson(newValueStr);
+      if (value.isObject()) {
+        value[toAdd] = newValue;
+      } else if (value.isArray()) {
+        const auto index =
+          ParseIndex(toAdd, Args{ args.begin(), args.end() + 1 });
+        if (value.isValidIndex(index)) {
+          value[static_cast<int>(index)] = newValue;
+        } else {
+          value.append(newValue);
+        }
+      } else {
+        throw json_error({ "SET needs to be called with an "
+                           "element of type OBJECT or ARRAY, got "_s,
+                           JsonTypeToString(value.type()) });
+      }
+
+      makefile.AddDefinition(*outputVariable, WriteJson(json));
+
+    } else if (mode == "EQUAL"_s) {
+      const auto& jsonstr2 =
+        args.PopFront("missing second json string argument"_s);
+      Json::Value json2 = ReadJson(jsonstr2);
+      makefile.AddDefinitionBool(*outputVariable, json == json2);
+    }
+
+  } catch (const json_error& e) {
+    if (outputVariable && e.ErrorPath) {
+      const auto errorPath = cmJoin(*e.ErrorPath, "-");
+      makefile.AddDefinition(*outputVariable,
+                             cmCatViews({ errorPath, "-NOTFOUND"_s }));
+    } else if (outputVariable) {
+      makefile.AddDefinition(*outputVariable, "NOTFOUND"_s);
+    }
+
+    if (errorVariable) {
+      makefile.AddDefinition(*errorVariable, e.what());
+    } else {
+      status.SetError(cmCatViews({ "sub-command JSON "_s, e.what(), "."_s }));
+      success = false;
+    }
+  }
+  return success;
+#else
+  status.SetError(cmStrCat(arguments[0], " not available during bootstrap"_s));
+  return false;
+#endif
+}
+
+} // namespace
+
+bool cmStringCommand(std::vector<std::string> const& args,
+                     cmExecutionStatus& status)
+{
+  if (args.empty()) {
+    status.SetError("must be called with at least one argument.");
+    return false;
+  }
+
+  static cmSubcommandTable const subcommand{
+    { "REGEX"_s, HandleRegexCommand },
+    { "REPLACE"_s, HandleReplaceCommand },
+    { "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 },
+    { "TOLOWER"_s, HandleToLowerCommand },
+    { "TOUPPER"_s, HandleToUpperCommand },
+    { "COMPARE"_s, HandleCompareCommand },
+    { "ASCII"_s, HandleAsciiCommand },
+    { "HEX"_s, HandleHexCommand },
+    { "CONFIGURE"_s, HandleConfigureCommand },
+    { "LENGTH"_s, HandleLengthCommand },
+    { "APPEND"_s, HandleAppendCommand },
+    { "PREPEND"_s, HandlePrependCommand },
+    { "CONCAT"_s, HandleConcatCommand },
+    { "JOIN"_s, HandleJoinCommand },
+    { "SUBSTRING"_s, HandleSubstringCommand },
+    { "STRIP"_s, HandleStripCommand },
+    { "REPEAT"_s, HandleRepeatCommand },
+    { "RANDOM"_s, HandleRandomCommand },
+    { "FIND"_s, HandleFindCommand },
+    { "TIMESTAMP"_s, HandleTimestampCommand },
+    { "MAKE_C_IDENTIFIER"_s, HandleMakeCIdentifierCommand },
+    { "GENEX_STRIP"_s, HandleGenexStripCommand },
+    { "UUID"_s, HandleUuidCommand },
+    { "JSON"_s, HandleJSONCommand },
+  };
+
+  return subcommand(args[0], args, status);
+}