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/filesystem_utils.h" 6 7 #include <algorithm> 8 9 #include "base/logging.h" 10 #include "base/strings/string_util.h" 11 #include "base/strings/utf_string_conversions.h" 12 #include "build/build_config.h" 13 #include "tools/gn/location.h" 14 #include "tools/gn/settings.h" 15 #include "tools/gn/source_dir.h" 16 17 namespace { 18 19 enum DotDisposition { 20 // The given dot is just part of a filename and is not special. 21 NOT_A_DIRECTORY, 22 23 // The given dot is the current directory. 24 DIRECTORY_CUR, 25 26 // The given dot is the first of a double dot that should take us up one. 27 DIRECTORY_UP 28 }; 29 30 // When we find a dot, this function is called with the character following 31 // that dot to see what it is. The return value indicates what type this dot is 32 // (see above). This code handles the case where the dot is at the end of the 33 // input. 34 // 35 // |*consumed_len| will contain the number of characters in the input that 36 // express what we found. 37 DotDisposition ClassifyAfterDot(const std::string& path, 38 size_t after_dot, 39 size_t* consumed_len) { 40 if (after_dot == path.size()) { 41 // Single dot at the end. 42 *consumed_len = 1; 43 return DIRECTORY_CUR; 44 } 45 if (path[after_dot] == '/') { 46 // Single dot followed by a slash. 47 *consumed_len = 2; // Consume the slash 48 return DIRECTORY_CUR; 49 } 50 51 if (path[after_dot] == '.') { 52 // Two dots. 53 if (after_dot + 1 == path.size()) { 54 // Double dot at the end. 55 *consumed_len = 2; 56 return DIRECTORY_UP; 57 } 58 if (path[after_dot + 1] == '/') { 59 // Double dot folowed by a slash. 60 *consumed_len = 3; 61 return DIRECTORY_UP; 62 } 63 } 64 65 // The dots are followed by something else, not a directory. 66 *consumed_len = 1; 67 return NOT_A_DIRECTORY; 68 } 69 70 #if defined(OS_WIN) 71 inline char NormalizeWindowsPathChar(char c) { 72 if (c == '/') 73 return '\\'; 74 return base::ToLowerASCII(c); 75 } 76 77 // Attempts to do a case and slash-insensitive comparison of two 8-bit Windows 78 // paths. 79 bool AreAbsoluteWindowsPathsEqual(const base::StringPiece& a, 80 const base::StringPiece& b) { 81 if (a.size() != b.size()) 82 return false; 83 84 // For now, just do a case-insensitive ASCII comparison. We could convert to 85 // UTF-16 and use ICU if necessary. Or maybe base::strcasecmp is good enough? 86 for (size_t i = 0; i < a.size(); i++) { 87 if (NormalizeWindowsPathChar(a[i]) != NormalizeWindowsPathChar(b[i])) 88 return false; 89 } 90 return true; 91 } 92 93 bool DoesBeginWindowsDriveLetter(const base::StringPiece& path) { 94 if (path.size() < 3) 95 return false; 96 97 // Check colon first, this will generally fail fastest. 98 if (path[1] != ':') 99 return false; 100 101 // Check drive letter 102 if (!((path[0] >= 'A' && path[0] <= 'Z') || 103 path[0] >= 'a' && path[0] <= 'z')) 104 return false; 105 106 if (path[2] != '/' && path[2] != '\\') 107 return false; 108 return true; 109 } 110 #endif 111 112 } // namespace 113 114 SourceFileType GetSourceFileType(const SourceFile& file, 115 Settings::TargetOS os) { 116 base::StringPiece extension = FindExtension(&file.value()); 117 if (extension == "cc" || extension == "cpp" || extension == "cxx") 118 return SOURCE_CC; 119 if (extension == "h") 120 return SOURCE_H; 121 if (extension == "c") 122 return SOURCE_C; 123 124 switch (os) { 125 case Settings::MAC: 126 if (extension == "m") 127 return SOURCE_M; 128 if (extension == "mm") 129 return SOURCE_MM; 130 break; 131 132 case Settings::WIN: 133 if (extension == "rc") 134 return SOURCE_RC; 135 // TODO(brettw) asm files. 136 break; 137 138 default: 139 break; 140 } 141 142 if (os != Settings::WIN) { 143 if (extension == "S") 144 return SOURCE_S; 145 } 146 147 return SOURCE_UNKNOWN; 148 } 149 150 const char* GetExtensionForOutputType(Target::OutputType type, 151 Settings::TargetOS os) { 152 switch (os) { 153 case Settings::MAC: 154 switch (type) { 155 case Target::EXECUTABLE: 156 return ""; 157 case Target::SHARED_LIBRARY: 158 return "dylib"; 159 case Target::STATIC_LIBRARY: 160 return "a"; 161 default: 162 NOTREACHED(); 163 } 164 break; 165 166 case Settings::WIN: 167 switch (type) { 168 case Target::EXECUTABLE: 169 return "exe"; 170 case Target::SHARED_LIBRARY: 171 return "dll.lib"; // Extension of import library. 172 case Target::STATIC_LIBRARY: 173 return "lib"; 174 default: 175 NOTREACHED(); 176 } 177 break; 178 179 case Settings::LINUX: 180 switch (type) { 181 case Target::EXECUTABLE: 182 return ""; 183 case Target::SHARED_LIBRARY: 184 return "so"; 185 case Target::STATIC_LIBRARY: 186 return "a"; 187 default: 188 NOTREACHED(); 189 } 190 break; 191 192 default: 193 NOTREACHED(); 194 } 195 return ""; 196 } 197 198 std::string FilePathToUTF8(const base::FilePath::StringType& str) { 199 #if defined(OS_WIN) 200 return WideToUTF8(str); 201 #else 202 return str; 203 #endif 204 } 205 206 base::FilePath UTF8ToFilePath(const base::StringPiece& sp) { 207 #if defined(OS_WIN) 208 return base::FilePath(UTF8ToWide(sp)); 209 #else 210 return base::FilePath(sp.as_string()); 211 #endif 212 } 213 214 size_t FindExtensionOffset(const std::string& path) { 215 for (int i = static_cast<int>(path.size()); i >= 0; i--) { 216 if (path[i] == '/') 217 break; 218 if (path[i] == '.') 219 return i + 1; 220 } 221 return std::string::npos; 222 } 223 224 base::StringPiece FindExtension(const std::string* path) { 225 size_t extension_offset = FindExtensionOffset(*path); 226 if (extension_offset == std::string::npos) 227 return base::StringPiece(); 228 return base::StringPiece(&path->data()[extension_offset], 229 path->size() - extension_offset); 230 } 231 232 size_t FindFilenameOffset(const std::string& path) { 233 for (int i = static_cast<int>(path.size()) - 1; i >= 0; i--) { 234 if (path[i] == '/') 235 return i + 1; 236 } 237 return 0; // No filename found means everything was the filename. 238 } 239 240 base::StringPiece FindFilename(const std::string* path) { 241 size_t filename_offset = FindFilenameOffset(*path); 242 if (filename_offset == 0) 243 return base::StringPiece(*path); // Everything is the file name. 244 return base::StringPiece(&(*path).data()[filename_offset], 245 path->size() - filename_offset); 246 } 247 248 base::StringPiece FindFilenameNoExtension(const std::string* path) { 249 if (path->empty()) 250 return base::StringPiece(); 251 size_t filename_offset = FindFilenameOffset(*path); 252 size_t extension_offset = FindExtensionOffset(*path); 253 254 size_t name_len; 255 if (extension_offset == std::string::npos) 256 name_len = path->size() - filename_offset; 257 else 258 name_len = extension_offset - filename_offset - 1; 259 260 return base::StringPiece(&(*path).data()[filename_offset], name_len); 261 } 262 263 void RemoveFilename(std::string* path) { 264 path->resize(FindFilenameOffset(*path)); 265 } 266 267 bool EndsWithSlash(const std::string& s) { 268 return !s.empty() && s[s.size() - 1] == '/'; 269 } 270 271 base::StringPiece FindDir(const std::string* path) { 272 size_t filename_offset = FindFilenameOffset(*path); 273 if (filename_offset == 0u) 274 return base::StringPiece(); 275 return base::StringPiece(path->data(), filename_offset); 276 } 277 278 bool EnsureStringIsInOutputDir(const SourceDir& dir, 279 const std::string& str, 280 const Value& originating, 281 Err* err) { 282 // The last char of the dir will be a slash. We don't care if the input ends 283 // in a slash or not, so just compare up until there. 284 // 285 // This check will be wrong for all proper prefixes "e.g. "/output" will 286 // match "/out" but we don't really care since this is just a sanity check. 287 const std::string& dir_str = dir.value(); 288 if (str.compare(0, dir_str.length() - 1, dir_str, 0, dir_str.length() - 1) 289 != 0) { 290 *err = Err(originating, "File not inside output directory.", 291 "The given file should be in the output directory. Normally you would " 292 "specify\n\"$target_output_dir/foo\" or " 293 "\"$target_gen_dir/foo\". I interpreted this as\n\"" 294 + str + "\"."); 295 return false; 296 } 297 return true; 298 } 299 300 bool IsPathAbsolute(const base::StringPiece& path) { 301 if (path.empty()) 302 return false; 303 304 if (path[0] != '/') { 305 #if defined(OS_WIN) 306 // Check for Windows system paths like "C:\foo". 307 if (path.size() > 2 && 308 path[1] == ':' && (path[2] == '/' || path[2] == '\\')) 309 return true; 310 #endif 311 return false; // Doesn't begin with a slash, is relative. 312 } 313 314 if (path.size() > 1 && path[1] == '/') 315 return false; // Double slash at the beginning means source-relative. 316 317 return true; 318 } 319 320 bool MakeAbsolutePathRelativeIfPossible(const base::StringPiece& source_root, 321 const base::StringPiece& path, 322 std::string* dest) { 323 DCHECK(IsPathAbsolute(source_root)); 324 DCHECK(IsPathAbsolute(path)); 325 326 dest->clear(); 327 328 if (source_root.size() > path.size()) 329 return false; // The source root is longer: the path can never be inside. 330 331 #if defined(OS_WIN) 332 // Source root should be canonical on Windows. 333 DCHECK(source_root.size() > 2 && source_root[0] != '/' && 334 source_root[1] == ':' && source_root[2] =='\\'); 335 336 size_t after_common_index = std::string::npos; 337 if (DoesBeginWindowsDriveLetter(path)) { 338 // Handle "C:\foo" 339 if (AreAbsoluteWindowsPathsEqual(source_root, 340 path.substr(0, source_root.size()))) 341 after_common_index = source_root.size(); 342 else 343 return false; 344 } else if (path[0] == '/' && source_root.size() <= path.size() - 1 && 345 DoesBeginWindowsDriveLetter(path.substr(1))) { 346 // Handle "/C:/foo" 347 if (AreAbsoluteWindowsPathsEqual(source_root, 348 path.substr(1, source_root.size()))) 349 after_common_index = source_root.size() + 1; 350 else 351 return false; 352 } else { 353 return false; 354 } 355 356 // If we get here, there's a match and after_common_index identifies the 357 // part after it. 358 359 // The base may or may not have a trailing slash, so skip all slashes from 360 // the path after our prefix match. 361 size_t first_after_slash = after_common_index; 362 while (first_after_slash < path.size() && 363 (path[first_after_slash] == '/' || path[first_after_slash] == '\\')) 364 first_after_slash++; 365 366 dest->assign("//"); // Result is source root relative. 367 dest->append(&path.data()[first_after_slash], 368 path.size() - first_after_slash); 369 return true; 370 371 #else 372 373 // On non-Windows this is easy. Since we know both are absolute, just do a 374 // prefix check. 375 if (path.substr(0, source_root.size()) == source_root) { 376 // The base may or may not have a trailing slash, so skip all slashes from 377 // the path after our prefix match. 378 size_t first_after_slash = source_root.size(); 379 while (first_after_slash < path.size() && path[first_after_slash] == '/') 380 first_after_slash++; 381 382 dest->assign("//"); // Result is source root relative. 383 dest->append(&path.data()[first_after_slash], 384 path.size() - first_after_slash); 385 return true; 386 } 387 return false; 388 #endif 389 } 390 391 std::string InvertDir(const SourceDir& path) { 392 const std::string value = path.value(); 393 if (value.empty()) 394 return std::string(); 395 396 DCHECK(value[0] == '/'); 397 size_t begin_index = 1; 398 399 // If the input begins with two slashes, skip over both (this is a 400 // source-relative dir). 401 if (value.size() > 1 && value[1] == '/') 402 begin_index = 2; 403 404 std::string ret; 405 for (size_t i = begin_index; i < value.size(); i++) { 406 if (value[i] == '/') 407 ret.append("../"); 408 } 409 return ret; 410 } 411 412 void NormalizePath(std::string* path) { 413 char* pathbuf = path->empty() ? NULL : &(*path)[0]; 414 415 // top_index is the first character we can modify in the path. Anything 416 // before this indicates where the path is relative to. 417 size_t top_index = 0; 418 bool is_relative = true; 419 if (!path->empty() && pathbuf[0] == '/') { 420 is_relative = false; 421 422 if (path->size() > 1 && pathbuf[1] == '/') { 423 // Two leading slashes, this is a path into the source dir. 424 top_index = 2; 425 } else { 426 // One leading slash, this is a system-absolute path. 427 top_index = 1; 428 } 429 } 430 431 size_t dest_i = top_index; 432 for (size_t src_i = top_index; src_i < path->size(); /* nothing */) { 433 if (pathbuf[src_i] == '.') { 434 if (src_i == 0 || pathbuf[src_i - 1] == '/') { 435 // Slash followed by a dot, see if it's something special. 436 size_t consumed_len; 437 switch (ClassifyAfterDot(*path, src_i + 1, &consumed_len)) { 438 case NOT_A_DIRECTORY: 439 // Copy the dot to the output, it means nothing special. 440 pathbuf[dest_i++] = pathbuf[src_i++]; 441 break; 442 case DIRECTORY_CUR: 443 // Current directory, just skip the input. 444 src_i += consumed_len; 445 break; 446 case DIRECTORY_UP: 447 // Back up over previous directory component. If we're already 448 // at the top, preserve the "..". 449 if (dest_i > top_index) { 450 // The previous char was a slash, remove it. 451 dest_i--; 452 } 453 454 if (dest_i == top_index) { 455 if (is_relative) { 456 // We're already at the beginning of a relative input, copy the 457 // ".." and continue. We need the trailing slash if there was 458 // one before (otherwise we're at the end of the input). 459 pathbuf[dest_i++] = '.'; 460 pathbuf[dest_i++] = '.'; 461 if (consumed_len == 3) 462 pathbuf[dest_i++] = '/'; 463 464 // This also makes a new "root" that we can't delete by going 465 // up more levels. Otherwise "../.." would collapse to 466 // nothing. 467 top_index = dest_i; 468 } 469 // Otherwise we're at the beginning of an absolute path. Don't 470 // allow ".." to go up another level and just eat it. 471 } else { 472 // Just find the previous slash or the beginning of input. 473 while (dest_i > 0 && pathbuf[dest_i - 1] != '/') 474 dest_i--; 475 } 476 src_i += consumed_len; 477 } 478 } else { 479 // Dot not preceeded by a slash, copy it literally. 480 pathbuf[dest_i++] = pathbuf[src_i++]; 481 } 482 } else if (pathbuf[src_i] == '/') { 483 if (src_i > 0 && pathbuf[src_i - 1] == '/') { 484 // Two slashes in a row, skip over it. 485 src_i++; 486 } else { 487 // Just one slash, copy it. 488 pathbuf[dest_i++] = pathbuf[src_i++]; 489 } 490 } else { 491 // Input nothing special, just copy it. 492 pathbuf[dest_i++] = pathbuf[src_i++]; 493 } 494 } 495 path->resize(dest_i); 496 } 497 498 void ConvertPathToSystem(std::string* path) { 499 #if defined(OS_WIN) 500 for (size_t i = 0; i < path->size(); i++) { 501 if ((*path)[i] == '/') 502 (*path)[i] = '\\'; 503 } 504 #endif 505 } 506 507 std::string PathToSystem(const std::string& path) { 508 std::string ret(path); 509 ConvertPathToSystem(&ret); 510 return ret; 511 } 512 513 std::string RebaseSourceAbsolutePath(const std::string& input, 514 const SourceDir& dest_dir) { 515 CHECK(input.size() >= 2 && input[0] == '/' && input[1] == '/') 516 << "Input to rebase isn't source-absolute: " << input; 517 CHECK(dest_dir.is_source_absolute()) 518 << "Dir to rebase to isn't source-absolute: " << dest_dir.value(); 519 520 const std::string& dest = dest_dir.value(); 521 522 // Skip the common prefixes of the source and dest as long as they end in 523 // a [back]slash. 524 size_t common_prefix_len = 2; // The beginning two "//" are always the same. 525 size_t max_common_length = std::min(input.size(), dest.size()); 526 for (size_t i = common_prefix_len; i < max_common_length; i++) { 527 if ((input[i] == '/' || input[i] == '\\') && 528 (dest[i] == '/' || dest[i] == '\\')) 529 common_prefix_len = i + 1; 530 else if (input[i] != dest[i]) 531 break; 532 } 533 534 // Invert the dest dir starting from the end of the common prefix. 535 std::string ret; 536 for (size_t i = common_prefix_len; i < dest.size(); i++) { 537 if (dest[i] == '/' || dest[i] == '\\') 538 ret.append("../"); 539 } 540 541 // Append any remaining unique input. 542 ret.append(&input[common_prefix_len], input.size() - common_prefix_len); 543 544 // If the result is still empty, the paths are the same. 545 if (ret.empty()) 546 ret.push_back('.'); 547 548 return ret; 549 } 550 551 std::string DirectoryWithNoLastSlash(const SourceDir& dir) { 552 std::string ret; 553 554 if (dir.value().empty()) { 555 // Just keep input the same. 556 } else if (dir.value() == "/") { 557 ret.assign("/."); 558 } else if (dir.value() == "//") { 559 ret.assign("//."); 560 } else { 561 ret.assign(dir.value()); 562 ret.resize(ret.size() - 1); 563 } 564 return ret; 565 } 566 567 SourceDir GetToolchainOutputDir(const Settings* settings) { 568 const OutputFile& toolchain_subdir = settings->toolchain_output_subdir(); 569 570 std::string result = settings->build_settings()->build_dir().value(); 571 if (!toolchain_subdir.value().empty()) 572 result.append(toolchain_subdir.value()); 573 574 return SourceDir(SourceDir::SWAP_IN, &result); 575 } 576 577 SourceDir GetToolchainGenDir(const Settings* settings) { 578 const OutputFile& toolchain_subdir = settings->toolchain_output_subdir(); 579 580 std::string result = settings->build_settings()->build_dir().value(); 581 if (!toolchain_subdir.value().empty()) 582 result.append(toolchain_subdir.value()); 583 584 result.append("gen/"); 585 return SourceDir(SourceDir::SWAP_IN, &result); 586 } 587 588 SourceDir GetOutputDirForSourceDir(const Settings* settings, 589 const SourceDir& source_dir) { 590 SourceDir toolchain = GetToolchainOutputDir(settings); 591 592 std::string ret; 593 toolchain.SwapValue(&ret); 594 ret.append("obj/"); 595 596 // The source dir should be source-absolute, so we trim off the two leading 597 // slashes to append to the toolchain object directory. 598 DCHECK(source_dir.is_source_absolute()); 599 ret.append(&source_dir.value()[2], source_dir.value().size() - 2); 600 601 return SourceDir(SourceDir::SWAP_IN, &ret); 602 } 603 604 SourceDir GetGenDirForSourceDir(const Settings* settings, 605 const SourceDir& source_dir) { 606 SourceDir toolchain = GetToolchainGenDir(settings); 607 608 std::string ret; 609 toolchain.SwapValue(&ret); 610 611 // The source dir should be source-absolute, so we trim off the two leading 612 // slashes to append to the toolchain object directory. 613 DCHECK(source_dir.is_source_absolute()); 614 ret.append(&source_dir.value()[2], source_dir.value().size() - 2); 615 616 return SourceDir(SourceDir::SWAP_IN, &ret); 617 } 618 619 SourceDir GetTargetOutputDir(const Target* target) { 620 return GetOutputDirForSourceDir(target->settings(), target->label().dir()); 621 } 622 623 SourceDir GetTargetGenDir(const Target* target) { 624 return GetGenDirForSourceDir(target->settings(), target->label().dir()); 625 } 626 627 SourceDir GetCurrentOutputDir(const Scope* scope) { 628 return GetOutputDirForSourceDir(scope->settings(), scope->GetSourceDir()); 629 } 630 631 SourceDir GetCurrentGenDir(const Scope* scope) { 632 return GetGenDirForSourceDir(scope->settings(), scope->GetSourceDir()); 633 } 634