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