/tags/rel-1.3.39/CCache/ccache.c
# · C · 1388 lines · 1016 code · 188 blank · 184 comment · 309 complexity · d9eb4c55515cac618555479180b7bbac MD5 · raw file
- /*
- a re-implementation of the compilercache scripts in C
- The idea is based on the shell-script compilercache by Erik Thiele <erikyyy@erikyyy.de>
- Copyright (C) Andrew Tridgell 2002
- Copyright (C) Martin Pool 2003
-
- 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
- #include "ccache.h"
- /* verbose mode */
- int ccache_verbose = 0;
- /* the base cache directory */
- char *cache_dir = NULL;
- /* the directory for temporary files */
- static char *temp_dir = NULL;
- /* the debug logfile name, if set */
- char *cache_logfile = NULL;
- /* the argument list after processing */
- static ARGS *stripped_args;
- /* the original argument list */
- static ARGS *orig_args;
- /* the output filename being compiled to */
- static char *output_file;
- /* the source file */
- static char *input_file;
- /* the name of the file containing the cached object code */
- static char *hashname;
- /* 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;
- /* the name of the statistics file */
- char *stats_file = NULL;
- /* can we safely use the unification hashing backend? */
- static int enable_unify;
- /* should we strip -c when running the preprocessor only? */
- static int strip_c_option;
- /* customisation for using the SWIG compiler */
- static int swig;
- /* a list of supported file extensions, and the equivalent
- extension for code that has been through the pre-processor
- */
- static struct {
- char *extension;
- 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}};
- /*
- 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) {
- cc_log("could not find executable (%s)\n", e);
- perror(e);
- exit(1);
- }
- args_add_prefix(orig_args, p);
- }
- if (ccache_verbose) {
- display_execute_args(orig_args->argv);
- }
- if (swig) {
- putenv("CCACHE_OUTFILES");
- }
- #ifndef _WIN32
- execv(orig_args->argv[0], orig_args->argv);
- cc_log("execv returned (%s)!\n", strerror(errno));
- perror(orig_args->argv[0]);
- exit(1);
- #else
- /* execv on Windows causes the 'non-regular' testcase to fail, so use Win32 API instead */
- {
- PROCESS_INFORMATION pinfo;
- STARTUPINFO sinfo;
- BOOL ret;
- DWORD exitcode;
- char *args;
- ZeroMemory(&pinfo, sizeof(PROCESS_INFORMATION));
- ZeroMemory(&sinfo, sizeof(STARTUPINFO));
- sinfo.cb = sizeof(STARTUPINFO);
- args = argvtos(orig_args->argv);
- ret = CreateProcessA(orig_args->argv[0], args, NULL, NULL, TRUE, 0, NULL, NULL,
- &sinfo, &pinfo);
- if (!ret) {
- exitcode = 1;
- cc_log("CreateProcessA failed starting %s\n", orig_args->argv[0]);
- perror_win32(orig_args->argv[0]);
- } else {
- WaitForSingleObject(pinfo.hProcess, INFINITE);
- GetExitCodeProcess(pinfo.hProcess, &exitcode);
- CloseHandle(pinfo.hProcess);
- CloseHandle(pinfo.hThread);
- }
- free(args);
- exit(exitcode);
- }
- #endif
- }
- /* return a string to be used to distinguish temporary files
- this also tries to cope with NFS by adding the local hostname
- */
- static const char *tmp_string(void)
- {
- static char *ret;
- if (!ret) {
- char hostname[200];
- strcpy(hostname, "unknown");
- #if HAVE_GETHOSTNAME
- gethostname(hostname, sizeof(hostname)-1);
- #endif
- hostname[sizeof(hostname)-1] = 0;
- if (asprintf(&ret, "%s.%u", hostname, (unsigned)getpid()) == -1) {
- fatal("could not allocate tmp_string");
- }
- }
- return ret;
- }
- /* update cached file sizes and count helper function for to_cache() */
- static void to_cache_stats_helper(struct stat *pstat, char *cached_filename, char *tmp_outfiles, int *files_size, int *cached_files_count)
- {
- #if ENABLE_ZLIB
- /* do an extra stat on the cache file for the size statistics */
- if (stat(cached_filename, pstat) != 0) {
- cc_log("failed to stat cache files - %s\n", strerror(errno));
- stats_update(STATS_ERROR);
- if (tmp_outfiles) {
- unlink(tmp_outfiles);
- }
- failed();
- }
- #else
- (void)cached_filename;
- (void)tmp_outfiles;
- #endif
- (*files_size) += file_size(pstat);
- (*cached_files_count)++;
- }
- /* run the real compiler and put the result in cache */
- static void to_cache(ARGS *args)
- {
- char *path_stderr;
- char *tmp_stdout, *tmp_stderr, *tmp_outfiles;
- struct stat st1;
- int status;
- int cached_files_count = 0;
- int files_size = 0;
- x_asprintf(&tmp_stdout, "%s/tmp.stdout.%s", temp_dir, tmp_string());
- x_asprintf(&tmp_stderr, "%s/tmp.stderr.%s", temp_dir, tmp_string());
- x_asprintf(&tmp_outfiles, "%s/tmp.outfiles.%s", temp_dir, tmp_string());
- if (strip_c_option && !swig) {
- args_add(stripped_args, "-c");
- }
- if (output_file) {
- args_add(args, "-o");
- args_add(args, output_file);
- }
- /* 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");
- /* Give SWIG a filename for it to create and populate with a list of files that it generates */
- if (swig) {
- char *ccache_outfiles;
- x_asprintf(&ccache_outfiles, "CCACHE_OUTFILES=%s", tmp_outfiles);
- unlink(tmp_outfiles);
- if (getenv("CCACHE_OUTFILES") || putenv(ccache_outfiles) == -1) {
- cc_log("CCACHE_OUTFILES env variable already set or could not be set\n");
- stats_update(STATS_ERROR);
- failed();
- }
- }
-
- if (getenv("CCACHE_CPP2")) {
- args_add(args, input_file);
- } else {
- if (swig) {
- args_add(args, "-nopreprocess");
- }
- args_add(args, i_tmpfile);
- }
- status = execute(args->argv, tmp_stdout, tmp_stderr);
- args_pop(args, 3);
- if (stat(tmp_stdout, &st1) != 0 || st1.st_size != 0) {
- cc_log("compiler produced stdout for %s\n", input_file);
- stats_update(STATS_STDOUT);
- unlink(tmp_stdout);
- unlink(tmp_stderr);
- unlink(tmp_outfiles);
- if (!swig) unlink(output_file);
- failed();
- }
- unlink(tmp_stdout);
- if (status != 0) {
- int fd;
- cc_log("compile of %s gave status = %d\n", input_file, status);
- stats_update(STATS_STATUS);
- fd = open(tmp_stderr, O_RDONLY | O_BINARY);
- if (fd != -1) {
- if (cpp_stderr) {
- /* we might have some stderr from cpp */
- int fd2 = open(cpp_stderr, O_RDONLY | O_BINARY);
- if (fd2 != -1) {
- copy_fd(fd2, 2);
- close(fd2);
- unlink(cpp_stderr);
- cpp_stderr = NULL;
- }
- }
- /* 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_outfiles);
- if (!swig) unlink(output_file);
- failed();
- } else {
- int hardlink = (getenv("CCACHE_NOCOMPRESS") != 0) && (getenv("CCACHE_HARDLINK") != 0);
- if (swig) {
- /* read the list of generated files and copy each of them into the cache */
- FILE *file;
- file = fopen(tmp_outfiles, "r");
- if (file) {
- char out_filename[FILENAME_MAX + 1];
- char out_filename_cache[FILENAME_MAX + 1];
- while (fgets(out_filename, FILENAME_MAX, file)) {
- char *linefeed = strchr(out_filename, '\n');
- if (linefeed) {
- char *potential_cr = linefeed - 1;
- if (potential_cr >= out_filename && *potential_cr == '\r')
- *potential_cr = 0;
- *linefeed = 0;
- if (cached_files_count == 0) {
- strcpy(out_filename_cache, hashname);
- } else {
- sprintf(out_filename_cache, "%s.%d", hashname, cached_files_count);
- }
- if (commit_to_cache(out_filename, out_filename_cache, hardlink) != 0) {
- fclose(file);
- unlink(tmp_outfiles);
- failed();
- }
- to_cache_stats_helper(&st1, out_filename_cache, tmp_outfiles, &files_size, &cached_files_count);
- } else {
- cached_files_count = 0;
- break;
- }
- }
- fclose(file);
- if (cached_files_count == 0) {
- cc_log("failed to copy output files to cache - internal error\n");
- stats_update(STATS_ERROR);
- unlink(tmp_outfiles);
- failed();
- }
- /* also copy the (uncompressed) file containing the list of generated files into the cache */
- sprintf(out_filename_cache, "%s.outfiles", hashname);
- if (stat(tmp_outfiles, &st1) != 0 ||
- safe_rename(tmp_outfiles, out_filename_cache) != 0) {
- cc_log("failed to copy outfiles file to cache - %s\n", strerror(errno));
- stats_update(STATS_ERROR);
- unlink(tmp_outfiles);
- failed();
- }
- to_cache_stats_helper(&st1, out_filename_cache, tmp_outfiles, &files_size, &cached_files_count);
- unlink(tmp_outfiles);
- } else {
- cc_log("failed to open temp outfiles file - %s\n", strerror(errno));
- stats_update(STATS_ERROR);
- failed();
- }
- } else {
- if (commit_to_cache(output_file, hashname, hardlink) != 0) {
- failed();
- }
- to_cache_stats_helper(&st1, hashname, 0, &files_size, &cached_files_count);
- }
- }
- x_asprintf(&path_stderr, "%s.stderr", hashname);
- if (stat(tmp_stderr, &st1) != 0 ||
- move_file(tmp_stderr, path_stderr) != 0) {
- cc_log("failed to rename tmp files - %s\n", strerror(errno));
- stats_update(STATS_ERROR);
- failed();
- }
- to_cache_stats_helper(&st1, path_stderr, 0, &files_size, &cached_files_count);
- cc_log("Placed %d files for %s into cache\n", cached_files_count, input_file);
- stats_tocache(files_size, cached_files_count);
- free(tmp_stderr);
- free(tmp_stdout);
- free(tmp_outfiles);
- free(path_stderr);
- }
- /* find the hash for a command. The hash includes all argument lists,
- plus the output from running the compiler with -E */
- static void find_hash(ARGS *args)
- {
- int i;
- char *path_stdout, *path_stderr;
- char *hash_dir;
- char *s;
- struct stat st;
- int status;
- int nlevels = 2;
- char *input_base;
- char *tmp;
-
- if ((s = getenv("CCACHE_NLEVELS"))) {
- nlevels = atoi(s);
- if (nlevels < 1) nlevels = 1;
- if (nlevels > 8) nlevels = 8;
- }
- hash_start();
- /* 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 || swig) {
- hash_string(input_file);
- }
- if (swig) {
- if (output_file) {
- hash_string(output_file);
- }
- } else {
- /* we have to hash the extension, as a .i file isn't treated the same
- by the compiler as a .ii file */
- hash_string(i_extension);
- }
- /* first the arguments */
- for (i=1;i<args->argc;i++) {
- /* 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, or they only affect linking */
- if (i < args->argc-1) {
- if (strcmp(args->argv[i], "-I") == 0 ||
- strcmp(args->argv[i], "-include") == 0 ||
- strcmp(args->argv[i], "-L") == 0 ||
- strcmp(args->argv[i], "-D") == 0 ||
- strcmp(args->argv[i], "-idirafter") == 0 ||
- strcmp(args->argv[i], "-isystem") == 0) {
- i++;
- continue;
- }
- }
- if (strncmp(args->argv[i], "-I", 2) == 0 ||
- strncmp(args->argv[i], "-L", 2) == 0 ||
- strncmp(args->argv[i], "-D", 2) == 0 ||
- strncmp(args->argv[i], "-idirafter", 10) == 0 ||
- strncmp(args->argv[i], "-isystem", 8) == 0) {
- 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 */
- hash_file(args->argv[i]+8);
- continue;
- }
- /* all other arguments are included in the hash */
- hash_string(args->argv[i]);
- }
- /* the compiler driver size and date. This is a simple minded way
- to try and detect compiler upgrades. It is not 100% reliable */
- if (stat(args->argv[0], &st) != 0) {
- cc_log("Couldn't stat the compiler!? (argv[0]='%s')\n", args->argv[0]);
- stats_update(STATS_COMPILER);
- failed();
- }
- /* also include the hash of the compiler name - as some compilers
- use hard links and behave differently depending on the real name */
- if (st.st_nlink > 1) {
- hash_string(str_basename(args->argv[0]));
- }
- hash_int(st.st_size);
- hash_int(st.st_mtime);
- /* possibly hash the current working directory */
- if (getenv("CCACHE_HASHDIR")) {
- char *cwd = gnu_getcwd();
- if (cwd) {
- hash_string(cwd);
- free(cwd);
- }
- }
- /* ~/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 = str_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());
- 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 = x_strdup(input_file);
- if (create_empty_file(path_stderr) != 0) {
- cc_log("failed to create empty stderr file\n");
- stats_update(STATS_ERROR);
- failed();
- }
- status = 0;
- }
- if (status != 0) {
- if (!direct_i_file) {
- unlink(path_stdout);
- }
- unlink(path_stderr);
- cc_log("the preprocessor gave %d\n", 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) {
- hash_file(path_stdout);
- } else {
- if (unify_hash(path_stdout) != 0) {
- stats_update(STATS_ERROR);
- failed();
- }
- }
- hash_file(path_stderr);
- i_tmpfile = path_stdout;
- if (!getenv("CCACHE_CPP2")) {
- /* if we are using the CPP trick then 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);
- }
- /* we use a N level subdir for the cache path to reduce the impact
- on filesystems which are slow for large directories
- */
- s = hash_result();
- x_asprintf(&hash_dir, "%s/%c", cache_dir, s[0]);
- x_asprintf(&stats_file, "%s/stats", hash_dir);
- for (i=1; i<nlevels; i++) {
- char *p;
- if (create_dir(hash_dir) != 0) {
- cc_log("failed to create %s\n", hash_dir);
- stats_update(STATS_ERROR);
- failed();
- }
- x_asprintf(&p, "%s/%c", hash_dir, s[i]);
- free(hash_dir);
- hash_dir = p;
- }
- if (create_dir(hash_dir) != 0) {
- cc_log("failed to create %s\n", hash_dir);
- stats_update(STATS_ERROR);
- failed();
- }
- x_asprintf(&hashname, "%s/%s", hash_dir, s+nlevels);
- free(hash_dir);
- }
- /*
- 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(int first)
- {
- int fd_stderr, fd_cpp_stderr;
- char *stderr_file;
- struct stat st;
- x_asprintf(&stderr_file, "%s.stderr", hashname);
- fd_stderr = open(stderr_file, O_RDONLY | O_BINARY);
- if (fd_stderr == -1) {
- /* it isn't in cache ... */
- free(stderr_file);
- return;
- }
- /* make sure the output is there too */
- if (stat(hashname, &st) != 0) {
- close(fd_stderr);
- unlink(stderr_file);
- free(stderr_file);
- return;
- }
- /* the user might be disabling cache hits */
- #ifndef ENABLE_ZLIB
- /* if the cache file is compressed we must recache */
- if ((first && getenv("CCACHE_RECACHE")) ||
- test_if_compressed(hashname) == 1)
- #else
- if (first && getenv("CCACHE_RECACHE"))
- #endif
- {
- close(fd_stderr);
- unlink(stderr_file);
- free(stderr_file);
- return;
- }
- if (first) {
- int hardlink;
- int passfail = -1;
- /* update timestamps for LRU cleanup
- also gives output_file a sensible mtime when hard-linking (for make) */
- x_utimes(stderr_file);
- hardlink = (getenv("CCACHE_HARDLINK") != 0);
- if (swig) {
- /* read the list of generated files and copy each of them out of the cache */
- FILE *file;
- char *outfiles;
- x_asprintf(&outfiles, "%s.outfiles", hashname);
- file = fopen(outfiles, "r");
- if (file) {
- char out_filename[FILENAME_MAX + 1];
- char out_filename_cache[FILENAME_MAX + 1];
- int retrieved_files_count = 0;
- x_utimes(outfiles);
- while (fgets(out_filename, FILENAME_MAX, file)) {
- char *linefeed = strchr(out_filename, '\n');
- if (linefeed) {
- char *potential_cr = linefeed - 1;
- if (potential_cr >= out_filename && *potential_cr == '\r')
- *potential_cr = 0;
- *linefeed = 0;
- if (retrieved_files_count == 0) {
- strcpy(out_filename_cache, hashname);
- } else {
- sprintf(out_filename_cache, "%s.%d", hashname, retrieved_files_count);
- }
- passfail = retrieve_from_cache(out_filename_cache, out_filename, hardlink);
- if (passfail == -1) {
- break;
- }
- retrieved_files_count++;
- } else {
- cc_log("failed to copy output files from cache - internal error\n");
- stats_update(STATS_ERROR);
- passfail = -1;
- break;
- }
- }
- if (retrieved_files_count == 0) {
- cc_log("failed to copy output files from cache - internal error\n");
- stats_update(STATS_ERROR);
- passfail = -1;
- }
- fclose(file);
- } else {
- cc_log("failed to open cached outfiles file - %s\n", strerror(errno));
- stats_update(STATS_ERROR);
- }
- } else {
- passfail = retrieve_from_cache(hashname, output_file, hardlink);
- }
- free(stderr_file);
- if (passfail == -1) {
- close(fd_stderr);
- unlink(stderr_file);
- return;
- }
- }
- /* get rid of the intermediate preprocessor file */
- if (i_tmpfile) {
- if (!direct_i_file) {
- unlink(i_tmpfile);
- }
- free(i_tmpfile);
- i_tmpfile = NULL;
- }
- /* send the cpp stderr, if applicable */
- fd_cpp_stderr = open(cpp_stderr, O_RDONLY | O_BINARY);
- if (fd_cpp_stderr != -1) {
- copy_fd(fd_cpp_stderr, 2);
- close(fd_cpp_stderr);
- unlink(cpp_stderr);
- free(cpp_stderr);
- cpp_stderr = NULL;
- }
- /* send the stderr */
- copy_fd(fd_stderr, 2);
- close(fd_stderr);
- /* and exit with the right status code */
- if (first) {
- cc_log("got cached result for %s\n", input_file);
- stats_update(STATS_CACHED);
- }
- 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 = str_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],'/')
- #ifdef _WIN32
- || strchr(argv[1],'\\')
- #endif
- ) {
- /* a full path was given */
- return;
- }
- base = str_basename(argv[1]);
- }
- /* support user override of the compiler */
- if ((path=getenv("CCACHE_CC"))) {
- base = x_strdup(path);
- }
- orig_args->argv[0] = find_executable(base, MYNAME);
- /* can't find the compiler! */
- if (!orig_args->argv[0]) {
- stats_update(STATS_COMPILER);
- cc_log("could not find compiler (%s)\n", base);
- 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;
- }
- if (swig) return "ii"; /* any file extension is acceptable as input for SWIG */
- 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)
- {
- int i;
- int found_c_opt = 0;
- int found_S_opt = 0;
- struct stat st;
- char *e;
- /* is gcc being asked to output dependencies? */
- int generating_dependencies = 0;
- /* is the dependency makefile name overridden with -MF? */
- int dependency_filename_specified = 0;
- /* is the dependency makefile target name specified with -MQ or -MF? */
- int dependency_target_specified = 0;
- stripped_args = args_init(0, NULL);
- args_add(stripped_args, argv[0]);
- /* -c not required for SWIG */
- if (swig) {
- found_c_opt = 1;
- }
- for (i=1; i<argc; i++) {
- /* some options will never work ... */
- if (strcmp(argv[i], "-E") == 0) {
- failed();
- }
- /* these are too hard */
- if (strcmp(argv[i], "-fbranch-probabilities")==0 ||
- strcmp(argv[i], "-fprofile-arcs") == 0 ||
- strcmp(argv[i], "-ftest-coverage") == 0 ||
- strcmp(argv[i], "--coverage") == 0 ||
- strcmp(argv[i], "-M") == 0 ||
- strcmp(argv[i], "-MM") == 0 ||
- strcmp(argv[i], "-x") == 0) {
- cc_log("argument %s is unsupported\n", argv[i]);
- stats_update(STATS_UNSUPPORTED);
- failed();
- continue;
- }
- /* we must have -c */
- if (strcmp(argv[i], "-c") == 0) {
- if (!strip_c_option) {
- args_add(stripped_args, argv[i]);
- }
- found_c_opt = 1;
- continue;
- }
- /* -S changes the default extension */
- if (strcmp(argv[i], "-S") == 0) {
- args_add(stripped_args, argv[i]);
- found_S_opt = 1;
- continue;
- }
-
- /* we need to work out where the output was meant to go */
- if (strcmp(argv[i], "-o") == 0) {
- if (i == argc-1) {
- cc_log("missing argument to %s\n", argv[i]);
- stats_update(STATS_ARGS);
- failed();
- }
- output_file = argv[i+1];
- i++;
- continue;
- }
-
- /* alternate form of -o, with no space */
- if (!swig) { /* some of SWIG's arguments begin with -o */
- if (strncmp(argv[i], "-o", 2) == 0) {
- output_file = &argv[i][2];
- continue;
- }
- }
- /* debugging is handled specially, so that we know if we
- can strip line number info
- */
- if (strncmp(argv[i], "-g", 2) == 0) {
- args_add(stripped_args, argv[i]);
- if (strcmp(argv[i], "-g0") != 0) {
- enable_unify = 0;
- }
- continue;
- }
- /* The user knows best: just swallow the next arg */
- if (strcmp(argv[i], "--ccache-skip") == 0) {
- i++;
- if (i == argc) {
- failed();
- }
- args_add(stripped_args, argv[i]);
- continue;
- }
- /* These options require special handling, because they
- behave differently with gcc -E, when the output
- file is not specified. */
- if (strcmp(argv[i], "-MD") == 0 || strcmp(argv[i], "-MMD") == 0) {
- generating_dependencies = 1;
- } else if (strcmp(argv[i], "-MF") == 0) {
- dependency_filename_specified = 1;
- } else if (strcmp(argv[i], "-MQ") == 0 || strcmp(argv[i], "-MT") == 0) {
- dependency_target_specified = 1;
- }
- /* the input file is already preprocessed */
- if (swig && strcmp(argv[i], "-nopreprocess") == 0) {
- direct_i_file = 1;
- continue;
- }
- /* options that take an argument */
- {
- const char *opts[] = {"-I", "-include", "-imacros", "-iprefix",
- "-iwithprefix", "-iwithprefixbefore",
- "-L", "-D", "-U", "-x", "-MF",
- "-MT", "-MQ", "-isystem", "-aux-info",
- "--param", "-A", "-Xlinker", "-u",
- "-idirafter",
- NULL};
- int j;
- for (j=0;opts[j];j++) {
- if (strcmp(argv[i], opts[j]) == 0) {
- if (i == argc-1) {
- cc_log("missing argument to %s\n",
- argv[i]);
- stats_update(STATS_ARGS);
- failed();
- }
-
- args_add(stripped_args, argv[i]);
- args_add(stripped_args, argv[i+1]);
- i++;
- break;
- }
- }
- if (opts[j]) continue;
- }
- /* other options */
- if (argv[i][0] == '-') {
- args_add(stripped_args, argv[i]);
- continue;
- }
- /* if an argument isn't a plain file then assume its
- an option, not an input file. This allows us to
- cope better with unusual compiler options */
- if (stat(argv[i], &st) != 0 || !S_ISREG(st.st_mode)) {
- args_add(stripped_args, argv[i]);
- continue;
- }
- if (input_file) {
- if (check_extension(argv[i], NULL)) {
- cc_log("multiple input files (%s and %s)\n",
- input_file, argv[i]);
- stats_update(STATS_MULTIPLE);
- } else if (!found_c_opt) {
- cc_log("called for link with %s\n", argv[i]);
- if (strstr(argv[i], "conftest.")) {
- stats_update(STATS_CONFTEST);
- } else {
- stats_update(STATS_LINK);
- }
- } else {
- cc_log("non C/C++ file %s\n", argv[i]);
- stats_update(STATS_NOTC);
- }
- failed();
- }
- input_file = argv[i];
- }
- if (!input_file) {
- cc_log("No input file found\n");
- stats_update(STATS_NOINPUT);
- failed();
- }
- if (swig) {
- i_extension = check_extension(input_file, NULL);
- } else {
- i_extension = check_extension(input_file, &direct_i_file);
- }
- if (i_extension == NULL) {
- cc_log("Not a C/C++ file - %s\n", input_file);
- stats_update(STATS_NOTC);
- failed();
- }
- if (!found_c_opt) {
- cc_log("No -c option found for %s\n", input_file);
- /* I find that having a separate statistic for autoconf tests is useful,
- as they are the dominant form of "called for link" in many cases */
- if (strstr(input_file, "conftest.")) {
- stats_update(STATS_CONFTEST);
- } else {
- stats_update(STATS_LINK);
- }
- failed();
- }
- /* don't try to second guess the compilers heuristics for stdout handling */
- if (output_file && strcmp(output_file, "-") == 0) {
- stats_update(STATS_OUTSTDOUT);
- failed();
- }
- if (!swig && !output_file) {
- char *p;
- output_file = x_strdup(input_file);
- if ((p = strrchr(output_file, '/'))) {
- output_file = p+1;
- }
- p = strrchr(output_file, '.');
- if (!p || !p[1]) {
- cc_log("badly formed output_file %s\n", output_file);
- stats_update(STATS_ARGS);
- failed();
- }
- p[1] = found_S_opt ? 's' : 'o';
- p[2] = 0;
- }
- /* If dependencies are generated, configure the preprocessor */
- if (generating_dependencies && output_file) {
- if (!dependency_filename_specified) {
- char *default_depfile_name = x_strdup(output_file);
- char *p = strrchr(default_depfile_name, '.');
- if (p) {
- if (strlen(p) < 2) {
- cc_log("badly formed dependency file %s\n", output_file);
- stats_update(STATS_ARGS);
- failed();
- return;
- }
- *p = 0;
- }
- else {
- int len = p - default_depfile_name;
-
- p = x_malloc(len + 3);
- strncpy(default_depfile_name, p, len - 1);
- free(default_depfile_name);
- default_depfile_name = p;
- }
- strcat(default_depfile_name, ".d");
- args_add(stripped_args, "-MF");
- args_add(stripped_args, default_depfile_name);
- }
- if (!dependency_target_specified) {
- args_add(stripped_args, "-MT");
- args_add(stripped_args, output_file);
- }
- }
- /* cope with -o /dev/null */
- if (output_file && strcmp(output_file,"/dev/null") != 0 && stat(output_file, &st) == 0 && !S_ISREG(st.st_mode)) {
- cc_log("Not a regular file %s\n", output_file);
- stats_update(STATS_DEVICE);
- failed();
- }
- if ((e=getenv("CCACHE_PREFIX"))) {
- char *p = find_executable(e, MYNAME);
- if (!p) {
- cc_log("could not find executable (%s)\n", e);
- stats_update(STATS_ENVIRONMMENT);
- perror(e);
- exit(1);
- }
- args_add_prefix(stripped_args, p);
- }
- }
- static void detect_swig()
- {
- char *basename = str_basename(orig_args->argv[0]);
- if (strstr(basename, "swig") || getenv("CCACHE_SWIG")) {
- swig = 1;
- }
- free(basename);
- }
- /* the main ccache driver function */
- static void ccache(int argc, char *argv[])
- {
- /* 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\n");
- cc_log("ccache is disabled\n");
- failed();
- }
-
- /* we might be disabled */
- if (getenv("CCACHE_DISABLE")) {
- cc_log("ccache is disabled\n");
- failed();
- }
- if (getenv("CCACHE_STRIPC")) {
- strip_c_option = 1;
- }
- if (getenv("CCACHE_UNIFY")) {
- enable_unify = 1;
- }
- detect_swig();
- /* process argument list, returning a new set of arguments for pre-processing */
- process_args(orig_args->argc, orig_args->argv);
- /* run with -E to find the hash */
- find_hash(stripped_args);
- /* if we can return from cache at this point then do */
- from_cache(1);
- if (getenv("CCACHE_READONLY")) {
- cc_log("read-only set - doing real compile\n");
- failed();
- }
-
- /* run real compiler, sending output to cache */
- to_cache(stripped_args);
- /* return from cache */
- from_cache(0);
- /* oh oh! */
- cc_log("secondary from_cache failed!\n");
- stats_update(STATS_ERROR);
- failed();
- }
- static void usage(void)
- {
- printf("%s, a compiler cache including support for SWIG. Version %s\n", MYNAME, CCACHE_VERSION);
- printf("Copyright Andrew Tridgell, 2002\n\n");
-
- printf("Usage:\n");
- printf("\t" MYNAME " [options]\n");
- printf("\t" MYNAME " compiler [compile options]\n");
- printf("\tcompiler [compile options] (via symbolic link)\n");
- printf("\nOptions:\n");
- printf("-s show statistics summary\n");
- printf("-z zero statistics\n");
- printf("-c run a cache cleanup\n");
- printf("-C clear the cache completely\n");
- printf("-F <maxfiles> set maximum files in cache\n");
- printf("-M <maxsize> set maximum size of cache (use G, M or K)\n");
- printf("-h this help page\n");
- printf("-V print version number\n");
- }
- static void check_cache_dir(void)
- {
- if (!cache_dir) {
- fatal("Unable to determine home directory");
- }
- }
- /* the main program when not doing a compile */
- static int ccache_main(int argc, char *argv[])
- {
- int c;
- size_t v;
- while ((c = getopt(argc, argv, "hszcCF:M:V")) != -1) {
- switch (c) {
- case 'V':
- printf("%s version %s\n", MYNAME, CCACHE_VERSION);
- printf("Copyright Andrew Tridgell 2002\n");
- printf("Released under the GNU GPL v2 or later\n");
- exit(0);
- case 'h':
- usage();
- 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) {
- 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) {
- printf("Set cache size limit to %uk\n", (unsigned)v);
- } else {
- printf("Could not set cache size limit.\n");
- exit(1);
- }
- break;
- default:
- usage();
- 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\n");
- stats_update(STATS_ERROR);
- failed();
- }
- /* leak a pointer to the environment */
- x_asprintf(&buf, "UNCACHED_ERR_FD=%d", uncached_fd);
- if (putenv(buf) == -1) {
- cc_log("putenv failed\n");
- stats_update(STATS_ERROR);
- failed();
- }
- }
- int main(int argc, char *argv[])
- {
- char *p;
- 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);
- }
- }
- cache_logfile = getenv("CCACHE_LOGFILE");
- if (getenv("CCACHE_VERBOSE")) {
- ccache_verbose = 1;
- }
- setup_uncached_err();
-
- /* 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);
- }
- }
- /* check if we are being invoked as "ccache" */
- if (strlen(argv[0]) >= strlen(MYNAME) &&
- strcmp(argv[0] + strlen(argv[0]) - strlen(MYNAME), MYNAME) == 0) {
- if (argc < 2) {
- usage();
- 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);
- }
- }
- /* make sure the cache dir exists */
- if (cache_dir && (create_dir(cache_dir) != 0)) {
- fprintf(stderr,"ccache: failed to create %s (%s)\n",
- cache_dir, strerror(errno));
- exit(1);
- }
- temp_dir = getenv("CCACHE_TEMPDIR");
- if (!temp_dir) {
- x_asprintf(&temp_dir, "%s/temp", cache_dir);
- /* make sure temp dir exists if not supplied by user */
- if (temp_dir && 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;
- }