// 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 #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" #if defined(OS_WIN) #include #endif 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 (IsSlash(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 (IsSlash(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; } #if defined(OS_WIN) inline char NormalizeWindowsPathChar(char c) { if (c == '/') return '\\'; return base::ToLowerASCII(c); } // Attempts to do a case and slash-insensitive comparison of two 8-bit Windows // paths. bool AreAbsoluteWindowsPathsEqual(const base::StringPiece& a, const base::StringPiece& b) { if (a.size() != b.size()) return false; // For now, just do a case-insensitive ASCII comparison. We could convert to // UTF-16 and use ICU if necessary. for (size_t i = 0; i < a.size(); i++) { if (NormalizeWindowsPathChar(a[i]) != NormalizeWindowsPathChar(b[i])) return false; } return true; } bool DoesBeginWindowsDriveLetter(const base::StringPiece& path) { if (path.size() < 3) return false; // Check colon first, this will generally fail fastest. if (path[1] != ':') return false; // Check drive letter. if (!base::IsAsciiAlpha(path[0])) return false; if (!IsSlash(path[2])) return false; return true; } #endif // 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 GetPathComponents( const base::FilePath& path) { std::vector result; path.GetComponents(&result); if (result.empty()) return result; // 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()); #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(result[1][0]))) result.erase(result.begin() + 1); #endif return result; } // 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 } // Helper function for computing subdirectories in the build directory // corresponding to absolute paths. This will try to resolve the absolute // path as a source-relative path first, and otherwise it creates a // special subdirectory for absolute paths to keep them from colliding with // other generated sources and outputs. void AppendFixedAbsolutePathSuffix(const BuildSettings* build_settings, const SourceDir& source_dir, OutputFile* result) { const std::string& build_dir = build_settings->build_dir().value(); if (base::StartsWith(source_dir.value(), build_dir, base::CompareCase::SENSITIVE)) { size_t build_dir_size = build_dir.size(); result->value().append(&source_dir.value()[build_dir_size], source_dir.value().size() - build_dir_size); } else { result->value().append("ABS_PATH"); #if defined(OS_WIN) // Windows absolute path contains ':' after drive letter. Remove it to // avoid inserting ':' in the middle of path (eg. "ABS_PATH/C:/"). std::string src_dir_value = source_dir.value(); const auto colon_pos = src_dir_value.find(':'); if (colon_pos != std::string::npos) src_dir_value.erase(src_dir_value.begin() + colon_pos); #else const std::string& src_dir_value = source_dir.value(); #endif result->value().append(src_dir_value); } } } // namespace std::string FilePathToUTF8(const base::FilePath::StringType& str) { #if defined(OS_WIN) return base::WideToUTF8(str); #else return str; #endif } base::FilePath UTF8ToFilePath(const base::StringPiece& sp) { #if defined(OS_WIN) 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(path.size()); i >= 0; i--) { if (IsSlash(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(path.size()) - 1; i >= 0; i--) { if (IsSlash(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() && IsSlash(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); } base::StringPiece FindLastDirComponent(const SourceDir& dir) { const std::string& dir_string = dir.value(); if (dir_string.empty()) return base::StringPiece(); int cur = static_cast(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 IsStringInOutputDir(const SourceDir& output_dir, const std::string& str) { // 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 = output_dir.value(); return str.compare(0, dir_str.length(), dir_str) == 0; } bool EnsureStringIsInOutputDir(const SourceDir& output_dir, const std::string& str, const ParseNode* origin, Err* err) { if (IsStringInOutputDir(output_dir, str)) 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 (!IsSlash(path[0])) { #if defined(OS_WIN) // Check for Windows system paths like "C:\foo". 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; return true; } bool IsPathSourceAbsolute(const base::StringPiece& path) { return (path.size() >= 2 && path[0] == '/' && path[1] == '/'); } bool MakeAbsolutePathRelativeIfPossible(const base::StringPiece& source_root, const base::StringPiece& path, std::string* dest) { DCHECK(IsPathAbsolute(source_root)); DCHECK(IsPathAbsolute(path)); dest->clear(); if (source_root.size() > path.size()) return false; // The source root is longer: the path can never be inside. #if defined(OS_WIN) // 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] == ':' && IsSlash(source_root[2])); size_t after_common_index = std::string::npos; if (DoesBeginWindowsDriveLetter(path)) { // Handle "C:\foo" if (AreAbsoluteWindowsPathsEqual(source_root, path.substr(0, source_root.size()))) after_common_index = source_root.size(); else return false; } else if (path[0] == '/' && source_root.size() <= path.size() - 1 && DoesBeginWindowsDriveLetter(path.substr(1))) { // Handle "/C:/foo" if (AreAbsoluteWindowsPathsEqual(source_root, path.substr(1, source_root.size()))) after_common_index = source_root.size() + 1; else return false; } else { return false; } // If we get here, there's a match and after_common_index identifies the // part after it. // 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() && IsSlash(path[first_after_slash])) first_after_slash++; dest->assign("//"); // Result is source root relative. dest->append(&path.data()[first_after_slash], path.size() - first_after_slash); return true; #else // On non-Windows this is easy. Since we know both are absolute, just do a // prefix check. if (path.substr(0, source_root.size()) == source_root) { // 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() && IsSlash(path[first_after_slash])) first_after_slash++; dest->assign("//"); // Result is source root relative. dest->append(&path.data()[first_after_slash], path.size() - first_after_slash); return true; } return false; #endif } base::FilePath MakeAbsoluteFilePathRelativeIfPossible( const base::FilePath& base, const base::FilePath& target) { DCHECK(base.IsAbsolute()); DCHECK(target.IsAbsolute()); std::vector base_components; std::vector target_components; base.GetComponents(&base_components); target.GetComponents(&target_components); #if defined(OS_WIN) // On Windows, it's impossible to have a relative path from C:\foo to D:\bar, // so return the target as an aboslute path instead. if (base_components[0] != target_components[0]) return target; #endif size_t i; for (i = 0; i < base_components.size() && i < target_components.size(); i++) { if (base_components[i] != target_components[i]) break; } std::vector relative_components; for (size_t j = i; j < base_components.size(); j++) relative_components.push_back(base::FilePath::kParentDirectory); for (size_t j = i; j < target_components.size(); j++) relative_components.push_back(target_components[j]); if (relative_components.size() <= 1) { // In case the file pointed-to is an executable, prepend the current // directory to the path -- if the path was "gn", use "./gn" instead. If // the file path is used in a command, this prevents issues where "gn" might // not be in the PATH (or it is in the path, and the wrong gn is used). relative_components.insert(relative_components.begin(), base::FilePath::kCurrentDirectory); } // base::FilePath::Append(component) replaces the file path with |component| // if the path is base::Filepath::kCurrentDirectory. We want to preserve the // leading "./", so we build the path ourselves and use that to construct the // base::FilePath. base::FilePath::StringType separator(&base::FilePath::kSeparators[0], 1); return base::FilePath(base::JoinString(relative_components, separator)); } void NormalizePath(std::string* path, const base::StringPiece& source_root) { char* pathbuf = path->empty() ? nullptr : &(*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 || 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)) { 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; } else if (top_index == 2 && !source_root.empty()) { // |path| was passed in as a source-absolute path. Prepend // |source_root| to make |path| absolute. |source_root| must not // end with a slash unless we are at root. DCHECK(source_root.size() == 1u || !IsSlash(source_root[source_root.size() - 1u])); size_t source_root_len = source_root.size(); #if defined(OS_WIN) // On Windows, if the source_root does not start with a slash, // append one here for consistency. if (!IsSlash(source_root[0])) { path->insert(0, "/" + source_root.as_string()); source_root_len++; } else { path->insert(0, source_root.data(), source_root_len); } // Normalize slashes in source root portion. for (size_t i = 0; i < source_root_len; ++i) { if ((*path)[i] == '\\') (*path)[i] = '/'; } #else path->insert(0, source_root.data(), source_root_len); #endif // |path| is now absolute, so |top_index| is 1. |dest_i| and // |src_i| should be incremented to keep the same relative // position. Comsume the leading "//" by decrementing |dest_i|. top_index = 1; pathbuf = &(*path)[0]; dest_i += source_root_len - 2; src_i += source_root_len; // Just find the previous slash or the beginning of input. while (dest_i > 0 && !IsSlash(pathbuf[dest_i - 1])) dest_i--; } // Otherwise we're at the beginning of a system-absolute path, or // a source-absolute path for which we don't know the 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 && !IsSlash(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 (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, normalizing to foward slash. pathbuf[dest_i] = '/'; dest_i++; 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 MakeRelativePath(const std::string& input, const std::string& dest) { #if defined(OS_WIN) // Make sure that absolute |input| path starts with a slash if |dest| path // does. Otherwise skipping common prefixes won't work properly. Ensure the // same for |dest| path too. if (IsPathAbsolute(input) && !IsSlash(input[0]) && IsSlash(dest[0])) { std::string corrected_input(1, dest[0]); corrected_input.append(input); return MakeRelativePath(corrected_input, dest); } if (IsPathAbsolute(dest) && !IsSlash(dest[0]) && IsSlash(input[0])) { std::string corrected_dest(1, input[0]); corrected_dest.append(dest); return MakeRelativePath(input, corrected_dest); } // Make sure that both absolute paths use the same drive letter case. if (IsPathAbsolute(input) && IsPathAbsolute(dest) && input.size() > 1 && dest.size() > 1) { int letter_pos = base::IsAsciiAlpha(input[0]) ? 0 : 1; if (input[letter_pos] != dest[letter_pos] && base::ToUpperASCII(input[letter_pos]) == base::ToUpperASCII(dest[letter_pos])) { std::string corrected_input = input; corrected_input[letter_pos] = dest[letter_pos]; return MakeRelativePath(corrected_input, dest); } } #endif std::string ret; // Skip the common prefixes of the source and dest as long as they end in // a [back]slash. size_t common_prefix_len = 0; size_t max_common_length = std::min(input.size(), dest.size()); for (size_t i = common_prefix_len; i < max_common_length; 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. for (size_t i = common_prefix_len; i < dest.size(); i++) { if (IsSlash(dest[i])) ret.append("../"); } // Append any remaining unique input. ret.append(&input[common_prefix_len], input.size() - common_prefix_len); // If the result is still empty, the paths are the same. if (ret.empty()) ret.push_back('.'); return ret; } std::string RebasePath(const std::string& input, const SourceDir& dest_dir, const base::StringPiece& source_root) { std::string ret; DCHECK(source_root.empty() || !source_root.ends_with("/")); bool input_is_source_path = (input.size() >= 2 && input[0] == '/' && input[1] == '/'); if (!source_root.empty() && (!input_is_source_path || !dest_dir.is_source_absolute())) { std::string input_full; std::string dest_full; if (input_is_source_path) { source_root.AppendToString(&input_full); input_full.push_back('/'); input_full.append(input, 2, std::string::npos); } else { input_full.append(input); } if (dest_dir.is_source_absolute()) { source_root.AppendToString(&dest_full); dest_full.push_back('/'); dest_full.append(dest_dir.value(), 2, std::string::npos); } else { #if defined(OS_WIN) // On Windows, SourceDir system-absolute paths start // with /, e.g. "/C:/foo/bar". const std::string& value = dest_dir.value(); if (value.size() > 2 && value[2] == ':') dest_full.append(dest_dir.value().substr(1)); else dest_full.append(dest_dir.value()); #else dest_full.append(dest_dir.value()); #endif } bool remove_slash = false; if (!EndsWithSlash(input_full)) { input_full.push_back('/'); remove_slash = true; } ret = MakeRelativePath(input_full, dest_full); if (remove_slash && ret.size() > 1) ret.resize(ret.size() - 1); return ret; } ret = MakeRelativePath(input, dest_dir.value()); return ret; } base::FilePath ResolvePath(const std::string& value, bool as_file, const base::FilePath& source_root) { if (value.empty()) return base::FilePath(); std::string converted; if (!IsPathSourceAbsolute(value)) { if (value.size() > 2 && value[2] == ':') { // Windows path, strip the leading slash. converted.assign(&value[1], value.size() - 1); } else { converted.assign(value); } return base::FilePath(UTF8ToFilePath(converted)); } // String the double-leading slash for source-relative paths. converted.assign(&value[2], value.size() - 2); if (as_file && source_root.empty()) return UTF8ToFilePath(converted).NormalizePathSeparatorsTo('/'); return source_root.Append(UTF8ToFilePath(converted)) .NormalizePathSeparatorsTo('/'); } template std::string ResolveRelative(const StringType& input, const std::string& value, bool as_file, const base::StringPiece& source_root) { std::string result; if (input.size() >= 2 && input[0] == '/' && input[1] == '/') { // Source-relative. result.assign(input.data(), input.size()); if (!as_file) { if (!EndsWithSlash(result)) result.push_back('/'); } NormalizePath(&result, source_root); return result; } else if (IsPathAbsolute(input)) { if (source_root.empty() || !MakeAbsolutePathRelativeIfPossible(source_root, input, &result)) { #if defined(OS_WIN) if (input[0] != '/') // See the file case for why we do this check. result = "/"; #endif result.append(input.data(), input.size()); } NormalizePath(&result); if (!as_file) { if (!EndsWithSlash(result)) result.push_back('/'); } return result; } if (!source_root.empty()) { std::string absolute = FilePathToUTF8(ResolvePath(value, as_file, UTF8ToFilePath(source_root)) .AppendASCII(input) .value()); NormalizePath(&absolute); if (!MakeAbsolutePathRelativeIfPossible(source_root, absolute, &result)) { #if defined(OS_WIN) if (absolute[0] != '/') // See the file case for why we do this check. result = "/"; #endif result.append(absolute.data(), absolute.size()); } if (!as_file && !EndsWithSlash(result)) result.push_back('/'); return result; } // With no source_root, there's nothing we can do about // e.g. input=../../../path/to/file and value=//source and we'll // errornously return //file. result.reserve(value.size() + input.size()); result.assign(value); result.append(input.data(), input.size()); NormalizePath(&result); if (!as_file && !EndsWithSlash(result)) result.push_back('/'); return result; } // Explicit template instantiation template std::string ResolveRelative(const base::StringPiece& input, const std::string& value, bool as_file, const base::StringPiece& source_root); template std::string ResolveRelative(const std::string& input, const std::string& value, bool as_file, const base::StringPiece& source_root); 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 source_comp = GetPathComponents(source_root); std::vector 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() || source_root.empty()) { // 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() + "/"; } bool ContentsEqual(const base::FilePath& file_path, const std::string& data) { // Compare file and stream sizes first. Quick and will save us some time if // they are different sizes. int64_t file_size; if (!base::GetFileSize(file_path, &file_size) || static_cast(file_size) != data.size()) { return false; } std::string file_data; file_data.resize(file_size); if (!base::ReadFileToString(file_path, &file_data)) return false; return file_data == data; } bool WriteFileIfChanged(const base::FilePath& file_path, const std::string& data, Err* err) { if (ContentsEqual(file_path, data)) return true; return WriteFile(file_path, data, err); } bool WriteFile(const base::FilePath& file_path, const std::string& data, Err* err) { // Create the directory if necessary. if (!base::CreateDirectory(file_path.DirName())) { if (err) { *err = Err(Location(), "Unable to create directory.", "I was using \"" + FilePathToUTF8(file_path.DirName()) + "\"."); } return false; } int size = static_cast(data.size()); bool write_success = false; #if defined(OS_WIN) // On Windows, provide a custom implementation of base::WriteFile. Sometimes // the base version fails, especially on the bots. The guess is that Windows // Defender or other antivirus programs still have the file open (after // checking for the read) when the write happens immediately after. This // version opens with FILE_SHARE_READ (normally not what you want when // replacing the entire contents of the file) which lets us continue even if // another program has the file open for reading. See http://crbug.com/468437 base::win::ScopedHandle file(::CreateFile(file_path.value().c_str(), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL)); if (file.IsValid()) { DWORD written; BOOL result = ::WriteFile(file.Get(), data.c_str(), size, &written, NULL); if (result) { if (static_cast(written) == size) { write_success = true; } else { // Didn't write all the bytes. LOG(ERROR) << "wrote" << written << " bytes to " << base::UTF16ToUTF8(file_path.value()) << " expected " << size; } } else { // WriteFile failed. PLOG(ERROR) << "writing file " << base::UTF16ToUTF8(file_path.value()) << " failed"; } } else { PLOG(ERROR) << "CreateFile failed for path " << base::UTF16ToUTF8(file_path.value()); } #else write_success = base::WriteFile(file_path, data.c_str(), size) == size; #endif if (!write_success && err) { *err = Err(Location(), "Unable to write file.", "I was writing \"" + FilePathToUTF8(file_path) + "\"."); } return write_success; } BuildDirContext::BuildDirContext(const Target* target) : BuildDirContext(target->settings()) { } BuildDirContext::BuildDirContext(const Settings* settings) : BuildDirContext(settings->build_settings(), settings->toolchain_label(), settings->is_default()) { } BuildDirContext::BuildDirContext(const Scope* execution_scope) : BuildDirContext(execution_scope->settings()) { } BuildDirContext::BuildDirContext(const Scope* execution_scope, const Label& toolchain_label) : BuildDirContext(execution_scope->settings()->build_settings(), toolchain_label, execution_scope->settings()->default_toolchain_label() == toolchain_label) { } BuildDirContext::BuildDirContext(const BuildSettings* in_build_settings, const Label& in_toolchain_label, bool in_is_default_toolchain) : build_settings(in_build_settings), toolchain_label(in_toolchain_label), is_default_toolchain(in_is_default_toolchain) { } SourceDir GetBuildDirAsSourceDir(const BuildDirContext& context, BuildDirType type) { return GetBuildDirAsOutputFile(context, type).AsSourceDir( context.build_settings); } OutputFile GetBuildDirAsOutputFile(const BuildDirContext& context, BuildDirType type) { OutputFile result(GetOutputSubdirName(context.toolchain_label, context.is_default_toolchain)); DCHECK(result.value().empty() || result.value().back() == '/'); if (type == BuildDirType::GEN) result.value().append("gen/"); else if (type == BuildDirType::OBJ) result.value().append("obj/"); return result; } SourceDir GetSubBuildDirAsSourceDir(const BuildDirContext& context, const SourceDir& source_dir, BuildDirType type) { return GetSubBuildDirAsOutputFile(context, source_dir, type) .AsSourceDir(context.build_settings); } OutputFile GetSubBuildDirAsOutputFile(const BuildDirContext& context, const SourceDir& source_dir, BuildDirType type) { DCHECK(type != BuildDirType::TOOLCHAIN_ROOT); OutputFile result = GetBuildDirAsOutputFile(context, type); 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); } else { // System-absolute. AppendFixedAbsolutePathSuffix(context.build_settings, source_dir, &result); } return result; } SourceDir GetBuildDirForTargetAsSourceDir(const Target* target, BuildDirType type) { return GetSubBuildDirAsSourceDir( BuildDirContext(target), target->label().dir(), type); } OutputFile GetBuildDirForTargetAsOutputFile(const Target* target, BuildDirType type) { return GetSubBuildDirAsOutputFile( BuildDirContext(target), target->label().dir(), type); } SourceDir GetScopeCurrentBuildDirAsSourceDir(const Scope* scope, BuildDirType type) { if (type == BuildDirType::TOOLCHAIN_ROOT) return GetBuildDirAsSourceDir(BuildDirContext(scope), type); return GetSubBuildDirAsSourceDir( BuildDirContext(scope), scope->GetSourceDir(), type); }