aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main
diff options
context:
space:
mode:
authorGravatar ccalvarin <ccalvarin@google.com>2018-06-05 15:27:26 -0700
committerGravatar Copybara-Service <copybara-piper@google.com>2018-06-05 15:28:51 -0700
commitac69da0367e0dbd9aa13a332668af07dbb7a8135 (patch)
tree16b533129defbe15440c268e9a128c59cb88d4c3 /src/main
parent216afa160b0ccff3df3ccc3a41acbb9814e27989 (diff)
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
Diffstat (limited to 'src/main')
-rw-r--r--src/main/cpp/blaze.cc19
-rw-r--r--src/main/cpp/blaze_util.cc12
-rw-r--r--src/main/cpp/blaze_util.h9
-rw-r--r--src/main/cpp/blaze_util_linux.cc1
-rw-r--r--src/main/cpp/blaze_util_platform.h16
-rw-r--r--src/main/cpp/blaze_util_posix.cc13
-rw-r--r--src/main/cpp/blaze_util_windows.cc38
-rw-r--r--src/main/cpp/option_processor.cc10
-rw-r--r--src/main/cpp/startup_options.cc20
-rw-r--r--src/main/cpp/util/BUILD17
-rw-r--r--src/main/cpp/util/file.cc38
-rw-r--r--src/main/cpp/util/file.h11
-rw-r--r--src/main/cpp/util/file_platform.h26
-rw-r--r--src/main/cpp/util/file_posix.cc26
-rw-r--r--src/main/cpp/util/file_windows.cc385
-rw-r--r--src/main/cpp/util/path.cc64
-rw-r--r--src/main/cpp/util/path.h43
-rw-r--r--src/main/cpp/util/path_platform.h119
-rw-r--r--src/main/cpp/util/path_posix.cc60
-rw-r--r--src/main/cpp/util/path_windows.cc420
-rw-r--r--src/main/cpp/workspace_layout.cc2
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 {