Browse Source

Merge topic 'project-command-argument-parser'

e28e66db8d project: use argument parser

Acked-by: Kitware Robot <[email protected]>
Merge-request: !10673
Brad King 7 months ago
parent
commit
ebbf767b98
1 changed files with 125 additions and 168 deletions
  1. 125 168
      Source/cmProjectCommand.cxx

+ 125 - 168
Source/cmProjectCommand.cxx

@@ -4,14 +4,18 @@
 
 #include <array>
 #include <cstdio>
-#include <functional>
 #include <limits>
+#include <set>
 #include <utility>
 
+#include <cm/optional>
+#include <cm/string_view>
 #include <cmext/string_view>
 
 #include "cmsys/RegularExpression.hxx"
 
+#include "cmArgumentParser.h"
+#include "cmArgumentParserTypes.h"
 #include "cmExecutionStatus.h"
 #include "cmList.h"
 #include "cmMakefile.h"
@@ -22,15 +26,52 @@
 #include "cmSystemTools.h"
 #include "cmValue.h"
 
-static bool IncludeByVariable(cmExecutionStatus& status,
-                              std::string const& variable);
-static void TopLevelCMakeVarCondSet(cmMakefile& mf, std::string const& name,
-                                    std::string const& value);
+namespace {
+
+bool IncludeByVariable(cmExecutionStatus& status, std::string const& variable);
+void TopLevelCMakeVarCondSet(cmMakefile& mf, std::string const& name,
+                             std::string const& value);
+
+struct ProjectArguments : ArgumentParser::ParseResult
+{
+  cm::optional<std::string> ProjectName;
+  cm::optional<std::string> Version;
+  cm::optional<std::string> Description;
+  cm::optional<std::string> HomepageURL;
+  cm::optional<ArgumentParser::MaybeEmpty<std::vector<std::string>>> Languages;
+};
+
+struct ProjectArgumentParser : public cmArgumentParser<void>
+{
+  ProjectArgumentParser& BindKeywordMissingValue(
+    std::vector<cm::string_view>& ref)
+  {
+    this->cmArgumentParser<void>::BindKeywordMissingValue(
+      [&ref](Instance&, cm::string_view arg) { ref.emplace_back(arg); });
+    return *this;
+  }
+};
+
+} // namespace
 
 bool cmProjectCommand(std::vector<std::string> const& args,
                       cmExecutionStatus& status)
 {
-  if (args.empty()) {
+  std::vector<std::string> unparsedArgs;
+  std::vector<cm::string_view> missingValueKeywords;
+  std::vector<cm::string_view> parsedKeywords;
+  ProjectArguments prArgs;
+  ProjectArgumentParser{}
+    .BindKeywordMissingValue(missingValueKeywords)
+    .BindParsedKeywords(parsedKeywords)
+    .Bind(0, prArgs.ProjectName)
+    .Bind("VERSION"_s, prArgs.Version)
+    .Bind("DESCRIPTION"_s, prArgs.Description)
+    .Bind("HOMEPAGE_URL"_s, prArgs.HomepageURL)
+    .Bind("LANGUAGES"_s, prArgs.Languages)
+    .Parse(args, &unparsedArgs, 0);
+
+  if (!prArgs.ProjectName) {
     status.SetError("PROJECT called with incorrect number of arguments");
     return false;
   }
@@ -49,18 +90,16 @@ bool cmProjectCommand(std::vector<std::string> const& args,
     return false;
   }
 
-  std::string const& projectName = args[0];
-
-  if (!IncludeByVariable(status,
-                         "CMAKE_PROJECT_" + projectName + "_INCLUDE_BEFORE")) {
+  if (!IncludeByVariable(
+        status, "CMAKE_PROJECT_" + *prArgs.ProjectName + "_INCLUDE_BEFORE")) {
     return false;
   }
 
-  mf.SetProjectName(projectName);
+  mf.SetProjectName(*prArgs.ProjectName);
 
   cmPolicies::PolicyStatus cmp0180 = mf.GetPolicyStatus(cmPolicies::CMP0180);
 
-  std::string varName = cmStrCat(projectName, "_BINARY_DIR"_s);
+  std::string varName = cmStrCat(*prArgs.ProjectName, "_BINARY_DIR"_s);
   bool nonCacheVarAlreadySet = mf.IsNormalDefinitionSet(varName);
   mf.AddCacheDefinition(varName, mf.GetCurrentBinaryDirectory(),
                         "Value Computed by CMake", cmStateEnums::STATIC);
@@ -68,7 +107,7 @@ bool cmProjectCommand(std::vector<std::string> const& args,
     mf.AddDefinition(varName, mf.GetCurrentBinaryDirectory());
   }
 
-  varName = cmStrCat(projectName, "_SOURCE_DIR"_s);
+  varName = cmStrCat(*prArgs.ProjectName, "_SOURCE_DIR"_s);
   nonCacheVarAlreadySet = mf.IsNormalDefinitionSet(varName);
   mf.AddCacheDefinition(varName, mf.GetCurrentSourceDirectory(),
                         "Value Computed by CMake", cmStateEnums::STATIC);
@@ -79,11 +118,11 @@ bool cmProjectCommand(std::vector<std::string> const& args,
   mf.AddDefinition("PROJECT_BINARY_DIR", mf.GetCurrentBinaryDirectory());
   mf.AddDefinition("PROJECT_SOURCE_DIR", mf.GetCurrentSourceDirectory());
 
-  mf.AddDefinition("PROJECT_NAME", projectName);
+  mf.AddDefinition("PROJECT_NAME", *prArgs.ProjectName);
 
   mf.AddDefinitionBool("PROJECT_IS_TOP_LEVEL", mf.IsRootMakefile());
 
-  varName = cmStrCat(projectName, "_IS_TOP_LEVEL"_s);
+  varName = cmStrCat(*prArgs.ProjectName, "_IS_TOP_LEVEL"_s);
   nonCacheVarAlreadySet = mf.IsNormalDefinitionSet(varName);
   mf.AddCacheDefinition(varName, mf.IsRootMakefile() ? "ON" : "OFF",
                         "Value Computed by CMake", cmStateEnums::STATIC);
@@ -99,143 +138,52 @@ bool cmProjectCommand(std::vector<std::string> const& args,
   // will work.
   if (!mf.GetDefinition("CMAKE_PROJECT_NAME") || mf.IsRootMakefile()) {
     mf.RemoveDefinition("CMAKE_PROJECT_NAME");
-    mf.AddCacheDefinition("CMAKE_PROJECT_NAME", projectName,
+    mf.AddCacheDefinition("CMAKE_PROJECT_NAME", *prArgs.ProjectName,
                           "Value Computed by CMake", cmStateEnums::STATIC);
   }
 
-  bool haveVersion = false;
-  bool haveLanguages = false;
-  bool haveDescription = false;
-  bool haveHomepage = false;
-  std::string version;
-  std::string description;
-  std::string homepage;
-  std::vector<std::string> languages;
-  std::function<void()> missedValueReporter;
-  auto resetReporter = [&missedValueReporter]() {
-    missedValueReporter = std::function<void()>();
-  };
-  enum Doing
-  {
-    DoingDescription,
-    DoingHomepage,
-    DoingLanguages,
-    DoingVersion
-  };
-  Doing doing = DoingLanguages;
-  for (size_t i = 1; i < args.size(); ++i) {
-    if (args[i] == "LANGUAGES") {
-      if (haveLanguages) {
-        mf.IssueMessage(MessageType::FATAL_ERROR,
-                        "LANGUAGES may be specified at most once.");
-        cmSystemTools::SetFatalErrorOccurred();
-        return true;
-      }
-      haveLanguages = true;
-      if (missedValueReporter) {
-        missedValueReporter();
-      }
-      doing = DoingLanguages;
-      if (!languages.empty()) {
-        std::string msg = cmStrCat(
-          "the following parameters must be specified after LANGUAGES "
-          "keyword: ",
-          cmJoin(languages, ", "), '.');
-        mf.IssueMessage(MessageType::WARNING, msg);
-      }
-    } else if (args[i] == "VERSION") {
-      if (haveVersion) {
-        mf.IssueMessage(MessageType::FATAL_ERROR,
-                        "VERSION may be specified at most once.");
-        cmSystemTools::SetFatalErrorOccurred();
-        return true;
-      }
-      haveVersion = true;
-      if (missedValueReporter) {
-        missedValueReporter();
-      }
-      doing = DoingVersion;
-      missedValueReporter = [&mf, &resetReporter]() {
-        mf.IssueMessage(
-          MessageType::WARNING,
-          "VERSION keyword not followed by a value or was followed by a "
-          "value that expanded to nothing.");
-        resetReporter();
-      };
-    } else if (args[i] == "DESCRIPTION") {
-      if (haveDescription) {
-        mf.IssueMessage(MessageType::FATAL_ERROR,
-                        "DESCRIPTION may be specified at most once.");
-        cmSystemTools::SetFatalErrorOccurred();
-        return true;
-      }
-      haveDescription = true;
-      if (missedValueReporter) {
-        missedValueReporter();
-      }
-      doing = DoingDescription;
-      missedValueReporter = [&mf, &resetReporter]() {
-        mf.IssueMessage(
-          MessageType::WARNING,
-          "DESCRIPTION keyword not followed by a value or was followed "
-          "by a value that expanded to nothing.");
-        resetReporter();
-      };
-    } else if (args[i] == "HOMEPAGE_URL") {
-      if (haveHomepage) {
-        mf.IssueMessage(MessageType::FATAL_ERROR,
-                        "HOMEPAGE_URL may be specified at most once.");
-        cmSystemTools::SetFatalErrorOccurred();
-        return true;
-      }
-      haveHomepage = true;
-      doing = DoingHomepage;
-      missedValueReporter = [&mf, &resetReporter]() {
-        mf.IssueMessage(
-          MessageType::WARNING,
-          "HOMEPAGE_URL keyword not followed by a value or was followed "
-          "by a value that expanded to nothing.");
-        resetReporter();
-      };
-    } else if (doing == DoingVersion) {
-      doing = DoingLanguages;
-      version = args[i];
-      resetReporter();
-    } else if (doing == DoingDescription) {
-      doing = DoingLanguages;
-      description = args[i];
-      resetReporter();
-    } else if (doing == DoingHomepage) {
-      doing = DoingLanguages;
-      homepage = args[i];
-      resetReporter();
-    } else // doing == DoingLanguages
-    {
-      languages.push_back(args[i]);
+  std::set<cm::string_view> seenKeywords;
+  for (cm::string_view keyword : parsedKeywords) {
+    if (seenKeywords.find(keyword) != seenKeywords.end()) {
+      mf.IssueMessage(MessageType::FATAL_ERROR,
+                      cmStrCat(keyword, " may be specified at most once."));
+      cmSystemTools::SetFatalErrorOccurred();
+      return true;
     }
+    seenKeywords.insert(keyword);
   }
 
-  if (missedValueReporter) {
-    missedValueReporter();
+  for (cm::string_view keyword : missingValueKeywords) {
+    mf.IssueMessage(MessageType::WARNING,
+                    cmStrCat(keyword,
+                             " keyword not followed by a value or was "
+                             "followed by a value that expanded to nothing."));
   }
 
-  if ((haveVersion || haveDescription || haveHomepage) && !haveLanguages &&
-      !languages.empty()) {
-    mf.IssueMessage(MessageType::FATAL_ERROR,
-                    "project with VERSION, DESCRIPTION or HOMEPAGE_URL must "
-                    "use LANGUAGES before language names.");
-    cmSystemTools::SetFatalErrorOccurred();
-    return true;
-  }
-  if (haveLanguages && languages.empty()) {
-    languages.emplace_back("NONE");
+  if (!unparsedArgs.empty()) {
+    if (prArgs.Languages) {
+      mf.IssueMessage(
+        MessageType::WARNING,
+        cmStrCat("the following parameters must be specified after LANGUAGES "
+                 "keyword: ",
+                 cmJoin(unparsedArgs, ", "), '.'));
+    } else if (prArgs.Version || prArgs.Description || prArgs.HomepageURL) {
+      mf.IssueMessage(MessageType::FATAL_ERROR,
+                      "project with VERSION, DESCRIPTION or HOMEPAGE_URL must "
+                      "use LANGUAGES before language names.");
+      cmSystemTools::SetFatalErrorOccurred();
+      return true;
+    }
+  } else if (prArgs.Languages && prArgs.Languages->empty()) {
+    prArgs.Languages->emplace_back("NONE");
   }
 
-  if (haveVersion) {
+  if (prArgs.Version) {
     cmsys::RegularExpression vx(
       R"(^([0-9]+(\.[0-9]+(\.[0-9]+(\.[0-9]+)?)?)?)?$)");
-    if (!vx.find(version)) {
-      std::string e = R"(VERSION ")" + version + R"(" format invalid.)";
+    if (!vx.find(*prArgs.Version)) {
+      std::string e =
+        R"(VERSION ")" + *prArgs.Version + R"(" format invalid.)";
       mf.IssueMessage(MessageType::FATAL_ERROR, e);
       cmSystemTools::SetFatalErrorOccurred();
       return true;
@@ -253,8 +201,8 @@ bool cmProjectCommand(std::vector<std::string> const& args,
         std::numeric_limits<unsigned>::digits10 + 2;
       char vb[MAX_VERSION_COMPONENTS][maxIntLength];
       unsigned v[MAX_VERSION_COMPONENTS] = { 0, 0, 0, 0 };
-      int const vc = std::sscanf(version.c_str(), "%u.%u.%u.%u", &v[0], &v[1],
-                                 &v[2], &v[3]);
+      int const vc = std::sscanf(prArgs.Version->c_str(), "%u.%u.%u.%u", &v[0],
+                                 &v[1], &v[2], &v[3]);
       for (auto i = 0u; i < MAX_VERSION_COMPONENTS; ++i) {
         if (static_cast<int>(i) < vc) {
           std::snprintf(vb[i], maxIntLength, "%u", v[i]);
@@ -268,7 +216,7 @@ bool cmProjectCommand(std::vector<std::string> const& args,
     } else {
       // The regex above verified that we have a .-separated string of
       // non-negative integer components.  Keep the original string.
-      version_string = std::move(version);
+      version_string = std::move(*prArgs.Version);
       // Split the integer components.
       auto components = cmSystemTools::SplitString(version_string, '.');
       for (auto i = 0u; i < components.size(); ++i) {
@@ -277,19 +225,19 @@ bool cmProjectCommand(std::vector<std::string> const& args,
     }
 
     std::string vv;
-    vv = projectName + "_VERSION";
+    vv = *prArgs.ProjectName + "_VERSION";
     mf.AddDefinition("PROJECT_VERSION", version_string);
     mf.AddDefinition(vv, version_string);
-    vv = projectName + "_VERSION_MAJOR";
+    vv = *prArgs.ProjectName + "_VERSION_MAJOR";
     mf.AddDefinition("PROJECT_VERSION_MAJOR", version_components[0]);
     mf.AddDefinition(vv, version_components[0]);
-    vv = projectName + "_VERSION_MINOR";
+    vv = *prArgs.ProjectName + "_VERSION_MINOR";
     mf.AddDefinition("PROJECT_VERSION_MINOR", version_components[1]);
     mf.AddDefinition(vv, version_components[1]);
-    vv = projectName + "_VERSION_PATCH";
+    vv = *prArgs.ProjectName + "_VERSION_PATCH";
     mf.AddDefinition("PROJECT_VERSION_PATCH", version_components[2]);
     mf.AddDefinition(vv, version_components[2]);
-    vv = projectName + "_VERSION_TWEAK";
+    vv = *prArgs.ProjectName + "_VERSION_TWEAK";
     mf.AddDefinition("PROJECT_VERSION_TWEAK", version_components[3]);
     mf.AddDefinition(vv, version_components[3]);
     // Also, try set top level variables
@@ -309,11 +257,11 @@ bool cmProjectCommand(std::vector<std::string> const& args,
                                     "PROJECT_VERSION_MINOR",
                                     "PROJECT_VERSION_PATCH",
                                     "PROJECT_VERSION_TWEAK",
-                                    projectName + "_VERSION",
-                                    projectName + "_VERSION_MAJOR",
-                                    projectName + "_VERSION_MINOR",
-                                    projectName + "_VERSION_PATCH",
-                                    projectName + "_VERSION_TWEAK" };
+                                    *prArgs.ProjectName + "_VERSION",
+                                    *prArgs.ProjectName + "_VERSION_MAJOR",
+                                    *prArgs.ProjectName + "_VERSION_MINOR",
+                                    *prArgs.ProjectName + "_VERSION_PATCH",
+                                    *prArgs.ProjectName + "_VERSION_TWEAK" };
     if (mf.IsRootMakefile()) {
       vv.emplace_back("CMAKE_PROJECT_VERSION");
       vv.emplace_back("CMAKE_PROJECT_VERSION_MAJOR");
@@ -329,34 +277,42 @@ bool cmProjectCommand(std::vector<std::string> const& args,
     }
   }
 
-  mf.AddDefinition("PROJECT_DESCRIPTION", description);
-  mf.AddDefinition(projectName + "_DESCRIPTION", description);
-  TopLevelCMakeVarCondSet(mf, "CMAKE_PROJECT_DESCRIPTION", description);
+  std::string const& desc = prArgs.Description.value_or("");
+  mf.AddDefinition("PROJECT_DESCRIPTION", desc);
+  mf.AddDefinition(*prArgs.ProjectName + "_DESCRIPTION", desc);
+  TopLevelCMakeVarCondSet(mf, "CMAKE_PROJECT_DESCRIPTION", desc);
 
-  mf.AddDefinition("PROJECT_HOMEPAGE_URL", homepage);
-  mf.AddDefinition(projectName + "_HOMEPAGE_URL", homepage);
-  TopLevelCMakeVarCondSet(mf, "CMAKE_PROJECT_HOMEPAGE_URL", homepage);
+  std::string const& url = prArgs.HomepageURL.value_or("");
+  mf.AddDefinition("PROJECT_HOMEPAGE_URL", url);
+  mf.AddDefinition(*prArgs.ProjectName + "_HOMEPAGE_URL", url);
+  TopLevelCMakeVarCondSet(mf, "CMAKE_PROJECT_HOMEPAGE_URL", url);
 
-  if (languages.empty()) {
+  if (unparsedArgs.empty() && !prArgs.Languages) {
     // if no language is specified do c and c++
-    languages = { "C", "CXX" };
+    mf.EnableLanguage({ "C", "CXX" }, false);
+  } else {
+    if (!unparsedArgs.empty()) {
+      mf.EnableLanguage(unparsedArgs, false);
+    }
+    if (prArgs.Languages) {
+      mf.EnableLanguage(*prArgs.Languages, false);
+    }
   }
-  mf.EnableLanguage(languages, false);
 
   if (!IncludeByVariable(status, "CMAKE_PROJECT_INCLUDE")) {
     return false;
   }
 
-  if (!IncludeByVariable(status,
-                         "CMAKE_PROJECT_" + projectName + "_INCLUDE")) {
+  if (!IncludeByVariable(
+        status, "CMAKE_PROJECT_" + *prArgs.ProjectName + "_INCLUDE")) {
     return false;
   }
 
   return true;
 }
 
-static bool IncludeByVariable(cmExecutionStatus& status,
-                              std::string const& variable)
+namespace {
+bool IncludeByVariable(cmExecutionStatus& status, std::string const& variable)
 {
   cmMakefile& mf = status.GetMakefile();
   cmValue include = mf.GetDefinition(variable);
@@ -414,8 +370,8 @@ static bool IncludeByVariable(cmExecutionStatus& status,
   return !failed;
 }
 
-static void TopLevelCMakeVarCondSet(cmMakefile& mf, std::string const& name,
-                                    std::string const& value)
+void TopLevelCMakeVarCondSet(cmMakefile& mf, std::string const& name,
+                             std::string const& value)
 {
   // Set the CMAKE_PROJECT_XXX variable to be the highest-level
   // project name in the tree. If there are two project commands
@@ -427,3 +383,4 @@ static void TopLevelCMakeVarCondSet(cmMakefile& mf, std::string const& name,
                           cmStateEnums::STATIC);
   }
 }
+}