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