| // 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 "gn/label_pattern.h" |
| |
| #include <stddef.h> |
| |
| #include "base/strings/string_util.h" |
| #include "gn/err.h" |
| #include "gn/filesystem_utils.h" |
| #include "gn/value.h" |
| #include "util/build_config.h" |
| |
| const char kLabelPattern_Help[] = |
| R"*(Label patterns |
| |
| A label pattern is a way of expressing one or more labels in a portion of the |
| source tree. They are not general regular expressions. |
| |
| They can take the following forms only: |
| |
| - Explicit (no wildcard): |
| "//foo/bar:baz" |
| ":baz" |
| |
| - Wildcard target names: |
| "//foo/bar:*" (all targets in the //foo/bar/BUILD.gn file) |
| ":*" (all targets in the current build file) |
| |
| - Wildcard directory names ("*" is only supported at the end) |
| "*" (all targets) |
| "//foo/bar/*" (all targets in any subdir of //foo/bar) |
| "./*" (all targets in the current build file or sub dirs) |
| |
| Any of the above forms can additionally take an explicit toolchain |
| in parenthesis at the end of the label pattern. In this case, the |
| toolchain must be fully qualified (no wildcards are supported in the |
| toolchain name). |
| |
| "//foo:bar(//build/toolchain:mac)" |
| An explicit target in an explicit toolchain. |
| |
| ":*(//build/toolchain/linux:32bit)" |
| All targets in the current build file using the 32-bit Linux toolchain. |
| |
| "//foo/*(//build/toolchain:win)" |
| All targets in //foo and any subdirectory using the Windows |
| toolchain. |
| )*"; |
| |
| LabelPattern::LabelPattern() : type_(MATCH) {} |
| |
| LabelPattern::LabelPattern(Type type, |
| const SourceDir& dir, |
| std::string_view name, |
| const Label& toolchain_label) |
| : toolchain_(toolchain_label), type_(type), dir_(dir), name_(name) {} |
| |
| LabelPattern::LabelPattern(const LabelPattern& other) = default; |
| |
| LabelPattern::~LabelPattern() = default; |
| |
| // static |
| LabelPattern LabelPattern::GetPattern(const SourceDir& current_dir, |
| std::string_view source_root, |
| const Value& value, |
| Err* err) { |
| if (!value.VerifyTypeIs(Value::STRING, err)) |
| return LabelPattern(); |
| |
| std::string_view str(value.string_value()); |
| if (str.empty()) { |
| *err = Err(value, "Label pattern must not be empty."); |
| return LabelPattern(); |
| } |
| |
| // If there's no wildcard, this is specifying an exact label, use the |
| // label resolution code to get all the implicit name stuff. |
| size_t star = str.find('*'); |
| if (star == std::string::npos) { |
| Label label = Label::Resolve(current_dir, source_root, Label(), value, err); |
| if (err->has_error()) |
| return LabelPattern(); |
| |
| // Toolchain. |
| Label toolchain_label; |
| if (!label.toolchain_dir().is_null() || !label.toolchain_name().empty()) |
| toolchain_label = label.GetToolchainLabel(); |
| |
| return LabelPattern(MATCH, label.dir(), label.name(), toolchain_label); |
| } |
| |
| // Wildcard case, need to split apart the label to see what it specifies. |
| Label toolchain_label; |
| size_t open_paren = str.find('('); |
| if (open_paren != std::string::npos) { |
| // Has a toolchain definition, extract inside the parens. |
| size_t close_paren = str.find(')', open_paren); |
| if (close_paren == std::string::npos) { |
| *err = Err(value, "No close paren when looking for toolchain name."); |
| return LabelPattern(); |
| } |
| |
| std::string toolchain_string( |
| str.substr(open_paren + 1, close_paren - open_paren - 1)); |
| if (toolchain_string.find('*') != std::string::npos) { |
| *err = Err(value, "Can't have a wildcard in the toolchain."); |
| return LabelPattern(); |
| } |
| |
| // Parse the inside of the parens as a label for a toolchain. |
| Value value_for_toolchain(value.origin(), toolchain_string); |
| toolchain_label = Label::Resolve(current_dir, source_root, Label(), |
| value_for_toolchain, err); |
| if (err->has_error()) |
| return LabelPattern(); |
| |
| // Trim off the toolchain for the processing below. |
| str = str.substr(0, open_paren); |
| } |
| |
| // Extract path and name. |
| std::string_view path; |
| std::string_view name; |
| size_t offset = 0; |
| #if defined(OS_WIN) |
| if (IsPathAbsolute(str)) { |
| size_t drive_letter_pos = str[0] == '/' ? 1 : 0; |
| if (str.size() > drive_letter_pos + 2 && str[drive_letter_pos + 1] == ':' && |
| IsSlash(str[drive_letter_pos + 2]) && |
| base::IsAsciiAlpha(str[drive_letter_pos])) { |
| // Skip over the drive letter colon. |
| offset = drive_letter_pos + 2; |
| } |
| } |
| #endif |
| size_t colon = str.find(':', offset); |
| if (colon == std::string::npos) { |
| path = std::string_view(str); |
| } else { |
| path = str.substr(0, colon); |
| name = str.substr(colon + 1); |
| } |
| |
| // The path can have these forms: |
| // 1. <empty> (use current dir) |
| // 2. <non wildcard stuff> (send through directory resolution) |
| // 3. <non wildcard stuff>* (send stuff through dir resolution, note star) |
| // 4. * (matches anything) |
| SourceDir dir; |
| bool has_path_star = false; |
| if (path.empty()) { |
| // Looks like ":foo". |
| dir = current_dir; |
| } else if (path[path.size() - 1] == '*') { |
| // Case 3 or 4 above. |
| has_path_star = true; |
| |
| // Adjust path to contain everything but the star. |
| path = path.substr(0, path.size() - 1); |
| |
| if (!path.empty() && path[path.size() - 1] != '/') { |
| // The input was "foo*" which is invalid. |
| *err = |
| Err(value, "'*' must match full directories in a label pattern.", |
| "You did \"foo*\" but this thing doesn't do general pattern\n" |
| "matching. Instead, you have to add a slash: \"foo/*\" to match\n" |
| "all targets in a directory hierarchy."); |
| return LabelPattern(); |
| } |
| } |
| |
| // Resolve the part of the path that's not the wildcard. |
| if (!path.empty()) { |
| // The non-wildcard stuff better not have a wildcard. |
| if (path.find('*') != std::string_view::npos) { |
| *err = Err(value, "Label patterns only support wildcard suffixes.", |
| "The pattern contained a '*' that wasn't at the end."); |
| return LabelPattern(); |
| } |
| |
| // Resolve the non-wildcard stuff. |
| dir = current_dir.ResolveRelativeDir(value, path, err, source_root); |
| if (err->has_error()) |
| return LabelPattern(); |
| } |
| |
| // Resolve the name. At this point, we're doing wildcard matches so the |
| // name should either be empty ("foo/*") or a wildcard ("foo:*"); |
| if (colon != std::string::npos && name != "*") { |
| *err = Err( |
| value, "Invalid label pattern.", |
| "You seem to be using the wildcard more generally that is supported.\n" |
| "Did you mean \"foo:*\" to match everything in the file, or\n" |
| "\"./*\" to recursively match everything in the current subtree."); |
| return LabelPattern(); |
| } |
| |
| Type type; |
| if (has_path_star) { |
| // We know there's a wildcard, so if the name is empty it looks like |
| // "foo/*". |
| type = RECURSIVE_DIRECTORY; |
| } else { |
| // Everything else should be of the form "foo:*". |
| type = DIRECTORY; |
| } |
| |
| // When we're doing wildcard matching, the name is always empty. |
| return LabelPattern(type, dir, std::string_view(), toolchain_label); |
| } |
| |
| bool LabelPattern::HasWildcard(const std::string& str) { |
| // Just look for a star. In the future, we may want to handle escaping or |
| // other types of patterns. |
| return str.find('*') != std::string::npos; |
| } |
| |
| bool LabelPattern::Matches(const Label& label) const { |
| if (!toolchain_.is_null()) { |
| // Toolchain must match exactly. |
| if (toolchain_.dir() != label.toolchain_dir() || |
| toolchain_.name() != label.toolchain_name()) |
| return false; |
| } |
| |
| switch (type_) { |
| case MATCH: |
| return label.name() == name_ && label.dir() == dir_; |
| case DIRECTORY: |
| // The directories must match exactly. |
| return label.dir() == dir_; |
| case RECURSIVE_DIRECTORY: |
| // Our directory must be a prefix of the input label for recursive. |
| return label.dir().value().compare(0, dir_.value().size(), |
| dir_.value()) == 0; |
| default: |
| NOTREACHED(); |
| return false; |
| } |
| } |
| |
| // static |
| bool LabelPattern::VectorMatches(const std::vector<LabelPattern>& patterns, |
| const Label& label) { |
| for (const auto& pattern : patterns) { |
| if (pattern.Matches(label)) |
| return true; |
| } |
| return false; |
| } |
| |
| std::string LabelPattern::Describe() const { |
| std::string result; |
| |
| switch (type()) { |
| case MATCH: |
| result = DirectoryWithNoLastSlash(dir()) + ":" + name(); |
| break; |
| case DIRECTORY: |
| result = DirectoryWithNoLastSlash(dir()) + ":*"; |
| break; |
| case RECURSIVE_DIRECTORY: |
| result = dir().value() + "*"; |
| break; |
| } |
| |
| if (!toolchain_.is_null()) { |
| result.push_back('('); |
| result.append(toolchain_.GetUserVisibleName(false)); |
| result.push_back(')'); |
| } |
| return result; |
| } |