• 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,const std::string_view & source_root,const std::string_view & input,SourceDir * result,Err * err)30 bool ComputeBuildLocationFromDep(const Value& input_value,
31                                  const SourceDir& current_dir,
32                                  const std::string_view& source_root,
33                                  const 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,const std::string_view & input,std::string * result,Err * err)51 bool ComputeTargetNameFromDep(const Value& input_value,
52                               const SourceDir& computed_location,
53                               const std::string_view& input,
54                               std::string* result,
55                               Err* err) {
56   if (!input.empty()) {
57     // Easy case: input is specified, just use it.
58     result->assign(input.data(), input.size());
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->assign(&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,const std::string_view & source_root,const Label & current_toolchain,const Value & original_value,const std::string_view & input,SourceDir * out_dir,std::string * out_name,SourceDir * out_toolchain_dir,std::string * out_toolchain_name,Err * err)90 bool Resolve(const SourceDir& current_dir,
91              const std::string_view& source_root,
92              const Label& current_toolchain,
93              const Value& original_value,
94              const std::string_view& input,
95              SourceDir* out_dir,
96              std::string* out_name,
97              SourceDir* out_toolchain_dir,
98              std::string* 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();
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(const SourceDir & dir,const std::string_view & name,const SourceDir & toolchain_dir,const std::string_view & toolchain_name)256 Label::Label(const SourceDir& dir,
257              const std::string_view& name,
258              const SourceDir& toolchain_dir,
259              const std::string_view& toolchain_name)
260     : dir_(dir), toolchain_dir_(toolchain_dir) {
261   name_.assign(name.data(), name.size());
262   toolchain_name_.assign(toolchain_name.data(), toolchain_name.size());
263 }
264 
265 Label::Label(const SourceDir& dir, const std::string_view& name) : dir_(dir) {
266   name_.assign(name.data(), name.size());
267 }
268 
269 // static
270 Label Label::Resolve(const SourceDir& current_dir,
271                      const std::string_view& source_root,
272                      const Label& current_toolchain,
273                      const Value& input,
274                      Err* err) {
275   Label ret;
276   if (input.type() != Value::STRING) {
277     *err = Err(input, "Dependency is not a string.");
278     return ret;
279   }
280   const std::string& input_string = input.string_value();
281   if (input_string.empty()) {
282     *err = Err(input, "Dependency string is empty.");
283     return ret;
284   }
285 
286   if (!::Resolve(current_dir, source_root, current_toolchain, input,
287                  input_string, &ret.dir_, &ret.name_, &ret.toolchain_dir_,
288                  &ret.toolchain_name_, err))
289     return Label();
290   return ret;
291 }
292 
293 Label Label::GetToolchainLabel() const {
294   return Label(toolchain_dir_, toolchain_name_);
295 }
296 
297 Label Label::GetWithNoToolchain() const {
298   return Label(dir_, name_);
299 }
300 
301 std::string Label::GetUserVisibleName(bool include_toolchain) const {
302   std::string ret;
303   ret.reserve(dir_.value().size() + name_.size() + 1);
304 
305   if (dir_.is_null())
306     return ret;
307 
308   ret = DirWithNoTrailingSlash(dir_);
309   ret.push_back(':');
310   ret.append(name_);
311 
312   if (include_toolchain) {
313     ret.push_back('(');
314     if (!toolchain_dir_.is_null() && !toolchain_name_.empty()) {
315       ret.append(DirWithNoTrailingSlash(toolchain_dir_));
316       ret.push_back(':');
317       ret.append(toolchain_name_);
318     }
319     ret.push_back(')');
320   }
321   return ret;
322 }
323 
324 std::string Label::GetUserVisibleName(const Label& default_toolchain) const {
325   bool include_toolchain = default_toolchain.dir() != toolchain_dir_ ||
326                            default_toolchain.name() != toolchain_name_;
327   return GetUserVisibleName(include_toolchain);
328 }
329