• 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 "tools/gn/filesystem_utils.h"
6 
7 #include <algorithm>
8 
9 #include "base/logging.h"
10 #include "base/strings/string_util.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "build/build_config.h"
13 #include "tools/gn/location.h"
14 #include "tools/gn/settings.h"
15 #include "tools/gn/source_dir.h"
16 
17 namespace {
18 
19 enum DotDisposition {
20   // The given dot is just part of a filename and is not special.
21   NOT_A_DIRECTORY,
22 
23   // The given dot is the current directory.
24   DIRECTORY_CUR,
25 
26   // The given dot is the first of a double dot that should take us up one.
27   DIRECTORY_UP
28 };
29 
30 // When we find a dot, this function is called with the character following
31 // that dot to see what it is. The return value indicates what type this dot is
32 // (see above). This code handles the case where the dot is at the end of the
33 // input.
34 //
35 // |*consumed_len| will contain the number of characters in the input that
36 // express what we found.
ClassifyAfterDot(const std::string & path,size_t after_dot,size_t * consumed_len)37 DotDisposition ClassifyAfterDot(const std::string& path,
38                                 size_t after_dot,
39                                 size_t* consumed_len) {
40   if (after_dot == path.size()) {
41     // Single dot at the end.
42     *consumed_len = 1;
43     return DIRECTORY_CUR;
44   }
45   if (path[after_dot] == '/') {
46     // Single dot followed by a slash.
47     *consumed_len = 2;  // Consume the slash
48     return DIRECTORY_CUR;
49   }
50 
51   if (path[after_dot] == '.') {
52     // Two dots.
53     if (after_dot + 1 == path.size()) {
54       // Double dot at the end.
55       *consumed_len = 2;
56       return DIRECTORY_UP;
57     }
58     if (path[after_dot + 1] == '/') {
59       // Double dot folowed by a slash.
60       *consumed_len = 3;
61       return DIRECTORY_UP;
62     }
63   }
64 
65   // The dots are followed by something else, not a directory.
66   *consumed_len = 1;
67   return NOT_A_DIRECTORY;
68 }
69 
70 #if defined(OS_WIN)
NormalizeWindowsPathChar(char c)71 inline char NormalizeWindowsPathChar(char c) {
72   if (c == '/')
73     return '\\';
74   return base::ToLowerASCII(c);
75 }
76 
77 // Attempts to do a case and slash-insensitive comparison of two 8-bit Windows
78 // paths.
AreAbsoluteWindowsPathsEqual(const base::StringPiece & a,const base::StringPiece & b)79 bool AreAbsoluteWindowsPathsEqual(const base::StringPiece& a,
80                                   const base::StringPiece& b) {
81   if (a.size() != b.size())
82     return false;
83 
84   // For now, just do a case-insensitive ASCII comparison. We could convert to
85   // UTF-16 and use ICU if necessary. Or maybe base::strcasecmp is good enough?
86   for (size_t i = 0; i < a.size(); i++) {
87     if (NormalizeWindowsPathChar(a[i]) != NormalizeWindowsPathChar(b[i]))
88       return false;
89   }
90   return true;
91 }
92 
DoesBeginWindowsDriveLetter(const base::StringPiece & path)93 bool DoesBeginWindowsDriveLetter(const base::StringPiece& path) {
94   if (path.size() < 3)
95     return false;
96 
97   // Check colon first, this will generally fail fastest.
98   if (path[1] != ':')
99     return false;
100 
101   // Check drive letter
102   if (!((path[0] >= 'A' && path[0] <= 'Z') ||
103          path[0] >= 'a' && path[0] <= 'z'))
104     return false;
105 
106   if (path[2] != '/' && path[2] != '\\')
107     return false;
108   return true;
109 }
110 #endif
111 
112 }  // namespace
113 
GetSourceFileType(const SourceFile & file,Settings::TargetOS os)114 SourceFileType GetSourceFileType(const SourceFile& file,
115                                  Settings::TargetOS os) {
116   base::StringPiece extension = FindExtension(&file.value());
117   if (extension == "cc" || extension == "cpp" || extension == "cxx")
118     return SOURCE_CC;
119   if (extension == "h")
120     return SOURCE_H;
121   if (extension == "c")
122     return SOURCE_C;
123 
124   switch (os) {
125     case Settings::MAC:
126       if (extension == "m")
127         return SOURCE_M;
128       if (extension == "mm")
129         return SOURCE_MM;
130       break;
131 
132     case Settings::WIN:
133       if (extension == "rc")
134         return SOURCE_RC;
135       // TODO(brettw) asm files.
136       break;
137 
138     default:
139       break;
140   }
141 
142   if (os != Settings::WIN) {
143     if (extension == "S")
144       return SOURCE_S;
145   }
146 
147   return SOURCE_UNKNOWN;
148 }
149 
GetExtensionForOutputType(Target::OutputType type,Settings::TargetOS os)150 const char* GetExtensionForOutputType(Target::OutputType type,
151                                       Settings::TargetOS os) {
152   switch (os) {
153     case Settings::MAC:
154       switch (type) {
155         case Target::EXECUTABLE:
156           return "";
157         case Target::SHARED_LIBRARY:
158           return "dylib";
159         case Target::STATIC_LIBRARY:
160           return "a";
161         default:
162           NOTREACHED();
163       }
164       break;
165 
166     case Settings::WIN:
167       switch (type) {
168         case Target::EXECUTABLE:
169           return "exe";
170         case Target::SHARED_LIBRARY:
171           return "dll.lib";  // Extension of import library.
172         case Target::STATIC_LIBRARY:
173           return "lib";
174         default:
175           NOTREACHED();
176       }
177       break;
178 
179     case Settings::LINUX:
180       switch (type) {
181         case Target::EXECUTABLE:
182           return "";
183         case Target::SHARED_LIBRARY:
184           return "so";
185         case Target::STATIC_LIBRARY:
186           return "a";
187         default:
188           NOTREACHED();
189       }
190       break;
191 
192     default:
193       NOTREACHED();
194   }
195   return "";
196 }
197 
FilePathToUTF8(const base::FilePath::StringType & str)198 std::string FilePathToUTF8(const base::FilePath::StringType& str) {
199 #if defined(OS_WIN)
200   return WideToUTF8(str);
201 #else
202   return str;
203 #endif
204 }
205 
UTF8ToFilePath(const base::StringPiece & sp)206 base::FilePath UTF8ToFilePath(const base::StringPiece& sp) {
207 #if defined(OS_WIN)
208   return base::FilePath(UTF8ToWide(sp));
209 #else
210   return base::FilePath(sp.as_string());
211 #endif
212 }
213 
FindExtensionOffset(const std::string & path)214 size_t FindExtensionOffset(const std::string& path) {
215   for (int i = static_cast<int>(path.size()); i >= 0; i--) {
216     if (path[i] == '/')
217       break;
218     if (path[i] == '.')
219       return i + 1;
220   }
221   return std::string::npos;
222 }
223 
FindExtension(const std::string * path)224 base::StringPiece FindExtension(const std::string* path) {
225   size_t extension_offset = FindExtensionOffset(*path);
226   if (extension_offset == std::string::npos)
227     return base::StringPiece();
228   return base::StringPiece(&path->data()[extension_offset],
229                            path->size() - extension_offset);
230 }
231 
FindFilenameOffset(const std::string & path)232 size_t FindFilenameOffset(const std::string& path) {
233   for (int i = static_cast<int>(path.size()) - 1; i >= 0; i--) {
234     if (path[i] == '/')
235       return i + 1;
236   }
237   return 0;  // No filename found means everything was the filename.
238 }
239 
FindFilename(const std::string * path)240 base::StringPiece FindFilename(const std::string* path) {
241   size_t filename_offset = FindFilenameOffset(*path);
242   if (filename_offset == 0)
243     return base::StringPiece(*path);  // Everything is the file name.
244   return base::StringPiece(&(*path).data()[filename_offset],
245                            path->size() - filename_offset);
246 }
247 
FindFilenameNoExtension(const std::string * path)248 base::StringPiece FindFilenameNoExtension(const std::string* path) {
249   if (path->empty())
250     return base::StringPiece();
251   size_t filename_offset = FindFilenameOffset(*path);
252   size_t extension_offset = FindExtensionOffset(*path);
253 
254   size_t name_len;
255   if (extension_offset == std::string::npos)
256     name_len = path->size() - filename_offset;
257   else
258     name_len = extension_offset - filename_offset - 1;
259 
260   return base::StringPiece(&(*path).data()[filename_offset], name_len);
261 }
262 
RemoveFilename(std::string * path)263 void RemoveFilename(std::string* path) {
264   path->resize(FindFilenameOffset(*path));
265 }
266 
EndsWithSlash(const std::string & s)267 bool EndsWithSlash(const std::string& s) {
268   return !s.empty() && s[s.size() - 1] == '/';
269 }
270 
FindDir(const std::string * path)271 base::StringPiece FindDir(const std::string* path) {
272   size_t filename_offset = FindFilenameOffset(*path);
273   if (filename_offset == 0u)
274     return base::StringPiece();
275   return base::StringPiece(path->data(), filename_offset);
276 }
277 
EnsureStringIsInOutputDir(const SourceDir & dir,const std::string & str,const Value & originating,Err * err)278 bool EnsureStringIsInOutputDir(const SourceDir& dir,
279                                const std::string& str,
280                                const Value& originating,
281                                Err* err) {
282   // The last char of the dir will be a slash. We don't care if the input ends
283   // in a slash or not, so just compare up until there.
284   //
285   // This check will be wrong for all proper prefixes "e.g. "/output" will
286   // match "/out" but we don't really care since this is just a sanity check.
287   const std::string& dir_str = dir.value();
288   if (str.compare(0, dir_str.length() - 1, dir_str, 0, dir_str.length() - 1)
289       != 0) {
290     *err = Err(originating, "File not inside output directory.",
291         "The given file should be in the output directory. Normally you would "
292         "specify\n\"$target_output_dir/foo\" or "
293         "\"$target_gen_dir/foo\". I interpreted this as\n\""
294         + str + "\".");
295     return false;
296   }
297   return true;
298 }
299 
IsPathAbsolute(const base::StringPiece & path)300 bool IsPathAbsolute(const base::StringPiece& path) {
301   if (path.empty())
302     return false;
303 
304   if (path[0] != '/') {
305 #if defined(OS_WIN)
306     // Check for Windows system paths like "C:\foo".
307     if (path.size() > 2 &&
308         path[1] == ':' && (path[2] == '/' || path[2] == '\\'))
309       return true;
310 #endif
311     return false;  // Doesn't begin with a slash, is relative.
312   }
313 
314   if (path.size() > 1 && path[1] == '/')
315     return false;  // Double slash at the beginning means source-relative.
316 
317   return true;
318 }
319 
MakeAbsolutePathRelativeIfPossible(const base::StringPiece & source_root,const base::StringPiece & path,std::string * dest)320 bool MakeAbsolutePathRelativeIfPossible(const base::StringPiece& source_root,
321                                         const base::StringPiece& path,
322                                         std::string* dest) {
323   DCHECK(IsPathAbsolute(source_root));
324   DCHECK(IsPathAbsolute(path));
325 
326   dest->clear();
327 
328   if (source_root.size() > path.size())
329     return false;  // The source root is longer: the path can never be inside.
330 
331 #if defined(OS_WIN)
332   // Source root should be canonical on Windows.
333   DCHECK(source_root.size() > 2 && source_root[0] != '/' &&
334          source_root[1] == ':' && source_root[2] =='\\');
335 
336   size_t after_common_index = std::string::npos;
337   if (DoesBeginWindowsDriveLetter(path)) {
338     // Handle "C:\foo"
339     if (AreAbsoluteWindowsPathsEqual(source_root,
340                                      path.substr(0, source_root.size())))
341       after_common_index = source_root.size();
342     else
343       return false;
344   } else if (path[0] == '/' && source_root.size() <= path.size() - 1 &&
345              DoesBeginWindowsDriveLetter(path.substr(1))) {
346     // Handle "/C:/foo"
347     if (AreAbsoluteWindowsPathsEqual(source_root,
348                                      path.substr(1, source_root.size())))
349       after_common_index = source_root.size() + 1;
350     else
351       return false;
352   } else {
353     return false;
354   }
355 
356   // If we get here, there's a match and after_common_index identifies the
357   // part after it.
358 
359   // The base may or may not have a trailing slash, so skip all slashes from
360   // the path after our prefix match.
361   size_t first_after_slash = after_common_index;
362   while (first_after_slash < path.size() &&
363          (path[first_after_slash] == '/' || path[first_after_slash] == '\\'))
364     first_after_slash++;
365 
366   dest->assign("//");  // Result is source root relative.
367   dest->append(&path.data()[first_after_slash],
368                path.size() - first_after_slash);
369   return true;
370 
371 #else
372 
373   // On non-Windows this is easy. Since we know both are absolute, just do a
374   // prefix check.
375   if (path.substr(0, source_root.size()) == source_root) {
376     // The base may or may not have a trailing slash, so skip all slashes from
377     // the path after our prefix match.
378     size_t first_after_slash = source_root.size();
379     while (first_after_slash < path.size() && path[first_after_slash] == '/')
380       first_after_slash++;
381 
382     dest->assign("//");  // Result is source root relative.
383     dest->append(&path.data()[first_after_slash],
384                  path.size() - first_after_slash);
385     return true;
386   }
387   return false;
388 #endif
389 }
390 
InvertDir(const SourceDir & path)391 std::string InvertDir(const SourceDir& path) {
392   const std::string value = path.value();
393   if (value.empty())
394     return std::string();
395 
396   DCHECK(value[0] == '/');
397   size_t begin_index = 1;
398 
399   // If the input begins with two slashes, skip over both (this is a
400   // source-relative dir).
401   if (value.size() > 1 && value[1] == '/')
402     begin_index = 2;
403 
404   std::string ret;
405   for (size_t i = begin_index; i < value.size(); i++) {
406     if (value[i] == '/')
407       ret.append("../");
408   }
409   return ret;
410 }
411 
NormalizePath(std::string * path)412 void NormalizePath(std::string* path) {
413   char* pathbuf = path->empty() ? NULL : &(*path)[0];
414 
415   // top_index is the first character we can modify in the path. Anything
416   // before this indicates where the path is relative to.
417   size_t top_index = 0;
418   bool is_relative = true;
419   if (!path->empty() && pathbuf[0] == '/') {
420     is_relative = false;
421 
422     if (path->size() > 1 && pathbuf[1] == '/') {
423       // Two leading slashes, this is a path into the source dir.
424       top_index = 2;
425     } else {
426       // One leading slash, this is a system-absolute path.
427       top_index = 1;
428     }
429   }
430 
431   size_t dest_i = top_index;
432   for (size_t src_i = top_index; src_i < path->size(); /* nothing */) {
433     if (pathbuf[src_i] == '.') {
434       if (src_i == 0 || pathbuf[src_i - 1] == '/') {
435         // Slash followed by a dot, see if it's something special.
436         size_t consumed_len;
437         switch (ClassifyAfterDot(*path, src_i + 1, &consumed_len)) {
438           case NOT_A_DIRECTORY:
439             // Copy the dot to the output, it means nothing special.
440             pathbuf[dest_i++] = pathbuf[src_i++];
441             break;
442           case DIRECTORY_CUR:
443             // Current directory, just skip the input.
444             src_i += consumed_len;
445             break;
446           case DIRECTORY_UP:
447             // Back up over previous directory component. If we're already
448             // at the top, preserve the "..".
449             if (dest_i > top_index) {
450               // The previous char was a slash, remove it.
451               dest_i--;
452             }
453 
454             if (dest_i == top_index) {
455               if (is_relative) {
456                 // We're already at the beginning of a relative input, copy the
457                 // ".." and continue. We need the trailing slash if there was
458                 // one before (otherwise we're at the end of the input).
459                 pathbuf[dest_i++] = '.';
460                 pathbuf[dest_i++] = '.';
461                 if (consumed_len == 3)
462                   pathbuf[dest_i++] = '/';
463 
464                 // This also makes a new "root" that we can't delete by going
465                 // up more levels.  Otherwise "../.." would collapse to
466                 // nothing.
467                 top_index = dest_i;
468               }
469               // Otherwise we're at the beginning of an absolute path. Don't
470               // allow ".." to go up another level and just eat it.
471             } else {
472               // Just find the previous slash or the beginning of input.
473               while (dest_i > 0 && pathbuf[dest_i - 1] != '/')
474                 dest_i--;
475             }
476             src_i += consumed_len;
477         }
478       } else {
479         // Dot not preceeded by a slash, copy it literally.
480         pathbuf[dest_i++] = pathbuf[src_i++];
481       }
482     } else if (pathbuf[src_i] == '/') {
483       if (src_i > 0 && pathbuf[src_i - 1] == '/') {
484         // Two slashes in a row, skip over it.
485         src_i++;
486       } else {
487         // Just one slash, copy it.
488         pathbuf[dest_i++] = pathbuf[src_i++];
489       }
490     } else {
491       // Input nothing special, just copy it.
492       pathbuf[dest_i++] = pathbuf[src_i++];
493     }
494   }
495   path->resize(dest_i);
496 }
497 
ConvertPathToSystem(std::string * path)498 void ConvertPathToSystem(std::string* path) {
499 #if defined(OS_WIN)
500   for (size_t i = 0; i < path->size(); i++) {
501     if ((*path)[i] == '/')
502       (*path)[i] = '\\';
503   }
504 #endif
505 }
506 
PathToSystem(const std::string & path)507 std::string PathToSystem(const std::string& path) {
508   std::string ret(path);
509   ConvertPathToSystem(&ret);
510   return ret;
511 }
512 
RebaseSourceAbsolutePath(const std::string & input,const SourceDir & dest_dir)513 std::string RebaseSourceAbsolutePath(const std::string& input,
514                                      const SourceDir& dest_dir) {
515   CHECK(input.size() >= 2 && input[0] == '/' && input[1] == '/')
516       << "Input to rebase isn't source-absolute: " << input;
517   CHECK(dest_dir.is_source_absolute())
518       << "Dir to rebase to isn't source-absolute: " << dest_dir.value();
519 
520   const std::string& dest = dest_dir.value();
521 
522   // Skip the common prefixes of the source and dest as long as they end in
523   // a [back]slash.
524   size_t common_prefix_len = 2;  // The beginning two "//" are always the same.
525   size_t max_common_length = std::min(input.size(), dest.size());
526   for (size_t i = common_prefix_len; i < max_common_length; i++) {
527     if ((input[i] == '/' || input[i] == '\\') &&
528         (dest[i] == '/' || dest[i] == '\\'))
529       common_prefix_len = i + 1;
530     else if (input[i] != dest[i])
531       break;
532   }
533 
534   // Invert the dest dir starting from the end of the common prefix.
535   std::string ret;
536   for (size_t i = common_prefix_len; i < dest.size(); i++) {
537     if (dest[i] == '/' || dest[i] == '\\')
538       ret.append("../");
539   }
540 
541   // Append any remaining unique input.
542   ret.append(&input[common_prefix_len], input.size() - common_prefix_len);
543 
544   // If the result is still empty, the paths are the same.
545   if (ret.empty())
546     ret.push_back('.');
547 
548   return ret;
549 }
550 
DirectoryWithNoLastSlash(const SourceDir & dir)551 std::string DirectoryWithNoLastSlash(const SourceDir& dir) {
552   std::string ret;
553 
554   if (dir.value().empty()) {
555     // Just keep input the same.
556   } else if (dir.value() == "/") {
557     ret.assign("/.");
558   } else if (dir.value() == "//") {
559     ret.assign("//.");
560   } else {
561     ret.assign(dir.value());
562     ret.resize(ret.size() - 1);
563   }
564   return ret;
565 }
566 
GetToolchainOutputDir(const Settings * settings)567 SourceDir GetToolchainOutputDir(const Settings* settings) {
568   const OutputFile& toolchain_subdir = settings->toolchain_output_subdir();
569 
570   std::string result = settings->build_settings()->build_dir().value();
571   if (!toolchain_subdir.value().empty())
572     result.append(toolchain_subdir.value());
573 
574   return SourceDir(SourceDir::SWAP_IN, &result);
575 }
576 
GetToolchainGenDir(const Settings * settings)577 SourceDir GetToolchainGenDir(const Settings* settings) {
578   const OutputFile& toolchain_subdir = settings->toolchain_output_subdir();
579 
580   std::string result = settings->build_settings()->build_dir().value();
581   if (!toolchain_subdir.value().empty())
582     result.append(toolchain_subdir.value());
583 
584   result.append("gen/");
585   return SourceDir(SourceDir::SWAP_IN, &result);
586 }
587 
GetOutputDirForSourceDir(const Settings * settings,const SourceDir & source_dir)588 SourceDir GetOutputDirForSourceDir(const Settings* settings,
589                                    const SourceDir& source_dir) {
590   SourceDir toolchain = GetToolchainOutputDir(settings);
591 
592   std::string ret;
593   toolchain.SwapValue(&ret);
594   ret.append("obj/");
595 
596   // The source dir should be source-absolute, so we trim off the two leading
597   // slashes to append to the toolchain object directory.
598   DCHECK(source_dir.is_source_absolute());
599   ret.append(&source_dir.value()[2], source_dir.value().size() - 2);
600 
601   return SourceDir(SourceDir::SWAP_IN, &ret);
602 }
603 
GetGenDirForSourceDir(const Settings * settings,const SourceDir & source_dir)604 SourceDir GetGenDirForSourceDir(const Settings* settings,
605                                 const SourceDir& source_dir) {
606   SourceDir toolchain = GetToolchainGenDir(settings);
607 
608   std::string ret;
609   toolchain.SwapValue(&ret);
610 
611   // The source dir should be source-absolute, so we trim off the two leading
612   // slashes to append to the toolchain object directory.
613   DCHECK(source_dir.is_source_absolute());
614   ret.append(&source_dir.value()[2], source_dir.value().size() - 2);
615 
616   return SourceDir(SourceDir::SWAP_IN, &ret);
617 }
618 
GetTargetOutputDir(const Target * target)619 SourceDir GetTargetOutputDir(const Target* target) {
620   return GetOutputDirForSourceDir(target->settings(), target->label().dir());
621 }
622 
GetTargetGenDir(const Target * target)623 SourceDir GetTargetGenDir(const Target* target) {
624   return GetGenDirForSourceDir(target->settings(), target->label().dir());
625 }
626 
GetCurrentOutputDir(const Scope * scope)627 SourceDir GetCurrentOutputDir(const Scope* scope) {
628   return GetOutputDirForSourceDir(scope->settings(), scope->GetSourceDir());
629 }
630 
GetCurrentGenDir(const Scope * scope)631 SourceDir GetCurrentGenDir(const Scope* scope) {
632   return GetGenDirForSourceDir(scope->settings(), scope->GetSourceDir());
633 }
634