/* 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;
*/
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).
/* 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;
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);
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) {
stats_update(STATS_STDOUT);
tmp_unlink(tmp_stdout);
tmp_unlink(tmp_stderr);
+ if (tmp_cov) {
+ tmp_unlink(tmp_cov);
+ }
failed();
}
tmp_unlink(tmp_stdout);
}
tmp_unlink(tmp_stderr);
+ if (tmp_cov) {
+ tmp_unlink(tmp_cov);
+ }
failed();
}
}
}
+ 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);
free(tmp_stderr);
free(tmp_stdout);
+ free(tmp_cov);
}
/*
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);
}
}
+ /* 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);
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);
}
if (produce_dep_file) {
update_mtime(cached_dep);
}
+ if (generating_coverage) {
+ update_mtime(cached_cov);
+ }
if (output_dia) {
update_mtime(cached_dia);
}
}
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);
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]));
}
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) {
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;
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;
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);
}
EOF
cat <<EOF >test3.h
int test3;
+EOF
+ cat <<EOF >code.c
+/* code.c */
+int test() {}
EOF
backdate test1.h test2.h test3.h
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.