summaryrefslogtreecommitdiffstats
path: root/tools/gn
diff options
context:
space:
mode:
authorbrettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-09-18 22:02:50 +0000
committerbrettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-09-18 22:02:50 +0000
commitb90c75afa26473fae4e6c2333beb241b7da27203 (patch)
treebc4f2383ca103e063dcf1d724c85172ca25e3b5d /tools/gn
parentebad924529462695b44cb0090d568a808ce83365 (diff)
downloadchromium_src-b90c75afa26473fae4e6c2333beb241b7da27203.zip
chromium_src-b90c75afa26473fae4e6c2333beb241b7da27203.tar.gz
chromium_src-b90c75afa26473fae4e6c2333beb241b7da27203.tar.bz2
Update GN copy file rule.
This merges the syntax of the script and copy file rules and adds lots of documentation. In doing this I realized that the template handling and escaping was bad in the script rules, so I did a bunch of work and added much better tests. BUG=288991 R=scottmg@chromium.org Review URL: https://codereview.chromium.org/23536068 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@223954 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'tools/gn')
-rw-r--r--tools/gn/BUILD.gn2
-rw-r--r--tools/gn/binary_target_generator.cc14
-rw-r--r--tools/gn/command_help.cc6
-rw-r--r--tools/gn/copy_target_generator.cc34
-rw-r--r--tools/gn/escape.cc33
-rw-r--r--tools/gn/escape.h26
-rw-r--r--tools/gn/escape_unittest.cc42
-rw-r--r--tools/gn/file_template.cc180
-rw-r--r--tools/gn/file_template.h75
-rw-r--r--tools/gn/file_template_unittest.cc44
-rw-r--r--tools/gn/functions_target.cc93
-rw-r--r--tools/gn/gn.gyp2
-rw-r--r--tools/gn/ninja_copy_target_writer.cc42
-rw-r--r--tools/gn/ninja_copy_target_writer_unittest.cc57
-rw-r--r--tools/gn/ninja_script_target_writer.cc109
-rw-r--r--tools/gn/ninja_script_target_writer.h31
-rw-r--r--tools/gn/ninja_script_target_writer_unittest.cc132
-rw-r--r--tools/gn/ninja_target_writer.cc14
-rw-r--r--tools/gn/ninja_target_writer.h4
-rw-r--r--tools/gn/scope_per_file_provider.cc6
-rw-r--r--tools/gn/script_target_generator.cc40
-rw-r--r--tools/gn/script_target_generator.h1
-rw-r--r--tools/gn/script_values.h2
-rw-r--r--tools/gn/secondary/third_party/icu/BUILD.gn2
-rw-r--r--tools/gn/target.h3
-rw-r--r--tools/gn/target_generator.cc22
-rw-r--r--tools/gn/target_generator.h1
-rw-r--r--tools/gn/variables.cc22
-rw-r--r--tools/gn/variables.h4
29 files changed, 854 insertions, 189 deletions
diff --git a/tools/gn/BUILD.gn b/tools/gn/BUILD.gn
index eb2e9e3..0b46d5a 100644
--- a/tools/gn/BUILD.gn
+++ b/tools/gn/BUILD.gn
@@ -159,7 +159,9 @@ test("gn_unittests") {
"function_to_build_path_unittest.cc",
"input_conversion_unittest.cc",
"label_unittest.cc",
+ "ninja_copy_target_writer_unittest.cc",
"ninja_helper_unittest.cc",
+ "ninja_script_target_writer_unittest.cc",
"parser_unittest.cc",
"path_output_unittest.cc",
"pattern_unittest.cc",
diff --git a/tools/gn/binary_target_generator.cc b/tools/gn/binary_target_generator.cc
index 001daef..b8d2d6e 100644
--- a/tools/gn/binary_target_generator.cc
+++ b/tools/gn/binary_target_generator.cc
@@ -25,10 +25,24 @@ void BinaryTargetGenerator::DoRun() {
target_->set_output_type(output_type_);
FillOutputName();
+ if (err_->has_error())
+ return;
+
FillExternal();
+ if (err_->has_error())
+ return;
+
FillSources();
+ if (err_->has_error())
+ return;
+
FillSourcePrereqs();
+ if (err_->has_error())
+ return;
+
FillConfigs();
+ if (err_->has_error())
+ return;
// Config values (compiler flags, etc.) set directly on this target.
ConfigValuesGenerator gen(&target_->config_values(), scope_,
diff --git a/tools/gn/command_help.cc b/tools/gn/command_help.cc
index 80065a8..b6780dc 100644
--- a/tools/gn/command_help.cc
+++ b/tools/gn/command_help.cc
@@ -8,6 +8,7 @@
#include "tools/gn/args.h"
#include "tools/gn/commands.h"
#include "tools/gn/err.h"
+#include "tools/gn/file_template.h"
#include "tools/gn/functions.h"
#include "tools/gn/input_conversion.h"
#include "tools/gn/pattern.h"
@@ -88,6 +89,7 @@ void PrintToplevelHelp() {
PrintShortHelp(
"input_conversion: Processing input from exec_script and read_file.");
PrintShortHelp("patterns: How to use patterns.");
+ PrintShortHelp("source_expansion: Map sources to outputs for scripts.");
}
} // namespace
@@ -161,6 +163,10 @@ int RunHelp(const std::vector<std::string>& args) {
OutputString(kPattern_Help);
return 0;
}
+ if (args[0] == "source_expansion") {
+ OutputString(kSourceExpansion_Help);
+ return 0;
+ }
// No help on this.
Err(Location(), "No help on \"" + args[0] + "\".").PrintToStdout();
diff --git a/tools/gn/copy_target_generator.cc b/tools/gn/copy_target_generator.cc
index 18ecc3f..f02d4a7 100644
--- a/tools/gn/copy_target_generator.cc
+++ b/tools/gn/copy_target_generator.cc
@@ -23,26 +23,28 @@ void CopyTargetGenerator::DoRun() {
target_->set_output_type(Target::COPY_FILES);
FillExternal();
+ if (err_->has_error())
+ return;
FillSources();
- FillDestDir();
-
- SetToolchainDependency();
-}
+ if (err_->has_error())
+ return;
+ FillOutputs();
+ if (err_->has_error())
+ return;
-void CopyTargetGenerator::FillDestDir() {
- // Destdir is required for all targets that use it.
- const Value* value = scope_->GetValue("destdir", true);
- if (!value) {
- *err_ = Err(function_token_, "This target type requires a \"destdir\".");
+ if (target_->sources().empty()) {
+ *err_ = Err(function_token_, "Empty sources for copy command.",
+ "You have to specify at least one file to copy in the \"sources\".");
return;
}
- if (!value->VerifyTypeIs(Value::STRING, err_))
+ if (target_->script_values().outputs().size() != 1) {
+ *err_ = Err(function_token_, "Copy command must have exactly one output.",
+ "You must specify exactly one value in the \"outputs\" array for the "
+ "destination of the copy\n(see \"gn help copy\"). If there are "
+ "multiple sources to copy, use source expansion\n(see \"gn help "
+ "source_expansion\").");
return;
+ }
- if (!EnsureStringIsInOutputDir(
- GetBuildSettings()->build_dir(),
- value->string_value(), *value, err_))
- return;
- target_->set_destdir(SourceDir(value->string_value()));
+ SetToolchainDependency();
}
-
diff --git a/tools/gn/escape.cc b/tools/gn/escape.cc
index 5fc7b6f..f00bb63 100644
--- a/tools/gn/escape.cc
+++ b/tools/gn/escape.cc
@@ -11,28 +11,34 @@ namespace {
template<typename DestString>
void EscapeStringToString(const base::StringPiece& str,
const EscapeOptions& options,
- DestString* dest) {
+ DestString* dest,
+ bool* needed_quoting) {
bool used_quotes = false;
for (size_t i = 0; i < str.size(); i++) {
- if (str[i] == '$' && options.mode == ESCAPE_NINJA) {
+ if (str[i] == '$' && (options.mode & ESCAPE_NINJA)) {
// Escape dollars signs since ninja treats these specially.
dest->push_back('$');
dest->push_back('$');
- } else if (str[i] == '"' && options.mode == ESCAPE_SHELL) {
+ } else if (str[i] == '"' && (options.mode & ESCAPE_SHELL)) {
// Escape quotes with backslashes for the command-line (Ninja doesn't
// care).
dest->push_back('\\');
dest->push_back('"');
} else if (str[i] == ' ') {
- if (options.mode == ESCAPE_NINJA) {
- // For ninja just escape spaces with $.
+ if (options.mode & ESCAPE_NINJA) {
+ // For Ninja just escape spaces with $.
dest->push_back('$');
- } else if (options.mode == ESCAPE_SHELL && !options.inhibit_quoting) {
+ }
+ if (options.mode & ESCAPE_SHELL) {
// For the shell, quote the whole string.
- if (!used_quotes) {
- used_quotes = true;
- dest->insert(dest->begin(), '"');
+ if (needed_quoting)
+ *needed_quoting = true;
+ if (!options.inhibit_quoting) {
+ if (!used_quotes) {
+ used_quotes = true;
+ dest->insert(dest->begin(), '"');
+ }
}
}
dest->push_back(' ');
@@ -41,7 +47,7 @@ void EscapeStringToString(const base::StringPiece& str,
// Convert slashes on Windows if requested.
dest->push_back('\\');
#else
- } else if (str[i] == '\\' && options.mode == ESCAPE_SHELL) {
+ } else if (str[i] == '\\' && (options.mode & ESCAPE_SHELL)) {
// For non-Windows shell, escape backslashes.
dest->push_back('\\');
dest->push_back('\\');
@@ -58,10 +64,11 @@ void EscapeStringToString(const base::StringPiece& str,
} // namespace
std::string EscapeString(const base::StringPiece& str,
- const EscapeOptions& options) {
+ const EscapeOptions& options,
+ bool* needed_quoting) {
std::string result;
result.reserve(str.size() + 4); // Guess we'll add a couple of extra chars.
- EscapeStringToString(str, options, &result);
+ EscapeStringToString(str, options, &result, needed_quoting);
return result;
}
@@ -71,7 +78,7 @@ void EscapeStringToStream(std::ostream& out,
// Escape to a stack buffer and then write out to the stream.
base::StackVector<char, 256> result;
result->reserve(str.size() + 4); // Guess we'll add a couple of extra chars.
- EscapeStringToString(str, options, &result.container());
+ EscapeStringToString(str, options, &result.container(), NULL);
if (!result->empty())
out.write(result->data(), result->size());
}
diff --git a/tools/gn/escape.h b/tools/gn/escape.h
index 1791161..ce0d471 100644
--- a/tools/gn/escape.h
+++ b/tools/gn/escape.h
@@ -9,14 +9,18 @@
#include "base/strings/string_piece.h"
-// TODO(brettw) we may need to make this a bitfield. If we want to write a
-// shell command in a ninja file, we need the shell characters to be escaped,
-// and THEN the ninja characters. Or maybe we require the caller to do two
-// passes.
enum EscapingMode {
- ESCAPE_NONE, // No escaping.
- ESCAPE_NINJA, // Ninja string escaping.
- ESCAPE_SHELL, // Shell string escaping.
+ // No escaping.
+ ESCAPE_NONE,
+
+ // Ninja string escaping.
+ ESCAPE_NINJA = 1,
+
+ // Shell string escaping.
+ ESCAPE_SHELL = 2,
+
+ // For writing shell commands to ninja files.
+ ESCAPE_NINJA_SHELL = ESCAPE_NINJA | ESCAPE_SHELL
};
struct EscapeOptions {
@@ -41,8 +45,14 @@ struct EscapeOptions {
};
// Escapes the given input, returnining the result.
+//
+// If needed_quoting is non-null, whether the string was or should have been
+// (if inhibit_quoting was set) quoted will be written to it. This value should
+// be initialized to false by the caller and will be written to only if it's
+// true (the common use-case is for chaining calls).
std::string EscapeString(const base::StringPiece& str,
- const EscapeOptions& options);
+ const EscapeOptions& options,
+ bool* needed_quoting);
// Same as EscapeString but writes the results to the given stream, saving a
// copy.
diff --git a/tools/gn/escape_unittest.cc b/tools/gn/escape_unittest.cc
index a7c19b3..a637e87 100644
--- a/tools/gn/escape_unittest.cc
+++ b/tools/gn/escape_unittest.cc
@@ -2,3 +2,45 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "testing/gtest/include/gtest/gtest.h"
+#include "tools/gn/escape.h"
+
+TEST(Escape, UsedQuotes) {
+ EscapeOptions shell_options;
+ shell_options.mode = ESCAPE_SHELL;
+
+ EscapeOptions ninja_options;
+ ninja_options.mode = ESCAPE_NINJA;
+
+ EscapeOptions ninja_shell_options;
+ ninja_shell_options.mode = ESCAPE_NINJA_SHELL;
+
+ // Shell escaping with quoting inhibited.
+ bool used_quotes = false;
+ shell_options.inhibit_quoting = true;
+ EXPECT_EQ("foo bar", EscapeString("foo bar", shell_options, &used_quotes));
+ EXPECT_TRUE(used_quotes);
+
+ // Shell escaping with regular quoting.
+ used_quotes = false;
+ shell_options.inhibit_quoting = false;
+ EXPECT_EQ("\"foo bar\"",
+ EscapeString("foo bar", shell_options, &used_quotes));
+ EXPECT_TRUE(used_quotes);
+
+ // Ninja shell escaping should be the same.
+ used_quotes = false;
+ EXPECT_EQ("\"foo$ bar\"",
+ EscapeString("foo bar", ninja_shell_options, &used_quotes));
+ EXPECT_TRUE(used_quotes);
+
+ // Ninja escaping shouldn't use quoting.
+ used_quotes = false;
+ EXPECT_EQ("foo$ bar", EscapeString("foo bar", ninja_options, &used_quotes));
+ EXPECT_FALSE(used_quotes);
+
+ // Used quotes not reset if it's already true.
+ used_quotes = true;
+ EscapeString("foo", ninja_options, &used_quotes);
+ EXPECT_TRUE(used_quotes);
+}
diff --git a/tools/gn/file_template.cc b/tools/gn/file_template.cc
index 8b5d09f..62235c4 100644
--- a/tools/gn/file_template.cc
+++ b/tools/gn/file_template.cc
@@ -4,16 +4,87 @@
#include "tools/gn/file_template.h"
+#include <algorithm>
+#include <iostream>
+
+#include "tools/gn/escape.h"
#include "tools/gn/filesystem_utils.h"
const char FileTemplate::kSource[] = "{{source}}";
const char FileTemplate::kSourceNamePart[] = "{{source_name_part}}";
+const char FileTemplate::kSourceFilePart[] = "{{source_file_part}}";
+
+const char kSourceExpansion_Help[] =
+ "How Source Expansion Works\n"
+ "\n"
+ " Source expansion is used for the custom script and copy target types\n"
+ " to map source file names to output file names or arguments.\n"
+ "\n"
+ " To perform source expansion in the outputs, GN maps every entry in the\n"
+ " sources to every entry in the outputs list, producing the cross\n"
+ " product of all combinations, expanding placeholders (see below).\n"
+ "\n"
+ " Source expansion in the args works similarly, but performing the\n"
+ " placeholder substitution produces a different set of arguments for\n"
+ " each invocation of the script.\n"
+ "\n"
+ " If no placeholders are found, the outputs or args list will be treated\n"
+ " as a static list of literal file names that do not depend on the\n"
+ " sources.\n"
+ "\n"
+ " See \"gn help copy\" and \"gn help custom\" for more on how this is\n"
+ " applied.\n"
+ "\n"
+ "Placeholders:\n"
+ "\n"
+ " {{source}}\n"
+ " The name of the source file relative to the root build output\n"
+ " directory (which is the current directory when running compilers\n"
+ " and scripts). This will generally be used for specifying inputs\n"
+ " to a script in the \"args\" variable.\n"
+ "\n"
+ " {{source_file_part}}\n"
+ " The file part of the source including the extension. For the\n"
+ " source \"foo/bar.txt\" the source file part will be \"bar.txt\".\n"
+ "\n"
+ " {{source_name_part}}\n"
+ " The filename part of the source file with no directory or\n"
+ " extension. This will generally be used for specifying a\n"
+ " transformation from a soruce file to a destination file with the\n"
+ " same name but different extension. For the source \"foo/bar.txt\"\n"
+ " the source name part will be \"bar\".\n"
+ "\n"
+ "Examples:\n"
+ "\n"
+ " Non-varying outputs:\n"
+ " script(\"hardcoded_outputs\") {\n"
+ " sources = [ \"input1.idl\", \"input2.idl\" ]\n"
+ " outputs = [ \"$target_out_dir/output1.dat\",\n"
+ " \"$target_out_dir/output2.dat\" ]\n"
+ " }\n"
+ " The outputs in this case will be the two literal files given.\n"
+ "\n"
+ " Varying outputs:\n"
+ " script(\"varying_outputs\") {\n"
+ " sources = [ \"input1.idl\", \"input2.idl\" ]\n"
+ " outputs = [ \"$target_out_dir/{{source_name_part}}.h\",\n"
+ " \"$target_out_dir/{{source_name_part}}.cc\" ]\n"
+ " }\n"
+ " Performing source expansion will result in the following output names:\n"
+ " //out/Debug/obj/mydirectory/input1.h\n"
+ " //out/Debug/obj/mydirectory/input1.cc\n"
+ " //out/Debug/obj/mydirectory/input2.h\n"
+ " //out/Debug/obj/mydirectory/input2.cc\n";
-FileTemplate::FileTemplate(const Value& t, Err* err) {
+FileTemplate::FileTemplate(const Value& t, Err* err)
+ : has_substitutions_(false) {
+ std::fill(types_required_, &types_required_[Subrange::NUM_TYPES], false);
ParseInput(t, err);
}
-FileTemplate::FileTemplate(const std::vector<std::string>& t) {
+FileTemplate::FileTemplate(const std::vector<std::string>& t)
+ : has_substitutions_(false) {
+ std::fill(types_required_, &types_required_[Subrange::NUM_TYPES], false);
for (size_t i = 0; i < t.size(); i++)
ParseOneTemplateString(t[i]);
}
@@ -21,6 +92,11 @@ FileTemplate::FileTemplate(const std::vector<std::string>& t) {
FileTemplate::~FileTemplate() {
}
+bool FileTemplate::IsTypeUsed(Subrange::Type type) const {
+ DCHECK(type > Subrange::LITERAL && type < Subrange::NUM_TYPES);
+ return types_required_[type];
+}
+
void FileTemplate::Apply(const Value& sources,
const ParseNode* origin,
std::vector<Value>* dest,
@@ -48,10 +124,10 @@ void FileTemplate::ApplyString(const std::string& str,
// Compute all substitutions needed so we can just do substitutions below.
// We skip the LITERAL one since that varies each time.
std::string subst[Subrange::NUM_TYPES];
- if (types_required_[Subrange::SOURCE])
- subst[Subrange::SOURCE] = str;
- if (types_required_[Subrange::NAME_PART])
- subst[Subrange::NAME_PART] = FindFilenameNoExtension(&str).as_string();
+ for (int i = 1; i < Subrange::NUM_TYPES; i++) {
+ if (types_required_[i])
+ subst[i] = GetSubstitution(str, static_cast<Subrange::Type>(i));
+ }
output->resize(templates_.container().size());
for (size_t template_i = 0;
@@ -68,6 +144,90 @@ void FileTemplate::ApplyString(const std::string& str,
}
}
+void FileTemplate::WriteWithNinjaExpansions(std::ostream& out) const {
+ EscapeOptions escape_options;
+ escape_options.mode = ESCAPE_NINJA_SHELL;
+ escape_options.inhibit_quoting = true;
+
+ for (size_t template_i = 0;
+ template_i < templates_.container().size(); template_i++) {
+ out << " "; // Separate args with spaces.
+
+ const Template& t = templates_[template_i];
+
+ // Escape each subrange into a string. Since we're writing out Ninja
+ // variables, we can't quote the whole thing, so we write in pieces, only
+ // escaping the literals, and then quoting the whole thing at the end if
+ // necessary.
+ bool needs_quoting = false;
+ std::string item_str;
+ for (size_t subrange_i = 0; subrange_i < t.container().size();
+ subrange_i++) {
+ if (t[subrange_i].type == Subrange::LITERAL) {
+ item_str.append(EscapeString(t[subrange_i].literal, escape_options,
+ &needs_quoting));
+ } else {
+ // Don't escape this since we need to preserve the $.
+ item_str.append("${");
+ item_str.append(GetNinjaVariableNameForType(t[subrange_i].type));
+ item_str.append("}");
+ }
+ }
+
+ if (needs_quoting) {
+ // Need to shell quote the whole string.
+ out << '"' << item_str << '"';
+ } else {
+ out << item_str;
+ }
+ }
+}
+
+void FileTemplate::WriteNinjaVariablesForSubstitution(
+ std::ostream& out,
+ const std::string& source,
+ const EscapeOptions& escape_options) const {
+ for (int i = 1; i < Subrange::NUM_TYPES; i++) {
+ if (types_required_[i]) {
+ Subrange::Type type = static_cast<Subrange::Type>(i);
+ out << " " << GetNinjaVariableNameForType(type) << " = ";
+ EscapeStringToStream(out, GetSubstitution(source, type), escape_options);
+ out << std::endl;
+ }
+ }
+}
+
+// static
+const char* FileTemplate::GetNinjaVariableNameForType(Subrange::Type type) {
+ switch (type) {
+ case Subrange::SOURCE:
+ return "source";
+ case Subrange::NAME_PART:
+ return "source_name_part";
+ case Subrange::FILE_PART:
+ return "source_file_part";
+ default:
+ NOTREACHED();
+ }
+ return "";
+}
+
+// static
+std::string FileTemplate::GetSubstitution(const std::string& source,
+ Subrange::Type type) {
+ switch (type) {
+ case Subrange::SOURCE:
+ return source;
+ case Subrange::NAME_PART:
+ return FindFilenameNoExtension(&source).as_string();
+ case Subrange::FILE_PART:
+ return FindFilename(&source).as_string();
+ default:
+ NOTREACHED();
+ }
+ return std::string();
+}
+
void FileTemplate::ParseInput(const Value& value, Err* err) {
switch (value.type()) {
case Value::STRING:
@@ -108,12 +268,20 @@ void FileTemplate::ParseOneTemplateString(const std::string& str) {
if (str.compare(next, arraysize(kSource) - 1, kSource) == 0) {
t.container().push_back(Subrange(Subrange::SOURCE));
types_required_[Subrange::SOURCE] = true;
+ has_substitutions_ = true;
cur = next + arraysize(kSource) - 1;
} else if (str.compare(next, arraysize(kSourceNamePart) - 1,
kSourceNamePart) == 0) {
t.container().push_back(Subrange(Subrange::NAME_PART));
types_required_[Subrange::NAME_PART] = true;
+ has_substitutions_ = true;
cur = next + arraysize(kSourceNamePart) - 1;
+ } else if (str.compare(next, arraysize(kSourceFilePart) - 1,
+ kSourceFilePart) == 0) {
+ t.container().push_back(Subrange(Subrange::FILE_PART));
+ types_required_[Subrange::FILE_PART] = true;
+ has_substitutions_ = true;
+ cur = next + arraysize(kSourceFilePart) - 1;
} else {
// If it's not a match, treat it like a one-char literal (this will be
// rare, so it's not worth the bother to add to the previous literal) so
diff --git a/tools/gn/file_template.h b/tools/gn/file_template.h
index 25d2251..8fddad2 100644
--- a/tools/gn/file_template.h
+++ b/tools/gn/file_template.h
@@ -5,20 +5,44 @@
#ifndef TOOLS_GN_FILE_TEMPLATE_H_
#define TOOLS_GN_FILE_TEMPLATE_H_
+#include <iosfwd>
+
#include "base/basictypes.h"
#include "base/containers/stack_container.h"
#include "tools/gn/err.h"
#include "tools/gn/value.h"
+struct EscapeOptions;
class ParseNode;
+extern const char kSourceExpansion_Help[];
+
+// A FileTemplate object implements source expansion for a given "template"
+// (either outputs or args, depending on the target type).
+//
+// There are two ways you can use this. You can make a template and then
+// apply a source to it to get a list of outputs manually. Or you can do the
+// actual substitution in Ninja, writing the arguments in a rule and using
+// variables in build statements to invoke the rule with the right
+// substitutions.
class FileTemplate {
public:
struct Subrange {
enum Type {
LITERAL = 0,
+
+ // {{source}} -> expands to be the source file name relative to the build
+ // root dir.
SOURCE,
+
+ // {{source_name_part}} -> file name without extension or directory.
+ // Maps "foo/bar.txt" to "bar".
NAME_PART,
+
+ // {{source_file_part}} -> file name including extension but no directory.
+ // Maps "foo/bar.txt" to "bar.txt".
+ FILE_PART,
+
NUM_TYPES // Must be last
};
Subrange(Type t, const std::string& l = std::string())
@@ -38,6 +62,12 @@ class FileTemplate {
FileTemplate(const std::vector<std::string>& t);
~FileTemplate();
+ // Returns true if the given substitution type is used by this template.
+ bool IsTypeUsed(Subrange::Type type) const;
+
+ // Returns true if there are any substitutions.
+ bool has_substitutions() const { return has_substitutions_; }
+
// Applies this template to the given list of sources, appending all
// results to the given dest list. The sources must be a list for the
// one that takes a value as an input, otherwise the given error will be set.
@@ -48,9 +78,48 @@ class FileTemplate {
void ApplyString(const std::string& input,
std::vector<std::string>* output) const;
- // Known template types.
+ // Writes a string representing the template with Ninja variables for the
+ // substitutions, and the literals escaped for Ninja consumption.
+ //
+ // For example, if the input is "foo{{source_name_part}}bar" this will write
+ // foo${source_name_part}bar. If there are multiple templates (we were
+ // constucted with a list of more than one item) then the values will be
+ // separated by spaces.
+ //
+ // If this template is nonempty, we will first print out a space to separate
+ // it from the previous command.
+ //
+ // The names used for the Ninja variables will be the same ones used by
+ // WriteNinjaVariablesForSubstitution. You would use this to define the Ninja
+ // rule, and then define the variables to substitute for each file using
+ // WriteNinjaVariablesForSubstitution.
+ void WriteWithNinjaExpansions(std::ostream& out) const;
+
+ // Writes to the given stream the variable declarations for extracting the
+ // required parts of the given source file string. The results will be
+ // indented two spaces.
+ //
+ // This is used to set up a build statement to invoke a rule where the rule
+ // contains a representation of this file template to be expanded by Ninja
+ // (see GetWithNinjaExpansions).
+ void WriteNinjaVariablesForSubstitution(
+ std::ostream& out,
+ const std::string& source,
+ const EscapeOptions& escape_options) const;
+
+ // Returns the Ninja variable name used by the above Ninja functions to
+ // substitute for the given type.
+ static const char* GetNinjaVariableNameForType(Subrange::Type type);
+
+ // Extracts the given type of substitution from the given source. The source
+ // should be the file name relative to the output directory.
+ static std::string GetSubstitution(const std::string& source,
+ Subrange::Type type);
+
+ // Known template types, these include the "{{ }}"
static const char kSource[];
static const char kSourceNamePart[];
+ static const char kSourceFilePart[];
private:
typedef base::StackVector<Subrange, 8> Template;
@@ -68,7 +137,9 @@ class FileTemplate {
// to a given source file.
bool types_required_[Subrange::NUM_TYPES];
- DISALLOW_COPY_AND_ASSIGN(FileTemplate);
+ // Set when any of the types_required_ is true. Otherwise, everythins is a
+ // literal (a common case so we can optimize some code paths).
+ bool has_substitutions_;
};
#endif // TOOLS_GN_FILE_TEMPLATE_H_
diff --git a/tools/gn/file_template_unittest.cc b/tools/gn/file_template_unittest.cc
index 47c84f0..e05bda0 100644
--- a/tools/gn/file_template_unittest.cc
+++ b/tools/gn/file_template_unittest.cc
@@ -2,13 +2,17 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <sstream>
+
#include "testing/gtest/include/gtest/gtest.h"
+#include "tools/gn/escape.h"
#include "tools/gn/file_template.h"
TEST(FileTemplate, Static) {
std::vector<std::string> templates;
templates.push_back("something_static");
FileTemplate t(templates);
+ EXPECT_FALSE(t.has_substitutions());
std::vector<std::string> result;
t.ApplyString("", &result);
@@ -25,6 +29,7 @@ TEST(FileTemplate, Typical) {
templates.push_back("foo/{{source_name_part}}.cc");
templates.push_back("foo/{{source_name_part}}.h");
FileTemplate t(templates);
+ EXPECT_TRUE(t.has_substitutions());
std::vector<std::string> result;
t.ApplyString("sources/ha.idl", &result);
@@ -37,9 +42,48 @@ TEST(FileTemplate, Weird) {
std::vector<std::string> templates;
templates.push_back("{{{source}}{{source}}{{");
FileTemplate t(templates);
+ EXPECT_TRUE(t.has_substitutions());
std::vector<std::string> result;
t.ApplyString("foo/lalala.c", &result);
ASSERT_EQ(1u, result.size());
EXPECT_EQ("{foo/lalala.cfoo/lalala.c{{", result[0]);
}
+
+TEST(FileTemplate, NinjaExpansions) {
+ std::vector<std::string> templates;
+ templates.push_back("-i");
+ templates.push_back("{{source}}");
+ templates.push_back("--out=foo bar\"{{source_name_part}}\".o");
+
+ FileTemplate t(templates);
+
+ std::ostringstream out;
+ t.WriteWithNinjaExpansions(out);
+
+ // The third argument should get quoted since it contains a space, and the
+ // embedded quotes should get shell escaped.
+ EXPECT_EQ(
+ " -i ${source} \"--out=foo$ bar\\\"${source_name_part}\\\".o\"",
+ out.str());
+}
+
+TEST(FileTemplate, NinjaVariables) {
+ std::vector<std::string> templates;
+ templates.push_back("-i");
+ templates.push_back("{{source}}");
+ templates.push_back("--out=foo bar\"{{source_name_part}}\".o");
+
+ FileTemplate t(templates);
+
+ std::ostringstream out;
+ EscapeOptions options;
+ options.mode = ESCAPE_NINJA_SHELL;
+ t.WriteNinjaVariablesForSubstitution(out, "../../foo/bar.txt", options);
+
+ // Just the variables used above should be written.
+ EXPECT_EQ(
+ " source = ../../foo/bar.txt\n"
+ " source_name_part = bar\n",
+ out.str());
+}
diff --git a/tools/gn/functions_target.cc b/tools/gn/functions_target.cc
index 3d163110..cfc4606 100644
--- a/tools/gn/functions_target.cc
+++ b/tools/gn/functions_target.cc
@@ -53,7 +53,20 @@ Value ExecuteGenericTarget(const char* target_type,
const char kComponent[] = "component";
const char kComponent_Help[] =
- "TODO(brettw) write this.";
+ "component: Declare a component target.\n"
+ "\n"
+ " A component is either a shared library or a static library depending\n"
+ " on the component mode. This allows a project to separate out a build\n"
+ " into shared libraries for faster devlopment (link time is reduced)\n"
+ " but to switch to a static build for releases (for better performance).\n"
+ "\n"
+ " To use this function you must set the value of the \"component_mode\n"
+ " variable to the string \"shared_library\" or \"static_library\". It is\n"
+ " an error to call \"component\" without defining the mode (typically\n"
+ " this is done in the master build configuration file).\n"
+ "\n"
+ " See \"gn help shared_library\" and \"gn help static_library\" for\n"
+ " more details.\n";
Value RunComponent(Scope* scope,
const FunctionCallNode* function,
@@ -99,7 +112,42 @@ Value RunComponent(Scope* scope,
const char kCopy[] = "copy";
const char kCopy_Help[] =
- "TODO(brettw) write this.";
+ "copy: Declare a target that copies files.\n"
+ "\n"
+ "File name handling\n"
+ "\n"
+ " All output files must be inside the output directory of the build.\n"
+ " You would generally use |$target_out_dir| or |$target_gen_dir| to\n"
+ " reference the output or generated intermediate file directories,\n"
+ " respectively.\n"
+ "\n"
+ " Both \"sources\" and \"outputs\" must be specified. Sources can\n"
+ " as many files as you want, but there can only be one item in the\n"
+ " outputs list (plural is used for the name for consistency with\n"
+ " other target types).\n"
+ "\n"
+ " If there is more than one source file, your output name should specify\n"
+ " a mapping from each source files to output file names using source\n"
+ " expansion (see \"gn help source_expansion\"). The placeholders will\n"
+ " will look like \"{{source_name_part}}\", for example.\n"
+ "\n"
+ "Examples:\n"
+ " # Write a rule that copies a checked-in DLL to the output directory.\n"
+ " copy(\"mydll\") {\n"
+ " sources = [ \"mydll.dll\" ]\n"
+ " outputs = [ \"$target_out_dir/mydll.dll\" ]\n"
+ " }\n"
+ "\n"
+ " # Write a rule to copy several files to the target generated files\n"
+ " # directory.\n"
+ " copy(\"myfiles\") {\n"
+ " sources = [ \"data1.dat\", \"data2.dat\", \"data3.dat\" ]\n"
+ "\n"
+ " # Use source expansion to generate output files with the\n"
+ " # corresponding file names in the gen dir. This will just copy each\n"
+ " # file.\n"
+ " outputs = [ \"$target_gen_dir/{{source_file_part}}\" ]\n"
+ " }\n";
Value RunCopy(const FunctionCallNode* function,
const std::vector<Value>& args,
@@ -145,30 +193,22 @@ const char kCustom_Help[] =
" |source_prereqs| variable and your |outputs| variable should just list\n"
" all outputs.\n"
"\n"
- "Variables:\n"
+ "File name handling\n"
"\n"
- " args, deps, outputs, script*, source_prereqs, sources\n"
- " * = required\n"
- "\n"
- " There are some special substrings that will be searched for when\n"
- " processing some variables:\n"
+ " All output files must be inside the output directory of the build.\n"
+ " You would generally use |$target_out_dir| or |$target_gen_dir| to\n"
+ " reference the output or generated intermediate file directories,\n"
+ " respectively.\n"
"\n"
- " {{source}}\n"
- " Expanded in |args|, this is the name of the source file relative\n"
- " to the build directory This is how you specify the current input\n"
- " file to your script.\n"
+ " You can specify a mapping from source files to output files using\n"
+ " source expansion (see \"gn help source_expansion\"). The placeholders\n"
+ " will look like \"{{source}}\", for example, and can appear in\n"
+ " either the outputs or the args lists.\n"
"\n"
- " {{source_name_part}}\n"
- " Expanded in |args| and |outputs|, this is just the filename part\n"
- " of the current source file with no directory or extension. This\n"
- " is how you specify a name transformation to the output. Normally\n"
- " you would write an output as\n"
- " \"$target_output_dir/{{source_name_part}}.o\".\n"
+ "Variables:\n"
"\n"
- " All |outputs| files must be inside the output directory of the build.\n"
- " You would generally use |$target_output_dir| or |$target_gen_dir| to\n"
- " reference the output or generated intermediate file directories,\n"
- " respectively.\n"
+ " args, deps, outputs, script*, source_prereqs, sources\n"
+ " * = required\n"
"\n"
"Examples:\n"
"\n"
@@ -315,7 +355,14 @@ Value RunStaticLibrary(Scope* scope,
const char kTest[] = "test";
const char kTest_Help[] =
- "TODO(brettw) write this.";
+ "test: Declares a test target.\n"
+ "\n"
+ " This is like an executable target, but is named differently to make\n"
+ " the purpose of the target more obvious. It's possible in the future\n"
+ " we can do some enhancements like \"list all of the tests in a given\n"
+ " directory\".\n"
+ "\n"
+ " See \"gn help executable\" for usage.\n";
Value RunTest(Scope* scope,
const FunctionCallNode* function,
diff --git a/tools/gn/gn.gyp b/tools/gn/gn.gyp
index 1153e53..4411c52 100644
--- a/tools/gn/gn.gyp
+++ b/tools/gn/gn.gyp
@@ -170,6 +170,8 @@
'input_conversion_unittest.cc',
'label_unittest.cc',
'ninja_helper_unittest.cc',
+ 'ninja_copy_target_writer_unittest.cc',
+ 'ninja_script_target_writer_unittest.cc',
'parser_unittest.cc',
'path_output_unittest.cc',
'pattern_unittest.cc',
diff --git a/tools/gn/ninja_copy_target_writer.cc b/tools/gn/ninja_copy_target_writer.cc
index d4a65ab..aae8e6d 100644
--- a/tools/gn/ninja_copy_target_writer.cc
+++ b/tools/gn/ninja_copy_target_writer.cc
@@ -5,6 +5,7 @@
#include "tools/gn/ninja_copy_target_writer.h"
#include "base/strings/string_util.h"
+#include "tools/gn/file_template.h"
#include "tools/gn/string_utils.h"
NinjaCopyTargetWriter::NinjaCopyTargetWriter(const Target* target,
@@ -16,32 +17,24 @@ NinjaCopyTargetWriter::~NinjaCopyTargetWriter() {
}
void NinjaCopyTargetWriter::Run() {
- // The dest dir should be inside the output dir so we can just remove the
- // prefix and get ninja-relative paths.
- const std::string& output_dir =
- settings_->build_settings()->build_dir().value();
- const std::string& dest_dir = target_->destdir().value();
- DCHECK(StartsWithASCII(dest_dir, output_dir, true));
- std::string relative_dest_dir(&dest_dir[output_dir.size()],
- dest_dir.size() - output_dir.size());
+ CHECK(target_->script_values().outputs().size() == 1);
+ FileTemplate output_template(GetOutputTemplate());
- const Target::FileList& sources = target_->sources();
- std::vector<OutputFile> dest_files;
- dest_files.reserve(sources.size());
+ std::vector<OutputFile> output_files;
- // Write out rules for each file copied.
- for (size_t i = 0; i < sources.size(); i++) {
- const SourceFile& input_file = sources[i];
+ for (size_t i = 0; i < target_->sources().size(); i++) {
+ const SourceFile& input_file = target_->sources()[i];
- // The files should have the same name but in the dest dir.
- base::StringPiece name_part = FindFilename(&input_file.value());
- OutputFile dest_file(relative_dest_dir);
- AppendStringPiece(&dest_file.value(), name_part);
+ // Make the output file from the template.
+ std::vector<std::string> template_result;
+ output_template.ApplyString(input_file.value(), &template_result);
+ CHECK(template_result.size() == 1);
+ OutputFile output_file(template_result[0]);
- dest_files.push_back(dest_file);
+ output_files.push_back(output_file);
out_ << "build ";
- path_output_.WriteFile(out_, dest_file);
+ path_output_.WriteFile(out_, output_file);
out_ << ": copy ";
path_output_.WriteFile(out_, input_file);
out_ << std::endl;
@@ -53,14 +46,9 @@ void NinjaCopyTargetWriter::Run() {
out_ << ": "
<< helper_.GetRulePrefix(target_->settings()->toolchain())
<< "stamp";
- for (size_t i = 0; i < dest_files.size(); i++) {
+ for (size_t i = 0; i < output_files.size(); i++) {
out_ << " ";
- path_output_.WriteFile(out_, dest_files[i]);
+ path_output_.WriteFile(out_, output_files[i]);
}
out_ << std::endl;
-
- // TODO(brettw) need some kind of stamp file for depending on this, as well
- // as order_only=prebuild.
- // TODO(brettw) also need to write out the dependencies of this rule (maybe
- // we're copying output files around).
}
diff --git a/tools/gn/ninja_copy_target_writer_unittest.cc b/tools/gn/ninja_copy_target_writer_unittest.cc
new file mode 100644
index 0000000..eb913d9
--- /dev/null
+++ b/tools/gn/ninja_copy_target_writer_unittest.cc
@@ -0,0 +1,57 @@
+// Copyright (c) 2013 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 <sstream>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "tools/gn/file_template.h"
+#include "tools/gn/ninja_copy_target_writer.h"
+#include "tools/gn/test_with_scope.h"
+
+TEST(NinjaCopyTargetWriter, Run) {
+ TestWithScope setup;
+ setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/"));
+ Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+ target.set_output_type(Target::COPY_FILES);
+
+ target.sources().push_back(SourceFile("//foo/input1.txt"));
+ target.sources().push_back(SourceFile("//foo/input2.txt"));
+
+ target.script_values().outputs().push_back(
+ SourceFile("//out/Debug/{{source_name_part}}.out"));
+
+ // Posix.
+ {
+ setup.settings()->set_target_os(Settings::LINUX);
+
+ std::ostringstream out;
+ NinjaCopyTargetWriter writer(&target, out);
+ writer.Run();
+
+ const char expected_linux[] =
+ "build input1.out: copy ../../foo/input1.txt\n"
+ "build input2.out: copy ../../foo/input2.txt\n"
+ "\n"
+ "build obj/foo/bar.stamp: tc_stamp input1.out input2.out\n";
+ EXPECT_EQ(expected_linux, out.str());
+ }
+
+ // Windows.
+ {
+ setup.settings()->set_target_os(Settings::WIN);
+
+ std::ostringstream out;
+ NinjaCopyTargetWriter writer(&target, out);
+ writer.Run();
+
+ // TODO(brettw) I think we'll need to worry about backslashes here
+ // depending if we're on actual Windows or Linux pretending to be Windows.
+ const char expected_win[] =
+ "build input1.out: copy ../../foo/input1.txt\n"
+ "build input2.out: copy ../../foo/input2.txt\n"
+ "\n"
+ "build obj/foo/bar.stamp: tc_stamp input1.out input2.out\n";
+ EXPECT_EQ(expected_win, out.str());
+ }
+}
diff --git a/tools/gn/ninja_script_target_writer.cc b/tools/gn/ninja_script_target_writer.cc
index 34913a3..ba0e7a2 100644
--- a/tools/gn/ninja_script_target_writer.cc
+++ b/tools/gn/ninja_script_target_writer.cc
@@ -12,7 +12,10 @@
NinjaScriptTargetWriter::NinjaScriptTargetWriter(const Target* target,
std::ostream& out)
- : NinjaTargetWriter(target, out) {
+ : NinjaTargetWriter(target, out),
+ path_output_no_escaping_(
+ target->settings()->build_settings()->build_dir(),
+ ESCAPE_NONE, true) {
}
NinjaScriptTargetWriter::~NinjaScriptTargetWriter() {
@@ -21,15 +24,17 @@ NinjaScriptTargetWriter::~NinjaScriptTargetWriter() {
void NinjaScriptTargetWriter::Run() {
WriteEnvironment();
- std::string custom_rule_name = WriteRuleDefinition();
+ FileTemplate args_template(target_->script_values().args());
+ std::string custom_rule_name = WriteRuleDefinition(args_template);
std::string implicit_deps = GetSourcesImplicitDeps();
// Collects all output files for writing below.
std::vector<OutputFile> output_files;
if (has_sources()) {
- // Write separate rules for each input source file.
- WriteSourceRules(custom_rule_name, implicit_deps, &output_files);
+ // Write separate build lines for each input source file.
+ WriteSourceRules(custom_rule_name, implicit_deps, args_template,
+ &output_files);
} else {
// No sources, write a rule that invokes the script once with the
// outputs as outputs, and the data as inputs.
@@ -50,7 +55,8 @@ void NinjaScriptTargetWriter::Run() {
WriteStamp(output_files);
}
-std::string NinjaScriptTargetWriter::WriteRuleDefinition() {
+std::string NinjaScriptTargetWriter::WriteRuleDefinition(
+ const FileTemplate& args_template) {
// Make a unique name for this rule.
//
// Use a unique name for the response file when there are multiple build
@@ -78,11 +84,8 @@ std::string NinjaScriptTargetWriter::WriteRuleDefinition() {
// The build command goes in the rsp file.
out_ << " rspfile_content = $pythonpath ";
path_output_.WriteFile(out_, target_->script_values().script());
- for (size_t i = 0; i < target_->script_values().args().size(); i++) {
- const std::string& arg = target_->script_values().args()[i];
- out_ << " ";
- WriteArg(arg);
- }
+ args_template.WriteWithNinjaExpansions(out_);
+ out_ << std::endl;
} else {
// Posix can execute Python directly.
out_ << "rule " << custom_rule_name << std::endl;
@@ -91,11 +94,7 @@ std::string NinjaScriptTargetWriter::WriteRuleDefinition() {
PathOutput::DIR_NO_LAST_SLASH);
out_ << "; $pythonpath ";
path_output_.WriteFile(out_, target_->script_values().script());
- for (size_t i = 0; i < target_->script_values().args().size(); i++) {
- const std::string& arg = target_->script_values().args()[i];
- out_ << " ";
- WriteArg(arg);
- }
+ args_template.WriteWithNinjaExpansions(out_);
out_ << std::endl;
out_ << " description = CUSTOM " << target_label << std::endl;
out_ << " restat = 1" << std::endl;
@@ -105,68 +104,42 @@ std::string NinjaScriptTargetWriter::WriteRuleDefinition() {
return custom_rule_name;
}
-void NinjaScriptTargetWriter::WriteArg(const std::string& arg) {
- // This can be optimized if it's called a lot.
- EscapeOptions options;
- options.mode = ESCAPE_NINJA;
- std::string output_str = EscapeString(arg, options);
-
- // Do this substitution after escaping our our $ will be escaped (which we
- // don't want).
- ReplaceSubstringsAfterOffset(&output_str, 0, FileTemplate::kSource,
- "${source}");
- ReplaceSubstringsAfterOffset(&output_str, 0, FileTemplate::kSourceNamePart,
- "${source_name_part}");
- out_ << output_str;
+void NinjaScriptTargetWriter::WriteArgsSubstitutions(
+ const SourceFile& source,
+ const FileTemplate& args_template) {
+ std::ostringstream source_file_stream;
+ path_output_no_escaping_.WriteFile(source_file_stream, source);
+
+ EscapeOptions template_escape_options;
+ template_escape_options.mode = ESCAPE_NINJA_SHELL;
+ template_escape_options.inhibit_quoting = true;
+
+ args_template.WriteNinjaVariablesForSubstitution(
+ out_, source_file_stream.str(), template_escape_options);
}
void NinjaScriptTargetWriter::WriteSourceRules(
const std::string& custom_rule_name,
const std::string& implicit_deps,
+ const FileTemplate& args_template,
std::vector<OutputFile>* output_files) {
- // Construct the template for generating the output files from each source.
- const Target::FileList& outputs = target_->script_values().outputs();
- std::vector<std::string> output_template_args;
- for (size_t i = 0; i < outputs.size(); i++) {
- // All outputs should be in the output dir.
- output_template_args.push_back(
- RemovePrefix(outputs[i].value(),
- settings_->build_settings()->build_dir().value()));
- }
- FileTemplate output_template(output_template_args);
-
- // Prevent re-allocating each time by initializing outside the loop.
- std::vector<std::string> output_template_result;
+ FileTemplate output_template(GetOutputTemplate());
const Target::FileList& sources = target_->sources();
for (size_t i = 0; i < sources.size(); i++) {
- // Write outputs for this source file computed by the template.
out_ << "build";
- output_template.ApplyString(sources[i].value(), &output_template_result);
- for (size_t out_i = 0; out_i < output_template_result.size(); out_i++) {
- OutputFile output_path(output_template_result[out_i]);
- output_files->push_back(output_path);
- out_ << " ";
- path_output_.WriteFile(out_, output_path);
- }
+ WriteOutputFilesForBuildLine(output_template, sources[i], output_files);
out_ << ": " << custom_rule_name;
path_output_.WriteFile(out_, sources[i]);
out_ << implicit_deps << std::endl;
- out_ << " unique_name = " << i << std::endl;
-
- // The source file here should be relative to the script directory since
- // this is the variable passed to the script. Here we slightly abuse the
- // OutputFile object by putting a non-output-relative path in it to signal
- // that the PathWriter should not prepend directories.
- out_ << " source = ";
- path_output_.WriteFile(out_, sources[i]);
- out_ << std::endl;
+ // Windows needs a unique ID for the response file.
+ if (target_->settings()->IsWin())
+ out_ << " unique_name = " << i << std::endl;
- out_ << " source_name_part = "
- << FindFilenameNoExtension(&sources[i].value()).as_string()
- << std::endl;
+ if (args_template.has_substitutions())
+ WriteArgsSubstitutions(sources[i], args_template);
}
}
@@ -183,3 +156,17 @@ void NinjaScriptTargetWriter::WriteStamp(
}
out_ << std::endl;
}
+
+void NinjaScriptTargetWriter::WriteOutputFilesForBuildLine(
+ const FileTemplate& output_template,
+ const SourceFile& source,
+ std::vector<OutputFile>* output_files) {
+ std::vector<std::string> output_template_result;
+ output_template.ApplyString(source.value(), &output_template_result);
+ for (size_t out_i = 0; out_i < output_template_result.size(); out_i++) {
+ OutputFile output_path(output_template_result[out_i]);
+ output_files->push_back(output_path);
+ out_ << " ";
+ path_output_.WriteFile(out_, output_path);
+ }
+}
diff --git a/tools/gn/ninja_script_target_writer.h b/tools/gn/ninja_script_target_writer.h
index 37bfe14..e84d618 100644
--- a/tools/gn/ninja_script_target_writer.h
+++ b/tools/gn/ninja_script_target_writer.h
@@ -5,9 +5,15 @@
#ifndef TOOLS_GN_NINJA_SCRIPT_TARGET_WRITER_H_
#define TOOLS_GN_NINJA_SCRIPT_TARGET_WRITER_H_
+#include <vector>
+
#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
#include "tools/gn/ninja_target_writer.h"
+class FileTemplate;
+class OutputFile;
+
// Writes a .ninja file for a custom script target type.
class NinjaScriptTargetWriter : public NinjaTargetWriter {
public:
@@ -17,6 +23,11 @@ class NinjaScriptTargetWriter : public NinjaTargetWriter {
virtual void Run() OVERRIDE;
private:
+ FRIEND_TEST_ALL_PREFIXES(NinjaScriptTargetWriter,
+ WriteOutputFilesForBuildLine);
+ FRIEND_TEST_ALL_PREFIXES(NinjaScriptTargetWriter,
+ WriteArgsSubstitutions);
+
bool has_sources() const { return !target_->sources().empty(); }
// Writes the Ninja rule for invoking the script.
@@ -24,7 +35,7 @@ class NinjaScriptTargetWriter : public NinjaTargetWriter {
// Returns the name of the custom rule generated. This will be based on the
// target name, and will include the string "$unique_name" if there are
// multiple inputs.
- std::string WriteRuleDefinition();
+ std::string WriteRuleDefinition(const FileTemplate& args_template);
// Writes the rules for compiling each source, writing all output files
// to the given vector.
@@ -33,14 +44,30 @@ class NinjaScriptTargetWriter : public NinjaTargetWriter {
// to each build step, it starts with a "|" if it's nonempty.
void WriteSourceRules(const std::string& custom_rule_name,
const std::string& implicit_deps,
+ const FileTemplate& args_template,
std::vector<OutputFile>* output_files);
- void WriteArg(const std::string& arg);
+ // Writes the Ninja variables that expand the substitutions required by the
+ // arguments for the given source file.
+ void WriteArgsSubstitutions(const SourceFile& source,
+ const FileTemplate& args_template);
// Writes the .stamp rule that names this target and collects all invocations
// of the script into one thing that other targets can depend on.
void WriteStamp(const std::vector<OutputFile>& output_files);
+ // Writes the output files generated by the output template for the given
+ // source file. This will start with a space and will not include a newline.
+ // Appends the output files to the given vector.
+ void WriteOutputFilesForBuildLine(const FileTemplate& output_template,
+ const SourceFile& source,
+ std::vector<OutputFile>* output_files);
+
+ // Path output writer that doesn't do any escaping or quoting. It does,
+ // however, convert slashes. Used for
+ // computing intermediate strings.
+ PathOutput path_output_no_escaping_;
+
DISALLOW_COPY_AND_ASSIGN(NinjaScriptTargetWriter);
};
diff --git a/tools/gn/ninja_script_target_writer_unittest.cc b/tools/gn/ninja_script_target_writer_unittest.cc
new file mode 100644
index 0000000..2052c74
--- /dev/null
+++ b/tools/gn/ninja_script_target_writer_unittest.cc
@@ -0,0 +1,132 @@
+// Copyright (c) 2013 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 <sstream>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "tools/gn/file_template.h"
+#include "tools/gn/ninja_script_target_writer.h"
+#include "tools/gn/test_with_scope.h"
+
+TEST(NinjaScriptTargetWriter, WriteOutputFilesForBuildLine) {
+ TestWithScope setup;
+ setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/"));
+ Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+
+ target.script_values().outputs().push_back(
+ SourceFile("//out/Debug/gen/a b{{source_name_part}}.h"));
+ target.script_values().outputs().push_back(
+ SourceFile("//out/Debug/gen/{{source_name_part}}.cc"));
+
+ std::ostringstream out;
+ NinjaScriptTargetWriter writer(&target, out);
+
+ FileTemplate output_template = writer.GetOutputTemplate();
+
+ SourceFile source("//foo/bar.in");
+ std::vector<OutputFile> output_files;
+ writer.WriteOutputFilesForBuildLine(output_template, source, &output_files);
+
+ EXPECT_EQ(" gen/a$ bbar.h gen/bar.cc", out.str());
+}
+
+TEST(NinjaScriptTargetWriter, WriteArgsSubstitutions) {
+ TestWithScope setup;
+ setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/"));
+ Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+
+ std::ostringstream out;
+ NinjaScriptTargetWriter writer(&target, out);
+
+ std::vector<std::string> args;
+ args.push_back("-i");
+ args.push_back("{{source}}");
+ args.push_back("--out=foo bar{{source_name_part}}.o");
+ FileTemplate args_template(args);
+
+ writer.WriteArgsSubstitutions(SourceFile("//foo/b ar.in"), args_template);
+
+ EXPECT_EQ(" source = ../../foo/b$ ar.in\n source_name_part = b$ ar\n",
+ out.str());
+}
+
+// Tests the "run script over multiple source files" mode.
+TEST(NinjaScriptTargetWriter, InvokeOverSources) {
+ TestWithScope setup;
+ setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/"));
+ Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+ target.set_output_type(Target::CUSTOM);
+
+ target.sources().push_back(SourceFile("//foo/input1.txt"));
+ target.sources().push_back(SourceFile("//foo/input2.txt"));
+
+ target.script_values().set_script(SourceFile("//foo/script.py"));
+
+ target.script_values().args().push_back("-i");
+ target.script_values().args().push_back("{{source}}");
+ target.script_values().args().push_back(
+ "--out=foo bar{{source_name_part}}.o");
+
+ target.script_values().outputs().push_back(SourceFile("//out/Debug/{{source_name_part}}.out"));
+
+ target.source_prereqs().push_back(SourceFile("//foo/included.txt"));
+
+ // Posix.
+ {
+ setup.settings()->set_target_os(Settings::LINUX);
+
+ std::ostringstream out;
+ NinjaScriptTargetWriter writer(&target, out);
+ writer.Run();
+
+ const char expected_linux[] =
+ "rule __foo_bar___rule\n"
+ " command = cd ../../foo; $pythonpath ../../foo/script.py -i ${source} \"--out=foo$ bar${source_name_part}.o\"\n"
+ " description = CUSTOM //foo:bar()\n"
+ " restat = 1\n"
+ "\n"
+ "build input1.out: __foo_bar___rule../../foo/input1.txt | ../../foo/included.txt\n"
+ " source = ../../foo/input1.txt\n"
+ " source_name_part = input1\n"
+ "build input2.out: __foo_bar___rule../../foo/input2.txt | ../../foo/included.txt\n"
+ " source = ../../foo/input2.txt\n"
+ " source_name_part = input2\n"
+ "\n"
+ "build obj/foo/bar.stamp: tc_stamp input1.out input2.out\n";
+
+ EXPECT_EQ(expected_linux, out.str());
+ }
+
+ // Windows.
+ {
+ setup.settings()->set_target_os(Settings::WIN);
+
+ std::ostringstream out;
+ NinjaScriptTargetWriter writer(&target, out);
+ writer.Run();
+
+ // TODO(brettw) I think we'll need to worry about backslashes here
+ // depending if we're on actual Windows or Linux pretending to be Windows.
+ const char expected_win[] =
+ "arch = environment.x86\n"
+ "rule __foo_bar___rule\n"
+ " command = $pythonpath gyp-win-tool action-wrapper $arch __foo_bar___rule.$unique_name.rsp\n"
+ " description = CUSTOM //foo:bar()\n"
+ " restat = 1\n"
+ " rspfile = __foo_bar___rule.$unique_name.rsp\n"
+ " rspfile_content = $pythonpath ../../foo/script.py -i ${source} \"--out=foo$ bar${source_name_part}.o\"\n"
+ "\n"
+ "build input1.out: __foo_bar___rule../../foo/input1.txt | ../../foo/included.txt\n"
+ " unique_name = 0\n"
+ " source = ../../foo/input1.txt\n"
+ " source_name_part = input1\n"
+ "build input2.out: __foo_bar___rule../../foo/input2.txt | ../../foo/included.txt\n"
+ " unique_name = 1\n"
+ " source = ../../foo/input2.txt\n"
+ " source_name_part = input2\n"
+ "\n"
+ "build obj/foo/bar.stamp: tc_stamp input1.out input2.out\n";
+ EXPECT_EQ(expected_win, out.str());
+ }
+}
diff --git a/tools/gn/ninja_target_writer.cc b/tools/gn/ninja_target_writer.cc
index 0424242..79cdc30 100644
--- a/tools/gn/ninja_target_writer.cc
+++ b/tools/gn/ninja_target_writer.cc
@@ -9,11 +9,13 @@
#include "base/file_util.h"
#include "tools/gn/err.h"
+#include "tools/gn/file_template.h"
#include "tools/gn/ninja_binary_target_writer.h"
#include "tools/gn/ninja_copy_target_writer.h"
#include "tools/gn/ninja_group_target_writer.h"
#include "tools/gn/ninja_script_target_writer.h"
#include "tools/gn/scheduler.h"
+#include "tools/gn/string_utils.h"
#include "tools/gn/target.h"
NinjaTargetWriter::NinjaTargetWriter(const Target* target, std::ostream& out)
@@ -119,3 +121,15 @@ std::string NinjaTargetWriter::GetSourcesImplicitDeps() const {
return ret.str();
return std::string(); // No files added.
}
+
+FileTemplate NinjaTargetWriter::GetOutputTemplate() const {
+ const Target::FileList& outputs = target_->script_values().outputs();
+ std::vector<std::string> output_template_args;
+ for (size_t i = 0; i < outputs.size(); i++) {
+ // All outputs should be in the output dir.
+ output_template_args.push_back(
+ RemovePrefix(outputs[i].value(),
+ settings_->build_settings()->build_dir().value()));
+ }
+ return FileTemplate(output_template_args);
+}
diff --git a/tools/gn/ninja_target_writer.h b/tools/gn/ninja_target_writer.h
index 5d9f55a..9e6f240 100644
--- a/tools/gn/ninja_target_writer.h
+++ b/tools/gn/ninja_target_writer.h
@@ -11,6 +11,7 @@
#include "tools/gn/ninja_helper.h"
#include "tools/gn/path_output.h"
+class FileTemplate;
class Settings;
class Target;
@@ -37,6 +38,9 @@ class NinjaTargetWriter {
// implicit dependencies, returns the empty string.
std::string GetSourcesImplicitDeps() const;
+ // Returns the FileTemplate constructed from the outputs variable.
+ FileTemplate GetOutputTemplate() const;
+
const Settings* settings_; // Non-owning.
const Target* target_; // Non-owning.
std::ostream& out_;
diff --git a/tools/gn/scope_per_file_provider.cc b/tools/gn/scope_per_file_provider.cc
index 60c03d0..1990193 100644
--- a/tools/gn/scope_per_file_provider.cc
+++ b/tools/gn/scope_per_file_provider.cc
@@ -107,6 +107,9 @@ std::string ScopePerFileProvider::GetRootOutputDirWithNoLastSlash(
const std::string& output_dir =
settings->build_settings()->build_dir().value();
+ if (output_dir == "//")
+ return "//.";
+
// Trim off a leading and trailing slash. So "//foo/bar/" -> /foo/bar".
DCHECK(output_dir.size() > 2 && output_dir[0] == '/' &&
output_dir[output_dir.size() - 1] == '/');
@@ -122,6 +125,9 @@ std::string ScopePerFileProvider::GetRootGenDirWithNoLastSlash(
std::string ScopePerFileProvider::GetFileDirWithNoLastSlash() const {
const std::string& dir_value = scope_->GetSourceDir().value();
+ if (dir_value == "//")
+ return "//.";
+
// Trim off a leading and trailing slash. So "//foo/bar/" -> /foo/bar".
DCHECK(dir_value.size() > 2 && dir_value[0] == '/' &&
dir_value[dir_value.size() - 1] == '/');
diff --git a/tools/gn/script_target_generator.cc b/tools/gn/script_target_generator.cc
index 9d0bf5e..f3521ae 100644
--- a/tools/gn/script_target_generator.cc
+++ b/tools/gn/script_target_generator.cc
@@ -24,11 +24,28 @@ void ScriptTargetGenerator::DoRun() {
target_->set_output_type(Target::CUSTOM);
FillExternal();
+ if (err_->has_error())
+ return;
+
FillSources();
+ if (err_->has_error())
+ return;
+
FillSourcePrereqs();
+ if (err_->has_error())
+ return;
+
FillScript();
+ if (err_->has_error())
+ return;
+
FillScriptArgs();
+ if (err_->has_error())
+ return;
+
FillOutputs();
+ if (err_->has_error())
+ return;
// Script outputs don't depend on the current toolchain so we can skip adding
// that dependency.
@@ -60,26 +77,3 @@ void ScriptTargetGenerator::FillScriptArgs() {
return;
target_->script_values().swap_in_args(&args);
}
-
-void ScriptTargetGenerator::FillOutputs() {
- // TODO(brettw) hook up a constant in variables.h
- const Value* value = scope_->GetValue("outputs", true);
- if (!value)
- return;
-
- Target::FileList outputs;
- if (!ExtractListOfRelativeFiles(scope_->settings()->build_settings(), *value,
- scope_->GetSourceDir(), &outputs, err_))
- return;
-
- // Validate that outputs are in the output dir.
- CHECK(outputs.size() == value->list_value().size());
- for (size_t i = 0; i < outputs.size(); i++) {
- if (!EnsureStringIsInOutputDir(
- GetBuildSettings()->build_dir(),
- outputs[i].value(), value->list_value()[i], err_))
- return;
- }
- target_->script_values().swap_in_outputs(&outputs);
-}
-
diff --git a/tools/gn/script_target_generator.h b/tools/gn/script_target_generator.h
index c5e1811..593dd01 100644
--- a/tools/gn/script_target_generator.h
+++ b/tools/gn/script_target_generator.h
@@ -23,7 +23,6 @@ class ScriptTargetGenerator : public TargetGenerator {
private:
void FillScript();
void FillScriptArgs();
- void FillOutputs();
DISALLOW_COPY_AND_ASSIGN(ScriptTargetGenerator);
};
diff --git a/tools/gn/script_values.h b/tools/gn/script_values.h
index 4a874b9..ac4f0db 100644
--- a/tools/gn/script_values.h
+++ b/tools/gn/script_values.h
@@ -23,10 +23,12 @@ class ScriptValues {
void set_script(const SourceFile& s) { script_ = s; }
// Arguments to the script.
+ std::vector<std::string>& args() { return args_; }
const std::vector<std::string>& args() const { return args_; }
void swap_in_args(std::vector<std::string>* a) { args_.swap(*a); }
// Files created by the script.
+ std::vector<SourceFile>& outputs() { return outputs_; }
const std::vector<SourceFile>& outputs() const { return outputs_; }
void swap_in_outputs(std::vector<SourceFile>* op) { outputs_.swap(*op); }
diff --git a/tools/gn/secondary/third_party/icu/BUILD.gn b/tools/gn/secondary/third_party/icu/BUILD.gn
index 1db4c2e..4c76ffe 100644
--- a/tools/gn/secondary/third_party/icu/BUILD.gn
+++ b/tools/gn/secondary/third_party/icu/BUILD.gn
@@ -404,7 +404,7 @@ if (is_win) {
copy("icudata") {
external = true
sources = [ "windows/icudt.dll" ]
- destdir = root_output_dir
+ outputs = [ "$root_output_dir/icudt.dll" ]
}
} else {
static_library("icudata") {
diff --git a/tools/gn/target.h b/tools/gn/target.h
index 12af7a6..10acfed 100644
--- a/tools/gn/target.h
+++ b/tools/gn/target.h
@@ -151,9 +151,6 @@ class Target : public Item {
ScriptValues& script_values() { return script_values_; }
const ScriptValues& script_values() const { return script_values_; }
- const SourceDir& destdir() const { return destdir_; }
- void set_destdir(const SourceDir& d) { destdir_ = d; }
-
const OrderedSet<std::string>& all_ldflags() const { return all_ldflags_; }
private:
diff --git a/tools/gn/target_generator.cc b/tools/gn/target_generator.cc
index b151b06..8d1abe6 100644
--- a/tools/gn/target_generator.cc
+++ b/tools/gn/target_generator.cc
@@ -9,6 +9,7 @@
#include "tools/gn/config.h"
#include "tools/gn/copy_target_generator.h"
#include "tools/gn/err.h"
+#include "tools/gn/filesystem_utils.h"
#include "tools/gn/functions.h"
#include "tools/gn/group_target_generator.h"
#include "tools/gn/item_node.h"
@@ -191,6 +192,27 @@ void TargetGenerator::FillExternal() {
target_->set_external(value->boolean_value());
}
+void TargetGenerator::FillOutputs() {
+ const Value* value = scope_->GetValue(variables::kOutputs, true);
+ if (!value)
+ return;
+
+ Target::FileList outputs;
+ if (!ExtractListOfRelativeFiles(scope_->settings()->build_settings(), *value,
+ scope_->GetSourceDir(), &outputs, err_))
+ return;
+
+ // Validate that outputs are in the output dir.
+ CHECK(outputs.size() == value->list_value().size());
+ for (size_t i = 0; i < outputs.size(); i++) {
+ if (!EnsureStringIsInOutputDir(
+ GetBuildSettings()->build_dir(),
+ outputs[i].value(), value->list_value()[i], err_))
+ return;
+ }
+ target_->script_values().swap_in_outputs(&outputs);
+}
+
void TargetGenerator::SetToolchainDependency() {
// TODO(brettw) currently we lock separately for each config, dep, and
// toolchain we add which is bad! Do this in one lock.
diff --git a/tools/gn/target_generator.h b/tools/gn/target_generator.h
index fba3771..62154fb 100644
--- a/tools/gn/target_generator.h
+++ b/tools/gn/target_generator.h
@@ -52,6 +52,7 @@ class TargetGenerator {
void FillSourcePrereqs();
void FillConfigs();
void FillExternal();
+ void FillOutputs();
// Sets the current toolchain as a dependecy of this target. All targets with
// a dependency on the toolchain should call this function.
diff --git a/tools/gn/variables.cc b/tools/gn/variables.cc
index aaa5159..e69a11f 100644
--- a/tools/gn/variables.cc
+++ b/tools/gn/variables.cc
@@ -474,10 +474,10 @@ const char kLdflags_Help[] =
" these rules).\n"
COMMON_FLAGS_HELP;
-extern const char kOutputName[] = "output_name";
-extern const char kOutputName_HelpShort[] =
+const char kOutputName[] = "output_name";
+const char kOutputName_HelpShort[] =
"output_name: [string] Name for the output file other than the default.";
-extern const char kOutputName_Help[] =
+const char kOutputName_Help[] =
"output_name: Define a name for the output file other than the default.\n"
"\n"
" Normally the output name of a target will be based on the target name,\n"
@@ -498,6 +498,21 @@ extern const char kOutputName_Help[] =
" output_name = \"fluffy_bunny\"\n"
" }\n";
+const char kOutputs[] = "outputs";
+const char kOutputs_HelpShort[] =
+ "outputs: [file list] Output files for custom script and copy targets.";
+const char kOutputs_Help[] =
+ "outputs: Output files for custom script and copy targets.\n"
+ "\n"
+ " Outputs is valid for \"copy\" and \"custom\" target types and\n"
+ " indicates the resulting files. The values may contain source\n"
+ " expansions to generate the output names from the sources (see\n"
+ " \"gn help source_expansion\").\n"
+ "\n"
+ " For copy targets, the outputs is the destination for the copied\n"
+ " file(s). For custom script targets, the outputs should be the list of\n"
+ " files generated by the script.\n";
+
const char kSourcePrereqs[] = "source_prereqs";
const char kSourcePrereqs_HelpShort[] =
"source_prereqs: [file list] Additional compile-time dependencies.";
@@ -605,6 +620,7 @@ const VariableInfoMap& GetTargetVariables() {
INSERT_VARIABLE(HardDep)
INSERT_VARIABLE(Ldflags)
INSERT_VARIABLE(OutputName)
+ INSERT_VARIABLE(Outputs)
INSERT_VARIABLE(SourcePrereqs)
INSERT_VARIABLE(Sources)
}
diff --git a/tools/gn/variables.h b/tools/gn/variables.h
index 7fb50cf..6bc0505 100644
--- a/tools/gn/variables.h
+++ b/tools/gn/variables.h
@@ -127,6 +127,10 @@ extern const char kOutputName[];
extern const char kOutputName_HelpShort[];
extern const char kOutputName_Help[];
+extern const char kOutputs[];
+extern const char kOutputs_HelpShort[];
+extern const char kOutputs_Help[];
+
extern const char kSourcePrereqs[];
extern const char kSourcePrereqs_HelpShort[];
extern const char kSourcePrereqs_Help[];