• 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 "tools/gn/label.h"
6 
7 #include "base/logging.h"
8 #include "tools/gn/err.h"
9 #include "tools/gn/parse_tree.h"
10 #include "tools/gn/value.h"
11 
12 namespace {
13 
14 // We print user visible label names with no trailing slash after the
15 // directory name.
DirWithNoTrailingSlash(const SourceDir & dir)16 std::string DirWithNoTrailingSlash(const SourceDir& dir) {
17   // Be careful not to trim if the input is just "/" or "//".
18   if (dir.value().size() > 2)
19     return dir.value().substr(0, dir.value().size() - 1);
20   return dir.value();
21 }
22 
23 // Given the separate-out input (everything before the colon) in the dep rule,
24 // computes the final build rule. Sets err on failure. On success,
25 // |*used_implicit| will be set to whether the implicit current directory was
26 // used. The value is used only for generating error messages.
ComputeBuildLocationFromDep(const Value & input_value,const SourceDir & current_dir,const base::StringPiece & input,SourceDir * result,Err * err)27 bool ComputeBuildLocationFromDep(const Value& input_value,
28                                  const SourceDir& current_dir,
29                                  const base::StringPiece& input,
30                                  SourceDir* result,
31                                  Err* err) {
32   // No rule, use the current locaton.
33   if (input.empty()) {
34     *result = current_dir;
35     return true;
36   }
37 
38   // Don't allow directories to start with a single slash. All labels must be
39   // in the source root.
40   if (input[0] == '/' && (input.size() == 1 || input[1] != '/')) {
41     *err = Err(input_value, "Label can't start with a single slash",
42         "Labels must be either relative (no slash at the beginning) or be "
43         "absolute\ninside the source root (two slashes at the beginning).");
44     return false;
45   }
46 
47   *result = current_dir.ResolveRelativeDir(input);
48   return true;
49 }
50 
51 // Given the separated-out target name (after the colon) computes the final
52 // name, using the implicit name from the previously-generated
53 // computed_location if necessary. The input_value is used only for generating
54 // error messages.
ComputeTargetNameFromDep(const Value & input_value,const SourceDir & computed_location,const base::StringPiece & input,std::string * result,Err * err)55 bool ComputeTargetNameFromDep(const Value& input_value,
56                               const SourceDir& computed_location,
57                               const base::StringPiece& input,
58                               std::string* result,
59                               Err* err) {
60   if (!input.empty()) {
61     // Easy case: input is specified, just use it.
62     result->assign(input.data(), input.size());
63     return true;
64   }
65 
66   const std::string& loc = computed_location.value();
67 
68   // Use implicit name. The path will be "//", "//base/", "//base/i18n/", etc.
69   if (loc.size() <= 2) {
70     *err = Err(input_value, "This dependency name is empty");
71     return false;
72   }
73 
74   size_t next_to_last_slash = loc.rfind('/', loc.size() - 2);
75   DCHECK(next_to_last_slash != std::string::npos);
76   result->assign(&loc[next_to_last_slash + 1],
77                  loc.size() - next_to_last_slash - 2);
78   return true;
79 }
80 
81 // The original value is used only for error reporting, use the |input| as the
82 // input to this function (which may be a substring of the original value when
83 // we're parsing toolchains.
84 //
85 // If the output toolchain vars are NULL, then we'll report an error if we
86 // find a toolchain specified (this is used when recursively parsing toolchain
87 // labels which themselves can't have toolchain specs).
88 //
89 // We assume that the output variables are initialized to empty so we don't
90 // write them unless we need them to contain something.
91 //
92 // Returns true on success. On failure, the out* variables might be written to
93 // but shouldn't be used.
Resolve(const SourceDir & current_dir,const Label & current_toolchain,const Value & original_value,const base::StringPiece & input,SourceDir * out_dir,std::string * out_name,SourceDir * out_toolchain_dir,std::string * out_toolchain_name,Err * err)94 bool Resolve(const SourceDir& current_dir,
95              const Label& current_toolchain,
96              const Value& original_value,
97              const base::StringPiece& input,
98              SourceDir* out_dir,
99              std::string* out_name,
100              SourceDir* out_toolchain_dir,
101              std::string* out_toolchain_name,
102              Err* err) {
103   // To workaround the problem that StringPiece operator[] doesn't return a ref.
104   const char* input_str = input.data();
105 
106   size_t path_separator = input.find_first_of(":(");
107   base::StringPiece location_piece;
108   base::StringPiece name_piece;
109   base::StringPiece toolchain_piece;
110   if (path_separator == std::string::npos) {
111     location_piece = input;
112     // Leave name & toolchain piece null.
113   } else {
114     location_piece = base::StringPiece(&input_str[0], path_separator);
115 
116     size_t toolchain_separator = input.find('(', path_separator);
117     if (toolchain_separator == std::string::npos) {
118       name_piece = base::StringPiece(&input_str[path_separator + 1],
119                                      input.size() - path_separator - 1);
120       // Leave location piece null.
121     } else if (!out_toolchain_dir) {
122       // Toolchain specified but not allows in this context.
123       *err = Err(original_value, "Toolchain has a toolchain.",
124           "Your toolchain definition (inside the parens) seems to itself "
125           "have a\ntoolchain. Don't do this.");
126       return false;
127     } else {
128       // Name piece is everything between the two separators. Note that the
129       // separators may be the same (e.g. "//foo(bar)" which means empty name.
130       if (toolchain_separator > path_separator) {
131         name_piece = base::StringPiece(
132             &input_str[path_separator + 1],
133             toolchain_separator - path_separator - 1);
134       }
135 
136       // Toolchain name should end in a ) and this should be the end of the
137       // string.
138       if (input[input.size() - 1] != ')') {
139         *err = Err(original_value, "Bad toolchain name.",
140             "Toolchain name must end in a \")\" at the end of the label.");
141         return false;
142       }
143 
144       // Subtract off the two parens to just get the toolchain name.
145       toolchain_piece = base::StringPiece(
146           &input_str[toolchain_separator + 1],
147           input.size() - toolchain_separator - 2);
148     }
149   }
150 
151   // Everything before the separator is the filename.
152   // We allow three cases:
153   //   Absolute:                "//foo:bar" -> /foo:bar
154   //   Target in current file:  ":foo"     -> <currentdir>:foo
155   //   Path with implicit name: "/foo"     -> /foo:foo
156   if (location_piece.empty() && name_piece.empty()) {
157     // Can't use both implicit filename and name (":").
158     *err = Err(original_value, "This doesn't specify a dependency.");
159     return false;
160   }
161 
162   if (!ComputeBuildLocationFromDep(original_value, current_dir, location_piece,
163                                    out_dir, err))
164     return false;
165 
166   if (!ComputeTargetNameFromDep(original_value, *out_dir, name_piece,
167                                 out_name, err))
168     return false;
169 
170   // Last, do the toolchains.
171   if (out_toolchain_dir) {
172     // Handle empty toolchain strings. We don't allow normal labels to be
173     // empty so we can't allow the recursive call of this function to do this
174     // check.
175     if (toolchain_piece.empty()) {
176       *out_toolchain_dir = current_toolchain.dir();
177       *out_toolchain_name = current_toolchain.name();
178       return true;
179     } else {
180       return Resolve(current_dir, current_toolchain,
181                      original_value, toolchain_piece,
182                      out_toolchain_dir, out_toolchain_name, NULL, NULL, err);
183     }
184   }
185   return true;
186 }
187 
188 }  // namespace
189 
Label()190 Label::Label() {
191 }
192 
Label(const SourceDir & dir,const base::StringPiece & name,const SourceDir & toolchain_dir,const base::StringPiece & toolchain_name)193 Label::Label(const SourceDir& dir,
194              const base::StringPiece& name,
195              const SourceDir& toolchain_dir,
196              const base::StringPiece& toolchain_name)
197     : dir_(dir),
198       toolchain_dir_(toolchain_dir) {
199   name_.assign(name.data(), name.size());
200   toolchain_name_.assign(toolchain_name.data(), toolchain_name.size());
201 }
202 
Label(const SourceDir & dir,const base::StringPiece & name)203 Label::Label(const SourceDir& dir, const base::StringPiece& name)
204     : dir_(dir) {
205   name_.assign(name.data(), name.size());
206 }
207 
~Label()208 Label::~Label() {
209 }
210 
211 // static
Resolve(const SourceDir & current_dir,const Label & current_toolchain,const Value & input,Err * err)212 Label Label::Resolve(const SourceDir& current_dir,
213                      const Label& current_toolchain,
214                      const Value& input,
215                      Err* err) {
216   Label ret;
217   if (input.type() != Value::STRING) {
218     *err = Err(input, "Dependency is not a string.");
219     return ret;
220   }
221   const std::string& input_string = input.string_value();
222   if (input_string.empty()) {
223     *err = Err(input, "Dependency string is empty.");
224     return ret;
225   }
226 
227   if (!::Resolve(current_dir, current_toolchain, input, input_string,
228                  &ret.dir_, &ret.name_,
229                  &ret.toolchain_dir_, &ret.toolchain_name_,
230                  err))
231     return Label();
232   return ret;
233 }
234 
GetToolchainLabel() const235 Label Label::GetToolchainLabel() const {
236   return Label(toolchain_dir_, toolchain_name_);
237 }
238 
GetWithNoToolchain() const239 Label Label::GetWithNoToolchain() const {
240   return Label(dir_, name_);
241 }
242 
GetUserVisibleName(bool include_toolchain) const243 std::string Label::GetUserVisibleName(bool include_toolchain) const {
244   std::string ret;
245   ret.reserve(dir_.value().size() + name_.size() + 1);
246 
247   if (dir_.is_null())
248     return ret;
249 
250   ret = DirWithNoTrailingSlash(dir_);
251   ret.push_back(':');
252   ret.append(name_);
253 
254   if (include_toolchain) {
255     ret.push_back('(');
256     if (!toolchain_dir_.is_null() && !toolchain_name_.empty()) {
257       ret.append(DirWithNoTrailingSlash(toolchain_dir_));
258       ret.push_back(':');
259       ret.append(toolchain_name_);
260     }
261     ret.push_back(')');
262   }
263   return ret;
264 }
265 
GetUserVisibleName(const Label & default_toolchain) const266 std::string Label::GetUserVisibleName(const Label& default_toolchain) const {
267   bool include_toolchain =
268       default_toolchain.dir() != toolchain_dir_ ||
269       default_toolchain.name() != toolchain_name_;
270   return GetUserVisibleName(include_toolchain);
271 }
272