Make more variables available to scripts.

This change makes certain extra variables available to 'action' target types,
i.e. scripts. Those variables are:
* defines
* include_dirs
* rustenv
They are deduced from any configs applied to the target, so
configs also now applies to 'action' targets.

These variables are normally exclusively used by compilers, and no compiler-
specific variables have been made available to scripts previously. Here's
why they're needed.

rustenv:

This variable represents environment variables passed to the Rust
build, and in the Chromium tree this is assembled using various transitive
'config's. These environment variables are typically used to allow `env!`
directives within the Rust code to work properly; often (but not always)
this is to support pre-existing third party Rust code ("crates").

Such pre-existing third party Rust code also expects the same environment
variables to be present when the crates' build scripts are run. In a gn
environment, these build scripts are run using an 'action', and therefore
we need to make this rustenv value available to 'action' targets.

This is described in https://chromium/1304254.

defines/include_dirs:

In Rust development it is common to produce Rust bindings to pre-existing
C/C++ headers. This is not built into rustc, but is instead a facility
provided by separate codegen tools - the most commonly known example being
bindgen (https://rust-lang.github.io/rust-bindgen/). In a gn environment,
these tools are run as a gn 'action' target.

Such tools involve parsing C/C++ headers (using something like libclang).
In order to parse headers properly, such tools obviously need to be aware
of the full list of -I and -D flags active in the current environment.
In Chromium, such -I and -D flags are assembled using a graph of complex
transitive gn 'config's, and there's no realistic prospect of hard-coding
equivalent command lines.

'bindgen' therefore absolutely has to have access to the 'include_dirs'
and 'defines' set using gn configs. One solution here is to define a
whole new toolchain where we inform GN that bindgen is a compiler - this
solution is outlined in https://chromium/1296241 - however this is not
ideal. To parse (say) just base/cpu_info.h, we would need to depend upon
  //base(//toolchains/fake_bindgen_toolchain)
which will run a command to 'compile' every .cc file within //base.
This can be 10,000s of extra ninja rules, and even if most of them
are just a 'touch' command, this is still a great deal of extra complexity.

Bug: chromium/1272780, chromium/1296155, chromium/1296241, chromium/1304254
Change-Id: Ib60067b310d8508f61d04fe7ddc537a5d2c2917f
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/13220
Reviewed-by: Brett Wilson <brettw@chromium.org>
Commit-Queue: Brett Wilson <brettw@chromium.org>
diff --git a/docs/reference.md b/docs/reference.md
index 566b3ed..51dcecc 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -1312,6 +1312,12 @@
   and stuff like other Python files required to run your script in the "inputs"
   variable.
 
+  Actions can take the configs and public_configs lists, as well as any of the
+  configs variables (defines, include_dirs, etc.) set directly on the target.
+  These behave exactly as they would on a binary target and can be accessed
+  using substitution patterns in the script args (see "gn help args") to
+  implement custom compiler-like tools.
+
   The "deps" and "public_deps" for an action will always be
   completed before any part of the action is run so it can depend on
   the output of previous steps. The "data_deps" will be built if the
@@ -1354,8 +1360,11 @@
 #### **Variables**
 
 ```
-  args, data, data_deps, depfile, deps, inputs, metadata, outputs*, pool,
-  response_file_contents, script*, sources
+  args, asmflags, bridge_header, cflags, cflags_c, cflags_cc, cflags_objc,
+  cflags_objcc, configs, data, data_deps, defines, depfile, deps,
+  framework_dirs, include_dirs, inputs, metadata, module_deps, module_name,
+  outputs*, pool, response_file_contents, rustenv, rustflags, script*, sources,
+  swiftflags
   * = required
 ```
 
@@ -1443,9 +1452,11 @@
 #### **Variables**
 
 ```
-  args, data, data_deps, depfile, deps, inputs, metadata, outputs*, pool,
-  response_file_contents, script*, sources*
-  * = required
+  args, asmflags, bridge_header, cflags, cflags_c, cflags_cc, cflags_objc,
+  cflags_objcc, configs, data, data_deps, defines, depfile, deps,
+  framework_dirs, include_dirs, inputs, metadata, module_deps, module_name,
+  outputs*, pool, response_file_contents, rustenv, rustflags, script*, sources,
+  swiftflags
 ```
 
 #### **Example**
@@ -4696,6 +4707,14 @@
   to the script. Typically you would use source expansion (see "gn help
   source_expansion") to insert the source file names.
 
+  Args can also expand the substitution patterns corresponding to config
+  variables in the same way that compiler tools (see "gn help tool") do. These
+  allow actions that run compiler or compiler-like tools to access the results
+  of propagating configs through the build graph. For example:
+
+  args = [ "{{defines}}", "{{include_dirs}}", "{{rustenv}}", "--input-file",
+           "{{source}}" ]
+
   See also "gn help action" and "gn help action_foreach".
 ```
 ### <a name="var_asmflags"></a>**asmflags**: Flags passed to the assembler.
diff --git a/src/gn/action_target_generator.cc b/src/gn/action_target_generator.cc
index 73fa288..ff66765 100644
--- a/src/gn/action_target_generator.cc
+++ b/src/gn/action_target_generator.cc
@@ -6,6 +6,7 @@
 
 #include "base/stl_util.h"
 #include "gn/build_settings.h"
+#include "gn/config_values_generator.h"
 #include "gn/err.h"
 #include "gn/filesystem_utils.h"
 #include "gn/functions.h"
@@ -63,9 +64,19 @@
   if (!FillCheckIncludes())
     return;
 
+  if (!FillConfigs())
+    return;
+
   if (!CheckOutputs())
     return;
 
+  // Config values (compiler flags, etc.) set directly on this target.
+  ConfigValuesGenerator gen(&target_->config_values(), scope_,
+                            scope_->GetSourceDir(), err_);
+  gen.Run();
+  if (err_->has_error())
+    return;
+
   // Action outputs don't depend on the current toolchain so we can skip adding
   // that dependency.
 
diff --git a/src/gn/c_substitution_type.cc b/src/gn/c_substitution_type.cc
index ea47ce9..971d87a 100644
--- a/src/gn/c_substitution_type.cc
+++ b/src/gn/c_substitution_type.cc
@@ -92,6 +92,19 @@
          type == &CSubstitutionModuleDepsNoSelf;
 }
 
+bool IsValidCompilerScriptArgsSubstitution(const Substitution* type) {
+  return type == &CSubstitutionAsmFlags || type == &CSubstitutionCFlags ||
+         type == &CSubstitutionCFlagsC || type == &CSubstitutionCFlagsCc ||
+         type == &CSubstitutionCFlagsObjC ||
+         type == &CSubstitutionCFlagsObjCc || type == &CSubstitutionDefines ||
+         type == &CSubstitutionFrameworkDirs ||
+         type == &CSubstitutionIncludeDirs ||
+         type == &CSubstitutionSwiftModuleName ||
+         type == &CSubstitutionSwiftBridgeHeader ||
+         type == &CSubstitutionSwiftModuleDirs ||
+         type == &CSubstitutionSwiftFlags;
+}
+
 bool IsValidCompilerOutputsSubstitution(const Substitution* type) {
   // All tool types except "output" (which would be infinitely recursive).
   return (IsValidToolSubstitution(type) && type != &SubstitutionOutput) ||
diff --git a/src/gn/c_substitution_type.h b/src/gn/c_substitution_type.h
index 1f4e90e..d0d1f8c 100644
--- a/src/gn/c_substitution_type.h
+++ b/src/gn/c_substitution_type.h
@@ -54,4 +54,7 @@
 bool IsValidLinkerOutputsSubstitution(const Substitution* type);
 bool IsValidALinkSubstitution(const Substitution* type);
 
+// action targets may rely on some compiler substitutions.
+bool IsValidCompilerScriptArgsSubstitution(const Substitution* type);
+
 #endif  // TOOLS_GN_C_SUBSTITUTION_TYPE_H_
diff --git a/src/gn/compile_commands_writer.cc b/src/gn/compile_commands_writer.cc
index 16f2a1a..c65d378 100644
--- a/src/gn/compile_commands_writer.cc
+++ b/src/gn/compile_commands_writer.cc
@@ -105,7 +105,7 @@
     std::ostringstream out;
     WriteOneFlag(config, target, substitution, has_precompiled_headers,
                  tool_name, getter, opts, path_output, out,
-                 /*write_substitution=*/false);
+                 /*write_substitution=*/false, /*indent=*/false);
     base::EscapeJSONString(out.str(), false, &result);
     return result;
   };
diff --git a/src/gn/functions_target.cc b/src/gn/functions_target.cc
index 7120b3f..74f53e9 100644
--- a/src/gn/functions_target.cc
+++ b/src/gn/functions_target.cc
@@ -138,6 +138,12 @@
   It is recommended you put inputs to your script in the "sources" variable,
   and stuff like other Python files required to run your script in the "inputs"
   variable.
+
+  Actions can take the configs and public_configs lists, as well as any of the
+  configs variables (defines, include_dirs, etc.) set directly on the target.
+  These behave exactly as they would on a binary target and can be accessed
+  using substitution patterns in the script args (see "gn help args") to
+  implement custom compiler-like tools.
 )"
 
     ACTION_DEPS
@@ -160,8 +166,11 @@
     R"(
 Variables
 
-  args, data, data_deps, depfile, deps, inputs, metadata, outputs*, pool,
-  response_file_contents, script*, sources
+  args, asmflags, bridge_header, cflags, cflags_c, cflags_cc, cflags_objc,
+  cflags_objcc, configs, data, data_deps, defines, depfile, deps,
+  framework_dirs, include_dirs, inputs, metadata, module_deps, module_name,
+  outputs*, pool, response_file_contents, rustenv, rustflags, script*, sources,
+  swiftflags
   * = required
 
 Example
@@ -230,9 +239,11 @@
     R"(
 Variables
 
-  args, data, data_deps, depfile, deps, inputs, metadata, outputs*, pool,
-  response_file_contents, script*, sources*
-  * = required
+  args, asmflags, bridge_header, cflags, cflags_c, cflags_cc, cflags_objc,
+  cflags_objcc, configs, data, data_deps, defines, depfile, deps,
+  framework_dirs, include_dirs, inputs, metadata, module_deps, module_name,
+  outputs*, pool, response_file_contents, rustenv, rustflags, script*, sources,
+  swiftflags
 
 Example
 
diff --git a/src/gn/ninja_action_target_writer.cc b/src/gn/ninja_action_target_writer.cc
index bfceef9..e717d33 100644
--- a/src/gn/ninja_action_target_writer.cc
+++ b/src/gn/ninja_action_target_writer.cc
@@ -80,6 +80,9 @@
     if (target_->action_values().has_depfile()) {
       WriteDepfile(SourceFile());
     }
+
+    WriteNinjaVariablesForAction();
+
     if (target_->action_values().pool().ptr) {
       out_ << "  pool = ";
       out_ << target_->action_values().pool().ptr->GetNinjaName(
@@ -149,7 +152,8 @@
   out_ << std::endl;
   out_ << "  description = ACTION " << target_label << std::endl;
   out_ << "  restat = 1" << std::endl;
-  const Tool* tool = target_->toolchain()->GetTool(GeneralTool::kGeneralToolAction);
+  const Tool* tool =
+      target_->toolchain()->GetTool(GeneralTool::kGeneralToolAction);
   if (tool && tool->pool().ptr) {
     out_ << "  pool = ";
     out_ << tool->pool().ptr->GetNinjaName(
@@ -204,6 +208,7 @@
         target_, settings_, sources[i],
         target_->action_values().rsp_file_contents().required_types(),
         args_escape_options, out_);
+    WriteNinjaVariablesForAction();
 
     if (target_->action_values().has_depfile()) {
       WriteDepfile(sources[i]);
@@ -248,3 +253,10 @@
     out_ << "  deps = gcc" << std::endl;
   }
 }
+
+void NinjaActionTargetWriter::WriteNinjaVariablesForAction() {
+  SubstitutionBits subst;
+  target_->action_values().args().FillRequiredTypes(&subst);
+  WriteRustCompilerVars(subst, /*indent=*/true, /*always_write=*/false);
+  WriteCCompilerVars(subst, /*indent=*/true, /*respect_source_types=*/false);
+}
diff --git a/src/gn/ninja_action_target_writer.h b/src/gn/ninja_action_target_writer.h
index ef91f4e..58b9489 100644
--- a/src/gn/ninja_action_target_writer.h
+++ b/src/gn/ninja_action_target_writer.h
@@ -50,6 +50,10 @@
 
   void WriteDepfile(const SourceFile& source);
 
+  // Writes variables that we make available to all actions, irrespective
+  // of whether they're associated with a specific source file.
+  void WriteNinjaVariablesForAction();
+
   // Path output writer that doesn't do any escaping or quoting. It does,
   // however, convert slashes.  Used for
   // computing intermediate strings.
diff --git a/src/gn/ninja_action_target_writer_unittest.cc b/src/gn/ninja_action_target_writer_unittest.cc
index 80b125b..d363b5e 100644
--- a/src/gn/ninja_action_target_writer_unittest.cc
+++ b/src/gn/ninja_action_target_writer_unittest.cc
@@ -5,6 +5,7 @@
 #include <algorithm>
 #include <sstream>
 
+#include "gn/config.h"
 #include "gn/ninja_action_target_writer.h"
 #include "gn/pool.h"
 #include "gn/substitution_list.h"
@@ -486,3 +487,63 @@
     EXPECT_EQ(expected_linux, out.str());
   }
 }
+
+TEST(NinjaActionTargetWriter, SeesConfig) {
+  Err err;
+  TestWithScope setup;
+
+  setup.build_settings()->set_python_path(
+      base::FilePath(FILE_PATH_LITERAL("/usr/bin/python")));
+
+  Config farcfg(setup.settings(), Label(SourceDir("//foo/"), "farcfg"));
+  farcfg.own_values().defines().push_back("MY_DEFINE2");
+  farcfg.own_values().cflags().push_back("-isysroot=baz");
+  farcfg.visibility().SetPublic();
+  ASSERT_TRUE(farcfg.OnResolved(&err));
+
+  Config cfgdep(setup.settings(), Label(SourceDir("//foo/"), "cfgdep"));
+  cfgdep.own_values().rustenv().push_back("my_rustenv");
+  cfgdep.own_values().include_dirs().push_back(SourceDir("//my_inc_dir/"));
+  cfgdep.own_values().defines().push_back("MY_DEFINE");
+  cfgdep.visibility().SetPublic();
+  cfgdep.configs().push_back(LabelConfigPair(&farcfg));
+  ASSERT_TRUE(cfgdep.OnResolved(&err));
+
+  Target foo(setup.settings(), Label(SourceDir("//foo/"), "foo"));
+  foo.set_output_type(Target::ACTION);
+  foo.visibility().SetPublic();
+  foo.sources().push_back(SourceFile("//foo/input1.txt"));
+  foo.action_values().set_script(SourceFile("//foo/script.py"));
+  foo.action_values().args() = SubstitutionList::MakeForTest(
+      "{{rustenv}}", "{{include_dirs}}", "{{defines}}", "{{cflags}}");
+  foo.configs().push_back(LabelConfigPair(&cfgdep));
+  foo.SetToolchain(setup.toolchain());
+  foo.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/foo.out");
+  ASSERT_TRUE(foo.OnResolved(&err));
+
+  {
+    std::ostringstream out;
+    NinjaActionTargetWriter writer(&foo, out);
+    writer.Run();
+
+    const char expected[] =
+        "rule __foo_foo___rule\n"
+        // These come from the args.
+        "  command = /usr/bin/python ../../foo/script.py ${rustenv} "
+        "${include_dirs} ${defines} ${cflags}\n"
+        "  description = ACTION //foo:foo()\n"
+        "  restat = 1\n"
+        "\n"
+        "build foo.out: __foo_foo___rule | ../../foo/script.py"
+        " ../../foo/input1.txt\n"
+        "  rustenv = my_rustenv\n"
+        "  defines = -DMY_DEFINE -DMY_DEFINE2\n"
+        "  include_dirs = -I../../my_inc_dir\n"
+        "  cflags = -isysroot=baz\n"
+        "\n"
+        "build obj/foo/foo.stamp: stamp foo.out\n";
+    std::string out_str = out.str();
+    EXPECT_EQ(expected, out_str) << expected << "\n" << out_str;
+  }
+}
diff --git a/src/gn/ninja_c_binary_target_writer.cc b/src/gn/ninja_c_binary_target_writer.cc
index 5f3afcb..052f03a 100644
--- a/src/gn/ninja_c_binary_target_writer.cc
+++ b/src/gn/ninja_c_binary_target_writer.cc
@@ -224,42 +224,8 @@
     const std::vector<ModuleDep>& module_dep_info) {
   const SubstitutionBits& subst = target_->toolchain()->substitution_bits();
 
-  // Defines.
-  if (subst.used.count(&CSubstitutionDefines)) {
-    out_ << CSubstitutionDefines.ninja_name << " =";
-    RecursiveTargetConfigToStream<std::string>(kRecursiveWriterSkipDuplicates,
-                                               target_, &ConfigValues::defines,
-                                               DefineWriter(), out_);
-    out_ << std::endl;
-  }
-
-  // Framework search path.
-  if (subst.used.count(&CSubstitutionFrameworkDirs)) {
-    const Tool* tool = target_->toolchain()->GetTool(CTool::kCToolLink);
-
-    out_ << CSubstitutionFrameworkDirs.ninja_name << " =";
-    PathOutput framework_dirs_output(
-        path_output_.current_dir(),
-        settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND);
-    RecursiveTargetConfigToStream<SourceDir>(
-        kRecursiveWriterSkipDuplicates, target_, &ConfigValues::framework_dirs,
-        FrameworkDirsWriter(framework_dirs_output,
-                            tool->framework_dir_switch()),
-        out_);
-    out_ << std::endl;
-  }
-
-  // Include directories.
-  if (subst.used.count(&CSubstitutionIncludeDirs)) {
-    out_ << CSubstitutionIncludeDirs.ninja_name << " =";
-    PathOutput include_path_output(
-        path_output_.current_dir(),
-        settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND);
-    RecursiveTargetConfigToStream<SourceDir>(
-        kRecursiveWriterSkipDuplicates, target_, &ConfigValues::include_dirs,
-        IncludeWriter(include_path_output), out_);
-    out_ << std::endl;
-  }
+  WriteCCompilerVars(subst, /*indent=*/false,
+                     /*respect_source_types_used=*/true);
 
   if (!module_dep_info.empty()) {
     // TODO(scottmg): Currently clang modules only working for C++.
@@ -272,89 +238,6 @@
     }
   }
 
-  bool has_precompiled_headers =
-      target_->config_values().has_precompiled_headers();
-
-  EscapeOptions opts = GetFlagOptions();
-  if (target_->source_types_used().Get(SourceFile::SOURCE_S) ||
-      target_->source_types_used().Get(SourceFile::SOURCE_ASM)) {
-    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
-                 &CSubstitutionAsmFlags, false, Tool::kToolNone,
-                 &ConfigValues::asmflags, opts, path_output_, out_);
-  }
-  if (target_->source_types_used().Get(SourceFile::SOURCE_C) ||
-      target_->source_types_used().Get(SourceFile::SOURCE_CPP) ||
-      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(kRecursiveWriterKeepDuplicates, target_, &CSubstitutionCFlags,
-                 false, Tool::kToolNone, &ConfigValues::cflags, opts,
-                 path_output_, out_);
-  }
-  if (target_->source_types_used().Get(SourceFile::SOURCE_C)) {
-    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(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(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(kRecursiveWriterKeepDuplicates, target_,
-                 &CSubstitutionCFlagsObjCc, has_precompiled_headers,
-                 CTool::kCToolObjCxx, &ConfigValues::cflags_objcc, opts,
-                 path_output_, out_);
-  }
-  if (target_->source_types_used().SwiftSourceUsed()) {
-    if (subst.used.count(&CSubstitutionSwiftModuleName)) {
-      out_ << CSubstitutionSwiftModuleName.ninja_name << " = ";
-      EscapeStringToStream(out_, target_->swift_values().module_name(), opts);
-      out_ << std::endl;
-    }
-
-    if (subst.used.count(&CSubstitutionSwiftBridgeHeader)) {
-      out_ << CSubstitutionSwiftBridgeHeader.ninja_name << " = ";
-      if (!target_->swift_values().bridge_header().is_null()) {
-        path_output_.WriteFile(out_, target_->swift_values().bridge_header());
-      } else {
-        out_ << R"("")";
-      }
-      out_ << std::endl;
-    }
-
-    if (subst.used.count(&CSubstitutionSwiftModuleDirs)) {
-      // Uniquify the list of swiftmodule dirs (in case multiple swiftmodules
-      // are generated in the same directory).
-      UniqueVector<SourceDir> swiftmodule_dirs;
-      for (const Target* dep : target_->swift_values().modules())
-        swiftmodule_dirs.push_back(dep->swift_values().module_output_dir());
-
-      out_ << CSubstitutionSwiftModuleDirs.ninja_name << " =";
-      PathOutput swiftmodule_path_output(
-          path_output_.current_dir(),
-          settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND);
-      IncludeWriter swiftmodule_path_writer(swiftmodule_path_output);
-      for (const SourceDir& swiftmodule_dir : swiftmodule_dirs) {
-        swiftmodule_path_writer(swiftmodule_dir, out_);
-      }
-      out_ << std::endl;
-    }
-
-    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
-                 &CSubstitutionSwiftFlags, false, CTool::kCToolSwift,
-                 &ConfigValues::swiftflags, opts, path_output_, out_);
-  }
-
   WriteSharedVars(subst);
 }
 
diff --git a/src/gn/ninja_c_binary_target_writer_unittest.cc b/src/gn/ninja_c_binary_target_writer_unittest.cc
index 2aafe92..e4ec800 100644
--- a/src/gn/ninja_c_binary_target_writer_unittest.cc
+++ b/src/gn/ninja_c_binary_target_writer_unittest.cc
@@ -2505,10 +2505,10 @@
 
     const char expected[] = R"(defines =
 include_dirs =
-module_deps = -Xclang -fmodules-embed-all-files -fmodule-file=obj/blah/liba.a.pcm
-module_deps_no_self = -Xclang -fmodules-embed-all-files
 cflags =
 cflags_cc =
+module_deps = -Xclang -fmodules-embed-all-files -fmodule-file=obj/blah/liba.a.pcm
+module_deps_no_self = -Xclang -fmodules-embed-all-files
 label = //blah$:a
 root_out_dir = withmodules
 target_out_dir = obj/blah
@@ -2550,10 +2550,10 @@
 
     const char expected[] = R"(defines =
 include_dirs =
-module_deps = -Xclang -fmodules-embed-all-files -fmodule-file=obj/stuff/libb.b.pcm
-module_deps_no_self = -Xclang -fmodules-embed-all-files
 cflags =
 cflags_cc =
+module_deps = -Xclang -fmodules-embed-all-files -fmodule-file=obj/stuff/libb.b.pcm
+module_deps_no_self = -Xclang -fmodules-embed-all-files
 label = //stuff$:b
 root_out_dir = withmodules
 target_out_dir = obj/stuff
@@ -2594,10 +2594,10 @@
 
     const char expected[] = R"(defines =
 include_dirs =
-module_deps = -Xclang -fmodules-embed-all-files -fmodule-file=obj/stuff/libc.c.pcm -fmodule-file=obj/blah/liba.a.pcm
-module_deps_no_self = -Xclang -fmodules-embed-all-files -fmodule-file=obj/blah/liba.a.pcm
 cflags =
 cflags_cc =
+module_deps = -Xclang -fmodules-embed-all-files -fmodule-file=obj/stuff/libc.c.pcm -fmodule-file=obj/blah/liba.a.pcm
+module_deps_no_self = -Xclang -fmodules-embed-all-files -fmodule-file=obj/blah/liba.a.pcm
 label = //things$:c
 root_out_dir = withmodules
 target_out_dir = obj/things
@@ -2635,10 +2635,10 @@
 
     const char expected[] = R"(defines =
 include_dirs =
-module_deps = -Xclang -fmodules-embed-all-files -fmodule-file=obj/blah/liba.a.pcm -fmodule-file=obj/stuff/libb.b.pcm
-module_deps_no_self = -Xclang -fmodules-embed-all-files -fmodule-file=obj/blah/liba.a.pcm -fmodule-file=obj/stuff/libb.b.pcm
 cflags =
 cflags_cc =
+module_deps = -Xclang -fmodules-embed-all-files -fmodule-file=obj/blah/liba.a.pcm -fmodule-file=obj/stuff/libb.b.pcm
+module_deps_no_self = -Xclang -fmodules-embed-all-files -fmodule-file=obj/blah/liba.a.pcm -fmodule-file=obj/stuff/libb.b.pcm
 label = //zap$:c
 root_out_dir = withmodules
 target_out_dir = obj/zap
diff --git a/src/gn/ninja_rust_binary_target_writer.cc b/src/gn/ninja_rust_binary_target_writer.cc
index 0fd26c0..f3ff21f 100644
--- a/src/gn/ninja_rust_binary_target_writer.cc
+++ b/src/gn/ninja_rust_binary_target_writer.cc
@@ -212,13 +212,7 @@
   EscapeOptions opts = GetFlagOptions();
   WriteCrateVars(target_, tool_, opts, out_);
 
-  WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
-               &kRustSubstitutionRustFlags, false, Tool::kToolNone,
-               &ConfigValues::rustflags, opts, path_output_, out_);
-
-  WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
-               &kRustSubstitutionRustEnv, false, Tool::kToolNone,
-               &ConfigValues::rustenv, opts, path_output_, out_);
+  WriteRustCompilerVars(subst, /*indent=*/false, /*always_write=*/true);
 
   WriteSharedVars(subst);
 }
diff --git a/src/gn/ninja_target_command_util.cc b/src/gn/ninja_target_command_util.cc
index b542fb2..a691ae3 100644
--- a/src/gn/ninja_target_command_util.cc
+++ b/src/gn/ninja_target_command_util.cc
@@ -51,10 +51,13 @@
                   EscapeOptions flag_escape_options,
                   PathOutput& path_output,
                   std::ostream& out,
-                  bool write_substitution) {
+                  bool write_substitution,
+                  bool indent) {
   if (!target->toolchain()->substitution_bits().used.count(subst_enum))
     return;
 
+  if (indent)
+    out << "  ";
   if (write_substitution)
     out << subst_enum->ninja_name << " =";
 
diff --git a/src/gn/ninja_target_command_util.h b/src/gn/ninja_target_command_util.h
index b0179a1..93666fb 100644
--- a/src/gn/ninja_target_command_util.h
+++ b/src/gn/ninja_target_command_util.h
@@ -102,7 +102,8 @@
                   EscapeOptions flag_escape_options,
                   PathOutput& path_output,
                   std::ostream& out,
-                  bool write_substitution = true);
+                  bool write_substitution = true,
+                  bool indent = false);
 
 // Fills |outputs| with the object or gch file for the precompiled header of the
 // given type (flag type and tool type must match).
diff --git a/src/gn/ninja_target_writer.cc b/src/gn/ninja_target_writer.cc
index efcf991..0f2514b 100644
--- a/src/gn/ninja_target_writer.cc
+++ b/src/gn/ninja_target_writer.cc
@@ -8,6 +8,7 @@
 
 #include "base/files/file_util.h"
 #include "base/strings/string_util.h"
+#include "gn/c_substitution_type.h"
 #include "gn/config_values_extractors.h"
 #include "gn/err.h"
 #include "gn/escape.h"
@@ -20,8 +21,10 @@
 #include "gn/ninja_create_bundle_target_writer.h"
 #include "gn/ninja_generated_file_target_writer.h"
 #include "gn/ninja_group_target_writer.h"
+#include "gn/ninja_target_command_util.h"
 #include "gn/ninja_utils.h"
 #include "gn/output_file.h"
+#include "gn/rust_substitution_type.h"
 #include "gn/scheduler.h"
 #include "gn/string_output_buffer.h"
 #include "gn/string_utils.h"
@@ -188,6 +191,178 @@
     out_ << std::endl;
 }
 
+void NinjaTargetWriter::WriteCCompilerVars(const SubstitutionBits& bits,
+                                           bool indent,
+                                           bool respect_source_used) {
+  // Defines.
+  if (bits.used.count(&CSubstitutionDefines)) {
+    if (indent)
+      out_ << "  ";
+    out_ << CSubstitutionDefines.ninja_name << " =";
+    RecursiveTargetConfigToStream<std::string>(kRecursiveWriterSkipDuplicates,
+                                               target_, &ConfigValues::defines,
+                                               DefineWriter(), out_);
+    out_ << std::endl;
+  }
+
+  // Framework search path.
+  if (bits.used.count(&CSubstitutionFrameworkDirs)) {
+    const Tool* tool = target_->toolchain()->GetTool(CTool::kCToolLink);
+
+    if (indent)
+      out_ << "  ";
+    out_ << CSubstitutionFrameworkDirs.ninja_name << " =";
+    PathOutput framework_dirs_output(
+        path_output_.current_dir(),
+        settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND);
+    RecursiveTargetConfigToStream<SourceDir>(
+        kRecursiveWriterSkipDuplicates, target_, &ConfigValues::framework_dirs,
+        FrameworkDirsWriter(framework_dirs_output,
+                            tool->framework_dir_switch()),
+        out_);
+    out_ << std::endl;
+  }
+
+  // Include directories.
+  if (bits.used.count(&CSubstitutionIncludeDirs)) {
+    if (indent)
+      out_ << "  ";
+    out_ << CSubstitutionIncludeDirs.ninja_name << " =";
+    PathOutput include_path_output(
+        path_output_.current_dir(),
+        settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND);
+    RecursiveTargetConfigToStream<SourceDir>(
+        kRecursiveWriterSkipDuplicates, target_, &ConfigValues::include_dirs,
+        IncludeWriter(include_path_output), out_);
+    out_ << std::endl;
+  }
+
+  bool has_precompiled_headers =
+      target_->config_values().has_precompiled_headers();
+
+  EscapeOptions opts;
+  opts.mode = ESCAPE_NINJA_COMMAND;
+  if (respect_source_used
+          ? target_->source_types_used().Get(SourceFile::SOURCE_S)
+          : bits.used.count(&CSubstitutionAsmFlags)) {
+    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
+                 &CSubstitutionAsmFlags, false, Tool::kToolNone,
+                 &ConfigValues::asmflags, opts, path_output_, out_, true,
+                 indent);
+  }
+  if (respect_source_used
+          ? (target_->source_types_used().Get(SourceFile::SOURCE_C) ||
+             target_->source_types_used().Get(SourceFile::SOURCE_CPP) ||
+             target_->source_types_used().Get(SourceFile::SOURCE_M) ||
+             target_->source_types_used().Get(SourceFile::SOURCE_MM) ||
+             target_->source_types_used().Get(SourceFile::SOURCE_MODULEMAP))
+          : bits.used.count(&CSubstitutionCFlags)) {
+    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_, &CSubstitutionCFlags,
+                 false, Tool::kToolNone, &ConfigValues::cflags, opts,
+                 path_output_, out_, true, indent);
+  }
+  if (respect_source_used
+          ? target_->source_types_used().Get(SourceFile::SOURCE_C)
+          : bits.used.count(&CSubstitutionCFlagsC)) {
+    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_, &CSubstitutionCFlagsC,
+                 has_precompiled_headers, CTool::kCToolCc,
+                 &ConfigValues::cflags_c, opts, path_output_, out_, true,
+                 indent);
+  }
+  if (respect_source_used
+          ? (target_->source_types_used().Get(SourceFile::SOURCE_CPP) ||
+             target_->source_types_used().Get(SourceFile::SOURCE_MODULEMAP))
+          : bits.used.count(&CSubstitutionCFlagsCc)) {
+    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
+                 &CSubstitutionCFlagsCc, has_precompiled_headers,
+                 CTool::kCToolCxx, &ConfigValues::cflags_cc, opts, path_output_,
+                 out_, true, indent);
+  }
+  if (respect_source_used
+          ? target_->source_types_used().Get(SourceFile::SOURCE_M)
+          : bits.used.count(&CSubstitutionCFlagsObjC)) {
+    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
+                 &CSubstitutionCFlagsObjC, has_precompiled_headers,
+                 CTool::kCToolObjC, &ConfigValues::cflags_objc, opts,
+                 path_output_, out_, true, indent);
+  }
+  if (respect_source_used
+          ? target_->source_types_used().Get(SourceFile::SOURCE_MM)
+          : bits.used.count(&CSubstitutionCFlagsObjCc)) {
+    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
+                 &CSubstitutionCFlagsObjCc, has_precompiled_headers,
+                 CTool::kCToolObjCxx, &ConfigValues::cflags_objcc, opts,
+                 path_output_, out_, true, indent);
+  }
+  if (target_->source_types_used().SwiftSourceUsed() || !respect_source_used) {
+    if (bits.used.count(&CSubstitutionSwiftModuleName)) {
+      if (indent)
+        out_ << "  ";
+      out_ << CSubstitutionSwiftModuleName.ninja_name << " = ";
+      EscapeStringToStream(out_, target_->swift_values().module_name(), opts);
+      out_ << std::endl;
+    }
+
+    if (bits.used.count(&CSubstitutionSwiftBridgeHeader)) {
+      if (indent)
+        out_ << "  ";
+      out_ << CSubstitutionSwiftBridgeHeader.ninja_name << " = ";
+      if (!target_->swift_values().bridge_header().is_null()) {
+        path_output_.WriteFile(out_, target_->swift_values().bridge_header());
+      } else {
+        out_ << R"("")";
+      }
+      out_ << std::endl;
+    }
+
+    if (bits.used.count(&CSubstitutionSwiftModuleDirs)) {
+      // Uniquify the list of swiftmodule dirs (in case multiple swiftmodules
+      // are generated in the same directory).
+      UniqueVector<SourceDir> swiftmodule_dirs;
+      for (const Target* dep : target_->swift_values().modules())
+        swiftmodule_dirs.push_back(dep->swift_values().module_output_dir());
+
+      if (indent)
+        out_ << "  ";
+      out_ << CSubstitutionSwiftModuleDirs.ninja_name << " =";
+      PathOutput swiftmodule_path_output(
+          path_output_.current_dir(),
+          settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND);
+      IncludeWriter swiftmodule_path_writer(swiftmodule_path_output);
+      for (const SourceDir& swiftmodule_dir : swiftmodule_dirs) {
+        swiftmodule_path_writer(swiftmodule_dir, out_);
+      }
+      out_ << std::endl;
+    }
+
+    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
+                 &CSubstitutionSwiftFlags, false, CTool::kCToolSwift,
+                 &ConfigValues::swiftflags, opts, path_output_, out_, true,
+                 indent);
+  }
+}
+
+void NinjaTargetWriter::WriteRustCompilerVars(const SubstitutionBits& bits,
+                                              bool indent,
+                                              bool always_write) {
+  EscapeOptions opts;
+  opts.mode = ESCAPE_NINJA_COMMAND;
+
+  if (bits.used.count(&kRustSubstitutionRustFlags) || always_write) {
+    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
+                 &kRustSubstitutionRustFlags, false, Tool::kToolNone,
+                 &ConfigValues::rustflags, opts, path_output_, out_, true,
+                 indent);
+  }
+
+  if (bits.used.count(&kRustSubstitutionRustEnv) || always_write) {
+    WriteOneFlag(kRecursiveWriterKeepDuplicates, target_,
+                 &kRustSubstitutionRustEnv, false, Tool::kToolNone,
+                 &ConfigValues::rustenv, opts, path_output_, out_, true,
+                 indent);
+  }
+}
+
 std::vector<OutputFile> NinjaTargetWriter::WriteInputDepsStampAndGetDep(
     const std::vector<const Target*>& additional_hard_deps,
     size_t num_stamp_uses) const {
diff --git a/src/gn/ninja_target_writer.h b/src/gn/ninja_target_writer.h
index 7a339ab..3d38a14 100644
--- a/src/gn/ninja_target_writer.h
+++ b/src/gn/ninja_target_writer.h
@@ -39,6 +39,23 @@
   // identified by the given bits will be written.
   void WriteSharedVars(const SubstitutionBits& bits);
 
+  // Writes out the substitution values that are shared between C compiler tools
+  // and action tools. Only the substitutions identified by the given bits will
+  // be written.
+  // If respect_source_used is set, the generated substitution values will
+  // respect the types of source code used; otherwise they will respect the bits
+  // passed in.
+  void WriteCCompilerVars(const SubstitutionBits& bits,
+                          bool indent,
+                          bool respect_source_used);
+
+  // Writes out the substitution values that are shared between Rust tools
+  // and action tools. Only the substitutions identified by the given bits will
+  // be written, unless 'always_write' is specified.
+  void WriteRustCompilerVars(const SubstitutionBits& bits,
+                             bool indent,
+                             bool always_write);
+
   // Writes to the output stream a stamp rule for input dependencies, and
   // returns the file to be appended to source rules that encodes the
   // order-only dependencies for the current target.
diff --git a/src/gn/rust_substitution_type.cc b/src/gn/rust_substitution_type.cc
index 69e6cf6..5423d0d 100644
--- a/src/gn/rust_substitution_type.cc
+++ b/src/gn/rust_substitution_type.cc
@@ -42,6 +42,12 @@
          type == &kRustSubstitutionSources;
 }
 
+// Rust substitution types which we also make available to action targets.
+bool IsValidRustScriptArgsSubstitution(const Substitution* type) {
+  return type == &kRustSubstitutionRustEnv ||
+         type == &kRustSubstitutionRustFlags;
+}
+
 bool IsValidRustLinkerSubstitution(const Substitution* type) {
   return IsValidRustSubstitution(type) ||
          type == &CSubstitutionLdFlags;
diff --git a/src/gn/rust_substitution_type.h b/src/gn/rust_substitution_type.h
index 36ba953..5a6ac6c 100644
--- a/src/gn/rust_substitution_type.h
+++ b/src/gn/rust_substitution_type.h
@@ -23,6 +23,7 @@
 extern const Substitution kRustSubstitutionSources;
 
 bool IsValidRustSubstitution(const Substitution* type);
+bool IsValidRustScriptArgsSubstitution(const Substitution* type);
 bool IsValidRustLinkerSubstitution(const Substitution* type);
 
 #endif  // TOOLS_GN_RUST_SUBSTITUTION_TYPE_H_
diff --git a/src/gn/substitution_list.cc b/src/gn/substitution_list.cc
index 21311c6..aed66a0 100644
--- a/src/gn/substitution_list.cc
+++ b/src/gn/substitution_list.cc
@@ -49,13 +49,16 @@
 
 SubstitutionList SubstitutionList::MakeForTest(const char* a,
                                                const char* b,
-                                               const char* c) {
+                                               const char* c,
+                                               const char* d) {
   std::vector<std::string> input_strings;
   input_strings.push_back(a);
   if (b)
     input_strings.push_back(b);
   if (c)
     input_strings.push_back(c);
+  if (d)
+    input_strings.push_back(d);
 
   Err err;
   SubstitutionList result;
diff --git a/src/gn/substitution_list.h b/src/gn/substitution_list.h
index 559b25d..0dbf206 100644
--- a/src/gn/substitution_list.h
+++ b/src/gn/substitution_list.h
@@ -27,7 +27,8 @@
   // Makes a SubstitutionList from the given hardcoded patterns.
   static SubstitutionList MakeForTest(const char* a,
                                       const char* b = nullptr,
-                                      const char* c = nullptr);
+                                      const char* c = nullptr,
+                                      const char* d = nullptr);
 
   const std::vector<SubstitutionPattern>& list() const { return list_; }
 
diff --git a/src/gn/substitution_type.cc b/src/gn/substitution_type.cc
index 06c21b3..723bf8a 100644
--- a/src/gn/substitution_type.cc
+++ b/src/gn/substitution_type.cc
@@ -163,7 +163,9 @@
 }
 
 bool IsValidScriptArgsSubstitution(const Substitution* type) {
-  return IsValidSourceSubstitution(type) || type == &SubstitutionRspFileName;
+  return IsValidSourceSubstitution(type) || type == &SubstitutionRspFileName ||
+         IsValidCompilerScriptArgsSubstitution(type) ||
+         IsValidRustScriptArgsSubstitution(type);
 }
 
 bool IsValidToolSubstitution(const Substitution* type) {
diff --git a/src/gn/variables.cc b/src/gn/variables.cc
index fba20e0..ca64776 100644
--- a/src/gn/variables.cc
+++ b/src/gn/variables.cc
@@ -561,6 +561,14 @@
   to the script. Typically you would use source expansion (see "gn help
   source_expansion") to insert the source file names.
 
+  Args can also expand the substitution patterns corresponding to config
+  variables in the same way that compiler tools (see "gn help tool") do. These
+  allow actions that run compiler or compiler-like tools to access the results
+  of propagating configs through the build graph. For example:
+
+  args = [ "{{defines}}", "{{include_dirs}}", "{{rustenv}}", "--input-file",
+           "{{source}}" ]
+
   See also "gn help action" and "gn help action_foreach".
 )";