Bazel client, Windows: implement AsWindowsPath
This method converts MSYS paths to Windows path.
It uses the BAZEL_SH envvar to obtain the MSYS
root directory, to which all Unix paths (except
for mounts) are relative.
We cannot handle mounts because we don't want to
read /etc/mtab every time there's a file operation
so we simply apply a heuristic similar to
https://github.com/bazelbuild/bazel/blob/cd4cc09fa6ef96380a3d0888f825dfd1dbada651/src/main/java/com/google/devtools/build/lib/vfs/WindowsFileSystem.java#L52-L63
Also clean up the #ifdefs surrounding SyncFile.
See https://github.com/bazelbuild/bazel/issues/2107
--
PiperOrigin-RevId: 142531986
MOS_MIGRATED_REVID=142531986
diff --git a/src/main/cpp/util/errors.cc b/src/main/cpp/util/errors.cc
index 49248a1..8497545 100644
--- a/src/main/cpp/util/errors.cc
+++ b/src/main/cpp/util/errors.cc
@@ -41,4 +41,13 @@
exit(exit_status);
}
+void PrintError(const char *format, ...) {
+ fprintf(stderr, "Error: ");
+ va_list ap;
+ va_start(ap, format);
+ vfprintf(stderr, format, ap);
+ va_end(ap);
+ fprintf(stderr, ": %s\n", strerror(errno));
+}
+
} // namespace blaze_util
diff --git a/src/main/cpp/util/errors.h b/src/main/cpp/util/errors.h
index 84de39d..833a6ee 100644
--- a/src/main/cpp/util/errors.h
+++ b/src/main/cpp/util/errors.h
@@ -28,6 +28,7 @@
// Prints "Error: <formatted-message>: <strerror(errno)>\n", and exits nonzero.
void pdie(const int exit_status, const char *format, ...) ATTRIBUTE_NORETURN
PRINTF_ATTRIBUTE(2, 3);
+void PrintError(const char *format, ...) PRINTF_ATTRIBUTE(1, 2);
} // namespace blaze_util
diff --git a/src/main/cpp/util/file_platform.h b/src/main/cpp/util/file_platform.h
index e6df884..15bfd5d 100644
--- a/src/main/cpp/util/file_platform.h
+++ b/src/main/cpp/util/file_platform.h
@@ -120,6 +120,26 @@
void ForEachDirectoryEntry(const std::string &path,
DirectoryEntryConsumer *consume);
+#if defined(COMPILER_MSVC) || defined(__CYGWIN__)
+// Converts a UTF8-encoded `path` to a widechar Windows path.
+//
+// Returns true if conversion succeeded and sets the contents of `result` to it.
+//
+// The `path` may be absolute or relative, and may be a Windows or MSYS path.
+// In every case, this method replaces forward slashes with backslashes if
+// necessary.
+//
+// Recognizes the drive letter in MSYS paths, so e.g. "/c/windows" becomes
+// "c:\windows". Prepends the MSYS root (computed from the BAZEL_SH envvar) to
+// absolute MSYS paths, so e.g. "/usr" becomes "c:\tools\msys64\usr".
+//
+// The result may be longer than MAX_PATH. It's the caller's responsibility to
+// prepend the long path prefix ("\\?\") in case they need to pass it to a
+// Windows API function (some require the prefix, some don't), or to quote the
+// path if necessary.
+bool AsWindowsPath(const std::string &path, std::wstring *result);
+#endif // defined(COMPILER_MSVC) || defined(__CYGWIN__)
+
} // namespace blaze_util
#endif // BAZEL_SRC_MAIN_CPP_UTIL_FILE_PLATFORM_H_
diff --git a/src/main/cpp/util/file_posix.cc b/src/main/cpp/util/file_posix.cc
index b256a68..81e9bd6 100644
--- a/src/main/cpp/util/file_posix.cc
+++ b/src/main/cpp/util/file_posix.cc
@@ -278,11 +278,8 @@
}
bool IsAbsolute(const string &path) { return !path.empty() && path[0] == '/'; }
-#endif // not __CYGWIN__
void SyncFile(const string& path) {
-// fsync always fails on Cygwin with "Permission denied" for some reason.
-#ifndef __CYGWIN__
const char* file_path = path.c_str();
int fd = open(file_path, O_RDONLY);
if (fd < 0) {
@@ -294,8 +291,8 @@
file_path);
}
close(fd);
-#endif // not __CYGWIN__
}
+#endif // not __CYGWIN__
time_t GetMtimeMillisec(const string& path) {
struct stat buf;
diff --git a/src/main/cpp/util/file_windows.cc b/src/main/cpp/util/file_windows.cc
index bf6e79c..021e96d 100644
--- a/src/main/cpp/util/file_windows.cc
+++ b/src/main/cpp/util/file_windows.cc
@@ -16,14 +16,19 @@
#include <ctype.h> // isalpha
#include <windows.h>
+#include <memory> // unique_ptr
+
#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/strings.h"
namespace blaze_util {
using std::pair;
using std::string;
+using std::unique_ptr;
+using std::wstring;
class WindowsPipe : public IPipe {
public:
@@ -132,6 +137,86 @@
return std::make_pair("", path);
}
+class MsysRoot {
+ public:
+ MsysRoot() : data_(Get()) {}
+ bool IsValid() const { return data_.first; }
+ const string& GetPath() const { return data_.second; }
+
+ private:
+ const std::pair<bool, string> data_;
+ static std::pair<bool, string> Get();
+};
+
+std::pair<bool, string> MsysRoot::Get() {
+ string result;
+ char value[MAX_PATH];
+ DWORD len = GetEnvironmentVariableA("BAZEL_SH", value, MAX_PATH);
+ if (len > 0) {
+ result = value;
+ } else {
+ const char* value2 = getenv("BAZEL_SH");
+ if (value2 == nullptr || value2[0] == '\0') {
+ PrintError(
+ "BAZEL_SH environment variable is not defined, cannot convert MSYS "
+ "paths to Windows paths");
+ return std::make_pair(false, "");
+ }
+ result = value2;
+ }
+ // BAZEL_SH is usually "c:\tools\msys64\bin\bash.exe", we need to return
+ // "c:\tools\msys64".
+ return std::make_pair(true, std::move(Dirname(Dirname(result))));
+}
+
+bool AsWindowsPath(const string& path, wstring* result) {
+ if (path.empty()) {
+ result->clear();
+ return true;
+ }
+
+ string mutable_path = path;
+ if (path[0] == '/') {
+ // This is an absolute MSYS path.
+ if (path.size() == 2 || (path.size() > 2 && path[2] == '/')) {
+ // The path is either "/x" or "/x/" or "/x/something". In all three cases
+ // "x" is the drive letter.
+ // TODO(laszlocsomor): use GetLogicalDrives to retrieve the list of drives
+ // and only apply this heuristic for the valid drives. It's possible that
+ // the user has a directory "/a" but no "A:\" drive, so in that case we
+ // should prepend the MSYS root.
+ mutable_path = path.substr(1, 1) + ":\\";
+ if (path.size() > 2) {
+ mutable_path += path.substr(3);
+ }
+ } else {
+ // The path is a normal MSYS path e.g. "/usr". Prefix it with the MSYS
+ // root.
+ // Define kMsysRoot only in this scope. This way we only initialize it
+ // and thus check for BAZEL_SH if we really need to, i.e. the caller
+ // passed an MSYS path and we have to convert it. If all paths ever passed
+ // are Windows paths, we don't need to check whether BAZEL_SH is defined.
+ static const MsysRoot kMsysRoot;
+ if (!kMsysRoot.IsValid()) {
+ return false;
+ }
+ mutable_path = JoinPath(kMsysRoot.GetPath(), path);
+ }
+ } // otherwise this is a relative path, or absolute Windows path.
+
+ unique_ptr<WCHAR[]> mutable_wpath(CstringToWstring(mutable_path.c_str()));
+ WCHAR* p = mutable_wpath.get();
+ // Replace forward slashes with backslashes.
+ while (*p != L'\0') {
+ if (*p == L'/') {
+ *p = L'\\';
+ }
+ ++p;
+ }
+ result->assign(mutable_wpath.get());
+ return true;
+}
+
#ifdef COMPILER_MSVC
bool ReadFile(const string& filename, string* content, int max_size) {
// TODO(bazel-team): implement this.
@@ -209,12 +294,10 @@
bool IsAbsolute(const string& path) { return IsRootOrAbsolute(path, false); }
-#ifdef COMPILER_MSVC
void SyncFile(const string& path) {
// No-op on Windows native; unsupported by Cygwin.
+ // fsync always fails on Cygwin with "Permission denied" for some reason.
}
-#else // not COMPILER_MSVC
-#endif // COMPILER_MSVC
#ifdef COMPILER_MSVC
time_t GetMtimeMillisec(const string& path) {