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