blob: 625a358a791e6247b7f4f516edce83dddfb08ff5 [file] [log] [blame]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "tools/gn/filesystem_utils.h"
#include "base/logging.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "tools/gn/location.h"
#include "tools/gn/source_dir.h"
namespace {
enum DotDisposition {
// The given dot is just part of a filename and is not special.
NOT_A_DIRECTORY,
// The given dot is the current directory.
DIRECTORY_CUR,
// The given dot is the first of a double dot that should take us up one.
DIRECTORY_UP
};
// When we find a dot, this function is called with the character following
// that dot to see what it is. The return value indicates what type this dot is
// (see above). This code handles the case where the dot is at the end of the
// input.
//
// |*consumed_len| will contain the number of characters in the input that
// express what we found.
DotDisposition ClassifyAfterDot(const std::string& path,
size_t after_dot,
size_t* consumed_len) {
if (after_dot == path.size()) {
// Single dot at the end.
*consumed_len = 1;
return DIRECTORY_CUR;
}
if (path[after_dot] == '/') {
// Single dot followed by a slash.
*consumed_len = 2; // Consume the slash
return DIRECTORY_CUR;
}
if (path[after_dot] == '.') {
// Two dots.
if (after_dot + 1 == path.size()) {
// Double dot at the end.
*consumed_len = 2;
return DIRECTORY_UP;
}
if (path[after_dot + 1] == '/') {
// Double dot folowed by a slash.
*consumed_len = 3;
return DIRECTORY_UP;
}
}
// The dots are followed by something else, not a directory.
*consumed_len = 1;
return NOT_A_DIRECTORY;
}
} // namesapce
SourceFileType GetSourceFileType(const SourceFile& file,
Settings::TargetOS os) {
base::StringPiece extension = FindExtension(&file.value());
if (extension == "cc" || extension == "cpp" || extension == "cxx")
return SOURCE_CC;
if (extension == "h")
return SOURCE_H;
if (extension == "c")
return SOURCE_C;
switch (os) {
case Settings::MAC:
if (extension == "m")
return SOURCE_M;
if (extension == "mm")
return SOURCE_MM;
break;
case Settings::WIN:
if (extension == "rc")
return SOURCE_RC;
break;
default:
break;
}
// TODO(brettw) asm files.
// TODO(brettw) weird thing with .S on non-Windows platforms.
return SOURCE_UNKNOWN;
}
const char* GetExtensionForOutputType(Target::OutputType type,
Settings::TargetOS os) {
switch (os) {
case Settings::WIN:
switch (type) {
case Target::NONE:
NOTREACHED();
return "";
case Target::EXECUTABLE:
return "exe";
case Target::SHARED_LIBRARY:
return "dll.lib"; // Extension of import library.
case Target::STATIC_LIBRARY:
return "lib";
case Target::LOADABLE_MODULE:
return "dll"; // TODO(brettw) what's this?
default:
NOTREACHED();
}
break;
default:
NOTREACHED();
}
return "";
}
std::string FilePathToUTF8(const base::FilePath& path) {
#if defined(OS_WIN)
return WideToUTF8(path.value());
#else
return path.value();
#endif
}
base::FilePath UTF8ToFilePath(const base::StringPiece& sp) {
#if defined(OS_WIN)
return base::FilePath(UTF8ToWide(sp));
#else
return base::FilePath(sp.as_string());
#endif
}
size_t FindExtensionOffset(const std::string& path) {
for (int i = static_cast<int>(path.size()); i >= 0; i--) {
if (path[i] == '/')
break;
if (path[i] == '.')
return i + 1;
}
return std::string::npos;
}
base::StringPiece FindExtension(const std::string* path) {
size_t extension_offset = FindExtensionOffset(*path);
if (extension_offset == std::string::npos)
return base::StringPiece();
return base::StringPiece(&path->data()[extension_offset],
path->size() - extension_offset);
}
size_t FindFilenameOffset(const std::string& path) {
for (int i = static_cast<int>(path.size()) - 1; i >= 0; i--) {
if (path[i] == '/')
return i + 1;
}
return 0; // No filename found means everything was the filename.
}
base::StringPiece FindFilename(const std::string* path) {
size_t filename_offset = FindFilenameOffset(*path);
if (filename_offset == 0)
return base::StringPiece(*path); // Everything is the file name.
return base::StringPiece(&(*path).data()[filename_offset],
path->size() - filename_offset);
}
base::StringPiece FindFilenameNoExtension(const std::string* path) {
if (path->empty())
return base::StringPiece();
size_t filename_offset = FindFilenameOffset(*path);
size_t extension_offset = FindExtensionOffset(*path);
size_t name_len;
if (extension_offset == std::string::npos)
name_len = path->size() - filename_offset;
else
name_len = extension_offset - filename_offset - 1;
return base::StringPiece(&(*path).data()[filename_offset], name_len);
}
void RemoveFilename(std::string* path) {
path->resize(FindFilenameOffset(*path));
}
bool EndsWithSlash(const std::string& s) {
return !s.empty() && s[s.size() - 1] == '/';
}
base::StringPiece FindDir(const std::string* path) {
size_t filename_offset = FindFilenameOffset(*path);
if (filename_offset == 0u)
return base::StringPiece();
return base::StringPiece(path->data(), filename_offset);
}
bool EnsureStringIsInOutputDir(const SourceDir& dir,
const std::string& str,
const Value& originating,
Err* err) {
// The last char of the dir will be a slash. We don't care if the input ends
// in a slash or not, so just compare up until there.
//
// This check will be wrong for all proper prefixes "e.g. "/output" will
// match "/out" but we don't really care since this is just a sanity check.
const std::string& dir_str = dir.value();
if (str.compare(0, dir_str.length() - 1, dir_str, 0, dir_str.length() - 1)
!= 0) {
*err = Err(originating, "File not inside output directory.",
"The given file should be in the output directory. Normally you would "
"specify\n\"$target_output_dir/foo\" or "
"\"$target_gen_dir/foo\". I interpreted this as\n\""
+ str + "\".");
return false;
}
return true;
}
std::string InvertDir(const SourceDir& path) {
const std::string value = path.value();
if (value.empty())
return std::string();
DCHECK(value[0] == '/');
size_t begin_index = 1;
// If the input begins with two slashes, skip over both (this is a
// source-relative dir).
if (value.size() > 1 && value[1] == '/')
begin_index = 2;
std::string ret;
for (size_t i = begin_index; i < value.size(); i++) {
if (value[i] == '/')
ret.append("../");
}
return ret;
}
void NormalizePath(std::string* path) {
char* pathbuf = path->empty() ? NULL : &(*path)[0];
// top_index is the first character we can modify in the path. Anything
// before this indicates where the path is relative to.
size_t top_index = 0;
bool is_relative = true;
if (!path->empty() && pathbuf[0] == '/') {
is_relative = false;
if (path->size() > 1 && pathbuf[1] == '/') {
// Two leading slashes, this is a path into the source dir.
top_index = 2;
} else {
// One leading slash, this is a system-absolute path.
top_index = 1;
}
}
size_t dest_i = top_index;
for (size_t src_i = top_index; src_i < path->size(); /* nothing */) {
if (pathbuf[src_i] == '.') {
if (src_i == 0 || pathbuf[src_i - 1] == '/') {
// Slash followed by a dot, see if it's something special.
size_t consumed_len;
switch (ClassifyAfterDot(*path, src_i + 1, &consumed_len)) {
case NOT_A_DIRECTORY:
// Copy the dot to the output, it means nothing special.
pathbuf[dest_i++] = pathbuf[src_i++];
break;
case DIRECTORY_CUR:
// Current directory, just skip the input.
src_i += consumed_len;
break;
case DIRECTORY_UP:
// Back up over previous directory component. If we're already
// at the top, preserve the "..".
if (dest_i > top_index) {
// The previous char was a slash, remove it.
dest_i--;
}
if (dest_i == top_index) {
if (is_relative) {
// We're already at the beginning of a relative input, copy the
// ".." and continue. We need the trailing slash if there was
// one before (otherwise we're at the end of the input).
pathbuf[dest_i++] = '.';
pathbuf[dest_i++] = '.';
if (consumed_len == 3)
pathbuf[dest_i++] = '/';
// This also makes a new "root" that we can't delete by going
// up more levels. Otherwise "../.." would collapse to
// nothing.
top_index = dest_i;
}
// Otherwise we're at the beginning of an absolute path. Don't
// allow ".." to go up another level and just eat it.
} else {
// Just find the previous slash or the beginning of input.
while (dest_i > 0 && pathbuf[dest_i - 1] != '/')
dest_i--;
}
src_i += consumed_len;
}
} else {
// Dot not preceeded by a slash, copy it literally.
pathbuf[dest_i++] = pathbuf[src_i++];
}
} else if (pathbuf[src_i] == '/') {
if (src_i > 0 && pathbuf[src_i - 1] == '/') {
// Two slashes in a row, skip over it.
src_i++;
} else {
// Just one slash, copy it.
pathbuf[dest_i++] = pathbuf[src_i++];
}
} else {
// Input nothing special, just copy it.
pathbuf[dest_i++] = pathbuf[src_i++];
}
}
path->resize(dest_i);
}
void ConvertPathToSystem(std::string* path) {
#if defined(OS_WIN)
for (size_t i = 0; i < path->size(); i++) {
if ((*path)[i] == '/')
(*path)[i] = '\\';
}
#endif
}
std::string PathToSystem(const std::string& path) {
std::string ret(path);
ConvertPathToSystem(&ret);
return ret;
}