Adjust the singlejar binary to accept an optional comma-separated "label" with
each entry in the --outputs flag.

RELNOTES: n/a
PiperOrigin-RevId: 173547248
diff --git a/src/tools/singlejar/options.cc b/src/tools/singlejar/options.cc
index 86c35fe..7e5a077 100644
--- a/src/tools/singlejar/options.cc
+++ b/src/tools/singlejar/options.cc
@@ -15,43 +15,55 @@
 #include "src/tools/singlejar/options.h"
 
 #include "src/tools/singlejar/diag.h"
-#include "src/tools/singlejar/token_stream.h"
 
 void Options::ParseCommandLine(int argc, const char * const argv[]) {
   ArgTokenStream tokens(argc, argv);
   std::string optarg;
   while (!tokens.AtEnd()) {
-    if (tokens.MatchAndSet("--output", &output_jar) ||
-        tokens.MatchAndSet("--main_class", &main_class) ||
-        tokens.MatchAndSet("--java_launcher", &java_launcher) ||
-        tokens.MatchAndSet("--deploy_manifest_lines", &manifest_lines) ||
-        tokens.MatchAndSet("--sources", &input_jars) ||
-        tokens.MatchAndSet("--resources", &resources) ||
-        tokens.MatchAndSet("--classpath_resources", &classpath_resources) ||
-        tokens.MatchAndSet("--include_prefixes", &include_prefixes) ||
-        tokens.MatchAndSet("--exclude_build_data", &exclude_build_data) ||
-        tokens.MatchAndSet("--compression", &force_compression) ||
-        tokens.MatchAndSet("--dont_change_compression",
-                           &preserve_compression) ||
-        tokens.MatchAndSet("--normalize", &normalize_timestamps) ||
-        tokens.MatchAndSet("--no_duplicates", &no_duplicates) ||
-        tokens.MatchAndSet("--verbose", &verbose) ||
-        tokens.MatchAndSet("--warn_duplicate_resources",
-                           &warn_duplicate_resources) ||
-        tokens.MatchAndSet("--nocompress_suffixes", &nocompress_suffixes) ||
-        tokens.MatchAndSet("--check_desugar_deps", &check_desugar_deps)) {
-      continue;
-    } else if (tokens.MatchAndSet("--build_info_file", &optarg)) {
-      build_info_files.push_back(optarg);
-      continue;
-    } else if (tokens.MatchAndSet("--extra_build_info", &optarg)) {
-      build_info_lines.push_back(optarg);
+    if (ParseToken(&tokens)) {
       continue;
     } else {
       diag_errx(1, "Bad command line argument %s", tokens.token().c_str());
     }
   }
 
+  PostValidateOptions();
+}
+
+bool Options::ParseToken(ArgTokenStream *tokens) {
+  std::string optarg;
+
+  if (tokens->MatchAndSet("--output", &output_jar) ||
+      tokens->MatchAndSet("--main_class", &main_class) ||
+      tokens->MatchAndSet("--java_launcher", &java_launcher) ||
+      tokens->MatchAndSet("--deploy_manifest_lines", &manifest_lines) ||
+      tokens->MatchAndSet("--sources", &input_jars) ||
+      tokens->MatchAndSet("--resources", &resources) ||
+      tokens->MatchAndSet("--classpath_resources", &classpath_resources) ||
+      tokens->MatchAndSet("--include_prefixes", &include_prefixes) ||
+      tokens->MatchAndSet("--exclude_build_data", &exclude_build_data) ||
+      tokens->MatchAndSet("--compression", &force_compression) ||
+      tokens->MatchAndSet("--dont_change_compression", &preserve_compression) ||
+      tokens->MatchAndSet("--normalize", &normalize_timestamps) ||
+      tokens->MatchAndSet("--no_duplicates", &no_duplicates) ||
+      tokens->MatchAndSet("--verbose", &verbose) ||
+      tokens->MatchAndSet("--warn_duplicate_resources",
+                          &warn_duplicate_resources) ||
+      tokens->MatchAndSet("--nocompress_suffixes", &nocompress_suffixes) ||
+      tokens->MatchAndSet("--check_desugar_deps", &check_desugar_deps)) {
+    return true;
+  } else if (tokens->MatchAndSet("--build_info_file", &optarg)) {
+    build_info_files.push_back(optarg);
+    return true;
+  } else if (tokens->MatchAndSet("--extra_build_info", &optarg)) {
+    build_info_lines.push_back(optarg);
+    return true;
+  }
+
+  return false;
+}
+
+void Options::PostValidateOptions() {
   if (output_jar.empty()) {
     diag_errx(1, "Use --output <output_jar> to specify the output file name");
   }
diff --git a/src/tools/singlejar/options.h b/src/tools/singlejar/options.h
index c228fc6..1fea135 100644
--- a/src/tools/singlejar/options.h
+++ b/src/tools/singlejar/options.h
@@ -17,6 +17,7 @@
 
 #include <string>
 #include <vector>
+#include "src/tools/singlejar/token_stream.h"
 
 /* Command line options. */
 class Options {
@@ -32,14 +33,16 @@
         warn_duplicate_resources(false),
         check_desugar_deps(false) {}
 
+  virtual ~Options() {}
+
   // Parses command line arguments into the fields of this instance.
-  void ParseCommandLine(int argc, const char * const argv[]);
+  void ParseCommandLine(int argc, const char *const argv[]);
 
   std::string output_jar;
   std::string main_class;
   std::string java_launcher;
   std::vector<std::string> manifest_lines;
-  std::vector<std::string> input_jars;
+  std::vector<std::pair<std::string, std::string> > input_jars;
   std::vector<std::string> resources;
   std::vector<std::string> classpath_resources;
   std::vector<std::string> build_info_files;
@@ -55,6 +58,22 @@
   bool verbose;
   bool warn_duplicate_resources;
   bool check_desugar_deps;
+
+ protected:
+  /*
+   * Given the token stream, consume one notional flag from the input stream and
+   * return true if the flag was recognized and fully consumed. This notional
+   * flag may result in many tokens being consumed, as flags like --inputs ends
+   * up consuming many future tokens: --inputs a b c d e --some_other_flag
+   */
+  virtual bool ParseToken(ArgTokenStream *tokens);
+
+  /*
+   * After all of the command line options are consumed, validate that the
+   * options make sense. This function will exit(1) if invalid combinations of
+   * flags are passed (e.g.: is missing --output_jar)
+   */
+  virtual void PostValidateOptions();
 };
 
 #endif  // THIRD_PARTY_BAZEL_SRC_TOOLS_SINGLEJAR_OPTIONS_H_
diff --git a/src/tools/singlejar/options_test.cc b/src/tools/singlejar/options_test.cc
index a16c134..2d33227 100644
--- a/src/tools/singlejar/options_test.cc
+++ b/src/tools/singlejar/options_test.cc
@@ -93,9 +93,9 @@
   options.ParseCommandLine(arraysize(args), args);
 
   ASSERT_EQ(3, options.input_jars.size());
-  EXPECT_EQ("jar1", options.input_jars[0]);
-  EXPECT_EQ("jar2", options.input_jars[1]);
-  EXPECT_EQ("jar3", options.input_jars[2]);
+  EXPECT_EQ("jar1", options.input_jars[0].first);
+  EXPECT_EQ("jar2", options.input_jars[1].first);
+  EXPECT_EQ("jar3", options.input_jars[2].first);
   ASSERT_EQ(2, options.resources.size());
   EXPECT_EQ("res1", options.resources[0]);
   EXPECT_EQ("res2", options.resources[1]);
diff --git a/src/tools/singlejar/output_jar.cc b/src/tools/singlejar/output_jar.cc
index df15874..a028df7 100644
--- a/src/tools/singlejar/output_jar.cc
+++ b/src/tools/singlejar/output_jar.cc
@@ -282,7 +282,11 @@
 }
 
 bool OutputJar::AddJar(int jar_path_index) {
-  const std::string& input_jar_path = options_->input_jars[jar_path_index];
+  const std::string &input_jar_path =
+      options_->input_jars[jar_path_index].first;
+  const std::string &input_jar_aux_label =
+      options_->input_jars[jar_path_index].second;
+
   InputJar input_jar;
   if (!input_jar.Open(input_jar_path)) {
     return false;
@@ -336,7 +340,7 @@
         known_members_.emplace(service_path, EntryInfo{service_handler});
       }
     } else {
-      ExtraHandler(jar_entry);
+      ExtraHandler(jar_entry, &input_jar_aux_label);
     }
 
     // Install a new entry unless it is already present. All the plain (non-dir)
@@ -363,10 +367,11 @@
       if (options_->no_duplicates ||
           (options_->no_duplicate_classes &&
            ends_with(file_name, file_name_length, ".class"))) {
-        diag_errx(1, "%s:%d: %.*s is present both in %s and %s", __FILE__,
-                  __LINE__, file_name_length, file_name,
-                  options_->input_jars[entry_info.input_jar_index_].c_str(),
-                  input_jar_path.c_str());
+        diag_errx(
+            1, "%s:%d: %.*s is present both in %s and %s", __FILE__, __LINE__,
+            file_name_length, file_name,
+            options_->input_jars[entry_info.input_jar_index_].first.c_str(),
+            input_jar_path.c_str());
       } else {
         duplicate_entries_++;
         continue;
@@ -913,4 +918,4 @@
   return written == count;
 }
 
-void OutputJar::ExtraHandler(const CDH *) {}
+void OutputJar::ExtraHandler(const CDH *, const std::string *) {}
diff --git a/src/tools/singlejar/output_jar.h b/src/tools/singlejar/output_jar.h
index 5917c86..0ad74c1 100644
--- a/src/tools/singlejar/output_jar.h
+++ b/src/tools/singlejar/output_jar.h
@@ -34,14 +34,15 @@
   // Constructor.
   OutputJar();
   // Do all that needs to be done. Can be called only once.
-  virtual int Doit(Options *options);
+  int Doit(Options *options);
   // Destructor.
   virtual ~OutputJar();
   // Add a combiner to handle the entries with given name. OutputJar will
   // own the instance of the combiner and will delete it on self destruction.
   void ExtraCombiner(const std::string& entry_name, Combiner *combiner);
   // Additional file handler to be redefined by a subclass.
-  virtual void ExtraHandler(const CDH *entry);
+  virtual void ExtraHandler(const CDH *entry,
+                            const std::string *input_jar_aux_label);
   // Return jar path.
   const char *path() const { return options_->output_jar.c_str(); }
 
diff --git a/src/tools/singlejar/output_jar_simple_test.cc b/src/tools/singlejar/output_jar_simple_test.cc
index 21bfbf7..a0b946c 100644
--- a/src/tools/singlejar/output_jar_simple_test.cc
+++ b/src/tools/singlejar/output_jar_simple_test.cc
@@ -54,7 +54,8 @@
 class CustomOutputJar : public OutputJar {
  public:
   ~CustomOutputJar() override {}
-  void ExtraHandler(const CDH *cdh) override {
+  void ExtraHandler(const CDH *cdh,
+                    const std::string *input_jar_aux_label) override {
     auto file_name = cdh->file_name();
     auto file_name_length = cdh->file_name_length();
     if (file_name_length > 0 && file_name[file_name_length - 1] != '/' &&
diff --git a/src/tools/singlejar/token_stream.h b/src/tools/singlejar/token_stream.h
index 5d6342c..23ee84d 100644
--- a/src/tools/singlejar/token_stream.h
+++ b/src/tools/singlejar/token_stream.h
@@ -20,6 +20,7 @@
 #include <string.h>
 #include <memory>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "src/tools/singlejar/diag.h"
@@ -223,6 +224,31 @@
     return true;
   }
 
+  // Process --OPTION OPTARG1,OPTSUFF1 OPTARG2,OPTSUFF2 ...
+  // If a current token is --OPTION, push_back all subsequent tokens up to the
+  // next option to the OPTARGS array, splitting the OPTARG,OPTSUFF by a comma,
+  // proceed to the next option and return true.
+  bool MatchAndSet(const char *option,
+                   std::vector<std::pair<std::string, std::string> > *optargs) {
+    if (token_.compare(option) != 0) {
+      return false;
+    }
+    next();
+    while (!AtEnd() && '-' != token_.at(0)) {
+      size_t commapos = token_.find(',');
+      if (commapos == std::string::npos) {
+        optargs->push_back(std::pair<std::string, std::string>(token_, ""));
+      } else {
+        std::string first = token_.substr(0, commapos);
+        token_.erase(0, commapos + 1);
+        optargs->push_back(std::pair<std::string, std::string>(first, token_));
+      }
+
+      next();
+    }
+    return true;
+  }
+
   // Current token.
   const std::string &token() const { return token_; }
 
diff --git a/src/tools/singlejar/token_stream_test.cc b/src/tools/singlejar/token_stream_test.cc
index 6d146cf..e277da3 100644
--- a/src/tools/singlejar/token_stream_test.cc
+++ b/src/tools/singlejar/token_stream_test.cc
@@ -115,3 +115,22 @@
 
   EXPECT_TRUE(token_stream.AtEnd());
 }
+
+// '--arg1 optval1,optsuff1 optval2,optstuff2 --arg2' command line.
+TEST(TokenStreamTest, OptargMultiSplit) {
+  const char *args[] = {"--arg1", "optval1,optsuff1", "optval2,optsuff2",
+                        "optvalnosuff"};
+  ArgTokenStream token_stream(ARRAY_SIZE(args), args);
+  std::vector<std::pair<std::string, std::string> > optvals1;
+
+  EXPECT_FALSE(token_stream.MatchAndSet("--foo", &optvals1));
+  ASSERT_TRUE(token_stream.MatchAndSet("--arg1", &optvals1));
+
+  ASSERT_EQ(3, optvals1.size());
+  EXPECT_EQ("optval1", optvals1[0].first);
+  EXPECT_EQ("optsuff1", optvals1[0].second);
+  EXPECT_EQ("optval2", optvals1[1].first);
+  EXPECT_EQ("optsuff2", optvals1[1].second);
+  EXPECT_EQ("optvalnosuff", optvals1[2].first);
+  EXPECT_EQ("", optvals1[2].second);
+}