Add to export_compile_commands from cmdline.

Adds a new switch to append to the export_compile_commands variable in
the dotfile. This is for users who may want to add more targets than
their project defines or, more commonly, for users to define something
when their project defines nothing.

Changes the previous behavior of the legacy filter switch. Previously it
overrode the new pattern behavior. But that prevents rolling out this
feature in a non-breaking way for users who use the switch. The new
behavior constructs the union of all ways the targets can be specified.

In support of this, adds the ability for the CommandLine to allow
multiple values for a given switch. The current behavior where the last
one is returned when querying for a single value is preserved.

Rename GetSwitchValueASCII to GetSwitchValueString and convert to UTF-8
on Windows. GN supports non-ASCII file paths which are either platform
native or UTF-8 on Windows, and this handling matches that rule. This
allows non-ASCII patterns to be specified (though the user is
responsible for getting the correct encoding on e.g. Linux).

Bug: 111, 302

Change-Id: I8ff60260b93d908b2f519c7d83ff8eafcfc5be79
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/14540
Reviewed-by: Takuto Ikuta <tikuta@google.com>
Commit-Queue: Brett Wilson <brettw@chromium.org>
diff --git a/src/base/command_line.cc b/src/base/command_line.cc
index 68bc587..0c37d53 100644
--- a/src/base/command_line.cc
+++ b/src/base/command_line.cc
@@ -184,6 +184,17 @@
 CommandLine::~CommandLine() = default;
 
 #if defined(OS_WIN)
+
+// static
+std::string CommandLine::StringTypeToUTF8(const StringType& input) {
+  return UTF16ToUTF8(input);
+}
+
+// static
+CommandLine::StringType CommandLine::UTF8ToStringType(std::string_view input) {
+  return UTF8ToUTF16(input);
+}
+
 // static
 void CommandLine::set_slash_is_not_a_switch() {
   // The last switch prefix should be slash, so adjust the size to skip it.
@@ -202,6 +213,19 @@
     argv_vector.push_back(UTF8ToUTF16(argv[i]));
   current_process_commandline_->InitFromArgv(argv_vector);
 }
+
+#else
+
+// static
+std::string CommandLine::StringTypeToUTF8(const StringType& input) {
+  return input;
+}
+
+// static
+CommandLine::StringType CommandLine::UTF8ToStringType(std::string_view input) {
+  return CommandLine::StringType(input);
+}
+
 #endif
 
 // static
@@ -292,18 +316,9 @@
   return HasSwitch(std::string_view(switch_constant));
 }
 
-std::string CommandLine::GetSwitchValueASCII(
+std::string CommandLine::GetSwitchValueString(
     std::string_view switch_string) const {
-  StringType value = GetSwitchValueNative(switch_string);
-  if (!IsStringASCII(value)) {
-    DLOG(WARNING) << "Value of switch (" << switch_string << ") must be ASCII.";
-    return std::string();
-  }
-#if defined(OS_WIN)
-  return UTF16ToASCII(value);
-#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
-  return value;
-#endif
+  return StringTypeToUTF8(GetSwitchValueNative(switch_string));
 }
 
 FilePath CommandLine::GetSwitchValuePath(std::string_view switch_string) const {
@@ -313,8 +328,43 @@
 CommandLine::StringType CommandLine::GetSwitchValueNative(
     std::string_view switch_string) const {
   DCHECK_EQ(ToLowerASCII(switch_string), switch_string);
-  auto result = switches_.find(switch_string);
-  return result == switches_.end() ? StringType() : result->second;
+
+  // There can be multiple matches, we want to find the last one.
+  auto iter = switches_.upper_bound(switch_string);
+  if (iter == switches_.begin())
+    return StringType();
+
+  // We want the item right before the upper bound, if it's a match.
+  --iter;
+  if (iter->first == switch_string)
+    return iter->second;
+  return StringType();
+}
+
+std::vector<std::string> CommandLine::GetSwitchValueStrings(
+    std::string_view switch_string) const {
+  std::vector<StringType> matches = GetSwitchValuesNative(switch_string);
+
+  std::vector<std::string> result;
+  result.reserve(matches.size());
+
+  for (const StringType& cur : matches) {
+    result.push_back(StringTypeToUTF8(cur));
+  }
+  return result;
+}
+
+std::vector<CommandLine::StringType> CommandLine::GetSwitchValuesNative(
+    std::string_view switch_string) const {
+  std::vector<StringType> result;
+
+  auto [iter, end] = switches_.equal_range(switch_string);
+  while (iter != end) {
+    result.push_back(iter->second);
+    ++iter;
+  }
+
+  return result;
 }
 
 void CommandLine::AppendSwitch(const std::string& switch_string) {
@@ -335,11 +385,10 @@
   const std::string& switch_key = switch_string;
   StringType combined_switch_string(switch_key);
 #endif
+
   size_t prefix_length = GetSwitchPrefixLength(combined_switch_string);
-  auto insertion =
-      switches_.insert(make_pair(switch_key.substr(prefix_length), value));
-  if (!insertion.second)
-    insertion.first->second = value;
+  switches_.insert(make_pair(switch_key.substr(prefix_length), value));
+
   // Preserve existing switch prefixes in |argv_|; only append one if necessary.
   if (prefix_length == 0)
     combined_switch_string = kSwitchPrefixes[0] + combined_switch_string;
@@ -349,15 +398,9 @@
   argv_.insert(argv_.begin() + begin_args_++, combined_switch_string);
 }
 
-void CommandLine::AppendSwitchASCII(const std::string& switch_string,
-                                    const std::string& value_string) {
-#if defined(OS_WIN)
-  AppendSwitchNative(switch_string, ASCIIToUTF16(value_string));
-#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
-  AppendSwitchNative(switch_string, value_string);
-#else
-#error Unsupported platform
-#endif
+void CommandLine::AppendSwitch(const std::string& switch_string,
+                               const std::string& value_string) {
+  AppendSwitchNative(switch_string, UTF8ToStringType(value_string));
 }
 
 void CommandLine::CopySwitchesFrom(const CommandLine& source,
diff --git a/src/base/command_line.h b/src/base/command_line.h
index 3a44346..ba30749 100644
--- a/src/base/command_line.h
+++ b/src/base/command_line.h
@@ -31,6 +31,7 @@
  public:
 #if defined(OS_WIN)
   // The native command line string type.
+  // See StringTypeToUTF8() and UTF8ToStringType() below.
   using StringType = std::u16string;
 #elif defined(OS_POSIX) || defined(OS_FUCHSIA)
   using StringType = std::string;
@@ -38,7 +39,7 @@
 
   using CharType = StringType::value_type;
   using StringVector = std::vector<StringType>;
-  using SwitchMap = std::map<std::string, StringType, std::less<>>;
+  using SwitchMap = std::multimap<std::string, StringType, std::less<>>;
 
   // A constructor for CommandLines that only carry switches and arguments.
   enum NoProgram { NO_PROGRAM };
@@ -57,6 +58,10 @@
 
   ~CommandLine();
 
+  // Converts the platform string type to/from UTF-8.
+  static std::string StringTypeToUTF8(const StringType& input);
+  static StringType UTF8ToStringType(std::string_view input);
+
 #if defined(OS_WIN)
   // By default this class will treat command-line arguments beginning with
   // slashes as switches on Windows, but not other platforms.
@@ -167,24 +172,35 @@
   bool HasSwitch(std::string_view switch_string) const;
   bool HasSwitch(const char switch_constant[]) const;
 
-  // Returns the value associated with the given switch. If the switch has no
-  // value or isn't present, this method returns the empty string.
-  // Switch names must be lowercase.
-  std::string GetSwitchValueASCII(std::string_view switch_string) const;
+  // Returns the last value associated with the given switch. If the switch has
+  // no value or isn't present, this method returns the empty string. Switch
+  // names must be lowercase.
+  //
+  // The "string" version returns an 8-bit representation. On Windows, this will
+  // convert to UTF-8, on 8-bit systems it will return the raw input.
+  std::string GetSwitchValueString(std::string_view switch_string) const;
   FilePath GetSwitchValuePath(std::string_view switch_string) const;
   StringType GetSwitchValueNative(std::string_view switch_string) const;
 
+  // Returns all values associated with the given switch.
+  std::vector<std::string> GetSwitchValueStrings(
+      std::string_view switch_string) const;
+  std::vector<StringType> GetSwitchValuesNative(
+      std::string_view switch_string) const;
+
   // Get a copy of all switches, along with their values.
   const SwitchMap& GetSwitches() const { return switches_; }
 
   // Append a switch [with optional value] to the command line.
   // Note: Switches will precede arguments regardless of appending order.
+  //
+  // The version that takes an 8-bit switch value converts from UTF-8 on
+  // Windows.
   void AppendSwitch(const std::string& switch_string);
   void AppendSwitchPath(const std::string& switch_string, const FilePath& path);
   void AppendSwitchNative(const std::string& switch_string,
                           const StringType& value);
-  void AppendSwitchASCII(const std::string& switch_string,
-                         const std::string& value);
+  void AppendSwitch(const std::string& switch_string, const std::string& value);
 
   // Copy a set of switches (and any values) from another command line.
   // Commonly used when launching a subprocess.
diff --git a/src/gn/command_args.cc b/src/gn/command_args.cc
index e74c598..6f4600b 100644
--- a/src/gn/command_args.cc
+++ b/src/gn/command_args.cc
@@ -212,7 +212,7 @@
   Args::ValueWithOverrideMap args =
       setup->build_settings().build_args().GetAllArguments();
   std::string list_value =
-      base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(kSwitchList);
+      base::CommandLine::ForCurrentProcess()->GetSwitchValueString(kSwitchList);
   if (!list_value.empty()) {
     // List just the one specified as the parameter to --list.
     auto found = args.find(list_value);
diff --git a/src/gn/command_desc.cc b/src/gn/command_desc.cc
index fcb3434..e3cd1eb 100644
--- a/src/gn/command_desc.cc
+++ b/src/gn/command_desc.cc
@@ -653,7 +653,7 @@
   if (args.size() == 3)
     what_to_print = args[2];
 
-  bool json = cmdline->GetSwitchValueASCII("format") == "json";
+  bool json = cmdline->GetSwitchValueString("format") == "json";
 
   if (target_matches.empty() && config_matches.empty()) {
     OutputString(
diff --git a/src/gn/command_format.cc b/src/gn/command_format.cc
index 041a037..6907710 100644
--- a/src/gn/command_format.cc
+++ b/src/gn/command_format.cc
@@ -1306,7 +1306,7 @@
   TreeDumpMode dump_tree = TreeDumpMode::kInactive;
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchDumpTree)) {
     std::string tree_type =
-        base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+        base::CommandLine::ForCurrentProcess()->GetSwitchValueString(
             kSwitchDumpTree);
     if (tree_type == kSwitchTreeTypeJSON) {
       dump_tree = TreeDumpMode::kJSON;
@@ -1361,7 +1361,7 @@
 
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchReadTree)) {
     std::string tree_type =
-        base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+        base::CommandLine::ForCurrentProcess()->GetSwitchValueString(
             kSwitchReadTree);
     if (tree_type != kSwitchTreeTypeJSON) {
       Err(Location(), "Only json supported for read-tree.\n").PrintToStdout();
diff --git a/src/gn/command_gen.cc b/src/gn/command_gen.cc
index e6ea5d7..880fb20 100644
--- a/src/gn/command_gen.cc
+++ b/src/gn/command_gen.cc
@@ -14,6 +14,7 @@
 #include "gn/eclipse_writer.h"
 #include "gn/filesystem_utils.h"
 #include "gn/json_project_writer.h"
+#include "gn/label_pattern.h"
 #include "gn/ninja_target_writer.h"
 #include "gn/ninja_tools.h"
 #include "gn/ninja_writer.h"
@@ -229,22 +230,22 @@
 
     std::string sln_name;
     if (command_line->HasSwitch(kSwitchSln))
-      sln_name = command_line->GetSwitchValueASCII(kSwitchSln);
+      sln_name = command_line->GetSwitchValueString(kSwitchSln);
     std::string filters;
     if (command_line->HasSwitch(kSwitchFilters))
-      filters = command_line->GetSwitchValueASCII(kSwitchFilters);
+      filters = command_line->GetSwitchValueString(kSwitchFilters);
     std::string win_kit;
     if (command_line->HasSwitch(kSwitchIdeValueWinSdk))
-      win_kit = command_line->GetSwitchValueASCII(kSwitchIdeValueWinSdk);
+      win_kit = command_line->GetSwitchValueString(kSwitchIdeValueWinSdk);
     std::string ninja_extra_args;
     if (command_line->HasSwitch(kSwitchNinjaExtraArgs)) {
       ninja_extra_args =
-          command_line->GetSwitchValueASCII(kSwitchNinjaExtraArgs);
+          command_line->GetSwitchValueString(kSwitchNinjaExtraArgs);
     }
     std::string ninja_executable;
     if (command_line->HasSwitch(kSwitchNinjaExecutable)) {
       ninja_executable =
-          command_line->GetSwitchValueASCII(kSwitchNinjaExecutable);
+          command_line->GetSwitchValueString(kSwitchNinjaExecutable);
     }
     bool no_deps = command_line->HasSwitch(kSwitchNoDeps);
     bool res = VisualStudioWriter::RunAndWriteFiles(
@@ -258,11 +259,11 @@
     return res;
   } else if (ide == kSwitchIdeValueXcode) {
     XcodeWriter::Options options = {
-        command_line->GetSwitchValueASCII(kSwitchXcodeProject),
-        command_line->GetSwitchValueASCII(kSwitchIdeRootTarget),
-        command_line->GetSwitchValueASCII(kSwitchNinjaExecutable),
-        command_line->GetSwitchValueASCII(kSwitchFilters),
-        command_line->GetSwitchValueASCII(kSwitchXcodeConfigurations),
+        command_line->GetSwitchValueString(kSwitchXcodeProject),
+        command_line->GetSwitchValueString(kSwitchIdeRootTarget),
+        command_line->GetSwitchValueString(kSwitchNinjaExecutable),
+        command_line->GetSwitchValueString(kSwitchFilters),
+        command_line->GetSwitchValueString(kSwitchXcodeConfigurations),
         command_line->GetSwitchValuePath(kSwitchXcodeConfigurationBuildPath),
         command_line->GetSwitchValueNative(kSwitchXcodeAdditionalFilesPatterns),
         command_line->GetSwitchValueNative(kSwitchXcodeAdditionalFilesRoots),
@@ -274,7 +275,7 @@
     }
 
     const std::string build_system =
-        command_line->GetSwitchValueASCII(kSwitchXcodeBuildSystem);
+        command_line->GetSwitchValueString(kSwitchXcodeBuildSystem);
     if (!build_system.empty()) {
       if (build_system == kSwitchXcodeBuildsystemValueNew) {
         options.build_system = XcodeBuildSystem::kNew;
@@ -297,7 +298,7 @@
   } else if (ide == kSwitchIdeValueQtCreator) {
     std::string root_target;
     if (command_line->HasSwitch(kSwitchIdeRootTarget))
-      root_target = command_line->GetSwitchValueASCII(kSwitchIdeRootTarget);
+      root_target = command_line->GetSwitchValueString(kSwitchIdeRootTarget);
     bool res = QtCreatorWriter::RunAndWriteFile(build_settings, builder, err,
                                                 root_target);
     if (res && !quiet) {
@@ -308,14 +309,14 @@
     return res;
   } else if (ide == kSwitchIdeValueJson) {
     std::string file_name =
-        command_line->GetSwitchValueASCII(kSwitchJsonFileName);
+        command_line->GetSwitchValueString(kSwitchJsonFileName);
     if (file_name.empty())
       file_name = "project.json";
     std::string exec_script =
-        command_line->GetSwitchValueASCII(kSwitchJsonIdeScript);
+        command_line->GetSwitchValueString(kSwitchJsonIdeScript);
     std::string exec_script_extra_args =
-        command_line->GetSwitchValueASCII(kSwitchJsonIdeScriptArgs);
-    std::string filters = command_line->GetSwitchValueASCII(kSwitchFilters);
+        command_line->GetSwitchValueString(kSwitchJsonIdeScriptArgs);
+    std::string filters = command_line->GetSwitchValueString(kSwitchFilters);
 
     bool res = JSONProjectWriter::RunAndWriteFiles(
         build_settings, builder, file_name, exec_script, exec_script_extra_args,
@@ -356,10 +357,11 @@
   // the command line flag is set. The command line flag takes precedence.
   const base::CommandLine* command_line =
       base::CommandLine::ForCurrentProcess();
-  bool has_switch = command_line->HasSwitch(kSwitchExportCompileCommands);
+  bool has_legacy_switch =
+      command_line->HasSwitch(kSwitchExportCompileCommands);
 
   bool has_patterns = !setup.export_compile_commands().empty();
-  if (!has_switch && !has_patterns)
+  if (!has_legacy_switch && !has_patterns)
     return true;  // No compilation database needs to be written.
 
   bool quiet = command_line->HasSwitch(switches::kQuiet);
@@ -373,21 +375,15 @@
     return false;
   base::FilePath output_path = setup.build_settings().GetFullPath(output_file);
 
-  bool ok = true;
-  if (has_switch) {
-    // Legacy format using the command-line switch.
-    std::string target_filters =
-        command_line->GetSwitchValueASCII(kSwitchExportCompileCommands);
-    ok = CompileCommandsWriter::RunAndWriteFilesLegacyFilters(
-        &setup.build_settings(), setup.builder(), output_path, target_filters,
-        err);
-  } else {
-    // Use the patterns from the .gn file.
-    ok = CompileCommandsWriter::RunAndWriteFiles(
-        &setup.build_settings(), setup.builder(), output_path,
-        setup.export_compile_commands(), err);
+  std::optional<std::string> legacy_target_filters;
+  if (has_legacy_switch) {
+    legacy_target_filters =
+        command_line->GetSwitchValueString(kSwitchExportCompileCommands);
   }
 
+  bool ok = CompileCommandsWriter::RunAndWriteFiles(
+      &setup.build_settings(), setup.builder().GetAllResolvedTargets(),
+      setup.export_compile_commands(), legacy_target_filters, output_path, err);
   if (ok && !quiet) {
     OutputString("Generating compile_commands took " +
                  base::Int64ToString(timer.Elapsed().InMilliseconds()) +
@@ -632,9 +628,28 @@
       replay of individual compilations independent of the build system.
       This is an unstable format and likely to change without warning.
 
+  --add-export-compile-commands=<label_pattern>
+      Adds an additional label pattern (see "gn help label_pattern") of a
+      target to add to the compilation database. This pattern is appended to any
+      list values specified in the export_compile_commands variable in the
+      .gn file (see "gn help dotfile"). This allows the user to add additional
+      targets to the compilation database that the project doesn't add by default.
+
+      To add more than one value, specify this switch more than once. Each
+      invocation adds an additional label pattern.
+
+      Example:
+        --add-export-compile-commands=//tools:my_tool
+        --add-export-compile-commands="//base/*"
+
   --export-compile-commands[=<target_name1,target_name2...>]
+      DEPRECATED https://bugs.chromium.org/p/gn/issues/detail?id=302.
+      Please use --add-export-compile-commands for per-user configuration, and
+      the "export_compile_commands" value in the project-level .gn file (see
+      "gn help dotfile") for per-project configuration.
+
       Overrides the value of the export_compile_commands in the .gn file (see
-      "gn help dotfile").
+      "gn help dotfile") as well as the --add-export-compile-commands switch.
 
       Unlike the .gn setting, this switch takes a legacy format which is a list
       of target names that are matched in any directory. For example, "foo" will
@@ -670,7 +685,7 @@
       base::CommandLine::ForCurrentProcess();
   if (command_line->HasSwitch(kSwitchCheck)) {
     setup->set_check_public_headers(true);
-    if (command_line->GetSwitchValueASCII(kSwitchCheck) == "system")
+    if (command_line->GetSwitchValueString(kSwitchCheck) == "system")
       setup->set_check_system_includes(true);
   }
 
@@ -732,7 +747,7 @@
     return 1;
 
   if (command_line->HasSwitch(kSwitchIde) &&
-      !RunIdeWriter(command_line->GetSwitchValueASCII(kSwitchIde),
+      !RunIdeWriter(command_line->GetSwitchValueString(kSwitchIde),
                     &setup->build_settings(), setup->builder(), &err)) {
     err.PrintToStdout();
     return 1;
diff --git a/src/gn/command_meta.cc b/src/gn/command_meta.cc
index aa5e88c..25f6de0 100644
--- a/src/gn/command_meta.cc
+++ b/src/gn/command_meta.cc
@@ -87,9 +87,9 @@
     return 1;
 
   const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
-  std::string rebase_dir = cmdline->GetSwitchValueASCII("rebase");
-  std::string data_keys_str = cmdline->GetSwitchValueASCII("data");
-  std::string walk_keys_str = cmdline->GetSwitchValueASCII("walk");
+  std::string rebase_dir = cmdline->GetSwitchValueString("rebase");
+  std::string data_keys_str = cmdline->GetSwitchValueString("data");
+  std::string walk_keys_str = cmdline->GetSwitchValueString("walk");
 
   std::vector<std::string> inputs(args.begin() + 1, args.end());
 
@@ -119,9 +119,8 @@
   if (rebase_dir.empty()) {
     rebase_source_dir = SourceDir();
   }
-  std::vector<Value> result =
-      WalkMetadata(targets, data_keys, walk_keys, rebase_source_dir,
-                   &targets_walked, &err);
+  std::vector<Value> result = WalkMetadata(
+      targets, data_keys, walk_keys, rebase_source_dir, &targets_walked, &err);
   if (err.has_error()) {
     err.PrintToStdout();
     return 1;
diff --git a/src/gn/commands.cc b/src/gn/commands.cc
index 93365ad..02926ec 100644
--- a/src/gn/commands.cc
+++ b/src/gn/commands.cc
@@ -408,7 +408,7 @@
   result.has_all_ = cmdline.HasSwitch("all");
   result.has_blame_ = cmdline.HasSwitch("blame");
   result.has_tree_ = cmdline.HasSwitch("tree");
-  result.has_format_json_ = cmdline.GetSwitchValueASCII("format") == "json";
+  result.has_format_json_ = cmdline.GetSwitchValueString("format") == "json";
   result.has_default_toolchain_ =
       cmdline.HasSwitch(switches::kDefaultToolchain);
 
@@ -419,7 +419,7 @@
 
   std::string_view target_print_switch = "as";
   if (cmdline.HasSwitch(target_print_switch)) {
-    std::string value = cmdline.GetSwitchValueASCII(target_print_switch);
+    std::string value = cmdline.GetSwitchValueString(target_print_switch);
     if (value == "buildfile") {
       result.target_print_mode_ = TARGET_PRINT_BUILDFILE;
     } else if (value == "label") {
@@ -438,7 +438,7 @@
 
   std::string_view target_type_switch = "type";
   if (cmdline.HasSwitch(target_type_switch)) {
-    std::string value = cmdline.GetSwitchValueASCII(target_type_switch);
+    std::string value = cmdline.GetSwitchValueString(target_type_switch);
     static const struct {
       const char* name;
       Target::OutputType type;
@@ -467,7 +467,7 @@
   }
   std::string_view testonly_switch = "testonly";
   if (cmdline.HasSwitch(testonly_switch)) {
-    std::string value = cmdline.GetSwitchValueASCII(testonly_switch);
+    std::string value = cmdline.GetSwitchValueString(testonly_switch);
     if (value == "true") {
       testonly_mode_ = TESTONLY_TRUE;
     } else if (value == "false") {
@@ -480,9 +480,9 @@
     }
   }
 
-  result.meta_rebase_dir_ = cmdline.GetSwitchValueASCII("rebase");
-  result.meta_data_keys_ = cmdline.GetSwitchValueASCII("data");
-  result.meta_walk_keys_ = cmdline.GetSwitchValueASCII("walk");
+  result.meta_rebase_dir_ = cmdline.GetSwitchValueString("rebase");
+  result.meta_data_keys_ = cmdline.GetSwitchValueString("data");
+  result.meta_walk_keys_ = cmdline.GetSwitchValueString("walk");
   *this = result;
   return true;
 }
diff --git a/src/gn/compile_commands_writer.cc b/src/gn/compile_commands_writer.cc
index 10c5bf5..23d6029 100644
--- a/src/gn/compile_commands_writer.cc
+++ b/src/gn/compile_commands_writer.cc
@@ -299,12 +299,15 @@
 
 bool CompileCommandsWriter::RunAndWriteFiles(
     const BuildSettings* build_settings,
-    const Builder& builder,
-    const base::FilePath& output_path,
+    const std::vector<const Target*>& all_targets,
     const std::vector<LabelPattern>& patterns,
+    const std::optional<std::string>& legacy_target_filters,
+    const base::FilePath& output_path,
     Err* err) {
-  std::vector<const Target*> to_write =
-      CollectDepsOfMatches(builder.GetAllResolvedTargets(), patterns);
+  std::vector<const Target*> to_write = CollectTargets(
+      build_settings, all_targets, patterns, legacy_target_filters, err);
+  if (err->has_error())
+    return false;
 
   StringOutputBuffer json;
   std::ostream output_to_json(&json);
@@ -313,37 +316,42 @@
   return json.WriteToFileIfChanged(output_path, err);
 }
 
-bool CompileCommandsWriter::RunAndWriteFilesLegacyFilters(
-    const BuildSettings* build_settings,
-    const Builder& builder,
-    const base::FilePath& output_path,
-    const std::string& target_filters,
+std::vector<const Target*> CompileCommandsWriter::CollectTargets(
+    const BuildSettings* build_setting,
+    const std::vector<const Target*>& all_targets,
+    const std::vector<LabelPattern>& patterns,
+    const std::optional<std::string>& legacy_target_filters,
     Err* err) {
-  std::vector<const Target*> all_targets = builder.GetAllResolvedTargets();
-
-  std::set<std::string> target_filters_set;
-  for (auto& target :
-       base::SplitString(target_filters, ",", base::TRIM_WHITESPACE,
-                         base::SPLIT_WANT_NONEMPTY)) {
-    target_filters_set.insert(target);
+  if (legacy_target_filters && legacy_target_filters->empty()) {
+    // The legacy filter was specified but has no parameter. This matches
+    // everything and we can skip any other kinds of matching.
+    return all_targets;
   }
 
-  StringOutputBuffer json;
-  std::ostream output_to_json(&json);
-  if (target_filters_set.empty()) {
-    OutputJSON(build_settings, all_targets, output_to_json);
-  } else {
-    std::vector<const Target*> preserved_targets =
-        FilterTargets(all_targets, target_filters_set);
-    OutputJSON(build_settings, preserved_targets, output_to_json);
+  // Collect the first level of target matches. These are the ones that the
+  // patterns match directly.
+  std::vector<const Target*> input_targets;
+  for (const Target* target : all_targets) {
+    if (LabelPattern::VectorMatches(patterns, target->label()))
+      input_targets.push_back(target);
   }
 
-  return json.WriteToFileIfChanged(output_path, err);
+  // Add in any legacy filter matches.
+  if (legacy_target_filters) {
+    std::vector<const Target*> legacy_matches =
+        FilterLegacyTargets(all_targets, *legacy_target_filters);
+
+    // This can produce some duplicates with the patterns but the "collect
+    // deps" phase will eliminate them.
+    input_targets.insert(input_targets.end(), legacy_matches.begin(),
+                         legacy_matches.end());
+  }
+
+  return CollectDepsOfMatches(input_targets);
 }
 
 std::vector<const Target*> CompileCommandsWriter::CollectDepsOfMatches(
-    const std::vector<const Target*>& all_targets,
-    const std::vector<LabelPattern>& patterns) {
+    const std::vector<const Target*>& input_targets) {
   // The current set of matched targets.
   TargetSet collected;
 
@@ -351,10 +359,11 @@
   // that we haven't checked so far.
   std::vector<const Target*> frontier;
 
-  // Collect the first level of target matches. These are the ones that the
+  // Collect the first level of target matches specified in the input. There may
+  // be duplicates so we still need to do the set checking.
   // patterns match directly.
-  for (const Target* target : all_targets) {
-    if (LabelPattern::VectorMatches(patterns, target->label())) {
+  for (const Target* target : input_targets) {
+    if (!collected.contains(target)) {
       collected.add(target);
       frontier.push_back(target);
     }
@@ -394,34 +403,21 @@
   return output;
 }
 
-std::vector<const Target*> CompileCommandsWriter::FilterTargets(
+std::vector<const Target*> CompileCommandsWriter::FilterLegacyTargets(
     const std::vector<const Target*>& all_targets,
-    const std::set<std::string>& target_filters_set) {
-  std::vector<const Target*> preserved_targets;
+    const std::string& target_filter_string) {
+  std::set<std::string> target_filters_set;
+  for (auto& target :
+       base::SplitString(target_filter_string, ",", base::TRIM_WHITESPACE,
+                         base::SPLIT_WANT_NONEMPTY)) {
+    target_filters_set.insert(target);
+  }
 
-  TargetSet visited;
+  std::vector<const Target*> result;
   for (auto& target : all_targets) {
-    if (target_filters_set.count(target->label().name())) {
-      VisitDeps(target, &visited);
-    }
+    if (target_filters_set.count(target->label().name()))
+      result.push_back(target);
   }
 
-  preserved_targets.reserve(visited.size());
-  // Preserve the original ordering of all_targets
-  // to allow easier debugging and testing.
-  for (auto& target : all_targets) {
-    if (visited.contains(target)) {
-      preserved_targets.push_back(target);
-    }
-  }
-  return preserved_targets;
-}
-
-void CompileCommandsWriter::VisitDeps(const Target* target,
-                                      TargetSet* visited) {
-  if (visited->add(target)) {
-    for (const auto& pair : target->GetDeps(Target::DEPS_ALL)) {
-      VisitDeps(pair.ptr, visited);
-    }
-  }
+  return result;
 }
diff --git a/src/gn/compile_commands_writer.h b/src/gn/compile_commands_writer.h
index 3193f50..6a006ef 100644
--- a/src/gn/compile_commands_writer.h
+++ b/src/gn/compile_commands_writer.h
@@ -5,6 +5,7 @@
 #ifndef TOOLS_GN_COMPILE_COMMANDS_WRITER_H_
 #define TOOLS_GN_COMPILE_COMMANDS_WRITER_H_
 
+#include <optional>
 #include <vector>
 
 #include "gn/err.h"
@@ -19,43 +20,46 @@
   // Writes a compilation database to the given file name consisting of the
   // recursive dependencies of all targets that match or are dependencies of
   // targets that match any given pattern.
-  static bool RunAndWriteFiles(const BuildSettings* build_setting,
-                               const Builder& builder,
-                               const base::FilePath& output_path,
-                               const std::vector<LabelPattern>& patterns,
-                               Err* err);
-
-  // Writes a compilation database using the legacy way of specifying which
-  // targets to output. This format uses a comma-separated list of target names
-  // ("target_name1,target_name2...") which are matched against targets in any
-  // directory. Then the recursive dependencies of these deps are collected.
   //
-  // TODO: Remove this legacy target_name behavior and use vector<LabelPattern>
-  // version consistently.
-  static bool RunAndWriteFilesLegacyFilters(const BuildSettings* build_setting,
-                                            const Builder& builder,
-                                            const base::FilePath& output_path,
-                                            const std::string& target_filters,
-                                            Err* err);
+  // The legacy target filters takes a deprecated list of comma-separated target
+  // names ("target_name1,target_name2...") which are matched against targets in
+  // any directory. This is passed as an optional to encapsulate the legacy
+  // behavior that providing the switch with no patterns matches everything, but
+  // not passing the flag (nullopt for the function parameter) matches nothing.
+  //
+  // The union of the legacy matches and the target patterns are used.
+  //
+  // TODO(https://bugs.chromium.org/p/gn/issues/detail?id=302):
+  // Remove this legacy target filters behavior.
+  static bool RunAndWriteFiles(
+      const BuildSettings* build_setting,
+      const std::vector<const Target*>& all_targets,
+      const std::vector<LabelPattern>& patterns,
+      const std::optional<std::string>& legacy_target_filters,
+      const base::FilePath& output_path,
+      Err* err);
+
+  // Collects all the targets whose commands should get written as part of
+  // RunAndWriteFiles() (separated out for unit testing).
+  static std::vector<const Target*> CollectTargets(
+      const BuildSettings* build_setting,
+      const std::vector<const Target*>& all_targets,
+      const std::vector<LabelPattern>& patterns,
+      const std::optional<std::string>& legacy_target_filters,
+      Err* err);
 
   static std::string RenderJSON(const BuildSettings* build_settings,
                                 std::vector<const Target*>& all_targets);
 
-  // Does a depth-first search of the graph starting at each target that matches
-  // the given pattern, and collects all recursive dependencies of those
-  // targets.
+  // Does a depth-first search of the graph starting at the input target and
+  // collects all recursive dependencies of those targets.
   static std::vector<const Target*> CollectDepsOfMatches(
-      const std::vector<const Target*>& all_targets,
-      const std::vector<LabelPattern>& patterns);
+      const std::vector<const Target*>& input_targets);
 
   // Performs the legacy target_name filtering.
-  static std::vector<const Target*> FilterTargets(
+  static std::vector<const Target*> FilterLegacyTargets(
       const std::vector<const Target*>& all_targets,
-      const std::set<std::string>& target_filters_set);
-
- private:
-  // This function visits the deps graph of a target in a DFS fashion.
-  static void VisitDeps(const Target* target, TargetSet* visited);
+      const std::string& target_filter_string);
 };
 
 #endif  // TOOLS_GN_COMPILE_COMMANDS_WRITER_H_
diff --git a/src/gn/compile_commands_writer_unittest.cc b/src/gn/compile_commands_writer_unittest.cc
index f2582c5..2f0294c 100644
--- a/src/gn/compile_commands_writer_unittest.cc
+++ b/src/gn/compile_commands_writer_unittest.cc
@@ -19,6 +19,10 @@
 
 namespace {
 
+bool CompareLabel(const Target* a, const Target* b) {
+  return a->label() < b->label();
+}
+
 // InputConversion needs a global scheduler object.
 class CompileCommandsTest : public TestWithScheduler {
  public:
@@ -29,6 +33,15 @@
   const TestWithScope& setup() { return setup_; }
   const Toolchain* toolchain() { return setup_.toolchain(); }
 
+  // Returns true if the two target vectors contain the same targets, order
+  // independent.
+  bool VectorsEqual(std::vector<const Target*> a,
+                    std::vector<const Target*> b) const {
+    std::sort(a.begin(), a.end(), &CompareLabel);
+    std::sort(b.begin(), b.end(), &CompareLabel);
+    return a == b;
+  }
+
  private:
   TestWithScope setup_;
 };
@@ -586,14 +599,14 @@
   EXPECT_EQ(expected, out);
 }
 
-TEST_F(CompileCommandsTest, CollectDepsOfMatches) {
+TEST_F(CompileCommandsTest, CollectTargets) {
   // Contruct the dependency tree:
   //
   //   //foo:bar1
   //     //base:base
   //   //foo:bar2
   //     //base:i18n
-  //       //base
+  //       //base:base
   //       //third_party:icu
   //   //random:random
   Err err;
@@ -643,94 +656,81 @@
   ASSERT_TRUE(random_target.OnResolved(&err));
   targets.push_back(&random_target);
 
-  // Sort for stability in comparisons below.
-  auto compare_label = [](const Target* a, const Target* b) -> bool {
-    return a->label() < b->label();
-  };
-  std::sort(targets.begin(), targets.end(), compare_label);
-
   // Collect everything, the result should match the input.
   const std::string source_root("/home/me/build/");
   LabelPattern wildcard_pattern = LabelPattern::GetPattern(
       SourceDir(), source_root, Value(nullptr, "//*"), &err);
   ASSERT_FALSE(err.has_error());
-  std::vector<const Target*> output =
-      CompileCommandsWriter::CollectDepsOfMatches(
-          targets, std::vector<LabelPattern>{wildcard_pattern});
-  std::sort(output.begin(), output.end(), compare_label);
-  EXPECT_EQ(output, targets);
+  std::vector<const Target*> output = CompileCommandsWriter::CollectTargets(
+      build_settings(), targets, std::vector<LabelPattern>{wildcard_pattern},
+      std::nullopt, &err);
+  EXPECT_TRUE(VectorsEqual(output, targets));
 
   // Collect nothing.
-  output = CompileCommandsWriter::CollectDepsOfMatches(
-      targets, std::vector<LabelPattern>());
+  output = CompileCommandsWriter::CollectTargets(build_settings(), targets,
+                                                 std::vector<LabelPattern>(),
+                                                 std::nullopt, &err);
   EXPECT_TRUE(output.empty());
 
   // Collect all deps of "//foo/*".
   LabelPattern foo_wildcard = LabelPattern::GetPattern(
       SourceDir(), source_root, Value(nullptr, "//foo/*"), &err);
   ASSERT_FALSE(err.has_error());
-  output = CompileCommandsWriter::CollectDepsOfMatches(
-      targets, std::vector<LabelPattern>{foo_wildcard});
+  output = CompileCommandsWriter::CollectTargets(
+      build_settings(), targets, std::vector<LabelPattern>{foo_wildcard},
+      std::nullopt, &err);
 
   // The result should be everything except "random".
-  std::sort(output.begin(), output.end(), compare_label);
+  std::sort(output.begin(), output.end(), &CompareLabel);
   ASSERT_EQ(5u, output.size());
-  EXPECT_EQ(&base_target, output[0]);  // Note: order sorted by label.
+  EXPECT_EQ(&base_target, output[0]);
   EXPECT_EQ(&base_i18n, output[1]);
   EXPECT_EQ(&target1, output[2]);
   EXPECT_EQ(&target2, output[3]);
   EXPECT_EQ(&icu_target, output[4]);
-}
 
-TEST_F(CompileCommandsTest, CompDBFilter) {
-  Err err;
+  // Collect everything using the legacy filter (present string but empty).
+  output = CompileCommandsWriter::CollectTargets(build_settings(), targets,
+                                                 std::vector<LabelPattern>{},
+                                                 std::string(), &err);
+  EXPECT_TRUE(VectorsEqual(output, targets));
 
-  std::vector<const Target*> targets;
-  Target target1(settings(), Label(SourceDir("//foo/"), "bar1"));
-  target1.set_output_type(Target::SOURCE_SET);
-  target1.sources().push_back(SourceFile("//foo/input1.c"));
-  target1.config_values().cflags_c().push_back("-DCONFIG=\"/config\"");
-  target1.SetToolchain(toolchain());
-  ASSERT_TRUE(target1.OnResolved(&err));
-  targets.push_back(&target1);
+  // Collect all deps of "bar2" using the legacy filter.
+  output = CompileCommandsWriter::CollectTargets(build_settings(), targets,
+                                                 std::vector<LabelPattern>{},
+                                                 std::string("bar2"), &err);
+  std::sort(output.begin(), output.end(), &CompareLabel);
+  ASSERT_EQ(4u, output.size());
+  EXPECT_EQ(&base_target, output[0]);
+  EXPECT_EQ(&base_i18n, output[1]);
+  EXPECT_EQ(&target2, output[2]);
+  EXPECT_EQ(&icu_target, output[3]);
 
-  Target target2(settings(), Label(SourceDir("//foo/"), "bar2"));
-  target2.set_output_type(Target::SOURCE_SET);
-  target2.sources().push_back(SourceFile("//foo/input2.c"));
-  target2.config_values().cflags_c().push_back("-DCONFIG=\"/config\"");
-  target2.SetToolchain(toolchain());
-  ASSERT_TRUE(target2.OnResolved(&err));
-  targets.push_back(&target2);
+  // Collect all deps of "bar1" and "bar2" using the legacy filter.
+  output = CompileCommandsWriter::CollectTargets(
+      build_settings(), targets, std::vector<LabelPattern>{},
+      std::string("bar2,bar1"), &err);
+  std::sort(output.begin(), output.end(), &CompareLabel);
+  ASSERT_EQ(5u, output.size());
+  EXPECT_EQ(&base_target, output[0]);
+  EXPECT_EQ(&base_i18n, output[1]);
+  EXPECT_EQ(&target1, output[2]);
+  EXPECT_EQ(&target2, output[3]);
+  EXPECT_EQ(&icu_target, output[4]);
 
-  Target target3(settings(), Label(SourceDir("//foo/"), "bar3"));
-  target3.set_output_type(Target::SOURCE_SET);
-  target3.sources().push_back(SourceFile("//foo/input3.c"));
-  target3.config_values().cflags_c().push_back("-DCONFIG=\"/config\"");
-  target3.SetToolchain(toolchain());
-  ASSERT_TRUE(target3.OnResolved(&err));
-  targets.push_back(&target3);
-
-  target1.private_deps().push_back(LabelTargetPair(&target2));
-  target1.private_deps().push_back(LabelTargetPair(&target3));
-
-  CompileCommandsWriter writer;
-
-  std::set<std::string> filter1;
-  std::vector<const Target*> test_results1 =
-      writer.FilterTargets(targets, filter1);
-  ASSERT_TRUE(test_results1.empty());
-
-  std::set<std::string> filter2;
-  filter2.insert(target1.label().name());
-  std::vector<const Target*> test_results2 =
-      writer.FilterTargets(targets, filter2);
-  ASSERT_EQ(test_results2, targets);
-
-  std::set<std::string> filter3;
-  filter3.insert(target2.label().name());
-  std::vector<const Target*> test_result3 =
-      writer.FilterTargets(targets, filter3);
-  std::vector<const Target*> expected_results3;
-  expected_results3.push_back(&target2);
-  ASSERT_EQ(test_result3, expected_results3);
+  // Combine the legacy (bar1) and pattern (bar2) filters, we should get the
+  // union.
+  LabelPattern foo_bar2 = LabelPattern::GetPattern(
+      SourceDir(), source_root, Value(nullptr, "//foo:bar2"), &err);
+  ASSERT_FALSE(err.has_error());
+  output = CompileCommandsWriter::CollectTargets(
+      build_settings(), targets, std::vector<LabelPattern>{foo_bar2},
+      std::string("bar1"), &err);
+  std::sort(output.begin(), output.end(), &CompareLabel);
+  ASSERT_EQ(5u, output.size());
+  EXPECT_EQ(&base_target, output[0]);
+  EXPECT_EQ(&base_i18n, output[1]);
+  EXPECT_EQ(&target1, output[2]);
+  EXPECT_EQ(&target2, output[3]);
+  EXPECT_EQ(&icu_target, output[4]);
 }
diff --git a/src/gn/ninja_build_writer.cc b/src/gn/ninja_build_writer.cc
index 05db217..6a5b91d 100644
--- a/src/gn/ninja_build_writer.cc
+++ b/src/gn/ninja_build_writer.cc
@@ -111,7 +111,7 @@
         i->first != switches::kDotfile && i->first != switches::kArgs) {
       std::string escaped_value =
           EscapeString(FilePathToUTF8(i->second), escape_shell, nullptr);
-      cmdline.AppendSwitchASCII(i->first, escaped_value);
+      cmdline.AppendSwitch(i->first, escaped_value);
     }
   }
 
diff --git a/src/gn/ninja_build_writer_unittest.cc b/src/gn/ninja_build_writer_unittest.cc
index f8abaf2..8ab5e56 100644
--- a/src/gn/ninja_build_writer_unittest.cc
+++ b/src/gn/ninja_build_writer_unittest.cc
@@ -54,7 +54,7 @@
   // (from //out/Debug to //).
   setup.build_settings()->SetRootPath(root_realpath);
   cmd_out = GetSelfInvocationCommandLine(setup.build_settings());
-  EXPECT_EQ("../..", cmd_out.GetSwitchValueASCII(switches::kRoot));
+  EXPECT_EQ("../..", cmd_out.GetSwitchValueString(switches::kRoot));
   EXPECT_FALSE(cmd_out.HasSwitch(switches::kDotfile));
 
   // If --root is . and --dotfile is foo/.gn, then --dotfile also needs
@@ -62,9 +62,9 @@
   setup.build_settings()->SetRootPath(root_realpath);
   setup.build_settings()->set_dotfile_name(gn_realpath);
   cmd_out = GetSelfInvocationCommandLine(setup.build_settings());
-  EXPECT_EQ("../..", cmd_out.GetSwitchValueASCII(switches::kRoot));
+  EXPECT_EQ("../..", cmd_out.GetSwitchValueString(switches::kRoot));
   EXPECT_EQ("../../testdot.gn",
-            cmd_out.GetSwitchValueASCII(switches::kDotfile));
+            cmd_out.GetSwitchValueString(switches::kDotfile));
 }
 
 TEST_F(NinjaBuildWriterTest, TwoTargets) {
diff --git a/src/gn/runtime_deps.cc b/src/gn/runtime_deps.cc
index 25eb2ef..9ca016d 100644
--- a/src/gn/runtime_deps.cc
+++ b/src/gn/runtime_deps.cc
@@ -133,7 +133,7 @@
                                 RuntimeDepsVector* files_to_write,
                                 Err* err) {
   std::string deps_target_list_file =
-      base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+      base::CommandLine::ForCurrentProcess()->GetSwitchValueString(
           switches::kRuntimeDepsListFile);
 
   if (deps_target_list_file.empty())
diff --git a/src/gn/setup.cc b/src/gn/setup.cc
index 41bd96d..139a919 100644
--- a/src/gn/setup.cc
+++ b/src/gn/setup.cc
@@ -127,8 +127,11 @@
       This is used for Clang-based tooling and some editor integration. See
       https://clang.llvm.org/docs/JSONCompilationDatabase.html
 
-      The switch --export-compile-commands to "gn gen" (see "gn help gen")
-      overwrites this value.
+      The switch --add-export-compile-commands to "gn gen" (see "gn help gen")
+      appends to this value which provides a per-user way to customize it.
+
+      The deprecated switch --export-compile-commands to "gn gen" (see "gn help
+      gen") adds to the export target list using a different format.
 
       Example:
         export_compile_commands = [
@@ -529,7 +532,7 @@
 
   base::FilePath build_arg_file =
       build_settings_.GetFullPath(GetBuildArgFile());
-  auto switch_value = cmdline.GetSwitchValueASCII(switches::kArgs);
+  auto switch_value = cmdline.GetSwitchValueString(switches::kArgs);
   if (cmdline.HasSwitch(switches::kArgs) ||
       (gen_empty_args_ && !PathExists(build_arg_file))) {
     if (!FillArgsFromCommandLine(
@@ -926,7 +929,7 @@
 
   // Root build file.
   if (cmdline.HasSwitch(switches::kRootTarget)) {
-    auto switch_value = cmdline.GetSwitchValueASCII(switches::kRootTarget);
+    auto switch_value = cmdline.GetSwitchValueString(switches::kRootTarget);
     Value root_value(nullptr, switch_value);
     root_target_label = Label::Resolve(current_dir, std::string_view(), Label(),
                                        root_value, err);
@@ -1080,5 +1083,20 @@
     }
   }
 
+  // Append any additional export compile command patterns from the cmdline.
+  for (const std::string& cur :
+       cmdline.GetSwitchValueStrings(switches::kAddExportCompileCommands)) {
+    LabelPattern pat = LabelPattern::GetPattern(
+        SourceDir("//"), build_settings_.root_path_utf8(), Value(nullptr, cur),
+        err);
+    if (err->has_error()) {
+      err->AppendSubErr(Err(
+          Location(),
+          "for the command-line switch --add-export-compile-commands=" + cur));
+      return false;
+    }
+    export_compile_commands_.push_back(std::move(pat));
+  }
+
   return true;
 }
diff --git a/src/gn/setup.h b/src/gn/setup.h
index 91aca82..b3abb1b 100644
--- a/src/gn/setup.h
+++ b/src/gn/setup.h
@@ -113,6 +113,8 @@
     return no_check_patterns_.get();
   }
 
+  // This is a combination of the export_compile_commands list in the dotfile,
+  // and any additions specified on the command-line.
   const std::vector<LabelPattern>& export_compile_commands() const {
     return export_compile_commands_;
   }
diff --git a/src/gn/setup_unittest.cc b/src/gn/setup_unittest.cc
index 8239589..b46bf94 100644
--- a/src/gn/setup_unittest.cc
+++ b/src/gn/setup_unittest.cc
@@ -31,7 +31,7 @@
   base::FilePath dot_gn_name = in_path.Append(FILE_PATH_LITERAL(".gn"));
   WriteFile(dot_gn_name, "buildconfig = \"//BUILDCONFIG.gn\"\n");
   WriteFile(in_path.Append(FILE_PATH_LITERAL("BUILDCONFIG.gn")), "");
-  cmdline.AppendSwitchASCII(switches::kRoot, FilePathToUTF8(in_path));
+  cmdline.AppendSwitchPath(switches::kRoot, in_path);
 
   // Create another temp dir for writing the generated files to.
   base::ScopedTempDir build_temp_dir;
@@ -49,18 +49,21 @@
 TEST_F(SetupTest, EmptyScriptExecutableDoesNotGenerateError) {
   base::CommandLine cmdline(base::CommandLine::NO_PROGRAM);
 
+  const char kDotfileContents[] = R"(
+buildconfig = "//BUILDCONFIG.gn"
+script_executable = ""
+)";
+
   // Create a temp directory containing a .gn file and a BUILDCONFIG.gn file,
   // pass it as --root.
   base::ScopedTempDir in_temp_dir;
   ASSERT_TRUE(in_temp_dir.CreateUniqueTempDir());
   base::FilePath in_path = in_temp_dir.GetPath();
   base::FilePath dot_gn_name = in_path.Append(FILE_PATH_LITERAL(".gn"));
-  WriteFile(dot_gn_name,
-            "buildconfig = \"//BUILDCONFIG.gn\"\n"
-            "script_executable = \"\"\n");
+  WriteFile(dot_gn_name, kDotfileContents);
 
   WriteFile(in_path.Append(FILE_PATH_LITERAL("BUILDCONFIG.gn")), "");
-  cmdline.AppendSwitchASCII(switches::kRoot, FilePathToUTF8(in_path));
+  cmdline.AppendSwitchPath(switches::kRoot, in_path);
 
   // Create another temp dir for writing the generated files to.
   base::ScopedTempDir build_temp_dir;
@@ -77,18 +80,21 @@
 TEST_F(SetupTest, MissingScriptExeGeneratesSetupErrorOnWindows) {
   base::CommandLine cmdline(base::CommandLine::NO_PROGRAM);
 
+  const char kDotfileContents[] = R"(
+buildconfig = "//BUILDCONFIG.gn"
+script_executable = "this_does_not_exist"
+)";
+
   // Create a temp directory containing a .gn file and a BUILDCONFIG.gn file,
   // pass it as --root.
   base::ScopedTempDir in_temp_dir;
   ASSERT_TRUE(in_temp_dir.CreateUniqueTempDir());
   base::FilePath in_path = in_temp_dir.GetPath();
   base::FilePath dot_gn_name = in_path.Append(FILE_PATH_LITERAL(".gn"));
-  WriteFile(dot_gn_name,
-            "buildconfig = \"//BUILDCONFIG.gn\"\n"
-            "script_executable = \"this_does_not_exist\"\n");
+  WriteFile(dot_gn_name, kDotfileContents);
 
   WriteFile(in_path.Append(FILE_PATH_LITERAL("BUILDCONFIG.gn")), "");
-  cmdline.AppendSwitchASCII(switches::kRoot, FilePathToUTF8(in_path));
+  cmdline.AppendSwitchPath(switches::kRoot, in_path);
 
   // Create another temp dir for writing the generated files to.
   base::ScopedTempDir build_temp_dir;
@@ -119,7 +125,7 @@
       build_file_extension = \"" +
                 extension + "\"");
   WriteFile(in_path.Append(FILE_PATH_LITERAL("BUILDCONFIG.gn")), "");
-  cmdline.AppendSwitchASCII(switches::kRoot, FilePathToUTF8(in_path));
+  cmdline.AppendSwitchPath(switches::kRoot, in_path);
 
   // Create another temp dir for writing the generated files to.
   base::ScopedTempDir build_temp_dir;
@@ -142,9 +148,51 @@
 #else
       "Build file extension 'hello/world' cannot contain a path separator"
 #endif
-        );
+  );
 }
 
 TEST_F(SetupTest, Extension) {
   RunExtensionCheckTest("yay", true, "");
 }
+
+TEST_F(SetupTest, AddExportCompileCommands) {
+  base::CommandLine cmdline(base::CommandLine::NO_PROGRAM);
+
+  // Provide a project default export compile command list.
+  const char kDotfileContents[] = R"(
+buildconfig = "//BUILDCONFIG.gn"
+export_compile_commands = [ "//base/*" ]
+)";
+
+  // Create a temp directory containing the build.
+  base::ScopedTempDir in_temp_dir;
+  ASSERT_TRUE(in_temp_dir.CreateUniqueTempDir());
+  base::FilePath in_path = in_temp_dir.GetPath();
+  base::FilePath dot_gn_name = in_path.Append(FILE_PATH_LITERAL(".gn"));
+  WriteFile(dot_gn_name, kDotfileContents);
+
+  WriteFile(in_path.Append(FILE_PATH_LITERAL("BUILDCONFIG.gn")), "");
+  cmdline.AppendSwitch(switches::kRoot, FilePathToUTF8(in_path));
+
+  // Two additions to the compile commands list.
+  cmdline.AppendSwitch(switches::kAddExportCompileCommands,
+                       "//tools:doom_melon");
+  cmdline.AppendSwitch(switches::kAddExportCompileCommands, "//src/gn:*");
+
+  // Create another temp dir for writing the generated files to.
+  base::ScopedTempDir build_temp_dir;
+  ASSERT_TRUE(build_temp_dir.CreateUniqueTempDir());
+
+  // Run setup and check that the .gn file is in the scheduler's gen deps.
+  Setup setup;
+  Err err;
+  EXPECT_TRUE(setup.DoSetupWithErr(FilePathToUTF8(build_temp_dir.GetPath()),
+                                   true, cmdline, &err));
+
+  // The export compile commands should have three items.
+  const std::vector<LabelPattern>& export_cc = setup.export_compile_commands();
+  ASSERT_EQ(3u, export_cc.size());
+  EXPECT_EQ("//base/*", export_cc[0].Describe());
+  EXPECT_EQ("//tools:doom_melon", export_cc[1].Describe());
+  EXPECT_EQ("//src/gn:*", export_cc[2].Describe());
+}
diff --git a/src/gn/switches.cc b/src/gn/switches.cc
index e670517..d86c349 100644
--- a/src/gn/switches.cc
+++ b/src/gn/switches.cc
@@ -153,8 +153,7 @@
 )";
 
 const char kRootTarget[] = "root-target";
-const char kRootTarget_HelpShort[] =
-    "--root-target: Override the root target.";
+const char kRootTarget_HelpShort[] = "--root-target: Override the root target.";
 const char kRootTarget_Help[] =
     R"(--root-target: Override the root target.
 
@@ -284,6 +283,9 @@
 const char kDefaultToolchain[] = "default-toolchain";
 
 const char kRegeneration[] = "regeneration";
+
+const char kAddExportCompileCommands[] = "add-export-compile-commands";
+
 // -----------------------------------------------------------------------------
 
 SwitchInfo::SwitchInfo() : short_help(""), long_help("") {}
diff --git a/src/gn/switches.h b/src/gn/switches.h
index 22099d3..3d1943f 100644
--- a/src/gn/switches.h
+++ b/src/gn/switches.h
@@ -114,6 +114,10 @@
 // so it can be shared between command_gen and ninja_build_writer.
 extern const char kRegeneration[];
 
+// This switch is read by Setup so needs to be in this global place, but is
+// relevant only by command_gen so is documented there.
+extern const char kAddExportCompileCommands[];
+
 }  // namespace switches
 
 #endif  // TOOLS_GN_SWITCHES_H_
diff --git a/src/util/worker_pool.cc b/src/util/worker_pool.cc
index 9f6a47b..f594699 100644
--- a/src/util/worker_pool.cc
+++ b/src/util/worker_pool.cc
@@ -53,7 +53,7 @@
 
 int GetThreadCount() {
   std::string thread_count =
-      base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+      base::CommandLine::ForCurrentProcess()->GetSwitchValueString(
           switches::kThreads);
 
   // See if an override was specified on the command line.