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"
int ccache_verbose = 0;
char *cache_dir = NULL;
static char *temp_dir = NULL;
char *cache_logfile = NULL;
static ARGS *stripped_args;
static ARGS *orig_args;
static char *output_file;
static char *input_file;
static char *hashname;
static const char *i_extension;
static char *i_tmpfile;
static int direct_i_file;
static char *cpp_stderr;
char *stats_file = NULL;
static int enable_unify;
static int strip_c_option;
static int swig;
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;
if (i_tmpfile) {
if (!direct_i_file) {
unlink(i_tmpfile);
}
free(i_tmpfile);
i_tmpfile = NULL;
}
if (cpp_stderr) {
unlink(cpp_stderr);
free(cpp_stderr);
cpp_stderr = NULL;
}
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);
free(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
{
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
}
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;
}
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
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)++;
}
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);
}
* 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 (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) {
int fd2 = open(cpp_stderr, O_RDONLY | O_BINARY);
if (fd2 != -1) {
copy_fd(fd2, 2);
close(fd2);
unlink(cpp_stderr);
cpp_stderr = NULL;
}
}
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) {
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();
}
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);
}
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();
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 {
by the compiler as a .ii file */
hash_string(i_extension);
}
for (i=1;i<args->argc;i++) {
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) {
don't include the path to it in the hash */
hash_file(args->argv[i]+8);
continue;
}
hash_string(args->argv[i]);
}
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();
}
use hard links and behave differently depending on the real name */
if (st.st_nlink > 1) {
char *path = str_basename(args->argv[0]);
hash_string(path);
free(path);
}
hash_int(st.st_size);
hash_int(st.st_mtime);
if (getenv("CCACHE_HASHDIR")) {
char *cwd = gnu_getcwd();
if (cwd) {
hash_string(cwd);
free(cwd);
}
}
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;
}
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());
free(input_base);
if (!direct_i_file) {
args_add(args, "-E");
args_add(args, input_file);
status = execute(args->argv, path_stdout, path_stderr);
args_pop(args, 2);
} else {
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();
}
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")) {
data and output it just before the main stderr from the compiler
pass */
cpp_stderr = path_stderr;
} else {
unlink(path_stderr);
free(path_stderr);
}
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) {
free(stderr_file);
return;
}
if (stat(hashname, &st) != 0) {
close(fd_stderr);
unlink(stderr_file);
free(stderr_file);
return;
}
#ifndef ENABLE_ZLIB
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;
also gives output_file a sensible mtime when hard-linking (for make) */
x_utimes(stderr_file);
hardlink = (getenv("CCACHE_HARDLINK") != 0);
if (swig) {
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);
}
if (i_tmpfile) {
if (!direct_i_file) {
unlink(i_tmpfile);
}
free(i_tmpfile);
i_tmpfile = NULL;
}
if (cpp_stderr) {
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;
}
}
copy_fd(fd_stderr, 2);
close(fd_stderr);
if (first) {
cc_log("got cached result for %s\n", input_file);
stats_update(STATS_CACHED);
}
exit(0);
}
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]);
if (strcmp(base, MYNAME) == 0) {
args_remove_first(orig_args);
free(base);
if (strchr(argv[1],'/')
#ifdef _WIN32
|| strchr(argv[1],'\\')
#endif
) {
return;
}
base = str_basename(argv[1]);
}
if ((path=getenv("CCACHE_CC"))) {
free(base);
base = x_strdup(path);
}
orig_args->argv[0] = find_executable(base, MYNAME);
if (!orig_args->argv[0]) {
stats_update(STATS_COMPILER);
cc_log("could not find compiler (%s)\n", base);
perror(base);
free(base);
exit(1);
}
free(base);
}
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";
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;
int generating_dependencies = 0;
int dependency_filename_specified = 0;
int dependency_target_specified = 0;
stripped_args = args_init(0, NULL);
args_add(stripped_args, argv[0]);
if (swig) {
found_c_opt = 1;
}
for (i=1; i<argc; i++) {
if (strcmp(argv[i], "-E") == 0) {
failed();
}
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;
}
if (strcmp(argv[i], "-c") == 0) {
if (!strip_c_option) {
args_add(stripped_args, argv[i]);
}
found_c_opt = 1;
continue;
}
if (strcmp(argv[i], "-S") == 0) {
args_add(stripped_args, argv[i]);
found_S_opt = 1;
continue;
}
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;
}
if (!swig) {
if (strncmp(argv[i], "-o", 2) == 0) {
output_file = &argv[i][2];
continue;
}
}
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;
}
if (strcmp(argv[i], "--ccache-skip") == 0) {
i++;
if (i == argc) {
failed();
}
args_add(stripped_args, argv[i]);
continue;
}
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;
}
if (swig && strcmp(argv[i], "-nopreprocess") == 0) {
direct_i_file = 1;
continue;
}
{
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;
}
if (argv[i][0] == '-') {
args_add(stripped_args, argv[i]);
continue;
}
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);
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();
}
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 (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);
free(default_depfile_name);
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);
free(default_depfile_name);
}
if (!dependency_target_specified) {
args_add(stripped_args, "-MT");
args_add(stripped_args, output_file);
}
}
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);
free(p);
}
}
static void detect_swig()
{
char *basename = str_basename(orig_args->argv[0]);
if (strstr(basename, "swig") || getenv("CCACHE_SWIG")) {
swig = 1;
}
free(basename);
}
static void ccache(int argc, char *argv[])
{
find_compiler(argc, argv);
if (!cache_dir) {
cc_log("Unable to determine home directory\n");
cc_log("ccache is disabled\n");
failed();
}
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_args(orig_args->argc, orig_args->argv);
find_hash(stripped_args);
from_cache(1);
if (getenv("CCACHE_READONLY")) {
cc_log("read-only set - doing real compile\n");
failed();
}
to_cache(stripped_args);
from_cache(0);
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");
}
}
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;
}
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();
}
x_asprintf(&buf, "UNCACHED_ERR_FD=%d", uncached_fd);
if (putenv(buf) == -1) {
cc_log("putenv failed\n");
close(uncached_fd);
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();
p = getenv("CCACHE_UMASK");
if (p) {
mode_t mask;
errno = 0;
mask = strtol(p, NULL, 8);
if (errno == 0) {
umask(mask);
}
}
if (strlen(argv[0]) >= strlen(MYNAME) &&
strcmp(argv[0] + strlen(argv[0]) - strlen(MYNAME), MYNAME) == 0) {
if (argc < 2) {
usage();
exit(1);
}
being passed a compiler name and options */
if (argv[1][0] == '-') {
return ccache_main(argc, argv);
}
}
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);
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;
}