summaryrefslogtreecommitdiff
path: root/chromium/tools/gn/label.cc
blob: 967d1464b8f49b5cf3a6d27470c66477b96a4f38 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "tools/gn/label.h"

#include "base/logging.h"
#include "base/strings/string_util.h"
#include "build/build_config.h"
#include "tools/gn/err.h"
#include "tools/gn/filesystem_utils.h"
#include "tools/gn/parse_tree.h"
#include "tools/gn/value.h"

namespace {

// We print user visible label names with no trailing slash after the
// directory name.
std::string DirWithNoTrailingSlash(const SourceDir& dir) {
  // Be careful not to trim if the input is just "/" or "//".
  if (dir.value().size() > 2)
    return dir.value().substr(0, dir.value().size() - 1);
  return dir.value();
}

// Given the separate-out input (everything before the colon) in the dep rule,
// computes the final build rule. Sets err on failure. On success,
// |*used_implicit| will be set to whether the implicit current directory was
// used. The value is used only for generating error messages.
bool ComputeBuildLocationFromDep(const Value& input_value,
                                 const SourceDir& current_dir,
                                 const base::StringPiece& input,
                                 SourceDir* result,
                                 Err* err) {
  // No rule, use the current location.
  if (input.empty()) {
    *result = current_dir;
    return true;
  }

  *result = current_dir.ResolveRelativeDir(input_value, input, err);
  return true;
}

// Given the separated-out target name (after the colon) computes the final
// name, using the implicit name from the previously-generated
// computed_location if necessary. The input_value is used only for generating
// error messages.
bool ComputeTargetNameFromDep(const Value& input_value,
                              const SourceDir& computed_location,
                              const base::StringPiece& input,
                              std::string* result,
                              Err* err) {
  if (!input.empty()) {
    // Easy case: input is specified, just use it.
    result->assign(input.data(), input.size());
    return true;
  }

  const std::string& loc = computed_location.value();

  // Use implicit name. The path will be "//", "//base/", "//base/i18n/", etc.
  if (loc.size() <= 2) {
    *err = Err(input_value, "This dependency name is empty");
    return false;
  }

  size_t next_to_last_slash = loc.rfind('/', loc.size() - 2);
  DCHECK(next_to_last_slash != std::string::npos);
  result->assign(&loc[next_to_last_slash + 1],
                 loc.size() - next_to_last_slash - 2);
  return true;
}

// The original value is used only for error reporting, use the |input| as the
// input to this function (which may be a substring of the original value when
// we're parsing toolchains.
//
// If the output toolchain vars are NULL, then we'll report an error if we
// find a toolchain specified (this is used when recursively parsing toolchain
// labels which themselves can't have toolchain specs).
//
// We assume that the output variables are initialized to empty so we don't
// write them unless we need them to contain something.
//
// Returns true on success. On failure, the out* variables might be written to
// but shouldn't be used.
bool 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) {
  // To workaround the problem that StringPiece operator[] doesn't return a ref.
  const char* input_str = input.data();
  size_t offset = 0;
#if defined(OS_WIN)
  if (IsPathAbsolute(input)) {
    size_t drive_letter_pos = input[0] == '/' ? 1 : 0;
    if (input.size() > drive_letter_pos + 2 &&
        input[drive_letter_pos + 1] == ':' &&
        IsSlash(input[drive_letter_pos + 2]) &&
        base::IsAsciiAlpha(input[drive_letter_pos])) {
      // Skip over the drive letter colon.
      offset = drive_letter_pos + 2;
    }
  }
#endif
  size_t path_separator = input.find_first_of(":(", offset);
  base::StringPiece location_piece;
  base::StringPiece name_piece;
  base::StringPiece toolchain_piece;
  if (path_separator == std::string::npos) {
    location_piece = input;
    // Leave name & toolchain piece null.
  } else {
    location_piece = base::StringPiece(&input_str[0], path_separator);

    size_t toolchain_separator = input.find('(', path_separator);
    if (toolchain_separator == std::string::npos) {
      name_piece = base::StringPiece(&input_str[path_separator + 1],
                                     input.size() - path_separator - 1);
      // Leave location piece null.
    } else if (!out_toolchain_dir) {
      // Toolchain specified but not allows in this context.
      *err = Err(original_value, "Toolchain has a toolchain.",
          "Your toolchain definition (inside the parens) seems to itself "
          "have a\ntoolchain. Don't do this.");
      return false;
    } else {
      // Name piece is everything between the two separators. Note that the
      // separators may be the same (e.g. "//foo(bar)" which means empty name.
      if (toolchain_separator > path_separator) {
        name_piece = base::StringPiece(
            &input_str[path_separator + 1],
            toolchain_separator - path_separator - 1);
      }

      // Toolchain name should end in a ) and this should be the end of the
      // string.
      if (input[input.size() - 1] != ')') {
        *err = Err(original_value, "Bad toolchain name.",
            "Toolchain name must end in a \")\" at the end of the label.");
        return false;
      }

      // Subtract off the two parens to just get the toolchain name.
      toolchain_piece = base::StringPiece(
          &input_str[toolchain_separator + 1],
          input.size() - toolchain_separator - 2);
    }
  }

  // Everything before the separator is the filename.
  // We allow three cases:
  //   Absolute:                "//foo:bar" -> /foo:bar
  //   Target in current file:  ":foo"     -> <currentdir>:foo
  //   Path with implicit name: "/foo"     -> /foo:foo
  if (location_piece.empty() && name_piece.empty()) {
    // Can't use both implicit filename and name (":").
    *err = Err(original_value, "This doesn't specify a dependency.");
    return false;
  }

  if (!ComputeBuildLocationFromDep(original_value, current_dir, location_piece,
                                   out_dir, err))
    return false;

  if (!ComputeTargetNameFromDep(original_value, *out_dir, name_piece,
                                out_name, err))
    return false;

  // Last, do the toolchains.
  if (out_toolchain_dir) {
    // Handle empty toolchain strings. We don't allow normal labels to be
    // empty so we can't allow the recursive call of this function to do this
    // check.
    if (toolchain_piece.empty()) {
      *out_toolchain_dir = current_toolchain.dir();
      *out_toolchain_name = current_toolchain.name();
      return true;
    } else {
      return Resolve(current_dir, current_toolchain, original_value,
                     toolchain_piece, out_toolchain_dir, out_toolchain_name,
                     nullptr, nullptr, err);
    }
  }
  return true;
}

}  // namespace

const char kLabels_Help[] =
    R"*(About labels

  Everything that can participate in the dependency graph (targets, configs,
  and toolchains) are identified by labels. A common label looks like:

    //base/test:test_support

  This consists of a source-root-absolute path, a colon, and a name. This means
  to look for the thing named "test_support" in "base/test/BUILD.gn".

  You can also specify system absolute paths if necessary. Typically such
  paths would be specified via a build arg so the developer can specify where
  the component is on their system.

    /usr/local/foo:bar    (Posix)
    /C:/Program Files/MyLibs:bar   (Windows)

Toolchains

  A canonical label includes the label of the toolchain being used. Normally,
  the toolchain label is implicitly inherited from the current execution
  context, but you can override this to specify cross-toolchain dependencies:

    //base/test:test_support(//build/toolchain/win:msvc)

  Here GN will look for the toolchain definition called "msvc" in the file
  "//build/toolchain/win" to know how to compile this target.

Relative labels

  If you want to refer to something in the same buildfile, you can omit
  the path name and just start with a colon. This format is recommended for
  all same-file references.

    :base

  Labels can be specified as being relative to the current directory.
  Stylistically, we prefer to use absolute paths for all non-file-local
  references unless a build file needs to be run in different contexts (like a
  project needs to be both standalone and pulled into other projects in
  difference places in the directory hierarchy).

    source/plugin:myplugin
    ../net:url_request

Implicit names

  If a name is unspecified, it will inherit the directory name. Stylistically,
  we prefer to omit the colon and name when possible:

    //net  ->  //net:net
    //tools/gn  ->  //tools/gn:gn
)*";

Label::Label() {
}

Label::Label(const SourceDir& dir,
             const base::StringPiece& name,
             const SourceDir& toolchain_dir,
             const base::StringPiece& toolchain_name)
    : dir_(dir),
      toolchain_dir_(toolchain_dir) {
  name_.assign(name.data(), name.size());
  toolchain_name_.assign(toolchain_name.data(), toolchain_name.size());
}

Label::Label(const SourceDir& dir, const base::StringPiece& name)
    : dir_(dir) {
  name_.assign(name.data(), name.size());
}

Label::Label(const Label& other) = default;

Label::~Label() {
}

// static
Label Label::Resolve(const SourceDir& current_dir,
                     const Label& current_toolchain,
                     const Value& input,
                     Err* err) {
  Label ret;
  if (input.type() != Value::STRING) {
    *err = Err(input, "Dependency is not a string.");
    return ret;
  }
  const std::string& input_string = input.string_value();
  if (input_string.empty()) {
    *err = Err(input, "Dependency string is empty.");
    return ret;
  }

  if (!::Resolve(current_dir, current_toolchain, input, input_string,
                 &ret.dir_, &ret.name_,
                 &ret.toolchain_dir_, &ret.toolchain_name_,
                 err))
    return Label();
  return ret;
}

Label Label::GetToolchainLabel() const {
  return Label(toolchain_dir_, toolchain_name_);
}

Label Label::GetWithNoToolchain() const {
  return Label(dir_, name_);
}

std::string Label::GetUserVisibleName(bool include_toolchain) const {
  std::string ret;
  ret.reserve(dir_.value().size() + name_.size() + 1);

  if (dir_.is_null())
    return ret;

  ret = DirWithNoTrailingSlash(dir_);
  ret.push_back(':');
  ret.append(name_);

  if (include_toolchain) {
    ret.push_back('(');
    if (!toolchain_dir_.is_null() && !toolchain_name_.empty()) {
      ret.append(DirWithNoTrailingSlash(toolchain_dir_));
      ret.push_back(':');
      ret.append(toolchain_name_);
    }
    ret.push_back(')');
  }
  return ret;
}

std::string Label::GetUserVisibleName(const Label& default_toolchain) const {
  bool include_toolchain =
      default_toolchain.dir() != toolchain_dir_ ||
      default_toolchain.name() != toolchain_name_;
  return GetUserVisibleName(include_toolchain);
}