• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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