• 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 "gn/target.h"
17 #include "util/build_config.h"
18 
19 #if defined(OS_WIN)
20 #include <direct.h>
21 #include <windows.h>
22 #endif
23 
24 namespace {
25 
26 enum DotDisposition {
27   // The given dot is just part of a filename and is not special.
28   NOT_A_DIRECTORY,
29 
30   // The given dot is the current directory.
31   DIRECTORY_CUR,
32 
33   // The given dot is the first of a double dot that should take us up one.
34   DIRECTORY_UP
35 };
36 
37 // When we find a dot, this function is called with the character following
38 // that dot to see what it is. The return value indicates what type this dot is
39 // (see above). This code handles the case where the dot is at the end of the
40 // input.
41 //
42 // |*consumed_len| will contain the number of characters in the input that
43 // express what we found.
ClassifyAfterDot(const std::string & path,size_t after_dot,size_t * consumed_len)44 DotDisposition ClassifyAfterDot(const std::string& path,
45                                 size_t after_dot,
46                                 size_t* consumed_len) {
47   if (after_dot == path.size()) {
48     // Single dot at the end.
49     *consumed_len = 1;
50     return DIRECTORY_CUR;
51   }
52   if (IsSlash(path[after_dot])) {
53     // Single dot followed by a slash.
54     *consumed_len = 2;  // Consume the slash
55     return DIRECTORY_CUR;
56   }
57 
58   if (path[after_dot] == '.') {
59     // Two dots.
60     if (after_dot + 1 == path.size()) {
61       // Double dot at the end.
62       *consumed_len = 2;
63       return DIRECTORY_UP;
64     }
65     if (IsSlash(path[after_dot + 1])) {
66       // Double dot folowed by a slash.
67       *consumed_len = 3;
68       return DIRECTORY_UP;
69     }
70   }
71 
72   // The dots are followed by something else, not a directory.
73   *consumed_len = 1;
74   return NOT_A_DIRECTORY;
75 }
76 
77 #if defined(OS_WIN)
NormalizeWindowsPathChar(char c)78 inline char NormalizeWindowsPathChar(char c) {
79   if (c == '/')
80     return '\\';
81   return base::ToLowerASCII(c);
82 }
83 
84 // Attempts to do a case and slash-insensitive comparison of two 8-bit Windows
85 // paths.
AreAbsoluteWindowsPathsEqual(std::string_view a,std::string_view b)86 bool AreAbsoluteWindowsPathsEqual(std::string_view a, std::string_view b) {
87   if (a.size() != b.size())
88     return false;
89 
90   // For now, just do a case-insensitive ASCII comparison. We could convert to
91   // UTF-16 and use ICU if necessary.
92   for (size_t i = 0; i < a.size(); i++) {
93     if (NormalizeWindowsPathChar(a[i]) != NormalizeWindowsPathChar(b[i]))
94       return false;
95   }
96   return true;
97 }
98 
DoesBeginWindowsDriveLetter(std::string_view path)99 bool DoesBeginWindowsDriveLetter(std::string_view path) {
100   if (path.size() < 3)
101     return false;
102 
103   // Check colon first, this will generally fail fastest.
104   if (path[1] != ':')
105     return false;
106 
107   // Check drive letter.
108   if (!base::IsAsciiAlpha(path[0]))
109     return false;
110 
111   if (!IsSlash(path[2]))
112     return false;
113   return true;
114 }
115 #endif
116 
117 // A wrapper around FilePath.GetComponents that works the way we need. This is
118 // not super efficient since it does some O(n) transformations on the path. If
119 // this is called a lot, we might want to optimize.
GetPathComponents(const base::FilePath & path)120 std::vector<base::FilePath::StringType> GetPathComponents(
121     const base::FilePath& path) {
122   std::vector<base::FilePath::StringType> result;
123   path.GetComponents(&result);
124 
125   if (result.empty())
126     return result;
127 
128   // GetComponents will preserve the "/" at the beginning, which confuses us.
129   // We don't expect to have relative paths in this function.
130   // Don't use IsSeparator since we always want to allow backslashes.
131   if (result[0] == FILE_PATH_LITERAL("/") ||
132       result[0] == FILE_PATH_LITERAL("\\"))
133     result.erase(result.begin());
134 
135 #if defined(OS_WIN)
136   // On Windows, GetComponents will give us [ "C:", "/", "foo" ], and we
137   // don't want the slash in there. This doesn't support input like "C:foo"
138   // which means foo relative to the current directory of the C drive but
139   // that's basically legacy DOS behavior we don't need to support.
140   if (result.size() >= 2 && result[1].size() == 1 &&
141       IsSlash(static_cast<char>(result[1][0])))
142     result.erase(result.begin() + 1);
143 #endif
144 
145   return result;
146 }
147 
148 // Provides the equivalent of == for filesystem strings, trying to do
149 // approximately the right thing with case.
FilesystemStringsEqual(const base::FilePath::StringType & a,const base::FilePath::StringType & b)150 bool FilesystemStringsEqual(const base::FilePath::StringType& a,
151                             const base::FilePath::StringType& b) {
152 #if defined(OS_WIN)
153   // Assume case-insensitive filesystems on Windows. We use the CompareString
154   // function to do a case-insensitive comparison based on the current locale
155   // (we don't want GN to depend on ICU which is large and requires data
156   // files). This isn't perfect, but getting this perfectly right is very
157   // difficult and requires I/O, and this comparison should cover 99.9999% of
158   // all cases.
159   //
160   // Note: The documentation for CompareString says it runs fastest on
161   // null-terminated strings with -1 passed for the length, so we do that here.
162   // There should not be embedded nulls in filesystem strings.
163   return ::CompareString(LOCALE_USER_DEFAULT, LINGUISTIC_IGNORECASE,
164                          reinterpret_cast<LPCWSTR>(a.c_str()), -1,
165                          reinterpret_cast<LPCWSTR>(b.c_str()),
166                          -1) == CSTR_EQUAL;
167 #else
168   // Assume case-sensitive filesystems on non-Windows.
169   return a == b;
170 #endif
171 }
172 
173 // Helper function for computing subdirectories in the build directory
174 // corresponding to absolute paths. This will try to resolve the absolute
175 // path as a source-relative path first, and otherwise it creates a
176 // special subdirectory for absolute paths to keep them from colliding with
177 // other generated sources and outputs.
AppendFixedAbsolutePathSuffix(const BuildSettings * build_settings,const SourceDir & source_dir,OutputFile * result)178 void AppendFixedAbsolutePathSuffix(const BuildSettings* build_settings,
179                                    const SourceDir& source_dir,
180                                    OutputFile* result) {
181   const std::string& build_dir = build_settings->build_dir().value();
182 
183   if (base::starts_with(source_dir.value(), build_dir)) {
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(std::string_view path)203 size_t AbsPathLenWithNoTrailingSlash(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(std::string_view sp)225 base::FilePath UTF8ToFilePath(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(std::string_view s)286 bool EndsWithSlash(std::string_view 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(std::string_view path)337 bool IsPathAbsolute(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(std::string_view path)358 bool IsPathSourceAbsolute(std::string_view path) {
359   return (path.size() >= 2 && path[0] == '/' && path[1] == '/');
360 }
361 
MakeAbsolutePathRelativeIfPossible(std::string_view source_root,std::string_view path,std::string * dest)362 bool MakeAbsolutePathRelativeIfPossible(std::string_view source_root,
363                                         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}SUFFIX/...
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}SUFFIX/...
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,std::string_view source_root)507 void NormalizePath(std::string* path, 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. Consume 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 preceded 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 forward 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 
643 #if defined(OS_WIN)
GetPathWithDriveLetter(std::string_view path)644 std::string GetPathWithDriveLetter(std::string_view path) {
645   if (!IsPathAbsolute(path) || !IsSlash(path[0]))
646     return std::string(path);
647 
648   int drive = _getdrive();
649   DCHECK(drive > 0 && drive <= 26);
650 
651   std::string ret;
652   ret.reserve(2 + path.size());
653   ret.push_back('A' + drive - 1);
654   ret.push_back(':');
655   ret += path;
656   return ret;
657 }
658 
659 // Regulate path if it is an absolute path.
RegulatePathIfAbsolute(std::string_view path)660 std::string RegulatePathIfAbsolute(std::string_view path) {
661   CHECK(!path.empty());
662   bool is_start_slash = IsSlash(path[0]);
663 
664   // 1. /C:/ -> C:/
665   if (path.size() > 3 && is_start_slash &&
666         base::IsAsciiAlpha(path[1]) && path[2] == ':') {
667     return RegulatePathIfAbsolute(path.substr(1));
668   }
669 
670   bool is_path_absolute = IsPathAbsolute(path);
671 
672   // 2. /Path -> ($PWD's Drive):/Path
673   if (is_path_absolute && is_start_slash) {
674     return GetPathWithDriveLetter(path);
675   }
676 
677   // 3. c:/ -> C:/
678   std::string ret(path);
679   if (is_path_absolute && !is_start_slash) {
680     ret[0] = base::ToUpperASCII(path[0]);
681   }
682 
683   return ret;
684 }
685 #endif
686 
MakeRelativePath(std::string_view input,std::string_view dest)687 std::string MakeRelativePath(std::string_view input,
688                              std::string_view dest) {
689 #if defined(OS_WIN)
690   // Regulate the paths.
691   std::string input_regulated = RegulatePathIfAbsolute(input);
692   std::string dest_regulated = RegulatePathIfAbsolute(dest);
693 
694   input = input_regulated;
695   dest = dest_regulated;
696 
697   // On Windows, it is invalid to make a relative path across different
698   // drive letters. A relative path cannot span over different drives.
699   // For example:
700   //    Input          : D:/Path/Any/Where
701   //    Dest           : C:/Path/In/Another/Drive
702   //    Invalid Result : ../../../../../D:/Path/Any/Where
703   //    Correct Result : D:/Path/Any/Where
704   // It will at least make ninja fail.
705   // See: https://bugs.chromium.org/p/gn/issues/detail?id=317
706   if (IsPathAbsolute(input) && IsPathAbsolute(dest) && input.size() > 1 &&
707       dest.size() > 1) {
708     if (input[0] != dest[0]) {
709       // If the drive letters are differnet, we have no choice but use
710       // the absolute path of input for correctness.
711       return input_regulated;
712     }
713   }
714 #endif
715 
716   DCHECK(EndsWithSlash(dest));
717   std::string ret;
718 
719   // Skip the common prefixes of the source and dest as long as they end in
720   // a [back]slash or end the string. dest always ends with a (back)slash in
721   // this function, so checking dest for just that is sufficient.
722   size_t common_prefix_len = 0;
723   size_t max_common_length = std::min(input.size(), dest.size());
724   for (size_t i = common_prefix_len; i <= max_common_length; i++) {
725     if (dest.size() == i)
726       break;
727     if ((input.size() == i || IsSlash(input[i])) && IsSlash(dest[i]))
728       common_prefix_len = i + 1;
729     else if (input.size() == i || input[i] != dest[i])
730       break;
731   }
732 
733   // Invert the dest dir starting from the end of the common prefix.
734   for (size_t i = common_prefix_len; i < dest.size(); i++) {
735     if (IsSlash(dest[i]))
736       ret.append("../");
737   }
738 
739   // Append any remaining unique input.
740   if (common_prefix_len <= input.size())
741     ret.append(input.begin() + common_prefix_len, input.end());
742   else if (input.back() != '/' && !ret.empty())
743     ret.pop_back();
744 
745   // If the result is still empty, the paths are the same.
746   if (ret.empty())
747     ret.push_back('.');
748 
749   return ret;
750 }
751 
RebasePath(const std::string & input,const SourceDir & dest_dir,std::string_view source_root)752 std::string RebasePath(const std::string& input,
753                        const SourceDir& dest_dir,
754                        std::string_view source_root) {
755   std::string ret;
756   DCHECK(source_root.empty() || !base::ends_with(source_root, "/"));
757 
758   bool input_is_source_path =
759       (input.size() >= 2 && input[0] == '/' && input[1] == '/');
760 
761   if (!source_root.empty() &&
762       (!input_is_source_path || !dest_dir.is_source_absolute())) {
763     std::string input_full;
764     std::string dest_full;
765     if (input_is_source_path) {
766       input_full.append(source_root);
767       input_full.push_back('/');
768       input_full.append(input, 2, std::string::npos);
769     } else {
770       input_full.append(input);
771     }
772     if (dest_dir.is_source_absolute()) {
773       dest_full.append(source_root);
774       dest_full.push_back('/');
775       dest_full.append(dest_dir.value(), 2, std::string::npos);
776     } else {
777 #if defined(OS_WIN)
778       // On Windows, SourceDir system-absolute paths start
779       // with /, e.g. "/C:/foo/bar".
780       const std::string& value = dest_dir.value();
781       if (value.size() > 2 && value[2] == ':')
782         dest_full.append(dest_dir.value().substr(1));
783       else
784         dest_full.append(dest_dir.value());
785 #else
786       dest_full.append(dest_dir.value());
787 #endif
788     }
789     bool remove_slash = false;
790     if (!EndsWithSlash(input_full)) {
791       input_full.push_back('/');
792       remove_slash = true;
793     }
794     ret = MakeRelativePath(input_full, dest_full);
795     if (remove_slash && ret.size() > 1)
796       ret.pop_back();
797     return ret;
798   }
799 
800   ret = MakeRelativePath(input, dest_dir.value());
801   return ret;
802 }
803 
ResolvePath(const std::string & value,bool as_file,const base::FilePath & source_root)804 base::FilePath ResolvePath(const std::string& value,
805                            bool as_file,
806                            const base::FilePath& source_root) {
807   if (value.empty())
808     return base::FilePath();
809 
810   std::string converted;
811   if (!IsPathSourceAbsolute(value)) {
812     if (value.size() > 2 && value[2] == ':') {
813       // Windows path, strip the leading slash.
814       converted.assign(&value[1], value.size() - 1);
815     } else {
816       converted.assign(value);
817     }
818     return base::FilePath(UTF8ToFilePath(converted));
819   }
820 
821   // String the double-leading slash for source-relative paths.
822   converted.assign(&value[2], value.size() - 2);
823 
824   if (as_file && source_root.empty())
825     return UTF8ToFilePath(converted).NormalizePathSeparatorsTo('/');
826 
827   return source_root.Append(UTF8ToFilePath(converted))
828       .NormalizePathSeparatorsTo('/');
829 }
830 
ResolveRelative(std::string_view input,const std::string & value,bool as_file,std::string_view source_root)831 std::string ResolveRelative(std::string_view input,
832                             const std::string& value,
833                             bool as_file,
834                             std::string_view source_root) {
835   std::string result;
836 
837   if (input.size() >= 2 && input[0] == '/' && input[1] == '/') {
838     // Source-relative.
839     result.assign(input.data(), input.size());
840     if (!as_file) {
841       if (!EndsWithSlash(result))
842         result.push_back('/');
843     }
844     NormalizePath(&result, source_root);
845     return result;
846   } else if (IsPathAbsolute(input)) {
847     if (source_root.empty() ||
848         !MakeAbsolutePathRelativeIfPossible(source_root, input, &result)) {
849 #if defined(OS_WIN)
850       if (input[0] != '/')  // See the file case for why we do this check.
851         result = "/";
852 #endif
853       result.append(input.data(), input.size());
854     }
855     NormalizePath(&result);
856     if (!as_file) {
857       if (!EndsWithSlash(result))
858         result.push_back('/');
859     }
860     return result;
861   }
862 
863   if (!source_root.empty()) {
864     std::string absolute =
865         FilePathToUTF8(ResolvePath(value, as_file, UTF8ToFilePath(source_root))
866                            .AppendASCII(input)
867                            .value());
868     NormalizePath(&absolute);
869     if (!MakeAbsolutePathRelativeIfPossible(source_root, absolute, &result)) {
870 #if defined(OS_WIN)
871       if (absolute[0] != '/')  // See the file case for why we do this check.
872         result = "/";
873 #endif
874       result.append(absolute.data(), absolute.size());
875     }
876     if (!as_file && !EndsWithSlash(result))
877       result.push_back('/');
878     return result;
879   }
880 
881   // With no source_root, there's nothing we can do about
882   // e.g. input=../../../path/to/file and value=//source and we'll
883   // erroneously return //file.
884   result.reserve(value.size() + input.size());
885   result.assign(value);
886   result.append(input.data(), input.size());
887 
888   NormalizePath(&result);
889   if (!as_file && !EndsWithSlash(result))
890     result.push_back('/');
891 
892   return result;
893 }
894 
DirectoryWithNoLastSlash(const SourceDir & dir)895 std::string DirectoryWithNoLastSlash(const SourceDir& dir) {
896   std::string ret;
897 
898   if (dir.value().empty()) {
899     // Just keep input the same.
900   } else if (dir.value() == "/") {
901     ret.assign("/.");
902   } else if (dir.value() == "//") {
903     ret.assign("//.");
904   } else {
905     ret.assign(dir.value());
906     ret.resize(ret.size() - 1);
907   }
908   return ret;
909 }
910 
SourceDirForPath(const base::FilePath & source_root,const base::FilePath & path)911 SourceDir SourceDirForPath(const base::FilePath& source_root,
912                            const base::FilePath& path) {
913   std::vector<base::FilePath::StringType> source_comp =
914       GetPathComponents(source_root);
915   std::vector<base::FilePath::StringType> path_comp = GetPathComponents(path);
916 
917   // See if path is inside the source root by looking for each of source root's
918   // components at the beginning of path.
919   bool is_inside_source;
920   if (path_comp.size() < source_comp.size() || source_root.empty()) {
921     // Too small to fit.
922     is_inside_source = false;
923   } else {
924     is_inside_source = true;
925     for (size_t i = 0; i < source_comp.size(); i++) {
926       if (!FilesystemStringsEqual(source_comp[i], path_comp[i])) {
927         is_inside_source = false;
928         break;
929       }
930     }
931   }
932 
933   std::string result_str;
934   size_t initial_path_comp_to_use;
935   if (is_inside_source) {
936     // Construct a source-relative path beginning in // and skip all of the
937     // shared directories.
938     result_str = "//";
939     initial_path_comp_to_use = source_comp.size();
940   } else {
941     // Not inside source code, construct a system-absolute path.
942     result_str = "/";
943     initial_path_comp_to_use = 0;
944   }
945 
946   for (size_t i = initial_path_comp_to_use; i < path_comp.size(); i++) {
947     result_str.append(FilePathToUTF8(path_comp[i]));
948     result_str.push_back('/');
949   }
950   return SourceDir(std::move(result_str));
951 }
952 
SourceDirForCurrentDirectory(const base::FilePath & source_root)953 SourceDir SourceDirForCurrentDirectory(const base::FilePath& source_root) {
954   base::FilePath cd;
955   base::GetCurrentDirectory(&cd);
956   return SourceDirForPath(source_root, cd);
957 }
958 
GetOutputSubdirName(const Label & toolchain_label,bool is_default)959 std::string GetOutputSubdirName(const Label& toolchain_label, bool is_default) {
960   // The default toolchain has no subdir.
961   if (is_default)
962     return std::string();
963 
964   // For now just assume the toolchain name is always a valid dir name. We may
965   // want to clean up the in the future.
966   return toolchain_label.name() + "/";
967 }
968 
ContentsEqual(const base::FilePath & file_path,const std::string & data)969 bool ContentsEqual(const base::FilePath& file_path, const std::string& data) {
970   // Compare file and stream sizes first. Quick and will save us some time if
971   // they are different sizes.
972   int64_t file_size;
973   if (!base::GetFileSize(file_path, &file_size) ||
974       static_cast<size_t>(file_size) != data.size()) {
975     return false;
976   }
977 
978   std::string file_data;
979   file_data.resize(file_size);
980   if (!base::ReadFileToString(file_path, &file_data))
981     return false;
982 
983   return file_data == data;
984 }
985 
WriteFile(const base::FilePath & file_path,const std::string & data,Err * err)986 bool WriteFile(const base::FilePath& file_path,
987                const std::string& data,
988                Err* err) {
989   // Create the directory if necessary.
990   if (!base::CreateDirectory(file_path.DirName())) {
991     if (err) {
992       *err =
993           Err(Location(), "Unable to create directory.",
994               "I was using \"" + FilePathToUTF8(file_path.DirName()) + "\".");
995     }
996     return false;
997   }
998 
999   FileWriter writer;
1000   writer.Create(file_path);
1001   writer.Write(data);
1002   bool write_success = writer.Close();
1003 
1004   if (!write_success && err) {
1005     *err = Err(Location(), "Unable to write file.",
1006                "I was writing \"" + FilePathToUTF8(file_path) + "\".");
1007   }
1008 
1009   return write_success;
1010 }
1011 
BuildDirContext(const Target * target)1012 BuildDirContext::BuildDirContext(const Target* target)
1013     : BuildDirContext(target->settings()) {}
1014 
BuildDirContext(const Settings * settings)1015 BuildDirContext::BuildDirContext(const Settings* settings)
1016     : BuildDirContext(settings->build_settings(),
1017                       settings->toolchain_label(),
1018                       settings->is_default()) {}
1019 
BuildDirContext(const Scope * execution_scope)1020 BuildDirContext::BuildDirContext(const Scope* execution_scope)
1021     : BuildDirContext(execution_scope->settings()) {}
1022 
BuildDirContext(const Scope * execution_scope,const Label & toolchain_label)1023 BuildDirContext::BuildDirContext(const Scope* execution_scope,
1024                                  const Label& toolchain_label)
1025     : BuildDirContext(execution_scope->settings()->build_settings(),
1026                       toolchain_label,
1027                       execution_scope->settings()->default_toolchain_label() ==
1028                           toolchain_label) {}
1029 
BuildDirContext(const BuildSettings * in_build_settings,const Label & in_toolchain_label,bool in_is_default_toolchain)1030 BuildDirContext::BuildDirContext(const BuildSettings* in_build_settings,
1031                                  const Label& in_toolchain_label,
1032                                  bool in_is_default_toolchain)
1033     : build_settings(in_build_settings),
1034       toolchain_label(in_toolchain_label),
1035       is_default_toolchain(in_is_default_toolchain) {}
1036 
GetBuildDirAsSourceDir(const BuildDirContext & context,BuildDirType type)1037 SourceDir GetBuildDirAsSourceDir(const BuildDirContext& context,
1038                                  BuildDirType type) {
1039   return GetBuildDirAsOutputFile(context, type)
1040       .AsSourceDir(context.build_settings);
1041 }
1042 
GetBuildDirAsOutputFile(const BuildDirContext & context,BuildDirType type)1043 OutputFile GetBuildDirAsOutputFile(const BuildDirContext& context,
1044                                    BuildDirType type) {
1045   OutputFile result(GetOutputSubdirName(context.toolchain_label,
1046                                         context.is_default_toolchain));
1047   DCHECK(result.value().empty() || result.value().back() == '/');
1048 
1049   if (type == BuildDirType::GEN)
1050     result.value().append("gen/");
1051   else if (type == BuildDirType::OBJ)
1052     result.value().append("obj/");
1053   else if (type == BuildDirType::PHONY)
1054     result.value().append("phony/");
1055   return result;
1056 }
1057 
GetSubBuildDirAsSourceDir(const BuildDirContext & context,const SourceDir & source_dir,BuildDirType type)1058 SourceDir GetSubBuildDirAsSourceDir(const BuildDirContext& context,
1059                                     const SourceDir& source_dir,
1060                                     BuildDirType type) {
1061   return GetSubBuildDirAsOutputFile(context, source_dir, type)
1062       .AsSourceDir(context.build_settings);
1063 }
1064 
GetSubBuildDirAsOutputFile(const BuildDirContext & context,const SourceDir & source_dir,BuildDirType type)1065 OutputFile GetSubBuildDirAsOutputFile(const BuildDirContext& context,
1066                                       const SourceDir& source_dir,
1067                                       BuildDirType type) {
1068   DCHECK(type != BuildDirType::TOOLCHAIN_ROOT);
1069   OutputFile result = GetBuildDirAsOutputFile(context, type);
1070 
1071   if (source_dir.is_source_absolute()) {
1072     std::string_view build_dir = context.build_settings->build_dir().value();
1073     std::string_view source_dir_path = source_dir.value();
1074     if (source_dir_path.substr(0, build_dir.size()) == build_dir) {
1075       // The source dir is source-absolute, but in the build directory
1076       // (e.g. `//out/Debug/gen/src/foo.cc` or
1077       // `//out/Debug/toolchain1/gen/foo.cc`), which happens for generated
1078       // sources. In this case, remove the build directory prefix, and replace
1079       // it with `BUILD_DIR`. This will create results like `obj/BUILD_DIR/gen`
1080       // or `toolchain2/obj/BUILD_DIR/toolchain1/gen` which look surprising,
1081       // but guarantee unicity.
1082       result.value().append("BUILD_DIR/");
1083       result.value().append(&source_dir_path[build_dir.size()],
1084                             source_dir_path.size() - build_dir.size());
1085     } else {
1086       // The source dir is source-absolute, so we trim off the two leading
1087       // slashes to append to the toolchain object directory.
1088       result.value().append(&source_dir.value()[2],
1089                             source_dir.value().size() - 2);
1090     }
1091   } else {
1092     // System-absolute.
1093     AppendFixedAbsolutePathSuffix(context.build_settings, source_dir, &result);
1094   }
1095   return result;
1096 }
1097 
GetBuildDirForTargetAsSourceDir(const Target * target,BuildDirType type)1098 SourceDir GetBuildDirForTargetAsSourceDir(const Target* target,
1099                                           BuildDirType type) {
1100   return GetSubBuildDirAsSourceDir(BuildDirContext(target),
1101                                    target->label().dir(), type);
1102 }
1103 
GetBuildDirForTargetAsOutputFile(const Target * target,BuildDirType type)1104 OutputFile GetBuildDirForTargetAsOutputFile(const Target* target,
1105                                             BuildDirType type) {
1106   return GetSubBuildDirAsOutputFile(BuildDirContext(target),
1107                                     target->label().dir(), type);
1108 }
1109 
GetScopeCurrentBuildDirAsSourceDir(const Scope * scope,BuildDirType type)1110 SourceDir GetScopeCurrentBuildDirAsSourceDir(const Scope* scope,
1111                                              BuildDirType type) {
1112   if (type == BuildDirType::TOOLCHAIN_ROOT)
1113     return GetBuildDirAsSourceDir(BuildDirContext(scope), type);
1114   return GetSubBuildDirAsSourceDir(BuildDirContext(scope),
1115                                    scope->GetSourceDir(), type);
1116 }
1117