]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
Add support for coverage (compiling for gcov)
authorAnders Björklund <anders@itension.se>
Tue, 2 Sep 2014 20:56:50 +0000 (22:56 +0200)
committerAnders Björklund <anders@itension.se>
Tue, 31 Mar 2015 19:51:37 +0000 (21:51 +0200)
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.

ccache.c
cleanup.c
compopt.c
test.sh
test/test_compopt.c

index 4c6cbe028052359d4ffe1715db567b0ec804d8dc..c0c6d51f5e437fd111e26950fd0be92f082d4152 100644 (file)
--- 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);
        }
index db2ac7f8ddc1aedbcd36242cdbf801b553790469..981423d9ab413d9480fabdd82bcc989672490939 100644 (file)
--- 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. */
index 71b968545a4476e8a8f9b752ca0aa3312171e6d9..41f710daab727480c38c068d5f82323fc82a9cf2 100644 (file)
--- 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 c4c23001da5474d6dd8a9d861ae147062476f909..f14e287dda33a0589b3c478125782a467f9abdc9 100755 (executable)
--- a/test.sh
+++ b/test.sh
@@ -761,6 +761,10 @@ int test2;
 EOF
     cat <<EOF >test3.h
 int test3;
+EOF
+    cat <<EOF >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.
index 9af5e4e1677e9f315ddb80145736bfdc29aa8a6d..6c17c3d337c03e466b158fd6f81796a33f06f4e0 100644 (file)
@@ -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"));