Mercurial > mm7
diff lib/swig/swigwin-2.0.11/CCache/ccache.c @ 1899:b3009adc0e2f
Adding swig, gitignore, hgignore
author | Nomad |
---|---|
date | Mon, 21 Oct 2013 10:42:27 +0200 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/swig/swigwin-2.0.11/CCache/ccache.c Mon Oct 21 10:42:27 2013 +0200 @@ -0,0 +1,1389 @@ +/* + 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); + } + + if (passfail == -1) { + close(fd_stderr); + unlink(stderr_file); + free(stderr_file); + return; + } + free(stderr_file); + } + + /* 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; +}