]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
feat: Add support for -fcoverage-compilation-dir/-fcoverage-prefix-map (#1644)
authorEvgeniy Isaev <isaev.evgeniy@gmail.com>
Sun, 19 Oct 2025 17:59:15 +0000 (20:59 +0300)
committerGitHub <noreply@github.com>
Sun, 19 Oct 2025 17:59:15 +0000 (19:59 +0200)
src/ccache/argprocessing.cpp
src/ccache/argsinfo.hpp
src/ccache/ccache.cpp
test/CMakeLists.txt
test/suites/coverage_compilation_dir.bash [new file with mode: 0644]
test/suites/coverage_prefix_map.bash [new file with mode: 0644]

index 8d431960c10d3bbb569d7676fb64ddca92626815..063bac342d044ae1baca0db5f5c64826faa89e50 100644 (file)
@@ -733,6 +733,13 @@ process_option_arg(const Context& ctx,
     return Statistic::none;
   }
 
+  if (util::starts_with(arg, "-fcoverage-prefix-map=")) {
+    std::string map = arg.substr(arg.find('=') + 1);
+    args_info.coverage_prefix_maps.push_back(map);
+    state.add_common_arg(args[i]);
+    return Statistic::none;
+  }
+
   if (util::starts_with(arg, "-fdebug-compilation-dir")
       || util::starts_with(arg, "-ffile-compilation-dir")) {
     std::string compilation_dir;
@@ -756,6 +763,13 @@ process_option_arg(const Context& ctx,
     return Statistic::none;
   }
 
+  if (std::string_view prefix{"-fcoverage-compilation-dir="};
+      util::starts_with(arg, prefix)) {
+    args_info.coverage_compilation_dir = arg.substr(prefix.length());
+    state.add_common_arg(args[i]);
+    return Statistic::none;
+  }
+
   // Debugging is handled specially, so that we know if we can strip line
   // number info.
   if (util::starts_with(arg, "-g")) {
@@ -1348,6 +1362,11 @@ process_args(Context& ctx)
     }
   }
 
+  std::reverse(args_info.debug_prefix_maps.begin(),
+               args_info.debug_prefix_maps.end());
+  std::reverse(args_info.coverage_prefix_maps.begin(),
+               args_info.coverage_prefix_maps.end());
+
   const bool is_link =
     !(state.found_c_opt || state.found_dc_opt || state.found_S_opt
       || state.found_syntax_only || state.found_analyze_opt);
index a1ba647bae060b07e6316b6be15ff49e9ac6549f..283ad38a9b13e2238f22a2aaa8ab077e22093dbe 100644 (file)
@@ -163,10 +163,16 @@ struct ArgsInfo
   // Relocating debuginfo in the format old=new.
   std::vector<std::string> debug_prefix_maps;
 
+  // Relocating coverage info in the format old=new.
+  std::vector<std::string> coverage_prefix_maps;
+
   // Compilation directory as passed in -ffile-compilation-dir or
   // -fdebug-compilation-dir.
   std::string compilation_dir;
 
+  // Coverage compilation directory as passed in -fcoverage-compilation-dir.
+  std::string coverage_compilation_dir;
+
   // Build session file as passed in -fbuild-session-file.
   std::filesystem::path build_session_file;
 };
index e95ac6b7ebbfb18d3a5e11d5b05074d519e6b6c5..6e503d311a540be1764da630611b6315831874d1 100644 (file)
@@ -1563,6 +1563,32 @@ hash_nvcc_host_compiler(const Context& ctx,
   return {};
 }
 
+static void
+apply_prefix_remapping(const std::vector<std::string>& maps, fs::path& path)
+{
+  for (const auto& map : maps) {
+    const size_t sep_pos{map.find('=')};
+    if (sep_pos == std::string::npos) {
+      continue;
+    }
+
+    const std::string old_prefix{map.substr(0, sep_pos)};
+    const std::string new_prefix{map.substr(sep_pos + 1)};
+    if (!util::starts_with(util::pstr(path).str(), old_prefix)) {
+      continue;
+    }
+
+    LOG("Relocating from '{}' to '{}' (original path: '{}')",
+        old_prefix,
+        new_prefix,
+        path);
+    fs::path suffix{util::pstr(path).str().substr(old_prefix.size())};
+    path = new_prefix / suffix;
+
+    return;
+  }
+}
+
 // update a hash with information common for the direct and preprocessor modes.
 static tl::expected<void, Failure>
 hash_common_info(const Context& ctx, const util::Args& args, Hash& hash)
@@ -1641,26 +1667,12 @@ hash_common_info(const Context& ctx, const util::Args& args, Hash& hash)
 
   // Possibly hash the current working directory.
   if (ctx.args_info.generating_debuginfo && ctx.config.hash_dir()) {
-    std::string dir_to_hash = util::pstr(ctx.apparent_cwd);
+    fs::path dir_to_hash{ctx.apparent_cwd};
     if (!ctx.args_info.compilation_dir.empty()) {
       dir_to_hash = ctx.args_info.compilation_dir;
     } else {
-      for (const auto& map : ctx.args_info.debug_prefix_maps) {
-        size_t sep_pos = map.find('=');
-        if (sep_pos != std::string::npos) {
-          std::string old_path = map.substr(0, sep_pos);
-          std::string new_path = map.substr(sep_pos + 1);
-          LOG("Relocating debuginfo from {} to {} (CWD: {})",
-              old_path,
-              new_path,
-              ctx.apparent_cwd);
-          if (util::starts_with(util::pstr(ctx.apparent_cwd).str(), old_path)) {
-            dir_to_hash =
-              new_path
-              + util::pstr(ctx.apparent_cwd).str().substr(old_path.size());
-          }
-        }
-      }
+      LOG("Applying debug prefix maps to CWD path '{}'", dir_to_hash);
+      apply_prefix_remapping(ctx.args_info.debug_prefix_maps, dir_to_hash);
     }
     LOG("Hashing CWD {}", dir_to_hash);
     hash.hash_delimiter("cwd");
@@ -1949,6 +1961,11 @@ hash_argument(const Context& ctx,
     hash.hash("-fprofile-prefix-path=");
     return {};
   }
+  if (util::starts_with(args[i], "-fcoverage-prefix-map=")) {
+    hash.hash_delimiter("arg");
+    hash.hash("-fcoverage-prefix-map=");
+    return {};
+  }
 
   if (util::starts_with(args[i], "-frandom-seed=")
       && ctx.config.sloppiness().contains(core::Sloppy::random_seed)) {
@@ -2284,11 +2301,17 @@ hash_profiling_related_data(const Context& ctx, Hash& hash)
     // For a relative profile directory D the compiler stores $PWD/D as part of
     // the profile filename so we need to include the same information in the
     // hash.
-    const fs::path profile_path =
-      ctx.args_info.profile_path.is_absolute()
-        ? ctx.args_info.profile_path
-        : ctx.apparent_cwd / ctx.args_info.profile_path;
-    LOG("Adding profile directory {} to our hash", profile_path);
+    fs::path profile_path = ctx.args_info.profile_path.is_absolute()
+                              ? ctx.args_info.profile_path
+                              : ctx.apparent_cwd / ctx.args_info.profile_path;
+
+    if (!ctx.args_info.coverage_compilation_dir.empty()) {
+      profile_path = ctx.args_info.coverage_compilation_dir;
+    } else if (!ctx.args_info.coverage_prefix_maps.empty()) {
+      LOG("Applying coverage prefix maps to profile path '{}'", profile_path);
+      apply_prefix_remapping(ctx.args_info.coverage_prefix_maps, profile_path);
+    }
+    LOG("Adding profile directory '{}' to our hash", profile_path);
     hash.hash_delimiter("-fprofile-dir");
     hash.hash(profile_path);
   }
index ab3c3b9f58026b7680b0dcc8801281e78946df2b..c8225dc15600e6c43331bb62a6e9b867d431748f 100644 (file)
@@ -29,6 +29,8 @@ addtest(clang_cu_direct)
 addtest(cleanup)
 addtest(color_diagnostics)
 addtest(config)
+addtest(coverage_compilation_dir)
+addtest(coverage_prefix_map)
 addtest(debug_compilation_dir)
 addtest(debug_prefix_map)
 addtest(depend)
diff --git a/test/suites/coverage_compilation_dir.bash b/test/suites/coverage_compilation_dir.bash
new file mode 100644 (file)
index 0000000..0d8775f
--- /dev/null
@@ -0,0 +1,88 @@
+SUITE_coverage_compilation_dir_PROBE() {
+    touch test.c
+    if ! $COMPILER -c -fcoverage-compilation-dir=dir test.c 2>/dev/null; then
+        echo "-fcoverage-compilation-dir not supported by compiler"
+    fi
+
+    if ! $RUN_WIN_XFAIL; then
+        echo "coverage-compilation-dir tests are broken on Windows."
+        return
+    fi
+}
+
+SUITE_coverage_compilation_dir_SETUP() {
+    unset CCACHE_NODIRECT
+
+    mkdir -p dir1/src dir1/include
+    cat <<EOF >dir1/src/test.c
+#include <stdarg.h>
+#include <test.h>
+EOF
+    cat <<EOF >dir1/include/test.h
+int test;
+EOF
+    cp -r dir1 dir2
+    backdate dir1/include/test.h dir2/include/test.h
+}
+
+SUITE_coverage_compilation_dir() {
+    # -------------------------------------------------------------------------
+    TEST "Cache misses without a configured coverage compilation directory"
+
+    cd dir1
+    CCACHE_BASEDIR=$(pwd) $CCACHE_COMPILE \
+        -I$(pwd)/include \
+        -g \
+        -fprofile-instr-generate \
+        -fdebug-compilation-dir=some_name_not_likely_to_exist_in_path \
+        -c $(pwd)/src/test.c \
+        -o $(pwd)/test.o
+    expect_stat direct_cache_hit 0
+    expect_stat preprocessed_cache_hit 0
+    expect_stat cache_miss 1
+    expect_stat files_in_cache 2
+
+    cd ../dir2
+    CCACHE_BASEDIR=$(pwd) $CCACHE_COMPILE \
+        -I$(pwd)/include \
+        -g \
+        -fprofile-instr-generate \
+        -fdebug-compilation-dir=some_name_not_likely_to_exist_in_path \
+        -c $(pwd)/src/test.c \
+        -o $(pwd)/test.o
+    expect_stat direct_cache_hit 0
+    expect_stat preprocessed_cache_hit 0
+    expect_stat cache_miss 2
+    expect_stat files_in_cache 4
+
+    # -------------------------------------------------------------------------
+    TEST "Cache hits with a configured coverage compilation directory"
+
+    cd dir1
+    CCACHE_BASEDIR=$(pwd) $CCACHE_COMPILE \
+        -I$(pwd)/include \
+        -g \
+        -fprofile-instr-generate \
+        -fdebug-compilation-dir=some_name_not_likely_to_exist_in_path \
+        -fcoverage-compilation-dir=some_other_name_not_likely_to_exist_in_path \
+        -c $(pwd)/src/test.c \
+        -o $(pwd)/test.o
+    expect_stat direct_cache_hit 0
+    expect_stat preprocessed_cache_hit 0
+    expect_stat cache_miss 1
+    expect_stat files_in_cache 2
+
+    cd ../dir2
+    CCACHE_BASEDIR=$(pwd) $CCACHE_COMPILE \
+        -I$(pwd)/include \
+        -g \
+        -fprofile-instr-generate \
+        -fdebug-compilation-dir=some_name_not_likely_to_exist_in_path \
+        -fcoverage-compilation-dir=some_other_name_not_likely_to_exist_in_path \
+        -c $(pwd)/src/test.c \
+        -o $(pwd)/test.o
+    expect_stat direct_cache_hit 1
+    expect_stat preprocessed_cache_hit 0
+    expect_stat cache_miss 1
+    expect_stat files_in_cache 2
+}
diff --git a/test/suites/coverage_prefix_map.bash b/test/suites/coverage_prefix_map.bash
new file mode 100644 (file)
index 0000000..b9ef45a
--- /dev/null
@@ -0,0 +1,154 @@
+SUITE_coverage_prefix_map_PROBE() {
+    touch test.c
+    if ! $COMPILER -c -fcoverage-prefix-map=old=new test.c 2>/dev/null; then
+        echo "-fcoverage-prefix-map not supported by compiler"
+    fi
+
+    if ! $RUN_WIN_XFAIL; then
+        echo "coverage_prefix_map tests are broken on Windows."
+        return
+    fi
+}
+
+SUITE_coverage_prefix_map_SETUP() {
+    unset CCACHE_NODIRECT
+
+    mkdir -p dir1/src dir1/include
+    cat <<EOF >dir1/src/test.c
+#include <stdarg.h>
+#include <test.h>
+EOF
+    cat <<EOF >dir1/include/test.h
+int test;
+EOF
+    cp -r dir1 dir2
+    backdate dir1/include/test.h dir2/include/test.h
+}
+
+SUITE_coverage_prefix_map() {
+    # -------------------------------------------------------------------------
+    TEST "Cache misses without a configured coverage prefix map"
+
+    cd dir1
+    CCACHE_BASEDIR=$(pwd) $CCACHE_COMPILE \
+        -I$(pwd)/include \
+        -g \
+        -fprofile-instr-generate \
+        -fdebug-compilation-dir=some_name_not_likely_to_exist_in_path \
+        -c $(pwd)/src/test.c \
+        -o $(pwd)/test.o
+    expect_stat direct_cache_hit 0
+    expect_stat preprocessed_cache_hit 0
+    expect_stat cache_miss 1
+    expect_stat files_in_cache 2
+
+    cd ../dir2
+    CCACHE_BASEDIR=$(pwd) $CCACHE_COMPILE \
+        -I$(pwd)/include \
+        -g \
+        -fprofile-instr-generate \
+        -fdebug-compilation-dir=some_name_not_likely_to_exist_in_path \
+        -c $(pwd)/src/test.c \
+        -o $(pwd)/test.o
+    expect_stat direct_cache_hit 0
+    expect_stat preprocessed_cache_hit 0
+    expect_stat cache_miss 2
+    expect_stat files_in_cache 4
+
+    # -------------------------------------------------------------------------
+    TEST "Cache hits with a configured coverage prefix map"
+
+    cd dir1
+    CCACHE_BASEDIR=$(pwd) $CCACHE_COMPILE \
+        -I$(pwd)/include \
+        -g \
+        -fprofile-instr-generate \
+        -fdebug-compilation-dir=some_name_not_likely_to_exist_in_path \
+        -fcoverage-prefix-map=$(pwd)=some_other_name_not_likely_to_exist_in_path \
+        -c $(pwd)/src/test.c \
+        -o $(pwd)/test.o
+    expect_stat direct_cache_hit 0
+    expect_stat preprocessed_cache_hit 0
+    expect_stat cache_miss 1
+    expect_stat files_in_cache 2
+
+    cd ../dir2
+    CCACHE_BASEDIR=$(pwd) $CCACHE_COMPILE \
+        -I$(pwd)/include \
+        -g \
+        -fprofile-instr-generate \
+        -fdebug-compilation-dir=some_name_not_likely_to_exist_in_path \
+        -fcoverage-prefix-map=$(pwd)=some_other_name_not_likely_to_exist_in_path \
+        -c $(pwd)/src/test.c \
+        -o $(pwd)/test.o
+    expect_stat direct_cache_hit 1
+    expect_stat preprocessed_cache_hit 0
+    expect_stat cache_miss 1
+    expect_stat files_in_cache 2
+
+    # -------------------------------------------------------------------------
+    TEST "Cache hits with multiple configured coverage prefix maps"
+
+    cd dir1
+    CCACHE_BASEDIR=$(pwd) $CCACHE_COMPILE \
+        -I$(pwd)/include \
+        -g \
+        -fprofile-instr-generate \
+        -fdebug-compilation-dir=some_name_not_likely_to_exist_in_path \
+        -fcoverage-prefix-map=$(pwd)=some_other_name_not_likely_to_exist_in_path \
+        -fcoverage-prefix-map=foo=bar \
+        -c $(pwd)/src/test.c \
+        -o $(pwd)/test.o
+    expect_stat direct_cache_hit 0
+    expect_stat preprocessed_cache_hit 0
+    expect_stat cache_miss 1
+    expect_stat files_in_cache 2
+
+    cd ../dir2
+    CCACHE_BASEDIR=$(pwd) $CCACHE_COMPILE \
+        -I$(pwd)/include \
+        -g \
+        -fprofile-instr-generate \
+        -fdebug-compilation-dir=some_name_not_likely_to_exist_in_path \
+        -fcoverage-prefix-map=$(pwd)=some_other_name_not_likely_to_exist_in_path \
+        -fcoverage-prefix-map=foo=bar \
+        -c $(pwd)/src/test.c \
+        -o $(pwd)/test.o
+    expect_stat direct_cache_hit 1
+    expect_stat preprocessed_cache_hit 0
+    expect_stat cache_miss 1
+    expect_stat files_in_cache 2
+
+    # -------------------------------------------------------------------------
+    TEST "Cache hits with multiple configured coverage prefix maps (different order)"
+
+    cd dir1
+    CCACHE_BASEDIR=$(pwd) $CCACHE_COMPILE \
+        -I$(pwd)/include \
+        -g \
+        -fprofile-instr-generate \
+        -fdebug-compilation-dir=some_name_not_likely_to_exist_in_path \
+        -fcoverage-prefix-map=foo=bar \
+        -fcoverage-prefix-map=$(pwd)=some_other_name_not_likely_to_exist_in_path \
+        -c $(pwd)/src/test.c \
+        -o $(pwd)/test.o
+    expect_stat direct_cache_hit 0
+    expect_stat preprocessed_cache_hit 0
+    expect_stat cache_miss 1
+    expect_stat files_in_cache 2
+
+    cd ../dir2
+    CCACHE_BASEDIR=$(pwd) $CCACHE_COMPILE \
+        -I$(pwd)/include \
+        -g \
+        -fprofile-instr-generate \
+        -fdebug-compilation-dir=some_name_not_likely_to_exist_in_path \
+        -fcoverage-prefix-map=$(pwd)=some_other_name_not_likely_to_exist_in_path \
+        -fcoverage-prefix-map=foo=bar \
+        -c $(pwd)/src/test.c \
+        -o $(pwd)/test.o
+    expect_stat direct_cache_hit 1
+    expect_stat preprocessed_cache_hit 0
+    expect_stat cache_miss 1
+    expect_stat files_in_cache 2
+}