blob: 2809920ea2be8076ac38a9b1d0c3b932f52a3986 [file] [log] [blame]
// 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 "tools/gn/file_template.h"
#include <algorithm>
#include <iostream>
#include "tools/gn/escape.h"
#include "tools/gn/filesystem_utils.h"
#include "tools/gn/string_utils.h"
#include "tools/gn/target.h"
const char FileTemplate::kSource[] = "{{source}}";
const char FileTemplate::kSourceNamePart[] = "{{source_name_part}}";
const char FileTemplate::kSourceFilePart[] = "{{source_file_part}}";
const char FileTemplate::kSourceDir[] = "{{source_dir}}";
const char FileTemplate::kRootRelDir[] = "{{source_root_relative_dir}}";
const char FileTemplate::kSourceGenDir[] = "{{source_gen_dir}}";
const char FileTemplate::kSourceOutDir[] = "{{source_out_dir}}";
const char kSourceExpansion_Help[] =
"How Source Expansion Works\n"
"\n"
" Source expansion is used for the action_foreach 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 action_foreach\" for more on how\n"
" this is 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"
" \"//foo/bar/baz.txt\" => \"../../foo/bar/baz.txt\"\n"
"\n"
" {{source_file_part}}\n"
" The file part of the source including the extension.\n"
" \"//foo/bar/baz.txt\" => \"baz.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.\n"
" \"//foo/bar/baz.txt\" => \"baz\"\n"
"\n"
" {{source_dir}}\n"
" The directory containing the source file, relative to the build\n"
" directory, with no trailing slash.\n"
" \"//foo/bar/baz.txt\" => \"../../foo/bar\"\n"
"\n"
" {{source_root_relative_dir}}\n"
" The path to the source file's directory relative to the source\n"
" root, with no leading \"//\" or trailing slashes. If the path is\n"
" system-absolute, (beginning in a single slash) this will just\n"
" return the path with no trailing slash.\n"
" \"//foo/bar/baz.txt\" => \"foo/bar\"\n"
"\n"
" {{source_gen_dir}}\n"
" The generated file directory corresponding to the source file's\n"
" path, relative to the build directory. This will be different than\n"
" the target's generated file directory if the source file is in a\n"
" different directory than the build.gn file. If the input path is\n"
" system absolute, this will return the root generated file\n"
" directory."
" \"//foo/bar/baz.txt\" => \"gen/foo/bar\"\n"
"\n"
" {{source_out_dir}}\n"
" The object file directory corresponding to the source file's\n"
" path, relative to the build directory. this us be different than\n"
" the target's out directory if the source file is in a different\n"
" directory than the build.gn file. if the input path is system\n"
" absolute, this will return the root generated file directory.\n"
" \"//foo/bar/baz.txt\" => \"obj/foo/bar\"\n"
"\n"
"Examples\n"
"\n"
" Non-varying outputs:\n"
" action(\"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"
" action_foreach(\"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 Settings* settings, const Value& t, Err* err)
: settings_(settings),
has_substitutions_(false) {
std::fill(types_required_, &types_required_[Subrange::NUM_TYPES], false);
ParseInput(t, err);
}
FileTemplate::FileTemplate(const Settings* settings,
const std::vector<std::string>& t)
: settings_(settings),
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]);
}
FileTemplate::FileTemplate(const Settings* settings,
const std::vector<SourceFile>& t)
: settings_(settings),
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].value());
}
FileTemplate::~FileTemplate() {
}
// static
FileTemplate FileTemplate::GetForTargetOutputs(const Target* target) {
const Target::FileList& outputs = target->action_values().outputs();
std::vector<std::string> output_template_args;
for (size_t i = 0; i < outputs.size(); i++)
output_template_args.push_back(outputs[i].value());
return FileTemplate(target->settings(), output_template_args);
}
bool FileTemplate::IsTypeUsed(Subrange::Type type) const {
DCHECK(type > Subrange::LITERAL && type < Subrange::NUM_TYPES);
return types_required_[type];
}
void FileTemplate::Apply(const SourceFile& source,
std::vector<std::string>* output) const {
// 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];
for (int i = 1; i < Subrange::NUM_TYPES; i++) {
if (types_required_[i]) {
subst[i] =
GetSubstitution(settings_, source, static_cast<Subrange::Type>(i));
}
}
size_t first_output_index = output->size();
output->resize(output->size() + templates_.container().size());
for (size_t template_i = 0;
template_i < templates_.container().size(); template_i++) {
const Template& t = templates_[template_i];
std::string& cur_output = (*output)[first_output_index + template_i];
for (size_t subrange_i = 0; subrange_i < t.container().size();
subrange_i++) {
if (t[subrange_i].type == Subrange::LITERAL)
cur_output.append(t[subrange_i].literal);
else
cur_output.append(subst[t[subrange_i].type]);
}
}
}
void FileTemplate::WriteWithNinjaExpansions(std::ostream& out) const {
EscapeOptions escape_options;
escape_options.mode = ESCAPE_NINJA_COMMAND;
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) {
bool cur_needs_quoting = false;
item_str.append(EscapeString(t[subrange_i].literal, escape_options,
&cur_needs_quoting));
needs_quoting |= cur_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 || item_str.empty()) {
// Need to shell quote the whole string. We also need to quote empty
// strings or it would be impossible to pass "" as a command-line
// argument.
out << '"' << item_str << '"';
} else {
out << item_str;
}
}
}
void FileTemplate::WriteNinjaVariablesForSubstitution(
std::ostream& out,
const Settings* settings,
const SourceFile& 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(settings, 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";
case Subrange::SOURCE_DIR:
return "source_dir";
case Subrange::ROOT_RELATIVE_DIR:
return "source_root_rel_dir";
case Subrange::SOURCE_GEN_DIR:
return "source_gen_dir";
case Subrange::SOURCE_OUT_DIR:
return "source_out_dir";
default:
NOTREACHED();
}
return "";
}
// static
std::string FileTemplate::GetSubstitution(const Settings* settings,
const SourceFile& source,
Subrange::Type type) {
switch (type) {
case Subrange::SOURCE:
if (source.is_system_absolute())
return source.value();
return RebaseSourceAbsolutePath(source.value(),
settings->build_settings()->build_dir());
case Subrange::NAME_PART:
return FindFilenameNoExtension(&source.value()).as_string();
case Subrange::FILE_PART:
return source.GetName();
case Subrange::SOURCE_DIR:
if (source.is_system_absolute())
return DirectoryWithNoLastSlash(source.GetDir());
return RebaseSourceAbsolutePath(
DirectoryWithNoLastSlash(source.GetDir()),
settings->build_settings()->build_dir());
case Subrange::ROOT_RELATIVE_DIR:
if (source.is_system_absolute())
return DirectoryWithNoLastSlash(source.GetDir());
return RebaseSourceAbsolutePath(
DirectoryWithNoLastSlash(source.GetDir()), SourceDir("//"));
case Subrange::SOURCE_GEN_DIR:
return RebaseSourceAbsolutePath(
DirectoryWithNoLastSlash(
GetGenDirForSourceDir(settings, source.GetDir())),
settings->build_settings()->build_dir());
case Subrange::SOURCE_OUT_DIR:
return RebaseSourceAbsolutePath(
DirectoryWithNoLastSlash(
GetOutputDirForSourceDir(settings, source.GetDir())),
settings->build_settings()->build_dir());
default:
NOTREACHED();
}
return std::string();
}
void FileTemplate::ParseInput(const Value& value, Err* err) {
switch (value.type()) {
case Value::STRING:
ParseOneTemplateString(value.string_value());
break;
case Value::LIST:
for (size_t i = 0; i < value.list_value().size(); i++) {
if (!value.list_value()[i].VerifyTypeIs(Value::STRING, err))
return;
ParseOneTemplateString(value.list_value()[i].string_value());
}
break;
default:
*err = Err(value, "File template must be a string or list.",
"A sarcastic comment about your skills goes here.");
}
}
void FileTemplate::ParseOneTemplateString(const std::string& str) {
templates_.container().resize(templates_.container().size() + 1);
Template& t = templates_[templates_.container().size() - 1];
size_t cur = 0;
while (true) {
size_t next = str.find("{{", cur);
// Pick up everything from the previous spot to here as a literal.
if (next == std::string::npos) {
if (cur != str.size())
t.container().push_back(Subrange(Subrange::LITERAL, str.substr(cur)));
break;
} else if (next > cur) {
t.container().push_back(
Subrange(Subrange::LITERAL, str.substr(cur, next - cur)));
}
// Given the name of the string constant and enum for a template parameter,
// checks for it and stores it. Writing this as a function requires passing
// the entire state of this function as arguments, so this actually ends
// up being more clear.
#define IF_MATCH_THEN_STORE(const_name, enum_name) \
if (str.compare(next, arraysize(const_name) - 1, const_name) == 0) { \
t.container().push_back(Subrange(Subrange::enum_name)); \
types_required_[Subrange::enum_name] = true; \
has_substitutions_ = true; \
cur = next + arraysize(const_name) - 1; \
}
// Decode the template param.
IF_MATCH_THEN_STORE(kSource, SOURCE)
else IF_MATCH_THEN_STORE(kSourceNamePart, NAME_PART)
else IF_MATCH_THEN_STORE(kSourceFilePart, FILE_PART)
else IF_MATCH_THEN_STORE(kSourceDir, SOURCE_DIR)
else IF_MATCH_THEN_STORE(kRootRelDir, ROOT_RELATIVE_DIR)
else IF_MATCH_THEN_STORE(kSourceGenDir, SOURCE_GEN_DIR)
else IF_MATCH_THEN_STORE(kSourceOutDir, SOURCE_OUT_DIR)
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
// we can keep going.
t.container().push_back(Subrange(Subrange::LITERAL, "{"));
cur = next + 1;
}
#undef IF_MATCH_THEN_STORE
}
}