Home | History | Annotate | Download | only in gn
      1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "tools/gn/file_template.h"
      6 
      7 #include <algorithm>
      8 #include <iostream>
      9 
     10 #include "tools/gn/escape.h"
     11 #include "tools/gn/filesystem_utils.h"
     12 #include "tools/gn/string_utils.h"
     13 #include "tools/gn/target.h"
     14 
     15 const char FileTemplate::kSource[] = "{{source}}";
     16 const char FileTemplate::kSourceNamePart[] = "{{source_name_part}}";
     17 const char FileTemplate::kSourceFilePart[] = "{{source_file_part}}";
     18 
     19 const char kSourceExpansion_Help[] =
     20     "How Source Expansion Works\n"
     21     "\n"
     22     "  Source expansion is used for the custom script and copy target types\n"
     23     "  to map source file names to output file names or arguments.\n"
     24     "\n"
     25     "  To perform source expansion in the outputs, GN maps every entry in the\n"
     26     "  sources to every entry in the outputs list, producing the cross\n"
     27     "  product of all combinations, expanding placeholders (see below).\n"
     28     "\n"
     29     "  Source expansion in the args works similarly, but performing the\n"
     30     "  placeholder substitution produces a different set of arguments for\n"
     31     "  each invocation of the script.\n"
     32     "\n"
     33     "  If no placeholders are found, the outputs or args list will be treated\n"
     34     "  as a static list of literal file names that do not depend on the\n"
     35     "  sources.\n"
     36     "\n"
     37     "  See \"gn help copy\" and \"gn help custom\" for more on how this is\n"
     38     "  applied.\n"
     39     "\n"
     40     "Placeholders\n"
     41     "\n"
     42     "  {{source}}\n"
     43     "      The name of the source file relative to the root build output\n"
     44     "      directory (which is the current directory when running compilers\n"
     45     "      and scripts). This will generally be used for specifying inputs\n"
     46     "      to a script in the \"args\" variable.\n"
     47     "\n"
     48     "  {{source_file_part}}\n"
     49     "      The file part of the source including the extension. For the\n"
     50     "      source \"foo/bar.txt\" the source file part will be \"bar.txt\".\n"
     51     "\n"
     52     "  {{source_name_part}}\n"
     53     "      The filename part of the source file with no directory or\n"
     54     "      extension. This will generally be used for specifying a\n"
     55     "      transformation from a soruce file to a destination file with the\n"
     56     "      same name but different extension. For the source \"foo/bar.txt\"\n"
     57     "      the source name part will be \"bar\".\n"
     58     "\n"
     59     "Examples\n"
     60     "\n"
     61     "  Non-varying outputs:\n"
     62     "    script(\"hardcoded_outputs\") {\n"
     63     "      sources = [ \"input1.idl\", \"input2.idl\" ]\n"
     64     "      outputs = [ \"$target_out_dir/output1.dat\",\n"
     65     "                  \"$target_out_dir/output2.dat\" ]\n"
     66     "    }\n"
     67     "  The outputs in this case will be the two literal files given.\n"
     68     "\n"
     69     "  Varying outputs:\n"
     70     "    script(\"varying_outputs\") {\n"
     71     "      sources = [ \"input1.idl\", \"input2.idl\" ]\n"
     72     "      outputs = [ \"$target_out_dir/{{source_name_part}}.h\",\n"
     73     "                  \"$target_out_dir/{{source_name_part}}.cc\" ]\n"
     74     "    }\n"
     75     "  Performing source expansion will result in the following output names:\n"
     76     "    //out/Debug/obj/mydirectory/input1.h\n"
     77     "    //out/Debug/obj/mydirectory/input1.cc\n"
     78     "    //out/Debug/obj/mydirectory/input2.h\n"
     79     "    //out/Debug/obj/mydirectory/input2.cc\n";
     80 
     81 FileTemplate::FileTemplate(const Value& t, Err* err)
     82     : has_substitutions_(false) {
     83   std::fill(types_required_, &types_required_[Subrange::NUM_TYPES], false);
     84   ParseInput(t, err);
     85 }
     86 
     87 FileTemplate::FileTemplate(const std::vector<std::string>& t)
     88     : has_substitutions_(false) {
     89   std::fill(types_required_, &types_required_[Subrange::NUM_TYPES], false);
     90   for (size_t i = 0; i < t.size(); i++)
     91     ParseOneTemplateString(t[i]);
     92 }
     93 
     94 FileTemplate::~FileTemplate() {
     95 }
     96 
     97 // static
     98 FileTemplate FileTemplate::GetForTargetOutputs(const Target* target) {
     99   const Target::FileList& outputs = target->script_values().outputs();
    100   std::vector<std::string> output_template_args;
    101   for (size_t i = 0; i < outputs.size(); i++)
    102     output_template_args.push_back(outputs[i].value());
    103   return FileTemplate(output_template_args);
    104 }
    105 
    106 bool FileTemplate::IsTypeUsed(Subrange::Type type) const {
    107   DCHECK(type > Subrange::LITERAL && type < Subrange::NUM_TYPES);
    108   return types_required_[type];
    109 }
    110 
    111 void FileTemplate::Apply(const Value& sources,
    112                          const ParseNode* origin,
    113                          std::vector<Value>* dest,
    114                          Err* err) const {
    115   if (!sources.VerifyTypeIs(Value::LIST, err))
    116     return;
    117   dest->reserve(sources.list_value().size() * templates_.container().size());
    118 
    119   // Temporary holding place, allocate outside to re-use- buffer.
    120   std::vector<std::string> string_output;
    121 
    122   const std::vector<Value>& sources_list = sources.list_value();
    123   for (size_t i = 0; i < sources_list.size(); i++) {
    124     if (!sources_list[i].VerifyTypeIs(Value::STRING, err))
    125       return;
    126 
    127     ApplyString(sources_list[i].string_value(), &string_output);
    128     for (size_t out_i = 0; out_i < string_output.size(); out_i++)
    129       dest->push_back(Value(origin, string_output[i]));
    130   }
    131 }
    132 
    133 void FileTemplate::ApplyString(const std::string& str,
    134                                std::vector<std::string>* output) const {
    135   // Compute all substitutions needed so we can just do substitutions below.
    136   // We skip the LITERAL one since that varies each time.
    137   std::string subst[Subrange::NUM_TYPES];
    138   for (int i = 1; i < Subrange::NUM_TYPES; i++) {
    139     if (types_required_[i])
    140       subst[i] = GetSubstitution(str, static_cast<Subrange::Type>(i));
    141   }
    142 
    143   output->resize(templates_.container().size());
    144   for (size_t template_i = 0;
    145        template_i < templates_.container().size(); template_i++) {
    146     const Template& t = templates_[template_i];
    147     (*output)[template_i].clear();
    148     for (size_t subrange_i = 0; subrange_i < t.container().size();
    149          subrange_i++) {
    150       if (t[subrange_i].type == Subrange::LITERAL)
    151         (*output)[template_i].append(t[subrange_i].literal);
    152       else
    153         (*output)[template_i].append(subst[t[subrange_i].type]);
    154     }
    155   }
    156 }
    157 
    158 void FileTemplate::WriteWithNinjaExpansions(std::ostream& out) const {
    159   EscapeOptions escape_options;
    160   escape_options.mode = ESCAPE_NINJA_SHELL;
    161   escape_options.inhibit_quoting = true;
    162 
    163   for (size_t template_i = 0;
    164        template_i < templates_.container().size(); template_i++) {
    165     out << " ";  // Separate args with spaces.
    166 
    167     const Template& t = templates_[template_i];
    168 
    169     // Escape each subrange into a string. Since we're writing out Ninja
    170     // variables, we can't quote the whole thing, so we write in pieces, only
    171     // escaping the literals, and then quoting the whole thing at the end if
    172     // necessary.
    173     bool needs_quoting = false;
    174     std::string item_str;
    175     for (size_t subrange_i = 0; subrange_i < t.container().size();
    176          subrange_i++) {
    177       if (t[subrange_i].type == Subrange::LITERAL) {
    178         item_str.append(EscapeString(t[subrange_i].literal, escape_options,
    179                                      &needs_quoting));
    180       } else {
    181         // Don't escape this since we need to preserve the $.
    182         item_str.append("${");
    183         item_str.append(GetNinjaVariableNameForType(t[subrange_i].type));
    184         item_str.append("}");
    185       }
    186     }
    187 
    188     if (needs_quoting) {
    189       // Need to shell quote the whole string.
    190       out << '"' << item_str << '"';
    191     } else {
    192       out << item_str;
    193     }
    194   }
    195 }
    196 
    197 void FileTemplate::WriteNinjaVariablesForSubstitution(
    198     std::ostream& out,
    199     const std::string& source,
    200     const EscapeOptions& escape_options) const {
    201   for (int i = 1; i < Subrange::NUM_TYPES; i++) {
    202     if (types_required_[i]) {
    203       Subrange::Type type = static_cast<Subrange::Type>(i);
    204       out << "  " << GetNinjaVariableNameForType(type) << " = ";
    205       EscapeStringToStream(out, GetSubstitution(source, type), escape_options);
    206       out << std::endl;
    207     }
    208   }
    209 }
    210 
    211 // static
    212 const char* FileTemplate::GetNinjaVariableNameForType(Subrange::Type type) {
    213   switch (type) {
    214     case Subrange::SOURCE:
    215       return "source";
    216     case Subrange::NAME_PART:
    217       return "source_name_part";
    218     case Subrange::FILE_PART:
    219       return "source_file_part";
    220     default:
    221       NOTREACHED();
    222   }
    223   return "";
    224 }
    225 
    226 // static
    227 std::string FileTemplate::GetSubstitution(const std::string& source,
    228                                           Subrange::Type type) {
    229   switch (type) {
    230     case Subrange::SOURCE:
    231       return source;
    232     case Subrange::NAME_PART:
    233       return FindFilenameNoExtension(&source).as_string();
    234     case Subrange::FILE_PART:
    235       return FindFilename(&source).as_string();
    236     default:
    237       NOTREACHED();
    238   }
    239   return std::string();
    240 }
    241 
    242 void FileTemplate::ParseInput(const Value& value, Err* err) {
    243   switch (value.type()) {
    244     case Value::STRING:
    245       ParseOneTemplateString(value.string_value());
    246       break;
    247     case Value::LIST:
    248       for (size_t i = 0; i < value.list_value().size(); i++) {
    249         if (!value.list_value()[i].VerifyTypeIs(Value::STRING, err))
    250           return;
    251         ParseOneTemplateString(value.list_value()[i].string_value());
    252       }
    253       break;
    254     default:
    255       *err = Err(value, "File template must be a string or list.",
    256                  "A sarcastic comment about your skills goes here.");
    257   }
    258 }
    259 
    260 void FileTemplate::ParseOneTemplateString(const std::string& str) {
    261   templates_.container().resize(templates_.container().size() + 1);
    262   Template& t = templates_[templates_.container().size() - 1];
    263 
    264   size_t cur = 0;
    265   while (true) {
    266     size_t next = str.find("{{", cur);
    267 
    268     // Pick up everything from the previous spot to here as a literal.
    269     if (next == std::string::npos) {
    270       if (cur != str.size())
    271         t.container().push_back(Subrange(Subrange::LITERAL, str.substr(cur)));
    272       break;
    273     } else if (next > cur) {
    274       t.container().push_back(
    275           Subrange(Subrange::LITERAL, str.substr(cur, next - cur)));
    276     }
    277 
    278     // Decode the template param.
    279     if (str.compare(next, arraysize(kSource) - 1, kSource) == 0) {
    280       t.container().push_back(Subrange(Subrange::SOURCE));
    281       types_required_[Subrange::SOURCE] = true;
    282       has_substitutions_ = true;
    283       cur = next + arraysize(kSource) - 1;
    284     } else if (str.compare(next, arraysize(kSourceNamePart) - 1,
    285                            kSourceNamePart) == 0) {
    286       t.container().push_back(Subrange(Subrange::NAME_PART));
    287       types_required_[Subrange::NAME_PART] = true;
    288       has_substitutions_ = true;
    289       cur = next + arraysize(kSourceNamePart) - 1;
    290     } else if (str.compare(next, arraysize(kSourceFilePart) - 1,
    291                            kSourceFilePart) == 0) {
    292       t.container().push_back(Subrange(Subrange::FILE_PART));
    293       types_required_[Subrange::FILE_PART] = true;
    294       has_substitutions_ = true;
    295       cur = next + arraysize(kSourceFilePart) - 1;
    296     } else {
    297       // If it's not a match, treat it like a one-char literal (this will be
    298       // rare, so it's not worth the bother to add to the previous literal) so
    299       // we can keep going.
    300       t.container().push_back(Subrange(Subrange::LITERAL, "{"));
    301       cur = next + 1;
    302     }
    303   }
    304 }
    305