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"
7 #include "base/logging.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "build/build_config.h"
11 #include "tools/gn/location.h"
12 #include "tools/gn/source_dir.h"
17 // The given dot is just part of a filename and is not special.
20 // The given dot is the current directory.
23 // The given dot is the first of a double dot that should take us up one.
27 // When we find a dot, this function is called with the character following
28 // that dot to see what it is. The return value indicates what type this dot is
29 // (see above). This code handles the case where the dot is at the end of the
32 // |*consumed_len| will contain the number of characters in the input that
33 // express what we found.
34 DotDisposition
ClassifyAfterDot(const std::string
& path
,
36 size_t* consumed_len
) {
37 if (after_dot
== path
.size()) {
38 // Single dot at the end.
42 if (path
[after_dot
] == '/') {
43 // Single dot followed by a slash.
44 *consumed_len
= 2; // Consume the slash
48 if (path
[after_dot
] == '.') {
50 if (after_dot
+ 1 == path
.size()) {
51 // Double dot at the end.
55 if (path
[after_dot
+ 1] == '/') {
56 // Double dot folowed by a slash.
62 // The dots are followed by something else, not a directory.
64 return NOT_A_DIRECTORY
;
68 inline char NormalizeWindowsPathChar(char c
) {
71 return base::ToLowerASCII(c
);
74 // Attempts to do a case and slash-insensitive comparison of two 8-bit Windows
76 bool AreAbsoluteWindowsPathsEqual(const base::StringPiece
& a
,
77 const base::StringPiece
& b
) {
78 if (a
.size() != b
.size())
81 // For now, just do a case-insensitive ASCII comparison. We could convert to
82 // UTF-16 and use ICU if necessary. Or maybe base::strcasecmp is good enough?
83 for (size_t i
= 0; i
< a
.size(); i
++) {
84 if (NormalizeWindowsPathChar(a
[i
]) != NormalizeWindowsPathChar(b
[i
]))
90 bool DoesBeginWindowsDriveLetter(const base::StringPiece
& path
) {
94 // Check colon first, this will generally fail fastest.
99 if (!((path
[0] >= 'A' && path
[0] <= 'Z') ||
100 path
[0] >= 'a' && path
[0] <= 'z'))
103 if (path
[2] != '/' && path
[2] != '\\')
111 SourceFileType
GetSourceFileType(const SourceFile
& file
,
112 Settings::TargetOS os
) {
113 base::StringPiece extension
= FindExtension(&file
.value());
114 if (extension
== "cc" || extension
== "cpp" || extension
== "cxx")
116 if (extension
== "h")
118 if (extension
== "c")
123 if (extension
== "m")
125 if (extension
== "mm")
130 if (extension
== "rc")
132 // TODO(brettw) asm files.
139 if (os
!= Settings::WIN
) {
140 if (extension
== "S")
144 return SOURCE_UNKNOWN
;
147 const char* GetExtensionForOutputType(Target::OutputType type
,
148 Settings::TargetOS os
) {
152 case Target::EXECUTABLE
:
154 case Target::SHARED_LIBRARY
:
156 case Target::STATIC_LIBRARY
:
165 case Target::EXECUTABLE
:
167 case Target::SHARED_LIBRARY
:
168 return "dll.lib"; // Extension of import library.
169 case Target::STATIC_LIBRARY
:
176 case Settings::LINUX
:
178 case Target::EXECUTABLE
:
180 case Target::SHARED_LIBRARY
:
182 case Target::STATIC_LIBRARY
:
195 std::string
FilePathToUTF8(const base::FilePath
& path
) {
197 return WideToUTF8(path
.value());
203 base::FilePath
UTF8ToFilePath(const base::StringPiece
& sp
) {
205 return base::FilePath(UTF8ToWide(sp
));
207 return base::FilePath(sp
.as_string());
211 size_t FindExtensionOffset(const std::string
& path
) {
212 for (int i
= static_cast<int>(path
.size()); i
>= 0; i
--) {
218 return std::string::npos
;
221 base::StringPiece
FindExtension(const std::string
* path
) {
222 size_t extension_offset
= FindExtensionOffset(*path
);
223 if (extension_offset
== std::string::npos
)
224 return base::StringPiece();
225 return base::StringPiece(&path
->data()[extension_offset
],
226 path
->size() - extension_offset
);
229 size_t FindFilenameOffset(const std::string
& path
) {
230 for (int i
= static_cast<int>(path
.size()) - 1; i
>= 0; i
--) {
234 return 0; // No filename found means everything was the filename.
237 base::StringPiece
FindFilename(const std::string
* path
) {
238 size_t filename_offset
= FindFilenameOffset(*path
);
239 if (filename_offset
== 0)
240 return base::StringPiece(*path
); // Everything is the file name.
241 return base::StringPiece(&(*path
).data()[filename_offset
],
242 path
->size() - filename_offset
);
245 base::StringPiece
FindFilenameNoExtension(const std::string
* path
) {
247 return base::StringPiece();
248 size_t filename_offset
= FindFilenameOffset(*path
);
249 size_t extension_offset
= FindExtensionOffset(*path
);
252 if (extension_offset
== std::string::npos
)
253 name_len
= path
->size() - filename_offset
;
255 name_len
= extension_offset
- filename_offset
- 1;
257 return base::StringPiece(&(*path
).data()[filename_offset
], name_len
);
260 void RemoveFilename(std::string
* path
) {
261 path
->resize(FindFilenameOffset(*path
));
264 bool EndsWithSlash(const std::string
& s
) {
265 return !s
.empty() && s
[s
.size() - 1] == '/';
268 base::StringPiece
FindDir(const std::string
* path
) {
269 size_t filename_offset
= FindFilenameOffset(*path
);
270 if (filename_offset
== 0u)
271 return base::StringPiece();
272 return base::StringPiece(path
->data(), filename_offset
);
275 bool EnsureStringIsInOutputDir(const SourceDir
& dir
,
276 const std::string
& str
,
277 const Value
& originating
,
279 // The last char of the dir will be a slash. We don't care if the input ends
280 // in a slash or not, so just compare up until there.
282 // This check will be wrong for all proper prefixes "e.g. "/output" will
283 // match "/out" but we don't really care since this is just a sanity check.
284 const std::string
& dir_str
= dir
.value();
285 if (str
.compare(0, dir_str
.length() - 1, dir_str
, 0, dir_str
.length() - 1)
287 *err
= Err(originating
, "File not inside output directory.",
288 "The given file should be in the output directory. Normally you would "
289 "specify\n\"$target_output_dir/foo\" or "
290 "\"$target_gen_dir/foo\". I interpreted this as\n\""
297 bool IsPathAbsolute(const base::StringPiece
& path
) {
301 if (path
[0] != '/') {
303 // Check for Windows system paths like "C:\foo".
304 if (path
.size() > 2 &&
305 path
[1] == ':' && (path
[2] == '/' || path
[2] == '\\'))
308 return false; // Doesn't begin with a slash, is relative.
311 if (path
.size() > 1 && path
[1] == '/')
312 return false; // Double slash at the beginning means source-relative.
317 bool MakeAbsolutePathRelativeIfPossible(const base::StringPiece
& source_root
,
318 const base::StringPiece
& path
,
320 DCHECK(IsPathAbsolute(source_root
));
321 DCHECK(IsPathAbsolute(path
));
325 if (source_root
.size() > path
.size())
326 return false; // The source root is longer: the path can never be inside.
329 // Source root should be canonical on Windows.
330 DCHECK(source_root
.size() > 2 && source_root
[0] != '/' &&
331 source_root
[1] == ':' && source_root
[2] =='\\');
333 size_t after_common_index
= std::string::npos
;
334 if (DoesBeginWindowsDriveLetter(path
)) {
336 if (AreAbsoluteWindowsPathsEqual(source_root
,
337 path
.substr(0, source_root
.size())))
338 after_common_index
= source_root
.size();
341 } else if (path
[0] == '/' && source_root
.size() <= path
.size() - 1 &&
342 DoesBeginWindowsDriveLetter(path
.substr(1))) {
344 if (AreAbsoluteWindowsPathsEqual(source_root
,
345 path
.substr(1, source_root
.size())))
346 after_common_index
= source_root
.size() + 1;
353 // If we get here, there's a match and after_common_index identifies the
356 // The base may or may not have a trailing slash, so skip all slashes from
357 // the path after our prefix match.
358 size_t first_after_slash
= after_common_index
;
359 while (first_after_slash
< path
.size() &&
360 (path
[first_after_slash
] == '/' || path
[first_after_slash
] == '\\'))
363 dest
->assign("//"); // Result is source root relative.
364 dest
->append(&path
.data()[first_after_slash
],
365 path
.size() - first_after_slash
);
370 // On non-Windows this is easy. Since we know both are absolute, just do a
372 if (path
.substr(0, source_root
.size()) == source_root
) {
373 // The base may or may not have a trailing slash, so skip all slashes from
374 // the path after our prefix match.
375 size_t first_after_slash
= source_root
.size();
376 while (first_after_slash
< path
.size() && path
[first_after_slash
] == '/')
379 dest
->assign("//"); // Result is source root relative.
380 dest
->append(&path
.data()[first_after_slash
],
381 path
.size() - first_after_slash
);
388 std::string
InvertDir(const SourceDir
& path
) {
389 const std::string value
= path
.value();
391 return std::string();
393 DCHECK(value
[0] == '/');
394 size_t begin_index
= 1;
396 // If the input begins with two slashes, skip over both (this is a
397 // source-relative dir).
398 if (value
.size() > 1 && value
[1] == '/')
402 for (size_t i
= begin_index
; i
< value
.size(); i
++) {
409 void NormalizePath(std::string
* path
) {
410 char* pathbuf
= path
->empty() ? NULL
: &(*path
)[0];
412 // top_index is the first character we can modify in the path. Anything
413 // before this indicates where the path is relative to.
414 size_t top_index
= 0;
415 bool is_relative
= true;
416 if (!path
->empty() && pathbuf
[0] == '/') {
419 if (path
->size() > 1 && pathbuf
[1] == '/') {
420 // Two leading slashes, this is a path into the source dir.
423 // One leading slash, this is a system-absolute path.
428 size_t dest_i
= top_index
;
429 for (size_t src_i
= top_index
; src_i
< path
->size(); /* nothing */) {
430 if (pathbuf
[src_i
] == '.') {
431 if (src_i
== 0 || pathbuf
[src_i
- 1] == '/') {
432 // Slash followed by a dot, see if it's something special.
434 switch (ClassifyAfterDot(*path
, src_i
+ 1, &consumed_len
)) {
435 case NOT_A_DIRECTORY
:
436 // Copy the dot to the output, it means nothing special.
437 pathbuf
[dest_i
++] = pathbuf
[src_i
++];
440 // Current directory, just skip the input.
441 src_i
+= consumed_len
;
444 // Back up over previous directory component. If we're already
445 // at the top, preserve the "..".
446 if (dest_i
> top_index
) {
447 // The previous char was a slash, remove it.
451 if (dest_i
== top_index
) {
453 // We're already at the beginning of a relative input, copy the
454 // ".." and continue. We need the trailing slash if there was
455 // one before (otherwise we're at the end of the input).
456 pathbuf
[dest_i
++] = '.';
457 pathbuf
[dest_i
++] = '.';
458 if (consumed_len
== 3)
459 pathbuf
[dest_i
++] = '/';
461 // This also makes a new "root" that we can't delete by going
462 // up more levels. Otherwise "../.." would collapse to
466 // Otherwise we're at the beginning of an absolute path. Don't
467 // allow ".." to go up another level and just eat it.
469 // Just find the previous slash or the beginning of input.
470 while (dest_i
> 0 && pathbuf
[dest_i
- 1] != '/')
473 src_i
+= consumed_len
;
476 // Dot not preceeded by a slash, copy it literally.
477 pathbuf
[dest_i
++] = pathbuf
[src_i
++];
479 } else if (pathbuf
[src_i
] == '/') {
480 if (src_i
> 0 && pathbuf
[src_i
- 1] == '/') {
481 // Two slashes in a row, skip over it.
484 // Just one slash, copy it.
485 pathbuf
[dest_i
++] = pathbuf
[src_i
++];
488 // Input nothing special, just copy it.
489 pathbuf
[dest_i
++] = pathbuf
[src_i
++];
492 path
->resize(dest_i
);
495 void ConvertPathToSystem(std::string
* path
) {
497 for (size_t i
= 0; i
< path
->size(); i
++) {
498 if ((*path
)[i
] == '/')
504 std::string
PathToSystem(const std::string
& path
) {
505 std::string
ret(path
);
506 ConvertPathToSystem(&ret
);