// 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" -> :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() = default; 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() = default; // 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); }