// Copyright 2014 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/substitution_pattern.h" #include #include "base/strings/string_number_conversions.h" #include "tools/gn/build_settings.h" #include "tools/gn/err.h" #include "tools/gn/filesystem_utils.h" #include "tools/gn/value.h" SubstitutionPattern::Subrange::Subrange() : type(SUBSTITUTION_LITERAL) { } SubstitutionPattern::Subrange::Subrange(SubstitutionType t, const std::string& l) : type(t), literal(l) { } SubstitutionPattern::Subrange::~Subrange() = default; SubstitutionPattern::SubstitutionPattern() : origin_(nullptr) { } SubstitutionPattern::SubstitutionPattern(const SubstitutionPattern& other) = default; SubstitutionPattern::~SubstitutionPattern() = default; bool SubstitutionPattern::Parse(const Value& value, Err* err) { if (!value.VerifyTypeIs(Value::STRING, err)) return false; return Parse(value.string_value(), value.origin(), err); } bool SubstitutionPattern::Parse(const std::string& str, const ParseNode* origin, Err* err) { DCHECK(ranges_.empty()); // Should only be called once. 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()) ranges_.push_back(Subrange(SUBSTITUTION_LITERAL, str.substr(cur))); break; } else if (next > cur) { ranges_.push_back( Subrange(SUBSTITUTION_LITERAL, str.substr(cur, next - cur))); } // Find which specific pattern this corresponds to. bool found_match = false; for (size_t i = SUBSTITUTION_FIRST_PATTERN; i < SUBSTITUTION_NUM_TYPES; i++) { const char* cur_pattern = kSubstitutionNames[i]; size_t cur_len = strlen(cur_pattern); if (str.compare(next, cur_len, cur_pattern) == 0) { ranges_.push_back(Subrange(static_cast(i))); cur = next + cur_len; found_match = true; break; } } // Expect all occurrances of {{ to resolve to a pattern. if (!found_match) { // Could make this error message more friendly if it comes up a lot. But // most people will not be writing substitution patterns and the code // to exactly indicate the error location is tricky. *err = Err(origin, "Unknown substitution pattern", "Found a {{ at offset " + base::NumberToString(next) + " and did not find a known substitution following it."); ranges_.clear(); return false; } } origin_ = origin; // Fill required types vector. SubstitutionBits bits; FillRequiredTypes(&bits); bits.FillVector(&required_types_); return true; } // static SubstitutionPattern SubstitutionPattern::MakeForTest(const char* str) { Err err; SubstitutionPattern pattern; CHECK(pattern.Parse(str, nullptr, &err)) << err.message(); return pattern; } std::string SubstitutionPattern::AsString() const { std::string result; for (const auto& elem : ranges_) { if (elem.type == SUBSTITUTION_LITERAL) result.append(elem.literal); else result.append(kSubstitutionNames[elem.type]); } return result; } void SubstitutionPattern::FillRequiredTypes(SubstitutionBits* bits) const { for (const auto& elem : ranges_) { if (elem.type != SUBSTITUTION_LITERAL) bits->used[static_cast(elem.type)] = true; } } bool SubstitutionPattern::IsInOutputDir(const BuildSettings* build_settings, Err* err) const { if (ranges_.empty()) { *err = Err(origin_, "This is empty but I was expecting an output file."); return false; } if (ranges_[0].type == SUBSTITUTION_LITERAL) { // If the first thing is a literal, it must start with the output dir. if (!EnsureStringIsInOutputDir( build_settings->build_dir(), ranges_[0].literal, origin_, err)) return false; } else { // Otherwise, the first subrange must be a pattern that expands to // something in the output directory. if (!SubstitutionIsInOutputDir(ranges_[0].type)) { *err = Err(origin_, "File is not inside output directory.", "The given file should be in the output directory. Normally you\n" "would specify\n\"$target_out_dir/foo\" or " "\"{{source_gen_dir}}/foo\"."); return false; } } return true; }