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")
185 return SOURCE_UNKNOWN
;
188 const char* GetExtensionForOutputType(Target::OutputType type
,
189 Settings::TargetOS os
) {
193 case Target::EXECUTABLE
:
195 case Target::SHARED_LIBRARY
:
197 case Target::STATIC_LIBRARY
:
206 case Target::EXECUTABLE
:
208 case Target::SHARED_LIBRARY
:
209 return "dll.lib"; // Extension of import library.
210 case Target::STATIC_LIBRARY
:
217 case Settings::LINUX
:
219 case Target::EXECUTABLE
:
221 case Target::SHARED_LIBRARY
:
223 case Target::STATIC_LIBRARY
:
236 std::string
FilePathToUTF8(const base::FilePath::StringType
& str
) {
238 return base::WideToUTF8(str
);
244 base::FilePath
UTF8ToFilePath(const base::StringPiece
& sp
) {
246 return base::FilePath(base::UTF8ToWide(sp
));
248 return base::FilePath(sp
.as_string());
252 size_t FindExtensionOffset(const std::string
& path
) {
253 for (int i
= static_cast<int>(path
.size()); i
>= 0; i
--) {
254 if (IsSlash(path
[i
]))
259 return std::string::npos
;
262 base::StringPiece
FindExtension(const std::string
* path
) {
263 size_t extension_offset
= FindExtensionOffset(*path
);
264 if (extension_offset
== std::string::npos
)
265 return base::StringPiece();
266 return base::StringPiece(&path
->data()[extension_offset
],
267 path
->size() - extension_offset
);
270 size_t FindFilenameOffset(const std::string
& path
) {
271 for (int i
= static_cast<int>(path
.size()) - 1; i
>= 0; i
--) {
272 if (IsSlash(path
[i
]))
275 return 0; // No filename found means everything was the filename.
278 base::StringPiece
FindFilename(const std::string
* path
) {
279 size_t filename_offset
= FindFilenameOffset(*path
);
280 if (filename_offset
== 0)
281 return base::StringPiece(*path
); // Everything is the file name.
282 return base::StringPiece(&(*path
).data()[filename_offset
],
283 path
->size() - filename_offset
);
286 base::StringPiece
FindFilenameNoExtension(const std::string
* path
) {
288 return base::StringPiece();
289 size_t filename_offset
= FindFilenameOffset(*path
);
290 size_t extension_offset
= FindExtensionOffset(*path
);
293 if (extension_offset
== std::string::npos
)
294 name_len
= path
->size() - filename_offset
;
296 name_len
= extension_offset
- filename_offset
- 1;
298 return base::StringPiece(&(*path
).data()[filename_offset
], name_len
);
301 void RemoveFilename(std::string
* path
) {
302 path
->resize(FindFilenameOffset(*path
));
305 bool EndsWithSlash(const std::string
& s
) {
306 return !s
.empty() && IsSlash(s
[s
.size() - 1]);
309 base::StringPiece
FindDir(const std::string
* path
) {
310 size_t filename_offset
= FindFilenameOffset(*path
);
311 if (filename_offset
== 0u)
312 return base::StringPiece();
313 return base::StringPiece(path
->data(), filename_offset
);
316 bool EnsureStringIsInOutputDir(const SourceDir
& dir
,
317 const std::string
& str
,
318 const Value
& originating
,
320 // The last char of the dir will be a slash. We don't care if the input ends
321 // in a slash or not, so just compare up until there.
323 // This check will be wrong for all proper prefixes "e.g. "/output" will
324 // match "/out" but we don't really care since this is just a sanity check.
325 const std::string
& dir_str
= dir
.value();
326 if (str
.compare(0, dir_str
.length() - 1, dir_str
, 0, dir_str
.length() - 1)
328 *err
= Err(originating
, "File is not inside output directory.",
329 "The given file should be in the output directory. Normally you would "
330 "specify\n\"$target_out_dir/foo\" or "
331 "\"$target_gen_dir/foo\". I interpreted this as\n\""
338 bool IsPathAbsolute(const base::StringPiece
& path
) {
342 if (!IsSlash(path
[0])) {
344 // Check for Windows system paths like "C:\foo".
345 if (path
.size() > 2 && path
[1] == ':' && IsSlash(path
[2]))
348 return false; // Doesn't begin with a slash, is relative.
351 // Double forward slash at the beginning means source-relative (we don't
352 // allow backslashes for denoting this).
353 if (path
.size() > 1 && path
[1] == '/')
359 bool MakeAbsolutePathRelativeIfPossible(const base::StringPiece
& source_root
,
360 const base::StringPiece
& path
,
362 DCHECK(IsPathAbsolute(source_root
));
363 DCHECK(IsPathAbsolute(path
));
367 if (source_root
.size() > path
.size())
368 return false; // The source root is longer: the path can never be inside.
371 // Source root should be canonical on Windows. Note that the initial slash
372 // must be forward slash, but that the other ones can be either forward or
374 DCHECK(source_root
.size() > 2 && source_root
[0] != '/' &&
375 source_root
[1] == ':' && IsSlash(source_root
[2]));
377 size_t after_common_index
= std::string::npos
;
378 if (DoesBeginWindowsDriveLetter(path
)) {
380 if (AreAbsoluteWindowsPathsEqual(source_root
,
381 path
.substr(0, source_root
.size())))
382 after_common_index
= source_root
.size();
385 } else if (path
[0] == '/' && source_root
.size() <= path
.size() - 1 &&
386 DoesBeginWindowsDriveLetter(path
.substr(1))) {
388 if (AreAbsoluteWindowsPathsEqual(source_root
,
389 path
.substr(1, source_root
.size())))
390 after_common_index
= source_root
.size() + 1;
397 // If we get here, there's a match and after_common_index identifies the
400 // The base may or may not have a trailing slash, so skip all slashes from
401 // the path after our prefix match.
402 size_t first_after_slash
= after_common_index
;
403 while (first_after_slash
< path
.size() && IsSlash(path
[first_after_slash
]))
406 dest
->assign("//"); // Result is source root relative.
407 dest
->append(&path
.data()[first_after_slash
],
408 path
.size() - first_after_slash
);
413 // On non-Windows this is easy. Since we know both are absolute, just do a
415 if (path
.substr(0, source_root
.size()) == source_root
) {
416 // The base may or may not have a trailing slash, so skip all slashes from
417 // the path after our prefix match.
418 size_t first_after_slash
= source_root
.size();
419 while (first_after_slash
< path
.size() && IsSlash(path
[first_after_slash
]))
422 dest
->assign("//"); // Result is source root relative.
423 dest
->append(&path
.data()[first_after_slash
],
424 path
.size() - first_after_slash
);
431 std::string
InvertDir(const SourceDir
& path
) {
432 const std::string value
= path
.value();
434 return std::string();
436 DCHECK(value
[0] == '/');
437 size_t begin_index
= 1;
439 // If the input begins with two slashes, skip over both (this is a
440 // source-relative dir). These must be forward slashes only.
441 if (value
.size() > 1 && value
[1] == '/')
445 for (size_t i
= begin_index
; i
< value
.size(); i
++) {
446 if (IsSlash(value
[i
]))
452 void NormalizePath(std::string
* path
) {
453 char* pathbuf
= path
->empty() ? NULL
: &(*path
)[0];
455 // top_index is the first character we can modify in the path. Anything
456 // before this indicates where the path is relative to.
457 size_t top_index
= 0;
458 bool is_relative
= true;
459 if (!path
->empty() && pathbuf
[0] == '/') {
462 if (path
->size() > 1 && pathbuf
[1] == '/') {
463 // Two leading slashes, this is a path into the source dir.
466 // One leading slash, this is a system-absolute path.
471 size_t dest_i
= top_index
;
472 for (size_t src_i
= top_index
; src_i
< path
->size(); /* nothing */) {
473 if (pathbuf
[src_i
] == '.') {
474 if (src_i
== 0 || IsSlash(pathbuf
[src_i
- 1])) {
475 // Slash followed by a dot, see if it's something special.
477 switch (ClassifyAfterDot(*path
, src_i
+ 1, &consumed_len
)) {
478 case NOT_A_DIRECTORY
:
479 // Copy the dot to the output, it means nothing special.
480 pathbuf
[dest_i
++] = pathbuf
[src_i
++];
483 // Current directory, just skip the input.
484 src_i
+= consumed_len
;
487 // Back up over previous directory component. If we're already
488 // at the top, preserve the "..".
489 if (dest_i
> top_index
) {
490 // The previous char was a slash, remove it.
494 if (dest_i
== top_index
) {
496 // We're already at the beginning of a relative input, copy the
497 // ".." and continue. We need the trailing slash if there was
498 // one before (otherwise we're at the end of the input).
499 pathbuf
[dest_i
++] = '.';
500 pathbuf
[dest_i
++] = '.';
501 if (consumed_len
== 3)
502 pathbuf
[dest_i
++] = '/';
504 // This also makes a new "root" that we can't delete by going
505 // up more levels. Otherwise "../.." would collapse to
509 // Otherwise we're at the beginning of an absolute path. Don't
510 // allow ".." to go up another level and just eat it.
512 // Just find the previous slash or the beginning of input.
513 while (dest_i
> 0 && !IsSlash(pathbuf
[dest_i
- 1]))
516 src_i
+= consumed_len
;
519 // Dot not preceeded by a slash, copy it literally.
520 pathbuf
[dest_i
++] = pathbuf
[src_i
++];
522 } else if (IsSlash(pathbuf
[src_i
])) {
523 if (src_i
> 0 && IsSlash(pathbuf
[src_i
- 1])) {
524 // Two slashes in a row, skip over it.
527 // Just one slash, copy it, normalizing to foward slash.
528 pathbuf
[dest_i
] = '/';
533 // Input nothing special, just copy it.
534 pathbuf
[dest_i
++] = pathbuf
[src_i
++];
537 path
->resize(dest_i
);
540 void ConvertPathToSystem(std::string
* path
) {
542 for (size_t i
= 0; i
< path
->size(); i
++) {
543 if ((*path
)[i
] == '/')
549 std::string
RebaseSourceAbsolutePath(const std::string
& input
,
550 const SourceDir
& dest_dir
) {
551 CHECK(input
.size() >= 2 && input
[0] == '/' && input
[1] == '/')
552 << "Input to rebase isn't source-absolute: " << input
;
553 CHECK(dest_dir
.is_source_absolute())
554 << "Dir to rebase to isn't source-absolute: " << dest_dir
.value();
556 const std::string
& dest
= dest_dir
.value();
558 // Skip the common prefixes of the source and dest as long as they end in
560 size_t common_prefix_len
= 2; // The beginning two "//" are always the same.
561 size_t max_common_length
= std::min(input
.size(), dest
.size());
562 for (size_t i
= common_prefix_len
; i
< max_common_length
; i
++) {
563 if (IsSlash(input
[i
]) && IsSlash(dest
[i
]))
564 common_prefix_len
= i
+ 1;
565 else if (input
[i
] != dest
[i
])
569 // Invert the dest dir starting from the end of the common prefix.
571 for (size_t i
= common_prefix_len
; i
< dest
.size(); i
++) {
572 if (IsSlash(dest
[i
]))
576 // Append any remaining unique input.
577 ret
.append(&input
[common_prefix_len
], input
.size() - common_prefix_len
);
579 // If the result is still empty, the paths are the same.
586 std::string
DirectoryWithNoLastSlash(const SourceDir
& dir
) {
589 if (dir
.value().empty()) {
590 // Just keep input the same.
591 } else if (dir
.value() == "/") {
593 } else if (dir
.value() == "//") {
596 ret
.assign(dir
.value());
597 ret
.resize(ret
.size() - 1);
602 SourceDir
SourceDirForPath(const base::FilePath
& source_root
,
603 const base::FilePath
& path
) {
604 std::vector
<base::FilePath::StringType
> source_comp
=
605 GetPathComponents(source_root
);
606 std::vector
<base::FilePath::StringType
> path_comp
=
607 GetPathComponents(path
);
609 // See if path is inside the source root by looking for each of source root's
610 // components at the beginning of path.
611 bool is_inside_source
;
612 if (path_comp
.size() < source_comp
.size()) {
614 is_inside_source
= false;
616 is_inside_source
= true;
617 for (size_t i
= 0; i
< source_comp
.size(); i
++) {
618 if (!FilesystemStringsEqual(source_comp
[i
], path_comp
[i
])) {
619 is_inside_source
= false;
625 std::string result_str
;
626 size_t initial_path_comp_to_use
;
627 if (is_inside_source
) {
628 // Construct a source-relative path beginning in // and skip all of the
629 // shared directories.
631 initial_path_comp_to_use
= source_comp
.size();
633 // Not inside source code, construct a system-absolute path.
635 initial_path_comp_to_use
= 0;
638 for (size_t i
= initial_path_comp_to_use
; i
< path_comp
.size(); i
++) {
639 result_str
.append(FilePathToUTF8(path_comp
[i
]));
640 result_str
.push_back('/');
642 return SourceDir(result_str
);
645 SourceDir
SourceDirForCurrentDirectory(const base::FilePath
& source_root
) {
647 base::GetCurrentDirectory(&cd
);
648 return SourceDirForPath(source_root
, cd
);
651 SourceDir
GetToolchainOutputDir(const Settings
* settings
) {
652 const OutputFile
& toolchain_subdir
= settings
->toolchain_output_subdir();
654 std::string result
= settings
->build_settings()->build_dir().value();
655 if (!toolchain_subdir
.value().empty())
656 result
.append(toolchain_subdir
.value());
658 return SourceDir(SourceDir::SWAP_IN
, &result
);
661 SourceDir
GetToolchainGenDir(const Settings
* settings
) {
662 const OutputFile
& toolchain_subdir
= settings
->toolchain_output_subdir();
664 std::string result
= settings
->build_settings()->build_dir().value();
665 if (!toolchain_subdir
.value().empty())
666 result
.append(toolchain_subdir
.value());
668 result
.append("gen/");
669 return SourceDir(SourceDir::SWAP_IN
, &result
);
672 SourceDir
GetOutputDirForSourceDir(const Settings
* settings
,
673 const SourceDir
& source_dir
) {
674 SourceDir toolchain
= GetToolchainOutputDir(settings
);
677 toolchain
.SwapValue(&ret
);
680 // The source dir should be source-absolute, so we trim off the two leading
681 // slashes to append to the toolchain object directory.
682 DCHECK(source_dir
.is_source_absolute());
683 ret
.append(&source_dir
.value()[2], source_dir
.value().size() - 2);
685 return SourceDir(SourceDir::SWAP_IN
, &ret
);
688 SourceDir
GetGenDirForSourceDir(const Settings
* settings
,
689 const SourceDir
& source_dir
) {
690 SourceDir toolchain
= GetToolchainGenDir(settings
);
693 toolchain
.SwapValue(&ret
);
695 // The source dir should be source-absolute, so we trim off the two leading
696 // slashes to append to the toolchain object directory.
697 DCHECK(source_dir
.is_source_absolute());
698 ret
.append(&source_dir
.value()[2], source_dir
.value().size() - 2);
700 return SourceDir(SourceDir::SWAP_IN
, &ret
);
703 SourceDir
GetTargetOutputDir(const Target
* target
) {
704 return GetOutputDirForSourceDir(target
->settings(), target
->label().dir());
707 SourceDir
GetTargetGenDir(const Target
* target
) {
708 return GetGenDirForSourceDir(target
->settings(), target
->label().dir());
711 SourceDir
GetCurrentOutputDir(const Scope
* scope
) {
712 return GetOutputDirForSourceDir(scope
->settings(), scope
->GetSourceDir());
715 SourceDir
GetCurrentGenDir(const Scope
* scope
) {
716 return GetGenDirForSourceDir(scope
->settings(), scope
->GetSourceDir());