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 const char FileTemplate::kSourceDir[] = "{{source_dir}}";
     19 const char FileTemplate::kRootRelDir[] = "{{source_root_relative_dir}}";
     20 const char FileTemplate::kSourceGenDir[] = "{{source_gen_dir}}";
     21 const char FileTemplate::kSourceOutDir[] = "{{source_out_dir}}";
     22 
     23 const char kSourceExpansion_Help[] =
     24     "How Source Expansion Works\n"
     25     "\n"
     26     "  Source expansion is used for the action_foreach and copy target types\n"
     27     "  to map source file names to output file names or arguments.\n"
     28     "\n"
     29     "  To perform source expansion in the outputs, GN maps every entry in the\n"
     30     "  sources to every entry in the outputs list, producing the cross\n"
     31     "  product of all combinations, expanding placeholders (see below).\n"
     32     "\n"
     33     "  Source expansion in the args works similarly, but performing the\n"
     34     "  placeholder substitution produces a different set of arguments for\n"
     35     "  each invocation of the script.\n"
     36     "\n"
     37     "  If no placeholders are found, the outputs or args list will be treated\n"
     38     "  as a static list of literal file names that do not depend on the\n"
     39     "  sources.\n"
     40     "\n"
     41     "  See \"gn help copy\" and \"gn help action_foreach\" for more on how\n"
     42     "  this is applied.\n"
     43     "\n"
     44     "Placeholders\n"
     45     "\n"
     46     "  {{source}}\n"
     47     "      The name of the source file relative to the root build output\n"
     48     "      directory (which is the current directory when running compilers\n"
     49     "      and scripts). This will generally be used for specifying inputs\n"
     50     "      to a script in the \"args\" variable.\n"
     51     "        \"//foo/bar/baz.txt\" => \"../../foo/bar/baz.txt\"\n"
     52     "\n"
     53     "  {{source_file_part}}\n"
     54     "      The file part of the source including the extension.\n"
     55     "        \"//foo/bar/baz.txt\" => \"baz.txt\"\n"
     56     "\n"
     57     "  {{source_name_part}}\n"
     58     "      The filename part of the source file with no directory or\n"
     59     "      extension. This will generally be used for specifying a\n"
     60     "      transformation from a soruce file to a destination file with the\n"
     61     "      same name but different extension.\n"
     62     "        \"//foo/bar/baz.txt\" => \"baz\"\n"
     63     "\n"
     64     "  {{source_dir}}\n"
     65     "      The directory containing the source file, relative to the build\n"
     66     "      directory, with no trailing slash.\n"
     67     "        \"//foo/bar/baz.txt\" => \"../../foo/bar\"\n"
     68     "\n"
     69     "  {{source_root_relative_dir}}\n"
     70     "      The path to the source file's directory relative to the source\n"
     71     "      root, with no leading \"//\" or trailing slashes. If the path is\n"
     72     "      system-absolute, (beginning in a single slash) this will just\n"
     73     "      return the path with no trailing slash.\n"
     74     "        \"//foo/bar/baz.txt\" => \"foo/bar\"\n"
     75     "\n"
     76     "  {{source_gen_dir}}\n"
     77     "      The generated file directory corresponding to the source file's\n"
     78     "      path, relative to the build directory. This will be different than\n"
     79     "      the target's generated file directory if the source file is in a\n"
     80     "      different directory than the build.gn file. If the input path is\n"
     81     "      system absolute, this will return the root generated file\n"
     82     "      directory."
     83     "        \"//foo/bar/baz.txt\" => \"gen/foo/bar\"\n"
     84     "\n"
     85     "  {{source_out_dir}}\n"
     86     "      The object file directory corresponding to the source file's\n"
     87     "      path, relative to the build directory. this us be different than\n"
     88     "      the target's out directory if the source file is in a different\n"
     89     "      directory than the build.gn file. if the input path is system\n"
     90     "      absolute, this will return the root generated file directory.\n"
     91     "        \"//foo/bar/baz.txt\" => \"obj/foo/bar\"\n"
     92     "\n"
     93     "Examples\n"
     94     "\n"
     95     "  Non-varying outputs:\n"
     96     "    action(\"hardcoded_outputs\") {\n"
     97     "      sources = [ \"input1.idl\", \"input2.idl\" ]\n"
     98     "      outputs = [ \"$target_out_dir/output1.dat\",\n"
     99     "                  \"$target_out_dir/output2.dat\" ]\n"
    100     "    }\n"
    101     "  The outputs in this case will be the two literal files given.\n"
    102     "\n"
    103     "  Varying outputs:\n"
    104     "    action_foreach(\"varying_outputs\") {\n"
    105     "      sources = [ \"input1.idl\", \"input2.idl\" ]\n"
    106     "      outputs = [ \"$target_out_dir/{{source_name_part}}.h\",\n"
    107     "                  \"$target_out_dir/{{source_name_part}}.cc\" ]\n"
    108     "    }\n"
    109     "  Performing source expansion will result in the following output names:\n"
    110     "    //out/Debug/obj/mydirectory/input1.h\n"
    111     "    //out/Debug/obj/mydirectory/input1.cc\n"
    112     "    //out/Debug/obj/mydirectory/input2.h\n"
    113     "    //out/Debug/obj/mydirectory/input2.cc\n";
    114 
    115 FileTemplate::FileTemplate(const Settings* settings, const Value& t, Err* err)
    116     : settings_(settings),
    117       has_substitutions_(false) {
    118   std::fill(types_required_, &types_required_[Subrange::NUM_TYPES], false);
    119   ParseInput(t, err);
    120 }
    121 
    122 FileTemplate::FileTemplate(const Settings* settings,
    123                            const std::vector<std::string>& t)
    124     : settings_(settings),
    125       has_substitutions_(false) {
    126   std::fill(types_required_, &types_required_[Subrange::NUM_TYPES], false);
    127   for (size_t i = 0; i < t.size(); i++)
    128     ParseOneTemplateString(t[i]);
    129 }
    130 
    131 FileTemplate::FileTemplate(const Settings* settings,
    132                            const std::vector<SourceFile>& t)
    133     : settings_(settings),
    134       has_substitutions_(false) {
    135   std::fill(types_required_, &types_required_[Subrange::NUM_TYPES], false);
    136   for (size_t i = 0; i < t.size(); i++)
    137     ParseOneTemplateString(t[i].value());
    138 }
    139 
    140 FileTemplate::~FileTemplate() {
    141 }
    142 
    143 // static
    144 FileTemplate FileTemplate::GetForTargetOutputs(const Target* target) {
    145   const Target::FileList& outputs = target->action_values().outputs();
    146   std::vector<std::string> output_template_args;
    147   for (size_t i = 0; i < outputs.size(); i++)
    148     output_template_args.push_back(outputs[i].value());
    149   return FileTemplate(target->settings(), output_template_args);
    150 }
    151 
    152 bool FileTemplate::IsTypeUsed(Subrange::Type type) const {
    153   DCHECK(type > Subrange::LITERAL && type < Subrange::NUM_TYPES);
    154   return types_required_[type];
    155 }
    156 
    157 void FileTemplate::Apply(const SourceFile& source,
    158                          std::vector<std::string>* output) const {
    159   // Compute all substitutions needed so we can just do substitutions below.
    160   // We skip the LITERAL one since that varies each time.
    161   std::string subst[Subrange::NUM_TYPES];
    162   for (int i = 1; i < Subrange::NUM_TYPES; i++) {
    163     if (types_required_[i]) {
    164       subst[i] =
    165           GetSubstitution(settings_, source, static_cast<Subrange::Type>(i));
    166     }
    167   }
    168 
    169   size_t first_output_index = output->size();
    170   output->resize(output->size() + templates_.container().size());
    171   for (size_t template_i = 0;
    172        template_i < templates_.container().size(); template_i++) {
    173     const Template& t = templates_[template_i];
    174     std::string& cur_output = (*output)[first_output_index + template_i];
    175     for (size_t subrange_i = 0; subrange_i < t.container().size();
    176          subrange_i++) {
    177       if (t[subrange_i].type == Subrange::LITERAL)
    178         cur_output.append(t[subrange_i].literal);
    179       else
    180         cur_output.append(subst[t[subrange_i].type]);
    181     }
    182   }
    183 }
    184 
    185 void FileTemplate::WriteWithNinjaExpansions(std::ostream& out) const {
    186   EscapeOptions escape_options;
    187   escape_options.mode = ESCAPE_NINJA_COMMAND;
    188   escape_options.inhibit_quoting = true;
    189 
    190   for (size_t template_i = 0;
    191        template_i < templates_.container().size(); template_i++) {
    192     out << " ";  // Separate args with spaces.
    193 
    194     const Template& t = templates_[template_i];
    195 
    196     // Escape each subrange into a string. Since we're writing out Ninja
    197     // variables, we can't quote the whole thing, so we write in pieces, only
    198     // escaping the literals, and then quoting the whole thing at the end if
    199     // necessary.
    200     bool needs_quoting = false;
    201     std::string item_str;
    202     for (size_t subrange_i = 0; subrange_i < t.container().size();
    203          subrange_i++) {
    204       if (t[subrange_i].type == Subrange::LITERAL) {
    205         bool cur_needs_quoting = false;
    206         item_str.append(EscapeString(t[subrange_i].literal, escape_options,
    207                                      &cur_needs_quoting));
    208         needs_quoting |= cur_needs_quoting;
    209       } else {
    210         // Don't escape this since we need to preserve the $.
    211         item_str.append("${");
    212         item_str.append(GetNinjaVariableNameForType(t[subrange_i].type));
    213         item_str.append("}");
    214       }
    215     }
    216 
    217     if (needs_quoting || item_str.empty()) {
    218       // Need to shell quote the whole string. We also need to quote empty
    219       // strings or it would be impossible to pass "" as a command-line
    220       // argument.
    221       out << '"' << item_str << '"';
    222     } else {
    223       out << item_str;
    224     }
    225   }
    226 }
    227 
    228 void FileTemplate::WriteNinjaVariablesForSubstitution(
    229     std::ostream& out,
    230     const Settings* settings,
    231     const SourceFile& source,
    232     const EscapeOptions& escape_options) const {
    233   for (int i = 1; i < Subrange::NUM_TYPES; i++) {
    234     if (types_required_[i]) {
    235       Subrange::Type type = static_cast<Subrange::Type>(i);
    236       out << "  " << GetNinjaVariableNameForType(type) << " = ";
    237       EscapeStringToStream(out, GetSubstitution(settings, source, type),
    238                            escape_options);
    239       out << std::endl;
    240     }
    241   }
    242 }
    243 
    244 // static
    245 const char* FileTemplate::GetNinjaVariableNameForType(Subrange::Type type) {
    246   switch (type) {
    247     case Subrange::SOURCE:
    248       return "source";
    249     case Subrange::NAME_PART:
    250       return "source_name_part";
    251     case Subrange::FILE_PART:
    252       return "source_file_part";
    253     case Subrange::SOURCE_DIR:
    254       return "source_dir";
    255     case Subrange::ROOT_RELATIVE_DIR:
    256       return "source_root_rel_dir";
    257     case Subrange::SOURCE_GEN_DIR:
    258       return "source_gen_dir";
    259     case Subrange::SOURCE_OUT_DIR:
    260       return "source_out_dir";
    261 
    262     default:
    263       NOTREACHED();
    264   }
    265   return "";
    266 }
    267 
    268 // static
    269 std::string FileTemplate::GetSubstitution(const Settings* settings,
    270                                           const SourceFile& source,
    271                                           Subrange::Type type) {
    272   switch (type) {
    273     case Subrange::SOURCE:
    274       if (source.is_system_absolute())
    275         return source.value();
    276       return RebaseSourceAbsolutePath(source.value(),
    277                                       settings->build_settings()->build_dir());
    278 
    279     case Subrange::NAME_PART:
    280       return FindFilenameNoExtension(&source.value()).as_string();
    281 
    282     case Subrange::FILE_PART:
    283       return source.GetName();
    284 
    285     case Subrange::SOURCE_DIR:
    286       if (source.is_system_absolute())
    287         return DirectoryWithNoLastSlash(source.GetDir());
    288       return RebaseSourceAbsolutePath(
    289           DirectoryWithNoLastSlash(source.GetDir()),
    290           settings->build_settings()->build_dir());
    291 
    292     case Subrange::ROOT_RELATIVE_DIR:
    293       if (source.is_system_absolute())
    294         return DirectoryWithNoLastSlash(source.GetDir());
    295       return RebaseSourceAbsolutePath(
    296           DirectoryWithNoLastSlash(source.GetDir()), SourceDir("//"));
    297 
    298     case Subrange::SOURCE_GEN_DIR:
    299       return RebaseSourceAbsolutePath(
    300           DirectoryWithNoLastSlash(
    301               GetGenDirForSourceDir(settings, source.GetDir())),
    302           settings->build_settings()->build_dir());
    303 
    304     case Subrange::SOURCE_OUT_DIR:
    305       return RebaseSourceAbsolutePath(
    306           DirectoryWithNoLastSlash(
    307               GetOutputDirForSourceDir(settings, source.GetDir())),
    308           settings->build_settings()->build_dir());
    309 
    310     default:
    311       NOTREACHED();
    312   }
    313   return std::string();
    314 }
    315 
    316 void FileTemplate::ParseInput(const Value& value, Err* err) {
    317   switch (value.type()) {
    318     case Value::STRING:
    319       ParseOneTemplateString(value.string_value());
    320       break;
    321     case Value::LIST:
    322       for (size_t i = 0; i < value.list_value().size(); i++) {
    323         if (!value.list_value()[i].VerifyTypeIs(Value::STRING, err))
    324           return;
    325         ParseOneTemplateString(value.list_value()[i].string_value());
    326       }
    327       break;
    328     default:
    329       *err = Err(value, "File template must be a string or list.",
    330                  "A sarcastic comment about your skills goes here.");
    331   }
    332 }
    333 
    334 void FileTemplate::ParseOneTemplateString(const std::string& str) {
    335   templates_.container().resize(templates_.container().size() + 1);
    336   Template& t = templates_[templates_.container().size() - 1];
    337 
    338   size_t cur = 0;
    339   while (true) {
    340     size_t next = str.find("{{", cur);
    341 
    342     // Pick up everything from the previous spot to here as a literal.
    343     if (next == std::string::npos) {
    344       if (cur != str.size())
    345         t.container().push_back(Subrange(Subrange::LITERAL, str.substr(cur)));
    346       break;
    347     } else if (next > cur) {
    348       t.container().push_back(
    349           Subrange(Subrange::LITERAL, str.substr(cur, next - cur)));
    350     }
    351 
    352     // Given the name of the string constant and enum for a template parameter,
    353     // checks for it and stores it. Writing this as a function requires passing
    354     // the entire state of this function as arguments, so this actually ends
    355     // up being more clear.
    356     #define IF_MATCH_THEN_STORE(const_name, enum_name) \
    357         if (str.compare(next, arraysize(const_name) - 1, const_name) == 0) { \
    358           t.container().push_back(Subrange(Subrange::enum_name)); \
    359           types_required_[Subrange::enum_name] = true; \
    360           has_substitutions_ = true; \
    361           cur = next + arraysize(const_name) - 1; \
    362         }
    363 
    364     // Decode the template param.
    365     IF_MATCH_THEN_STORE(kSource, SOURCE)
    366     else IF_MATCH_THEN_STORE(kSourceNamePart, NAME_PART)
    367     else IF_MATCH_THEN_STORE(kSourceFilePart, FILE_PART)
    368     else IF_MATCH_THEN_STORE(kSourceDir, SOURCE_DIR)
    369     else IF_MATCH_THEN_STORE(kRootRelDir, ROOT_RELATIVE_DIR)
    370     else IF_MATCH_THEN_STORE(kSourceGenDir, SOURCE_GEN_DIR)
    371     else IF_MATCH_THEN_STORE(kSourceOutDir, SOURCE_OUT_DIR)
    372     else {
    373       // If it's not a match, treat it like a one-char literal (this will be
    374       // rare, so it's not worth the bother to add to the previous literal) so
    375       // we can keep going.
    376       t.container().push_back(Subrange(Subrange::LITERAL, "{"));
    377       cur = next + 1;
    378     }
    379 
    380     #undef IF_MATCH_THEN_STORE
    381   }
    382 }
    383