json_project_writer: Output response_file_contents for action and action_for_each

Add response_file_contents as a property of the action
and action_for_each targets.

Bug: gn:84
Change-Id: I55eba6411013c6edaaac86331a2550078ac04821
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/5100
Reviewed-by: Brett Wilson <brettw@google.com>
Commit-Queue: Brett Wilson <brettw@google.com>
diff --git a/AUTHORS b/AUTHORS
index fe152fd..417cc5f 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -12,6 +12,7 @@
 IBM Inc. <*@*.ibm.com>
 Loongson Technology Corporation Limited. <*@loongson.cn>
 MIPS Technologies, Inc. <*@mips.com>
+NVIDIA Corporation <*@nvidia.com>
 Opera Software ASA <*@opera.com>
 The Chromium Authors <*@chromium.org>
 Vewd Software AS <*@vewd.com>
diff --git a/build/gen.py b/build/gen.py
index 3d356c7..766969f 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -587,6 +587,7 @@
         'tools/gn/header_checker_unittest.cc',
         'tools/gn/inherited_libraries_unittest.cc',
         'tools/gn/input_conversion_unittest.cc',
+        'tools/gn/json_project_writer_unittest.cc',
         'tools/gn/label_pattern_unittest.cc',
         'tools/gn/label_unittest.cc',
         'tools/gn/loader_unittest.cc',
diff --git a/tools/gn/desc_builder.cc b/tools/gn/desc_builder.cc
index ab6c686..6ede896 100644
--- a/tools/gn/desc_builder.cc
+++ b/tools/gn/desc_builder.cc
@@ -55,6 +55,7 @@
 //   "walk_keys" : [ list of target walk keys ]
 //   "rebase" : true or false
 //   "output_conversion" : "string for output conversion"
+//   "response_file_contents": [ list of response file contents entries ]
 // }
 //
 // Optionally, if "what" is specified while generating description, two other
@@ -416,6 +417,16 @@
 
         res->SetWithoutPathExpansion(variables::kArgs, std::move(args));
       }
+      if (what(variables::kResponseFileContents) &&
+          !target_->action_values().rsp_file_contents().list().empty()) {
+        auto rsp_file_contents = std::make_unique<base::ListValue>();
+        for (const auto& elem :
+             target_->action_values().rsp_file_contents().list())
+          rsp_file_contents->AppendString(elem.AsString());
+
+        res->SetWithoutPathExpansion(variables::kResponseFileContents,
+                                     std::move(rsp_file_contents));
+      }
       if (what(variables::kDepfile) &&
           !target_->action_values().depfile().empty()) {
         res->SetKey(variables::kDepfile,
diff --git a/tools/gn/json_project_writer.cc b/tools/gn/json_project_writer.cc
index 4f4b0e7..1b00086 100644
--- a/tools/gn/json_project_writer.cc
+++ b/tools/gn/json_project_writer.cc
@@ -81,48 +81,6 @@
   return true;
 }
 
-std::string RenderJSON(const BuildSettings* build_settings,
-                       const Builder& builder,
-                       std::vector<const Target*>& all_targets) {
-  Label default_toolchain_label;
-
-  auto targets = std::make_unique<base::DictionaryValue>();
-  for (const auto* target : all_targets) {
-    if (default_toolchain_label.is_null())
-      default_toolchain_label = target->settings()->default_toolchain_label();
-    auto description =
-        DescBuilder::DescriptionForTarget(target, "", false, false, false);
-    // Outputs need to be asked for separately.
-    auto outputs = DescBuilder::DescriptionForTarget(target, "source_outputs",
-                                                     false, false, false);
-    base::DictionaryValue* outputs_value = nullptr;
-    if (outputs->GetDictionary("source_outputs", &outputs_value) &&
-        !outputs_value->empty()) {
-      description->MergeDictionary(outputs.get());
-    }
-    targets->SetWithoutPathExpansion(
-        target->label().GetUserVisibleName(default_toolchain_label),
-        std::move(description));
-  }
-
-  auto settings = std::make_unique<base::DictionaryValue>();
-  settings->SetKey("root_path", base::Value(build_settings->root_path_utf8()));
-  settings->SetKey("build_dir",
-                   base::Value(build_settings->build_dir().value()));
-  settings->SetKey(
-      "default_toolchain",
-      base::Value(default_toolchain_label.GetUserVisibleName(false)));
-
-  auto output = std::make_unique<base::DictionaryValue>();
-  output->SetWithoutPathExpansion("targets", std::move(targets));
-  output->SetWithoutPathExpansion("build_settings", std::move(settings));
-
-  std::string s;
-  base::JSONWriter::WriteWithOptions(
-      *output.get(), base::JSONWriter::OPTIONS_PRETTY_PRINT, &s);
-  return s;
-}
-
 bool InvokePython(const BuildSettings* build_settings,
                   const base::FilePath& python_script_path,
                   const std::string& python_script_extra_args,
@@ -192,7 +150,7 @@
     return false;
   }
 
-  std::string json = RenderJSON(build_settings, builder, targets);
+  std::string json = RenderJSON(build_settings, targets);
   if (!ContentsEqual(output_path, json)) {
     if (!WriteFileIfChanged(output_path, json, err)) {
       return false;
@@ -218,3 +176,45 @@
 
   return true;
 }
+
+std::string JSONProjectWriter::RenderJSON(
+    const BuildSettings* build_settings,
+    std::vector<const Target*>& all_targets) {
+  Label default_toolchain_label;
+
+  auto targets = std::make_unique<base::DictionaryValue>();
+  for (const auto* target : all_targets) {
+    if (default_toolchain_label.is_null())
+      default_toolchain_label = target->settings()->default_toolchain_label();
+    auto description =
+        DescBuilder::DescriptionForTarget(target, "", false, false, false);
+    // Outputs need to be asked for separately.
+    auto outputs = DescBuilder::DescriptionForTarget(target, "source_outputs",
+                                                     false, false, false);
+    base::DictionaryValue* outputs_value = nullptr;
+    if (outputs->GetDictionary("source_outputs", &outputs_value) &&
+        !outputs_value->empty()) {
+      description->MergeDictionary(outputs.get());
+    }
+    targets->SetWithoutPathExpansion(
+        target->label().GetUserVisibleName(default_toolchain_label),
+        std::move(description));
+  }
+
+  auto settings = std::make_unique<base::DictionaryValue>();
+  settings->SetKey("root_path", base::Value(build_settings->root_path_utf8()));
+  settings->SetKey("build_dir",
+                   base::Value(build_settings->build_dir().value()));
+  settings->SetKey(
+      "default_toolchain",
+      base::Value(default_toolchain_label.GetUserVisibleName(false)));
+
+  auto output = std::make_unique<base::DictionaryValue>();
+  output->SetWithoutPathExpansion("targets", std::move(targets));
+  output->SetWithoutPathExpansion("build_settings", std::move(settings));
+
+  std::string s;
+  base::JSONWriter::WriteWithOptions(
+      *output.get(), base::JSONWriter::OPTIONS_PRETTY_PRINT, &s);
+  return s;
+}
diff --git a/tools/gn/json_project_writer.h b/tools/gn/json_project_writer.h
index 8c293bf..2c8a2b7 100644
--- a/tools/gn/json_project_writer.h
+++ b/tools/gn/json_project_writer.h
@@ -21,6 +21,13 @@
                                const std::string& dir_filter_string,
                                bool quiet,
                                Err* err);
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(JSONProjectWriter, ActionWithResponseFile);
+  FRIEND_TEST_ALL_PREFIXES(JSONProjectWriter, ForEachWithResponseFile);
+
+  static std::string RenderJSON(const BuildSettings* build_settings,
+                                std::vector<const Target*>& all_targets);
 };
 
 #endif
diff --git a/tools/gn/json_project_writer_unittest.cc b/tools/gn/json_project_writer_unittest.cc
new file mode 100644
index 0000000..9c933dd
--- /dev/null
+++ b/tools/gn/json_project_writer_unittest.cc
@@ -0,0 +1,137 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/string_util.h"
+#include "tools/gn/json_project_writer.h"
+#include "tools/gn/substitution_list.h"
+#include "tools/gn/target.h"
+#include "tools/gn/test_with_scope.h"
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+TEST(JSONProjectWriter, ActionWithResponseFile) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::ACTION);
+
+  target.sources().push_back(SourceFile("//foo/source1.txt"));
+  target.config_values().inputs().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("{{response_file_name}}");
+  target.action_values().rsp_file_contents() =
+      SubstitutionList::MakeForTest("-j", "3");
+  target.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/output1.out");
+
+  setup.build_settings()->set_python_path(
+      base::FilePath(FILE_PATH_LITERAL("/usr/bin/python")));
+  std::vector<const Target*> targets;
+  targets.push_back(&target);
+  std::string out =
+      JSONProjectWriter::RenderJSON(setup.build_settings(), targets);
+#if defined(OS_WIN)
+  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
+#endif
+  const char expected_json[] =
+      "{\n"
+      "   \"build_settings\": {\n"
+      "      \"build_dir\": \"//out/Debug/\",\n"
+      "      \"default_toolchain\": \"//toolchain:default\",\n"
+      "      \"root_path\": \"\"\n"
+      "   },\n"
+      "   \"targets\": {\n"
+      "      \"//foo:bar()\": {\n"
+      "         \"args\": [ \"{{response_file_name}}\" ],\n"
+      "         \"deps\": [  ],\n"
+      "         \"inputs\": [ \"//foo/input1.txt\" ],\n"
+      "         \"metadata\": {\n"
+      "\n"
+      "         },\n"
+      "         \"outputs\": [ \"//out/Debug/output1.out\" ],\n"
+      "         \"public\": \"*\",\n"
+      "         \"response_file_contents\": [ \"-j\", \"3\" ],\n"
+      "         \"script\": \"//foo/script.py\",\n"
+      "         \"sources\": [ \"//foo/source1.txt\" ],\n"
+      "         \"testonly\": false,\n"
+      "         \"toolchain\": \"\",\n"
+      "         \"type\": \"action\",\n"
+      "         \"visibility\": [  ]\n"
+      "      }\n"
+      "   }\n"
+      "}\n";
+  EXPECT_EQ(expected_json, out);
+}
+
+TEST(JSONProjectWriter, ForEachWithResponseFile) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::ACTION_FOREACH);
+
+  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.build_settings()->set_python_path(
+      base::FilePath(FILE_PATH_LITERAL("/usr/bin/python")));
+  std::vector<const Target*> targets;
+  targets.push_back(&target);
+  std::string out =
+      JSONProjectWriter::RenderJSON(setup.build_settings(), targets);
+#if defined(OS_WIN)
+  base::ReplaceSubstringsAfterOffset(&out, 0, "\r\n", "\n");
+#endif
+  const char expected_json[] =
+      "{\n"
+      "   \"build_settings\": {\n"
+      "      \"build_dir\": \"//out/Debug/\",\n"
+      "      \"default_toolchain\": \"//toolchain:default\",\n"
+      "      \"root_path\": \"\"\n"
+      "   },\n"
+      "   \"targets\": {\n"
+      "      \"//foo:bar()\": {\n"
+      "         \"args\": [ \"{{source}}\", \"{{source_file_part}}\", "
+      "\"{{response_file_name}}\" ],\n"
+      "         \"deps\": [  ],\n"
+      "         \"metadata\": {\n"
+      "\n"
+      "         },\n"
+      "         \"output_patterns\": [ "
+      "\"//out/Debug/{{source_name_part}}.out\" ],\n"
+      "         \"outputs\": [ \"//out/Debug/input1.out\" ],\n"
+      "         \"public\": \"*\",\n"
+      "         \"response_file_contents\": [ \"-j\", \"{{source_name_part}}\" "
+      "],\n"
+      "         \"script\": \"//foo/script.py\",\n"
+      "         \"sources\": [ \"//foo/input1.txt\" ],\n"
+      "         \"testonly\": false,\n"
+      "         \"toolchain\": \"\",\n"
+      "         \"type\": \"action_foreach\",\n"
+      "         \"visibility\": [  ]\n"
+      "      }\n"
+      "   }\n"
+      "}\n";
+  EXPECT_EQ(expected_json, out);
+}