Browse Source

Merge topic 'command-arg-parser'

4368a524c6 cmCMakePathCommand: Enforce non-empty string arguments via binding type
7ca8d9f0f8 cmArgumentParser: Model non-empty strings with wrapper type

Acked-by: Kitware Robot <[email protected]>
Acked-by: buildbot <[email protected]>
Merge-request: !7512
Brad King 3 years ago
parent
commit
514804c8bc

+ 14 - 0
Source/cmArgumentParser.cxx

@@ -76,6 +76,20 @@ void Instance::Bind(std::string& val)
     ExpectAtLeast{ 1 });
 }
 
+void Instance::Bind(NonEmpty<std::string>& val)
+{
+  this->Bind(
+    [this, &val](cm::string_view arg) -> Continue {
+      if (arg.empty() && this->ParseResults) {
+        this->ParseResults->AddKeywordError(this->Keyword,
+                                            "  empty string not allowed\n");
+      }
+      val.assign(std::string(arg));
+      return Continue::No;
+    },
+    ExpectAtLeast{ 1 });
+}
+
 void Instance::Bind(Maybe<std::string>& val)
 {
   this->Bind(

+ 1 - 0
Source/cmArgumentParser.h

@@ -171,6 +171,7 @@ public:
   void Bind(std::function<Continue(cm::string_view)> f, ExpectAtLeast expect);
   void Bind(bool& val);
   void Bind(std::string& val);
+  void Bind(NonEmpty<std::string>& val);
   void Bind(Maybe<std::string>& val);
   void Bind(MaybeEmpty<std::vector<std::string>>& val);
   void Bind(NonEmpty<std::vector<std::string>>& val);

+ 5 - 0
Source/cmArgumentParserTypes.h

@@ -34,6 +34,11 @@ struct NonEmpty<std::vector<T>> : public std::vector<T>
 {
   using std::vector<T>::vector;
 };
+template <>
+struct NonEmpty<std::string> : public std::string
+{
+  using std::string::basic_string;
+};
 
 } // namespace ArgumentParser
 

+ 5 - 39
Source/cmCMakePathCommand.cxx

@@ -15,6 +15,7 @@
 #include <cmext/string_view>
 
 #include "cmArgumentParser.h"
+#include "cmArgumentParserTypes.h"
 #include "cmCMakePath.h"
 #include "cmExecutionStatus.h"
 #include "cmMakefile.h"
@@ -82,22 +83,11 @@ public:
     return this->CMakePathArgumentParser<Result>::template Parse<Advance>(
       args);
   }
-
-  bool checkOutputVariable(const Result& arguments,
-                           cmExecutionStatus& status) const
-  {
-    if (arguments.Output && arguments.Output->empty()) {
-      status.SetError("Invalid name for output variable.");
-      return false;
-    }
-
-    return true;
-  }
 };
 
 struct OutputVariable : public ArgumentParser::ParseResult
 {
-  cm::optional<std::string> Output;
+  cm::optional<ArgumentParser::NonEmpty<std::string>> Output;
 };
 // Usable when OUTPUT_VARIABLE is the only option
 class OutputVariableParser
@@ -270,9 +260,6 @@ bool HandleAppendCommand(std::vector<std::string> const& args,
   if (arguments.MaybeReportError(status.GetMakefile())) {
     return true;
   }
-  if (!parser.checkOutputVariable(arguments, status)) {
-    return false;
-  }
 
   cmCMakePath path(status.GetMakefile().GetSafeDefinition(args[1]));
   for (const auto& input : parser.GetInputs()) {
@@ -295,9 +282,6 @@ bool HandleAppendStringCommand(std::vector<std::string> const& args,
   if (arguments.MaybeReportError(status.GetMakefile())) {
     return true;
   }
-  if (!parser.checkOutputVariable(arguments, status)) {
-    return false;
-  }
 
   std::string inputPath;
   if (!getInputPath(args[1], status, inputPath)) {
@@ -325,9 +309,6 @@ bool HandleRemoveFilenameCommand(std::vector<std::string> const& args,
   if (arguments.MaybeReportError(status.GetMakefile())) {
     return true;
   }
-  if (!parser.checkOutputVariable(arguments, status)) {
-    return false;
-  }
 
   if (!parser.GetInputs().empty()) {
     status.SetError("REMOVE_FILENAME called with unexpected arguments.");
@@ -358,9 +339,6 @@ bool HandleReplaceFilenameCommand(std::vector<std::string> const& args,
   if (arguments.MaybeReportError(status.GetMakefile())) {
     return true;
   }
-  if (!parser.checkOutputVariable(arguments, status)) {
-    return false;
-  }
 
   if (parser.GetInputs().size() > 1) {
     status.SetError("REPLACE_FILENAME called with unexpected arguments.");
@@ -387,7 +365,7 @@ bool HandleRemoveExtensionCommand(std::vector<std::string> const& args,
 {
   struct Arguments : public ArgumentParser::ParseResult
   {
-    cm::optional<std::string> Output;
+    cm::optional<ArgumentParser::NonEmpty<std::string>> Output;
     bool LastOnly = false;
   };
 
@@ -400,9 +378,6 @@ bool HandleRemoveExtensionCommand(std::vector<std::string> const& args,
   if (arguments.MaybeReportError(status.GetMakefile())) {
     return true;
   }
-  if (!parser.checkOutputVariable(arguments, status)) {
-    return false;
-  }
 
   if (!parser.GetInputs().empty()) {
     status.SetError("REMOVE_EXTENSION called with unexpected arguments.");
@@ -433,7 +408,7 @@ bool HandleReplaceExtensionCommand(std::vector<std::string> const& args,
 {
   struct Arguments : public ArgumentParser::ParseResult
   {
-    cm::optional<std::string> Output;
+    cm::optional<ArgumentParser::NonEmpty<std::string>> Output;
     bool LastOnly = false;
   };
 
@@ -446,9 +421,6 @@ bool HandleReplaceExtensionCommand(std::vector<std::string> const& args,
   if (arguments.MaybeReportError(status.GetMakefile())) {
     return true;
   }
-  if (!parser.checkOutputVariable(arguments, status)) {
-    return false;
-  }
 
   if (parser.GetInputs().size() > 1) {
     status.SetError("REPLACE_EXTENSION called with unexpected arguments.");
@@ -486,9 +458,6 @@ bool HandleNormalPathCommand(std::vector<std::string> const& args,
   if (arguments.MaybeReportError(status.GetMakefile())) {
     return true;
   }
-  if (!parser.checkOutputVariable(arguments, status)) {
-    return false;
-  }
 
   if (!parser.GetInputs().empty()) {
     status.SetError("NORMAL_PATH called with unexpected arguments.");
@@ -516,7 +485,7 @@ bool HandleTransformPathCommand(
 {
   struct Arguments : public ArgumentParser::ParseResult
   {
-    cm::optional<std::string> Output;
+    cm::optional<ArgumentParser::NonEmpty<std::string>> Output;
     cm::optional<std::string> BaseDirectory;
     bool Normalize = false;
   };
@@ -532,9 +501,6 @@ bool HandleTransformPathCommand(
   if (arguments.MaybeReportError(status.GetMakefile())) {
     return true;
   }
-  if (!parser.checkOutputVariable(arguments, status)) {
-    return false;
-  }
 
   if (!parser.GetInputs().empty()) {
     status.SetError(cmStrCat(args[0], " called with unexpected arguments."));

+ 16 - 0
Tests/CMakeLib/testArgumentParser.cxx

@@ -27,6 +27,8 @@ struct Result : public ArgumentParser::ParseResult
   cm::optional<std::string> String2;
   cm::optional<std::string> String3;
   ArgumentParser::Maybe<std::string> String4;
+  ArgumentParser::NonEmpty<std::string> String5;
+  ArgumentParser::NonEmpty<std::string> String6;
 
   ArgumentParser::NonEmpty<std::vector<std::string>> List1;
   ArgumentParser::NonEmpty<std::vector<std::string>> List2;
@@ -87,6 +89,8 @@ struct Result : public ArgumentParser::ParseResult
   ArgumentParser::NonEmpty<std::vector<std::string>> UnboundNonEmpty{
     1, "unbound"
   };
+  ArgumentParser::NonEmpty<std::string> UnboundNonEmptyStr{ 'u', 'n', 'b', 'o',
+                                                            'u', 'n', 'd' };
 
   std::vector<cm::string_view> ParsedKeywords;
 };
@@ -100,6 +104,8 @@ std::initializer_list<cm::string_view> const args = {
   "STRING_2", "foo", "bar",  // string arg + unparsed value, presence captured
   // "STRING_3",             // string arg that is not present
   "STRING_4",                // string arg allowed to be missing value
+  "STRING_5", "foo",         // string arg that is not empty
+  "STRING_6", "",            // string arg that is empty
   "LIST_1",                  // list arg missing values
   "LIST_2", "foo", "bar",    // list arg with 2 elems
   "LIST_3", "bar",           // list arg ...
@@ -133,6 +139,8 @@ bool verifyResult(Result const& result,
     "STRING_1",
     "STRING_2",
     "STRING_4",
+    "STRING_5",
+    "STRING_6",
     "LIST_1",
     "LIST_2",
     "LIST_3",
@@ -159,6 +167,7 @@ bool verifyResult(Result const& result,
   };
   static std::map<cm::string_view, std::string> const keywordErrors = {
     { "STRING_1"_s, "  missing required value\n" },
+    { "STRING_6"_s, "  empty string not allowed\n" },
     { "LIST_1"_s, "  missing required value\n" },
     { "LIST_4"_s, "  missing required value\n" },
     { "FUNC_0"_s, "  missing required value\n" }
@@ -184,6 +193,8 @@ bool verifyResult(Result const& result,
   ASSERT_TRUE(*result.String2 == "foo");
   ASSERT_TRUE(!result.String3);
   ASSERT_TRUE(result.String4.empty());
+  ASSERT_TRUE(result.String5 == "foo");
+  ASSERT_TRUE(result.String6.empty());
 
   ASSERT_TRUE(result.List1.empty());
   ASSERT_TRUE(result.List2 == foobar);
@@ -217,6 +228,7 @@ bool verifyResult(Result const& result,
   ASSERT_TRUE(result.UnboundMaybe == "unbound");
   ASSERT_TRUE(result.UnboundMaybeEmpty == unbound);
   ASSERT_TRUE(result.UnboundNonEmpty == unbound);
+  ASSERT_TRUE(result.UnboundNonEmptyStr == "unbound");
 
   ASSERT_TRUE(result.ParsedKeywords == parsedKeywords);
 
@@ -249,6 +261,8 @@ bool testArgumentParserDynamic()
       .Bind("STRING_2"_s, result.String2)
       .Bind("STRING_3"_s, result.String3)
       .Bind("STRING_4"_s, result.String4)
+      .Bind("STRING_5"_s, result.String5)
+      .Bind("STRING_6"_s, result.String6)
       .Bind("LIST_1"_s, result.List1)
       .Bind("LIST_2"_s, result.List2)
       .Bind("LIST_3"_s, result.List3)
@@ -299,6 +313,8 @@ static auto const parserStatic = //
     .Bind("STRING_2"_s, &Result::String2)
     .Bind("STRING_3"_s, &Result::String3)
     .Bind("STRING_4"_s, &Result::String4)
+    .Bind("STRING_5"_s, &Result::String5)
+    .Bind("STRING_6"_s, &Result::String6)
     .Bind("LIST_1"_s, &Result::List1)
     .Bind("LIST_2"_s, &Result::List2)
     .Bind("LIST_3"_s, &Result::List3)

+ 4 - 0
Tests/RunCMake/cmake_path/OUTPUT_VARIABLE-empty-stderr.txt

@@ -0,0 +1,4 @@
+CMake Error at .+/call-cmake_path.cmake:[0-9]+ \(cmake_path\):
+  Error after keyword "OUTPUT_VARIABLE":
+
+    empty string not allowed

+ 3 - 0
Tests/RunCMake/cmake_path/RunCMakeTest.cmake

@@ -114,6 +114,9 @@ foreach (command IN ITEMS NATIVE_PATH
   run_cmake_command (${command}-invalid-output "${CMAKE_COMMAND}" "-DCMAKE_PATH_ARGUMENTS=${command} path ${extra_args}" -DCHECK_INVALID_OUTPUT=ON -P "${RunCMake_SOURCE_DIR}/call-cmake_path.cmake")
 endforeach()
 
+# OUTPUT_VARIABLE empty name
+set (RunCMake-stderr-file "OUTPUT_VARIABLE-empty-stderr.txt")
+
 foreach (command IN ITEMS APPEND APPEND_STRING REMOVE_FILENAME REPLACE_FILENAME
                           REMOVE_EXTENSION REPLACE_EXTENSION NORMAL_PATH
                           RELATIVE_PATH ABSOLUTE_PATH)