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