1 // Copyright (c) 2013 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.h"
6
7 #include "base/logging.h"
8 #include "base/strings/string_util.h"
9 #include "gn/err.h"
10 #include "gn/filesystem_utils.h"
11 #include "gn/parse_tree.h"
12 #include "gn/value.h"
13 #include "util/build_config.h"
14
15 namespace {
16
17 // We print user visible label names with no trailing slash after the
18 // directory name.
DirWithNoTrailingSlash(const SourceDir & dir)19 std::string DirWithNoTrailingSlash(const SourceDir& dir) {
20 // Be careful not to trim if the input is just "/" or "//".
21 if (dir.value().size() > 2)
22 return dir.value().substr(0, dir.value().size() - 1);
23 return dir.value();
24 }
25
26 // Given the separate-out input (everything before the colon) in the dep rule,
27 // computes the final build rule. Sets err on failure. On success,
28 // |*used_implicit| will be set to whether the implicit current directory was
29 // used. The value is used only for generating error messages.
ComputeBuildLocationFromDep(const Value & input_value,const SourceDir & current_dir,std::string_view source_root,std::string_view input,SourceDir * result,Err * err)30 bool ComputeBuildLocationFromDep(const Value& input_value,
31 const SourceDir& current_dir,
32 std::string_view source_root,
33 std::string_view input,
34 SourceDir* result,
35 Err* err) {
36 // No rule, use the current location.
37 if (input.empty()) {
38 *result = current_dir;
39 return true;
40 }
41
42 *result =
43 current_dir.ResolveRelativeDir(input_value, input, err, source_root);
44 return true;
45 }
46
47 // Given the separated-out target name (after the colon) computes the final
48 // name, using the implicit name from the previously-generated
49 // computed_location if necessary. The input_value is used only for generating
50 // error messages.
ComputeTargetNameFromDep(const Value & input_value,const SourceDir & computed_location,std::string_view input,StringAtom * result,Err * err)51 bool ComputeTargetNameFromDep(const Value& input_value,
52 const SourceDir& computed_location,
53 std::string_view input,
54 StringAtom* result,
55 Err* err) {
56 if (!input.empty()) {
57 // Easy case: input is specified, just use it.
58 *result = StringAtom(input);
59 return true;
60 }
61
62 const std::string& loc = computed_location.value();
63
64 // Use implicit name. The path will be "//", "//base/", "//base/i18n/", etc.
65 if (loc.size() <= 2) {
66 *err = Err(input_value, "This dependency name is empty");
67 return false;
68 }
69
70 size_t next_to_last_slash = loc.rfind('/', loc.size() - 2);
71 DCHECK(next_to_last_slash != std::string::npos);
72 *result = StringAtom(std::string_view{&loc[next_to_last_slash + 1],
73 loc.size() - next_to_last_slash - 2});
74 return true;
75 }
76
77 // The original value is used only for error reporting, use the |input| as the
78 // input to this function (which may be a substring of the original value when
79 // we're parsing toolchains.
80 //
81 // If the output toolchain vars are NULL, then we'll report an error if we
82 // find a toolchain specified (this is used when recursively parsing toolchain
83 // labels which themselves can't have toolchain specs).
84 //
85 // We assume that the output variables are initialized to empty so we don't
86 // write them unless we need them to contain something.
87 //
88 // Returns true on success. On failure, the out* variables might be written to
89 // but shouldn't be used.
Resolve(const SourceDir & current_dir,std::string_view source_root,const Label & current_toolchain,const Value & original_value,std::string_view input,SourceDir * out_dir,StringAtom * out_name,SourceDir * out_toolchain_dir,StringAtom * out_toolchain_name,Err * err)90 bool Resolve(const SourceDir& current_dir,
91 std::string_view source_root,
92 const Label& current_toolchain,
93 const Value& original_value,
94 std::string_view input,
95 SourceDir* out_dir,
96 StringAtom* out_name,
97 SourceDir* out_toolchain_dir,
98 StringAtom* out_toolchain_name,
99 Err* err) {
100 // To workaround the problem that std::string_view operator[] doesn't return a
101 // ref.
102 const char* input_str = input.data();
103 size_t offset = 0;
104 #if defined(OS_WIN)
105 if (IsPathAbsolute(input)) {
106 size_t drive_letter_pos = input[0] == '/' ? 1 : 0;
107 if (input.size() > drive_letter_pos + 2 &&
108 input[drive_letter_pos + 1] == ':' &&
109 IsSlash(input[drive_letter_pos + 2]) &&
110 base::IsAsciiAlpha(input[drive_letter_pos])) {
111 // Skip over the drive letter colon.
112 offset = drive_letter_pos + 2;
113 }
114 }
115 #endif
116 size_t path_separator = input.find_first_of(":(", offset);
117 std::string_view location_piece;
118 std::string_view name_piece;
119 std::string_view toolchain_piece;
120 if (path_separator == std::string::npos) {
121 location_piece = input;
122 // Leave name & toolchain piece null.
123 } else {
124 location_piece = std::string_view(&input_str[0], path_separator);
125
126 size_t toolchain_separator = input.find('(', path_separator);
127 if (toolchain_separator == std::string::npos) {
128 name_piece = std::string_view(&input_str[path_separator + 1],
129 input.size() - path_separator - 1);
130 // Leave location piece null.
131 } else if (!out_toolchain_dir) {
132 // Toolchain specified but not allows in this context.
133 *err =
134 Err(original_value, "Toolchain has a toolchain.",
135 "Your toolchain definition (inside the parens) seems to itself "
136 "have a\ntoolchain. Don't do this.");
137 return false;
138 } else {
139 // Name piece is everything between the two separators. Note that the
140 // separators may be the same (e.g. "//foo(bar)" which means empty name.
141 if (toolchain_separator > path_separator) {
142 name_piece = std::string_view(&input_str[path_separator + 1],
143 toolchain_separator - path_separator - 1);
144 }
145
146 // Toolchain name should end in a ) and this should be the end of the
147 // string.
148 if (input[input.size() - 1] != ')') {
149 *err =
150 Err(original_value, "Bad toolchain name.",
151 "Toolchain name must end in a \")\" at the end of the label.");
152 return false;
153 }
154
155 // Subtract off the two parens to just get the toolchain name.
156 toolchain_piece =
157 std::string_view(&input_str[toolchain_separator + 1],
158 input.size() - toolchain_separator - 2);
159 }
160 }
161
162 // Everything before the separator is the filename.
163 // We allow three cases:
164 // Absolute: "//foo:bar" -> /foo:bar
165 // Target in current file: ":foo" -> <currentdir>:foo
166 // Path with implicit name: "/foo" -> /foo:foo
167 if (location_piece.empty() && name_piece.empty()) {
168 // Can't use both implicit filename and name (":").
169 *err = Err(original_value, "This doesn't specify a dependency.");
170 return false;
171 }
172
173 if (!ComputeBuildLocationFromDep(original_value, current_dir, source_root,
174 location_piece, out_dir, err))
175 return false;
176
177 if (!ComputeTargetNameFromDep(original_value, *out_dir, name_piece, out_name,
178 err))
179 return false;
180
181 // Last, do the toolchains.
182 if (out_toolchain_dir) {
183 // Handle empty toolchain strings. We don't allow normal labels to be
184 // empty so we can't allow the recursive call of this function to do this
185 // check.
186 if (toolchain_piece.empty()) {
187 *out_toolchain_dir = current_toolchain.dir();
188 *out_toolchain_name = current_toolchain.name_atom();
189 return true;
190 } else {
191 return Resolve(current_dir, source_root, current_toolchain,
192 original_value, toolchain_piece, out_toolchain_dir,
193 out_toolchain_name, nullptr, nullptr, err);
194 }
195 }
196 return true;
197 }
198
199 } // namespace
200
201 const char kLabels_Help[] =
202 R"*(About labels
203
204 Everything that can participate in the dependency graph (targets, configs,
205 and toolchains) are identified by labels. A common label looks like:
206
207 //base/test:test_support
208
209 This consists of a source-root-absolute path, a colon, and a name. This means
210 to look for the thing named "test_support" in "base/test/BUILD.gn".
211
212 You can also specify system absolute paths if necessary. Typically such
213 paths would be specified via a build arg so the developer can specify where
214 the component is on their system.
215
216 /usr/local/foo:bar (Posix)
217 /C:/Program Files/MyLibs:bar (Windows)
218
219 Toolchains
220
221 A canonical label includes the label of the toolchain being used. Normally,
222 the toolchain label is implicitly inherited from the current execution
223 context, but you can override this to specify cross-toolchain dependencies:
224
225 //base/test:test_support(//build/toolchain/win:msvc)
226
227 Here GN will look for the toolchain definition called "msvc" in the file
228 "//build/toolchain/win" to know how to compile this target.
229
230 Relative labels
231
232 If you want to refer to something in the same buildfile, you can omit
233 the path name and just start with a colon. This format is recommended for
234 all same-file references.
235
236 :base
237
238 Labels can be specified as being relative to the current directory.
239 Stylistically, we prefer to use absolute paths for all non-file-local
240 references unless a build file needs to be run in different contexts (like a
241 project needs to be both standalone and pulled into other projects in
242 difference places in the directory hierarchy).
243
244 source/plugin:myplugin
245 ../net:url_request
246
247 Implicit names
248
249 If a name is unspecified, it will inherit the directory name. Stylistically,
250 we prefer to omit the colon and name when possible:
251
252 //net -> //net:net
253 //tools/gn -> //tools/gn:gn
254 )*";
255
Label()256 Label::Label() : hash_(ComputeHash()) {}
257
258 Label::Label(const SourceDir& dir,
259 std::string_view name,
260 const SourceDir& toolchain_dir,
261 std::string_view toolchain_name)
262 : dir_(dir),
263 name_(StringAtom(name)),
264 toolchain_dir_(toolchain_dir),
265 toolchain_name_(StringAtom(toolchain_name)),
266 hash_(ComputeHash()) {}
267
268 Label::Label(const SourceDir& dir, std::string_view name)
269 : dir_(dir), name_(StringAtom(name)), hash_(ComputeHash()) {}
270
271 // static
272 Label Label::Resolve(const SourceDir& current_dir,
273 std::string_view source_root,
274 const Label& current_toolchain,
275 const Value& input,
276 Err* err) {
277 Label ret;
278 if (input.type() != Value::STRING) {
279 *err = Err(input, "Dependency is not a string.");
280 return ret;
281 }
282 const std::string& input_string = input.string_value();
283 if (input_string.empty()) {
284 *err = Err(input, "Dependency string is empty.");
285 return ret;
286 }
287
288 if (!::Resolve(current_dir, source_root, current_toolchain, input,
289 input_string, &ret.dir_, &ret.name_, &ret.toolchain_dir_,
290 &ret.toolchain_name_, err))
291 return Label();
292
293 ret.hash_ = ret.ComputeHash();
294 return ret;
295 }
296
297 Label Label::GetToolchainLabel() const {
298 return Label(toolchain_dir_, toolchain_name_);
299 }
300
301 Label Label::GetWithNoToolchain() const {
302 return Label(dir_, name_);
303 }
304
305 std::string Label::GetUserVisibleName(bool include_toolchain) const {
306 std::string ret;
307 ret.reserve(dir_.value().size() + name_.str().size() + 1);
308
309 if (dir_.is_null())
310 return ret;
311
312 ret = DirWithNoTrailingSlash(dir_);
313 ret.push_back(':');
314 ret.append(name_.str());
315
316 if (include_toolchain) {
317 ret.push_back('(');
318 if (!toolchain_dir_.is_null() && !toolchain_name_.empty()) {
319 ret.append(DirWithNoTrailingSlash(toolchain_dir_));
320 ret.push_back(':');
321 ret.append(toolchain_name_.str());
322 }
323 ret.push_back(')');
324 }
325 return ret;
326 }
327
328 std::string Label::GetUserVisibleName(const Label& default_toolchain) const {
329 bool include_toolchain = default_toolchain.dir() != toolchain_dir_ ||
330 default_toolchain.name_atom() != toolchain_name_;
331 return GetUserVisibleName(include_toolchain);
332 }
333