#include <algorithm>
+#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "tools/gn/location.h"
+#include "tools/gn/settings.h"
#include "tools/gn/source_dir.h"
namespace {
*consumed_len = 1;
return DIRECTORY_CUR;
}
- if (path[after_dot] == '/') {
+ if (IsSlash(path[after_dot])) {
// Single dot followed by a slash.
*consumed_len = 2; // Consume the slash
return DIRECTORY_CUR;
*consumed_len = 2;
return DIRECTORY_UP;
}
- if (path[after_dot + 1] == '/') {
+ if (IsSlash(path[after_dot + 1])) {
// Double dot folowed by a slash.
*consumed_len = 3;
return DIRECTORY_UP;
if (path[1] != ':')
return false;
- // Check drive letter
- if (!((path[0] >= 'A' && path[0] <= 'Z') ||
- path[0] >= 'a' && path[0] <= 'z'))
+ // Check drive letter.
+ if (!IsAsciiAlpha(path[0]))
return false;
- if (path[2] != '/' && path[2] != '\\')
+ if (!IsSlash(path[2]))
return false;
return true;
}
#endif
-} // namespace
-
-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;
+// A wrapper around FilePath.GetComponents that works the way we need. This is
+// not super efficient since it does some O(n) transformations on the path. If
+// this is called a lot, we might want to optimize.
+std::vector<base::FilePath::StringType> GetPathComponents(
+ const base::FilePath& path) {
+ std::vector<base::FilePath::StringType> result;
+ path.GetComponents(&result);
- switch (os) {
- case Settings::MAC:
- if (extension == "m")
- return SOURCE_M;
- if (extension == "mm")
- return SOURCE_MM;
- break;
+ if (result.empty())
+ return result;
- case Settings::WIN:
- if (extension == "rc")
- return SOURCE_RC;
- // TODO(brettw) asm files.
- break;
+ // GetComponents will preserve the "/" at the beginning, which confuses us.
+ // We don't expect to have relative paths in this function.
+ // Don't use IsSeparator since we always want to allow backslashes.
+ if (result[0] == FILE_PATH_LITERAL("/") ||
+ result[0] == FILE_PATH_LITERAL("\\"))
+ result.erase(result.begin());
- default:
- break;
- }
+#if defined(OS_WIN)
+ // On Windows, GetComponents will give us [ "C:", "/", "foo" ], and we
+ // don't want the slash in there. This doesn't support input like "C:foo"
+ // which means foo relative to the current directory of the C drive but
+ // that's basically legacy DOS behavior we don't need to support.
+ if (result.size() >= 2 && result[1].size() == 1 &&
+ IsSlash(static_cast<char>(result[1][0])))
+ result.erase(result.begin() + 1);
+#endif
- if (os != Settings::WIN) {
- if (extension == "S")
- return SOURCE_S;
- }
+ return result;
+}
- return SOURCE_UNKNOWN;
+// Provides the equivalent of == for filesystem strings, trying to do
+// approximately the right thing with case.
+bool FilesystemStringsEqual(const base::FilePath::StringType& a,
+ const base::FilePath::StringType& b) {
+#if defined(OS_WIN)
+ // Assume case-insensitive filesystems on Windows. We use the CompareString
+ // function to do a case-insensitive comparison based on the current locale
+ // (we don't want GN to depend on ICU which is large and requires data
+ // files). This isn't perfect, but getting this perfectly right is very
+ // difficult and requires I/O, and this comparison should cover 99.9999% of
+ // all cases.
+ //
+ // Note: The documentation for CompareString says it runs fastest on
+ // null-terminated strings with -1 passed for the length, so we do that here.
+ // There should not be embedded nulls in filesystem strings.
+ return ::CompareString(LOCALE_USER_DEFAULT, LINGUISTIC_IGNORECASE,
+ a.c_str(), -1, b.c_str(), -1) == CSTR_EQUAL;
+#else
+ // Assume case-sensitive filesystems on non-Windows.
+ return a == b;
+#endif
}
+} // namespace
+
const char* GetExtensionForOutputType(Target::OutputType type,
Settings::TargetOS os) {
switch (os) {
std::string FilePathToUTF8(const base::FilePath::StringType& str) {
#if defined(OS_WIN)
- return WideToUTF8(str);
+ return base::WideToUTF8(str);
#else
return str;
#endif
base::FilePath UTF8ToFilePath(const base::StringPiece& sp) {
#if defined(OS_WIN)
- return base::FilePath(UTF8ToWide(sp));
+ return base::FilePath(base::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] == '/')
+ if (IsSlash(path[i]))
break;
if (path[i] == '.')
return i + 1;
size_t FindFilenameOffset(const std::string& path) {
for (int i = static_cast<int>(path.size()) - 1; i >= 0; i--) {
- if (path[i] == '/')
+ if (IsSlash(path[i]))
return i + 1;
}
return 0; // No filename found means everything was the filename.
}
bool EndsWithSlash(const std::string& s) {
- return !s.empty() && s[s.size() - 1] == '/';
+ return !s.empty() && IsSlash(s[s.size() - 1]);
}
base::StringPiece FindDir(const std::string* path) {
return base::StringPiece(path->data(), filename_offset);
}
+base::StringPiece FindLastDirComponent(const SourceDir& dir) {
+ const std::string& dir_string = dir.value();
+
+ if (dir_string.empty())
+ return base::StringPiece();
+ int cur = static_cast<int>(dir_string.size()) - 1;
+ DCHECK(dir_string[cur] == '/');
+ int end = cur;
+ cur--; // Skip before the last slash.
+
+ for (; cur >= 0; cur--) {
+ if (dir_string[cur] == '/')
+ return base::StringPiece(&dir_string[cur + 1], end - cur - 1);
+ }
+ return base::StringPiece(&dir_string[0], end);
+}
+
bool EnsureStringIsInOutputDir(const SourceDir& dir,
const std::string& str,
- const Value& originating,
+ const ParseNode* origin,
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;
+ if (str.compare(0, dir_str.length(), dir_str) == 0)
+ return true; // Output directory is hardcoded.
+
+ *err = Err(origin, "File is not inside output directory.",
+ "The given file should be in the output directory. Normally you would "
+ "specify\n\"$target_out_dir/foo\" or "
+ "\"$target_gen_dir/foo\". I interpreted this as\n\""
+ + str + "\".");
+ return false;
}
bool IsPathAbsolute(const base::StringPiece& path) {
if (path.empty())
return false;
- if (path[0] != '/') {
+ if (!IsSlash(path[0])) {
#if defined(OS_WIN)
// Check for Windows system paths like "C:\foo".
- if (path.size() > 2 &&
- path[1] == ':' && (path[2] == '/' || path[2] == '\\'))
+ if (path.size() > 2 && path[1] == ':' && IsSlash(path[2]))
return true;
#endif
return false; // Doesn't begin with a slash, is relative.
}
+ // Double forward slash at the beginning means source-relative (we don't
+ // allow backslashes for denoting this).
if (path.size() > 1 && path[1] == '/')
- return false; // Double slash at the beginning means source-relative.
+ return false;
return true;
}
return false; // The source root is longer: the path can never be inside.
#if defined(OS_WIN)
- // Source root should be canonical on Windows.
+ // Source root should be canonical on Windows. Note that the initial slash
+ // must be forward slash, but that the other ones can be either forward or
+ // backward.
DCHECK(source_root.size() > 2 && source_root[0] != '/' &&
- source_root[1] == ':' && source_root[2] =='\\');
+ source_root[1] == ':' && IsSlash(source_root[2]));
size_t after_common_index = std::string::npos;
if (DoesBeginWindowsDriveLetter(path)) {
// The base may or may not have a trailing slash, so skip all slashes from
// the path after our prefix match.
size_t first_after_slash = after_common_index;
- while (first_after_slash < path.size() &&
- (path[first_after_slash] == '/' || path[first_after_slash] == '\\'))
+ while (first_after_slash < path.size() && IsSlash(path[first_after_slash]))
first_after_slash++;
dest->assign("//"); // Result is source root relative.
// The base may or may not have a trailing slash, so skip all slashes from
// the path after our prefix match.
size_t first_after_slash = source_root.size();
- while (first_after_slash < path.size() && path[first_after_slash] == '/')
+ while (first_after_slash < path.size() && IsSlash(path[first_after_slash]))
first_after_slash++;
dest->assign("//"); // Result is source root relative.
size_t begin_index = 1;
// If the input begins with two slashes, skip over both (this is a
- // source-relative dir).
+ // source-relative dir). These must be forward slashes only.
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] == '/')
+ if (IsSlash(value[i]))
ret.append("../");
}
return ret;
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] == '/') {
+ if (src_i == 0 || IsSlash(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)) {
// 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] != '/')
+ while (dest_i > 0 && !IsSlash(pathbuf[dest_i - 1]))
dest_i--;
}
src_i += consumed_len;
// 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] == '/') {
+ } else if (IsSlash(pathbuf[src_i])) {
+ if (src_i > 0 && IsSlash(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++];
+ // Just one slash, copy it, normalizing to foward slash.
+ pathbuf[dest_i] = '/';
+ dest_i++;
+ src_i++;
}
} else {
// Input nothing special, just copy it.
#endif
}
-std::string PathToSystem(const std::string& path) {
- std::string ret(path);
- ConvertPathToSystem(&ret);
- return ret;
-}
-
std::string RebaseSourceAbsolutePath(const std::string& input,
const SourceDir& dest_dir) {
CHECK(input.size() >= 2 && input[0] == '/' && input[1] == '/')
size_t common_prefix_len = 2; // The beginning two "//" are always the same.
size_t max_common_length = std::min(input.size(), dest.size());
for (size_t i = common_prefix_len; i < max_common_length; i++) {
- if ((input[i] == '/' || input[i] == '\\') &&
- (dest[i] == '/' || dest[i] == '\\'))
+ if (IsSlash(input[i]) && IsSlash(dest[i]))
common_prefix_len = i + 1;
else if (input[i] != dest[i])
break;
// Invert the dest dir starting from the end of the common prefix.
std::string ret;
for (size_t i = common_prefix_len; i < dest.size(); i++) {
- if (dest[i] == '/' || dest[i] == '\\')
+ if (IsSlash(dest[i]))
ret.append("../");
}
return ret;
}
+
+std::string DirectoryWithNoLastSlash(const SourceDir& dir) {
+ std::string ret;
+
+ if (dir.value().empty()) {
+ // Just keep input the same.
+ } else if (dir.value() == "/") {
+ ret.assign("/.");
+ } else if (dir.value() == "//") {
+ ret.assign("//.");
+ } else {
+ ret.assign(dir.value());
+ ret.resize(ret.size() - 1);
+ }
+ return ret;
+}
+
+SourceDir SourceDirForPath(const base::FilePath& source_root,
+ const base::FilePath& path) {
+ std::vector<base::FilePath::StringType> source_comp =
+ GetPathComponents(source_root);
+ std::vector<base::FilePath::StringType> path_comp =
+ GetPathComponents(path);
+
+ // See if path is inside the source root by looking for each of source root's
+ // components at the beginning of path.
+ bool is_inside_source;
+ if (path_comp.size() < source_comp.size()) {
+ // Too small to fit.
+ is_inside_source = false;
+ } else {
+ is_inside_source = true;
+ for (size_t i = 0; i < source_comp.size(); i++) {
+ if (!FilesystemStringsEqual(source_comp[i], path_comp[i])) {
+ is_inside_source = false;
+ break;
+ }
+ }
+ }
+
+ std::string result_str;
+ size_t initial_path_comp_to_use;
+ if (is_inside_source) {
+ // Construct a source-relative path beginning in // and skip all of the
+ // shared directories.
+ result_str = "//";
+ initial_path_comp_to_use = source_comp.size();
+ } else {
+ // Not inside source code, construct a system-absolute path.
+ result_str = "/";
+ initial_path_comp_to_use = 0;
+ }
+
+ for (size_t i = initial_path_comp_to_use; i < path_comp.size(); i++) {
+ result_str.append(FilePathToUTF8(path_comp[i]));
+ result_str.push_back('/');
+ }
+ return SourceDir(result_str);
+}
+
+SourceDir SourceDirForCurrentDirectory(const base::FilePath& source_root) {
+ base::FilePath cd;
+ base::GetCurrentDirectory(&cd);
+ return SourceDirForPath(source_root, cd);
+}
+
+std::string GetOutputSubdirName(const Label& toolchain_label, bool is_default) {
+ // The default toolchain has no subdir.
+ if (is_default)
+ return std::string();
+
+ // For now just assume the toolchain name is always a valid dir name. We may
+ // want to clean up the in the future.
+ return toolchain_label.name() + "/";
+}
+
+SourceDir GetToolchainOutputDir(const Settings* settings) {
+ return settings->toolchain_output_subdir().AsSourceDir(
+ settings->build_settings());
+}
+
+SourceDir GetToolchainOutputDir(const BuildSettings* build_settings,
+ const Label& toolchain_label, bool is_default) {
+ std::string result = build_settings->build_dir().value();
+ result.append(GetOutputSubdirName(toolchain_label, is_default));
+ return SourceDir(SourceDir::SWAP_IN, &result);
+}
+
+SourceDir GetToolchainGenDir(const Settings* settings) {
+ return GetToolchainGenDirAsOutputFile(settings).AsSourceDir(
+ settings->build_settings());
+}
+
+OutputFile GetToolchainGenDirAsOutputFile(const Settings* settings) {
+ OutputFile result(settings->toolchain_output_subdir());
+ result.value().append("gen/");
+ return result;
+}
+
+SourceDir GetToolchainGenDir(const BuildSettings* build_settings,
+ const Label& toolchain_label, bool is_default) {
+ std::string result = GetToolchainOutputDir(
+ build_settings, toolchain_label, is_default).value();
+ result.append("gen/");
+ return SourceDir(SourceDir::SWAP_IN, &result);
+}
+
+SourceDir GetOutputDirForSourceDir(const Settings* settings,
+ const SourceDir& source_dir) {
+ return GetOutputDirForSourceDirAsOutputFile(settings, source_dir).AsSourceDir(
+ settings->build_settings());
+}
+
+OutputFile GetOutputDirForSourceDirAsOutputFile(const Settings* settings,
+ const SourceDir& source_dir) {
+ OutputFile result = settings->toolchain_output_subdir();
+ result.value().append("obj/");
+
+ if (source_dir.is_source_absolute()) {
+ // The source dir is source-absolute, so we trim off the two leading
+ // slashes to append to the toolchain object directory.
+ result.value().append(&source_dir.value()[2],
+ source_dir.value().size() - 2);
+ }
+ return result;
+}
+
+SourceDir GetGenDirForSourceDir(const Settings* settings,
+ const SourceDir& source_dir) {
+ return GetGenDirForSourceDirAsOutputFile(settings, source_dir).AsSourceDir(
+ settings->build_settings());
+}
+
+OutputFile GetGenDirForSourceDirAsOutputFile(const Settings* settings,
+ const SourceDir& source_dir) {
+ OutputFile result = GetToolchainGenDirAsOutputFile(settings);
+
+ if (source_dir.is_source_absolute()) {
+ // The source dir should be source-absolute, so we trim off the two leading
+ // slashes to append to the toolchain object directory.
+ DCHECK(source_dir.is_source_absolute());
+ result.value().append(&source_dir.value()[2],
+ source_dir.value().size() - 2);
+ }
+ return result;
+}
+
+SourceDir GetTargetOutputDir(const Target* target) {
+ return GetOutputDirForSourceDirAsOutputFile(
+ target->settings(), target->label().dir()).AsSourceDir(
+ target->settings()->build_settings());
+}
+
+OutputFile GetTargetOutputDirAsOutputFile(const Target* target) {
+ return GetOutputDirForSourceDirAsOutputFile(
+ target->settings(), target->label().dir());
+}
+
+SourceDir GetTargetGenDir(const Target* target) {
+ return GetTargetGenDirAsOutputFile(target).AsSourceDir(
+ target->settings()->build_settings());
+}
+
+OutputFile GetTargetGenDirAsOutputFile(const Target* target) {
+ return GetGenDirForSourceDirAsOutputFile(
+ target->settings(), target->label().dir());
+}
+
+SourceDir GetCurrentOutputDir(const Scope* scope) {
+ return GetOutputDirForSourceDirAsOutputFile(
+ scope->settings(), scope->GetSourceDir()).AsSourceDir(
+ scope->settings()->build_settings());
+}
+
+SourceDir GetCurrentGenDir(const Scope* scope) {
+ return GetGenDirForSourceDir(scope->settings(), scope->GetSourceDir());
+}