diff options
Diffstat (limited to 'src/main')
-rw-r--r-- | src/main/cpp/blaze.cc | 19 | ||||
-rw-r--r-- | src/main/cpp/blaze_util.cc | 12 | ||||
-rw-r--r-- | src/main/cpp/blaze_util.h | 9 | ||||
-rw-r--r-- | src/main/cpp/blaze_util_linux.cc | 1 | ||||
-rw-r--r-- | src/main/cpp/blaze_util_platform.h | 16 | ||||
-rw-r--r-- | src/main/cpp/blaze_util_posix.cc | 13 | ||||
-rw-r--r-- | src/main/cpp/blaze_util_windows.cc | 38 | ||||
-rw-r--r-- | src/main/cpp/option_processor.cc | 10 | ||||
-rw-r--r-- | src/main/cpp/startup_options.cc | 20 | ||||
-rw-r--r-- | src/main/cpp/util/BUILD | 17 | ||||
-rw-r--r-- | src/main/cpp/util/file.cc | 38 | ||||
-rw-r--r-- | src/main/cpp/util/file.h | 11 | ||||
-rw-r--r-- | src/main/cpp/util/file_platform.h | 26 | ||||
-rw-r--r-- | src/main/cpp/util/file_posix.cc | 26 | ||||
-rw-r--r-- | src/main/cpp/util/file_windows.cc | 385 | ||||
-rw-r--r-- | src/main/cpp/util/path.cc | 64 | ||||
-rw-r--r-- | src/main/cpp/util/path.h | 43 | ||||
-rw-r--r-- | src/main/cpp/util/path_platform.h | 119 | ||||
-rw-r--r-- | src/main/cpp/util/path_posix.cc | 60 | ||||
-rw-r--r-- | src/main/cpp/util/path_windows.cc | 420 | ||||
-rw-r--r-- | src/main/cpp/workspace_layout.cc | 2 |
21 files changed, 773 insertions, 576 deletions
diff --git a/src/main/cpp/blaze.cc b/src/main/cpp/blaze.cc index d023f6491a..7487c76c90 100644 --- a/src/main/cpp/blaze.cc +++ b/src/main/cpp/blaze.cc @@ -64,6 +64,8 @@ #include "src/main/cpp/util/file.h" #include "src/main/cpp/util/logging.h" #include "src/main/cpp/util/numbers.h" +#include "src/main/cpp/util/path.h" +#include "src/main/cpp/util/path_platform.h" #include "src/main/cpp/util/port.h" #include "src/main/cpp/util/strings.h" #include "src/main/cpp/workspace_layout.h" @@ -412,7 +414,8 @@ static vector<string> GetArgumentArray( result.push_back("-XX:+HeapDumpOnOutOfMemoryError"); string heap_crash_path = globals->options->output_base; - result.push_back("-XX:HeapDumpPath=" + blaze::PathAsJvmFlag(heap_crash_path)); + result.push_back("-XX:HeapDumpPath=" + + blaze_util::PathAsJvmFlag(heap_crash_path)); result.push_back("-Xverify:none"); @@ -442,7 +445,7 @@ static vector<string> GetArgumentArray( bool first = true; for (const auto &it : globals->extracted_binaries) { if (IsSharedLibrary(it)) { - string libpath(blaze::PathAsJvmFlag( + string libpath(blaze_util::PathAsJvmFlag( blaze_util::JoinPath(real_install_dir, blaze_util::Dirname(it)))); // Only add the library path if it's not added yet. if (java_library_paths.find(libpath) == java_library_paths.end()) { @@ -497,14 +500,14 @@ static vector<string> GetArgumentArray( ToString(globals->options->connect_timeout_secs)); result.push_back("--output_user_root=" + - blaze::ConvertPath(globals->options->output_user_root)); + blaze_util::ConvertPath(globals->options->output_user_root)); result.push_back("--install_base=" + - blaze::ConvertPath(globals->options->install_base)); + blaze_util::ConvertPath(globals->options->install_base)); result.push_back("--install_md5=" + globals->install_md5); result.push_back("--output_base=" + - blaze::ConvertPath(globals->options->output_base)); + blaze_util::ConvertPath(globals->options->output_base)); result.push_back("--workspace_directory=" + - blaze::ConvertPath(globals->workspace)); + blaze_util::ConvertPath(globals->workspace)); result.push_back("--default_system_javabase=" + GetSystemJavabase()); if (!globals->options->server_jvm_out.empty()) { @@ -1170,8 +1173,8 @@ static void EnsureCorrectRunningVersion(BlazeServer *server) { string prev_installation; bool ok = blaze_util::ReadDirectorySymlink(installation_path, &prev_installation); - if (!ok || !CompareAbsolutePaths(prev_installation, - globals->options->install_base)) { + if (!ok || !blaze_util::CompareAbsolutePaths( + prev_installation, globals->options->install_base)) { if (server->Connected()) { BAZEL_LOG(INFO) << "Killing running server because it is using another version of " diff --git a/src/main/cpp/blaze_util.cc b/src/main/cpp/blaze_util.cc index d7ab44ca01..10c4e1a38f 100644 --- a/src/main/cpp/blaze_util.cc +++ b/src/main/cpp/blaze_util.cc @@ -43,18 +43,6 @@ const unsigned int kPostShutdownGracePeriodSeconds = 60; const unsigned int kPostKillGracePeriodSeconds = 10; -string MakeAbsolute(const string &p) { - string path = ConvertPath(p); - if (path.empty()) { - return blaze_util::GetCwd(); - } - if (blaze_util::IsDevNull(path.c_str()) || blaze_util::IsAbsolute(path)) { - return path; - } - - return blaze_util::JoinPath(blaze_util::GetCwd(), path); -} - const char* GetUnaryOption(const char *arg, const char *next_arg, const char *key) { diff --git a/src/main/cpp/blaze_util.h b/src/main/cpp/blaze_util.h index e7f8ba1c46..084e8d6653 100644 --- a/src/main/cpp/blaze_util.h +++ b/src/main/cpp/blaze_util.h @@ -30,15 +30,6 @@ namespace blaze { extern const char kServerPidFile[]; -// Returns the given path in absolute form. Does not change paths that are -// already absolute. -// -// If called from working directory "/bar": -// MakeAbsolute("foo") --> "/bar/foo" -// MakeAbsolute("/foo") ---> "/foo" -// MakeAbsolute("C:/foo") ---> "C:/foo" -std::string MakeAbsolute(const std::string &path); - // If 'arg' matches 'key=value', returns address of 'value'. // If it matches 'key' alone, returns address of next_arg. // Returns NULL otherwise. diff --git a/src/main/cpp/blaze_util_linux.cc b/src/main/cpp/blaze_util_linux.cc index 4f01ba833b..dee5463f72 100644 --- a/src/main/cpp/blaze_util_linux.cc +++ b/src/main/cpp/blaze_util_linux.cc @@ -32,6 +32,7 @@ #include "src/main/cpp/util/exit_code.h" #include "src/main/cpp/util/file.h" #include "src/main/cpp/util/logging.h" +#include "src/main/cpp/util/path.h" #include "src/main/cpp/util/port.h" #include "src/main/cpp/util/strings.h" diff --git a/src/main/cpp/blaze_util_platform.h b/src/main/cpp/blaze_util_platform.h index 6de5eb154a..f08aa567e4 100644 --- a/src/main/cpp/blaze_util_platform.h +++ b/src/main/cpp/blaze_util_platform.h @@ -119,16 +119,6 @@ int ExecuteDaemon(const std::string& exe, const std::string& server_dir, BlazeServerStartup** server_startup); -// Convert a path from Bazel internal form to underlying OS form. -// On Unixes this is an identity operation. -// On Windows, Bazel internal form is cygwin path, and underlying OS form -// is Windows path. -std::string ConvertPath(const std::string& path); - -// Converts `path` to a string that's safe to pass as path in a JVM flag. -// See https://github.com/bazelbuild/bazel/issues/2576 -std::string PathAsJvmFlag(const std::string& path); - // A character used to separate paths in a list. extern const char kListSeparator; @@ -137,12 +127,6 @@ extern const char kListSeparator; // Implemented via junctions on Windows. bool SymlinkDirectories(const std::string& target, const std::string& link); -// Compares two absolute paths. Necessary because the same path can have -// multiple different names under msys2: "C:\foo\bar" or "C:/foo/bar" -// (Windows-style) and "/c/foo/bar" (msys2 style). Returns if the paths are -// equal. -bool CompareAbsolutePaths(const std::string& a, const std::string& b); - struct BlazeLock { #if defined(COMPILER_MSVC) || defined(__CYGWIN__) /* HANDLE */ void* handle; diff --git a/src/main/cpp/blaze_util_posix.cc b/src/main/cpp/blaze_util_posix.cc index 4f8d9c766c..156b021529 100644 --- a/src/main/cpp/blaze_util_posix.cc +++ b/src/main/cpp/blaze_util_posix.cc @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "src/main/cpp/blaze_util_platform.h" + #define _WITH_DPRINTF #include <dirent.h> #include <errno.h> @@ -36,7 +38,6 @@ #include <cinttypes> #include "src/main/cpp/blaze_util.h" -#include "src/main/cpp/blaze_util_platform.h" #include "src/main/cpp/global_variables.h" #include "src/main/cpp/startup_options.h" #include "src/main/cpp/util/errors.h" @@ -45,6 +46,8 @@ #include "src/main/cpp/util/logging.h" #include "src/main/cpp/util/md5.h" #include "src/main/cpp/util/numbers.h" +#include "src/main/cpp/util/path.h" +#include "src/main/cpp/util/path_platform.h" namespace blaze { @@ -173,10 +176,6 @@ void ExecuteProgram(const string& exe, const vector<string>& args_vector) { execv(exe.c_str(), const_cast<char**>(argv)); } -std::string ConvertPath(const std::string &path) { return path; } - -std::string PathAsJvmFlag(const std::string& path) { return path; } - const char kListSeparator = ':'; bool SymlinkDirectories(const string &target, const string &link) { @@ -403,10 +402,6 @@ int ExecuteDaemon(const string& exe, } } -bool CompareAbsolutePaths(const string& a, const string& b) { - return a == b; -} - string GetHashedBaseDir(const string& root, const string& hashable) { unsigned char buf[blaze_util::Md5Digest::kDigestLength]; blaze_util::Md5Digest digest; diff --git a/src/main/cpp/blaze_util_windows.cc b/src/main/cpp/blaze_util_windows.cc index 565e8876c2..d41a2f4493 100644 --- a/src/main/cpp/blaze_util_windows.cc +++ b/src/main/cpp/blaze_util_windows.cc @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "src/main/cpp/blaze_util_platform.h" + #include <fcntl.h> #include <stdarg.h> // va_start, va_end, va_list @@ -33,7 +35,6 @@ #include <vector> #include "src/main/cpp/blaze_util.h" -#include "src/main/cpp/blaze_util_platform.h" #include "src/main/cpp/global_variables.h" #include "src/main/cpp/startup_options.h" #include "src/main/cpp/util/errors.h" @@ -43,6 +44,8 @@ #include "src/main/cpp/util/logging.h" #include "src/main/cpp/util/md5.h" #include "src/main/cpp/util/numbers.h" +#include "src/main/cpp/util/path.h" +#include "src/main/cpp/util/path_platform.h" #include "src/main/cpp/util/strings.h" #include "src/main/native/windows/file.h" #include "src/main/native/windows/util.h" @@ -652,36 +655,6 @@ void ExecuteProgram(const string& exe, const std::vector<string>& args_vector) { const char kListSeparator = ';'; -string PathAsJvmFlag(const string& path) { - string spath; - string error; - if (!blaze_util::AsShortWindowsPath(path, &spath, &error)) { - BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) - << "PathAsJvmFlag(" << path - << "): AsShortWindowsPath failed: " << error; - } - // Convert backslashes to forward slashes, in order to avoid the JVM parsing - // Windows paths as if they contained escaped characters. - // See https://github.com/bazelbuild/bazel/issues/2576 - std::replace(spath.begin(), spath.end(), '\\', '/'); - return spath; -} - -string ConvertPath(const string& path) { - // The path may not be Windows-style and may not be normalized, so convert it. - wstring wpath; - string error; - if (!blaze_util::AsAbsoluteWindowsPath(path, &wpath, &error)) { - BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) - << "ConvertPath(" << path - << "): AsAbsoluteWindowsPath failed: " << error; - } - std::transform(wpath.begin(), wpath.end(), wpath.begin(), ::towlower); - return string(blaze_util::WstringToCstring( - blaze_util::RemoveUncPrefixMaybe(wpath.c_str())) - .get()); -} - bool SymlinkDirectories(const string &posix_target, const string &posix_name) { wstring name; wstring target; @@ -708,9 +681,6 @@ bool SymlinkDirectories(const string &posix_target, const string &posix_name) { return true; } -bool CompareAbsolutePaths(const string& a, const string& b) { - return ConvertPath(a) == ConvertPath(b); -} #ifndef STILL_ACTIVE #define STILL_ACTIVE (259) // From MSDN about GetExitCodeProcess. diff --git a/src/main/cpp/option_processor.cc b/src/main/cpp/option_processor.cc index 7df0af7203..a1d7f9d976 100644 --- a/src/main/cpp/option_processor.cc +++ b/src/main/cpp/option_processor.cc @@ -27,6 +27,8 @@ #include "src/main/cpp/blaze_util_platform.h" #include "src/main/cpp/util/file.h" #include "src/main/cpp/util/logging.h" +#include "src/main/cpp/util/path.h" +#include "src/main/cpp/util/path_platform.h" #include "src/main/cpp/util/strings.h" #include "src/main/cpp/workspace_layout.h" @@ -136,7 +138,7 @@ blaze_exit_code::ExitCode OptionProcessor::FindUserBlazerc( "." + parsed_startup_options_->GetLowercaseProductName() + "rc"; if (cmd_line_rc_file != nullptr) { - string rcFile = MakeAbsolute(cmd_line_rc_file); + string rcFile = blaze_util::MakeAbsolute(cmd_line_rc_file); if (!blaze_util::CanReadFile(rcFile)) { blaze_util::StringPrintf(error, "Error: Unable to read %s file '%s'.", rc_basename.c_str(), @@ -424,7 +426,7 @@ static void PreprocessEnvString(string* env_str) { } else if (name == "TMP") { // A valid Windows path "c:/foo" is also a valid Unix path list of // ["c", "/foo"] so must use ConvertPath here. See GitHub issue #1684. - env_str->assign("TMP=" + ConvertPath(env_str->substr(pos + 1))); + env_str->assign("TMP=" + blaze_util::ConvertPath(env_str->substr(pos + 1))); } } @@ -477,7 +479,7 @@ std::vector<std::string> OptionProcessor::GetBlazercAndEnvCommandArgs( // from multiple places. if (rcfile_indexes.find(source_path) != rcfile_indexes.end()) continue; - result.push_back("--rc_source=" + blaze::ConvertPath(source_path)); + result.push_back("--rc_source=" + blaze_util::ConvertPath(source_path)); rcfile_indexes[source_path] = cur_index; cur_index++; } @@ -503,7 +505,7 @@ std::vector<std::string> OptionProcessor::GetBlazercAndEnvCommandArgs( for (const string& env_var : env) { result.push_back("--client_env=" + env_var); } - result.push_back("--client_cwd=" + blaze::ConvertPath(cwd)); + result.push_back("--client_cwd=" + blaze_util::ConvertPath(cwd)); return result; } diff --git a/src/main/cpp/startup_options.cc b/src/main/cpp/startup_options.cc index 5ccec1d635..3699d40498 100644 --- a/src/main/cpp/startup_options.cc +++ b/src/main/cpp/startup_options.cc @@ -25,6 +25,8 @@ #include "src/main/cpp/util/file.h" #include "src/main/cpp/util/logging.h" #include "src/main/cpp/util/numbers.h" +#include "src/main/cpp/util/path.h" +#include "src/main/cpp/util/path_platform.h" #include "src/main/cpp/util/strings.h" #include "src/main/cpp/workspace_layout.h" @@ -93,7 +95,7 @@ StartupOptions::StartupOptions(const string &product_name, original_startup_options_(std::vector<RcStartupFlag>()) { bool testing = !blaze::GetEnv("TEST_TMPDIR").empty(); if (testing) { - output_root = MakeAbsolute(blaze::GetEnv("TEST_TMPDIR")); + output_root = blaze_util::MakeAbsolute(blaze::GetEnv("TEST_TMPDIR")); max_idle_secs = 15; BAZEL_LOG(USER) << "$TEST_TMPDIR defined: output root default is '" << output_root << "' and max_idle_secs default is '" @@ -187,19 +189,19 @@ blaze_exit_code::ExitCode StartupOptions::ProcessArg( const char* value = NULL; if ((value = GetUnaryOption(arg, next_arg, "--output_base")) != NULL) { - output_base = MakeAbsolute(value); + output_base = blaze_util::MakeAbsolute(value); option_sources["output_base"] = rcfile; } else if ((value = GetUnaryOption(arg, next_arg, "--install_base")) != NULL) { - install_base = MakeAbsolute(value); + install_base = blaze_util::MakeAbsolute(value); option_sources["install_base"] = rcfile; } else if ((value = GetUnaryOption(arg, next_arg, "--output_user_root")) != NULL) { - output_user_root = MakeAbsolute(value); + output_user_root = blaze_util::MakeAbsolute(value); option_sources["output_user_root"] = rcfile; } else if ((value = GetUnaryOption(arg, next_arg, "--server_jvm_out")) != NULL) { - server_jvm_out = MakeAbsolute(value); + server_jvm_out = blaze_util::MakeAbsolute(value); option_sources["server_jvm_out"] = rcfile; } else if (GetNullaryOption(arg, "--deep_execroot")) { deep_execroot = true; @@ -221,7 +223,7 @@ blaze_exit_code::ExitCode StartupOptions::ProcessArg( "--host_javabase")) != NULL) { // TODO(bazel-team): Consider examining the javabase and re-execing in case // of architecture mismatch. - host_javabase = MakeAbsolute(value); + host_javabase = blaze_util::MakeAbsolute(value); option_sources["host_javabase"] = rcfile; } else if ((value = GetUnaryOption(arg, next_arg, "--host_jvm_args")) != NULL) { @@ -486,8 +488,8 @@ void StartupOptions::AddJVMArgumentSuffix(const string &real_install_dir, const string &jar_path, std::vector<string> *result) const { result->push_back("-jar"); - result->push_back( - blaze::PathAsJvmFlag(blaze_util::JoinPath(real_install_dir, jar_path))); + result->push_back(blaze_util::PathAsJvmFlag( + blaze_util::JoinPath(real_install_dir, jar_path))); } blaze_exit_code::ExitCode StartupOptions::AddJVMArguments( @@ -502,7 +504,7 @@ void StartupOptions::AddJVMLoggingArguments(std::vector<string> *result) const { const string propFile = blaze_util::JoinPath(output_base, "javalog.properties"); string java_log( - blaze::PathAsJvmFlag(blaze_util::JoinPath(output_base, "java.log"))); + blaze_util::PathAsJvmFlag(blaze_util::JoinPath(output_base, "java.log"))); if (!blaze_util::WriteFile("handlers=java.util.logging.FileHandler\n" ".level=INFO\n" "java.util.logging.FileHandler.level=INFO\n" diff --git a/src/main/cpp/util/BUILD b/src/main/cpp/util/BUILD index 254b6f8f1f..6f53b4d590 100644 --- a/src/main/cpp/util/BUILD +++ b/src/main/cpp/util/BUILD @@ -15,13 +15,15 @@ cc_library( "file_platform.h", "md5.h", "numbers.h", + "path.h", + "path_platform.h", "port.h", ], visibility = ["//visibility:public"], deps = [ ":blaze_exit_code", ":errors", - ":file", + ":filesystem", ":md5", ":numbers", ":port", @@ -30,18 +32,25 @@ cc_library( ) cc_library( - name = "file", - srcs = ["file.cc"] + select({ + name = "filesystem", + srcs = [ + "file.cc", + "path.cc", + ] + select({ "//src/conditions:windows": [ "file_windows.cc", + "path_windows.cc", ], "//conditions:default": [ "file_posix.cc", + "path_posix.cc", ], }), hdrs = [ "file.h", "file_platform.h", + "path.h", + "path_platform.h", ], visibility = [ ":ijar", @@ -108,7 +117,7 @@ cc_library( visibility = ["//visibility:public"], deps = [ ":blaze_exit_code", - ":file", + ":filesystem", ":logging", ], ) diff --git a/src/main/cpp/util/file.cc b/src/main/cpp/util/file.cc index 3eb614c0dc..041d7798dc 100644 --- a/src/main/cpp/util/file.cc +++ b/src/main/cpp/util/file.cc @@ -11,6 +11,9 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + +#include "src/main/cpp/util/file.h" + #include <limits.h> // PATH_MAX #include <algorithm> @@ -19,7 +22,7 @@ #include "src/main/cpp/util/errors.h" #include "src/main/cpp/util/exit_code.h" -#include "src/main/cpp/util/file.h" +#include "src/main/cpp/util/path.h" #include "src/main/cpp/util/strings.h" namespace blaze_util { @@ -85,39 +88,6 @@ bool WriteFile(const std::string &content, const std::string &filename, return WriteFile(content.c_str(), content.size(), filename, perm); } -string Dirname(const string &path) { - return SplitPath(path).first; -} - -string Basename(const string &path) { - return SplitPath(path).second; -} - -string JoinPath(const string &path1, const string &path2) { - if (path1.empty()) { - // "" + "/bar" - return path2; - } - - if (path1[path1.size() - 1] == '/') { - if (path2.find('/') == 0) { - // foo/ + /bar - return path1 + path2.substr(1); - } else { - // foo/ + bar - return path1 + path2; - } - } else { - if (path2.find('/') == 0) { - // foo + /bar - return path1 + path2; - } else { - // foo + bar - return path1 + "/" + path2; - } - } -} - class DirectoryTreeWalker : public DirectoryEntryConsumer { public: DirectoryTreeWalker(vector<string> *files, diff --git a/src/main/cpp/util/file.h b/src/main/cpp/util/file.h index 4bc165110e..235ec87527 100644 --- a/src/main/cpp/util/file.h +++ b/src/main/cpp/util/file.h @@ -63,17 +63,6 @@ bool ReadFrom(file_handle_type handle, void *data, size_t size); bool WriteFile(const std::string &content, const std::string &filename, unsigned int perm = 0644); -// Returns the part of the path before the final "/". If there is a single -// leading "/" in the path, the result will be the leading "/". If there is -// no "/" in the path, the result is the empty prefix of the input (i.e., ""). -std::string Dirname(const std::string &path); - -// Returns the part of the path after the final "/". If there is no -// "/" in the path, the result is the same as the input. -std::string Basename(const std::string &path); - -std::string JoinPath(const std::string &path1, const std::string &path2); - // Lists all files in `path` and all of its subdirectories. // // Does not follow symlinks / junctions. diff --git a/src/main/cpp/util/file_platform.h b/src/main/cpp/util/file_platform.h index 5b96133d3a..ac4fc3ab00 100644 --- a/src/main/cpp/util/file_platform.h +++ b/src/main/cpp/util/file_platform.h @@ -50,9 +50,6 @@ class IFileMtime { // Creates a platform-specific implementation of `IFileMtime`. IFileMtime *CreateFileMtime(); -// Split a path to dirname and basename parts. -std::pair<std::string, std::string> SplitPath(const std::string &path); - #if defined(COMPILER_MSVC) || defined(__CYGWIN__) // We cannot include <windows.h> because it #defines many symbols that conflict // with our function names, e.g. GetUserName, SendMessage. @@ -162,17 +159,9 @@ bool CanExecuteFile(const std::string &path); // Follows symlinks/junctions. bool CanAccessDirectory(const std::string &path); -bool IsDevNull(const char *path); - // Returns true if `path` refers to a directory or a symlink/junction to one. bool IsDirectory(const std::string& path); -// Returns true if `path` is the root directory or a Windows drive root. -bool IsRootDirectory(const std::string &path); - -// Returns true if `path` is absolute. -bool IsAbsolute(const std::string &path); - // Calls fsync() on the file (or directory) specified in 'file_path'. // pdie() if syncing fails. void SyncFile(const std::string& path); @@ -211,20 +200,7 @@ void ForEachDirectoryEntry(const std::string &path, DirectoryEntryConsumer *consume); #if defined(COMPILER_MSVC) || defined(__CYGWIN__) -const wchar_t *RemoveUncPrefixMaybe(const wchar_t *ptr); - -bool AsWindowsPath(const std::string &path, std::string *result, - std::string *error); - -bool AsAbsoluteWindowsPath(const std::string &path, std::wstring *wpath, - std::string *error); - -// Same as `AsWindowsPath`, but returns a lowercase 8dot3 style shortened path. -// Result will never have a UNC prefix, nor a trailing "/" or "\". -// Works also for non-existent paths; shortens as much of them as it can. -// Also works for non-existent drives. -bool AsShortWindowsPath(const std::string &path, std::string *result, - std::string *error); +std::wstring GetCwdW(); #endif // defined(COMPILER_MSVC) || defined(__CYGWIN__) } // namespace blaze_util diff --git a/src/main/cpp/util/file_posix.cc b/src/main/cpp/util/file_posix.cc index 5136df0655..df05b54a45 100644 --- a/src/main/cpp/util/file_posix.cc +++ b/src/main/cpp/util/file_posix.cc @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "src/main/cpp/util/file_platform.h" + #include <dirent.h> // DIR, dirent, opendir, closedir #include <errno.h> #include <fcntl.h> // O_RDONLY @@ -29,6 +31,8 @@ #include "src/main/cpp/util/exit_code.h" #include "src/main/cpp/util/file.h" #include "src/main/cpp/util/logging.h" +#include "src/main/cpp/util/path.h" +#include "src/main/cpp/util/path_platform.h" #include "src/main/cpp/util/strings.h" namespace blaze_util { @@ -178,18 +182,6 @@ IPipe* CreatePipe() { return new PosixPipe(fd[0], fd[1]); } -pair<string, string> SplitPath(const string &path) { - size_t pos = path.rfind('/'); - - // Handle the case with no '/' in 'path'. - if (pos == string::npos) return std::make_pair("", path); - - // Handle the case with a single leading '/' in 'path'. - if (pos == 0) return std::make_pair(string(path, 0, 1), string(path, 1)); - - return std::make_pair(string(path, 0, pos), string(path, pos + 1)); -} - int ReadFromHandle(file_handle_type fd, void *data, size_t size, int *error) { int result = read(fd, data, size); if (error != nullptr) { @@ -299,10 +291,6 @@ static bool CanAccess(const string &path, bool read, bool write, bool exec) { return access(path.c_str(), mode) == 0; } -bool IsDevNull(const char *path) { - return path != NULL && *path != 0 && strncmp("/dev/null\0", path, 10) == 0; -} - bool CanReadFile(const std::string &path) { return !IsDirectory(path) && CanAccess(path, true, false, false); } @@ -320,12 +308,6 @@ bool IsDirectory(const string& path) { return stat(path.c_str(), &buf) == 0 && S_ISDIR(buf.st_mode); } -bool IsRootDirectory(const string &path) { - return path.size() == 1 && path[0] == '/'; -} - -bool IsAbsolute(const string &path) { return !path.empty() && path[0] == '/'; } - void SyncFile(const string& path) { const char* file_path = path.c_str(); int fd = open(file_path, O_RDONLY); diff --git a/src/main/cpp/util/file_windows.cc b/src/main/cpp/util/file_windows.cc index 39160c9c22..537852eb70 100644 --- a/src/main/cpp/util/file_windows.cc +++ b/src/main/cpp/util/file_windows.cc @@ -24,6 +24,8 @@ #include "src/main/cpp/util/exit_code.h" #include "src/main/cpp/util/file.h" #include "src/main/cpp/util/logging.h" +#include "src/main/cpp/util/path.h" +#include "src/main/cpp/util/path_platform.h" #include "src/main/cpp/util/strings.h" #include "src/main/native/windows/file.h" #include "src/main/native/windows/util.h" @@ -40,11 +42,7 @@ using bazel::windows::GetLongPath; using bazel::windows::HasUncPrefix; using bazel::windows::OpenDirectory; -// Returns the current working directory as a Windows path. -// The result may have a UNC prefix. -static unique_ptr<WCHAR[]> GetCwdW(); -static char GetCurrentDrive(); // Returns true if `path` refers to a directory or (non-dangling) junction. // `path` must be a normalized Windows path, with UNC prefix (and absolute) if @@ -57,64 +55,15 @@ static bool IsDirectoryW(const wstring& path); // necessary. static bool UnlinkPathW(const wstring& path); -static bool IsRootDirectoryW(const wstring& path); - static bool MakeDirectoriesW(const wstring& path); static bool CanReadFileW(const wstring& path); -// Returns a normalized form of the input `path`. -// -// `path` must be a relative or absolute Windows path, it may use "/" instead of -// "\" but must not be a Unix-style (MSYS) path. -// The result won't have a UNC prefix, even if `path` did. -// -// Normalization means removing "." references, resolving ".." references, and -// deduplicating "/" characters while converting them to "\". -// For example if `path` is "foo/../bar/.//qux", the result is "bar\qux". -// -// Uplevel references that cannot go any higher in the directory tree are simply -// ignored, e.g. "c:/.." is normalized to "c:\" and "../../foo" is normalized to -// "foo". -// -// Visible for testing, would be static otherwise. -string NormalizeWindowsPath(string path); - -template <typename char_type> -struct CharTraits { - static bool IsAlpha(char_type ch); -}; - -template <> -struct CharTraits<char> { - static bool IsAlpha(char ch) { return isalpha(ch); } -}; - -template <> -struct CharTraits<wchar_t> { - static bool IsAlpha(wchar_t ch) { return iswalpha(ch); } -}; - template <typename char_type> static bool IsPathSeparator(char_type ch) { return ch == '/' || ch == '\\'; } -template <typename char_type> -static bool HasDriveSpecifierPrefix(const char_type* ch) { - return CharTraits<char_type>::IsAlpha(ch[0]) && ch[1] == ':'; -} - -static void AddUncPrefixMaybe(wstring* path, size_t max_path = MAX_PATH) { - if (path->size() >= max_path && !HasUncPrefix(path->c_str())) { - *path = wstring(L"\\\\?\\") + *path; - } -} - -const wchar_t* RemoveUncPrefixMaybe(const wchar_t* ptr) { - return ptr + (HasUncPrefix(ptr) ? 4 : 0); -} - class WindowsPipe : public IPipe { public: WindowsPipe(const HANDLE& read_handle, const HANDLE& write_handle) @@ -300,239 +249,6 @@ FILETIME WindowsFileMtime::GetFuture(WORD years) { IFileMtime* CreateFileMtime() { return new WindowsFileMtime(); } -// Checks if the path is absolute and/or is a root path. -// -// If `must_be_root` is true, then in addition to being absolute, the path must -// also be just the root part, no other components, e.g. "c:\" is both absolute -// and root, but "c:\foo" is just absolute. -template <typename char_type> -static bool IsRootOrAbsolute(const basic_string<char_type>& path, - bool must_be_root) { - // An absolute path is one that starts with "/", "\", "c:/", "c:\", - // "\\?\c:\", or rarely "\??\c:\" or "\\.\c:\". - // - // It is unclear whether the UNC prefix is just "\\?\" or is "\??\" also - // valid (in some cases it seems to be, though MSDN doesn't mention it). - return - // path is (or starts with) "/" or "\" - ((must_be_root ? path.size() == 1 : !path.empty()) && - IsPathSeparator(path[0])) || - // path is (or starts with) "c:/" or "c:\" or similar - ((must_be_root ? path.size() == 3 : path.size() >= 3) && - HasDriveSpecifierPrefix(path.c_str()) && IsPathSeparator(path[2])) || - // path is (or starts with) "\\?\c:\" or "\??\c:\" or similar - ((must_be_root ? path.size() == 7 : path.size() >= 7) && - HasUncPrefix(path.c_str()) && - HasDriveSpecifierPrefix(path.c_str() + 4) && IsPathSeparator(path[6])); -} - -template <typename char_type> -static pair<basic_string<char_type>, basic_string<char_type> > SplitPathImpl( - const basic_string<char_type>& path) { - if (path.empty()) { - return std::make_pair(basic_string<char_type>(), basic_string<char_type>()); - } - - size_t pos = path.size() - 1; - for (auto it = path.crbegin(); it != path.crend(); ++it, --pos) { - if (IsPathSeparator(*it)) { - if ((pos == 2 || pos == 6) && - IsRootOrAbsolute(path.substr(0, pos + 1), /* must_be_root */ true)) { - // Windows path, top-level directory, e.g. "c:\foo", - // result is ("c:\", "foo"). - // Or UNC path, top-level directory, e.g. "\\?\c:\foo" - // result is ("\\?\c:\", "foo"). - return std::make_pair( - // Include the "/" or "\" in the drive specifier. - path.substr(0, pos + 1), path.substr(pos + 1)); - } else { - // Windows path (neither top-level nor drive root), Unix path, or - // relative path. - return std::make_pair( - // If the only "/" is the leading one, then that shall be the first - // pair element, otherwise the substring up to the rightmost "/". - pos == 0 ? path.substr(0, 1) : path.substr(0, pos), - // If the rightmost "/" is the tail, then the second pair element - // should be empty. - pos == path.size() - 1 ? basic_string<char_type>() - : path.substr(pos + 1)); - } - } - } - // Handle the case with no '/' or '\' in `path`. - return std::make_pair(basic_string<char_type>(), path); -} - -pair<string, string> SplitPath(const string& path) { - return SplitPathImpl(path); -} - -pair<wstring, wstring> SplitPathW(const wstring& path) { - return SplitPathImpl(path); -} - -bool AsWindowsPath(const string& path, string* result, string* error) { - if (path.empty()) { - result->clear(); - return true; - } - if (IsDevNull(path.c_str())) { - result->assign("NUL"); - return true; - } - if (HasUncPrefix(path.c_str())) { - // Path has "\\?\" prefix --> assume it's already Windows-style. - *result = path.c_str(); - return true; - } - if (IsPathSeparator(path[0]) && path.size() > 1 && IsPathSeparator(path[1])) { - // Unsupported path: "\\" or "\\server\path", or some degenerate form of - // these, such as "//foo". - if (error) { - *error = "network paths are unsupported"; - } - return false; - } - if (HasDriveSpecifierPrefix(path.c_str()) && - (path.size() < 3 || !IsPathSeparator(path[2]))) { - // Unsupported path: "c:" or "c:foo" - if (error) { - *error = "working-directory relative paths are unsupported"; - } - return false; - } - - string mutable_path = path; - if (path[0] == '/') { - if (error) { - *error = "Unix-style paths are unsupported"; - } - return false; - } - - if (path[0] == '\\') { - // This is an absolute Windows path on the current drive, e.g. "\foo\bar". - mutable_path = string(1, GetCurrentDrive()) + ":" + path; - } // otherwise this is a relative path, or absolute Windows path. - - result->assign(NormalizeWindowsPath(mutable_path)); - return true; -} - -// Converts a UTF8-encoded `path` to a normalized, widechar Windows path. -// -// Returns true if conversion succeeded and sets the contents of `result` to it. -// -// The input `path` may be an absolute or relative Windows path. -// -// The returned path is normalized (see NormalizeWindowsPath). -// -// If `path` had a "\\?\" prefix then the function assumes it's already Windows -// style and converts it to wstring without any alterations. -// Otherwise `path` is normalized and converted to a Windows path and the result -// won't have a "\\?\" prefix even if it's longer than MAX_PATH (adding the -// prefix is the caller's responsibility). -// -// The method recognizes current-drive-relative Windows paths ("\foo") turning -// them into absolute paths ("c:\foo"). -bool AsWindowsPath(const string& path, wstring* result, string* error) { - string normalized_win_path; - if (!AsWindowsPath(path, &normalized_win_path, error)) { - return false; - } - - result->assign(CstringToWstring(normalized_win_path.c_str()).get()); - return true; -} - -bool AsAbsoluteWindowsPath(const string& path, wstring* result, string* error) { - if (path.empty()) { - result->clear(); - return true; - } - if (IsDevNull(path.c_str())) { - result->assign(L"NUL"); - return true; - } - if (!AsWindowsPath(path, result, error)) { - return false; - } - if (!IsRootOrAbsolute(*result, /* must_be_root */ false)) { - *result = wstring(GetCwdW().get()) + L"\\" + *result; - } - if (!HasUncPrefix(result->c_str())) { - *result = wstring(L"\\\\?\\") + *result; - } - return true; -} - -bool AsShortWindowsPath(const string& path, string* result, string* error) { - if (IsDevNull(path.c_str())) { - result->assign("NUL"); - return true; - } - - result->clear(); - wstring wpath; - wstring wsuffix; - if (!AsAbsoluteWindowsPath(path, &wpath, error)) { - return false; - } - DWORD size = ::GetShortPathNameW(wpath.c_str(), nullptr, 0); - if (size == 0) { - // GetShortPathNameW can fail if `wpath` does not exist. This is expected - // when we are about to create a file at that path, so instead of failing, - // walk up in the path until we find a prefix that exists and can be - // shortened, or is a root directory. Save the non-existent tail in - // `wsuffix`, we'll add it back later. - std::vector<wstring> segments; - while (size == 0 && !IsRootDirectoryW(wpath)) { - pair<wstring, wstring> split = SplitPathW(wpath); - wpath = split.first; - segments.push_back(split.second); - size = ::GetShortPathNameW(wpath.c_str(), nullptr, 0); - } - - // Join all segments. - std::wostringstream builder; - bool first = true; - for (auto it = segments.crbegin(); it != segments.crend(); ++it) { - if (!first || !IsRootDirectoryW(wpath)) { - builder << L'\\' << *it; - } else { - builder << *it; - } - first = false; - } - wsuffix = builder.str(); - } - - wstring wresult; - if (IsRootDirectoryW(wpath)) { - // Strip the UNC prefix from `wpath`, and the leading "\" from `wsuffix`. - wresult = wstring(RemoveUncPrefixMaybe(wpath.c_str())) + wsuffix; - } else { - unique_ptr<WCHAR[]> wshort( - new WCHAR[size]); // size includes null-terminator - if (size - 1 != ::GetShortPathNameW(wpath.c_str(), wshort.get(), size)) { - if (error) { - string last_error = GetLastErrorString(); - std::stringstream msg; - msg << "AsShortWindowsPath(" << path << "): GetShortPathNameW(" - << blaze_util::WstringToString(wpath) << ") failed: " << last_error; - *error = msg.str(); - } - return false; - } - // GetShortPathNameW may preserve the UNC prefix in the result, so strip it. - wresult = wstring(RemoveUncPrefixMaybe(wshort.get())) + wsuffix; - } - - result->assign(WstringToCstring(wresult.c_str()).get()); - ToLower(result); - return true; -} - static bool OpenFileForReading(const string& filename, HANDLE* result) { if (filename.empty()) { return false; @@ -1067,14 +783,6 @@ bool CanAccessDirectory(const std::string& path) { return true; } -bool IsDevNull(const char* path) { - return path != NULL && *path != 0 && - (strncmp("/dev/null\0", path, 10) == 0 || - ((path[0] == 'N' || path[0] == 'n') && - (path[1] == 'U' || path[1] == 'u') && - (path[2] == 'L' || path[2] == 'l') && path[3] == 0)); -} - static bool IsDirectoryW(const wstring& path) { DWORD attrs = ::GetFileAttributesW(path.c_str()); return (attrs != INVALID_FILE_ATTRIBUTES) && @@ -1097,21 +805,11 @@ bool IsDirectory(const string& path) { return IsDirectoryW(wpath); } -bool IsRootDirectory(const string& path) { - return IsRootOrAbsolute(path, true); -} - -bool IsAbsolute(const string& path) { return IsRootOrAbsolute(path, false); } - void SyncFile(const string& path) { // No-op on Windows native; unsupported by Cygwin. // fsync always fails on Cygwin with "Permission denied" for some reason. } -static bool IsRootDirectoryW(const wstring& path) { - return IsRootOrAbsolute(path, true); -} - static bool MakeDirectoriesW(const wstring& path) { if (path.empty()) { return false; @@ -1150,7 +848,7 @@ bool MakeDirectories(const string& path, unsigned int mode) { return MakeDirectoriesW(wpath); } -static unique_ptr<WCHAR[]> GetCwdW() { +std::wstring GetCwdW() { DWORD len = ::GetCurrentDirectoryW(0, nullptr); unique_ptr<WCHAR[]> cwd(new WCHAR[len]); if (!::GetCurrentDirectoryW(len, cwd.get())) { @@ -1160,18 +858,12 @@ static unique_ptr<WCHAR[]> GetCwdW() { for (WCHAR* p = cwd.get(); *p != 0; ++p) { *p = towlower(*p); } - return std::move(cwd); + return std::wstring(cwd.get()); } string GetCwd() { - return string(WstringToCstring(RemoveUncPrefixMaybe(GetCwdW().get())).get()); -} - -static char GetCurrentDrive() { - unique_ptr<wchar_t[]> cwd = GetCwdW(); - wchar_t wdrive = RemoveUncPrefixMaybe(cwd.get())[0]; - wchar_t offset = wdrive >= L'A' && wdrive <= L'Z' ? L'A' : L'a'; - return 'a' + wdrive - offset; + return string( + WstringToCstring(RemoveUncPrefixMaybe(GetCwdW().c_str())).get()); } bool ChangeDirectory(const string& path) { @@ -1227,69 +919,4 @@ void ForEachDirectoryEntry(const string &path, ::FindClose(handle); } -string NormalizeWindowsPath(string path) { - if (path.empty()) { - return ""; - } - if (path[0] == '/') { - // This is an absolute MSYS path, error out. - BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) - << "NormalizeWindowsPath(" << path << "): expected a Windows path"; - } - if (path.size() >= 4 && HasUncPrefix(path.c_str())) { - path = path.substr(4); - } - - static const string dot("."); - static const string dotdot(".."); - - std::vector<string> segments; - int segment_start = -1; - // Find the path segments in `path` (separated by "/"). - for (int i = 0;; ++i) { - if (!IsPathSeparator(path[i]) && path[i] != '\0') { - // The current character does not end a segment, so start one unless it's - // already started. - if (segment_start < 0) { - segment_start = i; - } - } else if (segment_start >= 0 && i > segment_start) { - // The current character is "/" or "\0", so this ends a segment. - // Add that to `segments` if there's anything to add; handle "." and "..". - string segment(path, segment_start, i - segment_start); - segment_start = -1; - if (segment == dotdot) { - if (!segments.empty() && - !HasDriveSpecifierPrefix(segments[0].c_str())) { - segments.pop_back(); - } - } else if (segment != dot) { - segments.push_back(segment); - } - } - if (path[i] == '\0') { - break; - } - } - - // Handle the case when `path` is just a drive specifier (or some degenerate - // form of it, e.g. "c:\.."). - if (segments.size() == 1 && segments[0].size() == 2 && - HasDriveSpecifierPrefix(segments[0].c_str())) { - return segments[0] + '\\'; - } - - // Join all segments. - bool first = true; - std::ostringstream result; - for (const auto& s : segments) { - if (!first) { - result << '\\'; - } - first = false; - result << s; - } - return result.str(); -} - } // namespace blaze_util diff --git a/src/main/cpp/util/path.cc b/src/main/cpp/util/path.cc new file mode 100644 index 0000000000..efa10b81d4 --- /dev/null +++ b/src/main/cpp/util/path.cc @@ -0,0 +1,64 @@ +// Copyright 2018 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/main/cpp/util/path.h" + +#include "src/main/cpp/util/file_platform.h" +#include "src/main/cpp/util/path_platform.h" + +namespace blaze_util { + +std::string Dirname(const std::string &path) { return SplitPath(path).first; } + +std::string Basename(const std::string &path) { return SplitPath(path).second; } + +std::string JoinPath(const std::string &path1, const std::string &path2) { + if (path1.empty()) { + // "" + "/bar" + return path2; + } + + if (path1[path1.size() - 1] == '/') { + if (path2.find('/') == 0) { + // foo/ + /bar + return path1 + path2.substr(1); + } else { + // foo/ + bar + return path1 + path2; + } + } else { + if (path2.find('/') == 0) { + // foo + /bar + return path1 + path2; + } else { + // foo + bar + return path1 + "/" + path2; + } + } +} + +std::string MakeAbsolute(const std::string &path) { + std::string converted_path = ConvertPath(path); + if (converted_path.empty()) { + return GetCwd(); + } + if (IsDevNull(converted_path.c_str()) || + blaze_util::IsAbsolute(converted_path)) { + return converted_path; + } + + return JoinPath(blaze_util::GetCwd(), converted_path); +} + +} // namespace blaze_util diff --git a/src/main/cpp/util/path.h b/src/main/cpp/util/path.h new file mode 100644 index 0000000000..38e735b7af --- /dev/null +++ b/src/main/cpp/util/path.h @@ -0,0 +1,43 @@ +// Copyright 2018 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef BAZEL_SRC_MAIN_CPP_UTIL_PATH_H_ +#define BAZEL_SRC_MAIN_CPP_UTIL_PATH_H_ + +#include <string> + +namespace blaze_util { + +// Returns the part of the path before the final "/". If there is a single +// leading "/" in the path, the result will be the leading "/". If there is +// no "/" in the path, the result is the empty prefix of the input (i.e., ""). +std::string Dirname(const std::string &path); + +// Returns the part of the path after the final "/". If there is no +// "/" in the path, the result is the same as the input. +std::string Basename(const std::string &path); + +std::string JoinPath(const std::string &path1, const std::string &path2); + +// Returns the given path in absolute form. Does not change paths that are +// already absolute. +// +// If called from working directory "/bar": +// MakeAbsolute("foo") --> "/bar/foo" +// MakeAbsolute("/foo") ---> "/foo" +// MakeAbsolute("C:/foo") ---> "C:/foo" +std::string MakeAbsolute(const std::string &path); + +} // namespace blaze_util + +#endif // BAZEL_SRC_MAIN_CPP_UTIL_PATH_H_ diff --git a/src/main/cpp/util/path_platform.h b/src/main/cpp/util/path_platform.h new file mode 100644 index 0000000000..abb25dcb75 --- /dev/null +++ b/src/main/cpp/util/path_platform.h @@ -0,0 +1,119 @@ +// Copyright 2018 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef BAZEL_SRC_MAIN_CPP_UTIL_PATH_PLATFORM_H_ +#define BAZEL_SRC_MAIN_CPP_UTIL_PATH_PLATFORM_H_ + +#include <string> + +namespace blaze_util { + +// Convert a path from Bazel internal form to underlying OS form. +// On Unixes this is an identity operation. +// On Windows, Bazel internal form is cygwin path, and underlying OS form +// is Windows path. +std::string ConvertPath(const std::string &path); + +// Converts `path` to a string that's safe to pass as path in a JVM flag. +// See https://github.com/bazelbuild/bazel/issues/2576 +std::string PathAsJvmFlag(const std::string &path); + +// Compares two absolute paths. Necessary because the same path can have +// multiple different names under msys2: "C:\foo\bar" or "C:/foo/bar" +// (Windows-style) and "/c/foo/bar" (msys2 style). Returns if the paths are +// equal. +bool CompareAbsolutePaths(const std::string &a, const std::string &b); + +// Split a path to dirname and basename parts. +std::pair<std::string, std::string> SplitPath(const std::string &path); + +bool IsDevNull(const char *path); + +// Returns true if `path` is the root directory or a Windows drive root. +bool IsRootDirectory(const std::string &path); + +// Returns true if `path` is absolute. +bool IsAbsolute(const std::string &path); + +// TODO(bazel-team) consider changing the path(_platform) header split to be a +// path.h and path_windows.h split, which would make it clearer what functions +// are included by an import statement. The downside to this gain in clarity +// is that this would add more complexity to the implementation file(s)? of +// path.h, which would have to have the platform-specific implementations. +#if defined(COMPILER_MSVC) || defined(__CYGWIN__) +const wchar_t *RemoveUncPrefixMaybe(const wchar_t *ptr); +void AddUncPrefixMaybe(std::wstring *path); + +std::pair<std::wstring, std::wstring> SplitPathW(const std::wstring &path); + +bool IsRootDirectoryW(const std::wstring &path); + +bool AsWindowsPath(const std::string &path, std::string *result, + std::string *error); + +// Returns a normalized form of the input `path`. +// +// `path` must be a relative or absolute Windows path, it may use "/" instead of +// "\" but must not be a Unix-style (MSYS) path. +// The result won't have a UNC prefix, even if `path` did. +// +// Normalization means removing "." references, resolving ".." references, and +// deduplicating "/" characters while converting them to "\". +// For example if `path` is "foo/../bar/.//qux", the result is "bar\qux". +// +// Uplevel references that cannot go any higher in the directory tree are simply +// ignored, e.g. "c:/.." is normalized to "c:\" and "../../foo" is normalized to +// "foo". +// +// Visible for testing, would be static otherwise. +std::string NormalizeWindowsPath(std::string path); + +// Converts a UTF8-encoded `path` to a normalized, widechar Windows path. +// +// Returns true if conversion succeeded and sets the contents of `result` to it. +// +// The input `path` may be an absolute or relative Windows path. +// +// The returned path is normalized (see NormalizeWindowsPath). +// +// If `path` had a "\\?\" prefix then the function assumes it's already Windows +// style and converts it to wstring without any alterations. +// Otherwise `path` is normalized and converted to a Windows path and the result +// won't have a "\\?\" prefix even if it's longer than MAX_PATH (adding the +// prefix is the caller's responsibility). +// +// The method recognizes current-drive-relative Windows paths ("\foo") turning +// them into absolute paths ("c:\foo"). +bool AsWindowsPath(const std::string &path, std::wstring *result, + std::string *error); + +bool AsAbsoluteWindowsPath(const std::string &path, std::wstring *wpath, + std::string *error); + +// Same as `AsWindowsPath`, but returns a lowercase 8dot3 style shortened path. +// Result will never have a UNC prefix, nor a trailing "/" or "\". +// Works also for non-existent paths; shortens as much of them as it can. +// Also works for non-existent drives. +bool AsShortWindowsPath(const std::string &path, std::string *result, + std::string *error); + +template <typename char_type> +bool IsPathSeparator(char_type ch); + +template <typename char_type> +bool HasDriveSpecifierPrefix(const char_type *ch); + +#endif // defined(COMPILER_MSVC) || defined(__CYGWIN__) +} // namespace blaze_util + +#endif // BAZEL_SRC_MAIN_CPP_UTIL_PATH_PLATFORM_H_ diff --git a/src/main/cpp/util/path_posix.cc b/src/main/cpp/util/path_posix.cc new file mode 100644 index 0000000000..bbeffca30c --- /dev/null +++ b/src/main/cpp/util/path_posix.cc @@ -0,0 +1,60 @@ +// Copyright 2018 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/main/cpp/util/path_platform.h" + +#include <limits.h> // PATH_MAX + +#include <string.h> // strncmp +#include <unistd.h> // access, open, close, fsync +#include "src/main/cpp/util/errors.h" +#include "src/main/cpp/util/exit_code.h" +#include "src/main/cpp/util/logging.h" + +namespace blaze_util { + +std::string ConvertPath(const std::string &path) { return path; } + +std::string PathAsJvmFlag(const std::string &path) { return path; } + +bool CompareAbsolutePaths(const std::string &a, const std::string &b) { + return a == b; +} + +std::pair<std::string, std::string> SplitPath(const std::string &path) { + size_t pos = path.rfind('/'); + + // Handle the case with no '/' in 'path'. + if (pos == std::string::npos) return std::make_pair("", path); + + // Handle the case with a single leading '/' in 'path'. + if (pos == 0) + return std::make_pair(std::string(path, 0, 1), std::string(path, 1)); + + return std::make_pair(std::string(path, 0, pos), std::string(path, pos + 1)); +} + +bool IsDevNull(const char *path) { + return path != NULL && *path != 0 && strncmp("/dev/null\0", path, 10) == 0; +} + +bool IsRootDirectory(const std::string &path) { + return path.size() == 1 && path[0] == '/'; +} + +bool IsAbsolute(const std::string &path) { + return !path.empty() && path[0] == '/'; +} + +} // namespace blaze_util diff --git a/src/main/cpp/util/path_windows.cc b/src/main/cpp/util/path_windows.cc new file mode 100644 index 0000000000..d3756c71bf --- /dev/null +++ b/src/main/cpp/util/path_windows.cc @@ -0,0 +1,420 @@ +// Copyright 2018 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/main/cpp/util/path_platform.h" + +#include <wchar.h> // wcslen +#include <windows.h> + +#include <algorithm> +#include <memory> // unique_ptr +#include <sstream> +#include <vector> + +#include "src/main/cpp/util/errors.h" +#include "src/main/cpp/util/exit_code.h" +#include "src/main/cpp/util/file_platform.h" +#include "src/main/cpp/util/logging.h" +#include "src/main/cpp/util/strings.h" +#include "src/main/native/windows/file.h" + +namespace blaze_util { + +using bazel::windows::HasUncPrefix; + +static char GetCurrentDrive(); + +template <typename char_type> +struct CharTraits { + static bool IsAlpha(char_type ch); +}; + +template <> +struct CharTraits<char> { + static bool IsAlpha(char ch) { return isalpha(ch); } +}; + +template <> +struct CharTraits<wchar_t> { + static bool IsAlpha(wchar_t ch) { return iswalpha(ch); } +}; + +template <typename char_type> +static bool IsPathSeparator(char_type ch) { + return ch == '/' || ch == '\\'; +} + +template <typename char_type> +static bool HasDriveSpecifierPrefix(const char_type* ch) { + return CharTraits<char_type>::IsAlpha(ch[0]) && ch[1] == ':'; +} + +std::string ConvertPath(const std::string& path) { + // The path may not be Windows-style and may not be normalized, so convert it. + std::wstring wpath; + std::string error; + if (!AsAbsoluteWindowsPath(path, &wpath, &error)) { + BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) + << "ConvertPath(" << path + << "): AsAbsoluteWindowsPath failed: " << error; + } + std::transform(wpath.begin(), wpath.end(), wpath.begin(), ::towlower); + return std::string( + WstringToCstring(RemoveUncPrefixMaybe(wpath.c_str())).get()); +} + +bool CompareAbsolutePaths(const std::string& a, const std::string& b) { + return ConvertPath(a) == ConvertPath(b); +} + +std::string PathAsJvmFlag(const std::string& path) { + std::string spath; + std::string error; + if (!AsShortWindowsPath(path, &spath, &error)) { + BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) + << "PathAsJvmFlag(" << path + << "): AsShortWindowsPath failed: " << error; + } + // Convert backslashes to forward slashes, in order to avoid the JVM parsing + // Windows paths as if they contained escaped characters. + // See https://github.com/bazelbuild/bazel/issues/2576 + std::replace(spath.begin(), spath.end(), '\\', '/'); + return spath; +} + +void AddUncPrefixMaybe(std::wstring* path) { + if (path->size() >= MAX_PATH && !HasUncPrefix(path->c_str())) { + *path = std::wstring(L"\\\\?\\") + *path; + } +} + +const wchar_t* RemoveUncPrefixMaybe(const wchar_t* ptr) { + return ptr + (HasUncPrefix(ptr) ? 4 : 0); +} + +// Checks if the path is absolute and/or is a root path. +// +// If `must_be_root` is true, then in addition to being absolute, the path must +// also be just the root part, no other components, e.g. "c:\" is both absolute +// and root, but "c:\foo" is just absolute. +template <typename char_type> +static bool IsRootOrAbsolute(const std::basic_string<char_type>& path, + bool must_be_root) { + // An absolute path is one that starts with "/", "\", "c:/", "c:\", + // "\\?\c:\", or rarely "\??\c:\" or "\\.\c:\". + // + // It is unclear whether the UNC prefix is just "\\?\" or is "\??\" also + // valid (in some cases it seems to be, though MSDN doesn't mention it). + return + // path is (or starts with) "/" or "\" + ((must_be_root ? path.size() == 1 : !path.empty()) && + IsPathSeparator(path[0])) || + // path is (or starts with) "c:/" or "c:\" or similar + ((must_be_root ? path.size() == 3 : path.size() >= 3) && + HasDriveSpecifierPrefix(path.c_str()) && IsPathSeparator(path[2])) || + // path is (or starts with) "\\?\c:\" or "\??\c:\" or similar + ((must_be_root ? path.size() == 7 : path.size() >= 7) && + HasUncPrefix(path.c_str()) && + HasDriveSpecifierPrefix(path.c_str() + 4) && IsPathSeparator(path[6])); +} + +template <typename char_type> +static std::pair<std::basic_string<char_type>, std::basic_string<char_type> > +SplitPathImpl(const std::basic_string<char_type>& path) { + if (path.empty()) { + return std::make_pair(std::basic_string<char_type>(), + std::basic_string<char_type>()); + } + + size_t pos = path.size() - 1; + for (auto it = path.crbegin(); it != path.crend(); ++it, --pos) { + if (IsPathSeparator(*it)) { + if ((pos == 2 || pos == 6) && + IsRootOrAbsolute(path.substr(0, pos + 1), /* must_be_root */ true)) { + // Windows path, top-level directory, e.g. "c:\foo", + // result is ("c:\", "foo"). + // Or UNC path, top-level directory, e.g. "\\?\c:\foo" + // result is ("\\?\c:\", "foo"). + return std::make_pair( + // Include the "/" or "\" in the drive specifier. + path.substr(0, pos + 1), path.substr(pos + 1)); + } else { + // Windows path (neither top-level nor drive root), Unix path, or + // relative path. + return std::make_pair( + // If the only "/" is the leading one, then that shall be the first + // pair element, otherwise the substring up to the rightmost "/". + pos == 0 ? path.substr(0, 1) : path.substr(0, pos), + // If the rightmost "/" is the tail, then the second pair element + // should be empty. + pos == path.size() - 1 ? std::basic_string<char_type>() + : path.substr(pos + 1)); + } + } + } + // Handle the case with no '/' or '\' in `path`. + return std::make_pair(std::basic_string<char_type>(), path); +} + +std::pair<std::string, std::string> SplitPath(const std::string& path) { + return SplitPathImpl(path); +} + +std::pair<std::wstring, std::wstring> SplitPathW(const std::wstring& path) { + return SplitPathImpl(path); +} + +bool AsWindowsPath(const std::string& path, std::string* result, + std::string* error) { + if (path.empty()) { + result->clear(); + return true; + } + if (IsDevNull(path.c_str())) { + result->assign("NUL"); + return true; + } + if (HasUncPrefix(path.c_str())) { + // Path has "\\?\" prefix --> assume it's already Windows-style. + *result = path.c_str(); + return true; + } + if (IsPathSeparator(path[0]) && path.size() > 1 && IsPathSeparator(path[1])) { + // Unsupported path: "\\" or "\\server\path", or some degenerate form of + // these, such as "//foo". + if (error) { + *error = "network paths are unsupported"; + } + return false; + } + if (HasDriveSpecifierPrefix(path.c_str()) && + (path.size() < 3 || !IsPathSeparator(path[2]))) { + // Unsupported path: "c:" or "c:foo" + if (error) { + *error = "working-directory relative paths are unsupported"; + } + return false; + } + + std::string mutable_path = path; + if (path[0] == '/') { + if (error) { + *error = "Unix-style paths are unsupported"; + } + return false; + } + + if (path[0] == '\\') { + // This is an absolute Windows path on the current drive, e.g. "\foo\bar". + mutable_path = std::string(1, GetCurrentDrive()) + ":" + path; + } // otherwise this is a relative path, or absolute Windows path. + + result->assign(NormalizeWindowsPath(mutable_path)); + return true; +} + +bool AsWindowsPath(const std::string& path, std::wstring* result, + std::string* error) { + std::string normalized_win_path; + if (!AsWindowsPath(path, &normalized_win_path, error)) { + return false; + } + + result->assign(CstringToWstring(normalized_win_path.c_str()).get()); + return true; +} + +bool AsAbsoluteWindowsPath(const std::string& path, std::wstring* result, + std::string* error) { + if (path.empty()) { + result->clear(); + return true; + } + if (IsDevNull(path.c_str())) { + result->assign(L"NUL"); + return true; + } + if (!AsWindowsPath(path, result, error)) { + return false; + } + if (!IsRootOrAbsolute(*result, /* must_be_root */ false)) { + *result = GetCwdW() + L"\\" + *result; + } + if (!HasUncPrefix(result->c_str())) { + *result = std::wstring(L"\\\\?\\") + *result; + } + return true; +} + +bool AsShortWindowsPath(const std::string& path, std::string* result, + std::string* error) { + if (IsDevNull(path.c_str())) { + result->assign("NUL"); + return true; + } + + result->clear(); + std::wstring wpath; + std::wstring wsuffix; + if (!AsAbsoluteWindowsPath(path, &wpath, error)) { + return false; + } + DWORD size = ::GetShortPathNameW(wpath.c_str(), nullptr, 0); + if (size == 0) { + // GetShortPathNameW can fail if `wpath` does not exist. This is expected + // when we are about to create a file at that path, so instead of failing, + // walk up in the path until we find a prefix that exists and can be + // shortened, or is a root directory. Save the non-existent tail in + // `wsuffix`, we'll add it back later. + std::vector<std::wstring> segments; + while (size == 0 && !IsRootDirectoryW(wpath)) { + std::pair<std::wstring, std::wstring> split = SplitPathW(wpath); + wpath = split.first; + segments.push_back(split.second); + size = ::GetShortPathNameW(wpath.c_str(), nullptr, 0); + } + + // Join all segments. + std::wostringstream builder; + bool first = true; + for (auto it = segments.crbegin(); it != segments.crend(); ++it) { + if (!first || !IsRootDirectoryW(wpath)) { + builder << L'\\' << *it; + } else { + builder << *it; + } + first = false; + } + wsuffix = builder.str(); + } + + std::wstring wresult; + if (IsRootDirectoryW(wpath)) { + // Strip the UNC prefix from `wpath`, and the leading "\" from `wsuffix`. + wresult = std::wstring(RemoveUncPrefixMaybe(wpath.c_str())) + wsuffix; + } else { + std::unique_ptr<WCHAR[]> wshort( + new WCHAR[size]); // size includes null-terminator + if (size - 1 != ::GetShortPathNameW(wpath.c_str(), wshort.get(), size)) { + if (error) { + std::string last_error = GetLastErrorString(); + std::stringstream msg; + msg << "AsShortWindowsPath(" << path << "): GetShortPathNameW(" + << WstringToString(wpath) << ") failed: " << last_error; + *error = msg.str(); + } + return false; + } + // GetShortPathNameW may preserve the UNC prefix in the result, so strip it. + wresult = std::wstring(RemoveUncPrefixMaybe(wshort.get())) + wsuffix; + } + + result->assign(WstringToCstring(wresult.c_str()).get()); + ToLower(result); + return true; +} + +bool IsDevNull(const char* path) { + return path != NULL && *path != 0 && + (strncmp("/dev/null\0", path, 10) == 0 || + ((path[0] == 'N' || path[0] == 'n') && + (path[1] == 'U' || path[1] == 'u') && + (path[2] == 'L' || path[2] == 'l') && path[3] == 0)); +} + +bool IsRootDirectory(const std::string& path) { + return IsRootOrAbsolute(path, true); +} + +bool IsAbsolute(const std::string& path) { + return IsRootOrAbsolute(path, false); +} + +bool IsRootDirectoryW(const std::wstring& path) { + return IsRootOrAbsolute(path, true); +} + +static char GetCurrentDrive() { + std::wstring cwd = GetCwdW(); + wchar_t wdrive = RemoveUncPrefixMaybe(cwd.c_str())[0]; + wchar_t offset = wdrive >= L'A' && wdrive <= L'Z' ? L'A' : L'a'; + return 'a' + wdrive - offset; +} + +std::string NormalizeWindowsPath(std::string path) { + if (path.empty()) { + return ""; + } + if (path[0] == '/') { + // This is an absolute MSYS path, error out. + BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) + << "NormalizeWindowsPath(" << path << "): expected a Windows path"; + } + if (path.size() >= 4 && HasUncPrefix(path.c_str())) { + path = path.substr(4); + } + + static const std::string dot("."); + static const std::string dotdot(".."); + + std::vector<std::string> segments; + int segment_start = -1; + // Find the path segments in `path` (separated by "/"). + for (int i = 0;; ++i) { + if (!IsPathSeparator(path[i]) && path[i] != '\0') { + // The current character does not end a segment, so start one unless it's + // already started. + if (segment_start < 0) { + segment_start = i; + } + } else if (segment_start >= 0 && i > segment_start) { + // The current character is "/" or "\0", so this ends a segment. + // Add that to `segments` if there's anything to add; handle "." and "..". + std::string segment(path, segment_start, i - segment_start); + segment_start = -1; + if (segment == dotdot) { + if (!segments.empty() && + !HasDriveSpecifierPrefix(segments[0].c_str())) { + segments.pop_back(); + } + } else if (segment != dot) { + segments.push_back(segment); + } + } + if (path[i] == '\0') { + break; + } + } + + // Handle the case when `path` is just a drive specifier (or some degenerate + // form of it, e.g. "c:\.."). + if (segments.size() == 1 && segments[0].size() == 2 && + HasDriveSpecifierPrefix(segments[0].c_str())) { + return segments[0] + '\\'; + } + + // Join all segments. + bool first = true; + std::ostringstream result; + for (const auto& s : segments) { + if (!first) { + result << '\\'; + } + first = false; + result << s; + } + return result.str(); +} + +} // namespace blaze_util diff --git a/src/main/cpp/workspace_layout.cc b/src/main/cpp/workspace_layout.cc index 64f4a6c06d..b1d3ff2fe7 100644 --- a/src/main/cpp/workspace_layout.cc +++ b/src/main/cpp/workspace_layout.cc @@ -19,6 +19,8 @@ #include "src/main/cpp/blaze_util_platform.h" #include "src/main/cpp/util/file.h" #include "src/main/cpp/util/file_platform.h" +#include "src/main/cpp/util/path.h" +#include "src/main/cpp/util/path_platform.h" namespace blaze { |