Create the StartupFlag class and use it instead of plain list of strings.

Additionally, add a warning note for developers who wish to delete startup
options: they first need to deprecate the flag and once it's a no-op
for a sufficient amount of time then they can delete it from the list
of valid options.
PiperOrigin-RevId: 160546248
diff --git a/src/main/cpp/startup_options.cc b/src/main/cpp/startup_options.cc
index 46f0df4..21e0815 100644
--- a/src/main/cpp/startup_options.cc
+++ b/src/main/cpp/startup_options.cc
@@ -32,9 +32,44 @@
 using std::string;
 using std::vector;
 
+StartupFlag::~StartupFlag() {}
+
+bool UnaryStartupFlag::NeedsParameter() const {
+  return true;
+}
+
+bool UnaryStartupFlag::IsValid(const std::string &arg) const {
+  // The second argument of GetUnaryOption is not relevant to determine
+  // whether the option is unary or not, hence we set it to the empty string
+  // by default.
+  //
+  // TODO(lpino): Improve GetUnaryOption to only require the arg and the
+  // option we are looking for.
+  return GetUnaryOption(arg.c_str(), "", ("--" + name_).c_str()) != NULL;
+}
+
+bool NullaryStartupFlag::NeedsParameter() const {
+  return false;
+}
+
+bool NullaryStartupFlag::IsValid(const std::string &arg) const {
+  return GetNullaryOption(arg.c_str(), ("--" + name_).c_str()) ||
+      GetNullaryOption(arg.c_str(), ("--no" + name_).c_str());
+}
+
 StartupOptions::StartupOptions(const WorkspaceLayout* workspace_layout)
     : StartupOptions("Bazel", workspace_layout) {}
 
+void StartupOptions::RegisterNullaryStartupFlag(const std::string &flag_name) {
+  valid_startup_flags.insert(std::unique_ptr<NullaryStartupFlag>(
+      new NullaryStartupFlag(flag_name)));
+}
+
+void StartupOptions::RegisterUnaryStartupFlag(const std::string &flag_name) {
+  valid_startup_flags.insert(std::unique_ptr<UnaryStartupFlag>(
+      new UnaryStartupFlag(flag_name)));
+}
+
 StartupOptions::StartupOptions(const string &product_name,
                                const WorkspaceLayout *workspace_layout)
     : product_name(product_name),
@@ -76,24 +111,37 @@
       output_root, "_" + product_name_lower + "_" + GetUserName());
   // 3 hours (but only 15 seconds if used within a test)
   max_idle_secs = testing ? 15 : (3 * 3600);
-  nullary_options = {"deep_execroot",
-                     "block_for_lock",
-                     "host_jvm_debug",
-                     "master_blazerc",
-                     "master_bazelrc",
-                     "batch",
-                     "batch_cpu_scheduling",
-                     "allow_configurable_attributes",
-                     "fatal_event_bus_exceptions",
-                     "experimental_oom_more_eagerly",
-                     "write_command_log",
-                     "watchfs",
-                     "client_debug"};
-  unary_options = {"output_base", "install_base",
-      "output_user_root", "host_jvm_profile", "host_javabase",
-      "host_jvm_args", "bazelrc", "blazerc", "io_nice_level",
-      "max_idle_secs", "experimental_oom_more_eagerly_threshold",
-      "command_port", "invocation_policy", "connect_timeout_secs"};
+
+  // IMPORTANT: Before modifying the statements below please contact a Bazel
+  // core team member that knows the internal procedure for adding/deprecating
+  // startup flags.
+  RegisterNullaryStartupFlag("allow_configurable_attributes");
+  RegisterNullaryStartupFlag("batch");
+  RegisterNullaryStartupFlag("batch_cpu_scheduling");
+  RegisterNullaryStartupFlag("block_for_lock");
+  RegisterNullaryStartupFlag("client_debug");
+  RegisterNullaryStartupFlag("deep_execroot");
+  RegisterNullaryStartupFlag("experimental_oom_more_eagerly");
+  RegisterNullaryStartupFlag("fatal_event_bus_exceptions");
+  RegisterNullaryStartupFlag("host_jvm_debug");
+  RegisterNullaryStartupFlag("master_bazelrc");
+  RegisterNullaryStartupFlag("master_blazerc");
+  RegisterNullaryStartupFlag("watchfs");
+  RegisterNullaryStartupFlag("write_command_log");
+  RegisterUnaryStartupFlag("bazelrc");
+  RegisterUnaryStartupFlag("blazerc");
+  RegisterUnaryStartupFlag("command_port");
+  RegisterUnaryStartupFlag("connect_timeout_secs");
+  RegisterUnaryStartupFlag("experimental_oom_more_eagerly_threshold");
+  RegisterUnaryStartupFlag("host_javabase");
+  RegisterUnaryStartupFlag("host_jvm_args");
+  RegisterUnaryStartupFlag("host_jvm_profile");
+  RegisterUnaryStartupFlag("invocation_policy");
+  RegisterUnaryStartupFlag("io_nice_level");
+  RegisterUnaryStartupFlag("install_base");
+  RegisterUnaryStartupFlag("max_idle_secs");
+  RegisterUnaryStartupFlag("output_base");
+  RegisterUnaryStartupFlag("output_user_root");
 }
 
 StartupOptions::~StartupOptions() {}
@@ -105,9 +153,8 @@
 }
 
 bool StartupOptions::IsNullary(const string& arg) const {
-  for (string option : nullary_options) {
-    if (GetNullaryOption(arg.c_str(), ("--" + option).c_str()) ||
-        GetNullaryOption(arg.c_str(), ("--no" + option).c_str())) {
+  for (const auto& flag : valid_startup_flags) {
+    if (!flag->NeedsParameter() && flag->IsValid(arg)) {
       return true;
     }
   }
@@ -115,14 +162,8 @@
 }
 
 bool StartupOptions::IsUnary(const string& arg) const {
-  for (string option : unary_options) {
-    // The second argument of GetUnaryOption is not relevant to determine
-    // whether the option is unary or not, hence we set it to the empty string
-    // by default.
-    //
-    // TODO(lpino): Improve GetUnaryOption to only require the arg and the
-    // option we are looking for.
-    if (GetUnaryOption(arg.c_str(), "", ("--" + option).c_str()) != NULL) {
+  for (const auto& flag : valid_startup_flags) {
+    if (flag->NeedsParameter() && flag->IsValid(arg)) {
       return true;
     }
   }
diff --git a/src/main/cpp/startup_options.h b/src/main/cpp/startup_options.h
index 607810d..4b6d2b9 100644
--- a/src/main/cpp/startup_options.h
+++ b/src/main/cpp/startup_options.h
@@ -16,6 +16,7 @@
 
 #include <map>
 #include <memory>
+#include <set>
 #include <string>
 #include <vector>
 
@@ -25,6 +26,40 @@
 
 class WorkspaceLayout;
 
+// Represents a single startup flag (or startup option).
+class StartupFlag {
+ public:
+  virtual ~StartupFlag() = 0;
+  virtual bool NeedsParameter() const = 0;
+  virtual bool IsValid(const std::string& arg) const = 0;
+};
+
+// A startup flag that doesn't expect a value.
+// For instance, NullaryStartupFlag("master_bazelrc") is used to represent
+// "--master_bazelrc" and "--nomaster_bazelrc".
+class NullaryStartupFlag : public StartupFlag {
+ public:
+  NullaryStartupFlag(const std::string& name) : name_(name) {}
+  bool IsValid(const std::string& arg) const override;
+  bool NeedsParameter() const override;
+
+ private:
+  const std::string name_;
+};
+
+// A startup flag that expects a value.
+// For instance, UnaryStartupFlag("bazelrc") is used to represent
+// "--bazelrc=foo" or "--bazelrc foo".
+class UnaryStartupFlag : public StartupFlag {
+ public:
+  UnaryStartupFlag(const std::string& name) : name_(name) {}
+  bool IsValid(const std::string& arg) const override;
+  bool NeedsParameter() const override;
+
+ private:
+  const std::string name_;
+};
+
 // This class holds the parsed startup options for Blaze.
 // These options and their defaults must be kept in sync with those in
 // src/main/java/com/google/devtools/build/lib/runtime/BlazeServerStartupOptions.java.
@@ -221,15 +256,15 @@
   explicit StartupOptions(const std::string &product_name,
                           const WorkspaceLayout* workspace_layout);
 
-  // Holds the valid nullary startup options.
-  std::vector<std::string> nullary_options;
+  void RegisterUnaryStartupFlag(const std::string& flag_name);
 
-  // Holds the valid unary startup options.
-  std::vector<std::string> unary_options;
+  void RegisterNullaryStartupFlag(const std::string& flag_name);
 
  private:
   std::string host_javabase;
   std::string default_host_javabase;
+  // Contains the collection of startup flags that Bazel accepts.
+  std::set<std::unique_ptr<StartupFlag>> valid_startup_flags;
 
 #if defined(COMPILER_MSVC) || defined(__CYGWIN__)
   static std::string WindowsUnixRoot(const std::string &bazel_sh);
diff --git a/src/test/cpp/startup_options_test.cc b/src/test/cpp/startup_options_test.cc
index 72cdf1b..c8c59d0 100644
--- a/src/test/cpp/startup_options_test.cc
+++ b/src/test/cpp/startup_options_test.cc
@@ -45,6 +45,34 @@
     startup_options_.reset(new StartupOptions(workspace_layout_.get()));
   }
 
+  void SuccessfulIsNullaryTest(const std::string& flag_name) const {
+    EXPECT_TRUE(startup_options_->IsNullary("--" + flag_name));
+    EXPECT_TRUE(startup_options_->IsNullary("--no" + flag_name));
+
+    EXPECT_FALSE(startup_options_->IsNullary("--" + flag_name + "__invalid"));
+
+    EXPECT_DEATH(startup_options_->IsNullary("--" + flag_name + "=foo"),
+                 ("In argument '--" + flag_name + "=foo': option "
+                     "'--" + flag_name + "' does not take a value").c_str());
+
+    EXPECT_DEATH(startup_options_->IsNullary("--no" + flag_name + "=foo"),
+                 ("In argument '--no" + flag_name + "=foo': option "
+                     "'--no" + flag_name + "' does not take a value").c_str());
+
+    EXPECT_FALSE(startup_options_->IsUnary("--" + flag_name));
+    EXPECT_FALSE(startup_options_->IsUnary("--no" + flag_name));
+  }
+
+  void SuccessfulIsUnaryTest(const std::string& flag_name) const {
+    EXPECT_TRUE(startup_options_->IsUnary("--" + flag_name));
+    EXPECT_TRUE(startup_options_->IsUnary("--" + flag_name + "="));
+    EXPECT_TRUE(startup_options_->IsUnary("--" + flag_name + "=foo"));
+
+    EXPECT_FALSE(startup_options_->IsUnary("--" + flag_name + "__invalid"));
+    EXPECT_FALSE(startup_options_->IsNullary("--" + flag_name));
+    EXPECT_FALSE(startup_options_->IsNullary("--no" + flag_name));
+  }
+
  private:
   std::unique_ptr<WorkspaceLayout> workspace_layout_;
 
@@ -92,16 +120,44 @@
   ASSERT_EQ("/tmp", startup_options_->output_root);
 }
 
-TEST_F(StartupOptionsTest, IsNullaryTest) {
-  EXPECT_TRUE(startup_options_->IsNullary("--master_bazelrc"));
-  EXPECT_TRUE(startup_options_->IsNullary("--nomaster_bazelrc"));
+TEST_F(StartupOptionsTest, EmptyFlagsAreInvalidTest) {
   EXPECT_FALSE(startup_options_->IsNullary(""));
   EXPECT_FALSE(startup_options_->IsNullary("--"));
-  EXPECT_FALSE(startup_options_->IsNullary("--master_bazelrcascasc"));
-  string error_msg = std::string("In argument '--master_bazelrc=foo': option ")
-      + std::string("'--master_bazelrc' does not take a value");
-  EXPECT_DEATH(startup_options_->IsNullary("--master_bazelrc=foo"),
-               error_msg.c_str());
+  EXPECT_FALSE(startup_options_->IsUnary(""));
+  EXPECT_FALSE(startup_options_->IsUnary("--"));
+}
+
+TEST_F(StartupOptionsTest, ValidStartupFlagsTest) {
+  // IMPORTANT: Before modifying this test, please contact a Bazel core team
+  // member that knows the Google-internal procedure for adding/deprecating
+  // startup flags.
+  SuccessfulIsNullaryTest("allow_configurable_attributes");
+  SuccessfulIsNullaryTest("batch");
+  SuccessfulIsNullaryTest("batch_cpu_scheduling");
+  SuccessfulIsNullaryTest("block_for_lock");
+  SuccessfulIsNullaryTest("client_debug");
+  SuccessfulIsNullaryTest("deep_execroot");
+  SuccessfulIsNullaryTest("experimental_oom_more_eagerly");
+  SuccessfulIsNullaryTest("fatal_event_bus_exceptions");
+  SuccessfulIsNullaryTest("host_jvm_debug");
+  SuccessfulIsNullaryTest("master_bazelrc");
+  SuccessfulIsNullaryTest("master_blazerc");
+  SuccessfulIsNullaryTest("watchfs");
+  SuccessfulIsNullaryTest("write_command_log");
+  SuccessfulIsUnaryTest("bazelrc");
+  SuccessfulIsUnaryTest("blazerc");
+  SuccessfulIsUnaryTest("command_port");
+  SuccessfulIsUnaryTest("connect_timeout_secs");
+  SuccessfulIsUnaryTest("experimental_oom_more_eagerly_threshold");
+  SuccessfulIsUnaryTest("host_javabase");
+  SuccessfulIsUnaryTest("host_jvm_args");
+  SuccessfulIsUnaryTest("host_jvm_profile");
+  SuccessfulIsUnaryTest("invocation_policy");
+  SuccessfulIsUnaryTest("io_nice_level");
+  SuccessfulIsUnaryTest("install_base");
+  SuccessfulIsUnaryTest("max_idle_secs");
+  SuccessfulIsUnaryTest("output_base");
+  SuccessfulIsUnaryTest("output_user_root");
 }
 
 TEST_F(StartupOptionsTest, IsUnaryTest) {