Merge topic 'revert-vs-ZERO_CHECK-proj'

26b724cf2b Merge branch 'backport-3.24-revert-vs-ZERO_CHECK-proj'
289932ded0 VS: Revert "Write ZERO_CHECK.proj for VS19 and above"

Acked-by: Kitware Robot <kwrobot@kitware.com>
Merge-request: !7499
diff --git a/Help/manual/cmake-generator-expressions.7.rst b/Help/manual/cmake-generator-expressions.7.rst
index 9949772..7a6188a 100644
--- a/Help/manual/cmake-generator-expressions.7.rst
+++ b/Help/manual/cmake-generator-expressions.7.rst
@@ -521,7 +521,7 @@
 
   See :ref:`cmake_path(REMOVE_EXTENSION) <REMOVE_EXTENSION>` for more details.
 
-.. genex:: $<PATH:REPLACE_EXTENSION[,LAST_ONLY],path>
+.. genex:: $<PATH:REPLACE_EXTENSION[,LAST_ONLY],path,input>
 
   .. versionadded:: 3.24
 
diff --git a/Help/policy/CMP0097.rst b/Help/policy/CMP0097.rst
index 2240874..24957d0 100644
--- a/Help/policy/CMP0097.rst
+++ b/Help/policy/CMP0097.rst
@@ -4,9 +4,11 @@
 .. versionadded:: 3.16
 
 :command:`ExternalProject_Add` with ``GIT_SUBMODULES ""`` initializes no
-submodules.
+submodules.  The policy also applies to :command:`FetchContent_Declare`,
+which uses the same download and update features as
+:command:`ExternalProject_Add`.
 
-The module provides a ``GIT_SUBMODULES`` option which controls what submodules
+The commands provide a ``GIT_SUBMODULES`` option which controls what submodules
 to initialize and update. Starting with CMake 3.16, explicitly setting
 ``GIT_SUBMODULES`` to an empty string means no submodules will be initialized
 or updated.
diff --git a/Source/CMakeVersion.cmake b/Source/CMakeVersion.cmake
index b4b53c0..c5024a5 100644
--- a/Source/CMakeVersion.cmake
+++ b/Source/CMakeVersion.cmake
@@ -1,7 +1,7 @@
 # CMake version number components.
 set(CMake_VERSION_MAJOR 3)
 set(CMake_VERSION_MINOR 24)
-set(CMake_VERSION_PATCH 20220722)
+set(CMake_VERSION_PATCH 20220725)
 #set(CMake_VERSION_RC 0)
 set(CMake_VERSION_IS_DIRTY 0)
 
diff --git a/Source/cmArgumentParser.cxx b/Source/cmArgumentParser.cxx
index f0c4cfc..25d5c68 100644
--- a/Source/cmArgumentParser.cxx
+++ b/Source/cmArgumentParser.cxx
@@ -5,10 +5,13 @@
 #include <algorithm>
 
 #include "cmArgumentParserTypes.h"
+#include "cmMakefile.h"
+#include "cmMessageType.h"
+#include "cmStringAlgorithms.h"
 
 namespace ArgumentParser {
 
-auto ActionMap::Emplace(cm::string_view name, Action action)
+auto KeywordActionMap::Emplace(cm::string_view name, KeywordAction action)
   -> std::pair<iterator, bool>
 {
   auto const it =
@@ -21,7 +24,7 @@
     : std::make_pair(this->emplace(it, name, std::move(action)), true);
 }
 
-auto ActionMap::Find(cm::string_view name) const -> const_iterator
+auto KeywordActionMap::Find(cm::string_view name) const -> const_iterator
 {
   auto const it =
     std::lower_bound(this->begin(), this->end(), name,
@@ -74,20 +77,16 @@
   this->ExpectValue = false;
 }
 
-void Instance::Consume(cm::string_view arg, void* result,
-                       std::vector<std::string>* unparsedArguments,
-                       std::vector<cm::string_view>* keywordsMissingValue,
-                       std::vector<cm::string_view>* parsedKeywords)
+void Instance::Consume(cm::string_view arg)
 {
-  auto const it = this->Bindings.Find(arg);
-  if (it != this->Bindings.end()) {
-    if (parsedKeywords != nullptr) {
-      parsedKeywords->emplace_back(it->first);
+  auto const it = this->Bindings.Keywords.Find(arg);
+  if (it != this->Bindings.Keywords.end()) {
+    this->FinishKeyword();
+    this->Keyword = it->first;
+    if (this->ParsedKeywords != nullptr) {
+      this->ParsedKeywords->emplace_back(it->first);
     }
-    it->second(*this, result);
-    if (this->ExpectValue && keywordsMissingValue != nullptr) {
-      keywordsMissingValue->emplace_back(it->first);
-    }
+    it->second(*this);
     return;
   }
 
@@ -97,16 +96,40 @@
     this->CurrentList = nullptr;
   } else if (this->CurrentList != nullptr) {
     this->CurrentList->emplace_back(arg);
-  } else if (unparsedArguments != nullptr) {
-    unparsedArguments->emplace_back(arg);
+  } else if (this->UnparsedArguments != nullptr) {
+    this->UnparsedArguments->emplace_back(arg);
   }
 
-  if (this->ExpectValue) {
-    if (keywordsMissingValue != nullptr) {
-      keywordsMissingValue->pop_back();
-    }
-    this->ExpectValue = false;
+  this->ExpectValue = false;
+}
+
+void Instance::FinishKeyword()
+{
+  if (this->Keyword.empty()) {
+    return;
   }
+  if (this->ExpectValue) {
+    if (this->ParseResults != nullptr) {
+      this->ParseResults->AddKeywordError(this->Keyword,
+                                          "  missing required value\n");
+    }
+    if (this->KeywordsMissingValue != nullptr) {
+      this->KeywordsMissingValue->emplace_back(this->Keyword);
+    }
+  }
+}
+
+bool ParseResult::MaybeReportError(cmMakefile& mf) const
+{
+  if (*this) {
+    return false;
+  }
+  std::string e;
+  for (auto const& ke : this->KeywordErrors) {
+    e = cmStrCat(e, "Error after keyword \"", ke.first, "\":\n", ke.second);
+  }
+  mf.IssueMessage(MessageType::FATAL_ERROR, e);
+  return true;
 }
 
 } // namespace ArgumentParser
diff --git a/Source/cmArgumentParser.h b/Source/cmArgumentParser.h
index 26148d9..8fda8b7 100644
--- a/Source/cmArgumentParser.h
+++ b/Source/cmArgumentParser.h
@@ -6,34 +6,116 @@
 
 #include <cassert>
 #include <functional>
+#include <map>
 #include <string>
 #include <utility>
 #include <vector>
 
 #include <cm/optional>
 #include <cm/string_view>
+#include <cm/type_traits>
 #include <cmext/string_view>
 
 #include "cmArgumentParserTypes.h" // IWYU pragma: keep
 
+template <typename Result>
+class cmArgumentParser; // IWYU pragma: keep
+
+class cmMakefile;
+
 namespace ArgumentParser {
 
-class Instance;
-using Action = std::function<void(Instance&, void*)>;
+class ParseResult
+{
+  std::map<cm::string_view, std::string> KeywordErrors;
 
-// using ActionMap = cm::flat_map<cm::string_view, Action>;
-class ActionMap : public std::vector<std::pair<cm::string_view, Action>>
+public:
+  explicit operator bool() const { return this->KeywordErrors.empty(); }
+
+  void AddKeywordError(cm::string_view key, cm::string_view text)
+
+  {
+    this->KeywordErrors[key] += text;
+  }
+
+  std::map<cm::string_view, std::string> const& GetKeywordErrors() const
+  {
+    return this->KeywordErrors;
+  }
+
+  bool MaybeReportError(cmMakefile& mf) const;
+};
+
+template <typename Result>
+typename std::enable_if<std::is_base_of<ParseResult, Result>::value,
+                        ParseResult*>::type
+AsParseResultPtr(Result& result)
+{
+  return &result;
+}
+
+template <typename Result>
+typename std::enable_if<!std::is_base_of<ParseResult, Result>::value,
+                        ParseResult*>::type
+AsParseResultPtr(Result&)
+{
+  return nullptr;
+}
+
+class Instance;
+using KeywordAction = std::function<void(Instance&)>;
+
+// using KeywordActionMap = cm::flat_map<cm::string_view, KeywordAction>;
+class KeywordActionMap
+  : public std::vector<std::pair<cm::string_view, KeywordAction>>
 {
 public:
-  std::pair<iterator, bool> Emplace(cm::string_view name, Action action);
+  std::pair<iterator, bool> Emplace(cm::string_view name,
+                                    KeywordAction action);
   const_iterator Find(cm::string_view name) const;
 };
 
+class ActionMap
+{
+public:
+  KeywordActionMap Keywords;
+};
+
+class Base
+{
+public:
+  using Instance = ArgumentParser::Instance;
+  using ParseResult = ArgumentParser::ParseResult;
+
+  ArgumentParser::ActionMap Bindings;
+
+  bool MaybeBind(cm::string_view name, KeywordAction action)
+  {
+    return this->Bindings.Keywords.Emplace(name, std::move(action)).second;
+  }
+
+  void Bind(cm::string_view name, KeywordAction action)
+  {
+    bool const inserted = this->MaybeBind(name, std::move(action));
+    assert(inserted);
+    static_cast<void>(inserted);
+  }
+};
+
 class Instance
 {
 public:
-  Instance(ActionMap const& bindings)
+  Instance(ActionMap const& bindings, ParseResult* parseResult,
+           std::vector<std::string>* unparsedArguments,
+           std::vector<cm::string_view>* keywordsMissingValue,
+           std::vector<cm::string_view>* parsedKeywords,
+           void* result = nullptr)
     : Bindings(bindings)
+    , ParseResults(parseResult)
+    , UnparsedArguments(unparsedArguments)
+    , KeywordsMissingValue(keywordsMissingValue)
+    , ParsedKeywords(parsedKeywords)
+    , Result(result)
   {
   }
 
@@ -54,22 +136,39 @@
     this->Bind(*optVal);
   }
 
-  void Consume(cm::string_view arg, void* result,
-               std::vector<std::string>* unparsedArguments,
-               std::vector<cm::string_view>* keywordsMissingValue,
-               std::vector<cm::string_view>* parsedKeywords);
+  template <typename Range>
+  void Parse(Range const& args)
+  {
+    for (cm::string_view arg : args) {
+      this->Consume(arg);
+    }
+    this->FinishKeyword();
+  }
 
 private:
   ActionMap const& Bindings;
+  ParseResult* ParseResults = nullptr;
+  std::vector<std::string>* UnparsedArguments = nullptr;
+  std::vector<cm::string_view>* KeywordsMissingValue = nullptr;
+  std::vector<cm::string_view>* ParsedKeywords = nullptr;
+  void* Result = nullptr;
+
+  cm::string_view Keyword;
   std::string* CurrentString = nullptr;
   std::vector<std::string>* CurrentList = nullptr;
   bool ExpectValue = false;
+
+  void Consume(cm::string_view arg);
+  void FinishKeyword();
+
+  template <typename Result>
+  friend class ::cmArgumentParser;
 };
 
 } // namespace ArgumentParser
 
 template <typename Result>
-class cmArgumentParser
+class cmArgumentParser : private ArgumentParser::Base
 {
 public:
   // I *think* this function could be made `constexpr` when the code is
@@ -77,28 +176,24 @@
   template <typename T>
   cmArgumentParser& Bind(cm::static_string_view name, T Result::*member)
   {
-    bool const inserted =
-      this->Bindings
-        .Emplace(name,
-                 [member](ArgumentParser::Instance& instance, void* result) {
-                   instance.Bind(static_cast<Result*>(result)->*member);
-                 })
-        .second;
-    assert(inserted), (void)inserted;
+    this->Base::Bind(name, [member](Instance& instance) {
+      instance.Bind(static_cast<Result*>(instance.Result)->*member);
+    });
     return *this;
   }
 
   template <typename Range>
-  void Parse(Result& result, Range const& args,
+  bool Parse(Result& result, Range const& args,
              std::vector<std::string>* unparsedArguments,
              std::vector<cm::string_view>* keywordsMissingValue = nullptr,
              std::vector<cm::string_view>* parsedKeywords = nullptr) const
   {
-    ArgumentParser::Instance instance(this->Bindings);
-    for (cm::string_view arg : args) {
-      instance.Consume(arg, &result, unparsedArguments, keywordsMissingValue,
-                       parsedKeywords);
-    }
+    using ArgumentParser::AsParseResultPtr;
+    ParseResult* parseResultPtr = AsParseResultPtr(result);
+    Instance instance(this->Bindings, parseResultPtr, unparsedArguments,
+                      keywordsMissingValue, parsedKeywords, &result);
+    instance.Parse(args);
+    return parseResultPtr ? static_cast<bool>(*parseResultPtr) : true;
   }
 
   template <typename Range>
@@ -111,47 +206,37 @@
                 parsedKeywords);
     return result;
   }
-
-private:
-  ArgumentParser::ActionMap Bindings;
 };
 
 template <>
-class cmArgumentParser<void>
+class cmArgumentParser<void> : private ArgumentParser::Base
 {
 public:
   template <typename T>
   cmArgumentParser& Bind(cm::static_string_view name, T& ref)
   {
-    bool const inserted = this->Bind(cm::string_view(name), ref);
-    assert(inserted), (void)inserted;
+    this->Base::Bind(name, [&ref](Instance& instance) { instance.Bind(ref); });
     return *this;
   }
 
   template <typename Range>
-  void Parse(Range const& args, std::vector<std::string>* unparsedArguments,
-             std::vector<cm::string_view>* keywordsMissingValue = nullptr,
-             std::vector<cm::string_view>* parsedKeywords = nullptr) const
+  ParseResult Parse(
+    Range const& args, std::vector<std::string>* unparsedArguments,
+    std::vector<cm::string_view>* keywordsMissingValue = nullptr,
+    std::vector<cm::string_view>* parsedKeywords = nullptr) const
   {
-    ArgumentParser::Instance instance(this->Bindings);
-    for (cm::string_view arg : args) {
-      instance.Consume(arg, nullptr, unparsedArguments, keywordsMissingValue,
-                       parsedKeywords);
-    }
+    ParseResult parseResult;
+    Instance instance(this->Bindings, &parseResult, unparsedArguments,
+                      keywordsMissingValue, parsedKeywords);
+    instance.Parse(args);
+    return parseResult;
   }
 
 protected:
   template <typename T>
   bool Bind(cm::string_view name, T& ref)
   {
-    return this->Bindings
-      .Emplace(name,
-               [&ref](ArgumentParser::Instance& instance, void*) {
-                 instance.Bind(ref);
-               })
-      .second;
+    return this->MaybeBind(name,
+                           [&ref](Instance& instance) { instance.Bind(ref); });
   }
-
-private:
-  ArgumentParser::ActionMap Bindings;
 };
diff --git a/Source/cmCMakeHostSystemInformationCommand.cxx b/Source/cmCMakeHostSystemInformationCommand.cxx
index 033dd6d..58129a0 100644
--- a/Source/cmCMakeHostSystemInformationCommand.cxx
+++ b/Source/cmCMakeHostSystemInformationCommand.cxx
@@ -474,7 +474,7 @@
   }
   std::string const& key = *args.begin();
 
-  struct Arguments
+  struct Arguments : public ArgumentParser::ParseResult
   {
     std::string ValueName;
     bool ValueNames = false;
@@ -491,19 +491,15 @@
     .Bind("SEPARATOR"_s, &Arguments::Separator)
     .Bind("ERROR_VARIABLE"_s, &Arguments::ErrorVariable);
   std::vector<std::string> invalidArgs;
-  std::vector<cm::string_view> keywordsMissingValue;
 
-  Arguments const arguments =
-    parser.Parse(args.advance(1), &invalidArgs, &keywordsMissingValue);
+  Arguments const arguments = parser.Parse(args.advance(1), &invalidArgs);
   if (!invalidArgs.empty()) {
     status.SetError(cmStrCat("given invalid argument(s) \"",
                              cmJoin(invalidArgs, ", "_s), "\"."));
     return false;
   }
-  if (!keywordsMissingValue.empty()) {
-    status.SetError(cmStrCat("missing expected value for argument(s) \"",
-                             cmJoin(keywordsMissingValue, ", "_s), "\"."));
-    return false;
+  if (arguments.MaybeReportError(status.GetMakefile())) {
+    return true;
   }
   if ((!arguments.ValueName.empty() &&
        (arguments.ValueNames || arguments.SubKeys)) ||
diff --git a/Source/cmCMakePathCommand.cxx b/Source/cmCMakePathCommand.cxx
index adcffa9..b955bcf 100644
--- a/Source/cmCMakePathCommand.cxx
+++ b/Source/cmCMakePathCommand.cxx
@@ -2,7 +2,6 @@
    file Copyright.txt or https://cmake.org/licensing for details.  */
 #include "cmCMakePathCommand.h"
 
-#include <algorithm>
 #include <functional>
 #include <iomanip>
 #include <map>
@@ -11,6 +10,7 @@
 #include <utility>
 #include <vector>
 
+#include <cm/optional>
 #include <cm/string_view>
 #include <cmext/string_view>
 
@@ -43,15 +43,12 @@
   }
 
   template <int Advance = 2>
-  Result Parse(std::vector<std::string> const& args,
-               std::vector<cm::string_view>* keywordsMissingValue = nullptr,
-               std::vector<cm::string_view>* parsedKeywords = nullptr) const
+  Result Parse(std::vector<std::string> const& args) const
   {
     this->Inputs.clear();
 
     return this->cmArgumentParser<Result>::Parse(
-      cmMakeRange(args).advance(Advance), &this->Inputs, keywordsMissingValue,
-      parsedKeywords);
+      cmMakeRange(args).advance(Advance), &this->Inputs);
   }
 
   const std::vector<std::string>& GetInputs() const { return this->Inputs; }
@@ -82,52 +79,25 @@
   template <int Advance = 2>
   Result Parse(std::vector<std::string> const& args) const
   {
-    this->KeywordsMissingValue.clear();
-    this->ParsedKeywords.clear();
-
     return this->CMakePathArgumentParser<Result>::template Parse<Advance>(
-      args, &this->KeywordsMissingValue, &this->ParsedKeywords);
-  }
-
-  const std::vector<cm::string_view>& GetKeywordsMissingValue() const
-  {
-    return this->KeywordsMissingValue;
-  }
-  const std::vector<cm::string_view>& GetParsedKeywords() const
-  {
-    return this->ParsedKeywords;
+      args);
   }
 
   bool checkOutputVariable(const Result& arguments,
                            cmExecutionStatus& status) const
   {
-    if (std::find(this->GetKeywordsMissingValue().begin(),
-                  this->GetKeywordsMissingValue().end(),
-                  "OUTPUT_VARIABLE"_s) !=
-        this->GetKeywordsMissingValue().end()) {
-      status.SetError("OUTPUT_VARIABLE requires an argument.");
-      return false;
-    }
-
-    if (std::find(this->GetParsedKeywords().begin(),
-                  this->GetParsedKeywords().end(),
-                  "OUTPUT_VARIABLE"_s) != this->GetParsedKeywords().end() &&
-        arguments.Output.empty()) {
+    if (arguments.Output && arguments.Output->empty()) {
       status.SetError("Invalid name for output variable.");
       return false;
     }
 
     return true;
   }
-
-private:
-  mutable std::vector<cm::string_view> KeywordsMissingValue;
-  mutable std::vector<cm::string_view> ParsedKeywords;
 };
 
-struct OutputVariable
+struct OutputVariable : public ArgumentParser::ParseResult
 {
-  std::string Output;
+  cm::optional<std::string> Output;
 };
 // Usable when OUTPUT_VARIABLE is the only option
 class OutputVariableParser
@@ -297,6 +267,9 @@
 
   const auto arguments = parser.Parse(args);
 
+  if (arguments.MaybeReportError(status.GetMakefile())) {
+    return true;
+  }
   if (!parser.checkOutputVariable(arguments, status)) {
     return false;
   }
@@ -307,7 +280,7 @@
   }
 
   status.GetMakefile().AddDefinition(
-    arguments.Output.empty() ? args[1] : arguments.Output, path.String());
+    arguments.Output ? *arguments.Output : args[1], path.String());
 
   return true;
 }
@@ -319,6 +292,9 @@
 
   const auto arguments = parser.Parse(args);
 
+  if (arguments.MaybeReportError(status.GetMakefile())) {
+    return true;
+  }
   if (!parser.checkOutputVariable(arguments, status)) {
     return false;
   }
@@ -334,7 +310,7 @@
   }
 
   status.GetMakefile().AddDefinition(
-    arguments.Output.empty() ? args[1] : arguments.Output, path.String());
+    arguments.Output ? *arguments.Output : args[1], path.String());
 
   return true;
 }
@@ -346,6 +322,9 @@
 
   const auto arguments = parser.Parse(args);
 
+  if (arguments.MaybeReportError(status.GetMakefile())) {
+    return true;
+  }
   if (!parser.checkOutputVariable(arguments, status)) {
     return false;
   }
@@ -364,7 +343,7 @@
   path.RemoveFileName();
 
   status.GetMakefile().AddDefinition(
-    arguments.Output.empty() ? args[1] : arguments.Output, path.String());
+    arguments.Output ? *arguments.Output : args[1], path.String());
 
   return true;
 }
@@ -376,6 +355,9 @@
 
   const auto arguments = parser.Parse(args);
 
+  if (arguments.MaybeReportError(status.GetMakefile())) {
+    return true;
+  }
   if (!parser.checkOutputVariable(arguments, status)) {
     return false;
   }
@@ -395,7 +377,7 @@
     parser.GetInputs().empty() ? "" : parser.GetInputs().front());
 
   status.GetMakefile().AddDefinition(
-    arguments.Output.empty() ? args[1] : arguments.Output, path.String());
+    arguments.Output ? *arguments.Output : args[1], path.String());
 
   return true;
 }
@@ -403,9 +385,9 @@
 bool HandleRemoveExtensionCommand(std::vector<std::string> const& args,
                                   cmExecutionStatus& status)
 {
-  struct Arguments
+  struct Arguments : public ArgumentParser::ParseResult
   {
-    std::string Output;
+    cm::optional<std::string> Output;
     bool LastOnly = false;
   };
 
@@ -415,6 +397,9 @@
 
   Arguments const arguments = parser.Parse(args);
 
+  if (arguments.MaybeReportError(status.GetMakefile())) {
+    return true;
+  }
   if (!parser.checkOutputVariable(arguments, status)) {
     return false;
   }
@@ -438,7 +423,7 @@
   }
 
   status.GetMakefile().AddDefinition(
-    arguments.Output.empty() ? args[1] : arguments.Output, path.String());
+    arguments.Output ? *arguments.Output : args[1], path.String());
 
   return true;
 }
@@ -446,9 +431,9 @@
 bool HandleReplaceExtensionCommand(std::vector<std::string> const& args,
                                    cmExecutionStatus& status)
 {
-  struct Arguments
+  struct Arguments : public ArgumentParser::ParseResult
   {
-    std::string Output;
+    cm::optional<std::string> Output;
     bool LastOnly = false;
   };
 
@@ -458,6 +443,9 @@
 
   Arguments const arguments = parser.Parse(args);
 
+  if (arguments.MaybeReportError(status.GetMakefile())) {
+    return true;
+  }
   if (!parser.checkOutputVariable(arguments, status)) {
     return false;
   }
@@ -483,7 +471,7 @@
   }
 
   status.GetMakefile().AddDefinition(
-    arguments.Output.empty() ? args[1] : arguments.Output, path.String());
+    arguments.Output ? *arguments.Output : args[1], path.String());
 
   return true;
 }
@@ -495,6 +483,9 @@
 
   const auto arguments = parser.Parse(args);
 
+  if (arguments.MaybeReportError(status.GetMakefile())) {
+    return true;
+  }
   if (!parser.checkOutputVariable(arguments, status)) {
     return false;
   }
@@ -512,7 +503,7 @@
   auto path = cmCMakePath(inputPath).Normal();
 
   status.GetMakefile().AddDefinition(
-    arguments.Output.empty() ? args[1] : arguments.Output, path.String());
+    arguments.Output ? *arguments.Output : args[1], path.String());
 
   return true;
 }
@@ -523,10 +514,10 @@
                                   const std::string& base)>& transform,
   bool normalizeOption = false)
 {
-  struct Arguments
+  struct Arguments : public ArgumentParser::ParseResult
   {
-    std::string Output;
-    std::string BaseDirectory;
+    cm::optional<std::string> Output;
+    cm::optional<std::string> BaseDirectory;
     bool Normalize = false;
   };
 
@@ -538,6 +529,9 @@
 
   Arguments arguments = parser.Parse(args);
 
+  if (arguments.MaybeReportError(status.GetMakefile())) {
+    return true;
+  }
   if (!parser.checkOutputVariable(arguments, status)) {
     return false;
   }
@@ -547,17 +541,11 @@
     return false;
   }
 
-  if (std::find(parser.GetKeywordsMissingValue().begin(),
-                parser.GetKeywordsMissingValue().end(), "BASE_DIRECTORY"_s) !=
-      parser.GetKeywordsMissingValue().end()) {
-    status.SetError("BASE_DIRECTORY requires an argument.");
-    return false;
-  }
-
-  if (std::find(parser.GetParsedKeywords().begin(),
-                parser.GetParsedKeywords().end(),
-                "BASE_DIRECTORY"_s) == parser.GetParsedKeywords().end()) {
-    arguments.BaseDirectory = status.GetMakefile().GetCurrentSourceDirectory();
+  std::string baseDirectory;
+  if (arguments.BaseDirectory) {
+    baseDirectory = *arguments.BaseDirectory;
+  } else {
+    baseDirectory = status.GetMakefile().GetCurrentSourceDirectory();
   }
 
   std::string inputPath;
@@ -565,13 +553,13 @@
     return false;
   }
 
-  auto path = transform(cmCMakePath(inputPath), arguments.BaseDirectory);
+  auto path = transform(cmCMakePath(inputPath), baseDirectory);
   if (arguments.Normalize) {
     path = path.Normal();
   }
 
   status.GetMakefile().AddDefinition(
-    arguments.Output.empty() ? args[1] : arguments.Output, path.String());
+    arguments.Output ? *arguments.Output : args[1], path.String());
 
   return true;
 }
diff --git a/Source/cmExecuteProcessCommand.cxx b/Source/cmExecuteProcessCommand.cxx
index af56e2d..7fbd826 100644
--- a/Source/cmExecuteProcessCommand.cxx
+++ b/Source/cmExecuteProcessCommand.cxx
@@ -47,7 +47,7 @@
     return false;
   }
 
-  struct Arguments
+  struct Arguments : public ArgumentParser::ParseResult
   {
     std::vector<std::vector<std::string>> Commands;
     std::string OutputVariable;
@@ -95,14 +95,10 @@
       .Bind("COMMAND_ERROR_IS_FATAL"_s, &Arguments::CommandErrorIsFatal);
 
   std::vector<std::string> unparsedArguments;
-  std::vector<cm::string_view> keywordsMissingValue;
-  Arguments const arguments =
-    parser.Parse(args, &unparsedArguments, &keywordsMissingValue);
+  Arguments const arguments = parser.Parse(args, &unparsedArguments);
 
-  if (!keywordsMissingValue.empty()) {
-    status.SetError(cmStrCat(" called with no value for ",
-                             keywordsMissingValue.front(), "."));
-    return false;
+  if (arguments.MaybeReportError(status.GetMakefile())) {
+    return true;
   }
   if (!unparsedArguments.empty()) {
     status.SetError(" given unknown argument \"" + unparsedArguments.front() +
diff --git a/Source/cmFileCommand.cxx b/Source/cmFileCommand.cxx
index d2aa63c..c7ba424 100644
--- a/Source/cmFileCommand.cxx
+++ b/Source/cmFileCommand.cxx
@@ -954,42 +954,34 @@
 {
   // Evaluate arguments.
   std::string file;
-  std::string oldRPath;
-  std::string newRPath;
+  cm::optional<std::string> oldRPath;
+  cm::optional<std::string> newRPath;
   bool removeEnvironmentRPath = false;
   cmArgumentParser<void> parser;
   std::vector<std::string> unknownArgs;
-  std::vector<cm::string_view> missingArgs;
-  std::vector<cm::string_view> parsedArgs;
   parser.Bind("FILE"_s, file)
     .Bind("OLD_RPATH"_s, oldRPath)
     .Bind("NEW_RPATH"_s, newRPath)
     .Bind("INSTALL_REMOVE_ENVIRONMENT_RPATH"_s, removeEnvironmentRPath);
-  parser.Parse(cmMakeRange(args).advance(1), &unknownArgs, &missingArgs,
-               &parsedArgs);
+  ArgumentParser::ParseResult parseResult =
+    parser.Parse(cmMakeRange(args).advance(1), &unknownArgs);
   if (!unknownArgs.empty()) {
     status.SetError(
       cmStrCat("RPATH_CHANGE given unknown argument ", unknownArgs.front()));
     return false;
   }
-  if (!missingArgs.empty()) {
-    status.SetError(cmStrCat("RPATH_CHANGE \"", missingArgs.front(),
-                             "\" argument not given value."));
-    return false;
+  if (parseResult.MaybeReportError(status.GetMakefile())) {
+    return true;
   }
   if (file.empty()) {
     status.SetError("RPATH_CHANGE not given FILE option.");
     return false;
   }
-  if (oldRPath.empty() &&
-      std::find(parsedArgs.begin(), parsedArgs.end(), "OLD_RPATH") ==
-        parsedArgs.end()) {
+  if (!oldRPath) {
     status.SetError("RPATH_CHANGE not given OLD_RPATH option.");
     return false;
   }
-  if (newRPath.empty() &&
-      std::find(parsedArgs.begin(), parsedArgs.end(), "NEW_RPATH") ==
-        parsedArgs.end()) {
+  if (!newRPath) {
     status.SetError("RPATH_CHANGE not given NEW_RPATH option.");
     return false;
   }
@@ -1003,17 +995,17 @@
   std::string emsg;
   bool changed;
 
-  if (!cmSystemTools::ChangeRPath(file, oldRPath, newRPath,
+  if (!cmSystemTools::ChangeRPath(file, *oldRPath, *newRPath,
                                   removeEnvironmentRPath, &emsg, &changed)) {
     status.SetError(cmStrCat("RPATH_CHANGE could not write new RPATH:\n  ",
-                             newRPath, "\nto the file:\n  ", file, "\n",
+                             *newRPath, "\nto the file:\n  ", file, "\n",
                              emsg));
     success = false;
   }
   if (success) {
     if (changed) {
       std::string message =
-        cmStrCat("Set runtime path of \"", file, "\" to \"", newRPath, '"');
+        cmStrCat("Set runtime path of \"", file, "\" to \"", *newRPath, '"');
       status.GetMakefile().DisplayStatus(message, -1);
     }
     ft.Store(file);
@@ -1026,31 +1018,25 @@
 {
   // Evaluate arguments.
   std::string file;
-  std::string newRPath;
+  cm::optional<std::string> newRPath;
   cmArgumentParser<void> parser;
   std::vector<std::string> unknownArgs;
-  std::vector<cm::string_view> missingArgs;
-  std::vector<cm::string_view> parsedArgs;
   parser.Bind("FILE"_s, file).Bind("NEW_RPATH"_s, newRPath);
-  parser.Parse(cmMakeRange(args).advance(1), &unknownArgs, &missingArgs,
-               &parsedArgs);
+  ArgumentParser::ParseResult parseResult =
+    parser.Parse(cmMakeRange(args).advance(1), &unknownArgs);
   if (!unknownArgs.empty()) {
     status.SetError(cmStrCat("RPATH_SET given unrecognized argument \"",
                              unknownArgs.front(), "\"."));
     return false;
   }
-  if (!missingArgs.empty()) {
-    status.SetError(cmStrCat("RPATH_SET \"", missingArgs.front(),
-                             "\" argument not given value."));
-    return false;
+  if (parseResult.MaybeReportError(status.GetMakefile())) {
+    return true;
   }
   if (file.empty()) {
     status.SetError("RPATH_SET not given FILE option.");
     return false;
   }
-  if (newRPath.empty() &&
-      std::find(parsedArgs.begin(), parsedArgs.end(), "NEW_RPATH") ==
-        parsedArgs.end()) {
+  if (!newRPath) {
     status.SetError("RPATH_SET not given NEW_RPATH option.");
     return false;
   }
@@ -1064,16 +1050,16 @@
   std::string emsg;
   bool changed;
 
-  if (!cmSystemTools::SetRPath(file, newRPath, &emsg, &changed)) {
+  if (!cmSystemTools::SetRPath(file, *newRPath, &emsg, &changed)) {
     status.SetError(cmStrCat("RPATH_SET could not write new RPATH:\n  ",
-                             newRPath, "\nto the file:\n  ", file, "\n",
+                             *newRPath, "\nto the file:\n  ", file, "\n",
                              emsg));
     success = false;
   }
   if (success) {
     if (changed) {
       std::string message =
-        cmStrCat("Set runtime path of \"", file, "\" to \"", newRPath, '"');
+        cmStrCat("Set runtime path of \"", file, "\" to \"", *newRPath, '"');
       status.GetMakefile().DisplayStatus(message, -1);
     }
     ft.Store(file);
@@ -1088,18 +1074,16 @@
   std::string file;
   cmArgumentParser<void> parser;
   std::vector<std::string> unknownArgs;
-  std::vector<cm::string_view> missingArgs;
   parser.Bind("FILE"_s, file);
-  parser.Parse(cmMakeRange(args).advance(1), &unknownArgs, &missingArgs);
+  ArgumentParser::ParseResult parseResult =
+    parser.Parse(cmMakeRange(args).advance(1), &unknownArgs);
   if (!unknownArgs.empty()) {
     status.SetError(
       cmStrCat("RPATH_REMOVE given unknown argument ", unknownArgs.front()));
     return false;
   }
-  if (!missingArgs.empty()) {
-    status.SetError(cmStrCat("RPATH_REMOVE \"", missingArgs.front(),
-                             "\" argument not given value."));
-    return false;
+  if (parseResult.MaybeReportError(status.GetMakefile())) {
+    return true;
   }
   if (file.empty()) {
     status.SetError("RPATH_REMOVE not given FILE option.");
@@ -1136,31 +1120,25 @@
 {
   // Evaluate arguments.
   std::string file;
-  std::string rpath;
+  cm::optional<std::string> rpath;
   cmArgumentParser<void> parser;
   std::vector<std::string> unknownArgs;
-  std::vector<cm::string_view> missingArgs;
-  std::vector<cm::string_view> parsedArgs;
   parser.Bind("FILE"_s, file).Bind("RPATH"_s, rpath);
-  parser.Parse(cmMakeRange(args).advance(1), &unknownArgs, &missingArgs,
-               &parsedArgs);
+  ArgumentParser::ParseResult parseResult =
+    parser.Parse(cmMakeRange(args).advance(1), &unknownArgs);
   if (!unknownArgs.empty()) {
     status.SetError(
       cmStrCat("RPATH_CHECK given unknown argument ", unknownArgs.front()));
     return false;
   }
-  if (!missingArgs.empty()) {
-    status.SetError(cmStrCat("RPATH_CHECK \"", missingArgs.front(),
-                             "\" argument not given value."));
-    return false;
+  if (parseResult.MaybeReportError(status.GetMakefile())) {
+    return true;
   }
   if (file.empty()) {
     status.SetError("RPATH_CHECK not given FILE option.");
     return false;
   }
-  if (rpath.empty() &&
-      std::find(parsedArgs.begin(), parsedArgs.end(), "RPATH") ==
-        parsedArgs.end()) {
+  if (!rpath) {
     status.SetError("RPATH_CHECK not given RPATH option.");
     return false;
   }
@@ -1169,7 +1147,7 @@
   // delete it.  This is used during installation to re-install a file
   // if its RPath will change.
   if (cmSystemTools::FileExists(file, true) &&
-      !cmSystemTools::CheckRPath(file, rpath)) {
+      !cmSystemTools::CheckRPath(file, *rpath)) {
     cmSystemTools::RemoveFile(file);
   }
 
@@ -1252,7 +1230,7 @@
     return false;
   }
 
-  struct Arguments
+  struct Arguments : public ArgumentParser::ParseResult
   {
     cm::optional<std::string> BaseDirectory;
     bool ExpandTilde = false;
@@ -1263,17 +1241,15 @@
       .Bind("EXPAND_TILDE"_s, &Arguments::ExpandTilde);
 
   std::vector<std::string> unparsedArguments;
-  std::vector<cm::string_view> keywordsMissingValue;
-  auto arguments = parser.Parse(cmMakeRange(args).advance(3),
-                                &unparsedArguments, &keywordsMissingValue);
+  auto arguments =
+    parser.Parse(cmMakeRange(args).advance(3), &unparsedArguments);
 
   if (!unparsedArguments.empty()) {
     status.SetError("REAL_PATH called with unexpected arguments");
     return false;
   }
-  if (!keywordsMissingValue.empty()) {
-    status.SetError("BASE_DIRECTORY requires a value");
-    return false;
+  if (arguments.MaybeReportError(status.GetMakefile())) {
+    return true;
   }
 
   if (!arguments.BaseDirectory) {
@@ -2495,7 +2471,7 @@
     return false;
   }
 
-  struct Arguments
+  struct Arguments : public ArgumentParser::ParseResult
   {
     cm::optional<std::string> Output;
     cm::optional<std::string> Input;
@@ -2521,17 +2497,13 @@
       .Bind("NEWLINE_STYLE"_s, &Arguments::NewLineStyle);
 
   std::vector<std::string> unparsedArguments;
-  std::vector<cm::string_view> keywordsMissingValues;
   std::vector<cm::string_view> parsedKeywords;
   Arguments const arguments =
     parser.Parse(cmMakeRange(args).advance(1), &unparsedArguments,
-                 &keywordsMissingValues, &parsedKeywords);
+                 /*keywordsMissingValue=*/nullptr, &parsedKeywords);
 
-  if (!keywordsMissingValues.empty()) {
-    status.SetError(
-      cmStrCat("GENERATE keywords missing values:\n  ",
-               cmJoin(cmMakeRange(keywordsMissingValues), "\n  ")));
-    return false;
+  if (arguments.MaybeReportError(status.GetMakefile())) {
+    return true;
   }
 
   if (!unparsedArguments.empty()) {
@@ -3045,7 +3017,7 @@
       "\n    ]])");
   }
 
-  struct Arguments
+  struct Arguments : public ArgumentParser::ParseResult
   {
     std::string ResolvedDependenciesVar;
     std::string UnresolvedDependenciesVar;
@@ -3088,10 +3060,8 @@
       .Bind("POST_EXCLUDE_FILES_STRICT"_s, &Arguments::PostExcludeFilesStrict);
 
   std::vector<std::string> unrecognizedArguments;
-  std::vector<cm::string_view> keywordsMissingValues;
   auto parsedArgs =
-    parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments,
-                 &keywordsMissingValues);
+    parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments);
   auto argIt = unrecognizedArguments.begin();
   if (argIt != unrecognizedArguments.end()) {
     status.SetError(cmStrCat("Unrecognized argument: \"", *argIt, "\""));
@@ -3099,12 +3069,9 @@
     return false;
   }
 
-  if (!keywordsMissingValues.empty()) {
-    status.SetError(
-      cmStrCat("Keywords missing values:\n  ",
-               cmJoin(cmMakeRange(keywordsMissingValues), "\n  ")));
+  if (parsedArgs.MaybeReportError(status.GetMakefile())) {
     cmSystemTools::SetFatalErrorOccurred();
-    return false;
+    return true;
   }
 
   cmRuntimeDependencyArchive archive(
@@ -3204,7 +3171,7 @@
 bool HandleConfigureCommand(std::vector<std::string> const& args,
                             cmExecutionStatus& status)
 {
-  struct Arguments
+  struct Arguments : public ArgumentParser::ParseResult
   {
     cm::optional<std::string> Output;
     cm::optional<std::string> Content;
@@ -3223,10 +3190,8 @@
       .Bind("NEWLINE_STYLE"_s, &Arguments::NewlineStyle);
 
   std::vector<std::string> unrecognizedArguments;
-  std::vector<cm::string_view> keywordsMissingValues;
   auto parsedArgs =
-    parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments,
-                 &keywordsMissingValues);
+    parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments);
 
   auto argIt = unrecognizedArguments.begin();
   if (argIt != unrecognizedArguments.end()) {
@@ -3236,12 +3201,9 @@
     return false;
   }
 
-  if (!keywordsMissingValues.empty()) {
-    status.SetError(
-      cmStrCat("CONFIGURE keywords missing values:\n  ",
-               cmJoin(cmMakeRange(keywordsMissingValues), "\n  ")));
+  if (parsedArgs.MaybeReportError(status.GetMakefile())) {
     cmSystemTools::SetFatalErrorOccurred();
-    return false;
+    return true;
   }
 
   if (!parsedArgs.Output) {
@@ -3336,7 +3298,7 @@
 bool HandleArchiveCreateCommand(std::vector<std::string> const& args,
                                 cmExecutionStatus& status)
 {
-  struct Arguments
+  struct Arguments : public ArgumentParser::ParseResult
   {
     std::string Output;
     std::string Format;
@@ -3362,10 +3324,8 @@
       .Bind("PATHS"_s, &Arguments::Paths);
 
   std::vector<std::string> unrecognizedArguments;
-  std::vector<cm::string_view> keywordsMissingValues;
   auto parsedArgs =
-    parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments,
-                 &keywordsMissingValues);
+    parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments);
   auto argIt = unrecognizedArguments.begin();
   if (argIt != unrecognizedArguments.end()) {
     status.SetError(cmStrCat("Unrecognized argument: \"", *argIt, "\""));
@@ -3373,12 +3333,9 @@
     return false;
   }
 
-  if (!keywordsMissingValues.empty()) {
-    status.SetError(
-      cmStrCat("Keywords missing values:\n  ",
-               cmJoin(cmMakeRange(keywordsMissingValues), "\n  ")));
+  if (parsedArgs.MaybeReportError(status.GetMakefile())) {
     cmSystemTools::SetFatalErrorOccurred();
-    return false;
+    return true;
   }
 
   const char* knownFormats[] = {
@@ -3467,7 +3424,7 @@
 bool HandleArchiveExtractCommand(std::vector<std::string> const& args,
                                  cmExecutionStatus& status)
 {
-  struct Arguments
+  struct Arguments : public ArgumentParser::ParseResult
   {
     std::string Input;
     bool Verbose = false;
@@ -3486,10 +3443,8 @@
                                .Bind("TOUCH"_s, &Arguments::Touch);
 
   std::vector<std::string> unrecognizedArguments;
-  std::vector<cm::string_view> keywordsMissingValues;
   auto parsedArgs =
-    parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments,
-                 &keywordsMissingValues);
+    parser.Parse(cmMakeRange(args).advance(1), &unrecognizedArguments);
   auto argIt = unrecognizedArguments.begin();
   if (argIt != unrecognizedArguments.end()) {
     status.SetError(cmStrCat("Unrecognized argument: \"", *argIt, "\""));
@@ -3497,12 +3452,9 @@
     return false;
   }
 
-  if (!keywordsMissingValues.empty()) {
-    status.SetError(
-      cmStrCat("Keywords missing values:\n  ",
-               cmJoin(cmMakeRange(keywordsMissingValues), "\n  ")));
+  if (parsedArgs.MaybeReportError(status.GetMakefile())) {
     cmSystemTools::SetFatalErrorOccurred();
-    return false;
+    return true;
   }
 
   std::string inFile = parsedArgs.Input;
@@ -3557,10 +3509,15 @@
   return true;
 }
 
-bool ValidateAndConvertPermissions(const std::vector<std::string>& permissions,
-                                   mode_t& perms, cmExecutionStatus& status)
+bool ValidateAndConvertPermissions(
+  cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>> const&
+    permissions,
+  mode_t& perms, cmExecutionStatus& status)
 {
-  for (const auto& i : permissions) {
+  if (!permissions) {
+    return true;
+  }
+  for (const auto& i : *permissions) {
     if (!cmFSPermissions::stringToModeT(i, perms)) {
       status.SetError(i + " is an invalid permission specifier");
       cmSystemTools::SetFatalErrorOccurred();
@@ -3592,11 +3549,14 @@
   globber.SetRecurse(recurse);
   globber.SetRecurseListDirs(recurse);
 
-  struct Arguments
+  struct Arguments : public ArgumentParser::ParseResult
   {
-    ArgumentParser::NonEmpty<std::vector<std::string>> Permissions;
-    ArgumentParser::NonEmpty<std::vector<std::string>> FilePermissions;
-    ArgumentParser::NonEmpty<std::vector<std::string>> DirectoryPermissions;
+    cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>>
+      Permissions;
+    cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>>
+      FilePermissions;
+    cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>>
+      DirectoryPermissions;
   };
 
   static auto const parser =
@@ -3606,21 +3566,20 @@
       .Bind("DIRECTORY_PERMISSIONS"_s, &Arguments::DirectoryPermissions);
 
   std::vector<std::string> pathEntries;
-  std::vector<cm::string_view> keywordsMissingValues;
-  Arguments parsedArgs = parser.Parse(cmMakeRange(args).advance(1),
-                                      &pathEntries, &keywordsMissingValues);
+  Arguments parsedArgs =
+    parser.Parse(cmMakeRange(args).advance(1), &pathEntries);
 
   // check validity of arguments
-  if (parsedArgs.Permissions.empty() && parsedArgs.FilePermissions.empty() &&
-      parsedArgs.DirectoryPermissions.empty()) // no permissions given
+  if (!parsedArgs.Permissions && !parsedArgs.FilePermissions &&
+      !parsedArgs.DirectoryPermissions) // no permissions given
   {
     status.SetError("No permissions given");
     cmSystemTools::SetFatalErrorOccurred();
     return false;
   }
 
-  if (!parsedArgs.Permissions.empty() && !parsedArgs.FilePermissions.empty() &&
-      !parsedArgs.DirectoryPermissions.empty()) // all keywords are used
+  if (parsedArgs.Permissions && parsedArgs.FilePermissions &&
+      parsedArgs.DirectoryPermissions) // all keywords are used
   {
     status.SetError("Remove either PERMISSIONS or FILE_PERMISSIONS or "
                     "DIRECTORY_PERMISSIONS from the invocation");
@@ -3628,12 +3587,9 @@
     return false;
   }
 
-  if (!keywordsMissingValues.empty()) {
-    for (const auto& i : keywordsMissingValues) {
-      status.SetError(cmStrCat(i, " is not given any arguments"));
-      cmSystemTools::SetFatalErrorOccurred();
-    }
-    return false;
+  if (parsedArgs.MaybeReportError(status.GetMakefile())) {
+    cmSystemTools::SetFatalErrorOccurred();
+    return true;
   }
 
   // validate permissions
@@ -3677,7 +3633,7 @@
     if (cmSystemTools::FileExists(i, true)) {
       bool success = true;
       const mode_t& filePermissions =
-        parsedArgs.FilePermissions.empty() ? perms : fperms;
+        parsedArgs.FilePermissions ? fperms : perms;
       if (filePermissions) {
         success = SetPermissions(i, filePermissions, status);
       }
@@ -3689,7 +3645,7 @@
     else if (cmSystemTools::FileIsDirectory(i)) {
       bool success = true;
       const mode_t& directoryPermissions =
-        parsedArgs.DirectoryPermissions.empty() ? perms : dperms;
+        parsedArgs.DirectoryPermissions ? dperms : perms;
       if (directoryPermissions) {
         success = SetPermissions(i, directoryPermissions, status);
       }
diff --git a/Source/cmInstallCommand.cxx b/Source/cmInstallCommand.cxx
index b1044a8..82adca8 100644
--- a/Source/cmInstallCommand.cxx
+++ b/Source/cmInstallCommand.cxx
@@ -2160,11 +2160,9 @@
   // These generic args also contain the runtime dependency set
   std::string runtimeDependencySetArg;
   std::vector<std::string> runtimeDependencyArgVector;
-  std::vector<cm::string_view> parsedArgs;
   cmInstallCommandArguments genericArgs(helper.DefaultComponentName);
   genericArgs.Bind("RUNTIME_DEPENDENCY_SET"_s, runtimeDependencySetArg);
-  genericArgs.Parse(genericArgVector, &runtimeDependencyArgVector, nullptr,
-                    &parsedArgs);
+  genericArgs.Parse(genericArgVector, &runtimeDependencyArgVector);
   bool success = genericArgs.Finalize();
 
   cmInstallCommandArguments libraryArgs(helper.DefaultComponentName);
diff --git a/Source/cmQtAutoGenInitializer.cxx b/Source/cmQtAutoGenInitializer.cxx
index 1514a8a..40f3ab5 100644
--- a/Source/cmQtAutoGenInitializer.cxx
+++ b/Source/cmQtAutoGenInitializer.cxx
@@ -909,7 +909,6 @@
   // The reason is that their file names might be discovered from source files
   // at generation time.
   if (this->MocOrUicEnabled()) {
-    std::unordered_set<std::string> addedFiles;
     for (const auto& sf : this->Makefile->GetSourceFiles()) {
       // sf->GetExtension() is only valid after sf->ResolveFullPath() ...
       // Since we're iterating over source files that might be not in the
@@ -951,28 +950,25 @@
                                                       cmExpandedList(uicOpts));
           }
 
-          auto uiHeaderFileName = cmStrCat(
-            "ui_"_s, cmSystemTools::GetFilenameWithoutLastExtension(fullPath),
-            ".h"_s);
+          auto uiHeaderRelativePath = cmSystemTools::RelativePath(
+            this->LocalGen->GetCurrentSourceDirectory(),
+            cmSystemTools::GetFilenamePath(fullPath));
 
-          // .ui files with the same base name will conflict. Yield an error.
-          {
-            auto insertResult = addedFiles.insert(uiHeaderFileName);
-            if (!insertResult.second) {
-              this->Makefile->IssueMessage(
-                MessageType::FATAL_ERROR,
-                cmStrCat("More than one .ui file with the name "_s,
-                         cmSystemTools::GetFilenameName(fullPath),
-                         " was found in the sources for target "_s,
-                         this->GenTarget->GetName(), "."));
-            }
+          // Avoid creating a path containing adjacent slashes
+          if (!uiHeaderRelativePath.empty() &&
+              uiHeaderRelativePath.back() != '/') {
+            uiHeaderRelativePath += '/';
           }
 
+          auto uiHeaderFilePath = cmStrCat(
+            '/', uiHeaderRelativePath, "ui_"_s,
+            cmSystemTools::GetFilenameWithoutLastExtension(fullPath), ".h"_s);
+
           ConfigString uiHeader;
           std::string uiHeaderGenex;
           this->ConfigFileNamesAndGenex(
             uiHeader, uiHeaderGenex, cmStrCat(this->Dir.Build, "/include"_s),
-            cmStrCat("/"_s, uiHeaderFileName));
+            uiHeaderFilePath);
 
           this->Uic.UiHeaders.emplace_back(
             std::make_pair(uiHeader, uiHeaderGenex));
diff --git a/Source/kwsys/Directory.cxx b/Source/kwsys/Directory.cxx
index d520c14..f239576 100644
--- a/Source/kwsys/Directory.cxx
+++ b/Source/kwsys/Directory.cxx
@@ -43,12 +43,12 @@
   {
     std::string Name;
 #if defined(_WIN32) && !defined(__CYGWIN__)
-    _wfinddata_t FindData;
+    WIN32_FIND_DATAW FindData;
 #endif
     FileData(std::string name
 #if defined(_WIN32) && !defined(__CYGWIN__)
              ,
-             _wfinddata_t data
+             WIN32_FIND_DATAW data
 #endif
              )
       : Name(std::move(name))
@@ -115,8 +115,8 @@
 bool Directory::FileIsDirectory(std::size_t i) const
 {
 #if defined(_WIN32) && !defined(__CYGWIN__)
-  _wfinddata_t const& data = this->Internal->Files[i].FindData;
-  return (data.attrib & FILE_ATTRIBUTE_DIRECTORY) != 0;
+  auto const& data = this->Internal->Files[i].FindData;
+  return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
 #else
   std::string const& path = this->GetFilePath(i);
   return kwsys::SystemTools::FileIsDirectory(path);
@@ -127,9 +127,9 @@
 {
   std::string const& path = this->GetFilePath(i);
 #if defined(_WIN32) && !defined(__CYGWIN__)
-  _wfinddata_t const& data = this->Internal->Files[i].FindData;
+  auto const& data = this->Internal->Files[i].FindData;
   return kwsys::SystemTools::FileIsSymlinkWithAttr(
-    Encoding::ToWindowsExtendedPath(path), data.attrib);
+    Encoding::ToWindowsExtendedPath(path), data.dwFileAttributes);
 #else
   return kwsys::SystemTools::FileIsSymlink(path);
 #endif
@@ -157,7 +157,7 @@
 Status Directory::Load(std::string const& name, std::string* errorMessage)
 {
   this->Clear();
-  intptr_t srchHandle;
+  HANDLE srchHandle;
   char* buf;
   size_t bufLength;
   size_t n = name.size();
@@ -176,14 +176,14 @@
       snprintf(buf, bufLength, "%s/*", name.c_str());
     }
   }
-  struct _wfinddata_t data; // data of current file
+  WIN32_FIND_DATAW data; // data of current file
 
   // Now put them into the file array
   srchHandle =
-    _wfindfirst((wchar_t*)Encoding::ToWindowsExtendedPath(buf).c_str(), &data);
+    FindFirstFileW(Encoding::ToWindowsExtendedPath(buf).c_str(), &data);
   delete[] buf;
 
-  if (srchHandle == -1) {
+  if (srchHandle == INVALID_HANDLE_VALUE) {
     Status status = Status::POSIX_errno();
     if (errorMessage) {
       *errorMessage = status.GetString();
@@ -193,10 +193,11 @@
 
   // Loop through names
   do {
-    this->Internal->Files.emplace_back(Encoding::ToNarrow(data.name), data);
-  } while (_wfindnext(srchHandle, &data) != -1);
+    this->Internal->Files.emplace_back(Encoding::ToNarrow(data.cFileName),
+                                       data);
+  } while (FindNextFileW(srchHandle, &data));
   this->Internal->Path = name;
-  if (_findclose(srchHandle) == -1) {
+  if (!FindClose(srchHandle)) {
     Status status = Status::POSIX_errno();
     if (errorMessage) {
       *errorMessage = status.GetString();
@@ -209,7 +210,7 @@
 unsigned long Directory::GetNumberOfFilesInDirectory(const std::string& name,
                                                      std::string* errorMessage)
 {
-  intptr_t srchHandle;
+  HANDLE srchHandle;
   char* buf;
   size_t bufLength;
   size_t n = name.size();
@@ -222,13 +223,13 @@
     buf = new char[n + 2 + 1];
     snprintf(buf, bufLength, "%s/*", name.c_str());
   }
-  struct _wfinddata_t data; // data of current file
+  WIN32_FIND_DATAW data; // data of current file
 
   // Now put them into the file array
-  srchHandle = _wfindfirst((wchar_t*)Encoding::ToWide(buf).c_str(), &data);
+  srchHandle = FindFirstFileW(Encoding::ToWide(buf).c_str(), &data);
   delete[] buf;
 
-  if (srchHandle == -1) {
+  if (srchHandle == INVALID_HANDLE_VALUE) {
     if (errorMessage) {
       if (unsigned int errorId = GetLastError()) {
         LPSTR message = nullptr;
@@ -250,8 +251,8 @@
   unsigned long count = 0;
   do {
     count++;
-  } while (_wfindnext(srchHandle, &data) != -1);
-  _findclose(srchHandle);
+  } while (FindNextFileW(srchHandle, &data));
+  FindClose(srchHandle);
   return count;
 }
 
diff --git a/Source/kwsys/SystemTools.cxx b/Source/kwsys/SystemTools.cxx
index 89a3cc7..a20901c 100644
--- a/Source/kwsys/SystemTools.cxx
+++ b/Source/kwsys/SystemTools.cxx
@@ -536,9 +536,11 @@
   StringMap TranslationMap;
 #endif
 #ifdef _WIN32
-  static std::string GetCasePathName(std::string const& pathIn);
+  static std::string GetCasePathName(std::string const& pathIn,
+                                     bool const cache);
   static std::string GetActualCaseForPathCached(std::string const& path);
   static const char* GetEnvBuffered(const char* key);
+  std::map<std::string, std::string, SystemToolsPathCaseCmp> FindFileMap;
   std::map<std::string, std::string, SystemToolsPathCaseCmp> PathCaseMap;
   std::map<std::string, std::string> EnvMap;
 #endif
@@ -571,7 +573,8 @@
 static SystemToolsStatic* SystemToolsStatics;
 
 #ifdef _WIN32
-std::string SystemToolsStatic::GetCasePathName(std::string const& pathIn)
+std::string SystemToolsStatic::GetCasePathName(std::string const& pathIn,
+                                               bool const cache)
 {
   std::string casePath;
 
@@ -623,14 +626,31 @@
       } else {
         std::string test_str = casePath;
         test_str += path_components[idx];
-        WIN32_FIND_DATAW findData;
-        HANDLE hFind =
-          ::FindFirstFileW(Encoding::ToWide(test_str).c_str(), &findData);
-        if (INVALID_HANDLE_VALUE != hFind) {
-          path_components[idx] = Encoding::ToNarrow(findData.cFileName);
-          ::FindClose(hFind);
-        } else {
-          converting = false;
+
+        bool found_in_cache = false;
+        if (cache) {
+          auto const it = SystemToolsStatics->FindFileMap.find(test_str);
+          if (it != SystemToolsStatics->FindFileMap.end()) {
+            path_components[idx] = it->second;
+            found_in_cache = true;
+          }
+        }
+
+        if (!found_in_cache) {
+          WIN32_FIND_DATAW findData;
+          HANDLE hFind =
+            ::FindFirstFileW(Encoding::ToWide(test_str).c_str(), &findData);
+          if (INVALID_HANDLE_VALUE != hFind) {
+            auto case_file_name = Encoding::ToNarrow(findData.cFileName);
+            if (cache) {
+              SystemToolsStatics->FindFileMap.emplace(test_str,
+                                                      case_file_name);
+            }
+            path_components[idx] = std::move(case_file_name);
+            ::FindClose(hFind);
+          } else {
+            converting = false;
+          }
         }
       }
     }
@@ -642,19 +662,16 @@
 
 std::string SystemToolsStatic::GetActualCaseForPathCached(std::string const& p)
 {
-  // Check to see if actual case has already been called
-  // for this path, and the result is stored in the PathCaseMap
-  auto& pcm = SystemToolsStatics->PathCaseMap;
-  {
-    auto itr = pcm.find(p);
-    if (itr != pcm.end()) {
-      return itr->second;
-    }
+  std::string casePath;
+
+  auto it = SystemToolsStatics->PathCaseMap.find(p);
+  if (it != SystemToolsStatics->PathCaseMap.end()) {
+    casePath = it->second;
+  } else {
+    casePath = SystemToolsStatic::GetCasePathName(p, true);
+    SystemToolsStatics->PathCaseMap.emplace(p, casePath);
   }
-  std::string casePath = SystemToolsStatic::GetCasePathName(p);
-  if (casePath.size() <= MAX_PATH) {
-    pcm[p] = casePath;
-  }
+
   return casePath;
 }
 #endif
@@ -3677,7 +3694,7 @@
 std::string SystemTools::GetActualCaseForPath(const std::string& p)
 {
 #ifdef _WIN32
-  return SystemToolsStatic::GetCasePathName(p);
+  return SystemToolsStatic::GetCasePathName(p, false);
 #else
   return p;
 #endif
diff --git a/Tests/CMakeLib/testArgumentParser.cxx b/Tests/CMakeLib/testArgumentParser.cxx
index ecfc5fc..f0ace50 100644
--- a/Tests/CMakeLib/testArgumentParser.cxx
+++ b/Tests/CMakeLib/testArgumentParser.cxx
@@ -3,7 +3,9 @@
 
 #include <initializer_list>
 #include <iostream>
+#include <map>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include <cm/optional>
@@ -15,7 +17,7 @@
 
 namespace {
 
-struct Result
+struct Result : public ArgumentParser::ParseResult
 {
   bool Option1 = false;
   bool Option2 = false;
@@ -69,6 +71,11 @@
   static std::vector<cm::string_view> const missing = { "STRING_1"_s,
                                                         "LIST_1"_s,
                                                         "LIST_4"_s };
+  static std::map<cm::string_view, std::string> const keywordErrors = {
+    { "STRING_1"_s, "  missing required value\n" },
+    { "LIST_1"_s, "  missing required value\n" },
+    { "LIST_4"_s, "  missing required value\n" }
+  };
 
 #define ASSERT_TRUE(x)                                                        \
   do {                                                                        \
@@ -78,6 +85,8 @@
     }                                                                         \
   } while (false)
 
+  ASSERT_TRUE(!result);
+
   ASSERT_TRUE(result.Option1);
   ASSERT_TRUE(!result.Option2);
 
@@ -110,6 +119,13 @@
   ASSERT_TRUE(unparsedArguments[0] == "bar");
   ASSERT_TRUE(keywordsMissingValue == missing);
 
+  ASSERT_TRUE(result.GetKeywordErrors().size() == keywordErrors.size());
+  for (auto const& ke : result.GetKeywordErrors()) {
+    auto const ki = keywordErrors.find(ke.first);
+    ASSERT_TRUE(ki != keywordErrors.end());
+    ASSERT_TRUE(ke.second == ki->second);
+  }
+
   return true;
 }
 
@@ -119,54 +135,65 @@
   std::vector<std::string> unparsedArguments;
   std::vector<cm::string_view> keywordsMissingValue;
 
-  cmArgumentParser<void>{}
-    .Bind("OPTION_1"_s, result.Option1)
-    .Bind("OPTION_2"_s, result.Option2)
-    .Bind("STRING_1"_s, result.String1)
-    .Bind("STRING_2"_s, result.String2)
-    .Bind("STRING_3"_s, result.String3)
-    .Bind("STRING_4"_s, result.String4)
-    .Bind("LIST_1"_s, result.List1)
-    .Bind("LIST_2"_s, result.List2)
-    .Bind("LIST_3"_s, result.List3)
-    .Bind("LIST_4"_s, result.List4)
-    .Bind("LIST_5"_s, result.List5)
-    .Bind("LIST_6"_s, result.List6)
-    .Bind("MULTI_1"_s, result.Multi1)
-    .Bind("MULTI_2"_s, result.Multi2)
-    .Bind("MULTI_3"_s, result.Multi3)
-    .Bind("MULTI_4"_s, result.Multi4)
-    .Parse(args, &unparsedArguments, &keywordsMissingValue);
+  static_cast<ArgumentParser::ParseResult&>(result) =
+    cmArgumentParser<void>{}
+      .Bind("OPTION_1"_s, result.Option1)
+      .Bind("OPTION_2"_s, result.Option2)
+      .Bind("STRING_1"_s, result.String1)
+      .Bind("STRING_2"_s, result.String2)
+      .Bind("STRING_3"_s, result.String3)
+      .Bind("STRING_4"_s, result.String4)
+      .Bind("LIST_1"_s, result.List1)
+      .Bind("LIST_2"_s, result.List2)
+      .Bind("LIST_3"_s, result.List3)
+      .Bind("LIST_4"_s, result.List4)
+      .Bind("LIST_5"_s, result.List5)
+      .Bind("LIST_6"_s, result.List6)
+      .Bind("MULTI_1"_s, result.Multi1)
+      .Bind("MULTI_2"_s, result.Multi2)
+      .Bind("MULTI_3"_s, result.Multi3)
+      .Bind("MULTI_4"_s, result.Multi4)
+      .Parse(args, &unparsedArguments, &keywordsMissingValue);
 
   return verifyResult(result, unparsedArguments, keywordsMissingValue);
 }
 
+static auto const parserStatic = //
+  cmArgumentParser<Result>{}
+    .Bind("OPTION_1"_s, &Result::Option1)
+    .Bind("OPTION_2"_s, &Result::Option2)
+    .Bind("STRING_1"_s, &Result::String1)
+    .Bind("STRING_2"_s, &Result::String2)
+    .Bind("STRING_3"_s, &Result::String3)
+    .Bind("STRING_4"_s, &Result::String4)
+    .Bind("LIST_1"_s, &Result::List1)
+    .Bind("LIST_2"_s, &Result::List2)
+    .Bind("LIST_3"_s, &Result::List3)
+    .Bind("LIST_4"_s, &Result::List4)
+    .Bind("LIST_5"_s, &Result::List5)
+    .Bind("LIST_6"_s, &Result::List6)
+    .Bind("MULTI_1"_s, &Result::Multi1)
+    .Bind("MULTI_2"_s, &Result::Multi2)
+    .Bind("MULTI_3"_s, &Result::Multi3)
+    .Bind("MULTI_4"_s, &Result::Multi4)
+  /* keep semicolon on own line */;
+
 bool testArgumentParserStatic()
 {
-  static auto const parser = //
-    cmArgumentParser<Result>{}
-      .Bind("OPTION_1"_s, &Result::Option1)
-      .Bind("OPTION_2"_s, &Result::Option2)
-      .Bind("STRING_1"_s, &Result::String1)
-      .Bind("STRING_2"_s, &Result::String2)
-      .Bind("STRING_3"_s, &Result::String3)
-      .Bind("STRING_4"_s, &Result::String4)
-      .Bind("LIST_1"_s, &Result::List1)
-      .Bind("LIST_2"_s, &Result::List2)
-      .Bind("LIST_3"_s, &Result::List3)
-      .Bind("LIST_4"_s, &Result::List4)
-      .Bind("LIST_5"_s, &Result::List5)
-      .Bind("LIST_6"_s, &Result::List6)
-      .Bind("MULTI_1"_s, &Result::Multi1)
-      .Bind("MULTI_2"_s, &Result::Multi2)
-      .Bind("MULTI_3"_s, &Result::Multi3)
-      .Bind("MULTI_4"_s, &Result::Multi4);
-
   std::vector<std::string> unparsedArguments;
   std::vector<cm::string_view> keywordsMissingValue;
   Result const result =
-    parser.Parse(args, &unparsedArguments, &keywordsMissingValue);
+    parserStatic.Parse(args, &unparsedArguments, &keywordsMissingValue);
+  return verifyResult(result, unparsedArguments, keywordsMissingValue);
+}
 
+bool testArgumentParserStaticBool()
+{
+  std::vector<std::string> unparsedArguments;
+  std::vector<cm::string_view> keywordsMissingValue;
+  Result result;
+  ASSERT_TRUE(parserStatic.Parse(result, args, &unparsedArguments,
+                                 &keywordsMissingValue) == false);
   return verifyResult(result, unparsedArguments, keywordsMissingValue);
 }
 
@@ -184,5 +211,10 @@
     return -1;
   }
 
+  if (!testArgumentParserStaticBool()) {
+    std::cout << "While executing testArgumentParserStaticBool().\n";
+    return -1;
+  }
+
   return 0;
 }
diff --git a/Tests/RunCMake/File_Archive/create-missing-args-stderr.txt b/Tests/RunCMake/File_Archive/create-missing-args-stderr.txt
index ecfe401..fd026f9 100644
--- a/Tests/RunCMake/File_Archive/create-missing-args-stderr.txt
+++ b/Tests/RunCMake/File_Archive/create-missing-args-stderr.txt
@@ -1,9 +1,19 @@
 ^CMake Error at create-missing-args.cmake:[0-9]+ \(file\):
-  file Keywords missing values:
+  Error after keyword "COMPRESSION":
 
-    OUTPUT
-    FORMAT
-    COMPRESSION
-    COMPRESSION_LEVEL
+    missing required value
+
+  Error after keyword "COMPRESSION_LEVEL":
+
+    missing required value
+
+  Error after keyword "FORMAT":
+
+    missing required value
+
+  Error after keyword "OUTPUT":
+
+    missing required value
+
 Call Stack \(most recent call first\):
   CMakeLists.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/File_Archive/extract-missing-args-stderr.txt b/Tests/RunCMake/File_Archive/extract-missing-args-stderr.txt
index 96c779f..0c93ece 100644
--- a/Tests/RunCMake/File_Archive/extract-missing-args-stderr.txt
+++ b/Tests/RunCMake/File_Archive/extract-missing-args-stderr.txt
@@ -1,7 +1,11 @@
 ^CMake Error at extract-missing-args.cmake:[0-9]+ \(file\):
-  file Keywords missing values:
+  Error after keyword "DESTINATION":
 
-    INPUT
-    DESTINATION
+    missing required value
+
+  Error after keyword "INPUT":
+
+    missing required value
+
 Call Stack \(most recent call first\):
   CMakeLists.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/File_Configure/BadArgContent-stderr.txt b/Tests/RunCMake/File_Configure/BadArgContent-stderr.txt
index dd6a2e7..72292f9 100644
--- a/Tests/RunCMake/File_Configure/BadArgContent-stderr.txt
+++ b/Tests/RunCMake/File_Configure/BadArgContent-stderr.txt
@@ -1,6 +1,7 @@
 CMake Error at BadArgContent.cmake:[0-9]+ \(file\):
-  file CONFIGURE keywords missing values:
+  Error after keyword "CONTENT":
 
-    CONTENT
+    missing required value
+
 Call Stack \(most recent call first\):
   CMakeLists.txt:3 \(include\)
diff --git a/Tests/RunCMake/File_Configure/BadArgOutput-stderr.txt b/Tests/RunCMake/File_Configure/BadArgOutput-stderr.txt
index e1bf7b4..d793f48 100644
--- a/Tests/RunCMake/File_Configure/BadArgOutput-stderr.txt
+++ b/Tests/RunCMake/File_Configure/BadArgOutput-stderr.txt
@@ -1,6 +1,7 @@
 CMake Error at BadArgOutput.cmake:[0-9]+ \(file\):
-  file CONFIGURE keywords missing values:
+  Error after keyword "OUTPUT":
 
-    OUTPUT
+    missing required value
+
 Call Stack \(most recent call first\):
   CMakeLists.txt:3 \(include\)
diff --git a/Tests/RunCMake/File_Generate/EmptyCondition1-stderr.txt b/Tests/RunCMake/File_Generate/EmptyCondition1-stderr.txt
index 4278bce..708e6be 100644
--- a/Tests/RunCMake/File_Generate/EmptyCondition1-stderr.txt
+++ b/Tests/RunCMake/File_Generate/EmptyCondition1-stderr.txt
@@ -1,6 +1,7 @@
 CMake Error at EmptyCondition1.cmake:2 \(file\):
-  file GENERATE keywords missing values:
+  Error after keyword "CONDITION":
 
-    CONDITION
+    missing required value
+
 Call Stack \(most recent call first\):
   CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/File_Generate/NewLineStyle-NoArg-stderr.txt b/Tests/RunCMake/File_Generate/NewLineStyle-NoArg-stderr.txt
index 4059bf8..b1b7f80 100644
--- a/Tests/RunCMake/File_Generate/NewLineStyle-NoArg-stderr.txt
+++ b/Tests/RunCMake/File_Generate/NewLineStyle-NoArg-stderr.txt
@@ -1,6 +1,7 @@
 CMake Error at NewLineStyle-NoArg.cmake:[0-9]+ \(file\):
-  file GENERATE keywords missing values:
+  Error after keyword "NEWLINE_STYLE":
 
-    NEWLINE_STYLE
+    missing required value
+
 Call Stack \(most recent call first\):
   CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/cmake_host_system_information/Registry_BadQuery2-stderr.txt b/Tests/RunCMake/cmake_host_system_information/Registry_BadQuery2-stderr.txt
index 6a430f1..ea1566d 100644
--- a/Tests/RunCMake/cmake_host_system_information/Registry_BadQuery2-stderr.txt
+++ b/Tests/RunCMake/cmake_host_system_information/Registry_BadQuery2-stderr.txt
@@ -1,5 +1,7 @@
 CMake Error at Registry_BadQuery2.cmake:[0-9]+ \(cmake_host_system_information\):
-  cmake_host_system_information missing expected value for argument\(s\)
-  "VALUE".
+  Error after keyword "VALUE":
+
+    missing required value
+
 Call Stack \(most recent call first\):
   CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/cmake_host_system_information/Registry_BadView1-stderr.txt b/Tests/RunCMake/cmake_host_system_information/Registry_BadView1-stderr.txt
index 5eda4ff..f8c96d8 100644
--- a/Tests/RunCMake/cmake_host_system_information/Registry_BadView1-stderr.txt
+++ b/Tests/RunCMake/cmake_host_system_information/Registry_BadView1-stderr.txt
@@ -1,5 +1,7 @@
 CMake Error at Registry_BadView1.cmake:[0-9]+ \(cmake_host_system_information\):
-  cmake_host_system_information missing expected value for argument\(s\)
-  "VIEW".
+  Error after keyword "VIEW":
+
+    missing required value
+
 Call Stack \(most recent call first\):
   CMakeLists.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/cmake_path/BASE_DIRECTORY-no-arg-stderr.txt b/Tests/RunCMake/cmake_path/BASE_DIRECTORY-no-arg-stderr.txt
new file mode 100644
index 0000000..ad7d134
--- /dev/null
+++ b/Tests/RunCMake/cmake_path/BASE_DIRECTORY-no-arg-stderr.txt
@@ -0,0 +1,4 @@
+CMake Error at .+/cmake_path/call-cmake_path.cmake:[0-9]+ \(cmake_path\):
+  Error after keyword "BASE_DIRECTORY":
+
+    missing required value
diff --git a/Tests/RunCMake/cmake_path/OUTPUT_VARIABLE-no-arg-stderr.txt b/Tests/RunCMake/cmake_path/OUTPUT_VARIABLE-no-arg-stderr.txt
index e1d6592..63289ef 100644
--- a/Tests/RunCMake/cmake_path/OUTPUT_VARIABLE-no-arg-stderr.txt
+++ b/Tests/RunCMake/cmake_path/OUTPUT_VARIABLE-no-arg-stderr.txt
@@ -1,2 +1,4 @@
 CMake Error at .+/cmake_path/call-cmake_path.cmake:[0-9]+ \(cmake_path\):
-  cmake_path OUTPUT_VARIABLE requires an argument.
+  Error after keyword "OUTPUT_VARIABLE":
+
+    missing required value
diff --git a/Tests/RunCMake/cmake_path/RunCMakeTest.cmake b/Tests/RunCMake/cmake_path/RunCMakeTest.cmake
index 991f46b..8a2dd95 100644
--- a/Tests/RunCMake/cmake_path/RunCMakeTest.cmake
+++ b/Tests/RunCMake/cmake_path/RunCMakeTest.cmake
@@ -74,6 +74,14 @@
 endforeach()
 
 
+## BASE_DIRECTORY without argument
+set (RunCMake-stderr-file "BASE_DIRECTORY-no-arg-stderr.txt")
+
+foreach (command IN ITEMS RELATIVE_PATH ABSOLUTE_PATH)
+  run_cmake_command (${command}-OUTPUT_VARIABLE-no-arg "${CMAKE_COMMAND}" "-DCMAKE_PATH_ARGUMENTS=${command} path BASE_DIRECTORY" -P "${RunCMake_SOURCE_DIR}/call-cmake_path.cmake")
+endforeach()
+
+
 ## Invalid output variable
 set (RunCMake-stderr-file "invalid-output-var-stderr.txt")
 
diff --git a/Tests/RunCMake/execute_process/EchoCommand3-stderr.txt b/Tests/RunCMake/execute_process/EchoCommand3-stderr.txt
index e27f1e6..62cad52 100644
--- a/Tests/RunCMake/execute_process/EchoCommand3-stderr.txt
+++ b/Tests/RunCMake/execute_process/EchoCommand3-stderr.txt
@@ -1,2 +1,4 @@
 CMake Error at .*EchoCommand.cmake:.*\(execute_process\):
-  execute_process called with no value for COMMAND_ECHO.
+  Error after keyword "COMMAND_ECHO":
+
+    missing required value
diff --git a/Tests/RunCMake/execute_process/EncodingMissing-stderr.txt b/Tests/RunCMake/execute_process/EncodingMissing-stderr.txt
index 1a69579..7f85654 100644
--- a/Tests/RunCMake/execute_process/EncodingMissing-stderr.txt
+++ b/Tests/RunCMake/execute_process/EncodingMissing-stderr.txt
@@ -1,4 +1,7 @@
 ^CMake Error at EncodingMissing.cmake:[0-9]+ \(execute_process\):
-  execute_process called with no value for ENCODING.
+  Error after keyword "ENCODING":
+
+    missing required value
+
 Call Stack \(most recent call first\):
   CMakeLists.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/file-CHMOD/CHMOD-all-perms-stderr.txt b/Tests/RunCMake/file-CHMOD/CHMOD-all-perms-stderr.txt
deleted file mode 100644
index b22387b..0000000
--- a/Tests/RunCMake/file-CHMOD/CHMOD-all-perms-stderr.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-CMake Error at CHMOD-all-perms\.cmake:[0-9]+ \(file\):
-  file Remove either PERMISSIONS or FILE_PERMISSIONS or DIRECTORY_PERMISSIONS
-  from the invocation
-Call Stack \(most recent call first\):
-  CMakeLists\.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/file-CHMOD/CHMOD-all-perms.cmake b/Tests/RunCMake/file-CHMOD/CHMOD-all-perms.cmake
deleted file mode 100644
index b49583d..0000000
--- a/Tests/RunCMake/file-CHMOD/CHMOD-all-perms.cmake
+++ /dev/null
@@ -1,6 +0,0 @@
-file(REMOVE_RECURSE ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
-file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
-
-file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a)
-file(CHMOD ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a PERMISSIONS OWNER_READ
-  FILE_PERMISSIONS OWNER_READ DIRECTORY_PERMISSIONS OWNER_READ)
diff --git a/Tests/RunCMake/file-CHMOD/CHMOD-invalid-path-stderr.txt b/Tests/RunCMake/file-CHMOD/CHMOD-invalid-path-stderr.txt
deleted file mode 100644
index 8d09e35..0000000
--- a/Tests/RunCMake/file-CHMOD/CHMOD-invalid-path-stderr.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-CMake Error at CHMOD-invalid-path\.cmake:[0-9]+ \(file\):
-  file does not exist:
-
-  .*/chmod-tests/I_dont_exist
-Call Stack \(most recent call first\):
-  CMakeLists\.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/file-CHMOD/CHMOD-invalid-path.cmake b/Tests/RunCMake/file-CHMOD/CHMOD-invalid-path.cmake
deleted file mode 100644
index 36915c1..0000000
--- a/Tests/RunCMake/file-CHMOD/CHMOD-invalid-path.cmake
+++ /dev/null
@@ -1,4 +0,0 @@
-file(REMOVE_RECURSE ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
-file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
-
-file(CHMOD ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/I_dont_exist PERMISSIONS OWNER_READ)
diff --git a/Tests/RunCMake/file-CHMOD/CHMOD-invalid-perms-stderr.txt b/Tests/RunCMake/file-CHMOD/CHMOD-invalid-perms-stderr.txt
deleted file mode 100644
index 84ba2a2..0000000
--- a/Tests/RunCMake/file-CHMOD/CHMOD-invalid-perms-stderr.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-CMake Error at CHMOD-invalid-perms\.cmake:[0-9]+ \(file\):
-  file INVALID_PERMISSION is an invalid permission specifier
-Call Stack \(most recent call first\):
-  CMakeLists\.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/file-CHMOD/CHMOD-invalid-perms.cmake b/Tests/RunCMake/file-CHMOD/CHMOD-invalid-perms.cmake
deleted file mode 100644
index 22cab0b..0000000
--- a/Tests/RunCMake/file-CHMOD/CHMOD-invalid-perms.cmake
+++ /dev/null
@@ -1,5 +0,0 @@
-file(REMOVE_RECURSE ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
-file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
-
-file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a)
-file(CHMOD ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a PERMISSIONS INVALID_PERMISSION)
diff --git a/Tests/RunCMake/file-CHMOD/CHMOD-no-keyword-result.txt b/Tests/RunCMake/file-CHMOD/CHMOD-no-keyword-result.txt
deleted file mode 100644
index d00491f..0000000
--- a/Tests/RunCMake/file-CHMOD/CHMOD-no-keyword-result.txt
+++ /dev/null
@@ -1 +0,0 @@
-1
diff --git a/Tests/RunCMake/file-CHMOD/CHMOD-no-keyword-stderr.txt b/Tests/RunCMake/file-CHMOD/CHMOD-no-keyword-stderr.txt
deleted file mode 100644
index 2c248f8..0000000
--- a/Tests/RunCMake/file-CHMOD/CHMOD-no-keyword-stderr.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-CMake Error at CHMOD-no-keyword\.cmake:[0-9]+ \(file\):
-  file No permissions given
-Call Stack \(most recent call first\):
-  CMakeLists\.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/file-CHMOD/CHMOD-no-keyword.cmake b/Tests/RunCMake/file-CHMOD/CHMOD-no-keyword.cmake
deleted file mode 100644
index 8b62106..0000000
--- a/Tests/RunCMake/file-CHMOD/CHMOD-no-keyword.cmake
+++ /dev/null
@@ -1,5 +0,0 @@
-file(REMOVE_RECURSE ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
-file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
-
-file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a)
-file(CHMOD ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a)
diff --git a/Tests/RunCMake/file-CHMOD/CHMOD-no-perms-stderr.txt b/Tests/RunCMake/file-CHMOD/CHMOD-no-perms-stderr.txt
deleted file mode 100644
index a18609f..0000000
--- a/Tests/RunCMake/file-CHMOD/CHMOD-no-perms-stderr.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-CMake Error at CHMOD-no-perms\.cmake:[0-9]+ \(file\):
-  file No permissions given
-Call Stack \(most recent call first\):
-  CMakeLists\.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/file-CHMOD/CHMOD-no-perms.cmake b/Tests/RunCMake/file-CHMOD/CHMOD-no-perms.cmake
deleted file mode 100644
index 9fbd359..0000000
--- a/Tests/RunCMake/file-CHMOD/CHMOD-no-perms.cmake
+++ /dev/null
@@ -1,5 +0,0 @@
-file(REMOVE_RECURSE ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
-file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
-
-file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a)
-file(CHMOD ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a PERMISSIONS)
diff --git a/Tests/RunCMake/file-CHMOD/CHMOD-ok.cmake b/Tests/RunCMake/file-CHMOD/CHMOD-ok.cmake
deleted file mode 100644
index 87e3e57..0000000
--- a/Tests/RunCMake/file-CHMOD/CHMOD-ok.cmake
+++ /dev/null
@@ -1,5 +0,0 @@
-file(REMOVE_RECURSE ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
-file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
-
-file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a)
-file(CHMOD ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a PERMISSIONS OWNER_READ)
diff --git a/Tests/RunCMake/file-CHMOD/CHMOD-override.cmake b/Tests/RunCMake/file-CHMOD/CHMOD-override.cmake
deleted file mode 100644
index d9226b8..0000000
--- a/Tests/RunCMake/file-CHMOD/CHMOD-override.cmake
+++ /dev/null
@@ -1,6 +0,0 @@
-file(REMOVE_RECURSE ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
-file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
-
-file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a)
-file(CHMOD ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a PERMISSIONS OWNER_READ
-    FILE_PERMISSIONS OWNER_READ OWNER_WRITE)
diff --git a/Tests/RunCMake/file-CHMOD/CHMOD-write-only-stderr.txt b/Tests/RunCMake/file-CHMOD/CHMOD-write-only-stderr.txt
deleted file mode 100644
index 1c87a59..0000000
--- a/Tests/RunCMake/file-CHMOD/CHMOD-write-only-stderr.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-CMake Error at CHMOD-write-only\.cmake:[0-9]+ \(file\):
-  file failed to open for reading \(Permission denied\):
-
-    .*/chmod-tests/a
-Call Stack \(most recent call first\):
-  CMakeLists\.txt:[0-9]+ \(include\)
diff --git a/Tests/RunCMake/file-CHMOD/CHMOD-write-only.cmake b/Tests/RunCMake/file-CHMOD/CHMOD-write-only.cmake
deleted file mode 100644
index 1289efc..0000000
--- a/Tests/RunCMake/file-CHMOD/CHMOD-write-only.cmake
+++ /dev/null
@@ -1,6 +0,0 @@
-file(REMOVE_RECURSE ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
-file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests)
-
-file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a "CONTENT")
-file(CHMOD ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a PERMISSIONS OWNER_WRITE)
-file(READ ${CMAKE_CURRENT_BINARY_DIR}/chmod-tests/a content)
diff --git a/Tests/RunCMake/file-CHMOD/RunCMakeTest.cmake b/Tests/RunCMake/file-CHMOD/RunCMakeTest.cmake
index 18deb89..e6b1169 100644
--- a/Tests/RunCMake/file-CHMOD/RunCMakeTest.cmake
+++ b/Tests/RunCMake/file-CHMOD/RunCMakeTest.cmake
@@ -1,12 +1,14 @@
 include(RunCMake)
 
-run_cmake(CHMOD-no-perms)
-run_cmake(CHMOD-no-keyword)
-run_cmake(CHMOD-all-perms)
-run_cmake(CHMOD-invalid-perms)
-run_cmake(CHMOD-invalid-path)
-run_cmake(CHMOD-ok)
-run_cmake(CHMOD-override)
+run_cmake_script(no-perms)
+run_cmake_script(missing-perms)
+run_cmake_script(missing-file-perms)
+run_cmake_script(missing-dir-perms)
+run_cmake_script(all-perms)
+run_cmake_script(invalid-perms)
+run_cmake_script(invalid-path)
+run_cmake_script(ok)
+run_cmake_script(override)
 
 if(UNIX)
   execute_process(COMMAND id -u $ENV{USER}
@@ -15,5 +17,5 @@
 endif()
 
 if(NOT WIN32 AND NOT MSYS AND NOT "${uid}" STREQUAL "0")
-  run_cmake(CHMOD-write-only)
+  run_cmake_script(write-only)
 endif()
diff --git a/Tests/RunCMake/file-CHMOD/CHMOD-all-perms-result.txt b/Tests/RunCMake/file-CHMOD/all-perms-result.txt
similarity index 100%
rename from Tests/RunCMake/file-CHMOD/CHMOD-all-perms-result.txt
rename to Tests/RunCMake/file-CHMOD/all-perms-result.txt
diff --git a/Tests/RunCMake/file-CHMOD/all-perms-stderr.txt b/Tests/RunCMake/file-CHMOD/all-perms-stderr.txt
new file mode 100644
index 0000000..6932a1f
--- /dev/null
+++ b/Tests/RunCMake/file-CHMOD/all-perms-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Error at [^
+]*/all-perms\.cmake:[0-9]+ \(file\):
+  file Remove either PERMISSIONS or FILE_PERMISSIONS or DIRECTORY_PERMISSIONS
+  from the invocation$
diff --git a/Tests/RunCMake/file-CHMOD/all-perms.cmake b/Tests/RunCMake/file-CHMOD/all-perms.cmake
new file mode 100644
index 0000000..5ff81b8
--- /dev/null
+++ b/Tests/RunCMake/file-CHMOD/all-perms.cmake
@@ -0,0 +1,3 @@
+file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/a)
+file(CHMOD ${CMAKE_CURRENT_BINARY_DIR}/a PERMISSIONS OWNER_READ
+  FILE_PERMISSIONS OWNER_READ DIRECTORY_PERMISSIONS OWNER_READ)
diff --git a/Tests/RunCMake/file-CHMOD/CHMOD-invalid-path-result.txt b/Tests/RunCMake/file-CHMOD/invalid-path-result.txt
similarity index 100%
rename from Tests/RunCMake/file-CHMOD/CHMOD-invalid-path-result.txt
rename to Tests/RunCMake/file-CHMOD/invalid-path-result.txt
diff --git a/Tests/RunCMake/file-CHMOD/invalid-path-stderr.txt b/Tests/RunCMake/file-CHMOD/invalid-path-stderr.txt
new file mode 100644
index 0000000..eb5fb31
--- /dev/null
+++ b/Tests/RunCMake/file-CHMOD/invalid-path-stderr.txt
@@ -0,0 +1,6 @@
+^CMake Error at [^
+]*/invalid-path\.cmake:[0-9]+ \(file\):
+  file does not exist:
+
+    [^
+]*/Tests/RunCMake/file-CHMOD/invalid-path-build/I_dont_exist$
diff --git a/Tests/RunCMake/file-CHMOD/invalid-path.cmake b/Tests/RunCMake/file-CHMOD/invalid-path.cmake
new file mode 100644
index 0000000..e8b0313
--- /dev/null
+++ b/Tests/RunCMake/file-CHMOD/invalid-path.cmake
@@ -0,0 +1 @@
+file(CHMOD ${CMAKE_CURRENT_BINARY_DIR}/I_dont_exist PERMISSIONS OWNER_READ)
diff --git a/Tests/RunCMake/file-CHMOD/CHMOD-invalid-perms-result.txt b/Tests/RunCMake/file-CHMOD/invalid-perms-result.txt
similarity index 100%
rename from Tests/RunCMake/file-CHMOD/CHMOD-invalid-perms-result.txt
rename to Tests/RunCMake/file-CHMOD/invalid-perms-result.txt
diff --git a/Tests/RunCMake/file-CHMOD/invalid-perms-stderr.txt b/Tests/RunCMake/file-CHMOD/invalid-perms-stderr.txt
new file mode 100644
index 0000000..daab22e
--- /dev/null
+++ b/Tests/RunCMake/file-CHMOD/invalid-perms-stderr.txt
@@ -0,0 +1,3 @@
+^CMake Error at [^
+]*/invalid-perms\.cmake:[0-9]+ \(file\):
+  file INVALID_PERMISSION is an invalid permission specifier$
diff --git a/Tests/RunCMake/file-CHMOD/invalid-perms.cmake b/Tests/RunCMake/file-CHMOD/invalid-perms.cmake
new file mode 100644
index 0000000..42129b9
--- /dev/null
+++ b/Tests/RunCMake/file-CHMOD/invalid-perms.cmake
@@ -0,0 +1,2 @@
+file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/a)
+file(CHMOD ${CMAKE_CURRENT_BINARY_DIR}/a PERMISSIONS INVALID_PERMISSION)
diff --git a/Tests/RunCMake/file-CHMOD/CHMOD-all-perms-result.txt b/Tests/RunCMake/file-CHMOD/missing-dir-perms-result.txt
similarity index 100%
copy from Tests/RunCMake/file-CHMOD/CHMOD-all-perms-result.txt
copy to Tests/RunCMake/file-CHMOD/missing-dir-perms-result.txt
diff --git a/Tests/RunCMake/file-CHMOD/missing-dir-perms-stderr.txt b/Tests/RunCMake/file-CHMOD/missing-dir-perms-stderr.txt
new file mode 100644
index 0000000..c05bb2d
--- /dev/null
+++ b/Tests/RunCMake/file-CHMOD/missing-dir-perms-stderr.txt
@@ -0,0 +1,5 @@
+^CMake Error at [^
+]*/missing-dir-perms.cmake:[0-9]+ \(file\):
+  Error after keyword "DIRECTORY_PERMISSIONS":
+
+    missing required value$
diff --git a/Tests/RunCMake/file-CHMOD/missing-dir-perms.cmake b/Tests/RunCMake/file-CHMOD/missing-dir-perms.cmake
new file mode 100644
index 0000000..9d0fdad
--- /dev/null
+++ b/Tests/RunCMake/file-CHMOD/missing-dir-perms.cmake
@@ -0,0 +1,2 @@
+file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/a)
+file(CHMOD ${CMAKE_CURRENT_BINARY_DIR}/a PERMISSIONS OWNER_READ DIRECTORY_PERMISSIONS)
diff --git a/Tests/RunCMake/file-CHMOD/CHMOD-all-perms-result.txt b/Tests/RunCMake/file-CHMOD/missing-file-perms-result.txt
similarity index 100%
copy from Tests/RunCMake/file-CHMOD/CHMOD-all-perms-result.txt
copy to Tests/RunCMake/file-CHMOD/missing-file-perms-result.txt
diff --git a/Tests/RunCMake/file-CHMOD/missing-file-perms-stderr.txt b/Tests/RunCMake/file-CHMOD/missing-file-perms-stderr.txt
new file mode 100644
index 0000000..c3f4f0f
--- /dev/null
+++ b/Tests/RunCMake/file-CHMOD/missing-file-perms-stderr.txt
@@ -0,0 +1,5 @@
+^CMake Error at [^
+]*/missing-file-perms.cmake:[0-9]+ \(file\):
+  Error after keyword "FILE_PERMISSIONS":
+
+    missing required value$
diff --git a/Tests/RunCMake/file-CHMOD/missing-file-perms.cmake b/Tests/RunCMake/file-CHMOD/missing-file-perms.cmake
new file mode 100644
index 0000000..bcf35aa
--- /dev/null
+++ b/Tests/RunCMake/file-CHMOD/missing-file-perms.cmake
@@ -0,0 +1,2 @@
+file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/a)
+file(CHMOD ${CMAKE_CURRENT_BINARY_DIR}/a PERMISSIONS OWNER_READ FILE_PERMISSIONS)
diff --git a/Tests/RunCMake/file-CHMOD/CHMOD-no-perms-result.txt b/Tests/RunCMake/file-CHMOD/missing-perms-result.txt
similarity index 100%
copy from Tests/RunCMake/file-CHMOD/CHMOD-no-perms-result.txt
copy to Tests/RunCMake/file-CHMOD/missing-perms-result.txt
diff --git a/Tests/RunCMake/file-CHMOD/missing-perms-stderr.txt b/Tests/RunCMake/file-CHMOD/missing-perms-stderr.txt
new file mode 100644
index 0000000..1508b13
--- /dev/null
+++ b/Tests/RunCMake/file-CHMOD/missing-perms-stderr.txt
@@ -0,0 +1,5 @@
+^CMake Error at [^
+]*/missing-perms.cmake:[0-9]+ \(file\):
+  Error after keyword "PERMISSIONS":
+
+    missing required value$
diff --git a/Tests/RunCMake/file-CHMOD/missing-perms.cmake b/Tests/RunCMake/file-CHMOD/missing-perms.cmake
new file mode 100644
index 0000000..da9f182
--- /dev/null
+++ b/Tests/RunCMake/file-CHMOD/missing-perms.cmake
@@ -0,0 +1,2 @@
+file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/a)
+file(CHMOD ${CMAKE_CURRENT_BINARY_DIR}/a FILE_PERMISSIONS OWNER_READ PERMISSIONS)
diff --git a/Tests/RunCMake/file-CHMOD/CHMOD-no-perms-result.txt b/Tests/RunCMake/file-CHMOD/no-perms-result.txt
similarity index 100%
rename from Tests/RunCMake/file-CHMOD/CHMOD-no-perms-result.txt
rename to Tests/RunCMake/file-CHMOD/no-perms-result.txt
diff --git a/Tests/RunCMake/file-CHMOD/no-perms-stderr.txt b/Tests/RunCMake/file-CHMOD/no-perms-stderr.txt
new file mode 100644
index 0000000..4c5a139
--- /dev/null
+++ b/Tests/RunCMake/file-CHMOD/no-perms-stderr.txt
@@ -0,0 +1,3 @@
+^CMake Error at [^
+]*/no-perms\.cmake:[0-9]+ \(file\):
+  file No permissions given$
diff --git a/Tests/RunCMake/file-CHMOD/no-perms.cmake b/Tests/RunCMake/file-CHMOD/no-perms.cmake
new file mode 100644
index 0000000..602cfc2
--- /dev/null
+++ b/Tests/RunCMake/file-CHMOD/no-perms.cmake
@@ -0,0 +1,2 @@
+file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/a)
+file(CHMOD ${CMAKE_CURRENT_BINARY_DIR}/a)
diff --git a/Tests/RunCMake/file-CHMOD/ok.cmake b/Tests/RunCMake/file-CHMOD/ok.cmake
new file mode 100644
index 0000000..7c74d27
--- /dev/null
+++ b/Tests/RunCMake/file-CHMOD/ok.cmake
@@ -0,0 +1,2 @@
+file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/a)
+file(CHMOD ${CMAKE_CURRENT_BINARY_DIR}/a PERMISSIONS OWNER_READ)
diff --git a/Tests/RunCMake/file-CHMOD/override.cmake b/Tests/RunCMake/file-CHMOD/override.cmake
new file mode 100644
index 0000000..67e5a23
--- /dev/null
+++ b/Tests/RunCMake/file-CHMOD/override.cmake
@@ -0,0 +1,3 @@
+file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/a)
+file(CHMOD ${CMAKE_CURRENT_BINARY_DIR}/a PERMISSIONS OWNER_READ
+    FILE_PERMISSIONS OWNER_READ OWNER_WRITE)
diff --git a/Tests/RunCMake/file-CHMOD/CHMOD-write-only-result.txt b/Tests/RunCMake/file-CHMOD/write-only-result.txt
similarity index 100%
rename from Tests/RunCMake/file-CHMOD/CHMOD-write-only-result.txt
rename to Tests/RunCMake/file-CHMOD/write-only-result.txt
diff --git a/Tests/RunCMake/file-CHMOD/write-only-stderr.txt b/Tests/RunCMake/file-CHMOD/write-only-stderr.txt
new file mode 100644
index 0000000..169a092
--- /dev/null
+++ b/Tests/RunCMake/file-CHMOD/write-only-stderr.txt
@@ -0,0 +1,6 @@
+^CMake Error at [^
+]*/write-only\.cmake:[0-9]+ \(file\):
+  file failed to open for reading \(Permission denied\):
+
+    [^
+]*/Tests/RunCMake/file-CHMOD/write-only-build/a$
diff --git a/Tests/RunCMake/file-CHMOD/write-only.cmake b/Tests/RunCMake/file-CHMOD/write-only.cmake
new file mode 100644
index 0000000..aa9d803
--- /dev/null
+++ b/Tests/RunCMake/file-CHMOD/write-only.cmake
@@ -0,0 +1,3 @@
+file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/a "CONTENT")
+file(CHMOD ${CMAKE_CURRENT_BINARY_DIR}/a PERMISSIONS OWNER_WRITE)
+file(READ ${CMAKE_CURRENT_BINARY_DIR}/a content)
diff --git a/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/badargs2-stderr.txt b/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/badargs2-stderr.txt
index c6ad3d0..39f307d 100644
--- a/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/badargs2-stderr.txt
+++ b/Tests/RunCMake/file-GET_RUNTIME_DEPENDENCIES/badargs2-stderr.txt
@@ -13,11 +13,21 @@
 This warning is for project developers\.  Use -Wno-dev to suppress it\.
 
 CMake Error at badargs2\.cmake:[0-9]+ \(file\):
-  file Keywords missing values:
+  Error after keyword "BUNDLE_EXECUTABLE":
 
-    RESOLVED_DEPENDENCIES_VAR
-    UNRESOLVED_DEPENDENCIES_VAR
-    CONFLICTING_DEPENDENCIES_PREFIX
-    BUNDLE_EXECUTABLE
+    missing required value
+
+  Error after keyword "CONFLICTING_DEPENDENCIES_PREFIX":
+
+    missing required value
+
+  Error after keyword "RESOLVED_DEPENDENCIES_VAR":
+
+    missing required value
+
+  Error after keyword "UNRESOLVED_DEPENDENCIES_VAR":
+
+    missing required value
+
 Call Stack \(most recent call first\):
   CMakeLists\.txt:[0-9]+ \(include\)$
diff --git a/Tests/RunCMake/file/REAL_PATH-no-base-dir-stderr.txt b/Tests/RunCMake/file/REAL_PATH-no-base-dir-stderr.txt
index 7c58aeb..54a3e5a 100644
--- a/Tests/RunCMake/file/REAL_PATH-no-base-dir-stderr.txt
+++ b/Tests/RunCMake/file/REAL_PATH-no-base-dir-stderr.txt
@@ -1,2 +1,7 @@
-CMake Error at REAL_PATH-no-base-dir.cmake:[0-9]+ \(file\):
-  file BASE_DIRECTORY requires a value
+^CMake Error at REAL_PATH-no-base-dir.cmake:[0-9]+ \(file\):
+  Error after keyword "BASE_DIRECTORY":
+
+    missing required value
+
+Call Stack \(most recent call first\):
+  CMakeLists.txt:[0-9]+ \(include\)$
diff --git a/Utilities/IWYU/mapping.imp b/Utilities/IWYU/mapping.imp
index b80fc22..b0ed911 100644
--- a/Utilities/IWYU/mapping.imp
+++ b/Utilities/IWYU/mapping.imp
@@ -91,7 +91,7 @@
   { symbol: [ "std::__decay_and_strip<cmFindPackageCommand::PathLabel &>::__type", private, "\"cmConfigure.h\"", public ] },
   { symbol: [ "std::__decay_and_strip<cmGlobalNinjaGenerator::TargetAlias &>::__type", private, "\"cmConfigure.h\"", public ] },
   { symbol: [ "std::__decay_and_strip<__gnu_cxx::__normal_iterator<const cmCTestTestHandler::cmCTestTestProperties *, std::vector<cmCTestTestHandler::cmCTestTestProperties, std::allocator<cmCTestTestHandler::cmCTestTestProperties> > > &>::__type", private, "\"cmConfigure.h\"", public ] },
-  { symbol: [ "std::__decay_and_strip<const __gnu_cxx::__normal_iterator<std::pair<cm::string_view, std::function<void (ArgumentParser::Instance &, void *)> > *, std::vector<std::pair<cm::string_view, std::function<void (ArgumentParser::Instance &, void *)> >, std::allocator<std::pair<cm::string_view, std::function<void (ArgumentParser::Instance &, void *)> > > > > &>::__type", private, "\"cmConfigure.h\"", public ] },
+  { symbol: [ "std::__decay_and_strip<const __gnu_cxx::__normal_iterator<std::pair<cm::string_view, std::function<void (ArgumentParser::Instance &)> > *, std::vector<std::pair<cm::string_view, std::function<void (ArgumentParser::Instance &)> >, std::allocator<std::pair<cm::string_view, std::function<void (ArgumentParser::Instance &)> > > > > &>::__type", private, "\"cmConfigure.h\"", public ] },
   { symbol: [ "std::__success_type<std::chrono::duration<double, std::ratio<1, 1> > >::type", private, "\"cmConfigure.h\"", public ] },
   { symbol: [ "std::__success_type<std::chrono::duration<long, std::ratio<1, 1000000000> > >::type", private, "\"cmConfigure.h\"", public ] },
   { symbol: [ "std::enable_if<true, std::chrono::duration<long, std::ratio<1, 1> > >::type", private, "\"cmConfigure.h\"", public ] },