]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
fix: Fix rewriting MSVC /FI argument to relative when using base_dir
authorJoel Rosdahl <joel@rosdahl.net>
Thu, 19 Mar 2026 13:42:46 +0000 (14:42 +0100)
committerJoel Rosdahl <joel@rosdahl.net>
Fri, 20 Mar 2026 17:44:10 +0000 (18:44 +0100)
MSVC's /FI option is special in that the compiler looks up a relative
file relative to the source file instead of the CWD.

src/ccache/argprocessing.cpp
src/ccache/core/common.cpp
src/ccache/core/common.hpp
src/ccache/util/path.cpp
src/ccache/util/path.hpp
unittest/test_util_path.cpp

index 07bff192f3ac1d592b7718749e82a1f490de0c53..535c4a176b6a068e7d84fc4a41328454e724d7ab 100644 (file)
@@ -89,6 +89,7 @@ public:
   bool found_wp_md_or_mmd_opt = false;
   bool found_md_or_mmd_opt = false;
   bool found_Wa_a_opt = false;
+  bool rewrite_FI_args = false;
 
   std::string explicit_language;             // As specified with -x.
   std::string input_charset_option;          // -finput-charset=...
@@ -143,6 +144,18 @@ public:
     m_native_args.push_back(std::forward<T>(arg));
   }
 
+  util::Args&
+  get_preprocessor_args()
+  {
+    return m_preprocessor_args;
+  }
+
+  util::Args&
+  get_compiler_args()
+  {
+    return m_compiler_args;
+  }
+
   ProcessArgsResult
   to_result()
   {
@@ -1263,6 +1276,27 @@ process_option_arg(const Context& ctx,
     return Statistic::none;
   }
 
+  if (config.is_compiler_group_msvc() && arg.starts_with("-FI")
+      && !ctx.config.base_dirs().empty()) {
+    // /FI is special in that the compiler looks up a relative file relative to
+    // the source file instead of CWD, so we need to special-case it. However,
+    // to do that we need to know the source code location, so we have to
+    // rewrite the /FI argument after we have processed all arguments.
+    state.rewrite_FI_args = true;
+
+    // Need to remember the path in raw form (not rewritten relative to CWD).
+    state.add_common_arg(args[i]);
+    if (arg.length() == 3) {
+      if (i == args.size() - 1) {
+        LOG("Missing argument to {}", args[i]);
+        return Statistic::bad_compiler_arguments;
+      }
+      state.add_common_arg(args[i + 1]);
+      ++i;
+    }
+    return Statistic::none;
+  }
+
   if (compopt_takes_arg(arg) && compopt_takes_path(arg)) {
     if (i == args.size() - 1) {
       LOG("Missing argument to {}", args[i]);
@@ -1493,6 +1527,41 @@ process_args(Context& ctx)
     return tl::unexpected(Statistic::unsupported_compiler_option);
   }
 
+  // Special case: rewrite /FI arguments relative to the input file.
+  if (state.rewrite_FI_args) {
+    auto r = fs::canonical(args_info.input_file.parent_path());
+    if (!r) {
+      LOG("Failed to convert {} to absolute: {}",
+          args_info.input_file.parent_path(),
+          r.error());
+      return tl::unexpected(Statistic::internal_error);
+    }
+    const auto& abs_input_file_dir = *r;
+    auto rewrite = [&](util::Args& arglist, size_t& i) {
+      if (arglist[i].starts_with("/FI") || arglist[i].starts_with("-FI")) {
+        if (arglist[i].length() > 3) {
+          arglist[i] = FMT("{}{}",
+                           arglist[i].substr(0, 3),
+                           core::make_relative_path(
+                             ctx, arglist[i].substr(3), abs_input_file_dir));
+        } else if (i + 1 < arglist.size()) {
+          arglist[i + 1] = util::pstr(
+            core::make_relative_path(ctx, arglist[i + 1], abs_input_file_dir));
+          ++i;
+        }
+      }
+    };
+
+    auto& preprocessor_args = state.get_preprocessor_args();
+    for (size_t i = 0; i < preprocessor_args.size(); ++i) {
+      rewrite(preprocessor_args, i);
+    }
+    auto& compiler_args = state.get_compiler_args();
+    for (size_t i = 0; i < compiler_args.size(); ++i) {
+      rewrite(compiler_args, i);
+    }
+  }
+
   // Don't try to second guess the compiler's heuristics for stdout handling.
   if (args_info.output_obj == "-") {
     LOG("Output file is -");
index 92ee861622d5e7e733eaba2d1bcc22b91b96ee01..528c3fe24c543a299d23bb7287bc9a319d772fac 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2023-2025 Joel Rosdahl and other contributors
+// Copyright (C) 2023-2026 Joel Rosdahl and other contributors
 //
 // See doc/authors.adoc for a complete list of contributors.
 //
@@ -80,16 +80,27 @@ ensure_dir_exists(const fs::path& dir)
 }
 
 fs::path
-make_relative_path(const Context& ctx, const fs::path& path)
+make_relative_path(const Context& ctx,
+                   const std::filesystem::path& path,
+                   const std::filesystem::path& dir1,
+                   const std::optional<std::filesystem::path>& dir2)
 {
+  DEBUG_ASSERT(dir1.is_absolute());
+  DEBUG_ASSERT(!dir2 || dir2->is_absolute());
   if (!ctx.config.base_dirs().empty() && path.is_absolute()
       && util::path_starts_with(path, ctx.config.base_dirs())) {
-    return util::make_relative_path(ctx.actual_cwd, ctx.apparent_cwd, path);
+    return util::make_relative_path(dir1, dir2.value_or(dir1), path);
   } else {
     return path;
   }
 }
 
+fs::path
+make_relative_path(const Context& ctx, const fs::path& path)
+{
+  return make_relative_path(ctx, path, ctx.actual_cwd, ctx.apparent_cwd);
+}
+
 inline bool
 parse_inlined_from_msg(std::string_view& line, std::string& result)
 {
index af7bce38357c2a90f1a31deb7c78d6a0ffaf15e5..b565e5046f8d38d3241cbd8af5bfbc193ca5d42d 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2023-2024 Joel Rosdahl and other contributors
+// Copyright (C) 2023-2026 Joel Rosdahl and other contributors
 //
 // See doc/authors.adoc for a complete list of contributors.
 //
@@ -19,6 +19,7 @@
 #pragma once
 
 #include <filesystem>
+#include <optional>
 #include <string>
 #include <string_view>
 
@@ -29,7 +30,14 @@ namespace core {
 // Like std::filesystem::create_directories but throws core::Fatal on error.
 void ensure_dir_exists(const std::filesystem::path& dir);
 
-// Make a `path` relative to CWD if it's under base_dir.
+// Make a `path` relative to `dir1` or `dir2` if the path is under base_dir.
+std::filesystem::path make_relative_path(
+  const Context& ctx,
+  const std::filesystem::path& path,
+  const std::filesystem::path& dir1,
+  const std::optional<std::filesystem::path>& dir2 = std::nullopt);
+
+// Make a `path` relative to actual/apparent CWD if the path is under base_dir.
 std::filesystem::path make_relative_path(const Context& ctx,
                                          const std::filesystem::path& path);
 
index d4136e26bf80809c9d0525f4f040db0a5a0f15b1..7a7e13f28fef39ce23cbda2acd1368d350257fbf 100644 (file)
@@ -71,12 +71,12 @@ lexically_normal(const fs::path& path)
 }
 
 fs::path
-make_relative_path(const fs::path& actual_cwd,
-                   const fs::path& apparent_cwd,
+make_relative_path(const fs::path& dir1,
+                   const fs::path& dir2,
                    const fs::path& path)
 {
-  DEBUG_ASSERT(actual_cwd.is_absolute());
-  DEBUG_ASSERT(apparent_cwd.is_absolute());
+  DEBUG_ASSERT(dir1.is_absolute());
+  DEBUG_ASSERT(dir2.is_absolute());
   DEBUG_ASSERT(path.is_absolute());
 
   fs::path normalized_path = util::lexically_normal(path);
@@ -90,13 +90,15 @@ make_relative_path(const fs::path& actual_cwd,
       path_suffix = closest_existing_path.filename() / path_suffix;
     }
     closest_existing_path = closest_existing_path.parent_path();
+    if (closest_existing_path == closest_existing_path.root_path()) {
+      break;
+    }
   }
 
-  relpath_candidates.push_back(
-    closest_existing_path.lexically_relative(actual_cwd));
-  if (apparent_cwd != actual_cwd) {
+  relpath_candidates.push_back(closest_existing_path.lexically_relative(dir1));
+  if (dir2 != dir1) {
     relpath_candidates.emplace_back(
-      closest_existing_path.lexically_relative(apparent_cwd));
+      closest_existing_path.lexically_relative(dir2));
   }
 
   // Find best (i.e. shortest existing) match:
@@ -107,7 +109,7 @@ make_relative_path(const fs::path& actual_cwd,
                      < util::pstr(path2).str().length();
             });
   for (const auto& relpath : relpath_candidates) {
-    if (fs::equivalent(relpath, closest_existing_path)) {
+    if (fs::equivalent(dir1 / relpath, closest_existing_path)) {
       return path_suffix.empty() ? relpath
                                  : (relpath / path_suffix).lexically_normal();
     }
index 4df77a3c737057afe94c2d6ba5847db87cdcc4e7..3373aab11c06d0f36755c8773784f0b648fa81e4 100644 (file)
@@ -57,12 +57,10 @@ bool is_dev_null_path(const std::filesystem::path& path);
 // Return whether `path` includes at least one directory separator.
 bool is_full_path(std::string_view path);
 
-// Make a relative path from current working directory (either `actual_cwd` or
-// `apparent_cwd`) to `path` if `path` is under `base_dir`.
-std::filesystem::path
-make_relative_path(const std::filesystem::path& actual_cwd,
-                   const std::filesystem::path& apparent_cwd,
-                   const std::filesystem::path& path);
+// Make a relative path from `dir1` or `dir2` to `path`.
+std::filesystem::path make_relative_path(const std::filesystem::path& dir1,
+                                         const std::filesystem::path& dir2,
+                                         const std::filesystem::path& path);
 
 // Construct a normalized native path.
 //
index be33463881465d652f2b9efaf8373aa50aecd7c6..78d5305938f327ec3fe3464f9c855a1b6d3c90ec 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2021-2025 Joel Rosdahl and other contributors
+// Copyright (C) 2021-2026 Joel Rosdahl and other contributors
 //
 // See doc/authors.adoc for a complete list of contributors.
 //
@@ -103,7 +103,7 @@ TEST_CASE("util::make_relative_path")
   SUBCASE("Path matches neither actual nor apparent CWD")
   {
 #ifdef _WIN32
-    CHECK(make_relative_path("C:/a", "C:/b", "C:/x") == "C:/x");
+    CHECK(make_relative_path("C:/a", "D:/b", "E:/x") == "E:/x");
 #else
     CHECK(make_relative_path("/a", "/b", "/x") == "/x");
 #endif