fix: Fix Windows MakeRelativePath.
On Windows, it is invalid to make a relative path span across different drives. This fix:
1. Instead of prepending slash, we remove slash for both paths if there's one.
2. Return input's absolute path if the drive letter is different between input and dest.
Fixes: https://bugs.chromium.org/p/gn/issues/detail?id=317
Change-Id: I3ee2a46d412f663b0739f95fe3683d892b8fe6b5
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/16620
Reviewed-by: Takuto Ikuta <tikuta@google.com>
Reviewed-by: David Turner <digit@google.com>
Commit-Queue: David Turner <digit@google.com>
diff --git a/src/gn/filesystem_utils.cc b/src/gn/filesystem_utils.cc
index d37750b..d747289 100644
--- a/src/gn/filesystem_utils.cc
+++ b/src/gn/filesystem_utils.cc
@@ -17,6 +17,7 @@
#include "util/build_config.h"
#if defined(OS_WIN)
+#include <direct.h>
#include <windows.h>
#endif
@@ -639,33 +640,75 @@
#endif
}
-std::string MakeRelativePath(const std::string& input,
- const std::string& dest) {
#if defined(OS_WIN)
- // Make sure that absolute |input| path starts with a slash if |dest| path
- // does. Otherwise skipping common prefixes won't work properly. Ensure the
- // same for |dest| path too.
- if (IsPathAbsolute(input) && !IsSlash(input[0]) && IsSlash(dest[0])) {
- std::string corrected_input(1, dest[0]);
- corrected_input.append(input);
- return MakeRelativePath(corrected_input, dest);
- }
- if (IsPathAbsolute(dest) && !IsSlash(dest[0]) && IsSlash(input[0])) {
- std::string corrected_dest(1, input[0]);
- corrected_dest.append(dest);
- return MakeRelativePath(input, corrected_dest);
+std::string GetPathWithDriveLetter(std::string_view path) {
+ if (!IsPathAbsolute(path) || !IsSlash(path[0]))
+ return std::string(path);
+
+ int drive = _getdrive();
+ DCHECK(drive > 0 && drive <= 26);
+
+ std::string ret;
+ ret.reserve(2 + path.size());
+ ret.push_back('A' + drive - 1);
+ ret.push_back(':');
+ ret += path;
+ return ret;
+}
+
+// Regulate path if it is an absolute path.
+std::string RegulatePathIfAbsolute(std::string_view path) {
+ CHECK(!path.empty());
+ bool is_start_slash = IsSlash(path[0]);
+
+ // 1. /C:/ -> C:/
+ if (path.size() > 3 && is_start_slash &&
+ base::IsAsciiAlpha(path[1]) && path[2] == ':') {
+ return RegulatePathIfAbsolute(path.substr(1));
}
- // Make sure that both absolute paths use the same drive letter case.
+ bool is_path_absolute = IsPathAbsolute(path);
+
+ // 2. /Path -> ($PWD's Drive):/Path
+ if (is_path_absolute && is_start_slash) {
+ return GetPathWithDriveLetter(path);
+ }
+
+ // 3. c:/ -> C:/
+ std::string ret(path);
+ if (is_path_absolute && !is_start_slash) {
+ ret[0] = base::ToUpperASCII(path[0]);
+ }
+
+ return ret;
+}
+#endif
+
+std::string MakeRelativePath(std::string_view input,
+ std::string_view dest) {
+#if defined(OS_WIN)
+ // Regulate the paths.
+ std::string input_regulated = RegulatePathIfAbsolute(input);
+ std::string dest_regulated = RegulatePathIfAbsolute(dest);
+
+ input = input_regulated;
+ dest = dest_regulated;
+
+ // On Windows, it is invalid to make a relative path across different
+ // drive letters. A relative path cannot span over different drives.
+ // For example:
+ // Input : D:/Path/Any/Where
+ // Dest : C:/Path/In/Another/Drive
+ // Invalid Result : ../../../../../D:/Path/Any/Where
+ // Correct Result : D:/Path/Any/Where
+ // It will at least make ninja fail.
+ // See: https://bugs.chromium.org/p/gn/issues/detail?id=317
if (IsPathAbsolute(input) && IsPathAbsolute(dest) && input.size() > 1 &&
dest.size() > 1) {
- int letter_pos = base::IsAsciiAlpha(input[0]) ? 0 : 1;
- if (input[letter_pos] != dest[letter_pos] &&
- base::ToUpperASCII(input[letter_pos]) ==
- base::ToUpperASCII(dest[letter_pos])) {
- std::string corrected_input = input;
- corrected_input[letter_pos] = dest[letter_pos];
- return MakeRelativePath(corrected_input, dest);
+ if (input[0] != dest[0]) {
+ // If the drive letters are differnet, we have no choice but use
+ // the absolute path of input for correctness.
+ return input_regulated;
}
}
#endif
diff --git a/src/gn/filesystem_utils_unittest.cc b/src/gn/filesystem_utils_unittest.cc
index ef25324..6f5baa3 100644
--- a/src/gn/filesystem_utils_unittest.cc
+++ b/src/gn/filesystem_utils_unittest.cc
@@ -543,6 +543,24 @@
EXPECT_EQ("../../../../path/to/foo",
RebasePath("/c:/path/to/foo", SourceDir("//a/b"),
std::string_view("C:/source/root")));
+
+ // Should be absolute path of destination if the drive letters are
+ // different between two paths.
+ EXPECT_EQ("C:/path/to/foo",
+ RebasePath("C:/path/to/foo", SourceDir("//a/b"),
+ std::string_view("D:/source/root")));
+ EXPECT_EQ("D:/path/to/foo",
+ RebasePath("D:/path/to/foo", SourceDir("//a/b"),
+ std::string_view("C:/source/root")));
+ EXPECT_EQ("E:/path/to/foo",
+ RebasePath("/E:/path/to/foo", SourceDir("//a/b"),
+ std::string_view("/c:/source/root")));
+ EXPECT_EQ("E:/path/to/foo",
+ RebasePath("/e:/path/to/foo", SourceDir("//a/b"),
+ std::string_view("c:/source/root")));
+ EXPECT_EQ("C:/path/to/foo",
+ RebasePath("/c:/path/to/foo", SourceDir("//a/b"),
+ std::string_view("D:/source/root")));
#endif
}