Home | History | Annotate | Download | only in gn
      1 // Copyright 2014 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/substitution_pattern.h"
      6 
      7 #include "base/strings/string_number_conversions.h"
      8 #include "tools/gn/build_settings.h"
      9 #include "tools/gn/err.h"
     10 #include "tools/gn/filesystem_utils.h"
     11 #include "tools/gn/value.h"
     12 
     13 SubstitutionPattern::Subrange::Subrange()
     14     : type(SUBSTITUTION_LITERAL) {
     15 }
     16 
     17 SubstitutionPattern::Subrange::Subrange(SubstitutionType t,
     18                                         const std::string& l)
     19     : type(t),
     20       literal(l) {
     21 }
     22 
     23 SubstitutionPattern::Subrange::~Subrange() {
     24 }
     25 
     26 SubstitutionPattern::SubstitutionPattern() : origin_(NULL) {
     27 }
     28 
     29 SubstitutionPattern::~SubstitutionPattern() {
     30 }
     31 
     32 bool SubstitutionPattern::Parse(const Value& value, Err* err) {
     33   if (!value.VerifyTypeIs(Value::STRING, err))
     34     return false;
     35   return Parse(value.string_value(), value.origin(), err);
     36 }
     37 
     38 bool SubstitutionPattern::Parse(const std::string& str,
     39                                 const ParseNode* origin,
     40                                 Err* err) {
     41   DCHECK(ranges_.empty());  // Should only be called once.
     42 
     43   size_t cur = 0;
     44   while (true) {
     45     size_t next = str.find("{{", cur);
     46 
     47     // Pick up everything from the previous spot to here as a literal.
     48     if (next == std::string::npos) {
     49       if (cur != str.size())
     50         ranges_.push_back(Subrange(SUBSTITUTION_LITERAL, str.substr(cur)));
     51       break;
     52     } else if (next > cur) {
     53       ranges_.push_back(
     54           Subrange(SUBSTITUTION_LITERAL, str.substr(cur, next - cur)));
     55     }
     56 
     57     // Find which specific pattern this corresponds to.
     58     bool found_match = false;
     59     for (size_t i = SUBSTITUTION_FIRST_PATTERN;
     60          i < SUBSTITUTION_NUM_TYPES; i++) {
     61       const char* cur_pattern = kSubstitutionNames[i];
     62       size_t cur_len = strlen(cur_pattern);
     63       if (str.compare(next, cur_len, cur_pattern) == 0) {
     64         ranges_.push_back(Subrange(static_cast<SubstitutionType>(i)));
     65         cur = next + cur_len;
     66         found_match = true;
     67         break;
     68       }
     69     }
     70 
     71     // Expect all occurrances of {{ to resolve to a pattern.
     72     if (!found_match) {
     73       // Could make this error message more friendly if it comes up a lot. But
     74       // most people will not be writing substitution patterns and the code
     75       // to exactly indicate the error location is tricky.
     76       *err = Err(origin, "Unknown substitution pattern",
     77           "Found a {{ at offset " +
     78           base::SizeTToString(next) +
     79           " and did not find a known substitution following it.");
     80       ranges_.clear();
     81       return false;
     82     }
     83   }
     84 
     85   origin_ = origin;
     86 
     87   // Fill required types vector.
     88   SubstitutionBits bits;
     89   FillRequiredTypes(&bits);
     90   bits.FillVector(&required_types_);
     91   return true;
     92 }
     93 
     94 // static
     95 SubstitutionPattern SubstitutionPattern::MakeForTest(const char* str) {
     96   Err err;
     97   SubstitutionPattern pattern;
     98   CHECK(pattern.Parse(str, NULL, &err)) << err.message();
     99   return pattern;
    100 }
    101 
    102 std::string SubstitutionPattern::AsString() const {
    103   std::string result;
    104   for (size_t i = 0; i < ranges_.size(); i++) {
    105     if (ranges_[i].type == SUBSTITUTION_LITERAL)
    106       result.append(ranges_[i].literal);
    107     else
    108       result.append(kSubstitutionNames[ranges_[i].type]);
    109   }
    110   return result;
    111 }
    112 
    113 void SubstitutionPattern::FillRequiredTypes(SubstitutionBits* bits) const {
    114   for (size_t i = 0; i < ranges_.size(); i++) {
    115     if (ranges_[i].type != SUBSTITUTION_LITERAL)
    116       bits->used[static_cast<size_t>(ranges_[i].type)] = true;
    117   }
    118 }
    119 
    120 bool SubstitutionPattern::IsInOutputDir(const BuildSettings* build_settings,
    121                                         Err* err) const {
    122   if (ranges_.empty()) {
    123     *err = Err(origin_, "This is empty but I was expecting an output file.");
    124     return false;
    125   }
    126 
    127   if (ranges_[0].type == SUBSTITUTION_LITERAL) {
    128     // If the first thing is a literal, it must start with the output dir.
    129     if (!EnsureStringIsInOutputDir(
    130             build_settings->build_dir(),
    131             ranges_[0].literal, origin_, err))
    132       return false;
    133   } else {
    134     // Otherwise, the first subrange must be a pattern that expands to
    135     // something in the output directory.
    136     if (!SubstitutionIsInOutputDir(ranges_[0].type)) {
    137       *err = Err(origin_,
    138           "File is not inside output directory.",
    139           "The given file should be in the output directory. Normally you\n"
    140           "would specify\n\"$target_out_dir/foo\" or "
    141           "\"{{source_gen_dir}}/foo\".");
    142       return false;
    143     }
    144   }
    145 
    146   return true;
    147 }
    148