Bazel client, Windows: fix AsShortWindowsPath
This method now works for non-existent paths too.
See https://github.com/bazelbuild/bazel/issues/2107
--
PiperOrigin-RevId: 149284633
MOS_MIGRATED_REVID=149284633
diff --git a/src/main/cpp/util/file_platform.h b/src/main/cpp/util/file_platform.h
index 02f5b87..7733e7a 100644
--- a/src/main/cpp/util/file_platform.h
+++ b/src/main/cpp/util/file_platform.h
@@ -208,7 +208,9 @@
bool AsWindowsPathWithUncPrefix(const std::string &path, std::wstring *wpath);
// Same as `AsWindowsPath`, but returns a lowercase 8dot3 style shortened path.
-// Result will never have a UNC prefix.
+// 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);
#endif // defined(COMPILER_MSVC) || defined(__CYGWIN__)
diff --git a/src/main/cpp/util/file_windows.cc b/src/main/cpp/util/file_windows.cc
index 23dc219..35d8242 100644
--- a/src/main/cpp/util/file_windows.cc
+++ b/src/main/cpp/util/file_windows.cc
@@ -107,6 +107,10 @@
}
}
+static const wchar_t* RemoveUncPrefixMaybe(const wchar_t* ptr) {
+ return ptr + (windows_util::HasUncPrefix(ptr) ? 4 : 0);
+}
+
class WindowsPipe : public IPipe {
public:
WindowsPipe(const HANDLE& read_handle, const HANDLE& write_handle)
@@ -505,24 +509,57 @@
result->clear();
wstring wpath;
+ wstring wsuffix;
if (!AsWindowsPathWithUncPrefix(path, &wpath)) {
return false;
}
DWORD size = ::GetShortPathNameW(wpath.c_str(), nullptr, 0);
if (size == 0) {
- return false;
+ // 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 (std::vector<wstring>::const_reverse_iterator& it = segments.crbegin();
+ it != segments.crend(); ++it) {
+ if (!first || !IsRootDirectoryW(wpath)) {
+ builder << L'\\' << *it;
+ } else {
+ builder << *it;
+ }
+ first = false;
+ }
+ wsuffix = builder.str();
}
- unique_ptr<WCHAR[]> wshort(new WCHAR[size]); // size includes null-terminator
- if (size - 1 != ::GetShortPathNameW(wpath.c_str(), wshort.get(), size)) {
- pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
- "AsShortWindowsPath(%s): GetShortPathNameW(%S) failed, err=%d",
- path.c_str(), wpath.c_str(), GetLastError());
+ 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)) {
+ pdie(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR,
+ "AsShortWindowsPath(%s): GetShortPathNameW(%S) failed, err=%d",
+ path.c_str(), wpath.c_str(), GetLastError());
+ }
+ // GetShortPathNameW may preserve the UNC prefix in the result, so strip it.
+ wresult = wstring(RemoveUncPrefixMaybe(wshort.get())) + wsuffix;
}
- // GetShortPathNameW may preserve the UNC prefix in the result, so strip it.
- WCHAR* result_ptr = wshort.get() + (HasUncPrefix(wshort.get()) ? 4 : 0);
- result->assign(WstringToCstring(result_ptr).get());
+ result->assign(WstringToCstring(wresult.c_str()).get());
ToLower(result);
return true;
}
@@ -911,9 +948,7 @@
size_t size = wcslen(long_realpath.get()) -
(windows_util::HasUncPrefix(long_realpath.get()) ? 4 : 0);
unique_ptr<WCHAR[]> lcase_realpath(new WCHAR[size + 1]);
- const WCHAR* p_from =
- long_realpath.get() +
- (windows_util::HasUncPrefix(long_realpath.get()) ? 4 : 0);
+ const WCHAR* p_from = RemoveUncPrefixMaybe(long_realpath.get());
WCHAR* p_to = lcase_realpath.get();
while (size-- > 0) {
*p_to++ = towlower(*p_from++);
@@ -1093,9 +1128,7 @@
}
string GetCwd() {
- unique_ptr<WCHAR[]> cwd(GetCwdW());
- return string(
- WstringToCstring(cwd.get() + (HasUncPrefix(cwd.get()) ? 4 : 0)).get());
+ return string(WstringToCstring(RemoveUncPrefixMaybe(GetCwdW().get())).get());
}
bool ChangeDirectory(const string& path) {
diff --git a/src/test/cpp/util/file_windows_test.cc b/src/test/cpp/util/file_windows_test.cc
index 8360063..53116f8 100644
--- a/src/test/cpp/util/file_windows_test.cc
+++ b/src/test/cpp/util/file_windows_test.cc
@@ -243,6 +243,19 @@
ASSERT_TRUE(AsShortWindowsPath("nul", &actual));
ASSERT_EQ(string("NUL"), actual);
+ ASSERT_TRUE(AsShortWindowsPath("C://", &actual));
+ ASSERT_EQ(string("c:\\"), actual);
+ ASSERT_TRUE(AsShortWindowsPath("/C//", &actual));
+ ASSERT_EQ(string("c:\\"), actual);
+
+ // The A drive usually doesn't exist but AsShortWindowsPath should still work.
+ // Here we even have multiple trailing slashes, that should be handled too.
+ ASSERT_TRUE(AsShortWindowsPath("A://", &actual));
+ ASSERT_EQ(string("a:\\"), actual);
+ ASSERT_TRUE(AsShortWindowsPath("/A//", &actual));
+ ASSERT_EQ(string("a:\\"), actual);
+
+ // Assert that we can shorten the TEST_TMPDIR.
string tmpdir;
GET_TEST_TMPDIR(tmpdir);
string short_tmpdir;
@@ -250,12 +263,26 @@
ASSERT_LT(0, short_tmpdir.size());
ASSERT_TRUE(PathExists(short_tmpdir));
+ // Assert that a trailing "/" doesn't change the shortening logic and it will
+ // be stripped from the result.
+ ASSERT_TRUE(AsShortWindowsPath(tmpdir + "/", &actual));
+ ASSERT_EQ(actual, short_tmpdir);
+ ASSERT_NE(actual.back(), '/');
+ ASSERT_NE(actual.back(), '\\');
+
+ // Assert shortening another long path, and that the result is lowercased.
string dirname(JoinPath(short_tmpdir, "LONGpathNAME"));
ASSERT_EQ(0, mkdir(dirname.c_str()));
ASSERT_TRUE(PathExists(dirname));
-
ASSERT_TRUE(AsShortWindowsPath(dirname, &actual));
ASSERT_EQ(short_tmpdir + "\\longpa~1", actual);
+
+ // Assert shortening non-existent paths.
+ ASSERT_TRUE(AsShortWindowsPath(JoinPath(tmpdir, "NonExistent/FOO"), &actual));
+ ASSERT_EQ(short_tmpdir + "\\nonexistent\\foo", actual);
+ // Assert shortening non-existent root paths.
+ ASSERT_TRUE(AsShortWindowsPath("/c/NonExistent/FOO", &actual));
+ ASSERT_EQ("c:\\nonexistent\\foo", actual);
}
TEST_F(FileWindowsTest, TestMsysRootRetrieval) {