Home | History | Annotate | Download | only in gn
      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.
     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)
     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.
     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 
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    263 void RemoveFilename(std::string* path) {
    264   path->resize(FindFilenameOffset(*path));
    265 }
    266 
    267 bool EndsWithSlash(const std::string& s) {
    268   return !s.empty() && s[s.size() - 1] == '/';
    269 }
    270 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    507 std::string PathToSystem(const std::string& path) {
    508   std::string ret(path);
    509   ConvertPathToSystem(&ret);
    510   return ret;
    511 }
    512 
    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 
    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 
    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 
    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 
    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 
    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 
    619 SourceDir GetTargetOutputDir(const Target* target) {
    620   return GetOutputDirForSourceDir(target->settings(), target->label().dir());
    621 }
    622 
    623 SourceDir GetTargetGenDir(const Target* target) {
    624   return GetGenDirForSourceDir(target->settings(), target->label().dir());
    625 }
    626 
    627 SourceDir GetCurrentOutputDir(const Scope* scope) {
    628   return GetOutputDirForSourceDir(scope->settings(), scope->GetSourceDir());
    629 }
    630 
    631 SourceDir GetCurrentGenDir(const Scope* scope) {
    632   return GetGenDirForSourceDir(scope->settings(), scope->GetSourceDir());
    633 }
    634