Index: tools/gn/label.cc |
diff --git a/tools/gn/label.cc b/tools/gn/label.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..f9b48da3c77eb54856cb80bd98d0714ea40119d2 |
--- /dev/null |
+++ b/tools/gn/label.cc |
@@ -0,0 +1,263 @@ |
+// 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 "tools/gn/err.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 locaton. |
+ if (input.empty()) { |
+ *result = current_dir; |
+ return true; |
+ } |
+ |
+ // Don't allow directories to start with a single slash. All labels must be |
+ // in the source root. |
+ if (input[0] == '/' && (input.size() == 1 || input[1] != '/')) { |
+ *err = Err(input_value, "Label can't start with a single slash", |
+ "Labels must be either relative (no slash at the beginning) or be " |
+ "absolute\ninside the source root (two slashes at the beginning)."); |
+ return false; |
+ } |
+ |
+ *result = current_dir.ResolveRelativeDir(input); |
+ 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() <= 1) { |
+ *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 path_separator = input.find_first_of(":("); |
+ 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, NULL, NULL, err); |
+ } |
+ } |
+ return true; |
+} |
+ |
+} // namespace |
+ |
+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() { |
+} |
+ |
+// 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_, |
+ SourceDir(), base::StringPiece()); |
+} |
+ |
+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); |
+} |