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 "gn/filesystem_utils.h"
6
7 #include <algorithm>
8
9 #include "base/files/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 "gn/location.h"
14 #include "gn/settings.h"
15 #include "gn/source_dir.h"
16 #include "util/build_config.h"
17
18 #if defined(OS_WIN)
19 #include <windows.h>
20 #endif
21
22 namespace {
23
24 enum DotDisposition {
25 // The given dot is just part of a filename and is not special.
26 NOT_A_DIRECTORY,
27
28 // The given dot is the current directory.
29 DIRECTORY_CUR,
30
31 // The given dot is the first of a double dot that should take us up one.
32 DIRECTORY_UP
33 };
34
35 // When we find a dot, this function is called with the character following
36 // that dot to see what it is. The return value indicates what type this dot is
37 // (see above). This code handles the case where the dot is at the end of the
38 // input.
39 //
40 // |*consumed_len| will contain the number of characters in the input that
41 // express what we found.
ClassifyAfterDot(const std::string & path,size_t after_dot,size_t * consumed_len)42 DotDisposition ClassifyAfterDot(const std::string& path,
43 size_t after_dot,
44 size_t* consumed_len) {
45 if (after_dot == path.size()) {
46 // Single dot at the end.
47 *consumed_len = 1;
48 return DIRECTORY_CUR;
49 }
50 if (IsSlash(path[after_dot])) {
51 // Single dot followed by a slash.
52 *consumed_len = 2; // Consume the slash
53 return DIRECTORY_CUR;
54 }
55
56 if (path[after_dot] == '.') {
57 // Two dots.
58 if (after_dot + 1 == path.size()) {
59 // Double dot at the end.
60 *consumed_len = 2;
61 return DIRECTORY_UP;
62 }
63 if (IsSlash(path[after_dot + 1])) {
64 // Double dot folowed by a slash.
65 *consumed_len = 3;
66 return DIRECTORY_UP;
67 }
68 }
69
70 // The dots are followed by something else, not a directory.
71 *consumed_len = 1;
72 return NOT_A_DIRECTORY;
73 }
74
75 #if defined(OS_WIN)
NormalizeWindowsPathChar(char c)76 inline char NormalizeWindowsPathChar(char c) {
77 if (c == '/')
78 return '\\';
79 return base::ToLowerASCII(c);
80 }
81
82 // Attempts to do a case and slash-insensitive comparison of two 8-bit Windows
83 // paths.
AreAbsoluteWindowsPathsEqual(const std::string_view & a,const std::string_view & b)84 bool AreAbsoluteWindowsPathsEqual(const std::string_view& a,
85 const std::string_view& b) {
86 if (a.size() != b.size())
87 return false;
88
89 // For now, just do a case-insensitive ASCII comparison. We could convert to
90 // UTF-16 and use ICU if necessary.
91 for (size_t i = 0; i < a.size(); i++) {
92 if (NormalizeWindowsPathChar(a[i]) != NormalizeWindowsPathChar(b[i]))
93 return false;
94 }
95 return true;
96 }
97
DoesBeginWindowsDriveLetter(const std::string_view & path)98 bool DoesBeginWindowsDriveLetter(const std::string_view& path) {
99 if (path.size() < 3)
100 return false;
101
102 // Check colon first, this will generally fail fastest.
103 if (path[1] != ':')
104 return false;
105
106 // Check drive letter.
107 if (!base::IsAsciiAlpha(path[0]))
108 return false;
109
110 if (!IsSlash(path[2]))
111 return false;
112 return true;
113 }
114 #endif
115
116 // A wrapper around FilePath.GetComponents that works the way we need. This is
117 // not super efficient since it does some O(n) transformations on the path. If
118 // this is called a lot, we might want to optimize.
GetPathComponents(const base::FilePath & path)119 std::vector<base::FilePath::StringType> GetPathComponents(
120 const base::FilePath& path) {
121 std::vector<base::FilePath::StringType> result;
122 path.GetComponents(&result);
123
124 if (result.empty())
125 return result;
126
127 // GetComponents will preserve the "/" at the beginning, which confuses us.
128 // We don't expect to have relative paths in this function.
129 // Don't use IsSeparator since we always want to allow backslashes.
130 if (result[0] == FILE_PATH_LITERAL("/") ||
131 result[0] == FILE_PATH_LITERAL("\\"))
132 result.erase(result.begin());
133
134 #if defined(OS_WIN)
135 // On Windows, GetComponents will give us [ "C:", "/", "foo" ], and we
136 // don't want the slash in there. This doesn't support input like "C:foo"
137 // which means foo relative to the current directory of the C drive but
138 // that's basically legacy DOS behavior we don't need to support.
139 if (result.size() >= 2 && result[1].size() == 1 &&
140 IsSlash(static_cast<char>(result[1][0])))
141 result.erase(result.begin() + 1);
142 #endif
143
144 return result;
145 }
146
147 // Provides the equivalent of == for filesystem strings, trying to do
148 // approximately the right thing with case.
FilesystemStringsEqual(const base::FilePath::StringType & a,const base::FilePath::StringType & b)149 bool FilesystemStringsEqual(const base::FilePath::StringType& a,
150 const base::FilePath::StringType& b) {
151 #if defined(OS_WIN)
152 // Assume case-insensitive filesystems on Windows. We use the CompareString
153 // function to do a case-insensitive comparison based on the current locale
154 // (we don't want GN to depend on ICU which is large and requires data
155 // files). This isn't perfect, but getting this perfectly right is very
156 // difficult and requires I/O, and this comparison should cover 99.9999% of
157 // all cases.
158 //
159 // Note: The documentation for CompareString says it runs fastest on
160 // null-terminated strings with -1 passed for the length, so we do that here.
161 // There should not be embedded nulls in filesystem strings.
162 return ::CompareString(LOCALE_USER_DEFAULT, LINGUISTIC_IGNORECASE,
163 reinterpret_cast<LPCWSTR>(a.c_str()), -1,
164 reinterpret_cast<LPCWSTR>(b.c_str()),
165 -1) == CSTR_EQUAL;
166 #else
167 // Assume case-sensitive filesystems on non-Windows.
168 return a == b;
169 #endif
170 }
171
172 // Helper function for computing subdirectories in the build directory
173 // corresponding to absolute paths. This will try to resolve the absolute
174 // path as a source-relative path first, and otherwise it creates a
175 // special subdirectory for absolute paths to keep them from colliding with
176 // other generated sources and outputs.
AppendFixedAbsolutePathSuffix(const BuildSettings * build_settings,const SourceDir & source_dir,OutputFile * result)177 void AppendFixedAbsolutePathSuffix(const BuildSettings* build_settings,
178 const SourceDir& source_dir,
179 OutputFile* result) {
180 const std::string& build_dir = build_settings->build_dir().value();
181
182 if (base::StartsWith(source_dir.value(), build_dir,
183 base::CompareCase::SENSITIVE)) {
184 size_t build_dir_size = build_dir.size();
185 result->value().append(&source_dir.value()[build_dir_size],
186 source_dir.value().size() - build_dir_size);
187 } else {
188 result->value().append("ABS_PATH");
189 #if defined(OS_WIN)
190 // Windows absolute path contains ':' after drive letter. Remove it to
191 // avoid inserting ':' in the middle of path (eg. "ABS_PATH/C:/").
192 std::string src_dir_value = source_dir.value();
193 const auto colon_pos = src_dir_value.find(':');
194 if (colon_pos != std::string::npos)
195 src_dir_value.erase(src_dir_value.begin() + colon_pos);
196 #else
197 const std::string& src_dir_value = source_dir.value();
198 #endif
199 result->value().append(src_dir_value);
200 }
201 }
202
AbsPathLenWithNoTrailingSlash(const std::string_view & path)203 size_t AbsPathLenWithNoTrailingSlash(const std::string_view& path) {
204 size_t len = path.size();
205 #if defined(OS_WIN)
206 size_t min_len = 3;
207 #else
208 // On posix system. The minimal abs path is "/".
209 size_t min_len = 1;
210 #endif
211 for (; len > min_len && IsSlash(path[len - 1]); len--)
212 ;
213 return len;
214 }
215 } // namespace
216
FilePathToUTF8(const base::FilePath::StringType & str)217 std::string FilePathToUTF8(const base::FilePath::StringType& str) {
218 #if defined(OS_WIN)
219 return base::UTF16ToUTF8(str);
220 #else
221 return str;
222 #endif
223 }
224
UTF8ToFilePath(const std::string_view & sp)225 base::FilePath UTF8ToFilePath(const std::string_view& sp) {
226 #if defined(OS_WIN)
227 return base::FilePath(base::UTF8ToUTF16(sp));
228 #else
229 return base::FilePath(sp);
230 #endif
231 }
232
FindExtensionOffset(const std::string & path)233 size_t FindExtensionOffset(const std::string& path) {
234 for (int i = static_cast<int>(path.size()); i >= 0; i--) {
235 if (IsSlash(path[i]))
236 break;
237 if (path[i] == '.')
238 return i + 1;
239 }
240 return std::string::npos;
241 }
242
FindExtension(const std::string * path)243 std::string_view FindExtension(const std::string* path) {
244 size_t extension_offset = FindExtensionOffset(*path);
245 if (extension_offset == std::string::npos)
246 return std::string_view();
247 return std::string_view(&path->data()[extension_offset],
248 path->size() - extension_offset);
249 }
250
FindFilenameOffset(const std::string & path)251 size_t FindFilenameOffset(const std::string& path) {
252 for (int i = static_cast<int>(path.size()) - 1; i >= 0; i--) {
253 if (IsSlash(path[i]))
254 return i + 1;
255 }
256 return 0; // No filename found means everything was the filename.
257 }
258
FindFilename(const std::string * path)259 std::string_view FindFilename(const std::string* path) {
260 size_t filename_offset = FindFilenameOffset(*path);
261 if (filename_offset == 0)
262 return std::string_view(*path); // Everything is the file name.
263 return std::string_view(&(*path).data()[filename_offset],
264 path->size() - filename_offset);
265 }
266
FindFilenameNoExtension(const std::string * path)267 std::string_view FindFilenameNoExtension(const std::string* path) {
268 if (path->empty())
269 return std::string_view();
270 size_t filename_offset = FindFilenameOffset(*path);
271 size_t extension_offset = FindExtensionOffset(*path);
272
273 size_t name_len;
274 if (extension_offset == std::string::npos)
275 name_len = path->size() - filename_offset;
276 else
277 name_len = extension_offset - filename_offset - 1;
278
279 return std::string_view(&(*path).data()[filename_offset], name_len);
280 }
281
RemoveFilename(std::string * path)282 void RemoveFilename(std::string* path) {
283 path->resize(FindFilenameOffset(*path));
284 }
285
EndsWithSlash(const std::string & s)286 bool EndsWithSlash(const std::string& s) {
287 return !s.empty() && IsSlash(s[s.size() - 1]);
288 }
289
FindDir(const std::string * path)290 std::string_view FindDir(const std::string* path) {
291 size_t filename_offset = FindFilenameOffset(*path);
292 if (filename_offset == 0u)
293 return std::string_view();
294 return std::string_view(path->data(), filename_offset);
295 }
296
FindLastDirComponent(const SourceDir & dir)297 std::string_view FindLastDirComponent(const SourceDir& dir) {
298 const std::string& dir_string = dir.value();
299
300 if (dir_string.empty())
301 return std::string_view();
302 int cur = static_cast<int>(dir_string.size()) - 1;
303 DCHECK(dir_string[cur] == '/');
304 int end = cur;
305 cur--; // Skip before the last slash.
306
307 for (; cur >= 0; cur--) {
308 if (dir_string[cur] == '/')
309 return std::string_view(&dir_string[cur + 1], end - cur - 1);
310 }
311 return std::string_view(&dir_string[0], end);
312 }
313
IsStringInOutputDir(const SourceDir & output_dir,const std::string & str)314 bool IsStringInOutputDir(const SourceDir& output_dir, const std::string& str) {
315 // This check will be wrong for all proper prefixes "e.g. "/output" will
316 // match "/out" but we don't really care since this is just a sanity check.
317 const std::string& dir_str = output_dir.value();
318 return str.compare(0, dir_str.length(), dir_str) == 0;
319 }
320
EnsureStringIsInOutputDir(const SourceDir & output_dir,const std::string & str,const ParseNode * origin,Err * err)321 bool EnsureStringIsInOutputDir(const SourceDir& output_dir,
322 const std::string& str,
323 const ParseNode* origin,
324 Err* err) {
325 if (IsStringInOutputDir(output_dir, str))
326 return true; // Output directory is hardcoded.
327
328 *err = Err(
329 origin, "File is not inside output directory.",
330 "The given file should be in the output directory. Normally you would "
331 "specify\n\"$target_out_dir/foo\" or "
332 "\"$target_gen_dir/foo\". I interpreted this as\n\"" +
333 str + "\".");
334 return false;
335 }
336
IsPathAbsolute(const std::string_view & path)337 bool IsPathAbsolute(const std::string_view& path) {
338 if (path.empty())
339 return false;
340
341 if (!IsSlash(path[0])) {
342 #if defined(OS_WIN)
343 // Check for Windows system paths like "C:\foo".
344 if (path.size() > 2 && path[1] == ':' && IsSlash(path[2]))
345 return true;
346 #endif
347 return false; // Doesn't begin with a slash, is relative.
348 }
349
350 // Double forward slash at the beginning means source-relative (we don't
351 // allow backslashes for denoting this).
352 if (path.size() > 1 && path[1] == '/')
353 return false;
354
355 return true;
356 }
357
IsPathSourceAbsolute(const std::string_view & path)358 bool IsPathSourceAbsolute(const std::string_view& path) {
359 return (path.size() >= 2 && path[0] == '/' && path[1] == '/');
360 }
361
MakeAbsolutePathRelativeIfPossible(const std::string_view & source_root,const std::string_view & path,std::string * dest)362 bool MakeAbsolutePathRelativeIfPossible(const std::string_view& source_root,
363 const std::string_view& path,
364 std::string* dest) {
365 DCHECK(IsPathAbsolute(source_root));
366 DCHECK(IsPathAbsolute(path));
367
368 dest->clear();
369
370 // There is no specification of how many slashes may be at the end of
371 // source_root or path. Trim them off for easier string manipulation.
372 size_t path_len = AbsPathLenWithNoTrailingSlash(path);
373 size_t source_root_len = AbsPathLenWithNoTrailingSlash(source_root);
374
375 if (source_root_len > path_len)
376 return false; // The source root is longer: the path can never be inside.
377 #if defined(OS_WIN)
378 // Source root should be canonical on Windows. Note that the initial slash
379 // must be forward slash, but that the other ones can be either forward or
380 // backward.
381 DCHECK(source_root.size() > 2 && source_root[0] != '/' &&
382 source_root[1] == ':' && IsSlash(source_root[2]));
383
384 size_t after_common_index = std::string::npos;
385 if (DoesBeginWindowsDriveLetter(path)) {
386 // Handle "C:\foo"
387 if (AreAbsoluteWindowsPathsEqual(source_root.substr(0, source_root_len),
388 path.substr(0, source_root_len))) {
389 after_common_index = source_root_len;
390 if (path_len == source_root_len) {
391 *dest = "//";
392 return true;
393 }
394 } else {
395 return false;
396 }
397 } else if (path[0] == '/' && source_root_len <= path_len - 1 &&
398 DoesBeginWindowsDriveLetter(path.substr(1))) {
399 // Handle "/C:/foo"
400 if (AreAbsoluteWindowsPathsEqual(source_root.substr(0, source_root_len),
401 path.substr(1, source_root_len))) {
402 after_common_index = source_root_len + 1;
403 if (path_len + 1 == source_root_len) {
404 *dest = "//";
405 return true;
406 }
407 } else {
408 return false;
409 }
410 } else {
411 return false;
412 }
413
414 // If we get here, there's a match and after_common_index identifies the
415 // part after it.
416
417 if (!IsSlash(path[after_common_index])) {
418 // path is ${source-root}SUFIX/...
419 return false;
420 }
421 // A source-root relative path, The input may have an unknown number of
422 // slashes after the previous match. Skip over them.
423 size_t first_after_slash = after_common_index + 1;
424 while (first_after_slash < path_len && IsSlash(path[first_after_slash]))
425 first_after_slash++;
426 dest->assign("//"); // Result is source root relative.
427 dest->append(&path.data()[first_after_slash],
428 path.size() - first_after_slash);
429 return true;
430
431 #else
432
433 // On non-Windows this is easy. Since we know both are absolute, just do a
434 // prefix check.
435
436 if (path.substr(0, source_root_len) ==
437 source_root.substr(0, source_root_len)) {
438 if (path_len == source_root_len) {
439 // path is equivalent to source_root.
440 *dest = "//";
441 return true;
442 } else if (!IsSlash(path[source_root_len])) {
443 // path is ${source-root}SUFIX/...
444 return false;
445 }
446 // A source-root relative path, The input may have an unknown number of
447 // slashes after the previous match. Skip over them.
448 size_t first_after_slash = source_root_len + 1;
449 while (first_after_slash < path_len && IsSlash(path[first_after_slash]))
450 first_after_slash++;
451
452 dest->assign("//"); // Result is source root relative.
453 dest->append(&path.data()[first_after_slash],
454 path.size() - first_after_slash);
455 return true;
456 }
457 return false;
458 #endif
459 }
460
MakeAbsoluteFilePathRelativeIfPossible(const base::FilePath & base,const base::FilePath & target)461 base::FilePath MakeAbsoluteFilePathRelativeIfPossible(
462 const base::FilePath& base,
463 const base::FilePath& target) {
464 DCHECK(base.IsAbsolute());
465 DCHECK(target.IsAbsolute());
466 std::vector<base::FilePath::StringType> base_components;
467 std::vector<base::FilePath::StringType> target_components;
468 base.GetComponents(&base_components);
469 target.GetComponents(&target_components);
470 #if defined(OS_WIN)
471 // On Windows, it's impossible to have a relative path from C:\foo to D:\bar,
472 // so return the target as an absolute path instead.
473 if (base_components[0] != target_components[0])
474 return target;
475
476 // GetComponents() returns the first slash after the root. Set it to the
477 // same value in both component lists so that relative paths between
478 // "C:/foo/..." and "C:\foo\..." are computed correctly.
479 target_components[1] = base_components[1];
480 #endif
481 size_t i;
482 for (i = 0; i < base_components.size() && i < target_components.size(); i++) {
483 if (base_components[i] != target_components[i])
484 break;
485 }
486 std::vector<base::FilePath::StringType> relative_components;
487 for (size_t j = i; j < base_components.size(); j++)
488 relative_components.push_back(base::FilePath::kParentDirectory);
489 for (size_t j = i; j < target_components.size(); j++)
490 relative_components.push_back(target_components[j]);
491 if (relative_components.size() <= 1) {
492 // In case the file pointed-to is an executable, prepend the current
493 // directory to the path -- if the path was "gn", use "./gn" instead. If
494 // the file path is used in a command, this prevents issues where "gn" might
495 // not be in the PATH (or it is in the path, and the wrong gn is used).
496 relative_components.insert(relative_components.begin(),
497 base::FilePath::kCurrentDirectory);
498 }
499 // base::FilePath::Append(component) replaces the file path with |component|
500 // if the path is base::Filepath::kCurrentDirectory. We want to preserve the
501 // leading "./", so we build the path ourselves and use that to construct the
502 // base::FilePath.
503 base::FilePath::StringType separator(&base::FilePath::kSeparators[0], 1);
504 return base::FilePath(base::JoinString(relative_components, separator));
505 }
506
NormalizePath(std::string * path,const std::string_view & source_root)507 void NormalizePath(std::string* path, const std::string_view& source_root) {
508 char* pathbuf = path->empty() ? nullptr : &(*path)[0];
509
510 // top_index is the first character we can modify in the path. Anything
511 // before this indicates where the path is relative to.
512 size_t top_index = 0;
513 bool is_relative = true;
514 if (!path->empty() && pathbuf[0] == '/') {
515 is_relative = false;
516
517 if (path->size() > 1 && pathbuf[1] == '/') {
518 // Two leading slashes, this is a path into the source dir.
519 top_index = 2;
520 } else {
521 // One leading slash, this is a system-absolute path.
522 top_index = 1;
523 }
524 }
525
526 size_t dest_i = top_index;
527 for (size_t src_i = top_index; src_i < path->size(); /* nothing */) {
528 if (pathbuf[src_i] == '.') {
529 if (src_i == 0 || IsSlash(pathbuf[src_i - 1])) {
530 // Slash followed by a dot, see if it's something special.
531 size_t consumed_len;
532 switch (ClassifyAfterDot(*path, src_i + 1, &consumed_len)) {
533 case NOT_A_DIRECTORY:
534 // Copy the dot to the output, it means nothing special.
535 pathbuf[dest_i++] = pathbuf[src_i++];
536 break;
537 case DIRECTORY_CUR:
538 // Current directory, just skip the input.
539 src_i += consumed_len;
540 break;
541 case DIRECTORY_UP:
542 // Back up over previous directory component. If we're already
543 // at the top, preserve the "..".
544 if (dest_i > top_index) {
545 // The previous char was a slash, remove it.
546 dest_i--;
547 }
548
549 if (dest_i == top_index) {
550 if (is_relative) {
551 // We're already at the beginning of a relative input, copy the
552 // ".." and continue. We need the trailing slash if there was
553 // one before (otherwise we're at the end of the input).
554 pathbuf[dest_i++] = '.';
555 pathbuf[dest_i++] = '.';
556 if (consumed_len == 3)
557 pathbuf[dest_i++] = '/';
558
559 // This also makes a new "root" that we can't delete by going
560 // up more levels. Otherwise "../.." would collapse to
561 // nothing.
562 top_index = dest_i;
563 } else if (top_index == 2 && !source_root.empty()) {
564 // |path| was passed in as a source-absolute path. Prepend
565 // |source_root| to make |path| absolute. |source_root| must not
566 // end with a slash unless we are at root.
567 DCHECK(source_root.size() == 1u ||
568 !IsSlash(source_root[source_root.size() - 1u]));
569 size_t source_root_len = source_root.size();
570
571 #if defined(OS_WIN)
572 // On Windows, if the source_root does not start with a slash,
573 // append one here for consistency.
574 if (!IsSlash(source_root[0])) {
575 path->insert(0, "/" + std::string(source_root));
576 source_root_len++;
577 } else {
578 path->insert(0, source_root.data(), source_root_len);
579 }
580
581 // Normalize slashes in source root portion.
582 for (size_t i = 0; i < source_root_len; ++i) {
583 if ((*path)[i] == '\\')
584 (*path)[i] = '/';
585 }
586 #else
587 path->insert(0, source_root.data(), source_root_len);
588 #endif
589
590 // |path| is now absolute, so |top_index| is 1. |dest_i| and
591 // |src_i| should be incremented to keep the same relative
592 // position. Comsume the leading "//" by decrementing |dest_i|.
593 top_index = 1;
594 pathbuf = &(*path)[0];
595 dest_i += source_root_len - 2;
596 src_i += source_root_len;
597
598 // Just find the previous slash or the beginning of input.
599 while (dest_i > 0 && !IsSlash(pathbuf[dest_i - 1]))
600 dest_i--;
601 }
602 // Otherwise we're at the beginning of a system-absolute path, or
603 // a source-absolute path for which we don't know the absolute
604 // path. Don't allow ".." to go up another level, and just eat it.
605 } else {
606 // Just find the previous slash or the beginning of input.
607 while (dest_i > 0 && !IsSlash(pathbuf[dest_i - 1]))
608 dest_i--;
609 }
610 src_i += consumed_len;
611 }
612 } else {
613 // Dot not preceeded by a slash, copy it literally.
614 pathbuf[dest_i++] = pathbuf[src_i++];
615 }
616 } else if (IsSlash(pathbuf[src_i])) {
617 if (src_i > 0 && IsSlash(pathbuf[src_i - 1])) {
618 // Two slashes in a row, skip over it.
619 src_i++;
620 } else {
621 // Just one slash, copy it, normalizing to foward slash.
622 pathbuf[dest_i] = '/';
623 dest_i++;
624 src_i++;
625 }
626 } else {
627 // Input nothing special, just copy it.
628 pathbuf[dest_i++] = pathbuf[src_i++];
629 }
630 }
631 path->resize(dest_i);
632 }
633
ConvertPathToSystem(std::string * path)634 void ConvertPathToSystem(std::string* path) {
635 #if defined(OS_WIN)
636 for (size_t i = 0; i < path->size(); i++) {
637 if ((*path)[i] == '/')
638 (*path)[i] = '\\';
639 }
640 #endif
641 }
642
MakeRelativePath(const std::string & input,const std::string & dest)643 std::string MakeRelativePath(const std::string& input,
644 const std::string& dest) {
645 #if defined(OS_WIN)
646 // Make sure that absolute |input| path starts with a slash if |dest| path
647 // does. Otherwise skipping common prefixes won't work properly. Ensure the
648 // same for |dest| path too.
649 if (IsPathAbsolute(input) && !IsSlash(input[0]) && IsSlash(dest[0])) {
650 std::string corrected_input(1, dest[0]);
651 corrected_input.append(input);
652 return MakeRelativePath(corrected_input, dest);
653 }
654 if (IsPathAbsolute(dest) && !IsSlash(dest[0]) && IsSlash(input[0])) {
655 std::string corrected_dest(1, input[0]);
656 corrected_dest.append(dest);
657 return MakeRelativePath(input, corrected_dest);
658 }
659
660 // Make sure that both absolute paths use the same drive letter case.
661 if (IsPathAbsolute(input) && IsPathAbsolute(dest) && input.size() > 1 &&
662 dest.size() > 1) {
663 int letter_pos = base::IsAsciiAlpha(input[0]) ? 0 : 1;
664 if (input[letter_pos] != dest[letter_pos] &&
665 base::ToUpperASCII(input[letter_pos]) ==
666 base::ToUpperASCII(dest[letter_pos])) {
667 std::string corrected_input = input;
668 corrected_input[letter_pos] = dest[letter_pos];
669 return MakeRelativePath(corrected_input, dest);
670 }
671 }
672 #endif
673
674 std::string ret;
675
676 // Skip the common prefixes of the source and dest as long as they end in
677 // a [back]slash.
678 size_t common_prefix_len = 0;
679 size_t max_common_length = std::min(input.size(), dest.size());
680 for (size_t i = common_prefix_len; i < max_common_length; i++) {
681 if (IsSlash(input[i]) && IsSlash(dest[i]))
682 common_prefix_len = i + 1;
683 else if (input[i] != dest[i])
684 break;
685 }
686
687 // Invert the dest dir starting from the end of the common prefix.
688 for (size_t i = common_prefix_len; i < dest.size(); i++) {
689 if (IsSlash(dest[i]))
690 ret.append("../");
691 }
692
693 // Append any remaining unique input.
694 ret.append(&input[common_prefix_len], input.size() - common_prefix_len);
695
696 // If the result is still empty, the paths are the same.
697 if (ret.empty())
698 ret.push_back('.');
699
700 return ret;
701 }
702
RebasePath(const std::string & input,const SourceDir & dest_dir,const std::string_view & source_root)703 std::string RebasePath(const std::string& input,
704 const SourceDir& dest_dir,
705 const std::string_view& source_root) {
706 std::string ret;
707 DCHECK(source_root.empty() ||
708 !base::EndsWith(source_root, "/", base::CompareCase::SENSITIVE));
709
710 bool input_is_source_path =
711 (input.size() >= 2 && input[0] == '/' && input[1] == '/');
712
713 if (!source_root.empty() &&
714 (!input_is_source_path || !dest_dir.is_source_absolute())) {
715 std::string input_full;
716 std::string dest_full;
717 if (input_is_source_path) {
718 input_full.append(source_root);
719 input_full.push_back('/');
720 input_full.append(input, 2, std::string::npos);
721 } else {
722 input_full.append(input);
723 }
724 if (dest_dir.is_source_absolute()) {
725 dest_full.append(source_root);
726 dest_full.push_back('/');
727 dest_full.append(dest_dir.value(), 2, std::string::npos);
728 } else {
729 #if defined(OS_WIN)
730 // On Windows, SourceDir system-absolute paths start
731 // with /, e.g. "/C:/foo/bar".
732 const std::string& value = dest_dir.value();
733 if (value.size() > 2 && value[2] == ':')
734 dest_full.append(dest_dir.value().substr(1));
735 else
736 dest_full.append(dest_dir.value());
737 #else
738 dest_full.append(dest_dir.value());
739 #endif
740 }
741 bool remove_slash = false;
742 if (!EndsWithSlash(input_full)) {
743 input_full.push_back('/');
744 remove_slash = true;
745 }
746 ret = MakeRelativePath(input_full, dest_full);
747 if (remove_slash && ret.size() > 1)
748 ret.resize(ret.size() - 1);
749 return ret;
750 }
751
752 ret = MakeRelativePath(input, dest_dir.value());
753 return ret;
754 }
755
ResolvePath(const std::string & value,bool as_file,const base::FilePath & source_root)756 base::FilePath ResolvePath(const std::string& value,
757 bool as_file,
758 const base::FilePath& source_root) {
759 if (value.empty())
760 return base::FilePath();
761
762 std::string converted;
763 if (!IsPathSourceAbsolute(value)) {
764 if (value.size() > 2 && value[2] == ':') {
765 // Windows path, strip the leading slash.
766 converted.assign(&value[1], value.size() - 1);
767 } else {
768 converted.assign(value);
769 }
770 return base::FilePath(UTF8ToFilePath(converted));
771 }
772
773 // String the double-leading slash for source-relative paths.
774 converted.assign(&value[2], value.size() - 2);
775
776 if (as_file && source_root.empty())
777 return UTF8ToFilePath(converted).NormalizePathSeparatorsTo('/');
778
779 return source_root.Append(UTF8ToFilePath(converted))
780 .NormalizePathSeparatorsTo('/');
781 }
782
783 template <typename StringType>
ResolveRelative(const StringType & input,const std::string & value,bool as_file,const std::string_view & source_root)784 std::string ResolveRelative(const StringType& input,
785 const std::string& value,
786 bool as_file,
787 const std::string_view& source_root) {
788 std::string result;
789
790 if (input.size() >= 2 && input[0] == '/' && input[1] == '/') {
791 // Source-relative.
792 result.assign(input.data(), input.size());
793 if (!as_file) {
794 if (!EndsWithSlash(result))
795 result.push_back('/');
796 }
797 NormalizePath(&result, source_root);
798 return result;
799 } else if (IsPathAbsolute(input)) {
800 if (source_root.empty() ||
801 !MakeAbsolutePathRelativeIfPossible(source_root, input, &result)) {
802 #if defined(OS_WIN)
803 if (input[0] != '/') // See the file case for why we do this check.
804 result = "/";
805 #endif
806 result.append(input.data(), input.size());
807 }
808 NormalizePath(&result);
809 if (!as_file) {
810 if (!EndsWithSlash(result))
811 result.push_back('/');
812 }
813 return result;
814 }
815
816 if (!source_root.empty()) {
817 std::string absolute =
818 FilePathToUTF8(ResolvePath(value, as_file, UTF8ToFilePath(source_root))
819 .AppendASCII(input)
820 .value());
821 NormalizePath(&absolute);
822 if (!MakeAbsolutePathRelativeIfPossible(source_root, absolute, &result)) {
823 #if defined(OS_WIN)
824 if (absolute[0] != '/') // See the file case for why we do this check.
825 result = "/";
826 #endif
827 result.append(absolute.data(), absolute.size());
828 }
829 if (!as_file && !EndsWithSlash(result))
830 result.push_back('/');
831 return result;
832 }
833
834 // With no source_root, there's nothing we can do about
835 // e.g. input=../../../path/to/file and value=//source and we'll
836 // errornously return //file.
837 result.reserve(value.size() + input.size());
838 result.assign(value);
839 result.append(input.data(), input.size());
840
841 NormalizePath(&result);
842 if (!as_file && !EndsWithSlash(result))
843 result.push_back('/');
844
845 return result;
846 }
847
848 // Explicit template instantiation
849 template std::string ResolveRelative(const std::string_view& input,
850 const std::string& value,
851 bool as_file,
852 const std::string_view& source_root);
853
854 template std::string ResolveRelative(const std::string& input,
855 const std::string& value,
856 bool as_file,
857 const std::string_view& source_root);
858
DirectoryWithNoLastSlash(const SourceDir & dir)859 std::string DirectoryWithNoLastSlash(const SourceDir& dir) {
860 std::string ret;
861
862 if (dir.value().empty()) {
863 // Just keep input the same.
864 } else if (dir.value() == "/") {
865 ret.assign("/.");
866 } else if (dir.value() == "//") {
867 ret.assign("//.");
868 } else {
869 ret.assign(dir.value());
870 ret.resize(ret.size() - 1);
871 }
872 return ret;
873 }
874
SourceDirForPath(const base::FilePath & source_root,const base::FilePath & path)875 SourceDir SourceDirForPath(const base::FilePath& source_root,
876 const base::FilePath& path) {
877 std::vector<base::FilePath::StringType> source_comp =
878 GetPathComponents(source_root);
879 std::vector<base::FilePath::StringType> path_comp = GetPathComponents(path);
880
881 // See if path is inside the source root by looking for each of source root's
882 // components at the beginning of path.
883 bool is_inside_source;
884 if (path_comp.size() < source_comp.size() || source_root.empty()) {
885 // Too small to fit.
886 is_inside_source = false;
887 } else {
888 is_inside_source = true;
889 for (size_t i = 0; i < source_comp.size(); i++) {
890 if (!FilesystemStringsEqual(source_comp[i], path_comp[i])) {
891 is_inside_source = false;
892 break;
893 }
894 }
895 }
896
897 std::string result_str;
898 size_t initial_path_comp_to_use;
899 if (is_inside_source) {
900 // Construct a source-relative path beginning in // and skip all of the
901 // shared directories.
902 result_str = "//";
903 initial_path_comp_to_use = source_comp.size();
904 } else {
905 // Not inside source code, construct a system-absolute path.
906 result_str = "/";
907 initial_path_comp_to_use = 0;
908 }
909
910 for (size_t i = initial_path_comp_to_use; i < path_comp.size(); i++) {
911 result_str.append(FilePathToUTF8(path_comp[i]));
912 result_str.push_back('/');
913 }
914 return SourceDir(std::move(result_str));
915 }
916
SourceDirForCurrentDirectory(const base::FilePath & source_root)917 SourceDir SourceDirForCurrentDirectory(const base::FilePath& source_root) {
918 base::FilePath cd;
919 base::GetCurrentDirectory(&cd);
920 return SourceDirForPath(source_root, cd);
921 }
922
GetOutputSubdirName(const Label & toolchain_label,bool is_default)923 std::string GetOutputSubdirName(const Label& toolchain_label, bool is_default) {
924 // The default toolchain has no subdir.
925 if (is_default)
926 return std::string();
927
928 // For now just assume the toolchain name is always a valid dir name. We may
929 // want to clean up the in the future.
930 return toolchain_label.name() + "/";
931 }
932
ContentsEqual(const base::FilePath & file_path,const std::string & data)933 bool ContentsEqual(const base::FilePath& file_path, const std::string& data) {
934 // Compare file and stream sizes first. Quick and will save us some time if
935 // they are different sizes.
936 int64_t file_size;
937 if (!base::GetFileSize(file_path, &file_size) ||
938 static_cast<size_t>(file_size) != data.size()) {
939 return false;
940 }
941
942 std::string file_data;
943 file_data.resize(file_size);
944 if (!base::ReadFileToString(file_path, &file_data))
945 return false;
946
947 return file_data == data;
948 }
949
WriteFileIfChanged(const base::FilePath & file_path,const std::string & data,Err * err)950 bool WriteFileIfChanged(const base::FilePath& file_path,
951 const std::string& data,
952 Err* err) {
953 if (ContentsEqual(file_path, data))
954 return true;
955
956 return WriteFile(file_path, data, err);
957 }
958
WriteFile(const base::FilePath & file_path,const std::string & data,Err * err)959 bool WriteFile(const base::FilePath& file_path,
960 const std::string& data,
961 Err* err) {
962 // Create the directory if necessary.
963 if (!base::CreateDirectory(file_path.DirName())) {
964 if (err) {
965 *err =
966 Err(Location(), "Unable to create directory.",
967 "I was using \"" + FilePathToUTF8(file_path.DirName()) + "\".");
968 }
969 return false;
970 }
971
972 int size = static_cast<int>(data.size());
973 bool write_success = false;
974
975 #if defined(OS_WIN)
976 // On Windows, provide a custom implementation of base::WriteFile. Sometimes
977 // the base version fails, especially on the bots. The guess is that Windows
978 // Defender or other antivirus programs still have the file open (after
979 // checking for the read) when the write happens immediately after. This
980 // version opens with FILE_SHARE_READ (normally not what you want when
981 // replacing the entire contents of the file) which lets us continue even if
982 // another program has the file open for reading. See http://crbug.com/468437
983 base::win::ScopedHandle file(::CreateFile(
984 reinterpret_cast<LPCWSTR>(file_path.value().c_str()), GENERIC_WRITE,
985 FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL));
986 if (file.IsValid()) {
987 DWORD written;
988 BOOL result = ::WriteFile(file.Get(), data.c_str(), size, &written, NULL);
989 if (result) {
990 if (static_cast<int>(written) == size) {
991 write_success = true;
992 } else {
993 // Didn't write all the bytes.
994 LOG(ERROR) << "wrote" << written << " bytes to "
995 << base::UTF16ToUTF8(file_path.value()) << " expected "
996 << size;
997 }
998 } else {
999 // WriteFile failed.
1000 PLOG(ERROR) << "writing file " << base::UTF16ToUTF8(file_path.value())
1001 << " failed";
1002 }
1003 } else {
1004 PLOG(ERROR) << "CreateFile failed for path "
1005 << base::UTF16ToUTF8(file_path.value());
1006 }
1007 #else
1008 write_success = base::WriteFile(file_path, data.c_str(), size) == size;
1009 #endif
1010
1011 if (!write_success && err) {
1012 *err = Err(Location(), "Unable to write file.",
1013 "I was writing \"" + FilePathToUTF8(file_path) + "\".");
1014 }
1015
1016 return write_success;
1017 }
1018
BuildDirContext(const Target * target)1019 BuildDirContext::BuildDirContext(const Target* target)
1020 : BuildDirContext(target->settings()) {}
1021
BuildDirContext(const Settings * settings)1022 BuildDirContext::BuildDirContext(const Settings* settings)
1023 : BuildDirContext(settings->build_settings(),
1024 settings->toolchain_label(),
1025 settings->is_default()) {}
1026
BuildDirContext(const Scope * execution_scope)1027 BuildDirContext::BuildDirContext(const Scope* execution_scope)
1028 : BuildDirContext(execution_scope->settings()) {}
1029
BuildDirContext(const Scope * execution_scope,const Label & toolchain_label)1030 BuildDirContext::BuildDirContext(const Scope* execution_scope,
1031 const Label& toolchain_label)
1032 : BuildDirContext(execution_scope->settings()->build_settings(),
1033 toolchain_label,
1034 execution_scope->settings()->default_toolchain_label() ==
1035 toolchain_label) {}
1036
BuildDirContext(const BuildSettings * in_build_settings,const Label & in_toolchain_label,bool in_is_default_toolchain)1037 BuildDirContext::BuildDirContext(const BuildSettings* in_build_settings,
1038 const Label& in_toolchain_label,
1039 bool in_is_default_toolchain)
1040 : build_settings(in_build_settings),
1041 toolchain_label(in_toolchain_label),
1042 is_default_toolchain(in_is_default_toolchain) {}
1043
GetBuildDirAsSourceDir(const BuildDirContext & context,BuildDirType type)1044 SourceDir GetBuildDirAsSourceDir(const BuildDirContext& context,
1045 BuildDirType type) {
1046 return GetBuildDirAsOutputFile(context, type)
1047 .AsSourceDir(context.build_settings);
1048 }
1049
GetBuildDirAsOutputFile(const BuildDirContext & context,BuildDirType type)1050 OutputFile GetBuildDirAsOutputFile(const BuildDirContext& context,
1051 BuildDirType type) {
1052 OutputFile result(GetOutputSubdirName(context.toolchain_label,
1053 context.is_default_toolchain));
1054 DCHECK(result.value().empty() || result.value().back() == '/');
1055
1056 if (type == BuildDirType::GEN)
1057 result.value().append("gen/");
1058 else if (type == BuildDirType::OBJ)
1059 result.value().append("obj/");
1060 return result;
1061 }
1062
GetSubBuildDirAsSourceDir(const BuildDirContext & context,const SourceDir & source_dir,BuildDirType type)1063 SourceDir GetSubBuildDirAsSourceDir(const BuildDirContext& context,
1064 const SourceDir& source_dir,
1065 BuildDirType type) {
1066 return GetSubBuildDirAsOutputFile(context, source_dir, type)
1067 .AsSourceDir(context.build_settings);
1068 }
1069
GetSubBuildDirAsOutputFile(const BuildDirContext & context,const SourceDir & source_dir,BuildDirType type)1070 OutputFile GetSubBuildDirAsOutputFile(const BuildDirContext& context,
1071 const SourceDir& source_dir,
1072 BuildDirType type) {
1073 DCHECK(type != BuildDirType::TOOLCHAIN_ROOT);
1074 OutputFile result = GetBuildDirAsOutputFile(context, type);
1075
1076 if (source_dir.is_source_absolute()) {
1077 // The source dir is source-absolute, so we trim off the two leading
1078 // slashes to append to the toolchain object directory.
1079 result.value().append(&source_dir.value()[2],
1080 source_dir.value().size() - 2);
1081 } else {
1082 // System-absolute.
1083 AppendFixedAbsolutePathSuffix(context.build_settings, source_dir, &result);
1084 }
1085 return result;
1086 }
1087
GetBuildDirForTargetAsSourceDir(const Target * target,BuildDirType type)1088 SourceDir GetBuildDirForTargetAsSourceDir(const Target* target,
1089 BuildDirType type) {
1090 return GetSubBuildDirAsSourceDir(BuildDirContext(target),
1091 target->label().dir(), type);
1092 }
1093
GetBuildDirForTargetAsOutputFile(const Target * target,BuildDirType type)1094 OutputFile GetBuildDirForTargetAsOutputFile(const Target* target,
1095 BuildDirType type) {
1096 return GetSubBuildDirAsOutputFile(BuildDirContext(target),
1097 target->label().dir(), type);
1098 }
1099
GetScopeCurrentBuildDirAsSourceDir(const Scope * scope,BuildDirType type)1100 SourceDir GetScopeCurrentBuildDirAsSourceDir(const Scope* scope,
1101 BuildDirType type) {
1102 if (type == BuildDirType::TOOLCHAIN_ROOT)
1103 return GetBuildDirAsSourceDir(BuildDirContext(scope), type);
1104 return GetSubBuildDirAsSourceDir(BuildDirContext(scope),
1105 scope->GetSourceDir(), type);
1106 }
1107