Skip duplicated items when generating build command

For some of the compiler settings, duplicate items can be
omitted (e.g. include_dirs, frameworks, libs, ...). The
order of the item is important, so changing the storage
to std::set<T> would not work (and this would break with
recursion anyway).

Instead add support to the code generating to compiler
command-line to skip over duplicated item when it make
sense.

Bug: none
Change-Id: I9096a3a804b5e9a3673c32f816b0e189eda4de84
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/11701
Commit-Queue: Sylvain Defresne <sdefresne@chromium.org>
Reviewed-by: Brett Wilson <brettw@chromium.org>
diff --git a/src/gn/compile_commands_writer.cc b/src/gn/compile_commands_writer.cc
index 1d9d5b4..74f6990 100644
--- a/src/gn/compile_commands_writer.cc
+++ b/src/gn/compile_commands_writer.cc
@@ -57,12 +57,13 @@
 // NOTE: The Windows compiler cannot properly deduce the first parameter type
 // so pass it at each call site to ensure proper builds for this platform.
 template <typename T, typename Writer>
-std::string FlagsGetter(const Target* target,
+std::string FlagsGetter(RecursiveWriterConfig config,
+                        const Target* target,
                         const std::vector<T>& (ConfigValues::*getter)() const,
                         const Writer& writer) {
   std::string result;
   std::ostringstream out;
-  RecursiveTargetConfigToStream<T>(target, getter, writer, out);
+  RecursiveTargetConfigToStream<T>(config, target, getter, writer, out);
   base::EscapeJSONString(out.str(), false, &result);
   return result;
 };
@@ -74,54 +75,60 @@
   bool has_precompiled_headers =
       target->config_values().has_precompiled_headers();
 
-  flags.defines =
-      FlagsGetter<std::string>(target, &ConfigValues::defines,
-                               DefineWriter(ESCAPE_COMPILATION_DATABASE));
+  flags.defines = FlagsGetter<std::string>(
+      kRecursiveWriterSkipDuplicates, target, &ConfigValues::defines,
+      DefineWriter(ESCAPE_COMPILATION_DATABASE));
 
-  flags.framework_dirs =
-      FlagsGetter<SourceDir>(target, &ConfigValues::framework_dirs,
-                             FrameworkDirsWriter(path_output, "-F"));
+  flags.framework_dirs = FlagsGetter<SourceDir>(
+      kRecursiveWriterSkipDuplicates, target, &ConfigValues::framework_dirs,
+      FrameworkDirsWriter(path_output, "-F"));
 
   flags.frameworks = FlagsGetter<std::string>(
-      target, &ConfigValues::frameworks,
+      kRecursiveWriterSkipDuplicates, target, &ConfigValues::frameworks,
       FrameworksWriter(ESCAPE_COMPILATION_DATABASE, "-framework"));
   flags.frameworks += FlagsGetter<std::string>(
-      target, &ConfigValues::weak_frameworks,
+      kRecursiveWriterSkipDuplicates, target, &ConfigValues::weak_frameworks,
       FrameworksWriter(ESCAPE_COMPILATION_DATABASE, "-weak_framework"));
 
-  flags.includes = FlagsGetter<SourceDir>(target, &ConfigValues::include_dirs,
+  flags.includes = FlagsGetter<SourceDir>(kRecursiveWriterSkipDuplicates,
+                                          target, &ConfigValues::include_dirs,
                                           IncludeWriter(path_output));
 
   // Helper lambda to call WriteOneFlag() and return the resulting
   // escaped JSON string.
-  auto one_flag = [&](const Substitution* substitution,
+  auto one_flag = [&](RecursiveWriterConfig config,
+                      const Substitution* substitution,
                       bool has_precompiled_headers, const char* tool_name,
                       const std::vector<std::string>& (ConfigValues::*getter)()
                           const) -> std::string {
     std::string result;
     std::ostringstream out;
-    WriteOneFlag(target, substitution, has_precompiled_headers, tool_name,
-                 getter, opts, path_output, out, /*write_substitution=*/false);
+    WriteOneFlag(config, target, substitution, has_precompiled_headers,
+                 tool_name, getter, opts, path_output, out,
+                 /*write_substitution=*/false);
     base::EscapeJSONString(out.str(), false, &result);
     return result;
   };
 
-  flags.cflags = one_flag(&CSubstitutionCFlags, false, Tool::kToolNone,
-                          &ConfigValues::cflags);
+  flags.cflags = one_flag(kRecursiveWriterKeepDuplicates, &CSubstitutionCFlags,
+                          false, Tool::kToolNone, &ConfigValues::cflags);
 
-  flags.cflags_c = one_flag(&CSubstitutionCFlagsC, has_precompiled_headers,
+  flags.cflags_c = one_flag(kRecursiveWriterKeepDuplicates,
+                            &CSubstitutionCFlagsC, has_precompiled_headers,
                             CTool::kCToolCc, &ConfigValues::cflags_c);
 
-  flags.cflags_cc = one_flag(&CSubstitutionCFlagsCc, has_precompiled_headers,
+  flags.cflags_cc = one_flag(kRecursiveWriterKeepDuplicates,
+                             &CSubstitutionCFlagsCc, has_precompiled_headers,
                              CTool::kCToolCxx, &ConfigValues::cflags_cc);
 
-  flags.cflags_objc =
-      one_flag(&CSubstitutionCFlagsObjC, has_precompiled_headers,
-               CTool::kCToolObjC, &ConfigValues::cflags_objc);
+  flags.cflags_objc = one_flag(
+      kRecursiveWriterKeepDuplicates, &CSubstitutionCFlagsObjC,
+      has_precompiled_headers, CTool::kCToolObjC, &ConfigValues::cflags_objc);
 
   flags.cflags_objcc =
-      one_flag(&CSubstitutionCFlagsObjCc, has_precompiled_headers,
-               CTool::kCToolObjCxx, &ConfigValues::cflags_objcc);
+      one_flag(kRecursiveWriterKeepDuplicates, &CSubstitutionCFlagsObjCc,
+               has_precompiled_headers, CTool::kCToolObjCxx,
+               &ConfigValues::cflags_objcc);
 }
 
 void WriteFile(const SourceFile& source,
diff --git a/src/gn/config_values_extractors.cc b/src/gn/config_values_extractors.cc
index 409e11d..a369008 100644
--- a/src/gn/config_values_extractors.cc
+++ b/src/gn/config_values_extractors.cc
@@ -25,10 +25,11 @@
 }  // namespace
 
 void RecursiveTargetConfigStringsToStream(
+    RecursiveWriterConfig config,
     const Target* target,
     const std::vector<std::string>& (ConfigValues::*getter)() const,
     const EscapeOptions& escape_options,
     std::ostream& out) {
-  RecursiveTargetConfigToStream(target, getter,
+  RecursiveTargetConfigToStream(config, target, getter,
                                 EscapedStringWriter(escape_options), out);
 }
diff --git a/src/gn/config_values_extractors.h b/src/gn/config_values_extractors.h
index ae51761..6479f21 100644
--- a/src/gn/config_values_extractors.h
+++ b/src/gn/config_values_extractors.h
@@ -68,32 +68,43 @@
   int cur_index_ = -1;
 };
 
-template <typename T, class Writer>
-inline void ConfigValuesToStream(const ConfigValues& values,
-                                 const std::vector<T>& (ConfigValues::*getter)()
-                                     const,
-                                 const Writer& writer,
-                                 std::ostream& out) {
-  const std::vector<T>& v = (values.*getter)();
-  for (size_t i = 0; i < v.size(); i++)
-    writer(v[i], out);
-}
+enum RecursiveWriterConfig {
+  kRecursiveWriterKeepDuplicates,
+  kRecursiveWriterSkipDuplicates,
+};
 
 // Writes a given config value that applies to a given target. This collects
 // all values from the target itself and all configs that apply, and writes
 // then in order.
 template <typename T, class Writer>
 inline void RecursiveTargetConfigToStream(
+    RecursiveWriterConfig config,
     const Target* target,
     const std::vector<T>& (ConfigValues::*getter)() const,
     const Writer& writer,
     std::ostream& out) {
-  for (ConfigValuesIterator iter(target); !iter.done(); iter.Next())
-    ConfigValuesToStream(iter.cur(), getter, writer, out);
+  std::set<T> seen;
+  for (ConfigValuesIterator iter(target); !iter.done(); iter.Next()) {
+    const std::vector<T>& values = ((iter.cur()).*getter)();
+    for (size_t i = 0; i < values.size(); i++) {
+      switch (config) {
+        case kRecursiveWriterKeepDuplicates:
+          writer(values[i], out);
+          break;
+
+        case kRecursiveWriterSkipDuplicates:
+          if (seen.find(values[i]) == seen.end()) {
+            seen.insert(values[i]);
+            writer(values[i], out);
+          }
+      }
+    }
+  }
 }
 
 // Writes the values out as strings with no transformation.
 void RecursiveTargetConfigStringsToStream(
+    RecursiveWriterConfig config,
     const Target* target,
     const std::vector<std::string>& (ConfigValues::*getter)() const,
     const EscapeOptions& escape_options,
diff --git a/src/gn/config_values_extractors_unittest.cc b/src/gn/config_values_extractors_unittest.cc
index 7c520ad..860d87c 100644
--- a/src/gn/config_values_extractors_unittest.cc
+++ b/src/gn/config_values_extractors_unittest.cc
@@ -37,6 +37,8 @@
   // Set up dep2, direct and all dependent configs.
   Config dep2_all(setup.settings(), Label(SourceDir("//dep2/"), "all"));
   dep2_all.own_values().cflags().push_back("--dep2-all");
+  dep2_all.own_values().cflags().push_back("--dep2-all");
+  dep2_all.own_values().include_dirs().push_back(SourceDir("//dep2/all/"));
   dep2_all.own_values().include_dirs().push_back(SourceDir("//dep2/all/"));
   ASSERT_TRUE(dep2_all.OnResolved(&err));
 
@@ -124,16 +126,18 @@
   std::ostringstream flag_out;
   FlagWriter flag_writer;
   RecursiveTargetConfigToStream<std::string, FlagWriter>(
-      &target, &ConfigValues::cflags, flag_writer, flag_out);
+      kRecursiveWriterKeepDuplicates, &target, &ConfigValues::cflags,
+      flag_writer, flag_out);
   EXPECT_EQ(flag_out.str(),
             "--target --target-config --target-all --target-direct "
-            "--dep1-all --dep1-all-sub --dep2-all --dep1-direct ");
+            "--dep1-all --dep1-all-sub --dep2-all --dep2-all --dep1-direct ");
 
   // Verify include dirs by serializing.
   std::ostringstream include_out;
   IncludeWriter include_writer;
   RecursiveTargetConfigToStream<SourceDir, IncludeWriter>(
-      &target, &ConfigValues::include_dirs, include_writer, include_out);
+      kRecursiveWriterSkipDuplicates, &target, &ConfigValues::include_dirs,
+      include_writer, include_out);
   EXPECT_EQ(include_out.str(),
             "//target/ //target/config/ //target/all/ //target/direct/ "
             "//dep1/all/ //dep2/all/ //dep1/direct/ ");
diff --git a/src/gn/desc_builder.cc b/src/gn/desc_builder.cc
index bbbf831..325735a 100644
--- a/src/gn/desc_builder.cc
+++ b/src/gn/desc_builder.cc
@@ -491,26 +491,39 @@
       FillInBundle(res.get());
 
     if (is_binary_output) {
-#define CONFIG_VALUE_ARRAY_HANDLER(name, type)                    \
-  if (what(#name)) {                                              \
-    ValuePtr ptr = RenderConfigValues<type>(&ConfigValues::name); \
-    if (ptr) {                                                    \
-      res->SetWithoutPathExpansion(#name, std::move(ptr));        \
-    }                                                             \
+#define CONFIG_VALUE_ARRAY_HANDLER(name, type, config)                    \
+  if (what(#name)) {                                                      \
+    ValuePtr ptr = RenderConfigValues<type>(config, &ConfigValues::name); \
+    if (ptr) {                                                            \
+      res->SetWithoutPathExpansion(#name, std::move(ptr));                \
+    }                                                                     \
   }
-      CONFIG_VALUE_ARRAY_HANDLER(arflags, std::string)
-      CONFIG_VALUE_ARRAY_HANDLER(asmflags, std::string)
-      CONFIG_VALUE_ARRAY_HANDLER(cflags, std::string)
-      CONFIG_VALUE_ARRAY_HANDLER(cflags_c, std::string)
-      CONFIG_VALUE_ARRAY_HANDLER(cflags_cc, std::string)
-      CONFIG_VALUE_ARRAY_HANDLER(cflags_objc, std::string)
-      CONFIG_VALUE_ARRAY_HANDLER(cflags_objcc, std::string)
-      CONFIG_VALUE_ARRAY_HANDLER(rustflags, std::string)
-      CONFIG_VALUE_ARRAY_HANDLER(defines, std::string)
-      CONFIG_VALUE_ARRAY_HANDLER(include_dirs, SourceDir)
-      CONFIG_VALUE_ARRAY_HANDLER(inputs, SourceFile)
-      CONFIG_VALUE_ARRAY_HANDLER(ldflags, std::string)
-      CONFIG_VALUE_ARRAY_HANDLER(swiftflags, std::string)
+      CONFIG_VALUE_ARRAY_HANDLER(arflags, std::string,
+                                 kRecursiveWriterKeepDuplicates)
+      CONFIG_VALUE_ARRAY_HANDLER(asmflags, std::string,
+                                 kRecursiveWriterKeepDuplicates)
+      CONFIG_VALUE_ARRAY_HANDLER(cflags, std::string,
+                                 kRecursiveWriterKeepDuplicates)
+      CONFIG_VALUE_ARRAY_HANDLER(cflags_c, std::string,
+                                 kRecursiveWriterKeepDuplicates)
+      CONFIG_VALUE_ARRAY_HANDLER(cflags_cc, std::string,
+                                 kRecursiveWriterKeepDuplicates)
+      CONFIG_VALUE_ARRAY_HANDLER(cflags_objc, std::string,
+                                 kRecursiveWriterKeepDuplicates)
+      CONFIG_VALUE_ARRAY_HANDLER(cflags_objcc, std::string,
+                                 kRecursiveWriterKeepDuplicates)
+      CONFIG_VALUE_ARRAY_HANDLER(rustflags, std::string,
+                                 kRecursiveWriterKeepDuplicates)
+      CONFIG_VALUE_ARRAY_HANDLER(defines, std::string,
+                                 kRecursiveWriterSkipDuplicates)
+      CONFIG_VALUE_ARRAY_HANDLER(include_dirs, SourceDir,
+                                 kRecursiveWriterSkipDuplicates)
+      CONFIG_VALUE_ARRAY_HANDLER(inputs, SourceFile,
+                                 kRecursiveWriterKeepDuplicates)
+      CONFIG_VALUE_ARRAY_HANDLER(ldflags, std::string,
+                                 kRecursiveWriterKeepDuplicates)
+      CONFIG_VALUE_ARRAY_HANDLER(swiftflags, std::string,
+                                 kRecursiveWriterKeepDuplicates)
 #undef CONFIG_VALUE_ARRAY_HANDLER
 
       // Libs and lib_dirs are handled specially below.
@@ -814,8 +827,10 @@
   // attribution.
   // This should match RecursiveTargetConfigToStream in the order it traverses.
   template <class T>
-  ValuePtr RenderConfigValues(const std::vector<T>& (ConfigValues::*getter)()
+  ValuePtr RenderConfigValues(RecursiveWriterConfig writer_config,
+                              const std::vector<T>& (ConfigValues::*getter)()
                                   const) {
+    std::set<T> seen;
     auto res = std::make_unique<base::ListValue>();
     for (ConfigValuesIterator iter(target_); !iter.done(); iter.Next()) {
       const std::vector<T>& vec = (iter.cur().*getter)();
@@ -844,7 +859,24 @@
         }
       }
 
+      // If blame is on, then do not de-dup across configs.
+      if (blame_)
+        seen.clear();
+
       for (const T& val : vec) {
+        switch (writer_config) {
+          case kRecursiveWriterKeepDuplicates:
+            break;
+
+          case kRecursiveWriterSkipDuplicates: {
+            if (seen.find(val) != seen.end())
+              continue;
+
+            seen.insert(val);
+            break;
+          }
+        }
+
         ValuePtr rendered = RenderValue(val);
         std::string str;
         // Indent string values in blame mode
diff --git a/src/gn/ninja_binary_target_writer.cc b/src/gn/ninja_binary_target_writer.cc
index e4b0652..0b7e4ea 100644
--- a/src/gn/ninja_binary_target_writer.cc
+++ b/src/gn/ninja_binary_target_writer.cc
@@ -300,8 +300,9 @@
     const SourceFile* optional_def_file) {
   if (tool->AsC()) {
     // First the ldflags from the target and its config.
-    RecursiveTargetConfigStringsToStream(target_, &ConfigValues::ldflags,
-                                       GetFlagOptions(), out);
+    RecursiveTargetConfigStringsToStream(kRecursiveWriterKeepDuplicates,
+                                         target_, &ConfigValues::ldflags,
+                                         GetFlagOptions(), out);
   }
 
   // Followed by library search paths that have been recursively pushed
diff --git a/src/gn/ninja_c_binary_target_writer.cc b/src/gn/ninja_c_binary_target_writer.cc
index b52475f..c1432b2 100644
--- a/src/gn/ninja_c_binary_target_writer.cc
+++ b/src/gn/ninja_c_binary_target_writer.cc
@@ -228,7 +228,8 @@
   // Defines.
   if (subst.used.count(&CSubstitutionDefines)) {
     out_ << CSubstitutionDefines.ninja_name << " =";
-    RecursiveTargetConfigToStream<std::string>(target_, &ConfigValues::defines,
+    RecursiveTargetConfigToStream<std::string>(kRecursiveWriterSkipDuplicates,
+                                               target_, &ConfigValues::defines,
                                                DefineWriter(), out_);
     out_ << std::endl;
   }
@@ -242,7 +243,7 @@
         path_output_.current_dir(),
         settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND);
     RecursiveTargetConfigToStream<SourceDir>(
-        target_, &ConfigValues::framework_dirs,
+        kRecursiveWriterSkipDuplicates, target_, &ConfigValues::framework_dirs,
         FrameworkDirsWriter(framework_dirs_output,
                             tool->framework_dir_switch()),
         out_);
@@ -256,7 +257,7 @@
         path_output_.current_dir(),
         settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND);
     RecursiveTargetConfigToStream<SourceDir>(
-        target_, &ConfigValues::include_dirs,
+        kRecursiveWriterSkipDuplicates, target_, &ConfigValues::include_dirs,
         IncludeWriter(include_path_output), out_);
     out_ << std::endl;
   }
@@ -278,7 +279,8 @@
   EscapeOptions opts = GetFlagOptions();
   if (target_->source_types_used().Get(SourceFile::SOURCE_S) ||
       target_->source_types_used().Get(SourceFile::SOURCE_ASM)) {
-    WriteOneFlag(target_, &CSubstitutionAsmFlags, false, Tool::kToolNone,
+    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
+                 &CSubstitutionAsmFlags, false, Tool::kToolNone,
                  &ConfigValues::asmflags, opts, path_output_, out_);
   }
   if (target_->source_types_used().Get(SourceFile::SOURCE_C) ||
@@ -286,27 +288,31 @@
       target_->source_types_used().Get(SourceFile::SOURCE_M) ||
       target_->source_types_used().Get(SourceFile::SOURCE_MM) ||
       target_->source_types_used().Get(SourceFile::SOURCE_MODULEMAP)) {
-    WriteOneFlag(target_, &CSubstitutionCFlags, false, Tool::kToolNone,
-                 &ConfigValues::cflags, opts, path_output_, out_);
+    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_, &CSubstitutionCFlags,
+                 false, Tool::kToolNone, &ConfigValues::cflags, opts,
+                 path_output_, out_);
   }
   if (target_->source_types_used().Get(SourceFile::SOURCE_C)) {
-    WriteOneFlag(target_, &CSubstitutionCFlagsC, has_precompiled_headers,
-                 CTool::kCToolCc, &ConfigValues::cflags_c, opts, path_output_,
-                 out_);
+    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_, &CSubstitutionCFlagsC,
+                 has_precompiled_headers, CTool::kCToolCc,
+                 &ConfigValues::cflags_c, opts, path_output_, out_);
   }
   if (target_->source_types_used().Get(SourceFile::SOURCE_CPP) ||
       target_->source_types_used().Get(SourceFile::SOURCE_MODULEMAP)) {
-    WriteOneFlag(target_, &CSubstitutionCFlagsCc, has_precompiled_headers,
+    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
+                 &CSubstitutionCFlagsCc, has_precompiled_headers,
                  CTool::kCToolCxx, &ConfigValues::cflags_cc, opts, path_output_,
                  out_);
   }
   if (target_->source_types_used().Get(SourceFile::SOURCE_M)) {
-    WriteOneFlag(target_, &CSubstitutionCFlagsObjC, has_precompiled_headers,
+    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
+                 &CSubstitutionCFlagsObjC, has_precompiled_headers,
                  CTool::kCToolObjC, &ConfigValues::cflags_objc, opts,
                  path_output_, out_);
   }
   if (target_->source_types_used().Get(SourceFile::SOURCE_MM)) {
-    WriteOneFlag(target_, &CSubstitutionCFlagsObjCc, has_precompiled_headers,
+    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
+                 &CSubstitutionCFlagsObjCc, has_precompiled_headers,
                  CTool::kCToolObjCxx, &ConfigValues::cflags_objcc, opts,
                  path_output_, out_);
   }
@@ -345,7 +351,8 @@
       out_ << std::endl;
     }
 
-    WriteOneFlag(target_, &CSubstitutionSwiftFlags, false, CTool::kCToolSwift,
+    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
+                 &CSubstitutionSwiftFlags, false, CTool::kCToolSwift,
                  &ConfigValues::swiftflags, opts, path_output_, out_);
   }
 
@@ -471,16 +478,20 @@
   // for .gch targets.
   EscapeOptions opts = GetFlagOptions();
   if (tool_name == CTool::kCToolCc) {
-    RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_c, opts,
+    RecursiveTargetConfigStringsToStream(kRecursiveWriterKeepDuplicates,
+                                         target_, &ConfigValues::cflags_c, opts,
                                          out_);
   } else if (tool_name == CTool::kCToolCxx) {
-    RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_cc,
+    RecursiveTargetConfigStringsToStream(kRecursiveWriterKeepDuplicates,
+                                         target_, &ConfigValues::cflags_cc,
                                          opts, out_);
   } else if (tool_name == CTool::kCToolObjC) {
-    RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_objc,
+    RecursiveTargetConfigStringsToStream(kRecursiveWriterKeepDuplicates,
+                                         target_, &ConfigValues::cflags_objc,
                                          opts, out_);
   } else if (tool_name == CTool::kCToolObjCxx) {
-    RecursiveTargetConfigStringsToStream(target_, &ConfigValues::cflags_objcc,
+    RecursiveTargetConfigStringsToStream(kRecursiveWriterKeepDuplicates,
+                                         target_, &ConfigValues::cflags_objcc,
                                          opts, out_);
   }
 
@@ -834,7 +845,8 @@
     out_ << std::endl;
   } else if (target_->output_type() == Target::STATIC_LIBRARY) {
     out_ << "  arflags =";
-    RecursiveTargetConfigStringsToStream(target_, &ConfigValues::arflags,
+    RecursiveTargetConfigStringsToStream(kRecursiveWriterKeepDuplicates,
+                                         target_, &ConfigValues::arflags,
                                          GetFlagOptions(), out_);
     out_ << std::endl;
   }
diff --git a/src/gn/ninja_rust_binary_target_writer.cc b/src/gn/ninja_rust_binary_target_writer.cc
index 568c2b4..82149bf 100644
--- a/src/gn/ninja_rust_binary_target_writer.cc
+++ b/src/gn/ninja_rust_binary_target_writer.cc
@@ -207,10 +207,12 @@
   EscapeOptions opts = GetFlagOptions();
   WriteCrateVars(target_, tool_, opts, out_);
 
-  WriteOneFlag(target_, &kRustSubstitutionRustFlags, false, Tool::kToolNone,
+  WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
+               &kRustSubstitutionRustFlags, false, Tool::kToolNone,
                &ConfigValues::rustflags, opts, path_output_, out_);
 
-  WriteOneFlag(target_, &kRustSubstitutionRustEnv, false, Tool::kToolNone,
+  WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
+               &kRustSubstitutionRustEnv, false, Tool::kToolNone,
                &ConfigValues::rustenv, opts, path_output_, out_);
 
   WriteSharedVars(subst);
diff --git a/src/gn/ninja_target_command_util.cc b/src/gn/ninja_target_command_util.cc
index 05f4ccd..b542fb2 100644
--- a/src/gn/ninja_target_command_util.cc
+++ b/src/gn/ninja_target_command_util.cc
@@ -41,7 +41,8 @@
   return ret;
 }
 
-void WriteOneFlag(const Target* target,
+void WriteOneFlag(RecursiveWriterConfig config,
+                  const Target* target,
                   const Substitution* subst_enum,
                   bool has_precompiled_headers,
                   const char* tool_name,
@@ -67,16 +68,16 @@
       // Enables precompiled headers and names the .h file. It's a string
       // rather than a file name (so no need to rebase or use path_output).
       out << " /Yu" << target->config_values().precompiled_header();
-      RecursiveTargetConfigStringsToStream(target, getter, flag_escape_options,
-                                           out);
+      RecursiveTargetConfigStringsToStream(config, target, getter,
+                                           flag_escape_options, out);
     } else if (tool && tool->precompiled_header_type() == CTool::PCH_GCC) {
       // The targets to build the .gch files should omit the -include flag
       // below. To accomplish this, each substitution flag is overwritten in
       // the target rule and these values are repeated. The -include flag is
       // omitted in place of the required -x <header lang> flag for .gch
       // targets.
-      RecursiveTargetConfigStringsToStream(target, getter, flag_escape_options,
-                                           out);
+      RecursiveTargetConfigStringsToStream(config, target, getter,
+                                           flag_escape_options, out);
 
       // Compute the gch file (it will be language-specific).
       std::vector<OutputFile> outputs;
@@ -90,12 +91,12 @@
         out << " -include " << pch_file;
       }
     } else {
-      RecursiveTargetConfigStringsToStream(target, getter, flag_escape_options,
-                                           out);
+      RecursiveTargetConfigStringsToStream(config, target, getter,
+                                           flag_escape_options, out);
     }
   } else {
-    RecursiveTargetConfigStringsToStream(target, getter, flag_escape_options,
-                                         out);
+    RecursiveTargetConfigStringsToStream(config, target, getter,
+                                         flag_escape_options, out);
   }
 
   if (write_substitution)
diff --git a/src/gn/ninja_target_command_util.h b/src/gn/ninja_target_command_util.h
index 00f9a77..b0179a1 100644
--- a/src/gn/ninja_target_command_util.h
+++ b/src/gn/ninja_target_command_util.h
@@ -92,7 +92,8 @@
 // The tool_type indicates the corresponding tool for flags that are
 // tool-specific (e.g. "cflags_c"). For non-tool-specific flags (e.g.
 // "defines") tool_type should be TYPE_NONE.
-void WriteOneFlag(const Target* target,
+void WriteOneFlag(RecursiveWriterConfig config,
+                  const Target* target,
                   const Substitution* subst_enum,
                   bool has_precompiled_headers,
                   const char* tool_name,
diff --git a/src/gn/visual_studio_writer.cc b/src/gn/visual_studio_writer.cc
index f6ea9d2..c47c910 100644
--- a/src/gn/visual_studio_writer.cc
+++ b/src/gn/visual_studio_writer.cc
@@ -547,8 +547,8 @@
         std::unique_ptr<XmlElementWriter> include_dirs =
             cl_compile->SubElement("AdditionalIncludeDirectories");
         RecursiveTargetConfigToStream<SourceDir>(
-            target, &ConfigValues::include_dirs, IncludeDirWriter(path_output),
-            include_dirs->StartContent(false));
+            kRecursiveWriterSkipDuplicates, target, &ConfigValues::include_dirs,
+            IncludeDirWriter(path_output), include_dirs->StartContent(false));
         include_dirs->Text(windows_kits_include_dirs_ +
                            "$(VSInstallDir)\\VC\\atlmfc\\include;" +
                            "%(AdditionalIncludeDirectories)");
@@ -583,7 +583,8 @@
         std::unique_ptr<XmlElementWriter> preprocessor_definitions =
             cl_compile->SubElement("PreprocessorDefinitions");
         RecursiveTargetConfigToStream<std::string>(
-            target, &ConfigValues::defines, SemicolonSeparatedWriter(),
+            kRecursiveWriterSkipDuplicates, target, &ConfigValues::defines,
+            SemicolonSeparatedWriter(),
             preprocessor_definitions->StartContent(false));
         preprocessor_definitions->Text("%(PreprocessorDefinitions)");
       }