]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
feat: Improve path rewriting on Windows
authorJoel Rosdahl <joel@rosdahl.net>
Wed, 25 Mar 2026 15:16:29 +0000 (16:16 +0100)
committerJoel Rosdahl <joel@rosdahl.net>
Thu, 26 Mar 2026 11:54:29 +0000 (12:54 +0100)
Made it possible to rewrite absolute paths to relative on Windows when
paths differ in case.

src/ccache/util/path.cpp
unittest/test_argprocessing.cpp
unittest/test_util_path.cpp

index 7a7e13f28fef39ce23cbda2acd1368d350257fbf..e8a7bb718dab810414ce6fb9758380a280fc9cb2 100644 (file)
@@ -31,6 +31,25 @@ const char k_dev_null_path[] = "/dev/null";
 
 namespace fs = util::filesystem;
 
+namespace {
+
+fs::path
+lexically_relative_case_aware(const fs::path& path, const fs::path& base)
+{
+#ifdef _WIN32
+  // Note: Case-folding might in theory lead to an incorrect path on Windows
+  // since not all filesystems are case-insensitive, but this is only done to
+  // produce a candidate path that will be verified by the caller later.
+  fs::path p = util::to_lowercase(path.string());
+  fs::path b = util::to_lowercase(base.string());
+  return p.lexically_relative(b);
+#else
+  return path.lexically_relative(base);
+#endif
+}
+
+} // namespace
+
 namespace util {
 
 fs::path
@@ -95,10 +114,11 @@ make_relative_path(const fs::path& dir1,
     }
   }
 
-  relpath_candidates.push_back(closest_existing_path.lexically_relative(dir1));
+  relpath_candidates.push_back(
+    lexically_relative_case_aware(closest_existing_path, dir1));
   if (dir2 != dir1) {
-    relpath_candidates.emplace_back(
-      closest_existing_path.lexically_relative(dir2));
+    relpath_candidates.push_back(
+      lexically_relative_case_aware(closest_existing_path, dir2));
   }
 
   // Find best (i.e. shortest existing) match:
index f031edc895d9feca3739901e151a6bdf58681add..3b108589bcc31b73f4e6a1a6c1188fb2392e653c 100644 (file)
@@ -765,6 +765,10 @@ TEST_CASE("MSVC PCH options")
   }
 }
 
+#ifdef _WIN32
+// The test uses absolute paths and will typically fail on macOS since
+// /Users/... will then be treated as -U/... and not an input file, so just run
+// it on Windows.
 TEST_CASE("MSVC /Yc in response file disables base_dir rewriting")
 {
   TestContext test_context;
@@ -794,6 +798,7 @@ TEST_CASE("MSVC /Yc in response file disables base_dir rewriting")
   CHECK(result->preprocessor_args.to_string()
         == FMT("cl.exe /Yc -Fp{} -FI{}", pch_path, include_path));
 }
+#endif
 
 TEST_CASE("MSVC /Yc with base_dir preserves later argument errors")
 {
index 78d5305938f327ec3fe3464f9c855a1b6d3c90ec..a84cfcdd1af0ca05ea4c67aeb237437a1b2e5b54 100644 (file)
@@ -22,6 +22,7 @@
 #include <ccache/util/filesystem.hpp>
 #include <ccache/util/format.hpp>
 #include <ccache/util/path.hpp>
+#include <ccache/util/string.hpp>
 
 #include <doctest/doctest.h>
 
@@ -134,6 +135,40 @@ TEST_CASE("util::make_relative_path")
 #endif
   }
 
+#ifdef _WIN32
+  SUBCASE("Case-insensitive match on Windows")
+  {
+    REQUIRE(fs::create_directory("casedir"));
+
+    // Construct variants with guaranteed drive-letter case difference.
+    // Windows drive letters are ASCII, so to_upper/to_lower always toggles
+    // case.
+    std::string upper_drive_cwd = actual_cwd;
+    upper_drive_cwd[0] = util::to_upper(actual_cwd[0]);
+    std::string lower_drive_cwd = actual_cwd;
+    lower_drive_cwd[0] = util::to_lower(actual_cwd[0]);
+
+    // dir1/dir2 with uppercase drive letter, path with lowercase drive letter.
+    CHECK(make_relative_path(
+            upper_drive_cwd, upper_drive_cwd, lower_drive_cwd + "/casedir")
+          == "casedir");
+
+    // dir1/dir2 with lowercase drive letter, path with uppercase drive letter.
+    CHECK(make_relative_path(
+            lower_drive_cwd, lower_drive_cwd, upper_drive_cwd + "/casedir")
+          == "casedir");
+
+    // Non-existing child: relative path also returned with drive-letter
+    // mismatch.
+    CHECK(make_relative_path(
+            upper_drive_cwd, upper_drive_cwd, lower_drive_cwd + "/nonexistent")
+          == "nonexistent");
+    CHECK(make_relative_path(
+            lower_drive_cwd, lower_drive_cwd, upper_drive_cwd + "/nonexistent")
+          == "nonexistent");
+  }
+#endif
+
 #ifndef _WIN32
   SUBCASE("Match of apparent CWD")
   {