1// Copyright 2014 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_pattern.h"
6
7#include "tools/gn/err.h"
8#include "tools/gn/filesystem_utils.h"
9#include "tools/gn/value.h"
10
11const char kLabelPattern_Help[] =
12    "Label patterns\n"
13    "\n"
14    "  A label pattern is a way of expressing one or more labels in a portion\n"
15    "  of the source tree. They are not general regular expressions.\n"
16    "\n"
17    "  They can take the following forms only:\n"
18    "\n"
19    "   - Explicit (no wildcard):\n"
20    "       \"//foo/bar:baz\"\n"
21    "       \":baz\"\n"
22    "\n"
23    "   - Wildcard target names:\n"
24    "       \"//foo/bar:*\" (all targets in the //foo/bar/BUILD.gn file)\n"
25    "       \":*\"  (all targets in the current build file)\n"
26    "\n"
27    "   - Wildcard directory names (\"*\" is only supported at the end)\n"
28    "       \"*\"  (all targets)\n"
29    "       \"//foo/bar/*\"  (all targets in any subdir of //foo/bar)\n"
30    "       \"./*\"  (all targets in the current build file or sub dirs)\n"
31    "\n"
32    "  Any of the above forms can additionally take an explicit toolchain.\n"
33    "  In this case, the toolchain must be fully qualified (no wildcards\n"
34    "  are supported in the toolchain name).\n"
35    "\n"
36    "    \"//foo:bar(//build/toochain:mac)\"\n"
37    "        An explicit target in an explicit toolchain.\n"
38    "\n"
39    "    \":*(//build/toolchain/linux:32bit)\"\n"
40    "        All targets in the current build file using the 32-bit Linux\n"
41    "        toolchain.\n"
42    "\n"
43    "    \"//foo/*(//build/toolchain:win)\"\n"
44    "        All targets in //foo and any subdirectory using the Windows\n"
45    "        toolchain.\n";
46
47LabelPattern::LabelPattern() : type_(MATCH) {
48}
49
50LabelPattern::LabelPattern(Type type,
51                           const SourceDir& dir,
52                           const base::StringPiece& name,
53                           const Label& toolchain_label)
54    : toolchain_(toolchain_label),
55      type_(type),
56      dir_(dir) {
57  name.CopyToString(&name_);
58}
59
60LabelPattern::~LabelPattern() {
61}
62
63// static
64LabelPattern LabelPattern::GetPattern(const SourceDir& current_dir,
65                                      const Value& value,
66                                      Err* err) {
67  if (!value.VerifyTypeIs(Value::STRING, err))
68    return LabelPattern();
69
70  base::StringPiece str(value.string_value());
71  if (str.empty()) {
72    *err = Err(value, "Label pattern must not be empty.");
73    return LabelPattern();
74  }
75
76  // If there's no wildcard, this is specifying an exact label, use the
77  // label resolution code to get all the implicit name stuff.
78  size_t star = str.find('*');
79  if (star == std::string::npos) {
80    Label label = Label::Resolve(current_dir, Label(), value, err);
81    if (err->has_error())
82      return LabelPattern();
83
84    // Toolchain.
85    Label toolchain_label;
86    if (!label.toolchain_dir().is_null() || !label.toolchain_name().empty())
87      toolchain_label = label.GetToolchainLabel();
88
89    return LabelPattern(MATCH, label.dir(), label.name(), toolchain_label);
90  }
91
92  // Wildcard case, need to split apart the label to see what it specifies.
93  Label toolchain_label;
94  size_t open_paren = str.find('(');
95  if (open_paren != std::string::npos) {
96    // Has a toolchain definition, extract inside the parens.
97    size_t close_paren = str.find(')', open_paren);
98    if (close_paren == std::string::npos) {
99      *err = Err(value, "No close paren when looking for toolchain name.");
100      return LabelPattern();
101    }
102
103    std::string toolchain_string =
104        str.substr(open_paren + 1, close_paren - open_paren - 1).as_string();
105    if (toolchain_string.find('*') != std::string::npos) {
106      *err = Err(value, "Can't have a wildcard in the toolchain.");
107      return LabelPattern();
108    }
109
110    // Parse the inside of the parens as a label for a toolchain.
111    Value value_for_toolchain(value.origin(), toolchain_string);
112    toolchain_label =
113        Label::Resolve(current_dir, Label(), value_for_toolchain, err);
114    if (err->has_error())
115      return LabelPattern();
116
117    // Trim off the toolchain for the processing below.
118    str = str.substr(0, open_paren);
119  }
120
121  // Extract path and name.
122  base::StringPiece path;
123  base::StringPiece name;
124  size_t colon = str.find(':');
125  if (colon == std::string::npos) {
126    path = base::StringPiece(str);
127  } else {
128    path = str.substr(0, colon);
129    name = str.substr(colon + 1);
130  }
131
132  // The path can have these forms:
133  //   1. <empty>  (use current dir)
134  //   2. <non wildcard stuff>  (send through directory resolution)
135  //   3. <non wildcard stuff>*  (send stuff through dir resolution, note star)
136  //   4. *  (matches anything)
137  SourceDir dir;
138  bool has_path_star = false;
139  if (path.empty()) {
140    // Looks like ":foo".
141    dir = current_dir;
142  } else if (path[path.size() - 1] == '*') {
143    // Case 3 or 4 above.
144    has_path_star = true;
145
146    // Adjust path to contain everything but the star.
147    path = path.substr(0, path.size() - 1);
148
149    if (!path.empty() && path[path.size() - 1] != '/') {
150      // The input was "foo*" which is invalid.
151      *err = Err(value, "'*' must match full directories in a label pattern.",
152          "You did \"foo*\" but this thing doesn't do general pattern\n"
153          "matching. Instead, you have to add a slash: \"foo/*\" to match\n"
154          "all targets in a directory hierarchy.");
155      return LabelPattern();
156    }
157  }
158
159  // Resolve the part of the path that's not the wildcard.
160  if (!path.empty()) {
161    // The non-wildcard stuff better not have a wildcard.
162    if (path.find('*') != base::StringPiece::npos) {
163      *err = Err(value, "Label patterns only support wildcard suffixes.",
164          "The pattern contained a '*' that wasn't at tne end.");
165      return LabelPattern();
166    }
167
168    // Resolve the non-wildcard stuff.
169    dir = current_dir.ResolveRelativeDir(path);
170    if (dir.is_null()) {
171      *err = Err(value, "Label pattern didn't resolve to a dir.",
172          "The directory name \"" + path.as_string() + "\" didn't\n"
173          "resolve to a directory.");
174      return LabelPattern();
175    }
176  }
177
178  // Resolve the name. At this point, we're doing wildcard matches so the
179  // name should either be empty ("foo/*") or a wildcard ("foo:*");
180  if (colon != std::string::npos && name != "*") {
181    *err = Err(value, "Invalid label pattern.",
182        "You seem to be using the wildcard more generally that is supported.\n"
183        "Did you mean \"foo:*\" to match everything in the file, or\n"
184        "\"./*\" to recursively match everything in the currend subtree.");
185    return LabelPattern();
186  }
187
188  Type type;
189  if (has_path_star) {
190    // We know there's a wildcard, so if the name is empty it looks like
191    // "foo/*".
192    type = RECURSIVE_DIRECTORY;
193  } else {
194    // Everything else should be of the form "foo:*".
195    type = DIRECTORY;
196  }
197
198  // When we're doing wildcard matching, the name is always empty.
199  return LabelPattern(type, dir, base::StringPiece(), toolchain_label);
200}
201
202bool LabelPattern::Matches(const Label& label) const {
203  if (!toolchain_.is_null()) {
204    // Toolchain must match exactly.
205    if (toolchain_.dir() != label.toolchain_dir() ||
206        toolchain_.name() != label.toolchain_name())
207      return false;
208  }
209
210  switch (type_) {
211    case MATCH:
212      return label.name() == name_ && label.dir() == dir_;
213    case DIRECTORY:
214      // The directories must match exactly.
215      return label.dir() == dir_;
216    case RECURSIVE_DIRECTORY:
217      // Our directory must be a prefix of the input label for recursive.
218      return label.dir().value().compare(0, dir_.value().size(), dir_.value())
219          == 0;
220    default:
221      NOTREACHED();
222      return false;
223  }
224}
225
226std::string LabelPattern::Describe() const {
227  std::string result;
228
229  switch (type()) {
230    case MATCH:
231      result = DirectoryWithNoLastSlash(dir()) + ":" + name();
232      break;
233    case DIRECTORY:
234      result = DirectoryWithNoLastSlash(dir()) + ":*";
235      break;
236    case RECURSIVE_DIRECTORY:
237      result = dir().value() + "*";
238      break;
239  }
240
241  if (!toolchain_.is_null()) {
242    result.push_back('(');
243    result.append(toolchain_.GetUserVisibleName(false));
244    result.push_back(')');
245  }
246  return result;
247}
248