From: Anders Björklund Date: Tue, 2 Sep 2014 20:56:50 +0000 (+0200) Subject: Add support for coverage (compiling for gcov) X-Git-Tag: v3.2.2~10 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=02d3f078bd2495b8db2264ae0b2c692b4c5ba1bd;p=thirdparty%2Fccache.git Add support for coverage (compiling for gcov) Store the coverage notes (.gcno file) in the cache, next to the object. If called on a file without actual code, like a header, then generate an empty file in the cache to be able to separate this case from file missing. Newer versions of gcc will generate a small file instead of not generating. In the object file generated with -fprofile-arcs, the runtime output file for the coverage data (.gcda file) is created from the output filename. Make sure to hash this path to avoid cache hits with another absolute path. Also make sure to use the source file path, since this is in the notes. --- diff --git a/ccache.c b/ccache.c index 4c6cbe028..c0c6d51f5 100644 --- a/ccache.c +++ b/ccache.c @@ -95,6 +95,9 @@ static char *output_obj; /* The path to the dependency file (implicit or specified with -MF). */ static char *output_dep; +/* The path to the coverage file (implicit when using -ftest-coverage). */ +static char *output_cov; + /* Diagnostic generation information (clang). */ static char *output_dia = NULL; @@ -122,6 +125,12 @@ static char *cached_stderr; */ static char *cached_dep; +/* + * Full path to the file containing the coverage information + * (cachedir/a/b/cdef[...]-size.gcno). + */ +static char *cached_cov; + /* * Full path to the file containing the diagnostic information (for clang) * (cachedir/a/b/cdef[...]-size.dia). @@ -149,6 +158,15 @@ static struct hashtable *included_files; /* is gcc being asked to output dependencies? */ static bool generating_dependencies; +/* is gcc being asked to output coverage ? */ +static bool generating_coverage; + +/* is gcc being asked to output coverage data (.gcda) at runtime? */ +static bool profile_arcs; + +/* name of the custom profile directory (default: object dirname) */ +static char *profile_dir; + /* the name of the temporary pre-processor file */ static char *i_tmpfile; @@ -776,15 +794,29 @@ void update_manifest_file(void) static void to_cache(struct args *args) { - char *tmp_stdout, *tmp_stderr; + char *tmp_stdout, *tmp_stderr, *tmp_aux, *tmp_cov; struct stat st; int status, tmp_stdout_fd, tmp_stderr_fd; + FILE *f; tmp_stdout = format("%s.tmp.stdout", cached_obj); tmp_stdout_fd = create_tmp_fd(&tmp_stdout); tmp_stderr = format("%s.tmp.stderr", cached_obj); tmp_stderr_fd = create_tmp_fd(&tmp_stderr); + if (generating_coverage) { + /* gcc has some funny rule about max extension length */ + if (strlen(get_extension(output_obj)) < 6) { + tmp_aux = remove_extension(output_obj); + } else { + tmp_aux = x_strdup(output_obj); + } + tmp_cov = format("%s.gcno", tmp_aux); + free(tmp_aux); + } else { + tmp_cov = NULL; + } + args_add(args, "-o"); args_add(args, output_obj); @@ -815,6 +847,9 @@ to_cache(struct args *args) stats_update(STATS_MISSING); tmp_unlink(tmp_stdout); tmp_unlink(tmp_stderr); + if (tmp_cov) { + tmp_unlink(tmp_cov); + } failed(); } if (st.st_size != 0) { @@ -822,6 +857,9 @@ to_cache(struct args *args) stats_update(STATS_STDOUT); tmp_unlink(tmp_stdout); tmp_unlink(tmp_stderr); + if (tmp_cov) { + tmp_unlink(tmp_cov); + } failed(); } tmp_unlink(tmp_stdout); @@ -883,6 +921,9 @@ to_cache(struct args *args) } tmp_unlink(tmp_stderr); + if (tmp_cov) { + tmp_unlink(tmp_cov); + } failed(); } @@ -924,6 +965,24 @@ to_cache(struct args *args) } } + if (generating_coverage) { + /* gcc won't generate notes if there is no code */ + if (stat(tmp_cov, &st) != 0 && errno == ENOENT) { + cc_log("Creating placeholder: %s", cached_cov); + + f = fopen(cached_cov, "wb"); + if (!f) { + cc_log("Failed to create %s: %s", cached_cov, strerror(errno)); + stats_update(STATS_ERROR); + failed(); + } + fclose(f); + stats_update_size(0, 1); + } else { + put_file_in_cache(tmp_cov, cached_cov); + } + } + if (output_dia) { if (x_stat(output_dia, &st) != 0) { stats_update(STATS_ERROR); @@ -969,6 +1028,7 @@ to_cache(struct args *args) free(tmp_stderr); free(tmp_stdout); + free(tmp_cov); } /* @@ -1091,6 +1151,7 @@ update_cached_result_globals(struct file_hash *hash) cached_obj = get_path_in_cache(object_name, ".o"); cached_stderr = get_path_in_cache(object_name, ".stderr"); cached_dep = get_path_in_cache(object_name, ".d"); + cached_cov = get_path_in_cache(object_name, ".gcno"); cached_dia = get_path_in_cache(object_name, ".dia"); stats_file = format("%s/%c/stats", conf->cache_dir, object_name[0]); free(object_name); @@ -1207,6 +1268,26 @@ calculate_common_hash(struct args *args, struct mdfour *hash) } } + /* Possibly hash the coverage data file path. */ + if (generating_coverage && profile_arcs) { + char *gcda_path; + char *dir = dirname(output_obj); + if (profile_dir) { + dir = x_strdup(profile_dir); + } else { + dir = x_realpath(dir); + } + if (dir) { + p = remove_extension(basename(output_obj)); + gcda_path = format("%s/%s.gcda", dir, p); + cc_log("Hashing coverage path %s", gcda_path); + free(p); + hash_delimiter(hash, "gcda"); + hash_string(hash, gcda_path); + free(dir); + } + } + if (!str_eq(conf->extra_files_to_hash, "")) { char *path, *p, *q, *saveptr = NULL; p = x_strdup(conf->extra_files_to_hash); @@ -1516,6 +1597,12 @@ from_cache(enum fromcache_call_mode mode, bool put_object_in_manifest) if (produce_dep_file) { get_file_from_cache(cached_dep, output_dep); } + if (generating_coverage) { + /* gcc won't generate notes if there is no code */ + if (stat(cached_cov, &st) == 0 && st.st_size > 0) { + get_file_from_cache(cached_cov, output_cov); + } + } if (output_dia) { get_file_from_cache(cached_dia, output_dia); } @@ -1527,6 +1614,9 @@ from_cache(enum fromcache_call_mode mode, bool put_object_in_manifest) if (produce_dep_file) { update_mtime(cached_dep); } + if (generating_coverage) { + update_mtime(cached_cov); + } if (output_dia) { update_mtime(cached_dia); } @@ -1875,6 +1965,27 @@ cc_process_args(struct args *args, struct args **preprocessor_args, } continue; } + if (str_eq(argv[i], "-fprofile-arcs")) { + profile_arcs = true; + args_add(stripped_args, argv[i]); + continue; + } + if (str_eq(argv[i], "-ftest-coverage")) { + generating_coverage = true; + args_add(stripped_args, argv[i]); + continue; + } + if (str_eq(argv[i], "--coverage") /* == -fprofile-arcs -ftest-coverage */ ) { + profile_arcs = true; + generating_coverage = true; + args_add(stripped_args, argv[i]); + continue; + } + if (str_startswith(argv[i], "-fprofile-dir=")) { + profile_dir = x_strdup(argv[i] + 14); + args_add(stripped_args, argv[i]); + continue; + } if (str_startswith(argv[i], "--sysroot=")) { char *relpath = make_relative_path(x_strdup(argv[i] + 10)); char *option = format("--sysroot=%s", relpath); @@ -2174,6 +2285,12 @@ cc_process_args(struct args *args, struct args **preprocessor_args, goto out; } + /* The source code file path gets put into the notes */ + if (generating_coverage) { + input_file = x_strdup(argv[i]); + continue; + } + /* Rewrite to relative to increase hit rate. */ input_file = make_relative_path(x_strdup(argv[i])); } @@ -2360,6 +2477,15 @@ cc_process_args(struct args *args, struct args **preprocessor_args, args_add(dep_args, output_obj); } } + if (generating_coverage) { + char *default_covfile_name; + char *base_name; + + base_name = remove_extension(output_obj); + default_covfile_name = format("%s.gcno", base_name); + free(base_name); + output_cov = make_relative_path(x_strdup(default_covfile_name)); + } *compiler_args = args_copy(stripped_args); if (conf->run_second_cpp) { @@ -2527,11 +2653,13 @@ cc_reset(void) free(input_file); input_file = NULL; free(output_obj); output_obj = NULL; free(output_dep); output_dep = NULL; + free(output_cov); output_cov = NULL; free(output_dia); output_dia = NULL; free(cached_obj_hash); cached_obj_hash = NULL; free(cached_obj); cached_obj = NULL; free(cached_stderr); cached_stderr = NULL; free(cached_dep); cached_dep = NULL; + free(cached_cov); cached_cov = NULL; free(cached_dia); cached_dia = NULL; free(manifest_path); manifest_path = NULL; time_of_compilation = 0; @@ -2539,6 +2667,9 @@ cc_reset(void) hashtable_destroy(included_files, 1); included_files = NULL; } generating_dependencies = false; + generating_coverage = false; + profile_arcs = false; + free(profile_dir); profile_dir = NULL; i_tmpfile = NULL; direct_i_file = false; free(cpp_stderr); cpp_stderr = NULL; @@ -2638,6 +2769,9 @@ ccache(int argc, char *argv[]) if (generating_dependencies) { cc_log("Dependency file: %s", output_dep); } + if (generating_coverage) { + cc_log("Coverage file: %s", output_cov); + } if (output_dia) { cc_log("Diagnostic file: %s", output_dia); } diff --git a/cleanup.c b/cleanup.c index db2ac7f8d..981423d9a 100644 --- a/cleanup.c +++ b/cleanup.c @@ -153,6 +153,7 @@ sort_and_clean(void) ext = get_extension(files[i]->fname); if (str_eq(ext, ".o") || str_eq(ext, ".d") + || str_eq(ext, ".gcno") || str_eq(ext, ".dia") || str_eq(ext, ".stderr") || str_eq(ext, "")) { @@ -167,6 +168,7 @@ sort_and_clean(void) */ delete_sibling_file(base, ".o"); delete_sibling_file(base, ".d"); + delete_sibling_file(base, ".gcno"); delete_sibling_file(base, ".dia"); delete_sibling_file(base, ".stderr"); delete_sibling_file(base, ""); /* Object file from ccache 2.4. */ diff --git a/compopt.c b/compopt.c index 71b968545..41f710daa 100644 --- a/compopt.c +++ b/compopt.c @@ -32,7 +32,6 @@ struct compopt { }; static const struct compopt compopts[] = { - {"--coverage", TOO_HARD}, /* implies -ftest-coverage */ {"--param", TAKES_ARG}, {"--serialize-diagnostics", TAKES_ARG | TAKES_PATH}, {"-A", TAKES_ARG}, @@ -60,7 +59,6 @@ static const struct compopt compopts[] = { {"-fno-working-directory", AFFECTS_CPP}, {"-fplugin=libcc1plugin", TOO_HARD}, /* interaction with GDB */ {"-frepo", TOO_HARD}, - {"-ftest-coverage", TOO_HARD}, /* generates a .gcno file at the same time */ {"-fworking-directory", AFFECTS_CPP}, {"-gsplit-dwarf", TOO_HARD}, /* generates a .dwo file at the same time */ {"-idirafter", AFFECTS_CPP | TAKES_ARG | TAKES_PATH}, diff --git a/test.sh b/test.sh index c4c23001d..f14e287dd 100755 --- a/test.sh +++ b/test.sh @@ -761,6 +761,10 @@ int test2; EOF cat <test3.h int test3; +EOF + cat <code.c +/* code.c */ +int test() {} EOF backdate test1.h test2.h test3.h @@ -977,6 +981,35 @@ EOF checkfile test.d "$expected_d_content" compare_file reference_test.o test.o + ################################################################## + # Check that coverage works. + testname="coverage (empty)" + $CCACHE -z >/dev/null + $CCACHE $COMPILER -c -fprofile-arcs -ftest-coverage test.c + checkstat 'cache hit (direct)' 0 + checkstat 'cache hit (preprocessed)' 0 + checkstat 'cache miss' 1 + $CCACHE $COMPILER -c -fprofile-arcs -ftest-coverage test.c + checkstat 'cache hit (direct)' 1 + checkstat 'cache hit (preprocessed)' 0 + checkstat 'cache miss' 1 + + testname="coverage (code)" + $CCACHE -z >/dev/null + $CCACHE $COMPILER -c -fprofile-arcs -ftest-coverage code.c + checkstat 'cache hit (direct)' 0 + checkstat 'cache hit (preprocessed)' 0 + checkstat 'cache miss' 1 + test -r code.gcno || test_failed "gcov" + + rm -f code.gcno + + $CCACHE $COMPILER -c -fprofile-arcs -ftest-coverage code.c + checkstat 'cache hit (direct)' 1 + checkstat 'cache hit (preprocessed)' 0 + checkstat 'cache miss' 1 + test -r code.gcno || test_failed "gcov" + ################################################################## # Check the scenario of running a ccache with direct mode on a cache # built up by a ccache without direct mode support. diff --git a/test/test_compopt.c b/test/test_compopt.c index 9af5e4e16..6c17c3d33 100644 --- a/test/test_compopt.c +++ b/test/test_compopt.c @@ -64,6 +64,16 @@ TEST(dash_MD_not_too_hard) CHECK(!compopt_too_hard("-MD")); } +TEST(dash_fprofile_arcs_not_too_hard) +{ + CHECK(!compopt_too_hard("-fprofile-arcs")); +} + +TEST(dash_ftest_coverage_not_too_hard) +{ + CHECK(!compopt_too_hard("-ftest-coverage")); +} + TEST(dash_doesnexist_not_too_hard) { CHECK(!compopt_too_hard("-doesntexist"));