Add option to supply multiple comma-separated types in --type

This refactors --type parsing in commands.cc to accept a comma-separated
list of target types.

The is needed to efficiently filter test targets in `autotest.py` script
in Chromium: http://crrev.com/c/8139431

Bug: 534608615
Change-Id: Iea625b69218583734b4ea4786788168e6a6a6964
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/24360
Commit-Queue: Zgroza (Luke) Klimek <zgroza@chromium.org>
Reviewed-by: Andrew Grieve <agrieve@google.com>
Reviewed-by: Takuto Ikuta <tikuta@google.com>
diff --git a/docs/reference.md b/docs/reference.md
index 3dcefac..75d3b42 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -675,7 +675,8 @@
           generated_file|group|loadable_module|rust_library|
           rust_proc_macro|shared_library|source_set|static_library)
       Restrict outputs to targets matching the given type. If
-      unspecified, no filtering will be performed.
+      unspecified, no filtering will be performed. You can specify
+      a comma-separated list of types to match multiple types.
 ```
 
 #### **Note**
@@ -1088,7 +1089,8 @@
           generated_file|group|loadable_module|rust_library|
           rust_proc_macro|shared_library|source_set|static_library)
       Restrict outputs to targets matching the given type. If
-      unspecified, no filtering will be performed.
+      unspecified, no filtering will be performed. You can specify
+      a comma-separated list of types to match multiple types.
 ```
 
 #### **Examples**
@@ -1351,7 +1353,8 @@
           generated_file|group|loadable_module|rust_library|
           rust_proc_macro|shared_library|source_set|static_library)
       Restrict outputs to targets matching the given type. If
-      unspecified, no filtering will be performed.
+      unspecified, no filtering will be performed. You can specify
+      a comma-separated list of types to match multiple types.
 
   --relation=(source|public|input|data|script|output)
       Restricts output to targets which refer to input files by a specific
diff --git a/src/gn/commands.cc b/src/gn/commands.cc
index bae4c4a..a2cca9a 100644
--- a/src/gn/commands.cc
+++ b/src/gn/commands.cc
@@ -145,11 +145,11 @@
 // current process. Returns true on success. On error, prints a message to the
 // console and returns false.
 //
-// Target::UNKNOWN will be set if there is no filter. Target::ACTION_FOREACH
+// The vector will be empty if there is no filter. Target::ACTION_FOREACH
 // will never be returned. Code applying the filters should apply Target::ACTION
 // to both ACTION and ACTION_FOREACH.
-bool GetTargetTypeFilter(Target::OutputType* type) {
-  *type = CommandSwitches::Get().target_type();
+bool GetTargetTypeFilter(std::vector<Target::OutputType>* types) {
+  *types = CommandSwitches::Get().target_types();
   return true;
 }
 
@@ -180,10 +180,10 @@
 // Applies any target type filtering specified on the command line to the given
 // target set. On failure, prints an error and returns false.
 bool ApplyTypeFilter(std::vector<const Target*>* targets) {
-  Target::OutputType type = Target::UNKNOWN;
-  if (!GetTargetTypeFilter(&type))
+  std::vector<Target::OutputType> types;
+  if (!GetTargetTypeFilter(&types))
     return false;
-  if (targets->empty() || type == Target::UNKNOWN)
+  if (targets->empty() || types.empty())
     return true;  // Nothing to filter out.
 
   // Filter into a copy of the vector, then replace the output.
@@ -191,11 +191,14 @@
   result.reserve(targets->size());
 
   for (const Target* target : *targets) {
-    // Make "action" also apply to ACTION_FOREACH.
-    if (target->output_type() == type ||
-        (type == Target::ACTION &&
-         target->output_type() == Target::ACTION_FOREACH))
+    if (std::ranges::any_of(types, [target](Target::OutputType type) {
+          // Make "action" also apply to ACTION_FOREACH.
+          return target->output_type() == type ||
+                 (type == Target::ACTION &&
+                  target->output_type() == Target::ACTION_FOREACH);
+        })) {
       result.push_back(target);
+    }
   }
 
   *targets = std::move(result);
@@ -463,6 +466,8 @@
   std::string_view target_type_switch = "type";
   if (cmdline.HasSwitch(target_type_switch)) {
     std::string value = cmdline.GetSwitchValueString(target_type_switch);
+    std::vector<std::string> tokens = base::SplitString(
+        value, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
     static const struct {
       const char* name;
       Target::OutputType type;
@@ -481,17 +486,20 @@
         {"bundle_data", Target::BUNDLE_DATA},
         {"create_bundle", Target::CREATE_BUNDLE},
     };
-    bool found = false;
-    for (const auto& type : kTypes) {
-      if (value == type.name) {
-        result.target_type_ = type.type;
-        found = true;
-        break;
+    for (const std::string& token : tokens) {
+      bool found = false;
+      for (const auto& type : kTypes) {
+        if (token == type.name) {
+          result.target_types_.push_back(type.type);
+          found = true;
+          break;
+        }
       }
-    }
-    if (!found) {
-      Err(Location(), "Invalid value for \"--type\".").PrintToStdout();
-      return false;
+      if (!found) {
+        Err(Location(), "Invalid value \"" + token + "\" for \"--type\".")
+            .PrintToStdout();
+        return false;
+      }
     }
   }
   std::string_view testonly_switch = "testonly";
diff --git a/src/gn/commands.h b/src/gn/commands.h
index 9a996b1..27fd81e 100644
--- a/src/gn/commands.h
+++ b/src/gn/commands.h
@@ -198,7 +198,9 @@
   TargetPrintMode target_print_mode() const { return target_print_mode_; }
 
   // For --type=TARGET_TYPE
-  Target::OutputType target_type() const { return target_type_; }
+  const std::vector<Target::OutputType>& target_types() const {
+    return target_types_;
+  }
 
   enum TestonlyMode {
     TESTONLY_NONE,   // no --testonly used.
@@ -245,7 +247,7 @@
   bool has_with_data_ = false;
 
   TargetPrintMode target_print_mode_ = TARGET_PRINT_LABEL;
-  Target::OutputType target_type_ = Target::UNKNOWN;
+  std::vector<Target::OutputType> target_types_;
   TestonlyMode testonly_mode_ = TESTONLY_NONE;
 
   std::string meta_rebase_dir_;
@@ -365,7 +367,8 @@
   "          generated_file|group|loadable_module|rust_library|\n"        \
   "          rust_proc_macro|shared_library|source_set|static_library)\n" \
   "      Restrict outputs to targets matching the given type. If\n"       \
-  "      unspecified, no filtering will be performed.\n"
+  "      unspecified, no filtering will be performed. You can specify\n"  \
+  "      a comma-separated list of types to match multiple types.\n"
 #define TARGET_TESTONLY_FILTER_COMMAND_LINE_HELP                           \
   "  --testonly=(true|false)\n"                                            \
   "      Restrict outputs to targets with the testonly flag set\n"         \
diff --git a/src/gn/commands_unittest.cc b/src/gn/commands_unittest.cc
index fe27816..eb95f68 100644
--- a/src/gn/commands_unittest.cc
+++ b/src/gn/commands_unittest.cc
@@ -83,4 +83,30 @@
     commands::CommandSwitches empty_switches;
     commands::CommandSwitches::Set(empty_switches);
   }
+
+  // Test multiple types separated by a comma.
+  {
+    std::vector<const Target*> targets_to_filter = all_targets;
+
+    base::CommandLine cmdline(base::CommandLine::NO_PROGRAM);
+    cmdline.AppendSwitch("type", "executable,rust_library");
+
+    commands::CommandSwitches::Init(cmdline);
+
+    base::ListValue out;
+    commands::FilterAndPrintTargets(&targets_to_filter, &out);
+
+    ASSERT_EQ(2u, targets_to_filter.size());
+    EXPECT_TRUE(
+        std::ranges::any_of(targets_to_filter, [](const Target* target) {
+          return target->output_type() == Target::EXECUTABLE;
+        }));
+    EXPECT_TRUE(
+        std::ranges::any_of(targets_to_filter, [](const Target* target) {
+          return target->output_type() == Target::RUST_LIBRARY;
+        }));
+
+    commands::CommandSwitches empty_switches;
+    commands::CommandSwitches::Set(empty_switches);
+  }
 }