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
12namespace {
13
14// We print user visible label names with no trailing slash after the
15// directory name.
16std::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.
27bool 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.
55bool 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.
94bool 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
190Label::Label() {
191}
192
193Label::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
203Label::Label(const SourceDir& dir, const base::StringPiece& name)
204    : dir_(dir) {
205  name_.assign(name.data(), name.size());
206}
207
208Label::~Label() {
209}
210
211// static
212Label 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
235Label Label::GetToolchainLabel() const {
236  return Label(toolchain_dir_, toolchain_name_);
237}
238
239Label Label::GetWithNoToolchain() const {
240  return Label(dir_, name_);
241}
242
243std::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
266std::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