Support script response files in GN.

Exposes Ninja's response file capabilities to GN actions so that long command lines can be passed to scripts without manually calling write_file.

Change the domain_reliability bake_in_configs script to use this facility as a test. There are now command-line flags that support both reading the .gypi file and passing a response file. This patch adds the BUILD rule for the new-style response file, but doesn't use it (pending a GN binary push).

Removes running actions through gyp-win-tool on Windows. This is a bunch of hardcoded commands that are not necessary to my knowledge.

Fixes an out-of-date cpu_arch reference in the mini installer build file I noticed (in a NaCl block that's not run yet).

Review URL: https://codereview.chromium.org/1430043002

Cr-Original-Commit-Position: refs/heads/master@{#358968}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: d6763a7390d6ca727c090a403d5dbefa94c5d79b
diff --git a/tools/gn/action_target_generator.cc b/tools/gn/action_target_generator.cc
index 40523fe..b2d7e27 100644
--- a/tools/gn/action_target_generator.cc
+++ b/tools/gn/action_target_generator.cc
@@ -48,6 +48,9 @@
   if (!FillScriptArgs())
     return;
 
+  if (!FillResponseFileContents())
+    return;
+
   if (!FillOutputs(output_type_ == Target::ACTION_FOREACH))
     return;
 
@@ -65,6 +68,29 @@
 
   // Action outputs don't depend on the current toolchain so we can skip adding
   // that dependency.
+
+  // response_file_contents and {{response_file_name}} in the args must go
+  // together.
+  const auto& required_args_substitutions =
+      target_->action_values().args().required_types();
+  bool has_rsp_file_name = std::find(required_args_substitutions.begin(),
+                                     required_args_substitutions.end(),
+                                     SUBSTITUTION_RSP_FILE_NAME) !=
+      required_args_substitutions.end();
+  if (target_->action_values().uses_rsp_file() && !has_rsp_file_name) {
+    *err_ = Err(function_call_, "Missing {{response_file_name}} in args.",
+        "This target defines response_file_contents but doesn't use\n"
+        "{{response_file_name}} in the args, which means the response file\n"
+        "will be unused.");
+    return;
+  }
+  if (!target_->action_values().uses_rsp_file() && has_rsp_file_name) {
+    *err_ = Err(function_call_, "Missing response_file_content definition.",
+        "This target uses {{response_file_name}} in the args, but does not\n"
+        "define response_file_content which means the response file\n"
+        "will be empty.");
+    return;
+  }
 }
 
 bool ActionTargetGenerator::FillScript() {
@@ -95,6 +121,13 @@
   return target_->action_values().args().Parse(*value, err_);
 }
 
+bool ActionTargetGenerator::FillResponseFileContents() {
+  const Value* value = scope_->GetValue(variables::kResponseFileContents, true);
+  if (!value)
+    return true;
+  return target_->action_values().rsp_file_contents().Parse(*value, err_);
+}
+
 bool ActionTargetGenerator::FillDepfile() {
   const Value* value = scope_->GetValue(variables::kDepfile, true);
   if (!value)
diff --git a/tools/gn/action_target_generator.h b/tools/gn/action_target_generator.h
index fc41273..0a69eda 100644
--- a/tools/gn/action_target_generator.h
+++ b/tools/gn/action_target_generator.h
@@ -25,6 +25,7 @@
  private:
   bool FillScript();
   bool FillScriptArgs();
+  bool FillResponseFileContents();
   bool FillDepfile();
   bool FillConsole();
 
diff --git a/tools/gn/action_values.h b/tools/gn/action_values.h
index 3b3c2a6..879ccf6 100644
--- a/tools/gn/action_values.h
+++ b/tools/gn/action_values.h
@@ -43,6 +43,13 @@
   bool has_depfile() const { return !depfile_.ranges().empty(); }
   void set_depfile(const SubstitutionPattern& depfile) { depfile_ = depfile; }
 
+  // Response file contents. Empty means no response file.
+  SubstitutionList& rsp_file_contents() { return rsp_file_contents_; }
+  const SubstitutionList& rsp_file_contents() const {
+    return rsp_file_contents_;
+  }
+  bool uses_rsp_file() const { return !rsp_file_contents_.list().empty(); }
+
   // Console pool option
   bool is_console() const { return console_; }
   void set_console(bool value) { console_ = value; }
@@ -52,6 +59,7 @@
   SubstitutionList args_;
   SubstitutionList outputs_;
   SubstitutionPattern depfile_;
+  SubstitutionList rsp_file_contents_;
   bool console_;
 
   DISALLOW_COPY_AND_ASSIGN(ActionValues);
diff --git a/tools/gn/function_write_file.cc b/tools/gn/function_write_file.cc
index d24abc1..02acca4 100644
--- a/tools/gn/function_write_file.cc
+++ b/tools/gn/function_write_file.cc
@@ -82,10 +82,15 @@
     "  written, the file will not be updated. This will prevent unnecessary\n"
     "  rebuilds of targets that depend on this file.\n"
     "\n"
+    "  One use for write_file is to write a list of inputs to an script\n"
+    "  that might be too long for the command line. However, it is\n"
+    "  preferrable to use response files for this purpose. See\n"
+    "  \"gn help response_file_contents\".\n"
+    "\n"
     "  TODO(brettw) we probably need an optional third argument to control\n"
     "  list formatting.\n"
     "\n"
-    "Arguments:\n"
+    "Arguments\n"
     "\n"
     "  filename\n"
     "      Filename to write. This must be within the output directory.\n"
diff --git a/tools/gn/functions_target.cc b/tools/gn/functions_target.cc
index 79af535..b04fea8 100644
--- a/tools/gn/functions_target.cc
+++ b/tools/gn/functions_target.cc
@@ -108,6 +108,9 @@
     "  if an input file changes) by writing a depfile when the script is run\n"
     "  (see \"gn help depfile\"). This is more flexible than \"inputs\".\n"
     "\n"
+    "  If the command line length is very long, you can use response files\n"
+    "  to pass args to your script. See \"gn help response_file_contents\".\n"
+    "\n"
     "  It is recommended you put inputs to your script in the \"sources\"\n"
     "  variable, and stuff like other Python files required to run your\n"
     "  script in the \"inputs\" variable.\n"
@@ -127,8 +130,8 @@
     "\n"
     "Variables\n"
     "\n"
-    "  args, console, data, data_deps, depfile, deps, outputs*, script*,\n"
-    "  inputs, sources\n"
+    "  args, console, data, data_deps, depfile, deps, inputs, outputs*,\n"
+    "  response_file_contents, script*, sources\n"
     "  * = required\n"
     "\n"
     "Example\n"
@@ -182,6 +185,9 @@
     "  listed in the \"inputs\" variable. These files are treated as\n"
     "  dependencies of each script invocation.\n"
     "\n"
+    "  If the command line length is very long, you can use response files\n"
+    "  to pass args to your script. See \"gn help response_file_contents\".\n"
+    "\n"
     "  You can dynamically write input dependencies (for incremental rebuilds\n"
     "  if an input file changes) by writing a depfile when the script is run\n"
     "  (see \"gn help depfile\"). This is more flexible than \"inputs\".\n"
@@ -198,8 +204,8 @@
     "\n"
     "Variables\n"
     "\n"
-    "  args, console, data, data_deps, depfile, deps, outputs*, script*,\n"
-    "  inputs, sources*\n"
+    "  args, console, data, data_deps, depfile, deps, inputs, outputs*,\n"
+    "  response_file_contents, script*, sources*\n"
     "  * = required\n"
     "\n"
     "Example\n"
diff --git a/tools/gn/ninja_action_target_writer.cc b/tools/gn/ninja_action_target_writer.cc
index e2440a4..d0edc34 100644
--- a/tools/gn/ninja_action_target_writer.cc
+++ b/tools/gn/ninja_action_target_writer.cc
@@ -105,53 +105,43 @@
   EscapeOptions args_escape_options;
   args_escape_options.mode = ESCAPE_NINJA_COMMAND;
 
-  if (settings_->IsWin()) {
-    // Send through gyp-win-tool and use a response file.
+  out_ << "rule " << custom_rule_name << std::endl;
+
+  if (target_->action_values().uses_rsp_file()) {
+    // Needs a response file. The unique_name part is for action_foreach so
+    // each invocation of the rule gets a different response file. This isn't
+    // strictly necessary for regular one-shot actions, but it's easier to
+    // just always define unique_name.
     std::string rspfile = custom_rule_name;
     if (!target_->sources().empty())
       rspfile += ".$unique_name";
     rspfile += ".rsp";
-
-    out_ << "rule " << custom_rule_name << std::endl;
-    out_ << "  command = ";
-    path_output_.WriteFile(out_, settings_->build_settings()->python_path());
-    // TODO(brettw) this hardcodes "environment.x86" which is something that
-    // the Chrome Windows toolchain writes. We should have a way to invoke
-    // python without requiring this gyp_win_tool thing.
-    out_ << " gyp-win-tool action-wrapper environment.x86 " << rspfile
-         << std::endl;
-    out_ << "  description = ACTION " << target_label << std::endl;
-    out_ << "  restat = 1" << std::endl;
     out_ << "  rspfile = " << rspfile << std::endl;
 
-    // The build command goes in the rsp file.
-    out_ << "  rspfile_content = ";
-    path_output_.WriteFile(out_, settings_->build_settings()->python_path());
-    out_ << " ";
-    path_output_.WriteFile(out_, target_->action_values().script());
-    for (const auto& arg : args.list()) {
+    // Response file contents.
+    out_ << "  rspfile_content =";
+    for (const auto& arg :
+         target_->action_values().rsp_file_contents().list()) {
       out_ << " ";
       SubstitutionWriter::WriteWithNinjaVariables(
           arg, args_escape_options, out_);
     }
     out_ << std::endl;
-  } else {
-    // Posix can execute Python directly.
-    out_ << "rule " << custom_rule_name << std::endl;
-    out_ << "  command = ";
-    path_output_.WriteFile(out_, settings_->build_settings()->python_path());
-    out_ << " ";
-    path_output_.WriteFile(out_, target_->action_values().script());
-    for (const auto& arg : args.list()) {
-      out_ << " ";
-      SubstitutionWriter::WriteWithNinjaVariables(
-          arg, args_escape_options, out_);
-    }
-    out_ << std::endl;
-    out_ << "  description = ACTION " << target_label << std::endl;
-    out_ << "  restat = 1" << std::endl;
   }
 
+  out_ << "  command = ";
+  path_output_.WriteFile(out_, settings_->build_settings()->python_path());
+  out_ << " ";
+  path_output_.WriteFile(out_, target_->action_values().script());
+  for (const auto& arg : args.list()) {
+    out_ << " ";
+    SubstitutionWriter::WriteWithNinjaVariables(
+        arg, args_escape_options, out_);
+  }
+  out_ << std::endl;
+  out_ << "  description = ACTION " << target_label << std::endl;
+  out_ << "  restat = 1" << std::endl;
+
   return custom_rule_name;
 }
 
@@ -165,9 +155,6 @@
   // they will get pasted into the real command line.
   args_escape_options.inhibit_quoting = true;
 
-  const std::vector<SubstitutionType>& args_substitutions_used =
-      target_->action_values().args().required_types();
-
   const Target::FileList& sources = target_->sources();
   for (size_t i = 0; i < sources.size(); i++) {
     out_ << "build";
@@ -185,12 +172,22 @@
     }
     out_ << std::endl;
 
-    // Windows needs a unique ID for the response file.
-    if (target_->settings()->IsWin())
+    // Response files require a unique name be defined.
+    if (target_->action_values().uses_rsp_file())
       out_ << "  unique_name = " << i << std::endl;
 
+    // The required types is the union of the args and response file. This
+    // might theoretically duplicate a definition if the same substitution is
+    // used in both the args and the reponse file. However, this should be
+    // very unusual (normally the substitutions will go in one place or the
+    // other) and the redundant assignment won't bother Ninja.
     SubstitutionWriter::WriteNinjaVariablesForSource(
-        settings_, sources[i], args_substitutions_used,
+        settings_, sources[i],
+        target_->action_values().args().required_types(),
+        args_escape_options, out_);
+    SubstitutionWriter::WriteNinjaVariablesForSource(
+        settings_, sources[i],
+        target_->action_values().rsp_file_contents().required_types(),
         args_escape_options, out_);
 
     if (target_->action_values().has_depfile()) {
diff --git a/tools/gn/ninja_action_target_writer_unittest.cc b/tools/gn/ninja_action_target_writer_unittest.cc
index 99adf1f..a7dd21b 100644
--- a/tools/gn/ninja_action_target_writer_unittest.cc
+++ b/tools/gn/ninja_action_target_writer_unittest.cc
@@ -140,57 +140,26 @@
   target.SetToolchain(setup.toolchain());
   ASSERT_TRUE(target.OnResolved(&err));
 
-  // Posix.
-  {
-    setup.settings()->set_target_os(Settings::LINUX);
-    setup.build_settings()->set_python_path(base::FilePath(FILE_PATH_LITERAL(
-        "/usr/bin/python")));
+  setup.settings()->set_target_os(Settings::LINUX);
+  setup.build_settings()->set_python_path(base::FilePath(FILE_PATH_LITERAL(
+      "/usr/bin/python")));
 
-    std::ostringstream out;
-    NinjaActionTargetWriter writer(&target, out);
-    writer.Run();
+  std::ostringstream out;
+  NinjaActionTargetWriter writer(&target, out);
+  writer.Run();
 
-    const char expected_linux[] =
-        "rule __foo_bar___rule\n"
-        "  command = /usr/bin/python ../../foo/script.py\n"
-        "  description = ACTION //foo:bar()\n"
-        "  restat = 1\n"
-        "build obj/foo/bar.inputdeps.stamp: stamp ../../foo/script.py "
-            "../../foo/included.txt ../../foo/source.txt\n"
-        "\n"
-        "build foo.out: __foo_bar___rule | obj/foo/bar.inputdeps.stamp\n"
-        "\n"
-        "build obj/foo/bar.stamp: stamp foo.out\n";
-    EXPECT_EQ(expected_linux, out.str());
-  }
-
-  // Windows.
-  {
-    // Note: we use forward slashes here so that the output will be the same on
-    // Linux and Windows.
-    setup.build_settings()->set_python_path(base::FilePath(FILE_PATH_LITERAL(
-        "C:/python/python.exe")));
-    setup.settings()->set_target_os(Settings::WIN);
-
-    std::ostringstream out;
-    NinjaActionTargetWriter writer(&target, out);
-    writer.Run();
-
-    const char expected_win[] =
-        "rule __foo_bar___rule\n"
-        "  command = C$:/python/python.exe gyp-win-tool action-wrapper environment.x86 __foo_bar___rule.$unique_name.rsp\n"
-        "  description = ACTION //foo:bar()\n"
-        "  restat = 1\n"
-        "  rspfile = __foo_bar___rule.$unique_name.rsp\n"
-        "  rspfile_content = C$:/python/python.exe ../../foo/script.py\n"
-        "build obj/foo/bar.inputdeps.stamp: stamp ../../foo/script.py "
-            "../../foo/included.txt ../../foo/source.txt\n"
-        "\n"
-        "build foo.out: __foo_bar___rule | obj/foo/bar.inputdeps.stamp\n"
-        "\n"
-        "build obj/foo/bar.stamp: stamp foo.out\n";
-    EXPECT_EQ(expected_win, out.str());
-  }
+  const char expected_linux[] =
+      "rule __foo_bar___rule\n"
+      "  command = /usr/bin/python ../../foo/script.py\n"
+      "  description = ACTION //foo:bar()\n"
+      "  restat = 1\n"
+      "build obj/foo/bar.inputdeps.stamp: stamp ../../foo/script.py "
+          "../../foo/included.txt ../../foo/source.txt\n"
+      "\n"
+      "build foo.out: __foo_bar___rule | obj/foo/bar.inputdeps.stamp\n"
+      "\n"
+      "build obj/foo/bar.stamp: stamp foo.out\n";
+  EXPECT_EQ(expected_linux, out.str());
 }
 
 TEST(NinjaActionTargetWriter, ForEach) {
@@ -237,86 +206,43 @@
   target.SetToolchain(setup.toolchain());
   ASSERT_TRUE(target.OnResolved(&err));
 
-  // Posix.
-  {
-    setup.settings()->set_target_os(Settings::LINUX);
-    setup.build_settings()->set_python_path(base::FilePath(FILE_PATH_LITERAL(
-        "/usr/bin/python")));
+  setup.settings()->set_target_os(Settings::LINUX);
+  setup.build_settings()->set_python_path(base::FilePath(FILE_PATH_LITERAL(
+      "/usr/bin/python")));
 
-    std::ostringstream out;
-    NinjaActionTargetWriter writer(&target, out);
-    writer.Run();
+  std::ostringstream out;
+  NinjaActionTargetWriter writer(&target, out);
+  writer.Run();
 
-    const char expected_linux[] =
-        "rule __foo_bar___rule\n"
-        "  command = /usr/bin/python ../../foo/script.py -i ${in} "
-            // Escaping is different between Windows and Posix.
+  const char expected_linux[] =
+      "rule __foo_bar___rule\n"
+      "  command = /usr/bin/python ../../foo/script.py -i ${in} "
+          // Escaping is different between Windows and Posix.
 #if defined(OS_WIN)
-            "\"--out=foo$ bar${source_name_part}.o\"\n"
+          "\"--out=foo$ bar${source_name_part}.o\"\n"
 #else
-            "--out=foo\\$ bar${source_name_part}.o\n"
+          "--out=foo\\$ bar${source_name_part}.o\n"
 #endif
-        "  description = ACTION //foo:bar()\n"
-        "  restat = 1\n"
-        "build obj/foo/bar.inputdeps.stamp: stamp ../../foo/script.py "
-            "../../foo/included.txt obj/foo/dep.stamp\n"
-        "\n"
-        "build input1.out: __foo_bar___rule ../../foo/input1.txt | "
-            "obj/foo/bar.inputdeps.stamp\n"
-        "  source_name_part = input1\n"
-        "build input2.out: __foo_bar___rule ../../foo/input2.txt | "
-            "obj/foo/bar.inputdeps.stamp\n"
-        "  source_name_part = input2\n"
-        "\n"
-        "build obj/foo/bar.stamp: "
-            "stamp input1.out input2.out || obj/foo/datadep.stamp\n";
+      "  description = ACTION //foo:bar()\n"
+      "  restat = 1\n"
+      "build obj/foo/bar.inputdeps.stamp: stamp ../../foo/script.py "
+          "../../foo/included.txt obj/foo/dep.stamp\n"
+      "\n"
+      "build input1.out: __foo_bar___rule ../../foo/input1.txt | "
+          "obj/foo/bar.inputdeps.stamp\n"
+      "  source_name_part = input1\n"
+      "build input2.out: __foo_bar___rule ../../foo/input2.txt | "
+          "obj/foo/bar.inputdeps.stamp\n"
+      "  source_name_part = input2\n"
+      "\n"
+      "build obj/foo/bar.stamp: "
+          "stamp input1.out input2.out || obj/foo/datadep.stamp\n";
 
-    std::string out_str = out.str();
+  std::string out_str = out.str();
 #if defined(OS_WIN)
-    std::replace(out_str.begin(), out_str.end(), '\\', '/');
+  std::replace(out_str.begin(), out_str.end(), '\\', '/');
 #endif
-    EXPECT_EQ(expected_linux, out_str);
-  }
-
-  // Windows.
-  {
-    setup.build_settings()->set_python_path(base::FilePath(FILE_PATH_LITERAL(
-        "C:/python/python.exe")));
-    setup.settings()->set_target_os(Settings::WIN);
-
-    std::ostringstream out;
-    NinjaActionTargetWriter writer(&target, out);
-    writer.Run();
-
-    const char expected_win[] =
-        "rule __foo_bar___rule\n"
-        "  command = C$:/python/python.exe gyp-win-tool action-wrapper "
-            "environment.x86 __foo_bar___rule.$unique_name.rsp\n"
-        "  description = ACTION //foo:bar()\n"
-        "  restat = 1\n"
-        "  rspfile = __foo_bar___rule.$unique_name.rsp\n"
-        "  rspfile_content = C$:/python/python.exe ../../foo/script.py -i "
-#if defined(OS_WIN)
-            "${in} \"--out=foo$ bar${source_name_part}.o\"\n"
-#else
-            "${in} --out=foo\\$ bar${source_name_part}.o\n"
-#endif
-        "build obj/foo/bar.inputdeps.stamp: stamp ../../foo/script.py "
-            "../../foo/included.txt obj/foo/dep.stamp\n"
-        "\n"
-        "build input1.out: __foo_bar___rule ../../foo/input1.txt | "
-            "obj/foo/bar.inputdeps.stamp\n"
-        "  unique_name = 0\n"
-        "  source_name_part = input1\n"
-        "build input2.out: __foo_bar___rule ../../foo/input2.txt | "
-            "obj/foo/bar.inputdeps.stamp\n"
-        "  unique_name = 1\n"
-        "  source_name_part = input2\n"
-        "\n"
-        "build obj/foo/bar.stamp: "
-            "stamp input1.out input2.out || obj/foo/datadep.stamp\n";
-    EXPECT_EQ(expected_win, out.str());
-  }
+  EXPECT_EQ(expected_linux, out_str);
 }
 
 TEST(NinjaActionTargetWriter, ForEachWithDepfile) {
@@ -349,80 +275,96 @@
 
   target.inputs().push_back(SourceFile("//foo/included.txt"));
 
-  // Posix.
-  {
-    setup.settings()->set_target_os(Settings::LINUX);
-    setup.build_settings()->set_python_path(base::FilePath(FILE_PATH_LITERAL(
-        "/usr/bin/python")));
+  setup.settings()->set_target_os(Settings::LINUX);
+  setup.build_settings()->set_python_path(base::FilePath(FILE_PATH_LITERAL(
+      "/usr/bin/python")));
 
-    std::ostringstream out;
-    NinjaActionTargetWriter writer(&target, out);
-    writer.Run();
+  std::ostringstream out;
+  NinjaActionTargetWriter writer(&target, out);
+  writer.Run();
 
-    const char expected_linux[] =
-        "rule __foo_bar___rule\n"
-        "  command = /usr/bin/python ../../foo/script.py -i ${in} "
+  const char expected_linux[] =
+      "rule __foo_bar___rule\n"
+      "  command = /usr/bin/python ../../foo/script.py -i ${in} "
 #if defined(OS_WIN)
-            "\"--out=foo$ bar${source_name_part}.o\"\n"
+          "\"--out=foo$ bar${source_name_part}.o\"\n"
 #else
-            "--out=foo\\$ bar${source_name_part}.o\n"
+          "--out=foo\\$ bar${source_name_part}.o\n"
 #endif
-        "  description = ACTION //foo:bar()\n"
-        "  restat = 1\n"
-        "build obj/foo/bar.inputdeps.stamp: stamp ../../foo/script.py "
-            "../../foo/included.txt\n"
-        "\n"
-        "build input1.out: __foo_bar___rule ../../foo/input1.txt"
-            " | obj/foo/bar.inputdeps.stamp\n"
-        "  source_name_part = input1\n"
-        "  depfile = gen/input1.d\n"
-        "build input2.out: __foo_bar___rule ../../foo/input2.txt"
-            " | obj/foo/bar.inputdeps.stamp\n"
-        "  source_name_part = input2\n"
-        "  depfile = gen/input2.d\n"
-        "\n"
-        "build obj/foo/bar.stamp: stamp input1.out input2.out\n";
-    EXPECT_EQ(expected_linux, out.str());
-  }
+      "  description = ACTION //foo:bar()\n"
+      "  restat = 1\n"
+      "build obj/foo/bar.inputdeps.stamp: stamp ../../foo/script.py "
+          "../../foo/included.txt\n"
+      "\n"
+      "build input1.out: __foo_bar___rule ../../foo/input1.txt"
+          " | obj/foo/bar.inputdeps.stamp\n"
+      "  source_name_part = input1\n"
+      "  depfile = gen/input1.d\n"
+      "build input2.out: __foo_bar___rule ../../foo/input2.txt"
+          " | obj/foo/bar.inputdeps.stamp\n"
+      "  source_name_part = input2\n"
+      "  depfile = gen/input2.d\n"
+      "\n"
+      "build obj/foo/bar.stamp: stamp input1.out input2.out\n";
+  EXPECT_EQ(expected_linux, out.str());
+}
 
-  // Windows.
-  {
-    setup.build_settings()->set_python_path(base::FilePath(FILE_PATH_LITERAL(
-        "C:/python/python.exe")));
-    setup.settings()->set_target_os(Settings::WIN);
+TEST(NinjaActionTargetWriter, ForEachWithResponseFile) {
+  TestWithScope setup;
+  Err err;
 
-    std::ostringstream out;
-    NinjaActionTargetWriter writer(&target, out);
-    writer.Run();
+  setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/"));
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::ACTION_FOREACH);
 
-    const char expected_win[] =
-        "rule __foo_bar___rule\n"
-        "  command = C$:/python/python.exe gyp-win-tool action-wrapper "
-            "environment.x86 __foo_bar___rule.$unique_name.rsp\n"
-        "  description = ACTION //foo:bar()\n"
-        "  restat = 1\n"
-        "  rspfile = __foo_bar___rule.$unique_name.rsp\n"
-        "  rspfile_content = C$:/python/python.exe ../../foo/script.py -i "
-#if defined(OS_WIN)
-            "${in} \"--out=foo$ bar${source_name_part}.o\"\n"
-#else
-            "${in} --out=foo\\$ bar${source_name_part}.o\n"
-#endif
-        "build obj/foo/bar.inputdeps.stamp: stamp ../../foo/script.py "
-            "../../foo/included.txt\n"
-        "\n"
-        "build input1.out: __foo_bar___rule ../../foo/input1.txt"
-            " | obj/foo/bar.inputdeps.stamp\n"
-        "  unique_name = 0\n"
-        "  source_name_part = input1\n"
-        "  depfile = gen/input1.d\n"
-        "build input2.out: __foo_bar___rule ../../foo/input2.txt"
-            " | obj/foo/bar.inputdeps.stamp\n"
-        "  unique_name = 1\n"
-        "  source_name_part = input2\n"
-        "  depfile = gen/input2.d\n"
-        "\n"
-        "build obj/foo/bar.stamp: stamp input1.out input2.out\n";
-    EXPECT_EQ(expected_win, out.str());
-  }
+  target.sources().push_back(SourceFile("//foo/input1.txt"));
+  target.action_values().set_script(SourceFile("//foo/script.py"));
+
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  // Make sure we get interesting substitutions for both the args and the
+  // response file contents.
+  target.action_values().args() = SubstitutionList::MakeForTest(
+      "{{source}}",
+      "{{source_file_part}}",
+      "{{response_file_name}}");
+  target.action_values().rsp_file_contents() = SubstitutionList::MakeForTest(
+      "-j",
+      "{{source_name_part}}");
+  target.action_values().outputs() = SubstitutionList::MakeForTest(
+      "//out/Debug/{{source_name_part}}.out");
+
+  setup.settings()->set_target_os(Settings::LINUX);
+  setup.build_settings()->set_python_path(base::FilePath(FILE_PATH_LITERAL(
+      "/usr/bin/python")));
+
+  std::ostringstream out;
+  NinjaActionTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected_linux[] =
+      "rule __foo_bar___rule\n"
+      // This name is autogenerated from the target rule name.
+      "  rspfile = __foo_bar___rule.$unique_name.rsp\n"
+      // These come from rsp_file_contents above.
+      "  rspfile_content = -j ${source_name_part}\n"
+      // These come from the args.
+      "  command = /usr/bin/python ../../foo/script.py ${in} "
+          "${source_file_part} ${rspfile}\n"
+      "  description = ACTION //foo:bar()\n"
+      "  restat = 1\n"
+      "build obj/foo/bar.inputdeps.stamp: stamp ../../foo/script.py\n"
+      "\n"
+      "build input1.out: __foo_bar___rule ../../foo/input1.txt"
+          " | obj/foo/bar.inputdeps.stamp\n"
+      // Necessary for the rspfile defined in the rule.
+      "  unique_name = 0\n"
+      // Substitution for the args.
+      "  source_file_part = input1.txt\n"
+      // Substitution for the rspfile contents.
+      "  source_name_part = input1\n"
+      "\n"
+      "build obj/foo/bar.stamp: stamp input1.out\n";
+  EXPECT_EQ(expected_linux, out.str());
 }
diff --git a/tools/gn/substitution_type.cc b/tools/gn/substitution_type.cc
index c185ae1..1b74d58 100644
--- a/tools/gn/substitution_type.cc
+++ b/tools/gn/substitution_type.cc
@@ -44,6 +44,8 @@
   "{{libs}}",  // SUBSTITUTION_LIBS
   "{{output_extension}}",  // SUBSTITUTION_OUTPUT_EXTENSION
   "{{solibs}}",  // SUBSTITUTION_SOLIBS
+
+  "{{response_file_name}}",  // SUBSTITUTION_RSP_FILE_NAME
 };
 
 const char* kSubstitutionNinjaNames[SUBSTITUTION_NUM_TYPES] = {
@@ -85,6 +87,8 @@
     "libs",              // SUBSTITUTION_LIBS
     "output_extension",  // SUBSTITUTION_OUTPUT_EXTENSION
     "solibs",            // SUBSTITUTION_SOLIBS
+
+    "rspfile",  // SUBSTITUTION_RSP_FILE_NAME
 };
 
 SubstitutionBits::SubstitutionBits() : used() {
diff --git a/tools/gn/substitution_type.h b/tools/gn/substitution_type.h
index 7d7ea12..dfe9b54 100644
--- a/tools/gn/substitution_type.h
+++ b/tools/gn/substitution_type.h
@@ -59,6 +59,9 @@
   SUBSTITUTION_OUTPUT_EXTENSION,  // {{output_extension}}
   SUBSTITUTION_SOLIBS,  // {{solibs}}
 
+  // Used only for the args of actions.
+  SUBSTITUTION_RSP_FILE_NAME,  // {{response_file_name}}
+
   SUBSTITUTION_NUM_TYPES  // Must be last.
 };
 
diff --git a/tools/gn/substitution_writer.cc b/tools/gn/substitution_writer.cc
index 0351eb1..2af2e18 100644
--- a/tools/gn/substitution_writer.cc
+++ b/tools/gn/substitution_writer.cc
@@ -310,8 +310,10 @@
     std::ostream& out) {
   for (const auto& type : types) {
     // Don't write SOURCE since that just maps to Ninja's $in variable, which
-    // is implicit in the rule.
-    if (type != SUBSTITUTION_SOURCE) {
+    // is implicit in the rule. RESPONSE_FILE_NAME is written separately
+    // only when writing target rules since it can never be used in any
+    // other context (like process_file_template).
+    if (type != SUBSTITUTION_SOURCE && type != SUBSTITUTION_RSP_FILE_NAME) {
       out << "  " << kSubstitutionNinjaNames[type] << " = ";
         EscapeStringToStream(
             out,
diff --git a/tools/gn/variables.cc b/tools/gn/variables.cc
index d4e978d..5a9b840 100644
--- a/tools/gn/variables.cc
+++ b/tools/gn/variables.cc
@@ -1164,6 +1164,45 @@
     "    public_deps = [ \":c\" ]\n"
     "  }\n";
 
+const char kResponseFileContents[] = "response_file_contents";
+const char kResponseFileContents_HelpShort[] =
+    "response_file_contents: [string list] Contents of .rsp file for actions.";
+const char kResponseFileContents_Help[] =
+    "response_file_contents: Contents of a response file for actions.\n"
+    "\n"
+    "  Sometimes the arguments passed to a script can be too long for the\n"
+    "  system's command-line capabilities. This is especially the case on\n"
+    "  Windows where the maximum command-line length is less than 8K. A\n"
+    "  response file allows you to pass an unlimited amount of data to a\n"
+    "  script in a temporary file for an action or action_foreach target.\n"
+    "\n"
+    "  If the response_file_contents variable is defined and non-empty, the\n"
+    "  list will be treated as script args (including possibly substitution\n"
+    "  patterns) that will be written to a temporary file at build time.\n"
+    "  The name of the temporary file will be substituted for\n"
+    "  \"{{response_file_name}}\" in the script args.\n"
+    "\n"
+    "  The response file contents will always be quoted and escaped\n"
+    "  according to Unix shell rules. To parse the response file, the Python\n"
+    "  script should use \"shlex.split(file_contents)\".\n"
+    "\n"
+    "Example\n"
+    "\n"
+    "  action(\"process_lots_of_files\") {\n"
+    "    script = \"process.py\",\n"
+    "    inputs = [ ... huge list of files ... ]\n"
+    "\n"
+    "    # Write all the inputs to a response file for the script. Also,\n"
+    "    # make the paths relative to the script working directory.\n"
+    "    response_file_contents = rebase_path(inputs, root_build_dir)\n"
+    "\n"
+    "    # The script expects the name of the response file in --file-list.\n"
+    "    args = [\n"
+    "      \"--enable-foo\",\n"
+    "      \"--file-list={{response_file_name}}\",\n"
+    "    ]\n"
+    "  }\n";
+
 const char kScript[] = "script";
 const char kScript_HelpShort[] =
     "script: [file name] Script file for actions.";
@@ -1358,6 +1397,7 @@
     INSERT_VARIABLE(Public)
     INSERT_VARIABLE(PublicConfigs)
     INSERT_VARIABLE(PublicDeps)
+    INSERT_VARIABLE(ResponseFileContents)
     INSERT_VARIABLE(Script)
     INSERT_VARIABLE(Sources)
     INSERT_VARIABLE(Testonly)
diff --git a/tools/gn/variables.h b/tools/gn/variables.h
index 074f731..d02d2c6 100644
--- a/tools/gn/variables.h
+++ b/tools/gn/variables.h
@@ -195,6 +195,10 @@
 extern const char kPublicDeps_HelpShort[];
 extern const char kPublicDeps_Help[];
 
+extern const char kResponseFileContents[];
+extern const char kResponseFileContents_HelpShort[];
+extern const char kResponseFileContents_Help[];
+
 extern const char kScript[];
 extern const char kScript_HelpShort[];
 extern const char kScript_Help[];