Commit e9bf4fcb authored by brettw@chromium.org's avatar brettw@chromium.org

Add directory extraction to GN path handling.

Some of the mojo templates want to put generated files in the directory
corresponding to the source file, rather than the directory containing the
BUILD file. Previously, this required putting the BUILD files in the same
directory.

This patch adds the ability to specify the generated file directory
corresponding to the input file in the action_foreach file templates so this
can be expressed naturally.

For parity, it also adds qquerying for this information via get_path_info.

BUG=
R=cjhopman@chromium.org

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@278402 0039d316-1c4b-4281-b951-d872f2087c98
parent 252e6603
......@@ -276,7 +276,7 @@ void PrintOutputs(const Target* target, bool display_header) {
std::vector<std::string> output_strings;
FileTemplate file_template = FileTemplate::GetForTargetOutputs(target);
for (size_t i = 0; i < target->sources().size(); i++)
file_template.ApplyString(target->sources()[i].value(), &output_strings);
file_template.Apply(target->sources()[i], &output_strings);
std::sort(output_strings.begin(), output_strings.end());
for (size_t i = 0; i < output_strings.size(); i++) {
......
This diff is collapsed.
......@@ -14,6 +14,7 @@
struct EscapeOptions;
class ParseNode;
class Settings;
class SourceFile;
class Target;
......@@ -30,20 +31,17 @@ extern const char kSourceExpansion_Help[];
class FileTemplate {
public:
struct Subrange {
// See the help in the .cc file for what these mean.
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,
SOURCE, // {{source}}
NAME_PART, // {{source_name_part}}
FILE_PART, // {{source_file_part}}
SOURCE_DIR, // {{source_dir}}
ROOT_RELATIVE_DIR, // {{root_relative_dir}}
SOURCE_GEN_DIR, // {{source_gen_dir}}
SOURCE_OUT_DIR, // {{source_out_dir}}
NUM_TYPES // Must be last
};
......@@ -60,9 +58,9 @@ class FileTemplate {
// Constructs a template from the given value. On error, the err will be
// set. In this case you should not use this object.
FileTemplate(const Value& t, Err* err);
FileTemplate(const std::vector<std::string>& t);
FileTemplate(const std::vector<SourceFile>& t);
FileTemplate(const Settings* settings, const Value& t, Err* err);
FileTemplate(const Settings* settings, const std::vector<std::string>& t);
FileTemplate(const Settings* settings, const std::vector<SourceFile>& t);
~FileTemplate();
......@@ -76,17 +74,9 @@ class FileTemplate {
// 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.
void Apply(const Value& sources,
const ParseNode* origin,
std::vector<Value>* dest,
Err* err) const;
// Low-level version of Apply that handles one source file. The results
// will be *appended* to the output.
void ApplyString(const std::string& input,
// Applies the template to one source file. The results will be *appended* to
// the output.
void Apply(const SourceFile& source,
std::vector<std::string>* output) const;
// Writes a string representing the template with Ninja variables for the
......@@ -115,7 +105,8 @@ class FileTemplate {
// (see GetWithNinjaExpansions).
void WriteNinjaVariablesForSubstitution(
std::ostream& out,
const std::string& source,
const Settings* settings,
const SourceFile& source,
const EscapeOptions& escape_options) const;
// Returns the Ninja variable name used by the above Ninja functions to
......@@ -124,13 +115,18 @@ class FileTemplate {
// 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,
static std::string GetSubstitution(const Settings* settings,
const SourceFile& source,
Subrange::Type type);
// Known template types, these include the "{{ }}"
static const char kSource[];
static const char kSourceNamePart[];
static const char kSourceFilePart[];
static const char kSourceDir[];
static const char kRootRelDir[];
static const char kSourceGenDir[];
static const char kSourceOutDir[];
private:
typedef base::StackVector<Subrange, 8> Template;
......@@ -141,6 +137,8 @@ class FileTemplate {
// Parses a template string and adds it to the templates_ list.
void ParseOneTemplateString(const std::string& str);
const Settings* settings_;
TemplateVector templates_;
// The corresponding value is set to true if the given subrange type is
......
......@@ -7,58 +7,62 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "tools/gn/escape.h"
#include "tools/gn/file_template.h"
#include "tools/gn/test_with_scope.h"
TEST(FileTemplate, Static) {
TestWithScope setup;
std::vector<std::string> templates;
templates.push_back("something_static");
FileTemplate t(templates);
FileTemplate t(setup.settings(), templates);
EXPECT_FALSE(t.has_substitutions());
std::vector<std::string> result;
t.ApplyString("", &result);
ASSERT_EQ(1u, result.size());
EXPECT_EQ("something_static", result[0]);
result.clear();
t.ApplyString("lalala", &result);
t.Apply(SourceFile("//foo/bar"), &result);
ASSERT_EQ(1u, result.size());
EXPECT_EQ("something_static", result[0]);
}
TEST(FileTemplate, Typical) {
TestWithScope setup;
std::vector<std::string> templates;
templates.push_back("foo/{{source_name_part}}.cc");
templates.push_back("foo/{{source_name_part}}.h");
FileTemplate t(templates);
FileTemplate t(setup.settings(), templates);
EXPECT_TRUE(t.has_substitutions());
std::vector<std::string> result;
t.ApplyString("sources/ha.idl", &result);
t.Apply(SourceFile("//sources/ha.idl"), &result);
ASSERT_EQ(2u, result.size());
EXPECT_EQ("foo/ha.cc", result[0]);
EXPECT_EQ("foo/ha.h", result[1]);
}
TEST(FileTemplate, Weird) {
TestWithScope setup;
std::vector<std::string> templates;
templates.push_back("{{{source}}{{source}}{{");
FileTemplate t(templates);
FileTemplate t(setup.settings(), templates);
EXPECT_TRUE(t.has_substitutions());
std::vector<std::string> result;
t.ApplyString("foo/lalala.c", &result);
t.Apply(SourceFile("//foo/lalala.c"), &result);
ASSERT_EQ(1u, result.size());
EXPECT_EQ("{foo/lalala.cfoo/lalala.c{{", result[0]);
EXPECT_EQ("{../../foo/lalala.c../../foo/lalala.c{{", result[0]);
}
TEST(FileTemplate, NinjaExpansions) {
TestWithScope setup;
std::vector<std::string> templates;
templates.push_back("-i");
templates.push_back("{{source}}");
templates.push_back("--out=foo bar\"{{source_name_part}}\".o");
templates.push_back(""); // Test empty string.
FileTemplate t(templates);
FileTemplate t(setup.settings(), templates);
std::ostringstream out;
t.WriteWithNinjaExpansions(out);
......@@ -79,21 +83,64 @@ TEST(FileTemplate, NinjaExpansions) {
}
TEST(FileTemplate, NinjaVariables) {
TestWithScope setup;
std::vector<std::string> templates;
templates.push_back("-i");
templates.push_back("{{source}}");
templates.push_back("--out=foo bar\"{{source_name_part}}\".o");
templates.push_back("{{source_file_part}}");
templates.push_back("{{source_dir}}");
templates.push_back("{{source_root_relative_dir}}");
templates.push_back("{{source_gen_dir}}");
templates.push_back("{{source_out_dir}}");
FileTemplate t(templates);
FileTemplate t(setup.settings(), templates);
std::ostringstream out;
EscapeOptions options;
options.mode = ESCAPE_NINJA_COMMAND;
t.WriteNinjaVariablesForSubstitution(out, "../../foo/bar.txt", options);
t.WriteNinjaVariablesForSubstitution(out, setup.settings(),
SourceFile("//foo/bar.txt"), options);
// Just the variables used above should be written.
EXPECT_EQ(
" source = ../../foo/bar.txt\n"
" source_name_part = bar\n",
" source_name_part = bar\n"
" source_file_part = bar.txt\n"
" source_dir = ../../foo\n"
" source_root_rel_dir = foo\n"
" source_gen_dir = gen/foo\n"
" source_out_dir = obj/foo\n",
out.str());
}
// Tests in isolation different types of substitutions and that the right
// things are generated.
TEST(FileTemplate, Substitutions) {
TestWithScope setup;
#define GetSubst(str, what) \
FileTemplate::GetSubstitution(setup.settings(), \
SourceFile(str), \
FileTemplate::Subrange::what)
// Try all possible templates with a normal looking string.
EXPECT_EQ("../../foo/bar/baz.txt", GetSubst("//foo/bar/baz.txt", SOURCE));
EXPECT_EQ("baz", GetSubst("//foo/bar/baz.txt", NAME_PART));
EXPECT_EQ("baz.txt", GetSubst("//foo/bar/baz.txt", FILE_PART));
EXPECT_EQ("../../foo/bar", GetSubst("//foo/bar/baz.txt", SOURCE_DIR));
EXPECT_EQ("foo/bar", GetSubst("//foo/bar/baz.txt", ROOT_RELATIVE_DIR));
EXPECT_EQ("gen/foo/bar", GetSubst("//foo/bar/baz.txt", SOURCE_GEN_DIR));
EXPECT_EQ("obj/foo/bar", GetSubst("//foo/bar/baz.txt", SOURCE_OUT_DIR));
// Operations on an absolute path.
EXPECT_EQ("/baz.txt", GetSubst("/baz.txt", SOURCE));
EXPECT_EQ("/.", GetSubst("/baz.txt", SOURCE_DIR));
EXPECT_EQ("gen", GetSubst("/baz.txt", SOURCE_GEN_DIR));
EXPECT_EQ("obj", GetSubst("/baz.txt", SOURCE_OUT_DIR));
EXPECT_EQ(".", GetSubst("//baz.txt", ROOT_RELATIVE_DIR));
#undef GetSubst
}
......@@ -720,10 +720,12 @@ SourceDir GetOutputDirForSourceDir(const Settings* settings,
toolchain.SwapValue(&ret);
ret.append("obj/");
// The source dir should be source-absolute, so we trim off the two leading
if (source_dir.is_source_absolute()) {
// The source dir is source-absolute, so we trim off the two leading
// slashes to append to the toolchain object directory.
DCHECK(source_dir.is_source_absolute());
ret.append(&source_dir.value()[2], source_dir.value().size() - 2);
}
// (Put system-absolute stuff in the root obj directory.)
return SourceDir(SourceDir::SWAP_IN, &ret);
}
......@@ -735,10 +737,13 @@ SourceDir GetGenDirForSourceDir(const Settings* settings,
std::string ret;
toolchain.SwapValue(&ret);
if (source_dir.is_source_absolute()) {
// The source dir should be source-absolute, so we trim off the two leading
// slashes to append to the toolchain object directory.
DCHECK(source_dir.is_source_absolute());
ret.append(&source_dir.value()[2], source_dir.value().size() - 2);
}
// (Put system-absolute stuff in the root gen directory.)
return SourceDir(SourceDir::SWAP_IN, &ret);
}
......
......@@ -20,9 +20,25 @@ enum What {
WHAT_EXTENSION,
WHAT_DIR,
WHAT_ABSPATH,
WHAT_GEN_DIR,
WHAT_OUT_DIR,
};
std::string GetOnePathInfo(const SourceDir& current_dir,
// Returns the directory containing the input (resolving it against the
// |current_dir|), regardless of whether the input is a directory or a file.
SourceDir DirForInput(const SourceDir& current_dir,
const std::string& input_string) {
if (!input_string.empty() && input_string[input_string.size() - 1] == '/') {
// Input is a directory.
return current_dir.ResolveRelativeDir(input_string);
}
// Input is a directory.
return current_dir.ResolveRelativeFile(input_string).GetDir();
}
std::string GetOnePathInfo(const Settings* settings,
const SourceDir& current_dir,
What what,
const Value& input,
Err* err) {
......@@ -62,6 +78,16 @@ std::string GetOnePathInfo(const SourceDir& current_dir,
return std::string("//.");
return dir_incl_slash.substr(0, dir_incl_slash.size() - 1).as_string();
}
case WHAT_GEN_DIR: {
return DirectoryWithNoLastSlash(
GetGenDirForSourceDir(settings,
DirForInput(current_dir, input_string)));
}
case WHAT_OUT_DIR: {
return DirectoryWithNoLastSlash(
GetOutputDirForSourceDir(settings,
DirForInput(current_dir, input_string)));
}
case WHAT_ABSPATH: {
if (!input_string.empty() && input_string[input_string.size() - 1] == '/')
return current_dir.ResolveRelativeDir(input_string).value();
......@@ -123,6 +149,16 @@ const char kGetPathInfo_Help[] =
" will be appended such that it is always legal to append a slash\n"
" and a filename and get a valid path.\n"
"\n"
" \"out_dir\"\n"
" The output file directory corresponding to the path of the\n"
" given file, not including a trailing slash.\n"
" \"//foo/bar/baz.txt\" => \"//out/Default/obj/foo/bar\"\n"
" \"gen_dir\"\n"
" The generated file directory corresponding to the path of the\n"
" given file, not including a trailing slash.\n"
" \"//foo/bar/baz.txt\" => \"//out/Default/gen/foo/bar\"\n"
"\n"
" \"abspath\"\n"
" The full absolute path name to the file or directory. It will be\n"
" resolved relative to the currebt directory, and then the source-\n"
......@@ -168,6 +204,10 @@ Value RunGetPathInfo(Scope* scope,
what = WHAT_EXTENSION;
} else if (args[1].string_value() == "dir") {
what = WHAT_DIR;
} else if (args[1].string_value() == "out_dir") {
what = WHAT_OUT_DIR;
} else if (args[1].string_value() == "gen_dir") {
what = WHAT_GEN_DIR;
} else if (args[1].string_value() == "abspath") {
what = WHAT_ABSPATH;
} else {
......@@ -177,13 +217,15 @@ Value RunGetPathInfo(Scope* scope,
const SourceDir& current_dir = scope->GetSourceDir();
if (args[0].type() == Value::STRING) {
return Value(function, GetOnePathInfo(current_dir, what, args[0], err));
return Value(function, GetOnePathInfo(scope->settings(), current_dir, what,
args[0], err));
} else if (args[0].type() == Value::LIST) {
const std::vector<Value>& input_list = args[0].list_value();
Value result(function, Value::LIST);
for (size_t i = 0; i < input_list.size(); i++) {
result.list_value().push_back(Value(function,
GetOnePathInfo(current_dir, what, input_list[i], err)));
GetOnePathInfo(scope->settings(), current_dir, what,
input_list[i], err)));
if (err->has_error())
return Value();
}
......
......@@ -87,3 +87,25 @@ TEST_F(GetPathInfoTest, AbsPath) {
EXPECT_EQ("/foo/", Call("/foo/", "abspath"));
EXPECT_EQ("/", Call("/", "abspath"));
}
// Note build dir is "//out/Debug/".
TEST_F(GetPathInfoTest, OutDir) {
EXPECT_EQ("//out/Debug/obj/src/foo/foo", Call("foo/bar.txt", "out_dir"));
EXPECT_EQ("//out/Debug/obj/src/foo/bar", Call("bar/", "out_dir"));
EXPECT_EQ("//out/Debug/obj/src/foo", Call(".", "out_dir"));
EXPECT_EQ("//out/Debug/obj/src/foo", Call("bar", "out_dir"));
EXPECT_EQ("//out/Debug/obj/foo", Call("//foo/bar.txt", "out_dir"));
// System paths go into the root obj directory.
EXPECT_EQ("//out/Debug/obj", Call("/foo/bar.txt", "out_dir"));
}
// Note build dir is "//out/Debug/".
TEST_F(GetPathInfoTest, GenDir) {
EXPECT_EQ("//out/Debug/gen/src/foo/foo", Call("foo/bar.txt", "gen_dir"));
EXPECT_EQ("//out/Debug/gen/src/foo/bar", Call("bar/", "gen_dir"));
EXPECT_EQ("//out/Debug/gen/src/foo", Call(".", "gen_dir"));
EXPECT_EQ("//out/Debug/gen/src/foo", Call("bar", "gen_dir"));
EXPECT_EQ("//out/Debug/gen/foo", Call("//foo/bar.txt", "gen_dir"));
// System paths go into the root obj directory.
EXPECT_EQ("//out/Debug/gen", Call("/foo/bar.txt", "gen_dir"));
}
......@@ -15,7 +15,7 @@ namespace functions {
namespace {
void GetOutputsForTarget(const BuildSettings* build_settings,
void GetOutputsForTarget(const Settings* settings,
const Target* target,
std::vector<std::string>* ret) {
switch (target->output_type()) {
......@@ -31,10 +31,10 @@ void GetOutputsForTarget(const BuildSettings* build_settings,
case Target::ACTION_FOREACH: {
// Action_foreach: return the result of the template in the outputs.
FileTemplate file_template(target->action_values().outputs());
FileTemplate file_template(settings, target->action_values().outputs());
const std::vector<SourceFile>& sources = target->sources();
for (size_t i = 0; i < sources.size(); i++)
file_template.ApplyString(sources[i].value(), ret);
file_template.Apply(sources[i], ret);
break;
}
......@@ -50,11 +50,12 @@ void GetOutputsForTarget(const BuildSettings* build_settings,
case Target::GROUP:
case Target::SOURCE_SET: {
// These return the stamp file, which is computed by the NinjaHelper.
NinjaHelper helper(build_settings);
NinjaHelper helper(settings->build_settings());
OutputFile output_file = helper.GetTargetOutputFile(target);
// The output file is relative to the build dir.
std::string absolute_output_file = build_settings->build_dir().value();
std::string absolute_output_file =
settings->build_settings()->build_dir().value();
absolute_output_file.append(output_file.value());
ret->push_back(absolute_output_file);
......@@ -168,7 +169,7 @@ Value RunGetTargetOutputs(Scope* scope,
}
std::vector<std::string> files;
GetOutputsForTarget(scope->settings()->build_settings(), target, &files);
GetOutputsForTarget(scope->settings(), target, &files);
Value ret(function, Value::LIST);
ret.list_value().reserve(files.size());
......
......@@ -5,6 +5,10 @@
#include "tools/gn/file_template.h"
#include "tools/gn/functions.h"
#include "tools/gn/parse_tree.h"
#include "tools/gn/scope.h"
#include "tools/gn/settings.h"
#include "tools/gn/target.h"
#include "tools/gn/value_extractors.h"
namespace functions {
......@@ -67,12 +71,26 @@ Value RunProcessFileTemplate(Scope* scope,
return Value();
}
FileTemplate file_template(args[1], err);
FileTemplate file_template(scope->settings(), args[1], err);
if (err->has_error())
return Value();
Target::FileList input_files;
if (!ExtractListOfRelativeFiles(scope->settings()->build_settings(), args[0],
scope->GetSourceDir(), &input_files, err))
return Value();
Value ret(function, Value::LIST);
file_template.Apply(args[0], function, &ret.list_value(), err);
// Temporary holding place, allocate outside to re-use buffer.
std::vector<std::string> string_output;
for (size_t i = 0; i < input_files.size(); i++) {
string_output.clear();
file_template.Apply(input_files[i], &string_output);
for (size_t out_i = 0; out_i < string_output.size(); out_i++)
ret.list_value().push_back(Value(function, string_output[out_i]));
}
return ret;
}
......
......@@ -23,7 +23,8 @@ NinjaActionTargetWriter::~NinjaActionTargetWriter() {
}
void NinjaActionTargetWriter::Run() {
FileTemplate args_template(target_->action_values().args());
FileTemplate args_template(target_->settings(),
target_->action_values().args());
std::string custom_rule_name = WriteRuleDefinition(args_template);
// Collect our deps to pass as "extra hard dependencies" for input deps. This
......@@ -136,14 +137,11 @@ std::string NinjaActionTargetWriter::WriteRuleDefinition(
void NinjaActionTargetWriter::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_COMMAND;
args_template.WriteNinjaVariablesForSubstitution(
out_, source_file_stream.str(), template_escape_options);
out_, target_->settings(), source, template_escape_options);
}
void NinjaActionTargetWriter::WriteSourceRules(
......@@ -208,7 +206,7 @@ void NinjaActionTargetWriter::WriteOutputFilesForBuildLine(
const SourceFile& source,
std::vector<OutputFile>* output_files) {
std::vector<std::string> output_template_result;
output_template.ApplyString(source.value(), &output_template_result);
output_template.Apply(source, &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);
......@@ -219,7 +217,7 @@ void NinjaActionTargetWriter::WriteOutputFilesForBuildLine(
void NinjaActionTargetWriter::WriteDepfile(const SourceFile& source) {
std::vector<std::string> result;
GetDepfileTemplate().ApplyString(source.value(), &result);
GetDepfileTemplate().Apply(source, &result);
path_output_.WriteFile(out_, OutputFile(result[0]));
}
......@@ -229,5 +227,5 @@ FileTemplate NinjaActionTargetWriter::GetDepfileTemplate() const {
RemovePrefix(target_->action_values().depfile().value(),
settings_->build_settings()->build_dir().value());
template_args.push_back(depfile_relative_to_build_dir);
return FileTemplate(template_args);
return FileTemplate(settings_, template_args);
}
......@@ -44,7 +44,7 @@ TEST(NinjaActionTargetWriter, WriteArgsSubstitutions) {
args.push_back("-i");
args.push_back("{{source}}");
args.push_back("--out=foo bar{{source_name_part}}.o");
FileTemplate args_template(args);
FileTemplate args_template(setup.settings(), args);
writer.WriteArgsSubstitutions(SourceFile("//foo/b ar.in"), args_template);
#if defined(OS_WIN)
......
......@@ -30,7 +30,7 @@ void NinjaCopyTargetWriter::Run() {
// Make the output file from the template.
std::vector<std::string> template_result;
output_template.ApplyString(input_file.value(), &template_result);
output_template.Apply(input_file, &template_result);
CHECK(template_result.size() == 1);
OutputFile output_file(template_result[0]);
......
......@@ -165,5 +165,5 @@ FileTemplate NinjaTargetWriter::GetOutputTemplate() const {
RemovePrefix(outputs[i].value(),
settings_->build_settings()->build_dir().value()));
}
return FileTemplate(output_template_args);
return FileTemplate(target_->settings(), output_template_args);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment