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.
5 #include "tools/gn/filesystem_utils.h"
9 #include "base/file_util.h"
10 #include "base/logging.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "build/build_config.h"
14 #include "tools/gn/location.h"
15 #include "tools/gn/settings.h"
16 #include "tools/gn/source_dir.h"
21 // The given dot is just part of a filename and is not special.
24 // The given dot is the current directory.
27 // The given dot is the first of a double dot that should take us up one.
31 // When we find a dot, this function is called with the character following
32 // that dot to see what it is. The return value indicates what type this dot is
33 // (see above). This code handles the case where the dot is at the end of the
36 // |*consumed_len| will contain the number of characters in the input that
37 // express what we found.
38 DotDisposition ClassifyAfterDot(const std::string& path,
40 size_t* consumed_len) {
41 if (after_dot == path.size()) {
42 // Single dot at the end.
46 if (IsSlash(path[after_dot])) {
47 // Single dot followed by a slash.
48 *consumed_len = 2; // Consume the slash
52 if (path[after_dot] == '.') {
54 if (after_dot + 1 == path.size()) {
55 // Double dot at the end.
59 if (IsSlash(path[after_dot + 1])) {
60 // Double dot folowed by a slash.
66 // The dots are followed by something else, not a directory.
68 return NOT_A_DIRECTORY;
72 inline char NormalizeWindowsPathChar(char c) {
75 return base::ToLowerASCII(c);
78 // Attempts to do a case and slash-insensitive comparison of two 8-bit Windows
80 bool AreAbsoluteWindowsPathsEqual(const base::StringPiece& a,
81 const base::StringPiece& b) {
82 if (a.size() != b.size())
85 // For now, just do a case-insensitive ASCII comparison. We could convert to
86 // UTF-16 and use ICU if necessary. Or maybe base::strcasecmp is good enough?
87 for (size_t i = 0; i < a.size(); i++) {
88 if (NormalizeWindowsPathChar(a[i]) != NormalizeWindowsPathChar(b[i]))
94 bool DoesBeginWindowsDriveLetter(const base::StringPiece& path) {
98 // Check colon first, this will generally fail fastest.
102 // Check drive letter
103 if (!((path[0] >= 'A' && path[0] <= 'Z') ||
104 path[0] >= 'a' && path[0] <= 'z'))
107 if (!IsSlash(path[2]))
113 // A wrapper around FilePath.GetComponents that works the way we need. This is
114 // not super efficient since it does some O(n) transformations on the path. If
115 // this is called a lot, we might want to optimize.
116 std::vector<base::FilePath::StringType> GetPathComponents(
117 const base::FilePath& path) {
118 std::vector<base::FilePath::StringType> result;
119 path.GetComponents(&result);
124 // GetComponents will preserve the "/" at the beginning, which confuses us.
125 // We don't expect to have relative paths in this function.
126 // Don't use IsSeparator since we always want to allow backslashes.
127 if (result[0] == FILE_PATH_LITERAL("/") ||
128 result[0] == FILE_PATH_LITERAL("\\"))
129 result.erase(result.begin());
132 // On Windows, GetComponents will give us [ "C:", "/", "foo" ], and we
133 // don't want the slash in there. This doesn't support input like "C:foo"
134 // which means foo relative to the current directory of the C drive but
135 // that's basically legacy DOS behavior we don't need to support.
136 if (result.size() >= 2 && result[1].size() == 1 && IsSlash(result[1][0]))
137 result.erase(result.begin() + 1);
143 // Provides the equivalent of == for filesystem strings, trying to do
144 // approximately the right thing with case.
145 bool FilesystemStringsEqual(const base::FilePath::StringType& a,
146 const base::FilePath::StringType& b) {
148 // Assume case-insensitive filesystems on Windows. We use the CompareString
149 // function to do a case-insensitive comparison based on the current locale
150 // (we don't want GN to depend on ICU which is large and requires data
151 // files). This isn't perfect, but getting this perfectly right is very
152 // difficult and requires I/O, and this comparison should cover 99.9999% of
155 // Note: The documentation for CompareString says it runs fastest on
156 // null-terminated strings with -1 passed for the length, so we do that here.
157 // There should not be embedded nulls in filesystem strings.
158 return ::CompareString(LOCALE_USER_DEFAULT, LINGUISTIC_IGNORECASE,
159 a.c_str(), -1, b.c_str(), -1) == CSTR_EQUAL;
161 // Assume case-sensitive filesystems on non-Windows.
168 SourceFileType GetSourceFileType(const SourceFile& file) {
169 base::StringPiece extension = FindExtension(&file.value());
170 if (extension == "cc" || extension == "cpp" || extension == "cxx")
172 if (extension == "h")
174 if (extension == "c")
176 if (extension == "m")
178 if (extension == "mm")
180 if (extension == "rc")
182 if (extension == "S" || extension == "s")
184 if (extension == "o" || extension == "obj")
187 return SOURCE_UNKNOWN;
190 const char* GetExtensionForOutputType(Target::OutputType type,
191 Settings::TargetOS os) {
195 case Target::EXECUTABLE:
197 case Target::SHARED_LIBRARY:
199 case Target::STATIC_LIBRARY:
208 case Target::EXECUTABLE:
210 case Target::SHARED_LIBRARY:
211 return "dll.lib"; // Extension of import library.
212 case Target::STATIC_LIBRARY:
219 case Settings::LINUX:
221 case Target::EXECUTABLE:
223 case Target::SHARED_LIBRARY:
225 case Target::STATIC_LIBRARY:
238 std::string FilePathToUTF8(const base::FilePath::StringType& str) {
240 return base::WideToUTF8(str);
246 base::FilePath UTF8ToFilePath(const base::StringPiece& sp) {
248 return base::FilePath(base::UTF8ToWide(sp));
250 return base::FilePath(sp.as_string());
254 size_t FindExtensionOffset(const std::string& path) {
255 for (int i = static_cast<int>(path.size()); i >= 0; i--) {
256 if (IsSlash(path[i]))
261 return std::string::npos;
264 base::StringPiece FindExtension(const std::string* path) {
265 size_t extension_offset = FindExtensionOffset(*path);
266 if (extension_offset == std::string::npos)
267 return base::StringPiece();
268 return base::StringPiece(&path->data()[extension_offset],
269 path->size() - extension_offset);
272 size_t FindFilenameOffset(const std::string& path) {
273 for (int i = static_cast<int>(path.size()) - 1; i >= 0; i--) {
274 if (IsSlash(path[i]))
277 return 0; // No filename found means everything was the filename.
280 base::StringPiece FindFilename(const std::string* path) {
281 size_t filename_offset = FindFilenameOffset(*path);
282 if (filename_offset == 0)
283 return base::StringPiece(*path); // Everything is the file name.
284 return base::StringPiece(&(*path).data()[filename_offset],
285 path->size() - filename_offset);
288 base::StringPiece FindFilenameNoExtension(const std::string* path) {
290 return base::StringPiece();
291 size_t filename_offset = FindFilenameOffset(*path);
292 size_t extension_offset = FindExtensionOffset(*path);
295 if (extension_offset == std::string::npos)
296 name_len = path->size() - filename_offset;
298 name_len = extension_offset - filename_offset - 1;
300 return base::StringPiece(&(*path).data()[filename_offset], name_len);
303 void RemoveFilename(std::string* path) {
304 path->resize(FindFilenameOffset(*path));
307 bool EndsWithSlash(const std::string& s) {
308 return !s.empty() && IsSlash(s[s.size() - 1]);
311 base::StringPiece FindDir(const std::string* path) {
312 size_t filename_offset = FindFilenameOffset(*path);
313 if (filename_offset == 0u)
314 return base::StringPiece();
315 return base::StringPiece(path->data(), filename_offset);
318 base::StringPiece FindLastDirComponent(const SourceDir& dir) {
319 const std::string& dir_string = dir.value();
321 if (dir_string.empty())
322 return base::StringPiece();
323 int cur = static_cast<int>(dir_string.size()) - 1;
324 DCHECK(dir_string[cur] == '/');
326 cur--; // Skip before the last slash.
328 for (; cur >= 0; cur--) {
329 if (dir_string[cur] == '/')
330 return base::StringPiece(&dir_string[cur + 1], end - cur - 1);
332 return base::StringPiece(&dir_string[0], end);
335 bool EnsureStringIsInOutputDir(const SourceDir& dir,
336 const std::string& str,
337 const Value& originating,
339 // The last char of the dir will be a slash. We don't care if the input ends
340 // in a slash or not, so just compare up until there.
342 // This check will be wrong for all proper prefixes "e.g. "/output" will
343 // match "/out" but we don't really care since this is just a sanity check.
344 const std::string& dir_str = dir.value();
345 if (str.compare(0, dir_str.length() - 1, dir_str, 0, dir_str.length() - 1)
347 *err = Err(originating, "File is not inside output directory.",
348 "The given file should be in the output directory. Normally you would "
349 "specify\n\"$target_out_dir/foo\" or "
350 "\"$target_gen_dir/foo\". I interpreted this as\n\""
357 bool IsPathAbsolute(const base::StringPiece& path) {
361 if (!IsSlash(path[0])) {
363 // Check for Windows system paths like "C:\foo".
364 if (path.size() > 2 && path[1] == ':' && IsSlash(path[2]))
367 return false; // Doesn't begin with a slash, is relative.
370 // Double forward slash at the beginning means source-relative (we don't
371 // allow backslashes for denoting this).
372 if (path.size() > 1 && path[1] == '/')
378 bool MakeAbsolutePathRelativeIfPossible(const base::StringPiece& source_root,
379 const base::StringPiece& path,
381 DCHECK(IsPathAbsolute(source_root));
382 DCHECK(IsPathAbsolute(path));
386 if (source_root.size() > path.size())
387 return false; // The source root is longer: the path can never be inside.
390 // Source root should be canonical on Windows. Note that the initial slash
391 // must be forward slash, but that the other ones can be either forward or
393 DCHECK(source_root.size() > 2 && source_root[0] != '/' &&
394 source_root[1] == ':' && IsSlash(source_root[2]));
396 size_t after_common_index = std::string::npos;
397 if (DoesBeginWindowsDriveLetter(path)) {
399 if (AreAbsoluteWindowsPathsEqual(source_root,
400 path.substr(0, source_root.size())))
401 after_common_index = source_root.size();
404 } else if (path[0] == '/' && source_root.size() <= path.size() - 1 &&
405 DoesBeginWindowsDriveLetter(path.substr(1))) {
407 if (AreAbsoluteWindowsPathsEqual(source_root,
408 path.substr(1, source_root.size())))
409 after_common_index = source_root.size() + 1;
416 // If we get here, there's a match and after_common_index identifies the
419 // The base may or may not have a trailing slash, so skip all slashes from
420 // the path after our prefix match.
421 size_t first_after_slash = after_common_index;
422 while (first_after_slash < path.size() && IsSlash(path[first_after_slash]))
425 dest->assign("//"); // Result is source root relative.
426 dest->append(&path.data()[first_after_slash],
427 path.size() - first_after_slash);
432 // On non-Windows this is easy. Since we know both are absolute, just do a
434 if (path.substr(0, source_root.size()) == source_root) {
435 // The base may or may not have a trailing slash, so skip all slashes from
436 // the path after our prefix match.
437 size_t first_after_slash = source_root.size();
438 while (first_after_slash < path.size() && IsSlash(path[first_after_slash]))
441 dest->assign("//"); // Result is source root relative.
442 dest->append(&path.data()[first_after_slash],
443 path.size() - first_after_slash);
450 std::string InvertDir(const SourceDir& path) {
451 const std::string value = path.value();
453 return std::string();
455 DCHECK(value[0] == '/');
456 size_t begin_index = 1;
458 // If the input begins with two slashes, skip over both (this is a
459 // source-relative dir). These must be forward slashes only.
460 if (value.size() > 1 && value[1] == '/')
464 for (size_t i = begin_index; i < value.size(); i++) {
465 if (IsSlash(value[i]))
471 void NormalizePath(std::string* path) {
472 char* pathbuf = path->empty() ? NULL : &(*path)[0];
474 // top_index is the first character we can modify in the path. Anything
475 // before this indicates where the path is relative to.
476 size_t top_index = 0;
477 bool is_relative = true;
478 if (!path->empty() && pathbuf[0] == '/') {
481 if (path->size() > 1 && pathbuf[1] == '/') {
482 // Two leading slashes, this is a path into the source dir.
485 // One leading slash, this is a system-absolute path.
490 size_t dest_i = top_index;
491 for (size_t src_i = top_index; src_i < path->size(); /* nothing */) {
492 if (pathbuf[src_i] == '.') {
493 if (src_i == 0 || IsSlash(pathbuf[src_i - 1])) {
494 // Slash followed by a dot, see if it's something special.
496 switch (ClassifyAfterDot(*path, src_i + 1, &consumed_len)) {
497 case NOT_A_DIRECTORY:
498 // Copy the dot to the output, it means nothing special.
499 pathbuf[dest_i++] = pathbuf[src_i++];
502 // Current directory, just skip the input.
503 src_i += consumed_len;
506 // Back up over previous directory component. If we're already
507 // at the top, preserve the "..".
508 if (dest_i > top_index) {
509 // The previous char was a slash, remove it.
513 if (dest_i == top_index) {
515 // We're already at the beginning of a relative input, copy the
516 // ".." and continue. We need the trailing slash if there was
517 // one before (otherwise we're at the end of the input).
518 pathbuf[dest_i++] = '.';
519 pathbuf[dest_i++] = '.';
520 if (consumed_len == 3)
521 pathbuf[dest_i++] = '/';
523 // This also makes a new "root" that we can't delete by going
524 // up more levels. Otherwise "../.." would collapse to
528 // Otherwise we're at the beginning of an absolute path. Don't
529 // allow ".." to go up another level and just eat it.
531 // Just find the previous slash or the beginning of input.
532 while (dest_i > 0 && !IsSlash(pathbuf[dest_i - 1]))
535 src_i += consumed_len;
538 // Dot not preceeded by a slash, copy it literally.
539 pathbuf[dest_i++] = pathbuf[src_i++];
541 } else if (IsSlash(pathbuf[src_i])) {
542 if (src_i > 0 && IsSlash(pathbuf[src_i - 1])) {
543 // Two slashes in a row, skip over it.
546 // Just one slash, copy it, normalizing to foward slash.
547 pathbuf[dest_i] = '/';
552 // Input nothing special, just copy it.
553 pathbuf[dest_i++] = pathbuf[src_i++];
556 path->resize(dest_i);
559 void ConvertPathToSystem(std::string* path) {
561 for (size_t i = 0; i < path->size(); i++) {
562 if ((*path)[i] == '/')
568 std::string RebaseSourceAbsolutePath(const std::string& input,
569 const SourceDir& dest_dir) {
570 CHECK(input.size() >= 2 && input[0] == '/' && input[1] == '/')
571 << "Input to rebase isn't source-absolute: " << input;
572 CHECK(dest_dir.is_source_absolute())
573 << "Dir to rebase to isn't source-absolute: " << dest_dir.value();
575 const std::string& dest = dest_dir.value();
577 // Skip the common prefixes of the source and dest as long as they end in
579 size_t common_prefix_len = 2; // The beginning two "//" are always the same.
580 size_t max_common_length = std::min(input.size(), dest.size());
581 for (size_t i = common_prefix_len; i < max_common_length; i++) {
582 if (IsSlash(input[i]) && IsSlash(dest[i]))
583 common_prefix_len = i + 1;
584 else if (input[i] != dest[i])
588 // Invert the dest dir starting from the end of the common prefix.
590 for (size_t i = common_prefix_len; i < dest.size(); i++) {
591 if (IsSlash(dest[i]))
595 // Append any remaining unique input.
596 ret.append(&input[common_prefix_len], input.size() - common_prefix_len);
598 // If the result is still empty, the paths are the same.
605 std::string DirectoryWithNoLastSlash(const SourceDir& dir) {
608 if (dir.value().empty()) {
609 // Just keep input the same.
610 } else if (dir.value() == "/") {
612 } else if (dir.value() == "//") {
615 ret.assign(dir.value());
616 ret.resize(ret.size() - 1);
621 SourceDir SourceDirForPath(const base::FilePath& source_root,
622 const base::FilePath& path) {
623 std::vector<base::FilePath::StringType> source_comp =
624 GetPathComponents(source_root);
625 std::vector<base::FilePath::StringType> path_comp =
626 GetPathComponents(path);
628 // See if path is inside the source root by looking for each of source root's
629 // components at the beginning of path.
630 bool is_inside_source;
631 if (path_comp.size() < source_comp.size()) {
633 is_inside_source = false;
635 is_inside_source = true;
636 for (size_t i = 0; i < source_comp.size(); i++) {
637 if (!FilesystemStringsEqual(source_comp[i], path_comp[i])) {
638 is_inside_source = false;
644 std::string result_str;
645 size_t initial_path_comp_to_use;
646 if (is_inside_source) {
647 // Construct a source-relative path beginning in // and skip all of the
648 // shared directories.
650 initial_path_comp_to_use = source_comp.size();
652 // Not inside source code, construct a system-absolute path.
654 initial_path_comp_to_use = 0;
657 for (size_t i = initial_path_comp_to_use; i < path_comp.size(); i++) {
658 result_str.append(FilePathToUTF8(path_comp[i]));
659 result_str.push_back('/');
661 return SourceDir(result_str);
664 SourceDir SourceDirForCurrentDirectory(const base::FilePath& source_root) {
666 base::GetCurrentDirectory(&cd);
667 return SourceDirForPath(source_root, cd);
670 SourceDir GetToolchainOutputDir(const Settings* settings) {
671 const OutputFile& toolchain_subdir = settings->toolchain_output_subdir();
673 std::string result = settings->build_settings()->build_dir().value();
674 if (!toolchain_subdir.value().empty())
675 result.append(toolchain_subdir.value());
677 return SourceDir(SourceDir::SWAP_IN, &result);
680 SourceDir GetToolchainGenDir(const Settings* settings) {
681 const OutputFile& toolchain_subdir = settings->toolchain_output_subdir();
683 std::string result = settings->build_settings()->build_dir().value();
684 if (!toolchain_subdir.value().empty())
685 result.append(toolchain_subdir.value());
687 result.append("gen/");
688 return SourceDir(SourceDir::SWAP_IN, &result);
691 SourceDir GetOutputDirForSourceDir(const Settings* settings,
692 const SourceDir& source_dir) {
693 SourceDir toolchain = GetToolchainOutputDir(settings);
696 toolchain.SwapValue(&ret);
699 // The source dir should be source-absolute, so we trim off the two leading
700 // slashes to append to the toolchain object directory.
701 DCHECK(source_dir.is_source_absolute());
702 ret.append(&source_dir.value()[2], source_dir.value().size() - 2);
704 return SourceDir(SourceDir::SWAP_IN, &ret);
707 SourceDir GetGenDirForSourceDir(const Settings* settings,
708 const SourceDir& source_dir) {
709 SourceDir toolchain = GetToolchainGenDir(settings);
712 toolchain.SwapValue(&ret);
714 // The source dir should be source-absolute, so we trim off the two leading
715 // slashes to append to the toolchain object directory.
716 DCHECK(source_dir.is_source_absolute());
717 ret.append(&source_dir.value()[2], source_dir.value().size() - 2);
719 return SourceDir(SourceDir::SWAP_IN, &ret);
722 SourceDir GetTargetOutputDir(const Target* target) {
723 return GetOutputDirForSourceDir(target->settings(), target->label().dir());
726 SourceDir GetTargetGenDir(const Target* target) {
727 return GetGenDirForSourceDir(target->settings(), target->label().dir());
730 SourceDir GetCurrentOutputDir(const Scope* scope) {
731 return GetOutputDirForSourceDir(scope->settings(), scope->GetSourceDir());
734 SourceDir GetCurrentGenDir(const Scope* scope) {
735 return GetGenDirForSourceDir(scope->settings(), scope->GetSourceDir());