diff options
author | brettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-18 22:02:50 +0000 |
---|---|---|
committer | brettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-18 22:02:50 +0000 |
commit | b90c75afa26473fae4e6c2333beb241b7da27203 (patch) | |
tree | bc4f2383ca103e063dcf1d724c85172ca25e3b5d /tools/gn | |
parent | ebad924529462695b44cb0090d568a808ce83365 (diff) | |
download | chromium_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')
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[]; |