/* * ccache -- a fast C/C++ compiler cache * * Copyright (C) 2002-2007 Andrew Tridgell * Copyright (C) 2009-2010 Joel Rosdahl * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "ccache.h" #include "getopt_long.h" #include "hashtable.h" #include "hashtable_itr.h" #include "hashutil.h" #include "manifest.h" #include #include #include #include #include #include #include #include #include #include static const char VERSION_TEXT[] = "ccache version " CCACHE_VERSION "\n" "\n" "Copyright (C) 2002-2007 Andrew Tridgell\n" "Copyright (C) 2009-2010 Joel Rosdahl\n" "\n" "This program is free software; you can redistribute it and/or modify it under\n" "the terms of the GNU General Public License as published by the Free Software\n" "Foundation; either version 3 of the License, or (at your option) any later\n" "version.\n"; static const char USAGE_TEXT[] = "Usage:\n" " ccache [options]\n" " ccache compiler [compiler options]\n" " compiler [compiler options] (via symbolic link)\n" "\n" "Options:\n" " -c, --cleanup run a cache cleanup\n" " -C, --clear clear the cache completely\n" " -F, --max-files=N set maximum number of files in cache to N (use 0 for\n" " no limit)\n" " -M, --max-size=SIZE set maximum size of cache to SIZE (use 0 for no\n" " limit; available suffixes: G, M and K; default\n" " suffix: G)\n" " -s, --show-stats show statistics summary\n" " -z, --zero-stats zero statistics counters\n" "\n" " -h, --help print this help text\n" " -V, --version print version and copyright information\n" "\n" "See also .\n"; /* current working directory taken from $PWD, or getcwd() if $PWD is bad */ static char *current_working_dir; /* the base cache directory */ char *cache_dir = NULL; /* the directory for temporary files */ static char *temp_dir; /* the debug logfile name, if set */ char *cache_logfile = NULL; /* base directory (from CCACHE_BASEDIR) */ static char *base_dir; /* the original argument list */ static ARGS *orig_args; /* the source file */ static char *input_file; /* The output file being compiled to. */ static char *output_obj; /* The path to the dependency file (implicit or specified with -MF). */ static char *output_dep; /* * Name (represented as a struct file_hash) of the file containing the cached * object code. */ static struct file_hash *cached_obj_hash; /* * Full path to the file containing the cached object code * (cachedir/a/b/cdef[...]-size.o). */ static char *cached_obj; /* * Full path to the file containing the standard error output * (cachedir/a/b/cdef[...]-size.stderr). */ static char *cached_stderr; /* * Full path to the file containing the dependency information * (cachedir/a/b/cdef[...]-size.d). */ static char *cached_dep; /* * Full path to the file containing the manifest * (cachedir/a/b/cdef[...]-size.manifest). */ static char *manifest_path; /* * Time of compilation. Used to see if include files have changed after * compilation. */ static time_t time_of_compilation; /* * Files included by the preprocessor and their hashes/sizes. Key: file path. * Value: struct file_hash. */ static struct hashtable *included_files; /* is gcc being asked to output dependencies? */ static int generating_dependencies; /* the extension of the file after pre-processing */ static const char *i_extension; /* the name of the temporary pre-processor file */ static char *i_tmpfile; /* are we compiling a .i or .ii file directly? */ static int direct_i_file; /* the name of the cpp stderr file */ static char *cpp_stderr; /* * Full path to the statistics file in the subdirectory where the cached result * belongs (CCACHE_DIR/X/stats). */ char *stats_file = NULL; /* can we safely use the unification hashing backend? */ static int enable_unify; /* should we use the direct mode? */ static int enable_direct = 1; /* number of levels (1 <= nlevels <= 8) */ static int nlevels = 2; /* * Whether we should use the optimization of passing the already existing * preprocessed source code to the compiler. */ static int compile_preprocessed_source_code; /* a list of supported file extensions, and the equivalent extension for code that has been through the pre-processor */ static const struct { const char *extension; const char *i_extension; } extensions[] = { {"c", "i"}, {"C", "ii"}, {"m", "mi"}, {"cc", "ii"}, {"CC", "ii"}, {"cpp", "ii"}, {"CPP", "ii"}, {"cxx", "ii"}, {"CXX", "ii"}, {"c++", "ii"}, {"C++", "ii"}, {"i", "i"}, {"ii", "ii"}, {NULL, NULL}}; enum fromcache_call_mode { FROMCACHE_DIRECT_MODE, FROMCACHE_CPP_MODE, FROMCACHE_COMPILED_MODE }; /* * This is a string that identifies the current "version" of the hash sum * computed by ccache. If, for any reason, we want to force the hash sum to be * different for the same input in a new ccache version, we can just change * this string. A typical example would be if the format of one of the files * stored in the cache changes in a backwards-incompatible way. */ static const char HASH_PREFIX[] = "3"; /* something went badly wrong - just execute the real compiler */ static void failed(void) { char *e; /* delete intermediate pre-processor file if needed */ if (i_tmpfile) { if (!direct_i_file) { unlink(i_tmpfile); } free(i_tmpfile); i_tmpfile = NULL; } /* delete the cpp stderr file if necessary */ if (cpp_stderr) { unlink(cpp_stderr); free(cpp_stderr); cpp_stderr = NULL; } /* strip any local args */ args_strip(orig_args, "--ccache-"); if ((e=getenv("CCACHE_PREFIX"))) { char *p = find_executable(e, MYNAME); if (!p) { perror(e); exit(1); } args_add_prefix(orig_args, p); } cc_log("Failed; falling back to running the real compiler"); if (getenv("CCACHE_VERBOSE")) { print_executed_command(orig_args->argv); } execv(orig_args->argv[0], orig_args->argv); cc_log("execv returned (%s)!", strerror(errno)); perror(orig_args->argv[0]); exit(1); } /* * Transform a name to a full path into the cache directory, creating needed * sublevels if needed. Caller frees. */ static char *get_path_in_cache(const char *name, const char *suffix) { int i; char *path; char *result; path = x_strdup(cache_dir); for (i = 0; i < nlevels; ++i) { char *p; x_asprintf(&p, "%s/%c", path, name[i]); free(path); path = p; if (create_dir(path) != 0) { cc_log("Failed to create %s", path); failed(); } } x_asprintf(&result, "%s/%s%s", path, name + nlevels, suffix); free(path); return result; } /* * This function hashes an include file and stores the path and hash in the * global included_files variable. Takes over ownership of path. */ static void remember_include_file(char *path, size_t path_len) { struct file_hash *h; struct mdfour fhash; struct stat st; int fd = -1; char *data = (char *)-1; int result; if (!included_files) { goto ignore; } if (path_len >= 2 && (path[0] == '<' && path[path_len - 1] == '>')) { /* Typically or . */ goto ignore; } if (strcmp(path, input_file) == 0) { /* Don't remember the input file. */ goto ignore; } if (hashtable_search(included_files, path)) { /* Already known include file. */ goto ignore; } /* Let's hash the include file. */ fd = open(path, O_RDONLY|O_BINARY); if (fd == -1) { cc_log("Failed to open include file %s", path); goto failure; } if (fstat(fd, &st) != 0) { cc_log("Failed to fstat include file %s", path); goto failure; } if (S_ISDIR(st.st_mode)) { /* Ignore directory, typically $PWD. */ goto ignore; } if (st.st_mtime >= time_of_compilation) { cc_log("Include file %s too new", path); goto failure; } data = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); if (data == (char *)-1) { cc_log("Failed to mmap %s", path); goto failure; } hash_start(&fhash); result = hash_source_code_string(&fhash, data, st.st_size, path); if (result & HASH_SOURCE_CODE_ERROR || result & HASH_SOURCE_CODE_FOUND_TIME) { goto failure; } h = x_malloc(sizeof(*h)); hash_result_as_bytes(&fhash, h->hash); h->size = fhash.totalN; hashtable_insert(included_files, path, h); munmap(data, st.st_size); return; failure: cc_log("Disabling direct mode"); enable_direct = 0; /* Fall through. */ ignore: free(path); if (data != (char *)-1) { munmap(data, st.st_size); } if (fd != -1) { close(fd); } } /* * Make a relative path from CCACHE_BASEDIR to path. Takes over ownership of * path. Caller frees. */ static char *make_relative_path(char *path) { char *relpath; if (!base_dir || strncmp(path, base_dir, strlen(base_dir)) != 0) { return path; } relpath = get_relative_path(current_working_dir, path); free(path); return relpath; } /* * This function reads and hashes a file. While doing this, it also does these * things: * * - Makes include file paths whose prefix is CCACHE_BASEDIR relative when * computing the hash sum. * - Stores the paths and hashes of included files in the global variable * included_files. */ static int process_preprocessed_file(struct mdfour *hash, const char *path) { int fd; char *data; char *p, *q, *end; off_t size; struct stat st; fd = open(path, O_RDONLY); if (fd == -1) { cc_log("Failed to open %s", path); return 0; } if (fstat(fd, &st) != 0) { cc_log("Failed to fstat %s", path); return 0; } size = st.st_size; data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); if (data == (void *)-1) { cc_log("Failed to mmap %s", path); return 0; } if (enable_direct) { included_files = create_hashtable(1000, hash_from_string, strings_equal); } /* Bytes between p and q are pending to be hashed. */ end = data + size; p = data; q = data; while (q < end - 1) { if (q[0] == '#' && q[1] == ' ' /* Need to avoid "#pragma"... */ && (q == data || q[-1] == '\n')) { char *path; while (q < end && *q != '"') { q++; } q++; if (q >= end) { cc_log("Failed to parse included file path"); munmap(data, size); return 0; } /* q points to the beginning of an include file path */ hash_buffer(hash, p, q - p); p = q; while (q < end && *q != '"') { q++; } /* p and q span the include file path */ path = x_strndup(p, q - p); path = make_relative_path(path); hash_string(hash, path); if (enable_direct) { remember_include_file(path, q - p); } else { free(path); } p = q; } else { q++; } } hash_buffer(hash, p, (end - p)); munmap(data, size); return 1; } /* run the real compiler and put the result in cache */ static void to_cache(ARGS *args) { char *tmp_stdout, *tmp_stderr, *tmp_obj; struct stat st; int status; int compress; x_asprintf(&tmp_stdout, "%s.tmp.stdout.%s", cached_obj, tmp_string()); x_asprintf(&tmp_stderr, "%s.tmp.stderr.%s", cached_obj, tmp_string()); x_asprintf(&tmp_obj, "%s.tmp.%s", cached_obj, tmp_string()); args_add(args, "-o"); args_add(args, tmp_obj); /* Turn off DEPENDENCIES_OUTPUT when running cc1, because * otherwise it will emit a line like * * tmp.stdout.vexed.732.o: /home/mbp/.ccache/tmp.stdout.vexed.732.i * * unsetenv() is on BSD and Linux but not portable. */ putenv("DEPENDENCIES_OUTPUT"); if (compile_preprocessed_source_code) { args_add(args, i_tmpfile); } else { args_add(args, input_file); } cc_log("Running real compiler"); status = execute(args->argv, tmp_stdout, tmp_stderr); args_pop(args, 3); if (stat(tmp_stdout, &st) != 0 || st.st_size != 0) { cc_log("Compiler produced stdout"); stats_update(STATS_STDOUT); unlink(tmp_stdout); unlink(tmp_stderr); unlink(tmp_obj); failed(); } unlink(tmp_stdout); /* * Merge stderr from the preprocessor (if any) and stderr from the real * compiler into tmp_stderr. */ if (cpp_stderr) { int fd_cpp_stderr; int fd_real_stderr; int fd_result; fd_cpp_stderr = open(cpp_stderr, O_RDONLY | O_BINARY); if (fd_cpp_stderr == -1) { cc_log("Failed opening %s", cpp_stderr); failed(); } fd_real_stderr = open(tmp_stderr, O_RDONLY | O_BINARY); if (fd_real_stderr == -1) { cc_log("Failed opening %s", tmp_stderr); failed(); } unlink(tmp_stderr); fd_result = open(tmp_stderr, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666); if (fd_result == -1) { cc_log("Failed opening %s", tmp_stderr); failed(); } copy_fd(fd_cpp_stderr, fd_result); copy_fd(fd_real_stderr, fd_result); close(fd_cpp_stderr); close(fd_real_stderr); close(fd_result); unlink(cpp_stderr); free(cpp_stderr); cpp_stderr = NULL; } if (status != 0) { int fd; cc_log("Compiler gave exit status %d", status); stats_update(STATS_STATUS); fd = open(tmp_stderr, O_RDONLY | O_BINARY); if (fd != -1) { if (strcmp(output_obj, "/dev/null") == 0 || move_file(tmp_obj, output_obj, 0) == 0 || errno == ENOENT) { /* we can use a quick method of getting the failed output */ copy_fd(fd, 2); close(fd); unlink(tmp_stderr); if (i_tmpfile && !direct_i_file) { unlink(i_tmpfile); } exit(status); } } unlink(tmp_stderr); unlink(tmp_obj); failed(); } if (stat(tmp_obj, &st) != 0) { cc_log("Compiler didn't produce an object file"); stats_update(STATS_NOOUTPUT); failed(); } if (st.st_size == 0) { cc_log("Compiler produced an empty object file"); stats_update(STATS_EMPTYOUTPUT); failed(); } compress = getenv("CCACHE_COMPRESS") ? 1 : 0; if (stat(tmp_stderr, &st) != 0) { cc_log("Failed to stat %s", tmp_stderr); stats_update(STATS_ERROR); failed(); } if (st.st_size > 0) { if (move_file(tmp_stderr, cached_stderr, compress) != 0) { cc_log("Failed to move %s to %s", tmp_stderr, cached_stderr); stats_update(STATS_ERROR); failed(); } cc_log("Stored in cache: %s", cached_stderr); } else { unlink(tmp_stderr); } if (move_file(tmp_obj, cached_obj, compress) != 0) { cc_log("Failed to move %s to %s", tmp_obj, cached_obj); stats_update(STATS_ERROR); failed(); } else { cc_log("Stored in cache: %s", cached_obj); } /* * Do an extra stat on the potentially compressed object file for the * size statistics. */ if (stat(cached_obj, &st) != 0) { cc_log("Failed to stat %s", strerror(errno)); stats_update(STATS_ERROR); failed(); } stats_tocache(file_size(&st)); free(tmp_obj); free(tmp_stderr); free(tmp_stdout); } /* * Find the object file name by running the compiler in preprocessor mode. * Returns the hash as a heap-allocated hex string. */ static struct file_hash * get_object_name_from_cpp(ARGS *args, struct mdfour *hash) { char *input_base; char *tmp; char *path_stdout, *path_stderr; int status; struct file_hash *result; /* ~/hello.c -> tmp.hello.123.i limit the basename to 10 characters in order to cope with filesystem with small maximum filename length limits */ input_base = basename(input_file); tmp = strchr(input_base, '.'); if (tmp != NULL) { *tmp = 0; } if (strlen(input_base) > 10) { input_base[10] = 0; } /* now the run */ x_asprintf(&path_stdout, "%s/%s.tmp.%s.%s", temp_dir, input_base, tmp_string(), i_extension); x_asprintf(&path_stderr, "%s/tmp.cpp_stderr.%s", temp_dir, tmp_string()); time_of_compilation = time(NULL); if (!direct_i_file) { /* run cpp on the input file to obtain the .i */ args_add(args, "-E"); args_add(args, input_file); status = execute(args->argv, path_stdout, path_stderr); args_pop(args, 2); } else { /* we are compiling a .i or .ii file - that means we can skip the cpp stage and directly form the correct i_tmpfile */ path_stdout = input_file; if (create_empty_file(path_stderr) != 0) { stats_update(STATS_ERROR); cc_log("Failed to create %s", path_stderr); failed(); } status = 0; } if (status != 0) { if (!direct_i_file) { unlink(path_stdout); } unlink(path_stderr); cc_log("Preprocessor gave exit status %d", status); stats_update(STATS_PREPROCESSOR); failed(); } /* if the compilation is with -g then we have to include the whole of the preprocessor output, which means we are sensitive to line number information. Otherwise we can discard line number info, which makes us less sensitive to reformatting changes Note! I have now disabled the unification code by default as it gives the wrong line numbers for warnings. Pity. */ if (!enable_unify) { if (!process_preprocessed_file(hash, path_stdout)) { stats_update(STATS_ERROR); unlink(path_stderr); failed(); } } else { if (unify_hash(hash, path_stdout) != 0) { stats_update(STATS_ERROR); unlink(path_stderr); cc_log("Failed to unify %s", path_stdout); failed(); } } if (!hash_file(hash, path_stderr)) { fatal("Failed to open %s", path_stderr); } hash_delimiter(hash); i_tmpfile = path_stdout; if (compile_preprocessed_source_code) { /* * If we are using the CPP trick, we need to remember this * stderr data and output it just before the main stderr from * the compiler pass. */ cpp_stderr = path_stderr; } else { unlink(path_stderr); free(path_stderr); } result = x_malloc(sizeof(*result)); hash_result_as_bytes(hash, result->hash); result->size = hash->totalN; return result; } static void update_cached_result_globals(struct file_hash *hash) { char *object_name; object_name = format_hash_as_string(hash->hash, hash->size); cached_obj_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"); x_asprintf(&stats_file, "%s/%c/stats", cache_dir, object_name[0]); free(object_name); } /* * Update a hash sum with information common for the direct and preprocessor * modes. */ static void calculate_common_hash(ARGS *args, struct mdfour *hash) { struct stat st; const char *compilercheck; char *p; hash_string(hash, HASH_PREFIX); hash_delimiter(hash); /* * When we are doing the unifying tricks we need to include the input * file name in the hash to get the warnings right. */ if (enable_unify) { hash_string(hash, input_file); } hash_delimiter(hash); /* * We have to hash the extension, as a .i file isn't treated the same * by the compiler as a .ii file. */ hash_string(hash, i_extension); hash_delimiter(hash); if (stat(args->argv[0], &st) != 0) { cc_log("Couldn't stat the compiler (%s)", args->argv[0]); stats_update(STATS_COMPILER); failed(); } /* * Hash information about the compiler. */ compilercheck = getenv("CCACHE_COMPILERCHECK"); if (!compilercheck) { compilercheck = "mtime"; } if (strcmp(compilercheck, "none") == 0) { /* Do nothing. */ } else if (strcmp(compilercheck, "content") == 0) { hash_file(hash, args->argv[0]); } else { /* mtime */ hash_int(hash, st.st_size); hash_int(hash, st.st_mtime); } hash_delimiter(hash); /* * Also hash the compiler name as some compilers use hard links and * behave differently depending on the real name. */ hash_string(hash, basename(args->argv[0])); hash_delimiter(hash); /* Possibly hash the current working directory. */ if (getenv("CCACHE_HASHDIR")) { char *cwd = gnu_getcwd(); if (cwd) { hash_string(hash, cwd); free(cwd); } } hash_delimiter(hash); p = getenv("CCACHE_EXTRAFILES"); if (p) { char *path, *q; p = x_strdup(p); q = p; while ((path = strtok(q, " \t\r\n"))) { cc_log("Hashing extra file %s", path); if (!hash_file(hash, path)) { stats_update(STATS_BADEXTRAFILE); failed(); } hash_delimiter(hash); q = NULL; } free(p); } } /* * Update a hash sum with information specific to the direct and preprocessor * modes and calculate the object hash. Returns the object hash on success, * otherwise NULL. Caller frees. */ static struct file_hash *calculate_object_hash( ARGS *args, struct mdfour *hash, int direct_mode) { int i; char *manifest_name; struct stat st; int result; struct file_hash *object_hash = NULL; /* first the arguments */ for (i=1;iargc;i++) { /* -L doesn't affect compilation. */ if (i < args->argc-1 && strcmp(args->argv[i], "-L") == 0) { i++; continue; } if (strncmp(args->argv[i], "-L", 2) == 0) { continue; } /* When using the preprocessor, some arguments don't contribute to the hash. The theory is that these arguments will change the output of -E if they are going to have any effect at all. */ if (!direct_mode) { if (i < args->argc-1) { if (strcmp(args->argv[i], "-I") == 0 || strcmp(args->argv[i], "-imacros") == 0 || strcmp(args->argv[i], "-include") == 0 || strcmp(args->argv[i], "-D") == 0 || strcmp(args->argv[i], "-iprefix") == 0 || strcmp(args->argv[i], "-iwithprefix") == 0 || strcmp(args->argv[i], "-iwithprefixbefore") == 0 || strcmp(args->argv[i], "-idirafter") == 0 || strcmp(args->argv[i], "-isystem") == 0 || strcmp(args->argv[i], "-nostdinc") == 0 || strcmp(args->argv[i], "-nostdinc++") == 0 || strcmp(args->argv[i], "-U") == 0) { /* Skip from hash. */ i++; continue; } } if (strncmp(args->argv[i], "-I", 2) == 0 || strncmp(args->argv[i], "-D", 2) == 0) { /* Skip from hash. */ continue; } } if (strncmp(args->argv[i], "--specs=", 8) == 0 && stat(args->argv[i] + 8, &st) == 0) { /* If given a explicit specs file, then hash that file, but don't include the path to it in the hash. */ if (!hash_file(hash, args->argv[i] + 8)) { failed(); } hash_delimiter(hash); continue; } /* All other arguments are included in the hash. */ hash_string(hash, args->argv[i]); hash_delimiter(hash); } if (direct_mode) { /* * The source code file or an include file may contain * __FILE__, so make sure that the hash is unique for the file * name. */ hash_string(hash, input_file); hash_delimiter(hash); result = hash_source_code_file(hash, input_file); if (result & HASH_SOURCE_CODE_ERROR) { failed(); } if (result & HASH_SOURCE_CODE_FOUND_TIME) { cc_log("Disabling direct mode"); enable_direct = 0; return NULL; } manifest_name = hash_result(hash); manifest_path = get_path_in_cache(manifest_name, ".manifest"); free(manifest_name); cc_log("Looking for object file hash in %s", manifest_path); object_hash = manifest_get(manifest_path); if (object_hash) { cc_log("Got object file hash from manifest"); } else { cc_log("Did not find object file hash in manifest"); } } else { object_hash = get_object_name_from_cpp(args, hash); cc_log("Got object file hash from preprocessor"); if (generating_dependencies) { cc_log("Preprocessor created %s", output_dep); } } return object_hash; } /* try to return the compile result from cache. If we can return from cache then this function exits with the correct status code, otherwise it returns */ static void from_cache(enum fromcache_call_mode mode, int put_object_in_manifest) { int fd_stderr; int ret; struct stat st; int produce_dep_file; /* the user might be disabling cache hits */ if (mode != FROMCACHE_COMPILED_MODE && getenv("CCACHE_RECACHE")) { return; } /* Check if the object file is there. */ if (stat(cached_obj, &st) != 0) { cc_log("Object file %s not in cache", cached_obj); return; } /* * (If mode != FROMCACHE_DIRECT_MODE, the dependency file is created by * gcc.) */ produce_dep_file = \ generating_dependencies && mode == FROMCACHE_DIRECT_MODE; /* If the dependency file should be in the cache, check that it is. */ if (produce_dep_file && stat(cached_dep, &st) != 0) { cc_log("Dependency file %s missing in cache", cached_dep); return; } if (strcmp(output_obj, "/dev/null") == 0) { ret = 0; } else { unlink(output_obj); /* only make a hardlink if the cache file is uncompressed */ if (getenv("CCACHE_HARDLINK") && test_if_compressed(cached_obj) == 0) { ret = link(cached_obj, output_obj); } else { ret = copy_file(cached_obj, output_obj, 0); } } if (ret == -1) { if (errno == ENOENT) { /* Someone removed the file just before we began copying? */ cc_log("Object file %s just disappeared from cache", cached_obj); stats_update(STATS_MISSING); } else { cc_log("Failed to copy/link %s to %s (%s)", cached_obj, output_obj, strerror(errno)); stats_update(STATS_ERROR); failed(); } unlink(output_obj); unlink(cached_stderr); unlink(cached_obj); unlink(cached_dep); return; } else { cc_log("Created %s from %s", output_obj, cached_obj); } if (produce_dep_file) { unlink(output_dep); /* only make a hardlink if the cache file is uncompressed */ if (getenv("CCACHE_HARDLINK") && test_if_compressed(cached_dep) == 0) { ret = link(cached_dep, output_dep); } else { ret = copy_file(cached_dep, output_dep, 0); } if (ret == -1) { if (errno == ENOENT) { /* * Someone removed the file just before we * began copying? */ cc_log("Dependency file %s just disappeared" " from cache", output_obj); stats_update(STATS_MISSING); } else { cc_log("Failed to copy/link %s to %s (%s)", cached_dep, output_dep, strerror(errno)); stats_update(STATS_ERROR); failed(); } unlink(output_obj); unlink(output_dep); unlink(cached_stderr); unlink(cached_obj); unlink(cached_dep); return; } else { cc_log("Created %s from %s", output_dep, cached_dep); } } /* Update modification timestamps to save files from LRU cleanup. Also gives files a sensible mtime when hard-linking. */ update_mtime(cached_obj); update_mtime(cached_stderr); if (produce_dep_file) { update_mtime(cached_dep); } if (generating_dependencies && mode != FROMCACHE_DIRECT_MODE) { /* Store the dependency file in the cache. */ ret = copy_file(output_dep, cached_dep, 1); if (ret == -1) { cc_log("Failed to copy %s to %s", output_dep, cached_dep); /* Continue despite the error. */ } else { cc_log("Stored in cache: %s", cached_dep); } } /* get rid of the intermediate preprocessor file */ if (i_tmpfile) { if (!direct_i_file) { unlink(i_tmpfile); } free(i_tmpfile); i_tmpfile = NULL; } /* Delete the cpp stderr file if necessary. */ if (cpp_stderr) { unlink(cpp_stderr); free(cpp_stderr); cpp_stderr = NULL; } /* Send the stderr, if any. */ fd_stderr = open(cached_stderr, O_RDONLY | O_BINARY); if (fd_stderr != -1) { copy_fd(fd_stderr, 2); close(fd_stderr); } /* Create or update the manifest file. */ if (enable_direct && put_object_in_manifest && included_files && !getenv("CCACHE_READONLY")) { if (manifest_put(manifest_path, cached_obj_hash, included_files)) { cc_log("Added object file hash to %s", manifest_path); update_mtime(manifest_path); } else { cc_log("Failed to add object file hash to %s", manifest_path); } } /* log the cache hit */ switch (mode) { case FROMCACHE_DIRECT_MODE: cc_log("Succeded getting cached result"); stats_update(STATS_CACHEHIT_DIR); break; case FROMCACHE_CPP_MODE: cc_log("Succeded getting cached result"); stats_update(STATS_CACHEHIT_CPP); break; case FROMCACHE_COMPILED_MODE: break; } /* and exit with the right status code */ exit(0); } /* find the real compiler. We just search the PATH to find a executable of the same name that isn't a link to ourselves */ static void find_compiler(int argc, char **argv) { char *base; char *path; orig_args = args_init(argc, argv); base = basename(argv[0]); /* we might be being invoked like "ccache gcc -c foo.c" */ if (strcmp(base, MYNAME) == 0) { args_remove_first(orig_args); free(base); if (strchr(argv[1],'/')) { /* a full path was given */ return; } base = basename(argv[1]); } /* support user override of the compiler */ if ((path=getenv("CCACHE_CC"))) { base = strdup(path); } orig_args->argv[0] = find_executable(base, MYNAME); /* can't find the compiler! */ if (!orig_args->argv[0]) { stats_update(STATS_COMPILER); perror(base); exit(1); } } /* check a filename for C/C++ extension. Return the pre-processor extension */ static const char *check_extension(const char *fname, int *direct_i) { int i; const char *p; if (direct_i) { *direct_i = 0; } p = strrchr(fname, '.'); if (!p) return NULL; p++; for (i=0; extensions[i].extension; i++) { if (strcmp(p, extensions[i].extension) == 0) { if (direct_i && strcmp(p, extensions[i].i_extension) == 0) { *direct_i = 1; } p = getenv("CCACHE_EXTENSION"); if (p) return p; return extensions[i].i_extension; } } return NULL; } /* process the compiler options to form the correct set of options for obtaining the preprocessor output */ static void process_args(int argc, char **argv, ARGS **preprocessor_args, ARGS **compiler_args) { int i; int found_c_opt = 0; int found_S_opt = 0; struct stat st; /* is the dependency makefile name overridden with -MF? */ int dependency_filename_specified = 0; /* is the dependency makefile target name specified with -MT or -MQ? */ int dependency_target_specified = 0; ARGS *stripped_args; char *input_charset = NULL; stripped_args = args_init(0, NULL); args_add(stripped_args, argv[0]); for (i=1; iargc, stripped_args->argv); args_add(*preprocessor_args, input_charset); } else { *preprocessor_args = stripped_args; } *compiler_args = stripped_args; } /* the main ccache driver function */ static void ccache(int argc, char *argv[]) { char now[64]; time_t t; struct tm *tm; int put_object_in_manifest = 0; struct file_hash *object_hash; struct file_hash *object_hash_from_manifest = NULL; char *env; struct mdfour common_hash; struct mdfour direct_hash; struct mdfour cpp_hash; /* Arguments (except -E) to send to the preprocessor. */ ARGS *preprocessor_args; /* Arguments to send to the real compiler. */ ARGS *compiler_args; t = time(NULL); tm = localtime(&t); if (!tm) { cc_log("localtime failed"); failed(); } if (strftime(now, sizeof(now), "%Y-%m-%d %H:%M:%S", tm) == 0) { cc_log("strftime failed"); failed(); } cc_log("=== %s ===", now); if (base_dir) { cc_log("Base directory: %s", base_dir); } /* find the real compiler */ find_compiler(argc, argv); /* use the real compiler if HOME is not set */ if (!cache_dir) { cc_log("Unable to determine home directory"); cc_log("ccache is disabled"); failed(); } /* we might be disabled */ if (getenv("CCACHE_DISABLE")) { cc_log("ccache is disabled"); failed(); } if (getenv("CCACHE_UNIFY")) { enable_unify = 1; } if (getenv("CCACHE_NODIRECT") || enable_unify) { cc_log("Direct mode disabled"); enable_direct = 0; } if ((env = getenv("CCACHE_NLEVELS"))) { nlevels = atoi(env); if (nlevels < 1) nlevels = 1; if (nlevels > 8) nlevels = 8; } /* * Process argument list, returning a new set of arguments to pass to * the preprocessor and the real compiler. */ process_args(orig_args->argc, orig_args->argv, &preprocessor_args, &compiler_args); cc_log("Source file: %s", input_file); if (generating_dependencies) { cc_log("Dependency file: %s", output_dep); } cc_log("Object file: %s", output_obj); hash_start(&common_hash); calculate_common_hash(preprocessor_args, &common_hash); /* try to find the hash using the manifest */ direct_hash = common_hash; if (enable_direct) { cc_log("Trying direct lookup"); object_hash = calculate_object_hash( preprocessor_args, &direct_hash, 1); if (object_hash) { update_cached_result_globals(object_hash); /* * If we can return from cache at this point then do * so. */ from_cache(FROMCACHE_DIRECT_MODE, 0); /* * Wasn't able to return from cache at this point. * However, the object was already found in manifest, * so don't readd it later. */ put_object_in_manifest = 0; object_hash_from_manifest = object_hash; } else { /* Add object to manifest later. */ put_object_in_manifest = 1; } } /* * Find the hash using the preprocessed output. Also updates * included_files. */ cpp_hash = common_hash; cc_log("Running preprocessor"); object_hash = calculate_object_hash(preprocessor_args, &cpp_hash, 0); if (!object_hash) { fatal("internal error: object hash from cpp returned NULL"); } update_cached_result_globals(object_hash); if (object_hash_from_manifest && !file_hashes_equal(object_hash_from_manifest, object_hash)) { /* * The hash from manifest differs from the hash of the * preprocessor output. This could be because: * * - The preprocessor produces different output for the same * input (not likely). * - There's a bug in ccache (maybe incorrect handling of * compiler arguments). * - The user has used a different CCACHE_BASEDIR (most * likely). * * The best thing here would probably be to remove the hash * entry from the manifest. For now, we use a simpler method: * just remove the manifest file. */ cc_log("Hash from manifest doesn't match preprocessor output"); cc_log("Likely reason: different CCACHE_BASEDIRs used"); cc_log("Removing manifest as a safety measure"); unlink(manifest_path); put_object_in_manifest = 1; } /* if we can return from cache at this point then do */ from_cache(FROMCACHE_CPP_MODE, put_object_in_manifest); if (getenv("CCACHE_READONLY")) { cc_log("Read-only mode; running real compiler"); failed(); } env = getenv("CCACHE_PREFIX"); if (env) { char *p = find_executable(env, MYNAME); if (!p) { perror(env); exit(1); } cc_log("Using command-line prefix %s", env); args_add_prefix(compiler_args, p); } /* run real compiler, sending output to cache */ to_cache(compiler_args); /* return from cache */ from_cache(FROMCACHE_COMPILED_MODE, put_object_in_manifest); /* oh oh! */ cc_log("Secondary from_cache failed"); stats_update(STATS_ERROR); failed(); } static void check_cache_dir(void) { if (!cache_dir) { fatal("Unable to determine cache directory"); } } /* the main program when not doing a compile */ static int ccache_main(int argc, char *argv[]) { int c; size_t v; static const struct option long_options[] = { {"show-stats", no_argument, 0, 's'}, {"zero-stats", no_argument, 0, 'z'}, {"cleanup", no_argument, 0, 'c'}, {"clear", no_argument, 0, 'C'}, {"max-files", required_argument, 0, 'F'}, {"max-size", required_argument, 0, 'M'}, {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, {0, 0, 0, 0} }; int option_index = 0; while ((c = getopt_long(argc, argv, "hszcCF:M:V", long_options, &option_index)) != -1) { switch (c) { case 'V': fputs(VERSION_TEXT, stdout); exit(0); case 'h': fputs(USAGE_TEXT, stdout); exit(0); case 's': check_cache_dir(); stats_summary(); break; case 'c': check_cache_dir(); cleanup_all(cache_dir); printf("Cleaned cache\n"); break; case 'C': check_cache_dir(); wipe_all(cache_dir); printf("Cleared cache\n"); break; case 'z': check_cache_dir(); stats_zero(); printf("Statistics cleared\n"); break; case 'F': check_cache_dir(); v = atoi(optarg); if (stats_set_limits(v, -1) == 0) { if (v == 0) { printf("Unset cache file limit\n"); } else { printf("Set cache file limit to %u\n", (unsigned)v); } } else { printf("Could not set cache file limit.\n"); exit(1); } break; case 'M': check_cache_dir(); v = value_units(optarg); if (stats_set_limits(-1, v) == 0) { if (v == 0) { printf("Unset cache size limit\n"); } else { char *s = format_size(v); printf("Set cache size limit to %s\n", s); free(s); } } else { printf("Could not set cache size limit.\n"); exit(1); } break; default: fputs(USAGE_TEXT, stderr); exit(1); } } return 0; } /* Make a copy of stderr that will not be cached, so things like distcc can send networking errors to it. */ static void setup_uncached_err(void) { char *buf; int uncached_fd; uncached_fd = dup(2); if (uncached_fd == -1) { cc_log("dup(2) failed"); failed(); } /* leak a pointer to the environment */ x_asprintf(&buf, "UNCACHED_ERR_FD=%d", uncached_fd); if (putenv(buf) == -1) { cc_log("putenv failed"); failed(); } } int main(int argc, char *argv[]) { char *p; char *program_name; /* the user might have set CCACHE_UMASK */ p = getenv("CCACHE_UMASK"); if (p) { mode_t mask; errno = 0; mask = strtol(p, NULL, 8); if (errno == 0) { umask(mask); } } current_working_dir = get_cwd(); cache_dir = getenv("CCACHE_DIR"); if (!cache_dir) { const char *home_directory = get_home_directory(); if (home_directory) { x_asprintf(&cache_dir, "%s/.ccache", home_directory); } } /* check if we are being invoked as "ccache" */ program_name = basename(argv[0]); if (strcmp(program_name, MYNAME) == 0) { if (argc < 2) { fputs(USAGE_TEXT, stderr); exit(1); } /* if the first argument isn't an option, then assume we are being passed a compiler name and options */ if (argv[1][0] == '-') { return ccache_main(argc, argv); } } free(program_name); check_cache_dir(); temp_dir = getenv("CCACHE_TEMPDIR"); if (!temp_dir) { x_asprintf(&temp_dir, "%s/tmp", cache_dir); } cache_logfile = getenv("CCACHE_LOGFILE"); base_dir = getenv("CCACHE_BASEDIR"); if (base_dir && base_dir[0] != '/') { cc_log("Ignoring non-absolute base directory %s", base_dir); base_dir = NULL; } compile_preprocessed_source_code = !getenv("CCACHE_CPP2"); setup_uncached_err(); /* make sure the cache dir exists */ if (create_dir(cache_dir) != 0) { fprintf(stderr,"ccache: failed to create %s (%s)\n", cache_dir, strerror(errno)); exit(1); } /* make sure the temp dir exists */ if (create_dir(temp_dir) != 0) { fprintf(stderr,"ccache: failed to create %s (%s)\n", temp_dir, strerror(errno)); exit(1); } if (!getenv("CCACHE_READONLY")) { if (create_cachedirtag(cache_dir) != 0) { fprintf(stderr,"ccache: failed to create %s/CACHEDIR.TAG (%s)\n", cache_dir, strerror(errno)); exit(1); } } ccache(argc, argv); return 1; }