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[];