When support for caching a compilation with coverage (producing a .gcno
file), the commit[1] made sure to avoid rewriting the input path to
relative with the motivation “also make sure to use the source file
path, since this is in the notes”. However, this seems to be unnecessary
since a relative input file path will be written as is to the .gcno, not
the absolute path. This is the case for at least GCC 4.7+ and Clang
3.6+. Fix this by potentially converting the input path to relative even
when generating coverage.
When investigating the above issue, I noticed that GCC 9+ includes the
current working directory (CWD) in the .gcno file. This means that we
have include the CWD in the hash when compiling with
-ftest-coverage/--coverage in order to replicate what the compiler would
produce. Since this makes it impossible get cache hits when compiling in
different directories, a new gcno_cwd sloppiness has been added for
opting out of hashing the CWD, with the tradeoff of potentially getting
an incorrect directory in the .gcno file.
[1]:
02d3f078bd2495b8db2264ae0b2c692b4c5ba1bd
Fixes #1032.
*file_stat_matches_ctime*::
Ignore ctimes when *file_stat_matches* is enabled. This can be useful when
backdating files' mtimes in a controlled way.
+*gcno_cwd*::
+ By default, ccache will include the current working directory in the hash
+ when producing a `.gcno` file (when compiling with `-ftest-coverage` or
+ `--coverage`). This is because GCC 9+ includes the current working directory
+ in the `.gcno` file. The *gcno_cwd* sloppiness makes ccache not hash the
+ current working directory so that you can get cache hits when compiling in
+ different directories, with the tradeoff of potentially getting an incorrect
+ directory in the `.gcno` file.
*include_file_ctime*::
By default, ccache will not cache a file if it includes a header whose ctime
is too new. This sloppiness disables that check. See also
result.enable(core::Sloppy::file_stat_matches);
} else if (token == "file_stat_matches_ctime") {
result.enable(core::Sloppy::file_stat_matches_ctime);
+ } else if (token == "gcno_cwd") {
+ result.enable(core::Sloppy::gcno_cwd);
} else if (token == "include_file_ctime") {
result.enable(core::Sloppy::include_file_ctime);
} else if (token == "include_file_mtime") {
if (sloppiness.is_enabled(core::Sloppy::file_stat_matches_ctime)) {
result += "file_stat_matches_ctime, ";
}
+ if (sloppiness.is_enabled(core::Sloppy::gcno_cwd)) {
+ result += "gcno_cwd, ";
+ }
if (sloppiness.is_enabled(core::Sloppy::include_file_ctime)) {
result += "include_file_ctime, ";
}
}
}
- // The source code file path gets put into the notes.
- if (args_info.generating_coverage) {
- args_info.input_file = args[i];
- return nullopt;
- }
-
// Rewrite to relative to increase hit rate.
args_info.input_file = Util::make_relative_path(ctx, args[i]);
hash.hash(ctx.args_info.output_obj);
}
+ if (ctx.args_info.generating_coverage
+ && !(ctx.config.sloppiness().is_enabled(core::Sloppy::gcno_cwd))) {
+ // GCC 9+ includes $PWD in the .gcno file. Since we don't have knowledge
+ // about compiler version we always (unless sloppiness is wanted) include
+ // the directory in the hash for now.
+ LOG_RAW("Hashing apparent CWD due to generating a .gcno file");
+ hash.hash_delimiter("CWD in .gcno");
+ hash.hash(ctx.apparent_cwd);
+ }
+
// Possibly hash the coverage data file path.
if (ctx.args_info.generating_coverage && ctx.args_info.profile_arcs) {
std::string dir;
-// Copyright (C) 2021 Joel Rosdahl and other contributors
+// Copyright (C) 2021-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
modules = 1U << 9,
// Ignore virtual file system (VFS) overlay file.
ivfsoverlay = 1U << 10,
+ // Allow us to include incorrect working directory in .gcno files.
+ gcno_cwd = 1U << 11,
};
class Sloppiness
+# Remove header, including a volatile timestamp, from a .gcno file.
+normalize_gcno_file() {
+ local from="$1"
+ local to="$2"
+ cut -b 13- "${from}" >"${to}"
+}
+
+
SUITE_profiling_PROBE() {
touch test.c
if ! $COMPILER -fprofile-generate -c test.c 2>/dev/null; then
expect_different_content obj1/test.o obj2/test.o # different paths to .gcda file
expect_stat direct_cache_hit 2
expect_stat cache_miss 2
+
+ # -------------------------------------------------------------------------
+ TEST "-ftest-coverage, different directories"
+
+ mkdir obj1 obj2
+
+ cd obj1
+ $COMPILER -ftest-coverage -c "$(pwd)/../test.c"
+ normalize_gcno_file test.gcno test.gcno.reference
+
+ $CCACHE_COMPILE -ftest-coverage -c "$(pwd)/../test.c"
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ normalize_gcno_file test.gcno test.gcno.ccache-miss
+ expect_equal_content test.gcno.reference test.gcno.ccache-miss
+
+ $CCACHE_COMPILE -ftest-coverage -c "$(pwd)/../test.c"
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ normalize_gcno_file test.gcno test.gcno.ccache-hit
+ expect_equal_content test.gcno.reference test.gcno.ccache-hit
+
+ cd ../obj2
+ $COMPILER -ftest-coverage -c "$(pwd)/../test.c"
+ normalize_gcno_file test.gcno test.gcno.reference
+
+ $CCACHE_COMPILE -ftest-coverage -c "$(pwd)/../test.c"
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
+ normalize_gcno_file test.gcno test.gcno.ccache-miss
+ expect_equal_content test.gcno.reference test.gcno.ccache-miss
+
+ $CCACHE_COMPILE -ftest-coverage -c "$(pwd)/../test.c"
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 2
+ normalize_gcno_file test.gcno test.gcno.ccache-hit
+ expect_equal_content test.gcno.reference test.gcno.ccache-hit
+
+ # -------------------------------------------------------------------------
+ TEST "-ftest-coverage, different directories, basedir, sloppy gcno_cwd"
+
+ export CCACHE_SLOPPINESS="$CCACHE_SLOPPINESS gcno_cwd"
+ export CCACHE_BASEDIR="$(pwd)"
+
+ mkdir obj1 obj2
+
+ cd obj1
+
+ $CCACHE_COMPILE -ftest-coverage -c "$(pwd)/../test.c"
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+
+ $CCACHE_COMPILE -ftest-coverage -c "$(pwd)/../test.c"
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+
+ cd ../obj2
+
+ $CCACHE_COMPILE -ftest-coverage -c "$(pwd)/../test.c"
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 1
}
merge_profiling_data() {
"run_second_cpp = false\n"
"sloppiness = time_macros ,include_file_mtime"
" include_file_ctime,file_stat_matches,file_stat_matches_ctime,pch_defines"
- " , no_system_headers,system_headers,clang_index_store,ivfsoverlay\n"
+ " , no_system_headers,system_headers,clang_index_store,ivfsoverlay,gcno_cwd\n"
"stats = false\n"
"temporary_dir = ${USER}_foo\n"
"umask = 777"); // Note: no newline.
== (static_cast<uint32_t>(core::Sloppy::clang_index_store)
| static_cast<uint32_t>(core::Sloppy::file_stat_matches)
| static_cast<uint32_t>(core::Sloppy::file_stat_matches_ctime)
+ | static_cast<uint32_t>(core::Sloppy::gcno_cwd)
| static_cast<uint32_t>(core::Sloppy::include_file_ctime)
| static_cast<uint32_t>(core::Sloppy::include_file_mtime)
| static_cast<uint32_t>(core::Sloppy::ivfsoverlay)
"secondary_storage = ss\n"
"sloppiness = include_file_mtime, include_file_ctime, time_macros,"
" file_stat_matches, file_stat_matches_ctime, pch_defines, system_headers,"
- " clang_index_store, ivfsoverlay\n"
+ " clang_index_store, ivfsoverlay, gcno_cwd\n"
"stats = false\n"
"stats_log = sl\n"
"temporary_dir = td\n"
"(test.conf) run_second_cpp = false",
"(test.conf) secondary_storage = ss",
"(test.conf) sloppiness = clang_index_store, file_stat_matches,"
- " file_stat_matches_ctime, include_file_ctime, include_file_mtime,"
- " ivfsoverlay, pch_defines, system_headers, time_macros",
+ " file_stat_matches_ctime, gcno_cwd, include_file_ctime,"
+ " include_file_mtime, ivfsoverlay, pch_defines, system_headers,"
+ " time_macros",
"(test.conf) stats = false",
"(test.conf) stats_log = sl",
"(test.conf) temporary_dir = td",