Use a CommandLine struct to store the command line parsed by the OptionProcessor.

This replaces the startup_args_, command_ and command_argument members to allow a more consistent representation of the command line throughout the class.

PiperOrigin-RevId: 161408010
diff --git a/src/main/cpp/blaze.cc b/src/main/cpp/blaze.cc
index f9acc8a..a77504b 100644
--- a/src/main/cpp/blaze.cc
+++ b/src/main/cpp/blaze.cc
@@ -640,8 +640,8 @@
             globals->options->product_name.c_str());
   }
   string command = globals->option_processor->GetCommand();
-  vector<string> command_arguments;
-  globals->option_processor->GetCommandArguments(&command_arguments);
+  const vector<string> command_arguments =
+      globals->option_processor->GetCommandArguments();
 
   if (!command_arguments.empty() && command == "shutdown") {
     string product = globals->options->product_name;
@@ -1604,7 +1604,13 @@
     AddLoggingArgs(&arg_vector);
   }
 
-  globals->option_processor->GetCommandArguments(&arg_vector);
+  const vector<string> command_args =
+      globals->option_processor->GetCommandArguments();
+  if (!command_args.empty()) {
+    arg_vector.insert(arg_vector.end(),
+                      command_args.begin(),
+                      command_args.end());
+  }
 
   command_server::RunRequest request;
   request.set_cookie(request_cookie_);
diff --git a/src/main/cpp/option_processor.cc b/src/main/cpp/option_processor.cc
index db19d3f..fc6d8b1 100644
--- a/src/main/cpp/option_processor.cc
+++ b/src/main/cpp/option_processor.cc
@@ -169,14 +169,12 @@
 OptionProcessor::OptionProcessor(
     const WorkspaceLayout* workspace_layout,
     std::unique_ptr<StartupOptions> default_startup_options)
-    : initialized_(false),
-      workspace_layout_(workspace_layout),
+    : workspace_layout_(workspace_layout),
       parsed_startup_options_(std::move(default_startup_options)) {
 }
 
 std::unique_ptr<CommandLine> OptionProcessor::SplitCommandLine(
-    const vector<string>& args,
-    string* error) {
+    const vector<string>& args, string* error) const {
   const string lowercase_product_name =
       parsed_startup_options_->GetLowercaseProductName();
 
@@ -299,6 +297,7 @@
 }
 
 namespace internal {
+
 vector<string> DedupeBlazercPaths(const vector<string>& paths) {
   set<string> canonical_paths;
   vector<string> result;
@@ -314,6 +313,7 @@
   }
   return result;
 }
+
 }  // namespace internal
 
 // Parses the arguments provided in args using the workspace path and the
@@ -323,24 +323,19 @@
     const string& workspace,
     const string& cwd,
     string* error) {
-  assert(!initialized_);
-  initialized_ = true;
 
-  args_ = args;
-  std::unique_ptr<CommandLine> cmd_line = SplitCommandLine(args, error);
-  if (cmd_line == nullptr) {
+  cmd_line_ = SplitCommandLine(args, error);
+  if (cmd_line_ == nullptr) {
     return blaze_exit_code::BAD_ARGV;
   }
-  explicit_command_arguments_ = cmd_line->command_args;
-
-  const char* blazerc = SearchUnaryOption(cmd_line->startup_args, "--blazerc");
+  const char* blazerc = SearchUnaryOption(cmd_line_->startup_args, "--blazerc");
   if (blazerc == NULL) {
-    blazerc = SearchUnaryOption(cmd_line->startup_args, "--bazelrc");
+    blazerc = SearchUnaryOption(cmd_line_->startup_args, "--bazelrc");
   }
 
   bool use_master_blazerc = true;
-  if (SearchNullaryOption(cmd_line->startup_args, "--nomaster_blazerc") ||
-      SearchNullaryOption(cmd_line->startup_args, "--nomaster_bazelrc")) {
+  if (SearchNullaryOption(cmd_line_->startup_args, "--nomaster_blazerc") ||
+      SearchNullaryOption(cmd_line_->startup_args, "--nomaster_bazelrc")) {
     use_master_blazerc = false;
   }
 
@@ -350,7 +345,7 @@
   vector<string> candidate_blazerc_paths;
   if (use_master_blazerc) {
     workspace_layout_->FindCandidateBlazercPaths(
-        workspace, cwd, cmd_line->path_to_binary, cmd_line->startup_args,
+        workspace, cwd, cmd_line_->path_to_binary, cmd_line_->startup_args,
         &candidate_blazerc_paths);
   }
 
@@ -386,20 +381,8 @@
     return parse_startup_options_exit_code;
   }
 
-  // Once we're done with startup options the next arg is the command.
-  if (startup_args_ + 1 >= args.size()) {
-    command_ = "";
-    return blaze_exit_code::SUCCESS;
-  }
-  command_ = args[startup_args_ + 1];
-
-  AddRcfileArgsAndOptions(cwd);
-
-  // The rest of the args are the command options.
-  for (unsigned int cmd_arg = startup_args_ + 2;
-       cmd_arg < args.size(); cmd_arg++) {
-    command_arguments_.push_back(args[cmd_arg]);
-  }
+  blazerc_and_env_command_args_ = GetBlazercAndEnvCommandArgs(
+      cwd, blazercs_, rcoptions_);
   return blaze_exit_code::SUCCESS;
 }
 
@@ -417,69 +400,28 @@
   return ParseOptions(args, workspace, cwd, error);
 }
 
-blaze_exit_code::ExitCode OptionProcessor::ParseStartupOptions(string *error) {
-  // Process rcfile startup options
-  map< string, vector<RcOption> >::const_iterator it =
-      rcoptions_.find("startup");
-  blaze_exit_code::ExitCode process_arg_exit_code;
-  bool is_space_separated;
-  if (it != rcoptions_.end()) {
-    const vector<RcOption>& startup_options = it->second;
-    int i = 0;
-    // Process all elements except the last one.
-    for (; i < startup_options.size() - 1; i++) {
-      const RcOption& option = startup_options[i];
-      const string& blazerc = blazercs_[option.rcfile_index()]->Filename();
-      process_arg_exit_code = parsed_startup_options_->ProcessArg(
-          option.option(), startup_options[i + 1].option(), blazerc,
-          &is_space_separated, error);
-      if (process_arg_exit_code != blaze_exit_code::SUCCESS) {
-          return process_arg_exit_code;
-      }
-      if (is_space_separated) {
-        i++;
-      }
-    }
-    // Process last element, if any.
-    if (i < startup_options.size()) {
-      const RcOption& option = startup_options[i];
-      if (IsArg(option.option())) {
-        const string& blazerc = blazercs_[option.rcfile_index()]->Filename();
-        process_arg_exit_code = parsed_startup_options_->ProcessArg(
-            option.option(), "", blazerc, &is_space_separated, error);
-        if (process_arg_exit_code != blaze_exit_code::SUCCESS) {
-          return process_arg_exit_code;
-        }
-      }
+blaze_exit_code::ExitCode OptionProcessor::ParseStartupOptions(
+    std::string *error) {
+  std::vector<RcStartupFlag> rcstartup_flags;
+
+  auto iter = rcoptions_.find("startup");
+  if (iter != rcoptions_.end()) {
+    const vector<RcOption>& startup_rcoptions = iter->second;
+    for (const RcOption& option : startup_rcoptions) {
+      rcstartup_flags.push_back(
+          RcStartupFlag(blazercs_[option.rcfile_index()]->Filename(),
+                        option.option()));
     }
   }
 
-  // Process command-line args next, so they override any of the same options
-  // from .blazerc. Stop on first non-arg, this includes --help
-  unsigned int i = 1;
-  if (!args_.empty()) {
-    for (;  (i < args_.size() - 1) && IsArg(args_[i]); i++) {
-      process_arg_exit_code = parsed_startup_options_->ProcessArg(
-          args_[i], args_[i + 1], "", &is_space_separated, error);
-      if (process_arg_exit_code != blaze_exit_code::SUCCESS) {
-          return process_arg_exit_code;
-      }
-      if (is_space_separated) {
-        i++;
-      }
+  for (const std::string& arg : cmd_line_->startup_args) {
+    if (!IsArg(arg)) {
+      break;
     }
-    if (i < args_.size() && IsArg(args_[i])) {
-      process_arg_exit_code = parsed_startup_options_->ProcessArg(
-          args_[i], "", "", &is_space_separated, error);
-      if (process_arg_exit_code != blaze_exit_code::SUCCESS) {
-          return process_arg_exit_code;
-      }
-      i++;
-    }
+    rcstartup_flags.push_back(RcStartupFlag("", arg));
   }
-  startup_args_ = i -1;
 
-  return blaze_exit_code::SUCCESS;
+  return parsed_startup_options_->ProcessArgs(rcstartup_flags, error);
 }
 
 static bool IsValidEnvName(const char* p) {
@@ -532,39 +474,34 @@
 }
 #endif  // defined(COMPILER_MSVC)
 
-// Appends the command and arguments from argc/argv to the end of arg_vector,
-// and also splices in some additional terminal and environment options between
-// the command and the arguments. NB: Keep the options added here in sync with
+// IMPORTANT: Keep the options added here in sync with
 // BlazeCommandDispatcher.INTERNAL_COMMAND_OPTIONS!
-void OptionProcessor::AddRcfileArgsAndOptions(const string& cwd) {
+std::vector<std::string> OptionProcessor::GetBlazercAndEnvCommandArgs(
+    const std::string& cwd,
+    const std::vector<RcFile*>& blazercs,
+    const std::map<std::string, std::vector<RcOption>>& rcoptions) {
   // Provide terminal options as coming from the least important rc file.
-  command_arguments_.push_back("--rc_source=client");
-  command_arguments_.push_back("--default_override=0:common=--is_stderr_atty=" +
-                               ToString(IsStderrStandardTerminal()));
-  command_arguments_.push_back(
-      "--default_override=0:common=--terminal_columns=" +
-      ToString(GetStderrTerminalColumns()));
+  std::vector<std::string> result = {
+    "--rc_source=client",
+    "--default_override=0:common=--is_stderr_atty=" +
+        ToString(IsStderrStandardTerminal()),
+    "--default_override=0:common=--terminal_columns=" +
+        ToString(GetStderrTerminalColumns())};
 
   // Push the options mapping .blazerc numbers to filenames.
-  for (int i_blazerc = 0; i_blazerc < blazercs_.size(); i_blazerc++) {
-    const RcFile* blazerc = blazercs_[i_blazerc];
-    command_arguments_.push_back("--rc_source=" +
-                                 blaze::ConvertPath(blazerc->Filename()));
+  for (const RcFile* blazerc : blazercs) {
+    result.push_back("--rc_source=" + blaze::ConvertPath(blazerc->Filename()));
   }
 
-  // Push the option defaults
-  for (map<string, vector<RcOption> >::const_iterator it = rcoptions_.begin();
-       it != rcoptions_.end(); ++it) {
-    if (it->first == "startup") {
-      // Skip startup options, they are parsed in the C++ wrapper
-      continue;
-    }
-
-    for (int ii = 0; ii < it->second.size(); ii++) {
-      const RcOption& rcoption = it->second[ii];
-      command_arguments_.push_back(
-          "--default_override=" + ToString(rcoption.rcfile_index() + 1) + ":"
-          + it->first + "=" + rcoption.option());
+  // Process RcOptions exept for the startup flags that are already parsed
+  // by the client and shouldn't be handled by defult_overrides.
+  for (auto it = rcoptions.begin(); it != rcoptions.end(); ++it) {
+    if (it->first != "startup") {
+      for (const RcOption& rcoption : it->second) {
+        result.push_back(
+            "--default_override=" + ToString(rcoption.rcfile_index() + 1) + ":"
+            + it->first + "=" + rcoption.option());
+      }
     }
   }
 
@@ -573,31 +510,44 @@
     string env_str(*env);
     if (IsValidEnvName(*env)) {
       PreprocessEnvString(&env_str);
-      command_arguments_.push_back("--client_env=" + env_str);
+      result.push_back("--client_env=" + env_str);
     }
   }
-  command_arguments_.push_back("--client_cwd=" + blaze::ConvertPath(cwd));
+  result.push_back("--client_cwd=" + blaze::ConvertPath(cwd));
 
   if (IsEmacsTerminal()) {
-    command_arguments_.push_back("--emacs");
+    result.push_back("--emacs");
   }
+  return result;
 }
 
-void OptionProcessor::GetCommandArguments(vector<string>* result) const {
-  result->insert(result->end(),
-                 command_arguments_.begin(),
-                 command_arguments_.end());
+std::vector<std::string> OptionProcessor::GetCommandArguments() const {
+  assert(cmd_line_ != nullptr);
+  // When the user didn't specify a command, the server expects the command
+  // arguments to be empty in order to display the help message.
+  if (cmd_line_->command.empty()) {
+    return {};
+  }
+
+  std::vector<std::string> command_args = blazerc_and_env_command_args_;
+  command_args.insert(command_args.end(),
+                      cmd_line_->command_args.begin(),
+                      cmd_line_->command_args.end());
+  return command_args;
 }
 
 std::vector<std::string> OptionProcessor::GetExplicitCommandArguments() const {
-  return explicit_command_arguments_;
+  assert(cmd_line_ != nullptr);
+  return cmd_line_->command_args;
 }
 
-const string& OptionProcessor::GetCommand() const {
-  return command_;
+std::string OptionProcessor::GetCommand() const {
+  assert(cmd_line_ != nullptr);
+  return cmd_line_->command;
 }
 
 StartupOptions* OptionProcessor::GetParsedStartupOptions() const {
+  assert(parsed_startup_options_ != NULL);
   return parsed_startup_options_.get();
 }
 
diff --git a/src/main/cpp/option_processor.h b/src/main/cpp/option_processor.h
index af32dca..f572003 100644
--- a/src/main/cpp/option_processor.h
+++ b/src/main/cpp/option_processor.h
@@ -75,7 +75,7 @@
   // remains untouched.
   std::unique_ptr<CommandLine> SplitCommandLine(
       const std::vector<std::string>& args,
-      std::string* error);
+      std::string* error) const;
 
   // Parse a command line and the appropriate blazerc files. This should be
   // invoked only once per OptionProcessor object.
@@ -91,13 +91,13 @@
 
   // Get the Blaze command to be executed.
   // Returns an empty string if no command was found on the command line.
-  const std::string& GetCommand() const;
+  std::string GetCommand() const;
 
   // Gets the arguments to the command. This is put together from the default
   // options specified in the blazerc file(s), the command line, and various
   // bits and pieces of information about the environment the blaze binary is
   // executed in.
-  void GetCommandArguments(std::vector<std::string>* result) const;
+  std::vector<std::string> GetCommandArguments() const;
 
   // Gets the arguments explicitly provided by the user's command line.
   std::vector<std::string> GetExplicitCommandArguments() const;
@@ -145,31 +145,30 @@
     int index_;
   };
 
-  void AddRcfileArgsAndOptions(const std::string& cwd);
   blaze_exit_code::ExitCode ParseStartupOptions(std::string* error);
 
+  static std::vector<std::string> GetBlazercAndEnvCommandArgs(
+      const std::string& cwd,
+      const std::vector<RcFile*>& blazercs,
+      const std::map<std::string, std::vector<RcOption>>& rcoptions);
+
   // The list of parsed rc files, this field is initialized by ParseOptions.
   std::vector<RcFile*> blazercs_;
-  std::map<std::string, std::vector<RcOption> > rcoptions_;
-  // The original args given by the user, this field is initialized by
-  // ParseOptions.
-  std::vector<std::string> args_;
-  // The index in args where the last startup option occurs, this field is
-  // initialized by ParseOptions.
-  unsigned int startup_args_;
-  // The command found in args, this field is initialized by ParseOptions.
-  std::string command_;
-  // The list of command options. This is put together from the default
-  // options specified in the bazelrc file(s), the command line, and various
-  // bits and pieces of information about the environment the bazel binary is
-  // executed in. This field is initialized by ParseOptions.
-  std::vector<std::string> command_arguments_;
-  // The list of command options found in args, this field is initialized by
-  // ParseOptions.
-  std::vector<std::string> explicit_command_arguments_;
-  // Whether ParseOptions has been called.
-  bool initialized_;
+
+  // A map representing the flags parsed from the bazelrc files.
+  // A key is a command (e.g. 'build', 'startup') and its value is an ordered
+  // list of RcOptions.
+  std::map<std::string, std::vector<RcOption>> rcoptions_;
+
+  // An ordered list of command args that contain information about the
+  // execution environment and the flags passed via the bazelrc files.
+  std::vector<std::string> blazerc_and_env_command_args_;
+
+  // The command line constructed after calling ParseOptions.
+  std::unique_ptr<CommandLine> cmd_line_;
+
   const WorkspaceLayout* workspace_layout_;
+
   // The startup options parsed from args, this field is initialized by
   // ParseOptions.
   std::unique_ptr<StartupOptions> parsed_startup_options_;
diff --git a/src/main/cpp/startup_options.cc b/src/main/cpp/startup_options.cc
index 21e0815..d54dbb2 100644
--- a/src/main/cpp/startup_options.cc
+++ b/src/main/cpp/startup_options.cc
@@ -376,6 +376,30 @@
   return blaze_exit_code::SUCCESS;
 }
 
+blaze_exit_code::ExitCode StartupOptions::ProcessArgs(
+    const std::vector<RcStartupFlag>& rcstartup_flags,
+    std::string *error) {
+  std::vector<RcStartupFlag>::size_type i = 0;
+  while (i < rcstartup_flags.size()) {
+    bool is_space_separated;
+    const bool is_last_elem = i == rcstartup_flags.size() - 1;
+    const blaze_exit_code::ExitCode process_arg_exit_code =
+        ProcessArg(rcstartup_flags[i].value,
+                   is_last_elem ? "" : rcstartup_flags[i + 1].value,
+                   rcstartup_flags[i].source,
+                   &is_space_separated,
+                   error);
+    if (process_arg_exit_code != blaze_exit_code::SUCCESS) {
+      return process_arg_exit_code;
+    }
+    if (is_space_separated) {
+      i++;
+    }
+    i++;
+  }
+  return blaze_exit_code::SUCCESS;
+}
+
 blaze_exit_code::ExitCode StartupOptions::ProcessArgExtra(
     const char *arg, const char *next_arg, const string &rcfile,
     const char **value, bool *is_processed, string *error) {
diff --git a/src/main/cpp/startup_options.h b/src/main/cpp/startup_options.h
index 4b6d2b9..81cfe78 100644
--- a/src/main/cpp/startup_options.h
+++ b/src/main/cpp/startup_options.h
@@ -18,6 +18,7 @@
 #include <memory>
 #include <set>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "src/main/cpp/util/exit_code.h"
@@ -60,6 +61,18 @@
   const std::string name_;
 };
 
+// A startup flag parsed from a bazelrc file.
+// For instance, RcStartupFlag("somepath/.bazelrc", "--foo") is used to
+// represent that the line "startup --foo" was found when parsing
+// "somepath/.bazelrc".
+struct RcStartupFlag {
+  const std::string source;
+  const std::string value;
+  RcStartupFlag(const std::string& source_arg,
+                const std::string& value_arg)
+      : source(source_arg), value(value_arg) {}
+};
+
 // 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.
@@ -96,6 +109,11 @@
                                        bool *is_space_separated,
                                        std::string *error);
 
+  // Process an ordered list of RcStartupFlags using ProcessArg.
+  blaze_exit_code::ExitCode ProcessArgs(
+      const std::vector<RcStartupFlag>& rcstartup_flags,
+      std::string *error);
+
   // Adds any other options needed to result.
   //
   // TODO(jmmv): Now that we support site-specific options via subclasses of
diff --git a/src/test/cpp/option_processor_test.cc b/src/test/cpp/option_processor_test.cc
index da7c743..ab8ea59 100644
--- a/src/test/cpp/option_processor_test.cc
+++ b/src/test/cpp/option_processor_test.cc
@@ -59,8 +59,8 @@
     std::string error;
     const std::unique_ptr<CommandLine> result =
         option_processor_->SplitCommandLine(args, &error);
-    ASSERT_EQ(nullptr, result);
     ASSERT_EQ(expected_error, error);
+    ASSERT_EQ(nullptr, result);
   }
 
   void SuccessfulSplitStartupOptionsTest(const std::vector<std::string>& args,
@@ -90,7 +90,8 @@
        "--flag", "//my:target", "--flag2=42"};
   std::string error;
   ASSERT_EQ(blaze_exit_code::SUCCESS,
-            option_processor_->ParseOptions(args, workspace_, cwd_, &error));
+            option_processor_->ParseOptions(args, workspace_, cwd_, &error))
+                << error;
 
   ASSERT_EQ("", error);
   ASSERT_EQ(1,
@@ -113,8 +114,10 @@
        "--flag", "//my:target", "--flag2=42"};
   std::string error;
   ASSERT_EQ(blaze_exit_code::SUCCESS,
-            option_processor_->ParseOptions(args, workspace_, cwd_, &error));
+            option_processor_->ParseOptions(args, workspace_, cwd_, &error))
+                << error;
 
+  ASSERT_EQ("", error);
   ASSERT_EQ(1,
             option_processor_->GetParsedStartupOptions()->host_jvm_args.size());
   EXPECT_EQ("MyParam",
@@ -131,7 +134,9 @@
   const std::vector<std::string> args = {"bazel"};
   std::string error;
   ASSERT_EQ(blaze_exit_code::SUCCESS,
-            option_processor_->ParseOptions(args, workspace_, cwd_, &error));
+            option_processor_->ParseOptions(args, workspace_, cwd_, &error))
+                << error;
+  ASSERT_EQ("", error);
 
   EXPECT_EQ("", option_processor_->GetCommand());
 
@@ -145,7 +150,9 @@
        "--nobatch", "--host_jvm_args=MyParam", "--host_jvm_args", "42"};
   std::string error;
   ASSERT_EQ(blaze_exit_code::SUCCESS,
-            option_processor_->ParseOptions(args, workspace_, cwd_, &error));
+            option_processor_->ParseOptions(args, workspace_, cwd_, &error))
+                << error;
+  ASSERT_EQ("", error);
 
   ASSERT_EQ(2,
             option_processor_->GetParsedStartupOptions()->host_jvm_args.size());
@@ -175,7 +182,8 @@
           "  For more info, run 'bazel help startup_options'.";
   std::string error;
   ASSERT_NE(blaze_exit_code::SUCCESS,
-            option_processor_->ParseOptions(args, workspace_, cwd_, &error));
+            option_processor_->ParseOptions(args, workspace_, cwd_, &error))
+                << error;
   ASSERT_EQ(expected_error, error);
 }
 
@@ -199,7 +207,8 @@
        "build"};
   std::string error;
   ASSERT_EQ(blaze_exit_code::SUCCESS,
-            option_processor_->ParseOptions(args, workspace_, cwd_, &error));
+            option_processor_->ParseOptions(args, workspace_, cwd_, &error))
+                << error;
 
   EXPECT_EQ(123, option_processor_->GetParsedStartupOptions()->max_idle_secs);
 }