Move path-manipulation functions to own library file. Leave functions that make file accesses in the file library, and general blaze utilities in the blaze_util file, but move the functions that boil down to string manipulation and path formatting to their own file. (With the exception of getCWD, since absolute path syntax is relevant here.) Doing this largely to consolidate all Windows path control into a single place, so that it's easier to notice inconsistencies. For instance, ConvertPath currently makes Windows paths absolute, but not Posix paths, and MakeAbsolute relies on this behavior. In addition, JoinPath assumes Posix path syntax, which leads to some odd looking paths. These will be fixed in a followup change. (Found these issues while working on #4502, trying to fix the windows-specific system bazelrc.) RELNOTES: None. PiperOrigin-RevId: 199368226
diff --git a/src/main/cpp/blaze.cc b/src/main/cpp/blaze.cc index d023f64..7487c76 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 @@ 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 @@ 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 @@ 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 @@ 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 d7ab44c..10c4e1a 100644 --- a/src/main/cpp/blaze_util.cc +++ b/src/main/cpp/blaze_util.cc
@@ -43,18 +43,6 @@ 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 e7f8ba1..084e8d6 100644 --- a/src/main/cpp/blaze_util.h +++ b/src/main/cpp/blaze_util.h
@@ -30,15 +30,6 @@ 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 4f01ba8..dee5463 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 6de5eb1..f08aa56 100644 --- a/src/main/cpp/blaze_util_platform.h +++ b/src/main/cpp/blaze_util_platform.h
@@ -119,16 +119,6 @@ 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 @@ // 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 4f8d9c7..156b021 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 @@ 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 @@ } } -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 565e887..d41a2f4 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 @@ 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 @@ 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 7df0af72..a1d7f9d 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 @@ "." + 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 @@ } 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 @@ // 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 @@ 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 5ccec1d..3699d40 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 @@ 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 @@ 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 @@ "--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 @@ 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 @@ 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 254b6f8..6f53b4d 100644 --- a/src/main/cpp/util/BUILD +++ b/src/main/cpp/util/BUILD
@@ -15,13 +15,15 @@ "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( - 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 @@ 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 3eb614c..041d779 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 @@ 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 4bc1651..235ec87 100644 --- a/src/main/cpp/util/file.h +++ b/src/main/cpp/util/file.h
@@ -63,17 +63,6 @@ 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 5b96133..ac4fc3a 100644 --- a/src/main/cpp/util/file_platform.h +++ b/src/main/cpp/util/file_platform.h
@@ -50,9 +50,6 @@ // 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 @@ // 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 @@ 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 5136df0..df05b54 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 @@ 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 @@ 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 @@ 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 39160c9..537852e 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::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 @@ // 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ ::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 0000000..efa10b8 --- /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 0000000..38e735b --- /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 0000000..abb25dc --- /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 0000000..bbeffca --- /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 0000000..d3756c7 --- /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 64f4a6c..b1d3ff2 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 {