• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "gn/label_pattern.h"
6 
7 #include <stddef.h>
8 
9 #include "base/strings/string_util.h"
10 #include "gn/err.h"
11 #include "gn/filesystem_utils.h"
12 #include "gn/value.h"
13 #include "util/build_config.h"
14 
15 const char kLabelPattern_Help[] =
16     R"*(Label patterns
17 
18   A label pattern is a way of expressing one or more labels in a portion of the
19   source tree. They are not general regular expressions.
20 
21   They can take the following forms only:
22 
23    - Explicit (no wildcard):
24        "//foo/bar:baz"
25        ":baz"
26 
27    - Wildcard target names:
28        "//foo/bar:*" (all targets in the //foo/bar/BUILD.gn file)
29        ":*"  (all targets in the current build file)
30 
31    - Wildcard directory names ("*" is only supported at the end)
32        "*"  (all targets)
33        "//foo/bar/*"  (all targets in any subdir of //foo/bar)
34        "./*"  (all targets in the current build file or sub dirs)
35 
36   Any of the above forms can additionally take an explicit toolchain
37   in parenthesis at the end of the label pattern. In this case, the
38   toolchain must be fully qualified (no wildcards are supported in the
39   toolchain name).
40 
41     "//foo:bar(//build/toolchain:mac)"
42         An explicit target in an explicit toolchain.
43 
44     ":*(//build/toolchain/linux:32bit)"
45         All targets in the current build file using the 32-bit Linux toolchain.
46 
47     "//foo/*(//build/toolchain:win)"
48         All targets in //foo and any subdirectory using the Windows
49         toolchain.
50 )*";
51 
LabelPattern()52 LabelPattern::LabelPattern() : type_(MATCH) {}
53 
54 LabelPattern::LabelPattern(Type type,
55                            const SourceDir& dir,
56                            std::string_view name,
57                            const Label& toolchain_label)
58     : toolchain_(toolchain_label), type_(type), dir_(dir), name_(name) {}
59 
60 LabelPattern::LabelPattern(const LabelPattern& other) = default;
61 
62 LabelPattern::~LabelPattern() = default;
63 
64 // static
65 LabelPattern LabelPattern::GetPattern(const SourceDir& current_dir,
66                                       std::string_view source_root,
67                                       const Value& value,
68                                       Err* err) {
69   if (!value.VerifyTypeIs(Value::STRING, err))
70     return LabelPattern();
71 
72   std::string_view str(value.string_value());
73   if (str.empty()) {
74     *err = Err(value, "Label pattern must not be empty.");
75     return LabelPattern();
76   }
77 
78   // If there's no wildcard, this is specifying an exact label, use the
79   // label resolution code to get all the implicit name stuff.
80   size_t star = str.find('*');
81   if (star == std::string::npos) {
82     Label label = Label::Resolve(current_dir, source_root, Label(), value, err);
83     if (err->has_error())
84       return LabelPattern();
85 
86     // Toolchain.
87     Label toolchain_label;
88     if (!label.toolchain_dir().is_null() || !label.toolchain_name().empty())
89       toolchain_label = label.GetToolchainLabel();
90 
91     return LabelPattern(MATCH, label.dir(), label.name(), toolchain_label);
92   }
93 
94   // Wildcard case, need to split apart the label to see what it specifies.
95   Label toolchain_label;
96   size_t open_paren = str.find('(');
97   if (open_paren != std::string::npos) {
98     // Has a toolchain definition, extract inside the parens.
99     size_t close_paren = str.find(')', open_paren);
100     if (close_paren == std::string::npos) {
101       *err = Err(value, "No close paren when looking for toolchain name.");
102       return LabelPattern();
103     }
104 
105     std::string toolchain_string(
106         str.substr(open_paren + 1, close_paren - open_paren - 1));
107     if (toolchain_string.find('*') != std::string::npos) {
108       *err = Err(value, "Can't have a wildcard in the toolchain.");
109       return LabelPattern();
110     }
111 
112     // Parse the inside of the parens as a label for a toolchain.
113     Value value_for_toolchain(value.origin(), toolchain_string);
114     toolchain_label = Label::Resolve(current_dir, source_root, Label(),
115                                      value_for_toolchain, err);
116     if (err->has_error())
117       return LabelPattern();
118 
119     // Trim off the toolchain for the processing below.
120     str = str.substr(0, open_paren);
121   }
122 
123   // Extract path and name.
124   std::string_view path;
125   std::string_view name;
126   size_t offset = 0;
127 #if defined(OS_WIN)
128   if (IsPathAbsolute(str)) {
129     size_t drive_letter_pos = str[0] == '/' ? 1 : 0;
130     if (str.size() > drive_letter_pos + 2 && str[drive_letter_pos + 1] == ':' &&
131         IsSlash(str[drive_letter_pos + 2]) &&
132         base::IsAsciiAlpha(str[drive_letter_pos])) {
133       // Skip over the drive letter colon.
134       offset = drive_letter_pos + 2;
135     }
136   }
137 #endif
138   size_t colon = str.find(':', offset);
139   if (colon == std::string::npos) {
140     path = std::string_view(str);
141   } else {
142     path = str.substr(0, colon);
143     name = str.substr(colon + 1);
144   }
145 
146   // The path can have these forms:
147   //   1. <empty>  (use current dir)
148   //   2. <non wildcard stuff>  (send through directory resolution)
149   //   3. <non wildcard stuff>*  (send stuff through dir resolution, note star)
150   //   4. *  (matches anything)
151   SourceDir dir;
152   bool has_path_star = false;
153   if (path.empty()) {
154     // Looks like ":foo".
155     dir = current_dir;
156   } else if (path[path.size() - 1] == '*') {
157     // Case 3 or 4 above.
158     has_path_star = true;
159 
160     // Adjust path to contain everything but the star.
161     path = path.substr(0, path.size() - 1);
162 
163     if (!path.empty() && path[path.size() - 1] != '/') {
164       // The input was "foo*" which is invalid.
165       *err =
166           Err(value, "'*' must match full directories in a label pattern.",
167               "You did \"foo*\" but this thing doesn't do general pattern\n"
168               "matching. Instead, you have to add a slash: \"foo/*\" to match\n"
169               "all targets in a directory hierarchy.");
170       return LabelPattern();
171     }
172   }
173 
174   // Resolve the part of the path that's not the wildcard.
175   if (!path.empty()) {
176     // The non-wildcard stuff better not have a wildcard.
177     if (path.find('*') != std::string_view::npos) {
178       *err = Err(value, "Label patterns only support wildcard suffixes.",
179                  "The pattern contained a '*' that wasn't at the end.");
180       return LabelPattern();
181     }
182 
183     // Resolve the non-wildcard stuff.
184     dir = current_dir.ResolveRelativeDir(value, path, err, source_root);
185     if (err->has_error())
186       return LabelPattern();
187   }
188 
189   // Resolve the name. At this point, we're doing wildcard matches so the
190   // name should either be empty ("foo/*") or a wildcard ("foo:*");
191   if (colon != std::string::npos && name != "*") {
192     *err = Err(
193         value, "Invalid label pattern.",
194         "You seem to be using the wildcard more generally that is supported.\n"
195         "Did you mean \"foo:*\" to match everything in the file, or\n"
196         "\"./*\" to recursively match everything in the current subtree.");
197     return LabelPattern();
198   }
199 
200   Type type;
201   if (has_path_star) {
202     // We know there's a wildcard, so if the name is empty it looks like
203     // "foo/*".
204     type = RECURSIVE_DIRECTORY;
205   } else {
206     // Everything else should be of the form "foo:*".
207     type = DIRECTORY;
208   }
209 
210   // When we're doing wildcard matching, the name is always empty.
211   return LabelPattern(type, dir, std::string_view(), toolchain_label);
212 }
213 
HasWildcard(const std::string & str)214 bool LabelPattern::HasWildcard(const std::string& str) {
215   // Just look for a star. In the future, we may want to handle escaping or
216   // other types of patterns.
217   return str.find('*') != std::string::npos;
218 }
219 
Matches(const Label & label) const220 bool LabelPattern::Matches(const Label& label) const {
221   if (!toolchain_.is_null()) {
222     // Toolchain must match exactly.
223     if (toolchain_.dir() != label.toolchain_dir() ||
224         toolchain_.name() != label.toolchain_name())
225       return false;
226   }
227 
228   switch (type_) {
229     case MATCH:
230       return label.name() == name_ && label.dir() == dir_;
231     case DIRECTORY:
232       // The directories must match exactly.
233       return label.dir() == dir_;
234     case RECURSIVE_DIRECTORY:
235       // Our directory must be a prefix of the input label for recursive.
236       return label.dir().value().compare(0, dir_.value().size(),
237                                          dir_.value()) == 0;
238     default:
239       NOTREACHED();
240       return false;
241   }
242 }
243 
244 // static
VectorMatches(const std::vector<LabelPattern> & patterns,const Label & label)245 bool LabelPattern::VectorMatches(const std::vector<LabelPattern>& patterns,
246                                  const Label& label) {
247   for (const auto& pattern : patterns) {
248     if (pattern.Matches(label))
249       return true;
250   }
251   return false;
252 }
253 
Describe() const254 std::string LabelPattern::Describe() const {
255   std::string result;
256 
257   switch (type()) {
258     case MATCH:
259       result = DirectoryWithNoLastSlash(dir()) + ":" + name();
260       break;
261     case DIRECTORY:
262       result = DirectoryWithNoLastSlash(dir()) + ":*";
263       break;
264     case RECURSIVE_DIRECTORY:
265       result = dir().value() + "*";
266       break;
267   }
268 
269   if (!toolchain_.is_null()) {
270     result.push_back('(');
271     result.append(toolchain_.GetUserVisibleName(false));
272     result.push_back(')');
273   }
274   return result;
275 }
276