diff options
| author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-02-17 21:28:53 +0000 |
|---|---|---|
| committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-02-17 21:28:53 +0000 |
| commit | 189d7eb970b025c570c7ccce916759083373bdde (patch) | |
| tree | f4f7c3a71ecc7851a8a7b86206bbb202b5cab551 /ccache.c | |
| download | ccache-tarball-master.tar.gz | |
ccache-3.3.4HEADccache-3.3.4master
Diffstat (limited to 'ccache.c')
| -rw-r--r-- | ccache.c | 3476 |
1 files changed, 3476 insertions, 0 deletions
diff --git a/ccache.c b/ccache.c new file mode 100644 index 0000000..de2b1e3 --- /dev/null +++ b/ccache.c @@ -0,0 +1,3476 @@ +// ccache -- a fast C/C++ compiler cache +// +// Copyright (C) 2002-2007 Andrew Tridgell +// Copyright (C) 2009-2017 Joel Rosdahl +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program; if not, write to the Free Software Foundation, Inc., 51 +// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include "ccache.h" +#include "compopt.h" +#ifdef HAVE_GETOPT_LONG +#include <getopt.h> +#else +#include "getopt_long.h" +#endif +#include "hashtable.h" +#include "hashtable_itr.h" +#include "hashutil.h" +#include "language.h" +#include "manifest.h" + +#define STRINGIFY(x) #x +#define TO_STRING(x) STRINGIFY(x) + +static const char VERSION_TEXT[] = + MYNAME " version %s\n" + "\n" + "Copyright (C) 2002-2007 Andrew Tridgell\n" + "Copyright (C) 2009-2017 Joel Rosdahl\n" + "\n" + "This program is free software; you can redistribute it and/or modify it under\n" + "the terms of the GNU General Public License as published by the Free Software\n" + "Foundation; either version 3 of the License, or (at your option) any later\n" + "version.\n"; + +static const char USAGE_TEXT[] = + "Usage:\n" + " " MYNAME " [options]\n" + " " MYNAME " compiler [compiler options]\n" + " compiler [compiler options] (via symbolic link)\n" + "\n" + "Options:\n" + " -c, --cleanup delete old files and recalculate size counters\n" + " (normally not needed as this is done automatically)\n" + " -C, --clear clear the cache completely (except configuration)\n" + " -F, --max-files=N set maximum number of files in cache to N (use 0 for\n" + " no limit)\n" + " -M, --max-size=SIZE set maximum size of cache to SIZE (use 0 for no\n" + " limit); available suffixes: k, M, G, T (decimal) and\n" + " Ki, Mi, Gi, Ti (binary); default suffix: G\n" + " -o, --set-config=K=V set configuration key K to value V\n" + " -p, --print-config print current configuration options\n" + " -s, --show-stats show statistics summary\n" + " -z, --zero-stats zero statistics counters\n" + "\n" + " -h, --help print this help text\n" + " -V, --version print version and copyright information\n" + "\n" + "See also <https://ccache.samba.org>.\n"; + +// Global configuration data. +struct conf *conf = NULL; + +// Where to write configuration changes. +char *primary_config_path = NULL; + +// Secondary, read-only configuration file (if any). +char *secondary_config_path = NULL; + +// Current working directory taken from $PWD, or getcwd() if $PWD is bad. +char *current_working_dir = NULL; + +// The original argument list. +static struct args *orig_args; + +// The source file. +static char *input_file; + +// The output file being compiled to. +static char *output_obj; + +// The path to the dependency file (implicit or specified with -MF). +static char *output_dep; + +// The path to the coverage file (implicit when using -ftest-coverage). +static char *output_cov; + +// Diagnostic generation information (clang). Contains pathname if not NULL. +static char *output_dia = NULL; + +// Split dwarf information (GCC 4.8 andup). Contains pathname if not NULL. +static char *output_dwo = NULL; + +// Array for storing -arch options. +#define MAX_ARCH_ARGS 10 +static size_t arch_args_size = 0; +static char *arch_args[MAX_ARCH_ARGS] = {NULL}; + +// Name (represented as a struct file_hash) of the file containing the cached +// object code. +static struct file_hash *cached_obj_hash; + +// Full path to the file containing the cached object code +// (cachedir/a/b/cdef[...]-size.o). +static char *cached_obj; + +// Full path to the file containing the standard error output +// (cachedir/a/b/cdef[...]-size.stderr). +static char *cached_stderr; + +// Full path to the file containing the dependency information +// (cachedir/a/b/cdef[...]-size.d). +static char *cached_dep; + +// Full path to the file containing the coverage information +// (cachedir/a/b/cdef[...]-size.gcno). +static char *cached_cov; + +// Full path to the file containing the diagnostic information (for clang) +// (cachedir/a/b/cdef[...]-size.dia). +static char *cached_dia; + +// Full path to the file containing the split dwarf (for GCC 4.8 and above) +// (cachedir/a/b/cdef[...]-size.dwo). +// +// Contains NULL if -gsplit-dwarf is not given. +static char *cached_dwo; + +// using_split_dwarf is true if "-gsplit-dwarf" is given to the compiler (GCC +// 4.8 and up). +bool using_split_dwarf = false; + +// Full path to the file containing the manifest +// (cachedir/a/b/cdef[...]-size.manifest). +static char *manifest_path; + +// Time of compilation. Used to see if include files have changed after +// compilation. +time_t time_of_compilation; + +// Files included by the preprocessor and their hashes/sizes. Key: file path. +// Value: struct file_hash. +static struct hashtable *included_files = NULL; + +// Uses absolute path for some include files. +static bool has_absolute_include_headers = false; + +// List of headers to ignore. +static char **ignore_headers; + +// Size of headers to ignore list. +static size_t ignore_headers_len; + +// Is the compiler being asked to output debug info? +static bool generating_debuginfo; + +// Is the compiler being asked to output dependencies? +static bool generating_dependencies; + +// Is the compiler being asked to output coverage? +static bool generating_coverage; + +// Relocating debuginfo in the format old=new. +static char *debug_prefix_map = NULL; + +// Is the compiler being asked to output coverage data (.gcda) at runtime? +static bool profile_arcs; + +// Name of the custom profile directory (default: object dirname). +static char *profile_dir; + +// The name of the temporary preprocessed file. +static char *i_tmpfile; + +// Are we compiling a .i or .ii file directly? +static bool direct_i_file; + +// The name of the cpp stderr file. +static char *cpp_stderr; + +// Full path to the statistics file in the subdirectory where the cached result +// belongs (<cache_dir>/<x>/stats). +char *stats_file = NULL; + +// Whether the output is a precompiled header. +static bool output_is_precompiled_header = false; + +// Profile generation / usage information. +static char *profile_dir = NULL; +static bool profile_use = false; +static bool profile_generate = false; + +// Whether we are using a precompiled header (either via -include, #include or +// clang's -include-pch or -include-pth). +static bool using_precompiled_header = false; + +// The .gch/.pch/.pth file used for compilation. +static char *included_pch_file = NULL; + +// How long (in microseconds) to wait before breaking a stale lock. +unsigned lock_staleness_limit = 2000000; + +enum fromcache_call_mode { + FROMCACHE_DIRECT_MODE, + FROMCACHE_CPP_MODE +}; + +struct pending_tmp_file { + char *path; + struct pending_tmp_file *next; +}; + +// Temporary files to remove at program exit. +static struct pending_tmp_file *pending_tmp_files = NULL; + +#ifndef _WIN32 +static sigset_t fatal_signal_set; + +// PID of currently executing compiler that we have started, if any. 0 means no +// ongoing compilation. +static pid_t compiler_pid = 0; +#endif + +// This is a string that identifies the current "version" of the hash sum +// computed by ccache. If, for any reason, we want to force the hash sum to be +// different for the same input in a new ccache version, we can just change +// this string. A typical example would be if the format of one of the files +// stored in the cache changes in a backwards-incompatible way. +static const char HASH_PREFIX[] = "3"; + +static void +add_prefix(struct args *args, char *prefix_command) +{ + if (str_eq(prefix_command, "")) { + return; + } + + struct args *prefix = args_init(0, NULL); + char *e = x_strdup(prefix_command); + char *saveptr = NULL; + for (char *tok = strtok_r(e, " ", &saveptr); + tok; + tok = strtok_r(NULL, " ", &saveptr)) { + char *p; + + p = find_executable(tok, MYNAME); + if (!p) { + fatal("%s: %s", tok, strerror(errno)); + } + + args_add(prefix, p); + free(p); + } + free(e); + + cc_log("Using command-line prefix %s", prefix_command); + for (int i = prefix->argc; i != 0; i--) { + args_add_prefix(args, prefix->argv[i-1]); + } + args_free(prefix); +} + +// Something went badly wrong - just execute the real compiler. +static void +failed(void) +{ + assert(orig_args); + + args_strip(orig_args, "--ccache-"); + add_prefix(orig_args, conf->prefix_command); + + cc_log("Failed; falling back to running the real compiler"); + cc_log_argv("Executing ", orig_args->argv); + exitfn_call(); + execv(orig_args->argv[0], orig_args->argv); + fatal("execv of %s failed: %s", orig_args->argv[0], strerror(errno)); +} + +static const char * +temp_dir() +{ + static char *path = NULL; + if (path) { + return path; // Memoize + } + path = conf->temporary_dir; + if (str_eq(path, "")) { + path = format("%s/tmp", conf->cache_dir); + } + return path; +} + +void +block_signals(void) +{ +#ifndef _WIN32 + sigprocmask(SIG_BLOCK, &fatal_signal_set, NULL); +#endif +} + +void +unblock_signals(void) +{ +#ifndef _WIN32 + sigset_t empty; + sigemptyset(&empty); + sigprocmask(SIG_SETMASK, &empty, NULL); +#endif +} + +static void +add_pending_tmp_file(const char *path) +{ + block_signals(); + struct pending_tmp_file *e = x_malloc(sizeof(*e)); + e->path = x_strdup(path); + e->next = pending_tmp_files; + pending_tmp_files = e; + unblock_signals(); +} + +static void +do_clean_up_pending_tmp_files(void) +{ + struct pending_tmp_file *p = pending_tmp_files; + while (p) { + // Can't call tmp_unlink here since its cc_log calls aren't signal safe. + unlink(p->path); + p = p->next; + // Leak p->path and p here because clean_up_pending_tmp_files needs to be + // signal safe. + } +} + +static void +clean_up_pending_tmp_files(void) +{ + block_signals(); + do_clean_up_pending_tmp_files(); + unblock_signals(); +} + +#ifndef _WIN32 +static void +signal_handler(int signum) +{ + // Unregister handler for this signal so that we can send the signal to + // ourselves at the end of the handler. + signal(signum, SIG_DFL); + + // If ccache was killed explicitly, then bring the compiler subprocess (if + // any) with us as well. + if (signum == SIGTERM + && compiler_pid != 0 + && waitpid(compiler_pid, NULL, WNOHANG) == 0) { + kill(compiler_pid, signum); + } + + do_clean_up_pending_tmp_files(); + + if (compiler_pid != 0) { + // Wait for compiler subprocess to exit before we snuff it. + waitpid(compiler_pid, NULL, 0); + } + + // Resend signal to ourselves to exit properly after returning from the + // handler. + kill(getpid(), signum); +} + +static void +register_signal_handler(int signum) +{ + struct sigaction act; + memset(&act, 0, sizeof(act)); + act.sa_handler = signal_handler; + act.sa_mask = fatal_signal_set; +#ifdef SA_RESTART + act.sa_flags = SA_RESTART; +#endif + sigaction(signum, &act, NULL); +} + +static void +set_up_signal_handlers(void) +{ + sigemptyset(&fatal_signal_set); + sigaddset(&fatal_signal_set, SIGINT); + sigaddset(&fatal_signal_set, SIGTERM); +#ifdef SIGHUP + sigaddset(&fatal_signal_set, SIGHUP); +#endif +#ifdef SIGQUIT + sigaddset(&fatal_signal_set, SIGQUIT); +#endif + + register_signal_handler(SIGINT); + register_signal_handler(SIGTERM); +#ifdef SIGHUP + register_signal_handler(SIGHUP); +#endif +#ifdef SIGQUIT + register_signal_handler(SIGQUIT); +#endif +} +#endif // _WIN32 + +static void +clean_up_internal_tempdir(void) +{ + time_t now = time(NULL); + struct stat st; + if (x_stat(conf->cache_dir, &st) != 0 || st.st_mtime + 3600 >= now) { + // No cleanup needed. + return; + } + + update_mtime(conf->cache_dir); + + DIR *dir = opendir(temp_dir()); + if (!dir) { + return; + } + + struct dirent *entry; + while ((entry = readdir(dir))) { + if (str_eq(entry->d_name, ".") || str_eq(entry->d_name, "..")) { + continue; + } + + char *path = format("%s/%s", temp_dir(), entry->d_name); + if (x_lstat(path, &st) == 0 && st.st_mtime + 3600 < now) { + tmp_unlink(path); + } + free(path); + } + + closedir(dir); +} + +static char * +get_current_working_dir(void) +{ + if (!current_working_dir) { + char *cwd = get_cwd(); + if (cwd) { + current_working_dir = x_realpath(cwd); + free(cwd); + } + if (!current_working_dir) { + cc_log("Unable to determine current working directory: %s", + strerror(errno)); + failed(); + } + } + return current_working_dir; +} + +// Transform a name to a full path into the cache directory, creating needed +// sublevels if needed. Caller frees. +static char * +get_path_in_cache(const char *name, const char *suffix) +{ + char *path = x_strdup(conf->cache_dir); + for (unsigned i = 0; i < conf->cache_dir_levels; ++i) { + char *p = format("%s/%c", path, name[i]); + free(path); + path = p; + } + + char *result = + format("%s/%s%s", path, name + conf->cache_dir_levels, suffix); + free(path); + return result; +} + +// This function hashes an include file and stores the path and hash in the +// global included_files variable. If the include file is a PCH, cpp_hash is +// also updated. Takes over ownership of path. +static void +remember_include_file(char *path, struct mdfour *cpp_hash, bool system) +{ + size_t path_len = strlen(path); + if (path_len >= 2 && (path[0] == '<' && path[path_len - 1] == '>')) { + // Typically <built-in> or <command-line>. + goto ignore; + } + + if (str_eq(path, input_file)) { + // Don't remember the input file. + goto ignore; + } + + if (system && (conf->sloppiness & SLOPPY_NO_SYSTEM_HEADERS)) { + // Don't remember this system header. + goto ignore; + } + + if (hashtable_search(included_files, path)) { + // Already known include file. + goto ignore; + } + +#ifdef _WIN32 + // stat fails on directories on win32. + DWORD attributes = GetFileAttributes(path); + if (attributes != INVALID_FILE_ATTRIBUTES && + attributes & FILE_ATTRIBUTE_DIRECTORY) { + goto ignore; + } +#endif + + struct stat st; + if (x_stat(path, &st) != 0) { + goto failure; + } + if (S_ISDIR(st.st_mode)) { + // Ignore directory, typically $PWD. + goto ignore; + } + if (!S_ISREG(st.st_mode)) { + // Device, pipe, socket or other strange creature. + cc_log("Non-regular include file %s", path); + goto failure; + } + + // Canonicalize path for comparison; clang uses ./header.h. + char *canonical = path; + size_t canonical_len = path_len; + if (canonical[0] == '.' && canonical[1] == '/') { + canonical += 2; + canonical_len -= 2; + } + + for (size_t i = 0; i < ignore_headers_len; i++) { + char *ignore = ignore_headers[i]; + size_t ignore_len = strlen(ignore); + if (ignore_len > canonical_len) { + continue; + } + if (strncmp(canonical, ignore, ignore_len) == 0 + && (ignore[ignore_len-1] == DIR_DELIM_CH + || canonical[ignore_len] == DIR_DELIM_CH + || canonical[ignore_len] == '\0')) { + goto ignore; + } + } + + if (!(conf->sloppiness & SLOPPY_INCLUDE_FILE_MTIME) + && st.st_mtime >= time_of_compilation) { + cc_log("Include file %s too new", path); + goto failure; + } + + if (!(conf->sloppiness & SLOPPY_INCLUDE_FILE_CTIME) + && st.st_ctime >= time_of_compilation) { + cc_log("Include file %s ctime too new", path); + goto failure; + } + + // Let's hash the include file content. + struct mdfour fhash; + hash_start(&fhash); + + bool is_pch = is_precompiled_header(path); + if (is_pch) { + if (!hash_file(&fhash, path)) { + goto failure; + } + struct file_hash pch_hash; + hash_result_as_bytes(&fhash, pch_hash.hash); + pch_hash.size = fhash.totalN; + hash_delimiter(cpp_hash, "pch_hash"); + hash_buffer(cpp_hash, pch_hash.hash, sizeof(pch_hash.hash)); + } + + if (conf->direct_mode) { + if (!is_pch) { // else: the file has already been hashed. + char *source = NULL; + size_t size; + if (st.st_size > 0) { + if (!read_file(path, st.st_size, &source, &size)) { + goto failure; + } + } else { + source = x_strdup(""); + size = 0; + } + + int result = hash_source_code_string(conf, &fhash, source, size, path); + free(source); + if (result & HASH_SOURCE_CODE_ERROR + || result & HASH_SOURCE_CODE_FOUND_TIME) { + goto failure; + } + } + + struct file_hash *h = x_malloc(sizeof(*h)); + hash_result_as_bytes(&fhash, h->hash); + h->size = fhash.totalN; + hashtable_insert(included_files, path, h); + } else { + free(path); + } + + return; + +failure: + if (conf->direct_mode) { + cc_log("Disabling direct mode"); + conf->direct_mode = false; + } + // Fall through. +ignore: + free(path); +} + +// Make a relative path from current working directory to path if path is under +// the base directory. Takes over ownership of path. Caller frees. +static char * +make_relative_path(char *path) +{ + if (str_eq(conf->base_dir, "") || !str_startswith(path, conf->base_dir)) { + return path; + } + +#ifdef _WIN32 + if (path[0] == '/') { + path++; // Skip leading slash. + } +#endif + + // x_realpath only works for existing paths, so if path doesn't exist, try + // dirname(path) and assemble the path afterwards. We only bother to try + // canonicalizing one of these two paths since a compiler path argument + // typically only makes sense if path or dirname(path) exists. + char *path_suffix = NULL; + struct stat st; + if (stat(path, &st) != 0) { + // path doesn't exist. + char *dir = dirname(path); + if (stat(dir, &st) != 0) { + // And neither does its parent directory, so no action to take. + free(dir); + return path; + } + free(dir); + path_suffix = basename(path); + char *p = path; + path = dirname(path); + free(p); + } + + char *canon_path = x_realpath(path); + if (canon_path) { + free(path); + char *relpath = get_relative_path(get_current_working_dir(), canon_path); + free(canon_path); + if (path_suffix) { + path = format("%s/%s", relpath, path_suffix); + free(relpath); + free(path_suffix); + return path; + } else { + return relpath; + } + } else { + // path doesn't exist, so leave it as it is. + free(path_suffix); + return path; + } +} + +// This function reads and hashes a file. While doing this, it also does these +// things: +// +// - Makes include file paths for which the base directory is a prefix relative +// when computing the hash sum. +// - Stores the paths and hashes of included files in the global variable +// included_files. +static bool +process_preprocessed_file(struct mdfour *hash, const char *path) +{ + char *data; + size_t size; + if (!read_file(path, 0, &data, &size)) { + return false; + } + + ignore_headers = NULL; + ignore_headers_len = 0; + if (!str_eq(conf->ignore_headers_in_manifest, "")) { + char *header, *p, *q, *saveptr = NULL; + p = x_strdup(conf->ignore_headers_in_manifest); + q = p; + while ((header = strtok_r(q, PATH_DELIM, &saveptr))) { + ignore_headers = x_realloc(ignore_headers, + (ignore_headers_len+1) * sizeof(char *)); + ignore_headers[ignore_headers_len++] = x_strdup(header); + q = NULL; + } + free(p); + } + + if (!included_files) { + included_files = create_hashtable(1000, hash_from_string, strings_equal); + } + + // Bytes between p and q are pending to be hashed. + char *p = data; + char *q = data; + char *end = data + size; + + // There must be at least 7 characters (# 1 "x") left to potentially find an + // include file path. + while (q < end - 7) { + // Check if we look at a line containing the file name of an included file. + // At least the following formats exist (where N is a positive integer): + // + // GCC: + // + // # N "file" + // # N "file" N + // #pragma GCC pch_preprocess "file" + // + // HP's compiler: + // + // #line N "file" + // + // AIX's compiler: + // + // #line N "file" + // #line N + // + // Note that there may be other lines starting with '#' left after + // preprocessing as well, for instance "# pragma". + if (q[0] == '#' + // GCC: + && ((q[1] == ' ' && q[2] >= '0' && q[2] <= '9') + // GCC precompiled header: + || (q[1] == 'p' + && str_startswith(&q[2], "ragma GCC pch_preprocess ")) + // HP/AIX: + || (q[1] == 'l' && q[2] == 'i' && q[3] == 'n' && q[4] == 'e' + && q[5] == ' ')) + && (q == data || q[-1] == '\n')) { + // Workarounds for preprocessor linemarker bugs in GCC version 6. + if (q[2] == '3') { + if (str_startswith(q, "# 31 \"<command-line>\"\n")) { + // Bogus extra line with #31, after the regular #1: Ignore the whole + // line, and continue parsing. + hash_buffer(hash, p, q - p); + while (q < end && *q != '\n') { + q++; + } + q++; + p = q; + continue; + } else if (str_startswith(q, "# 32 \"<command-line>\" 2\n")) { + // Bogus wrong line with #32, instead of regular #1: Replace the line + // number with the usual one. + hash_buffer(hash, p, q - p); + q += 1; + q[0] = '#'; + q[1] = ' '; + q[2] = '1'; + p = q; + } + } + + while (q < end && *q != '"' && *q != '\n') { + q++; + } + if (q < end && *q == '\n') { + // A newline before the quotation mark -> no match. + continue; + } + q++; + if (q >= end) { + cc_log("Failed to parse included file path"); + free(data); + return false; + } + // q points to the beginning of an include file path + hash_buffer(hash, p, q - p); + p = q; + while (q < end && *q != '"') { + q++; + } + // Look for preprocessor flags, after the "filename". + bool system = false; + char *r = q + 1; + while (r < end && *r != '\n') { + if (*r == '3') { // System header. + system = true; + } + r++; + } + // p and q span the include file path. + char *inc_path = x_strndup(p, q - p); + if (!has_absolute_include_headers) { + has_absolute_include_headers = is_absolute_path(inc_path); + } + inc_path = make_relative_path(inc_path); + + bool should_hash_inc_path = true; + if (!conf->hash_dir) { + char *cwd = gnu_getcwd(); + if (str_startswith(inc_path, cwd) && str_endswith(inc_path, "//")) { + // When compiling with -g or similar, GCC adds the absolute path to + // CWD like this: + // + // # 1 "CWD//" + // + // If the user has opted out of including the CWD in the hash, don't + // hash it. See also how debug_prefix_map is handled. + should_hash_inc_path = false; + } + free(cwd); + } + if (should_hash_inc_path) { + hash_string(hash, inc_path); + } + + remember_include_file(inc_path, hash, system); + p = q; // Everything of interest between p and q has been hashed now. + } else if (q[0] == '.' && q[1] == 'i' && q[2] == 'n' && q[3] == 'c' + && q[4] == 'b' && q[5] == 'i' && q[6] == 'n') { + // An assembler .incbin statement (which could be part of inline + // assembly) refers to an external file. If the file changes, the hash + // should change as well, but finding out what file to hash is too hard + // for ccache, so just bail out. + cc_log("Found unsupported .incbin directive in source code"); + stats_update(STATS_UNSUPPORTED_DIRECTIVE); + failed(); + } else { + q++; + } + } + + hash_buffer(hash, p, (end - p)); + free(data); + + // Explicitly check the .gch/.pch/.pth file, Clang does not include any + // mention of it in the preprocessed output. + if (included_pch_file) { + char *path = x_strdup(included_pch_file); + path = make_relative_path(path); + hash_string(hash, path); + remember_include_file(path, hash, false); + } + + return true; +} + +// Replace absolute paths with relative paths in the provided dependency file. +static void +use_relative_paths_in_depfile(const char *depfile) +{ + if (str_eq(conf->base_dir, "")) { + cc_log("Base dir not set, skip using relative paths"); + return; // nothing to do + } + if (!has_absolute_include_headers) { + cc_log("No absolute path for included files found, skip using relative" + " paths"); + return; // nothing to do + } + + FILE *f; + f = fopen(depfile, "r"); + if (!f) { + cc_log("Cannot open dependency file: %s (%s)", depfile, strerror(errno)); + return; + } + + char *tmp_file = format("%s.tmp", depfile); + FILE *tmpf = create_tmp_file(&tmp_file, "w"); + + bool result = false; + char buf[10000]; + while (fgets(buf, sizeof(buf), f) && !ferror(tmpf)) { + char *saveptr; + char *token = strtok_r(buf, " \t", &saveptr); + while (token) { + char *relpath; + if (is_absolute_path(token) && str_startswith(token, conf->base_dir)) { + relpath = make_relative_path(x_strdup(token)); + result = true; + } else { + relpath = token; + } + if (token != buf) { // This is a dependency file. + fputc(' ', tmpf); + } + fputs(relpath, tmpf); + if (relpath != token) { + free(relpath); + } + token = strtok_r(NULL, " \t", &saveptr); + } + } + + if (ferror(f)) { + cc_log("Error reading dependency file: %s, skip relative path usage", + depfile); + result = false; + goto out; + } + if (ferror(tmpf)) { + cc_log("Error writing temporary dependency file: %s, skip relative path" + " usage", tmp_file); + result = false; + goto out; + } + +out: + fclose(tmpf); + fclose(f); + if (result) { + if (x_rename(tmp_file, depfile) != 0) { + cc_log("Error renaming dependency file: %s -> %s (%s), skip relative" + " path usage", tmp_file, depfile, strerror(errno)); + result = false; + } else { + cc_log("Renamed dependency file: %s -> %s", tmp_file, depfile); + } + } + if (!result) { + cc_log("Removing temporary dependency file: %s", tmp_file); + x_unlink(tmp_file); + } + free(tmp_file); +} + +// Copy or link a file to the cache. +static void +put_file_in_cache(const char *source, const char *dest) +{ + assert(!conf->read_only); + assert(!conf->read_only_direct); + + bool do_link = conf->hard_link && !conf->compression; + if (do_link) { + x_unlink(dest); + int ret = link(source, dest); + if (ret != 0) { + cc_log("Failed to link %s to %s: %s", source, dest, strerror(errno)); + cc_log("Falling back to copying"); + do_link = false; + } + } + if (!do_link) { + int ret = copy_file( + source, dest, conf->compression ? conf->compression_level : 0); + if (ret != 0) { + cc_log("Failed to copy %s to %s: %s", source, dest, strerror(errno)); + stats_update(STATS_ERROR); + failed(); + } + } + + cc_log("Stored in cache: %s -> %s", source, dest); + + struct stat st; + if (x_stat(dest, &st) != 0) { + stats_update(STATS_ERROR); + failed(); + } + stats_update_size(file_size(&st), 1); +} + +// Copy or link a file from the cache. +static void +get_file_from_cache(const char *source, const char *dest) +{ + int ret; + bool do_link = conf->hard_link && !file_is_compressed(source); + if (do_link) { + x_unlink(dest); + ret = link(source, dest); + } else { + ret = copy_file(source, dest, 0); + } + + if (ret == -1) { + if (errno == ENOENT || errno == ESTALE) { + // Someone removed the file just before we began copying? + cc_log("Cache file %s just disappeared from cache", source); + stats_update(STATS_MISSING); + } else { + cc_log("Failed to %s %s to %s: %s", + do_link ? "link" : "copy", + source, + dest, + strerror(errno)); + stats_update(STATS_ERROR); + } + + // If there was trouble getting a file from the cached result, wipe the + // whole cached result for consistency. + x_unlink(cached_stderr); + x_unlink(cached_obj); + x_unlink(cached_dep); + x_unlink(cached_dia); + + failed(); + } + + cc_log("Created from cache: %s -> %s", source, dest); +} + +// Send cached stderr, if any, to stderr. +static void +send_cached_stderr(void) +{ + int fd_stderr = open(cached_stderr, O_RDONLY | O_BINARY); + if (fd_stderr != -1) { + copy_fd(fd_stderr, 2); + close(fd_stderr); + } +} + +// Create or update the manifest file. +void update_manifest_file(void) +{ + if (!conf->direct_mode + || !included_files + || conf->read_only + || conf->read_only_direct) { + return; + } + + struct stat st; + size_t old_size = 0; // in bytes + if (stat(manifest_path, &st) == 0) { + old_size = file_size(&st); + } + if (manifest_put(manifest_path, cached_obj_hash, included_files)) { + cc_log("Added object file hash to %s", manifest_path); + update_mtime(manifest_path); + if (x_stat(manifest_path, &st) == 0) { + stats_update_size(file_size(&st) - old_size, old_size == 0 ? 1 : 0); + } + } else { + cc_log("Failed to add object file hash to %s", manifest_path); + } +} + +// Run the real compiler and put the result in cache. +static void +to_cache(struct args *args) +{ + char *tmp_stdout = format("%s.tmp.stdout", cached_obj); + int tmp_stdout_fd = create_tmp_fd(&tmp_stdout); + char *tmp_stderr = format("%s.tmp.stderr", cached_obj); + int tmp_stderr_fd = create_tmp_fd(&tmp_stderr); + + char *tmp_cov; + if (generating_coverage) { + char *tmp_aux; + // GCC has some funny rule about max extension length. + if (strlen(get_extension(output_obj)) < 6) { + tmp_aux = remove_extension(output_obj); + } else { + tmp_aux = x_strdup(output_obj); + } + tmp_cov = format("%s.gcno", tmp_aux); + free(tmp_aux); + } else { + tmp_cov = NULL; + } + + // GCC (at least 4.8 and 4.9) forms the .dwo file name by removing everything + // after (and including) the last "." from the object file name and then + // appending ".dwo". + char *tmp_dwo = NULL; + if (using_split_dwarf) { + char *base_name = remove_extension(output_obj); + tmp_dwo = format("%s.dwo", base_name); + free(base_name); + } + + args_add(args, "-o"); + args_add(args, output_obj); + + if (output_dia) { + args_add(args, "--serialize-diagnostics"); + args_add(args, output_dia); + } + + // Turn off DEPENDENCIES_OUTPUT when running cc1, because otherwise it will + // emit a line like this: + // + // tmp.stdout.vexed.732.o: /home/mbp/.ccache/tmp.stdout.vexed.732.i + x_unsetenv("DEPENDENCIES_OUTPUT"); + + if (conf->run_second_cpp) { + args_add(args, input_file); + } else { + args_add(args, i_tmpfile); + } + + cc_log("Running real compiler"); + int status = + execute(args->argv, tmp_stdout_fd, tmp_stderr_fd, &compiler_pid); + args_pop(args, 3); + + struct stat st; + if (x_stat(tmp_stdout, &st) != 0) { + // The stdout file was removed - cleanup in progress? Better bail out. + stats_update(STATS_MISSING); + tmp_unlink(tmp_stdout); + tmp_unlink(tmp_stderr); + if (tmp_cov) { + tmp_unlink(tmp_cov); + } + if (tmp_dwo) { + tmp_unlink(tmp_dwo); + } + failed(); + } + if (st.st_size != 0) { + cc_log("Compiler produced stdout"); + stats_update(STATS_STDOUT); + tmp_unlink(tmp_stdout); + tmp_unlink(tmp_stderr); + if (tmp_cov) { + tmp_unlink(tmp_cov); + } + if (tmp_dwo) { + tmp_unlink(tmp_dwo); + } + failed(); + } + tmp_unlink(tmp_stdout); + + // Merge stderr from the preprocessor (if any) and stderr from the real + // compiler into tmp_stderr. + if (cpp_stderr) { + char *tmp_stderr2 = format("%s.2", tmp_stderr); + if (x_rename(tmp_stderr, tmp_stderr2)) { + cc_log("Failed to rename %s to %s: %s", tmp_stderr, tmp_stderr2, + strerror(errno)); + failed(); + } + + int fd_cpp_stderr = open(cpp_stderr, O_RDONLY | O_BINARY); + if (fd_cpp_stderr == -1) { + cc_log("Failed opening %s: %s", cpp_stderr, strerror(errno)); + failed(); + } + + int fd_real_stderr = open(tmp_stderr2, O_RDONLY | O_BINARY); + if (fd_real_stderr == -1) { + cc_log("Failed opening %s: %s", tmp_stderr2, strerror(errno)); + failed(); + } + + int fd_result = + open(tmp_stderr, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666); + if (fd_result == -1) { + cc_log("Failed opening %s: %s", tmp_stderr, strerror(errno)); + failed(); + } + + copy_fd(fd_cpp_stderr, fd_result); + copy_fd(fd_real_stderr, fd_result); + close(fd_cpp_stderr); + close(fd_real_stderr); + close(fd_result); + tmp_unlink(tmp_stderr2); + free(tmp_stderr2); + } + + if (status != 0) { + cc_log("Compiler gave exit status %d", status); + stats_update(STATS_STATUS); + + int fd = open(tmp_stderr, O_RDONLY | O_BINARY); + if (fd != -1) { + // We can output stderr immediately instead of rerunning the compiler. + copy_fd(fd, 2); + close(fd); + tmp_unlink(tmp_stderr); + + x_exit(status); + } + + tmp_unlink(tmp_stderr); + if (tmp_cov) { + tmp_unlink(tmp_cov); + } + if (tmp_dwo) { + tmp_unlink(tmp_dwo); + } + + failed(); + } + + if (stat(output_obj, &st) != 0) { + cc_log("Compiler didn't produce an object file"); + stats_update(STATS_NOOUTPUT); + failed(); + } + if (st.st_size == 0) { + cc_log("Compiler produced an empty object file"); + stats_update(STATS_EMPTYOUTPUT); + failed(); + } + + if (using_split_dwarf) { + if (stat(tmp_dwo, &st) != 0) { + cc_log("Compiler didn't produce a split dwarf file"); + stats_update(STATS_NOOUTPUT); + failed(); + } + if (st.st_size == 0) { + cc_log("Compiler produced an empty split dwarf file"); + stats_update(STATS_EMPTYOUTPUT); + failed(); + } + } + + if (x_stat(tmp_stderr, &st) != 0) { + stats_update(STATS_ERROR); + failed(); + } + if (st.st_size > 0) { + if (move_uncompressed_file( + tmp_stderr, cached_stderr, + conf->compression ? conf->compression_level : 0) != 0) { + cc_log("Failed to move %s to %s: %s", tmp_stderr, cached_stderr, + strerror(errno)); + stats_update(STATS_ERROR); + failed(); + } + cc_log("Stored in cache: %s", cached_stderr); + if (!conf->compression + // If the file was compressed, obtain the size again: + || x_stat(cached_stderr, &st) == 0) { + stats_update_size(file_size(&st), 1); + } + } else { + tmp_unlink(tmp_stderr); + if (conf->recache) { + // If recaching, we need to remove any previous .stderr. + x_unlink(cached_stderr); + } + } + + if (generating_coverage) { + // GCC won't generate notes if there is no code. + if (stat(tmp_cov, &st) != 0 && errno == ENOENT) { + FILE *f = fopen(cached_cov, "wb"); + cc_log("Creating placeholder: %s", cached_cov); + if (!f) { + cc_log("Failed to create %s: %s", cached_cov, strerror(errno)); + stats_update(STATS_ERROR); + failed(); + } + fclose(f); + stats_update_size(0, 1); + } else { + put_file_in_cache(tmp_cov, cached_cov); + } + } + + if (output_dia) { + if (x_stat(output_dia, &st) != 0) { + stats_update(STATS_ERROR); + failed(); + } + if (st.st_size > 0) { + put_file_in_cache(output_dia, cached_dia); + } + } + + put_file_in_cache(output_obj, cached_obj); + + if (using_split_dwarf) { + assert(tmp_dwo); + assert(cached_dwo); + put_file_in_cache(tmp_dwo, cached_dwo); + } + + if (generating_dependencies) { + use_relative_paths_in_depfile(output_dep); + put_file_in_cache(output_dep, cached_dep); + } + stats_update(STATS_TOCACHE); + + // Make sure we have a CACHEDIR.TAG in the cache part of cache_dir. This can + // be done almost anywhere, but we might as well do it near the end as we + // save the stat call if we exit early. + { + char *first_level_dir = dirname(stats_file); + if (create_cachedirtag(first_level_dir) != 0) { + cc_log("Failed to create %s/CACHEDIR.TAG (%s)\n", + first_level_dir, strerror(errno)); + stats_update(STATS_ERROR); + failed(); + } + free(first_level_dir); + + // Remove any CACHEDIR.TAG on the cache_dir level where it was located in + // previous ccache versions. + if (getpid() % 1000 == 0) { + char *path = format("%s/CACHEDIR.TAG", conf->cache_dir); + x_unlink(path); + free(path); + } + } + + // Everything OK. + send_cached_stderr(); + update_manifest_file(); + + free(tmp_stderr); + free(tmp_stdout); + free(tmp_cov); + free(tmp_dwo); +} + +// Find the object file name by running the compiler in preprocessor mode. +// Returns the hash as a heap-allocated hex string. +static struct file_hash * +get_object_name_from_cpp(struct args *args, struct mdfour *hash) +{ + time_of_compilation = time(NULL); + + char *path_stderr = format("%s/tmp.cpp_stderr", temp_dir()); + int path_stderr_fd = create_tmp_fd(&path_stderr); + add_pending_tmp_file(path_stderr); + + char *path_stdout; + int status; + if (direct_i_file) { + // We are compiling a .i or .ii file - that means we can skip the cpp stage + // and directly form the correct i_tmpfile. + path_stdout = input_file; + status = 0; + } else { + // Run cpp on the input file to obtain the .i. + + // Limit the basename to 10 characters in order to cope with filesystem with + // small maximum filename length limits. + char *input_base = basename(input_file); + char *tmp = strchr(input_base, '.'); + if (tmp) { + *tmp = 0; + } + if (strlen(input_base) > 10) { + input_base[10] = 0; + } + + path_stdout = format("%s/%s.stdout", temp_dir(), input_base); + int path_stdout_fd = create_tmp_fd(&path_stdout); + add_pending_tmp_file(path_stdout); + + int args_added = 2; + args_add(args, "-E"); + if (conf->keep_comments_cpp) { + args_add(args, "-C"); + args_added = 3; + } + args_add(args, input_file); + add_prefix(args, conf->prefix_command_cpp); + cc_log("Running preprocessor"); + status = execute(args->argv, path_stdout_fd, path_stderr_fd, &compiler_pid); + args_pop(args, args_added); + } + + if (status != 0) { + cc_log("Preprocessor gave exit status %d", status); + stats_update(STATS_PREPROCESSOR); + failed(); + } + + if (conf->unify) { + // When we are doing the unifying tricks we need to include the input file + // name in the hash to get the warnings right. + hash_delimiter(hash, "unifyfilename"); + hash_string(hash, input_file); + + hash_delimiter(hash, "unifycpp"); + if (unify_hash(hash, path_stdout) != 0) { + stats_update(STATS_ERROR); + cc_log("Failed to unify %s", path_stdout); + failed(); + } + } else { + hash_delimiter(hash, "cpp"); + if (!process_preprocessed_file(hash, path_stdout)) { + stats_update(STATS_ERROR); + failed(); + } + } + + hash_delimiter(hash, "cppstderr"); + if (!hash_file(hash, path_stderr)) { + fatal("Failed to open %s: %s", path_stderr, strerror(errno)); + } + + if (direct_i_file) { + i_tmpfile = input_file; + } else { + // i_tmpfile needs the proper cpp_extension for the compiler to do its + // thing correctly + i_tmpfile = format("%s.%s", path_stdout, conf->cpp_extension); + x_rename(path_stdout, i_tmpfile); + add_pending_tmp_file(i_tmpfile); + } + + if (conf->run_second_cpp) { + free(path_stderr); + } else { + // If we are using the CPP trick, we need to remember this stderr data and + // output it just before the main stderr from the compiler pass. + cpp_stderr = path_stderr; + hash_delimiter(hash, "runsecondcpp"); + hash_string(hash, "false"); + } + + struct file_hash *result = x_malloc(sizeof(*result)); + hash_result_as_bytes(hash, result->hash); + result->size = hash->totalN; + return result; +} + +static void +update_cached_result_globals(struct file_hash *hash) +{ + char *object_name = format_hash_as_string(hash->hash, hash->size); + cached_obj_hash = hash; + cached_obj = get_path_in_cache(object_name, ".o"); + cached_stderr = get_path_in_cache(object_name, ".stderr"); + cached_dep = get_path_in_cache(object_name, ".d"); + cached_cov = get_path_in_cache(object_name, ".gcno"); + cached_dia = get_path_in_cache(object_name, ".dia"); + + if (using_split_dwarf) { + cached_dwo = get_path_in_cache(object_name, ".dwo"); + } else { + cached_dwo = NULL; + } + + stats_file = format("%s/%c/stats", conf->cache_dir, object_name[0]); + free(object_name); +} + +// Hash mtime or content of a file, or the output of a command, according to +// the CCACHE_COMPILERCHECK setting. +static void +hash_compiler(struct mdfour *hash, struct stat *st, const char *path, + bool allow_command) +{ + if (str_eq(conf->compiler_check, "none")) { + // Do nothing. + } else if (str_eq(conf->compiler_check, "mtime")) { + hash_delimiter(hash, "cc_mtime"); + hash_int(hash, st->st_size); + hash_int(hash, st->st_mtime); + } else if (str_startswith(conf->compiler_check, "string:")) { + hash_delimiter(hash, "cc_hash"); + hash_string(hash, conf->compiler_check + strlen("string:")); + } else if (str_eq(conf->compiler_check, "content") || !allow_command) { + hash_delimiter(hash, "cc_content"); + hash_file(hash, path); + } else { // command string + if (!hash_multicommand_output( + hash, conf->compiler_check, orig_args->argv[0])) { + fatal("Failure running compiler check command: %s", conf->compiler_check); + } + } +} + +// Note that these compiler checks are unreliable, so nothing should +// hard-depend on them. + +static bool +compiler_is_clang(struct args *args) +{ + char *name = basename(args->argv[0]); + bool result = strstr(name, "clang") != NULL; + free(name); + return result; +} + +static bool +compiler_is_gcc(struct args *args) +{ + char *name = basename(args->argv[0]); + bool result = strstr(name, "gcc") || strstr(name, "g++"); + free(name); + return result; +} + +// Update a hash sum with information common for the direct and preprocessor +// modes. +static void +calculate_common_hash(struct args *args, struct mdfour *hash) +{ + hash_string(hash, HASH_PREFIX); + + // We have to hash the extension, as a .i file isn't treated the same by the + // compiler as a .ii file. + hash_delimiter(hash, "ext"); + hash_string(hash, conf->cpp_extension); + +#ifdef _WIN32 + const char *ext = strrchr(args->argv[0], '.'); + char full_path_win_ext[MAX_PATH + 1] = {0}; + add_exe_ext_if_no_to_fullpath(full_path_win_ext, MAX_PATH, ext, + args->argv[0]); + const char *full_path = full_path_win_ext; +#else + const char *full_path = args->argv[0]; +#endif + + struct stat st; + if (x_stat(full_path, &st) != 0) { + stats_update(STATS_COMPILER); + failed(); + } + + // Hash information about the compiler. + hash_compiler(hash, &st, args->argv[0], true); + + // Also hash the compiler name as some compilers use hard links and behave + // differently depending on the real name. + hash_delimiter(hash, "cc_name"); + char *p = basename(args->argv[0]); + hash_string(hash, p); + free(p); + + // Possibly hash the current working directory. + if (generating_debuginfo && conf->hash_dir) { + char *cwd = gnu_getcwd(); + if (debug_prefix_map) { + char *map = debug_prefix_map; + char *sep = strchr(map, '='); + if (sep) { + char *old = x_strndup(map, sep - map); + char *new = x_strdup(sep + 1); + cc_log("Relocating debuginfo cwd %s, from %s to %s", cwd, old, new); + if (str_startswith(cwd, old)) { + char *dir = format("%s%s", new, cwd + strlen(old)); + free(cwd); + cwd = dir; + } + free(old); + free(new); + } + } + if (cwd) { + hash_delimiter(hash, "cwd"); + hash_string(hash, cwd); + free(cwd); + } + } + + // Possibly hash the coverage data file path. + if (generating_coverage && profile_arcs) { + char *dir = dirname(output_obj); + if (profile_dir) { + dir = x_strdup(profile_dir); + } else { + char *real_dir = x_realpath(dir); + free(dir); + dir = real_dir; + } + if (dir) { + char *base_name = basename(output_obj); + p = remove_extension(base_name); + free(base_name); + char *gcda_path = format("%s/%s.gcda", dir, p); + cc_log("Hashing coverage path %s", gcda_path); + free(p); + hash_delimiter(hash, "gcda"); + hash_string(hash, gcda_path); + free(dir); + } + } + + if (!str_eq(conf->extra_files_to_hash, "")) { + char *p = x_strdup(conf->extra_files_to_hash); + char *q = p; + char *path; + char *saveptr = NULL; + while ((path = strtok_r(q, PATH_DELIM, &saveptr))) { + cc_log("Hashing extra file %s", path); + hash_delimiter(hash, "extrafile"); + if (!hash_file(hash, path)) { + stats_update(STATS_BADEXTRAFILE); + failed(); + } + q = NULL; + } + free(p); + } + + // Possibly hash GCC_COLORS (for color diagnostics). + if (compiler_is_gcc(args)) { + const char *gcc_colors = getenv("GCC_COLORS"); + if (gcc_colors) { + hash_delimiter(hash, "gcccolors"); + hash_string(hash, gcc_colors); + } + } +} + +// Update a hash sum with information specific to the direct and preprocessor +// modes and calculate the object hash. Returns the object hash on success, +// otherwise NULL. Caller frees. +static struct file_hash * +calculate_object_hash(struct args *args, struct mdfour *hash, int direct_mode) +{ + if (direct_mode) { + hash_delimiter(hash, "manifest version"); + hash_int(hash, MANIFEST_VERSION); + } + + // First the arguments. + for (int i = 1; i < args->argc; i++) { + // -L doesn't affect compilation. + if (i < args->argc-1 && str_eq(args->argv[i], "-L")) { + i++; + continue; + } + if (str_startswith(args->argv[i], "-L")) { + continue; + } + + // -Wl,... doesn't affect compilation. + if (str_startswith(args->argv[i], "-Wl,")) { + continue; + } + + // The -fdebug-prefix-map option may be used in combination with + // CCACHE_BASEDIR to reuse results across different directories. Skip it + // from hashing. + if (str_startswith(args->argv[i], "-fdebug-prefix-map=")) { + continue; + } + + // When using the preprocessor, some arguments don't contribute to the + // hash. The theory is that these arguments will change the output of -E if + // they are going to have any effect at all. For precompiled headers this + // might not be the case. + if (!direct_mode && !output_is_precompiled_header + && !using_precompiled_header) { + if (compopt_affects_cpp(args->argv[i])) { + i++; + continue; + } + if (compopt_short(compopt_affects_cpp, args->argv[i])) { + continue; + } + } + + // If we're generating dependencies, we make sure to skip the filename of + // the dependency file, since it doesn't impact the output. + if (generating_dependencies) { + if (str_startswith(args->argv[i], "-Wp,")) { + if (str_startswith(args->argv[i], "-Wp,-MD,") + && !strchr(args->argv[i] + 8, ',')) { + hash_string_length(hash, args->argv[i], 8); + continue; + } else if (str_startswith(args->argv[i], "-Wp,-MMD,") + && !strchr(args->argv[i] + 9, ',')) { + hash_string_length(hash, args->argv[i], 9); + continue; + } + } else if (str_startswith(args->argv[i], "-MF")) { + // In either case, hash the "-MF" part. + hash_string_length(hash, args->argv[i], 3); + + bool separate_argument = (strlen(args->argv[i]) == 3); + if (separate_argument) { + // Next argument is dependency name, so skip it. + i++; + } + continue; + } + } + + char *p = NULL; + if (str_startswith(args->argv[i], "-specs=")) { + p = args->argv[i] + 7; + } else if (str_startswith(args->argv[i], "--specs=")) { + p = args->argv[i] + 8; + } + + struct stat st; + if (p && x_stat(p, &st) == 0) { + // If given an explicit specs file, then hash that file, but don't + // include the path to it in the hash. + hash_delimiter(hash, "specs"); + hash_compiler(hash, &st, p, false); + continue; + } + + if (str_startswith(args->argv[i], "-fplugin=") + && x_stat(args->argv[i] + 9, &st) == 0) { + hash_delimiter(hash, "plugin"); + hash_compiler(hash, &st, args->argv[i] + 9, false); + continue; + } + + if (str_eq(args->argv[i], "-Xclang") + && i + 3 < args->argc + && str_eq(args->argv[i+1], "-load") + && str_eq(args->argv[i+2], "-Xclang") + && x_stat(args->argv[i+3], &st) == 0) { + hash_delimiter(hash, "plugin"); + hash_compiler(hash, &st, args->argv[i+3], false); + i += 3; + continue; + } + + // All other arguments are included in the hash. + hash_delimiter(hash, "arg"); + hash_string(hash, args->argv[i]); + if (i + 1 < args->argc && compopt_takes_arg(args->argv[i])) { + i++; + hash_delimiter(hash, "arg"); + hash_string(hash, args->argv[i]); + } + } + + // For profile generation (-fprofile-arcs, -fprofile-generate): + // - hash profile directory + // + // For profile usage (-fprofile-use): + // - hash profile data + // + // -fbranch-probabilities and -fvpt usage is covered by + // -fprofile-generate/-fprofile-use. + // + // The profile directory can be specified as an argument to + // -fprofile-generate=, -fprofile-use= or -fprofile-dir=. + if (profile_generate) { + if (!profile_dir) { + profile_dir = get_cwd(); + } + cc_log("Adding profile directory %s to our hash", profile_dir); + hash_delimiter(hash, "-fprofile-dir"); + hash_string(hash, profile_dir); + } + + if (profile_use) { + // Calculate gcda name. + if (!profile_dir) { + profile_dir = get_cwd(); + } + char *base_name = remove_extension(output_obj); + char *gcda_name = format("%s/%s.gcda", profile_dir, base_name); + cc_log("Adding profile data %s to our hash", gcda_name); + // Add the gcda to our hash. + hash_delimiter(hash, "-fprofile-use"); + hash_file(hash, gcda_name); + free(base_name); + free(gcda_name); + } + + // Adding -arch to hash since cpp output is affected. + for (size_t i = 0; i < arch_args_size; ++i) { + hash_delimiter(hash, "-arch"); + hash_string(hash, arch_args[i]); + } + + struct file_hash *object_hash = NULL; + if (direct_mode) { + // Hash environment variables that affect the preprocessor output. + const char *envvars[] = { + "CPATH", + "C_INCLUDE_PATH", + "CPLUS_INCLUDE_PATH", + "OBJC_INCLUDE_PATH", + "OBJCPLUS_INCLUDE_PATH", // clang + NULL + }; + for (const char **p = envvars; *p; ++p) { + char *v = getenv(*p); + if (v) { + hash_delimiter(hash, *p); + hash_string(hash, v); + } + } + + if (!(conf->sloppiness & SLOPPY_FILE_MACRO)) { + // The source code file or an include file may contain __FILE__, so make + // sure that the hash is unique for the file name. + hash_delimiter(hash, "inputfile"); + hash_string(hash, input_file); + } + + hash_delimiter(hash, "sourcecode"); + int result = hash_source_code_file(conf, hash, input_file); + if (result & HASH_SOURCE_CODE_ERROR) { + failed(); + } + if (result & HASH_SOURCE_CODE_FOUND_TIME) { + cc_log("Disabling direct mode"); + conf->direct_mode = false; + return NULL; + } + char *manifest_name = hash_result(hash); + manifest_path = get_path_in_cache(manifest_name, ".manifest"); + free(manifest_name); + cc_log("Looking for object file hash in %s", manifest_path); + object_hash = manifest_get(conf, manifest_path); + if (object_hash) { + cc_log("Got object file hash from manifest"); + } else { + cc_log("Did not find object file hash in manifest"); + } + } else { + if (arch_args_size == 0) { + object_hash = get_object_name_from_cpp(args, hash); + cc_log("Got object file hash from preprocessor"); + } else { + args_add(args, "-arch"); + for (size_t i = 0; i < arch_args_size; ++i) { + args_add(args, arch_args[i]); + object_hash = get_object_name_from_cpp(args, hash); + cc_log("Got object file hash from preprocessor with -arch %s", + arch_args[i]); + if (i != arch_args_size - 1) { + free(object_hash); + } + args_pop(args, 1); + } + args_pop(args, 1); + } + if (generating_dependencies) { + cc_log("Preprocessor created %s", output_dep); + } + } + + return object_hash; +} + +// Try to return the compile result from cache. If we can return from cache +// then this function exits with the correct status code, otherwise it returns. +static void +from_cache(enum fromcache_call_mode mode, bool put_object_in_manifest) +{ + // The user might be disabling cache hits. + if (conf->recache) { + return; + } + + struct stat st; + if (stat(cached_obj, &st) != 0) { + cc_log("Object file %s not in cache", cached_obj); + return; + } + + // Check if the diagnostic file is there. + if (output_dia && stat(cached_dia, &st) != 0) { + cc_log("Diagnostic file %s not in cache", cached_dia); + return; + } + + // Occasionally, e.g. on hard reset, our cache ends up as just filesystem + // meta-data with no content. Catch an easy case of this. + if (st.st_size == 0) { + cc_log("Invalid (empty) object file %s in cache", cached_obj); + x_unlink(cached_obj); + return; + } + + if (using_split_dwarf && !generating_dependencies) { + assert(output_dwo); + } + if (output_dwo) { + assert(cached_dwo); + if (stat(cached_dwo, &st) != 0) { + cc_log("Split dwarf file %s not in cache", cached_dwo); + return; + } + if (st.st_size == 0) { + cc_log("Invalid (empty) dwo file %s in cache", cached_dwo); + x_unlink(cached_dwo); + x_unlink(cached_obj); // To really invalidate. + return; + } + } + + // (If mode != FROMCACHE_DIRECT_MODE, the dependency file is created by gcc.) + bool produce_dep_file = + generating_dependencies && mode == FROMCACHE_DIRECT_MODE; + + // If the dependency file should be in the cache, check that it is. + if (produce_dep_file && stat(cached_dep, &st) != 0) { + cc_log("Dependency file %s missing in cache", cached_dep); + return; + } + + // Copy object file from cache. Do so also for FissionDwarf file, cached_dwo, + // when -gsplit-dwarf is specified. + if (!str_eq(output_obj, "/dev/null")) { + get_file_from_cache(cached_obj, output_obj); + if (using_split_dwarf) { + assert(output_dwo); + get_file_from_cache(cached_dwo, output_dwo); + } + } + if (produce_dep_file) { + get_file_from_cache(cached_dep, output_dep); + } + if (generating_coverage && stat(cached_cov, &st) == 0 && st.st_size > 0) { + // The compiler won't generate notes if there is no code + get_file_from_cache(cached_cov, output_cov); + } + if (output_dia) { + get_file_from_cache(cached_dia, output_dia); + } + + // Update modification timestamps to save files from LRU cleanup. Also gives + // files a sensible mtime when hard-linking. + update_mtime(cached_obj); + update_mtime(cached_stderr); + if (produce_dep_file) { + update_mtime(cached_dep); + } + if (generating_coverage) { + update_mtime(cached_cov); + } + if (output_dia) { + update_mtime(cached_dia); + } + if (cached_dwo) { + update_mtime(cached_dwo); + } + + if (generating_dependencies && mode == FROMCACHE_CPP_MODE + && !conf->read_only && !conf->read_only_direct) { + put_file_in_cache(output_dep, cached_dep); + } + + send_cached_stderr(); + + if (put_object_in_manifest) { + update_manifest_file(); + } + + // Log the cache hit. + switch (mode) { + case FROMCACHE_DIRECT_MODE: + cc_log("Succeeded getting cached result"); + stats_update(STATS_CACHEHIT_DIR); + break; + + case FROMCACHE_CPP_MODE: + cc_log("Succeeded getting cached result"); + stats_update(STATS_CACHEHIT_CPP); + break; + } + + // And exit with the right status code. + x_exit(0); +} + +// Find the real compiler. We just search the PATH to find an executable of the +// same name that isn't a link to ourselves. +static void +find_compiler(char **argv) +{ + // We might be being invoked like "ccache gcc -c foo.c". + char *base = basename(argv[0]); + if (same_executable_name(base, MYNAME)) { + args_remove_first(orig_args); + free(base); + if (is_full_path(orig_args->argv[0])) { + // A full path was given. + return; + } + base = basename(orig_args->argv[0]); + } + + // Support user override of the compiler. + if (!str_eq(conf->compiler, "")) { + base = conf->compiler; + } + + char *compiler = find_executable(base, MYNAME); + if (!compiler) { + stats_update(STATS_COMPILER); + fatal("Could not find compiler \"%s\" in PATH", base); + } + if (str_eq(compiler, argv[0])) { + fatal("Recursive invocation (the name of the ccache binary must be \"%s\")", + MYNAME); + } + orig_args->argv[0] = compiler; +} + +bool +is_precompiled_header(const char *path) +{ + return str_eq(get_extension(path), ".gch") + || str_eq(get_extension(path), ".pch") + || str_eq(get_extension(path), ".pth"); +} + +static bool +color_output_possible(void) +{ + const char *term_env = getenv("TERM"); + return isatty(STDERR_FILENO) && term_env && strcasecmp(term_env, "DUMB") != 0; +} + +static bool +detect_pch(const char *option, const char *arg, bool *found_pch) +{ + struct stat st; + + // Try to be smart about detecting precompiled headers. + char *pch_file = NULL; + if (str_eq(option, "-include-pch") || str_eq(option, "-include-pth")) { + if (stat(arg, &st) == 0) { + cc_log("Detected use of precompiled header: %s", arg); + pch_file = x_strdup(arg); + } + } else { + char *gchpath = format("%s.gch", arg); + if (stat(gchpath, &st) == 0) { + cc_log("Detected use of precompiled header: %s", gchpath); + pch_file = x_strdup(gchpath); + } else { + char *pchpath = format("%s.pch", arg); + if (stat(pchpath, &st) == 0) { + cc_log("Detected use of precompiled header: %s", pchpath); + pch_file = x_strdup(pchpath); + } else { + // clang may use pretokenized headers. + char *pthpath = format("%s.pth", arg); + if (stat(pthpath, &st) == 0) { + cc_log("Detected use of pretokenized header: %s", pthpath); + pch_file = x_strdup(pthpath); + } + free(pthpath); + } + free(pchpath); + } + free(gchpath); + } + + if (pch_file) { + if (included_pch_file) { + cc_log("Multiple precompiled headers used: %s and %s\n", + included_pch_file, pch_file); + stats_update(STATS_ARGS); + return false; + } + included_pch_file = pch_file; + *found_pch = true; + } + return true; +} + +// Process the compiler options into options suitable for passing to the +// preprocessor and the real compiler. The preprocessor options don't include +// -E; this is added later. Returns true on success, otherwise false. +bool +cc_process_args(struct args *args, struct args **preprocessor_args, + struct args **compiler_args) +{ + bool found_c_opt = false; + bool found_S_opt = false; + bool found_pch = false; + bool found_fpch_preprocess = false; + const char *explicit_language = NULL; // As specified with -x. + const char *file_language; // As deduced from file extension. + const char *actual_language; // Language to actually use. + const char *input_charset = NULL; + // Is the dependency makefile name overridden with -MF? + bool dependency_filename_specified = false; + // Is the dependency makefile target name specified with -MT or -MQ? + bool dependency_target_specified = false; + // expanded_args is a copy of the original arguments given to the compiler + // but with arguments from @file and similar constructs expanded. It's only + // used as a temporary data structure to loop over. + struct args *expanded_args = args_copy(args); + // stripped_args essentially contains all original arguments except those + // that only should be passed to the preprocessor (if run_second_cpp is + // false) and except dependency options (like -MD and friends). + struct args *stripped_args = args_init(0, NULL); + // cpp_args contains arguments that were not added to stripped_args, i.e. + // those that should only be passed to the preprocessor if run_second_cpp is + // false. If run_second_cpp is true, they will be passed to the compiler as + // well. + struct args *cpp_args = args_init(0, NULL); + // dep_args contains dependency options like -MD. They only passed to the + // preprocessor, never to the compiler. + struct args *dep_args = args_init(0, NULL); + + bool found_color_diagnostics = false; + int debug_level = 0; + const char *debug_argument = NULL; + + int argc = expanded_args->argc; + char **argv = expanded_args->argv; + args_add(stripped_args, argv[0]); + + bool result = true; + for (int i = 1; i < argc; i++) { + // The user knows best: just swallow the next arg. + if (str_eq(argv[i], "--ccache-skip")) { + i++; + if (i == argc) { + cc_log("--ccache-skip lacks an argument"); + result = false; + goto out; + } + args_add(stripped_args, argv[i]); + continue; + } + + // Special case for -E. + if (str_eq(argv[i], "-E")) { + stats_update(STATS_PREPROCESSING); + result = false; + goto out; + } + + // Handle "@file" argument. + if (str_startswith(argv[i], "@") || str_startswith(argv[i], "-@")) { + char *argpath = argv[i] + 1; + + if (argpath[-1] == '-') { + ++argpath; + } + struct args *file_args = args_init_from_gcc_atfile(argpath); + if (!file_args) { + cc_log("Couldn't read arg file %s", argpath); + stats_update(STATS_ARGS); + result = false; + goto out; + } + + args_insert(expanded_args, i, file_args, true); + argc = expanded_args->argc; + argv = expanded_args->argv; + i--; + continue; + } + + // Handle cuda "-optf" and "--options-file" argument. + if (str_eq(argv[i], "-optf") || str_eq(argv[i], "--options-file")) { + if (i > argc) { + cc_log("Expected argument after -optf/--options-file"); + stats_update(STATS_ARGS); + result = false; + goto out; + } + ++i; + + // Argument is a comma-separated list of files. + char *str_start = argv[i]; + char *str_end = strchr(str_start, ','); + int index = i + 1; + + if (!str_end) { + str_end = str_start + strlen(str_start); + } + + while (str_end) { + *str_end = '\0'; + struct args *file_args = args_init_from_gcc_atfile(str_start); + if (!file_args) { + cc_log("Couldn't read cuda options file %s", str_start); + stats_update(STATS_ARGS); + result = false; + goto out; + } + + int new_index = file_args->argc + index; + args_insert(expanded_args, index, file_args, false); + index = new_index; + str_start = str_end; + str_end = strchr(str_start, ','); + } + + argc = expanded_args->argc; + argv = expanded_args->argv; + continue; + } + + // These are always too hard. + if (compopt_too_hard(argv[i]) || str_startswith(argv[i], "-fdump-")) { + cc_log("Compiler option %s is unsupported", argv[i]); + stats_update(STATS_UNSUPPORTED_OPTION); + result = false; + goto out; + } + + // These are too hard in direct mode. + if (conf->direct_mode && compopt_too_hard_for_direct_mode(argv[i])) { + cc_log("Unsupported compiler option for direct mode: %s", argv[i]); + conf->direct_mode = false; + } + + // -Xarch_* options are too hard. + if (str_startswith(argv[i], "-Xarch_")) { + cc_log("Unsupported compiler option :%s", argv[i]); + stats_update(STATS_UNSUPPORTED_OPTION); + result = false; + goto out; + } + + // Handle -arch options. + if (str_eq(argv[i], "-arch")) { + if (arch_args_size == MAX_ARCH_ARGS - 1) { + cc_log("Too many -arch compiler options; ccache supports at most %d", + MAX_ARCH_ARGS); + stats_update(STATS_UNSUPPORTED_OPTION); + result = false; + goto out; + } + + ++i; + arch_args[arch_args_size] = x_strdup(argv[i]); // It will leak. + ++arch_args_size; + if (arch_args_size == 2) { + conf->run_second_cpp = true; + } + continue; + } + + if (str_eq(argv[i], "-fpch-preprocess") + || str_eq(argv[i], "-emit-pch") + || str_eq(argv[i], "-emit-pth")) { + found_fpch_preprocess = true; + } + + // We must have -c. + if (str_eq(argv[i], "-c")) { + found_c_opt = true; + continue; + } + + // -S changes the default extension. + if (str_eq(argv[i], "-S")) { + args_add(stripped_args, argv[i]); + found_S_opt = true; + continue; + } + + // Special handling for -x: remember the last specified language before the + // input file and strip all -x options from the arguments. + if (str_eq(argv[i], "-x")) { + if (i == argc-1) { + cc_log("Missing argument to %s", argv[i]); + stats_update(STATS_ARGS); + result = false; + goto out; + } + if (!input_file) { + explicit_language = argv[i+1]; + } + i++; + continue; + } + if (str_startswith(argv[i], "-x")) { + if (!input_file) { + explicit_language = &argv[i][2]; + } + continue; + } + + // We need to work out where the output was meant to go. + if (str_eq(argv[i], "-o")) { + if (i == argc-1) { + cc_log("Missing argument to %s", argv[i]); + stats_update(STATS_ARGS); + result = false; + goto out; + } + output_obj = make_relative_path(x_strdup(argv[i+1])); + i++; + continue; + } + + // Alternate form of -o with no space. + if (str_startswith(argv[i], "-o")) { + output_obj = make_relative_path(x_strdup(&argv[i][2])); + continue; + } + + if (str_eq(argv[i], "-gsplit-dwarf")) { + cc_log("Enabling caching of dwarf files since -gsplit-dwarf is used"); + using_split_dwarf = true; + args_add(stripped_args, argv[i]); + continue; + } + if (str_startswith(argv[i], "-fdebug-prefix-map=")) { + debug_prefix_map = x_strdup(argv[i] + 19); + args_add(stripped_args, argv[i]); + continue; + } + + // Debugging is handled specially, so that we know if we can strip line + // number info. + if (str_startswith(argv[i], "-g")) { + const char *pLevel = argv[i] + 2; + if (str_startswith(argv[i], "-ggdb")) { + pLevel = argv[i] + 5; + } else if (str_startswith(argv[i], "-gstabs")) { + pLevel = argv[i] + 7; + } else if (str_startswith(argv[i], "-gcoff")) { + pLevel = argv[i] + 6; + } else if (str_startswith(argv[i], "-gxcoff")) { + pLevel = argv[i] + 7; + } else if (str_startswith(argv[i], "-gvms")) { + pLevel = argv[i] + 5; + } + + // Deduce level from argument, default is 2. + int foundlevel = -1; + if (pLevel[0] == '\0') { + foundlevel = 2; + } else if (pLevel[0] >= '0' && pLevel[0] <= '9') { + foundlevel = atoi(pLevel); + } + + if (foundlevel >= 0) { + debug_level = foundlevel; + debug_argument = argv[i]; + continue; + } + } + + // These options require special handling, because they behave differently + // with gcc -E, when the output file is not specified. + if (str_eq(argv[i], "-MD") || str_eq(argv[i], "-MMD")) { + generating_dependencies = true; + args_add(dep_args, argv[i]); + continue; + } + if (str_startswith(argv[i], "-MF")) { + dependency_filename_specified = true; + free(output_dep); + + char *arg; + bool separate_argument = (strlen(argv[i]) == 3); + if (separate_argument) { + // -MF arg + if (i >= argc - 1) { + cc_log("Missing argument to %s", argv[i]); + stats_update(STATS_ARGS); + result = false; + goto out; + } + arg = argv[i + 1]; + i++; + } else { + // -MFarg + arg = &argv[i][3]; + } + output_dep = make_relative_path(x_strdup(arg)); + // Keep the format of the args the same. + if (separate_argument) { + args_add(dep_args, "-MF"); + args_add(dep_args, output_dep); + } else { + char *option = format("-MF%s", output_dep); + args_add(dep_args, option); + free(option); + } + continue; + } + if (str_startswith(argv[i], "-MQ") || str_startswith(argv[i], "-MT")) { + dependency_target_specified = true; + + char *relpath; + if (strlen(argv[i]) == 3) { + // -MQ arg or -MT arg + if (i >= argc - 1) { + cc_log("Missing argument to %s", argv[i]); + stats_update(STATS_ARGS); + result = false; + goto out; + } + args_add(dep_args, argv[i]); + relpath = make_relative_path(x_strdup(argv[i + 1])); + args_add(dep_args, relpath); + free(relpath); + i++; + } else { + char *arg_opt = x_strndup(argv[i], 3); + relpath = make_relative_path(x_strdup(argv[i] + 3)); + char *option = format("%s%s", arg_opt, relpath); + args_add(dep_args, option); + free(arg_opt); + free(relpath); + free(option); + } + continue; + } + if (str_eq(argv[i], "-fprofile-arcs")) { + profile_arcs = true; + args_add(stripped_args, argv[i]); + continue; + } + if (str_eq(argv[i], "-ftest-coverage")) { + generating_coverage = true; + args_add(stripped_args, argv[i]); + continue; + } + if (str_eq(argv[i], "--coverage") // = -fprofile-arcs -ftest-coverage + || str_eq(argv[i], "-coverage")) { // Undocumented but still works. + profile_arcs = true; + generating_coverage = true; + args_add(stripped_args, argv[i]); + continue; + } + if (str_startswith(argv[i], "-fprofile-dir=")) { + profile_dir = x_strdup(argv[i] + 14); + args_add(stripped_args, argv[i]); + continue; + } + if (str_startswith(argv[i], "--sysroot=")) { + char *relpath = make_relative_path(x_strdup(argv[i] + 10)); + char *option = format("--sysroot=%s", relpath); + args_add(stripped_args, option); + free(relpath); + free(option); + continue; + } + if (str_startswith(argv[i], "-Wp,")) { + if (str_eq(argv[i], "-Wp,-P") + || strstr(argv[i], ",-P,") + || str_endswith(argv[i], ",-P")) { + // -P removes preprocessor information in such a way that the object + // file from compiling the preprocessed file will not be equal to the + // object file produced when compiling without ccache. + cc_log("Too hard option -Wp,-P detected"); + stats_update(STATS_UNSUPPORTED_OPTION); + failed(); + } else if (str_startswith(argv[i], "-Wp,-MD,") + && !strchr(argv[i] + 8, ',')) { + generating_dependencies = true; + dependency_filename_specified = true; + free(output_dep); + output_dep = make_relative_path(x_strdup(argv[i] + 8)); + args_add(dep_args, argv[i]); + continue; + } else if (str_startswith(argv[i], "-Wp,-MMD,") + && !strchr(argv[i] + 9, ',')) { + generating_dependencies = true; + dependency_filename_specified = true; + free(output_dep); + output_dep = make_relative_path(x_strdup(argv[i] + 9)); + args_add(dep_args, argv[i]); + continue; + } else if (str_startswith(argv[i], "-Wp,-D") + && !strchr(argv[i] + 6, ',')) { + // Treat it like -D. + args_add(cpp_args, argv[i] + 4); + continue; + } else if (str_eq(argv[i], "-Wp,-MP") + || (strlen(argv[i]) > 8 + && str_startswith(argv[i], "-Wp,-M") + && argv[i][7] == ',' + && (argv[i][6] == 'F' + || argv[i][6] == 'Q' + || argv[i][6] == 'T') + && !strchr(argv[i] + 8, ','))) { + // TODO: Make argument to MF/MQ/MT relative. + args_add(dep_args, argv[i]); + continue; + } else if (conf->direct_mode) { + // -Wp, can be used to pass too hard options to the preprocessor. + // Hence, disable direct mode. + cc_log("Unsupported compiler option for direct mode: %s", argv[i]); + conf->direct_mode = false; + } + + // Any other -Wp,* arguments are only relevant for the preprocessor. + args_add(cpp_args, argv[i]); + continue; + } + if (str_eq(argv[i], "-MP")) { + args_add(dep_args, argv[i]); + continue; + } + + // Input charset needs to be handled specially. + if (str_startswith(argv[i], "-finput-charset=")) { + input_charset = argv[i]; + continue; + } + + if (str_eq(argv[i], "--serialize-diagnostics")) { + if (i >= argc - 1) { + cc_log("Missing argument to %s", argv[i]); + stats_update(STATS_ARGS); + result = false; + goto out; + } + output_dia = make_relative_path(x_strdup(argv[i+1])); + i++; + continue; + } + + if (str_startswith(argv[i], "-fprofile-")) { + char *arg = x_strdup(argv[i]); + const char *arg_profile_dir = strchr(argv[i], '='); + if (arg_profile_dir) { + // Convert to absolute path. + char *dir = x_realpath(arg_profile_dir + 1); + if (!dir) { + // Directory doesn't exist. + dir = x_strdup(arg_profile_dir + 1); + } + + // We can get a better hit rate by using the real path here. + free(arg); + char *option = x_strndup(argv[i], arg_profile_dir - argv[i]); + arg = format("%s=%s", option, dir); + cc_log("Rewriting %s to %s", argv[i], arg); + free(option); + free(dir); + } + + bool supported_profile_option = false; + if (str_startswith(argv[i], "-fprofile-generate") + || str_eq(argv[i], "-fprofile-arcs")) { + profile_generate = true; + supported_profile_option = true; + } else if (str_startswith(argv[i], "-fprofile-use") + || str_eq(argv[i], "-fbranch-probabilities")) { + profile_use = true; + supported_profile_option = true; + } else if (str_eq(argv[i], "-fprofile-dir")) { + supported_profile_option = true; + } + + if (supported_profile_option) { + args_add(stripped_args, arg); + free(arg); + + // If the profile directory has already been set, give up... Hard to + // know what the user means, and what the compiler will do. + if (arg_profile_dir && profile_dir) { + cc_log("Profile directory already set; giving up"); + result = false; + goto out; + } else if (arg_profile_dir) { + cc_log("Setting profile directory to %s", profile_dir); + profile_dir = x_strdup(arg_profile_dir); + } + continue; + } + cc_log("Unknown profile option: %s", argv[i]); + free(arg); + } + + if (str_eq(argv[i], "-fcolor-diagnostics") + || str_eq(argv[i], "-fno-color-diagnostics") + || str_eq(argv[i], "-fdiagnostics-color") + || str_eq(argv[i], "-fdiagnostics-color=always") + || str_eq(argv[i], "-fno-diagnostics-color") + || str_eq(argv[i], "-fdiagnostics-color=never")) { + args_add(stripped_args, argv[i]); + found_color_diagnostics = true; + continue; + } + if (str_eq(argv[i], "-fdiagnostics-color=auto")) { + if (color_output_possible()) { + // Output is redirected, so color output must be forced. + args_add(stripped_args, "-fdiagnostics-color=always"); + cc_log("Automatically forcing colors"); + } else { + args_add(stripped_args, argv[i]); + } + found_color_diagnostics = true; + continue; + } + + // Options taking an argument that we may want to rewrite to relative paths + // to get better hit rate. A secondary effect is that paths in the standard + // error output produced by the compiler will be normalized. + if (compopt_takes_path(argv[i])) { + if (i == argc-1) { + cc_log("Missing argument to %s", argv[i]); + stats_update(STATS_ARGS); + result = false; + goto out; + } + + if (!detect_pch(argv[i], argv[i+1], &found_pch)) { + result = false; + goto out; + } + + char *relpath = make_relative_path(x_strdup(argv[i+1])); + if (compopt_affects_cpp(argv[i])) { + args_add(cpp_args, argv[i]); + args_add(cpp_args, relpath); + } else { + args_add(stripped_args, argv[i]); + args_add(stripped_args, relpath); + } + free(relpath); + + i++; + continue; + } + + // Same as above but options with concatenated argument beginning with a + // slash. + if (argv[i][0] == '-') { + char *slash_pos = strchr(argv[i], '/'); + if (slash_pos) { + char *option = x_strndup(argv[i], slash_pos - argv[i]); + if (compopt_takes_concat_arg(option) && compopt_takes_path(option)) { + char *relpath = make_relative_path(x_strdup(slash_pos)); + char *new_option = format("%s%s", option, relpath); + if (compopt_affects_cpp(option)) { + args_add(cpp_args, new_option); + } else { + args_add(stripped_args, new_option); + } + free(new_option); + free(relpath); + free(option); + continue; + } else { + free(option); + } + } + } + + // Options that take an argument. + if (compopt_takes_arg(argv[i])) { + if (i == argc-1) { + cc_log("Missing argument to %s", argv[i]); + stats_update(STATS_ARGS); + result = false; + goto out; + } + + if (compopt_affects_cpp(argv[i])) { + args_add(cpp_args, argv[i]); + args_add(cpp_args, argv[i+1]); + } else { + args_add(stripped_args, argv[i]); + args_add(stripped_args, argv[i+1]); + } + + i++; + continue; + } + + // Other options. + if (argv[i][0] == '-') { + if (compopt_affects_cpp(argv[i]) + || compopt_prefix_affects_cpp(argv[i])) { + args_add(cpp_args, argv[i]); + } else { + 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. + struct stat st; + if (stat(argv[i], &st) != 0 || !S_ISREG(st.st_mode)) { + cc_log("%s is not a regular file, not considering as input file", + argv[i]); + args_add(stripped_args, argv[i]); + continue; + } + + if (input_file) { + if (language_for_file(argv[i])) { + cc_log("Multiple input files: %s and %s", input_file, argv[i]); + stats_update(STATS_MULTIPLE); + } else if (!found_c_opt) { + cc_log("Called for link with %s", argv[i]); + if (strstr(argv[i], "conftest.")) { + stats_update(STATS_CONFTEST); + } else { + stats_update(STATS_LINK); + } + } else { + cc_log("Unsupported source extension: %s", argv[i]); + stats_update(STATS_SOURCELANG); + } + result = false; + goto out; + } + + // The source code file path gets put into the notes. + if (generating_coverage) { + input_file = x_strdup(argv[i]); + continue; + } + + if (is_symlink(argv[i])) { + // Don't rewrite source file path if it's a symlink since + // make_relative_path resolves symlinks using realpath(3) and this leads + // to potentially choosing incorrect relative header files. See the + // "symlink to source file" test. + input_file = x_strdup(argv[i]); + } else { + // Rewrite to relative to increase hit rate. + input_file = make_relative_path(x_strdup(argv[i])); + } + } // for + + if (debug_level > 0) { + generating_debuginfo = true; + args_add(stripped_args, debug_argument); + if (conf->unify) { + cc_log("%s used; disabling unify mode", debug_argument); + conf->unify = false; + } + if (debug_level >= 3 && !conf->run_second_cpp) { + cc_log("%s used; not compiling preprocessed code", debug_argument); + conf->run_second_cpp = true; + } + } + + if (found_S_opt) { + // Even if -gsplit-dwarf is given, the .dwo file is not generated when -S + // is also given. + using_split_dwarf = false; + cc_log("Disabling caching of dwarf files since -S is used"); + } + + if (!input_file) { + cc_log("No input file found"); + stats_update(STATS_NOINPUT); + result = false; + goto out; + } + + if (found_pch || found_fpch_preprocess) { + using_precompiled_header = true; + if (!(conf->sloppiness & SLOPPY_TIME_MACROS)) { + cc_log("You have to specify \"time_macros\" sloppiness when using" + " precompiled headers to get direct hits"); + cc_log("Disabling direct mode"); + stats_update(STATS_CANTUSEPCH); + result = false; + goto out; + } + } + + if (explicit_language && str_eq(explicit_language, "none")) { + explicit_language = NULL; + } + file_language = language_for_file(input_file); + if (explicit_language) { + if (!language_is_supported(explicit_language)) { + cc_log("Unsupported language: %s", explicit_language); + stats_update(STATS_SOURCELANG); + result = false; + goto out; + } + actual_language = explicit_language; + } else { + actual_language = file_language; + } + + output_is_precompiled_header = + actual_language && strstr(actual_language, "-header"); + + if (output_is_precompiled_header + && !(conf->sloppiness & SLOPPY_PCH_DEFINES)) { + cc_log("You have to specify \"pch_defines,time_macros\" sloppiness when" + " creating precompiled headers"); + stats_update(STATS_CANTUSEPCH); + result = false; + goto out; + } + + if (!found_c_opt && !found_S_opt) { + if (output_is_precompiled_header) { + args_add(stripped_args, "-c"); + } else { + cc_log("No -c option found"); + // 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); + } + result = false; + goto out; + } + } + + if (!actual_language) { + cc_log("Unsupported source extension: %s", input_file); + stats_update(STATS_SOURCELANG); + result = false; + goto out; + } + + direct_i_file = language_is_preprocessed(actual_language); + + if (output_is_precompiled_header && !conf->run_second_cpp) { + // It doesn't work to create the .gch from preprocessed source. + cc_log("Creating precompiled header; not compiling preprocessed code"); + conf->run_second_cpp = true; + } + + if (str_eq(conf->cpp_extension, "")) { + const char *p_language = p_language_for_language(actual_language); + free(conf->cpp_extension); + conf->cpp_extension = x_strdup(extension_for_language(p_language) + 1); + } + + // Don't try to second guess the compilers heuristics for stdout handling. + if (output_obj && str_eq(output_obj, "-")) { + stats_update(STATS_OUTSTDOUT); + cc_log("Output file is -"); + result = false; + goto out; + } + + if (!output_obj) { + if (output_is_precompiled_header) { + output_obj = format("%s.gch", input_file); + } else { + output_obj = basename(input_file); + char *p = strrchr(output_obj, '.'); + if (!p || !p[1]) { + cc_log("Badly formed object filename"); + stats_update(STATS_ARGS); + result = false; + goto out; + } + p[1] = found_S_opt ? 's' : 'o'; + p[2] = 0; + } + } + + if (using_split_dwarf) { + char *p = strrchr(output_obj, '.'); + if (!p || !p[1]) { + cc_log("Badly formed object filename"); + stats_update(STATS_ARGS); + result = false; + goto out; + } + + char *base_name = remove_extension(output_obj); + output_dwo = format("%s.dwo", base_name); + free(base_name); + } + + // Cope with -o /dev/null. + struct stat st; + if (!str_eq(output_obj, "/dev/null") + && stat(output_obj, &st) == 0 + && !S_ISREG(st.st_mode)) { + cc_log("Not a regular file: %s", output_obj); + stats_update(STATS_DEVICE); + result = false; + goto out; + } + + // Some options shouldn't be passed to the real compiler when it compiles + // preprocessed code: + // + // -finput-charset=XXX (otherwise conversion happens twice) + // -x XXX (otherwise the wrong language is selected) + if (input_charset) { + args_add(cpp_args, input_charset); + } + if (found_pch) { + args_add(cpp_args, "-fpch-preprocess"); + } + if (explicit_language) { + args_add(cpp_args, "-x"); + args_add(cpp_args, explicit_language); + } + + // Since output is redirected, compilers will not color their output by + // default, so force it explicitly if it would be otherwise done. + if (!found_color_diagnostics && color_output_possible()) { + if (compiler_is_clang(args)) { + if (!str_eq(actual_language, "assembler")) { + args_add(stripped_args, "-fcolor-diagnostics"); + cc_log("Automatically enabling colors"); + } + } else if (compiler_is_gcc(args)) { + // GCC has it since 4.9, but that'd require detecting what GCC version is + // used for the actual compile. However it requires also GCC_COLORS to be + // set (and not empty), so use that for detecting if GCC would use + // colors. + if (getenv("GCC_COLORS") && getenv("GCC_COLORS")[0] != '\0') { + args_add(stripped_args, "-fdiagnostics-color"); + cc_log("Automatically enabling colors"); + } + } + } + + // Add flags for dependency generation only to the preprocessor command line. + if (generating_dependencies) { + if (!dependency_filename_specified) { + char *base_name = remove_extension(output_obj); + char *default_depfile_name = format("%s.d", base_name); + free(base_name); + args_add(dep_args, "-MF"); + args_add(dep_args, default_depfile_name); + output_dep = make_relative_path(x_strdup(default_depfile_name)); + } + + if (!dependency_target_specified) { + args_add(dep_args, "-MQ"); + args_add(dep_args, output_obj); + } + } + if (generating_coverage) { + char *base_name = remove_extension(output_obj); + char *default_covfile_name = format("%s.gcno", base_name); + free(base_name); + output_cov = make_relative_path(x_strdup(default_covfile_name)); + } + + *compiler_args = args_copy(stripped_args); + if (conf->run_second_cpp) { + args_extend(*compiler_args, cpp_args); + } else if (explicit_language) { + // Workaround for a bug in Apple's patched distcc -- it doesn't properly + // reset the language specified with -x, so if -x is given, we have to + // specify the preprocessed language explicitly. + args_add(*compiler_args, "-x"); + args_add(*compiler_args, p_language_for_language(explicit_language)); + } + + if (found_c_opt) { + args_add(*compiler_args, "-c"); + } + + for (size_t i = 0; i < arch_args_size; ++i) { + args_add(*compiler_args, "-arch"); + args_add(*compiler_args, arch_args[i]); + } + + // Only pass dependency arguments to the preprocesor since Intel's C++ + // compiler doesn't produce a correct .d file when compiling preprocessed + // source. + args_extend(cpp_args, dep_args); + + *preprocessor_args = args_copy(stripped_args); + args_extend(*preprocessor_args, cpp_args); + +out: + args_free(expanded_args); + args_free(stripped_args); + args_free(dep_args); + args_free(cpp_args); + return result; +} + +static void +create_initial_config_file(struct conf *conf, const char *path) +{ + if (create_parent_dirs(path) != 0) { + return; + } + + unsigned max_files; + uint64_t max_size; + char *stats_dir = format("%s/0", conf->cache_dir); + struct stat st; + if (stat(stats_dir, &st) == 0) { + stats_get_obsolete_limits(stats_dir, &max_files, &max_size); + // STATS_MAXFILES and STATS_MAXSIZE was stored for each top directory. + max_files *= 16; + max_size *= 16; + } else { + max_files = 0; + max_size = conf->max_size; + } + free(stats_dir); + + FILE *f = fopen(path, "w"); + if (!f) { + return; + } + if (max_files != 0) { + fprintf(f, "max_files = %u\n", max_files); + conf->max_files = max_files; + } + if (max_size != 0) { + char *size = format_parsable_size_with_suffix(max_size); + fprintf(f, "max_size = %s\n", size); + free(size); + conf->max_size = max_size; + } + fclose(f); +} + +// Read config file(s), populate variables, create configuration file in cache +// directory if missing, etc. +static void +initialize(void) +{ + conf_free(conf); + conf = conf_create(); + + char *errmsg; + struct stat st; + char *p = getenv("CCACHE_CONFIGPATH"); + if (p) { + primary_config_path = x_strdup(p); + } else { + secondary_config_path = format("%s/ccache.conf", TO_STRING(SYSCONFDIR)); + if (!conf_read(conf, secondary_config_path, &errmsg)) { + if (stat(secondary_config_path, &st) == 0) { + fatal("%s", errmsg); + } + // Missing config file in SYSCONFDIR is OK. + free(errmsg); + } + + if (str_eq(conf->cache_dir, "")) { + fatal("configuration setting \"cache_dir\" must not be the empty string"); + } + if ((p = getenv("CCACHE_DIR"))) { + free(conf->cache_dir); + conf->cache_dir = strdup(p); + } + if (str_eq(conf->cache_dir, "")) { + fatal("CCACHE_DIR must not be the empty string"); + } + + primary_config_path = format("%s/ccache.conf", conf->cache_dir); + } + + bool should_create_initial_config = false; + if (!conf_read(conf, primary_config_path, &errmsg)) { + if (stat(primary_config_path, &st) == 0) { + fatal("%s", errmsg); + } + should_create_initial_config = true; + } + + if (!conf_update_from_environment(conf, &errmsg)) { + fatal("%s", errmsg); + } + + if (conf->disable) { + should_create_initial_config = false; + } + + if (should_create_initial_config) { + create_initial_config_file(conf, primary_config_path); + } + + exitfn_init(); + exitfn_add_nullary(stats_flush); + exitfn_add_nullary(clean_up_pending_tmp_files); + + cc_log("=== CCACHE %s STARTED =========================================", + CCACHE_VERSION); + + if (conf->umask != UINT_MAX) { + umask(conf->umask); + } +} + +// Reset the global state. Used by the test suite. +void +cc_reset(void) +{ + conf_free(conf); conf = NULL; + free(primary_config_path); primary_config_path = NULL; + free(secondary_config_path); secondary_config_path = NULL; + free(current_working_dir); current_working_dir = NULL; + free(debug_prefix_map); debug_prefix_map = NULL; + free(profile_dir); profile_dir = NULL; + free(included_pch_file); included_pch_file = NULL; + args_free(orig_args); orig_args = NULL; + free(input_file); input_file = NULL; + free(output_obj); output_obj = NULL; + free(output_dwo); output_dwo = NULL; + free(output_dep); output_dep = NULL; + free(output_cov); output_cov = NULL; + free(output_dia); output_dia = NULL; + free(cached_obj_hash); cached_obj_hash = NULL; + free(cached_obj); cached_obj = NULL; + free(cached_dwo); cached_dwo = NULL; + free(cached_stderr); cached_stderr = NULL; + free(cached_dep); cached_dep = NULL; + free(cached_cov); cached_cov = NULL; + free(cached_dia); cached_dia = NULL; + free(manifest_path); manifest_path = NULL; + time_of_compilation = 0; + for (size_t i = 0; i < ignore_headers_len; i++) { + free(ignore_headers[i]); + ignore_headers[i] = NULL; + } + free(ignore_headers); ignore_headers = NULL; + ignore_headers_len = 0; + if (included_files) { + hashtable_destroy(included_files, 1); included_files = NULL; + } + has_absolute_include_headers = false; + generating_debuginfo = false; + generating_dependencies = false; + generating_coverage = false; + profile_arcs = false; + free(profile_dir); profile_dir = NULL; + i_tmpfile = NULL; + direct_i_file = false; + free(cpp_stderr); cpp_stderr = NULL; + free(stats_file); stats_file = NULL; + output_is_precompiled_header = false; + + conf = conf_create(); + using_split_dwarf = false; +} + +// 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) +{ + int uncached_fd = dup(2); + if (uncached_fd == -1) { + cc_log("dup(2) failed: %s", strerror(errno)); + failed(); + } + + // Leak a pointer to the environment. + char *buf = format("UNCACHED_ERR_FD=%d", uncached_fd); + if (putenv(buf) == -1) { + cc_log("putenv failed: %s", strerror(errno)); + failed(); + } +} + +static void +configuration_logger(const char *descr, const char *origin, void *context) +{ + (void)context; + cc_bulklog("Config: (%s) %s", origin, descr); +} + +// The main ccache driver function. +static void +ccache(int argc, char *argv[]) +{ +#ifndef _WIN32 + set_up_signal_handlers(); +#endif + + orig_args = args_init(argc, argv); + + initialize(); + find_compiler(argv); + + if (str_eq(conf->temporary_dir, "")) { + clean_up_internal_tempdir(); + } + + if (!str_eq(conf->log_file, "")) { + conf_print_items(conf, configuration_logger, NULL); + } + + if (conf->disable) { + cc_log("ccache is disabled"); + failed(); + } + + setup_uncached_err(); + + cc_log_argv("Command line: ", argv); + cc_log("Hostname: %s", get_hostname()); + cc_log("Working directory: %s", get_current_working_dir()); + + if (conf->unify) { + cc_log("Direct mode disabled because unify mode is enabled"); + conf->direct_mode = false; + } + + conf->limit_multiple = MIN(MAX(conf->limit_multiple, 0.0), 1.0); + + // Arguments (except -E) to send to the preprocessor. + struct args *preprocessor_args; + // Arguments to send to the real compiler. + struct args *compiler_args; + if (!cc_process_args(orig_args, &preprocessor_args, &compiler_args)) { + failed(); + } + + cc_log("Source file: %s", input_file); + if (generating_dependencies) { + cc_log("Dependency file: %s", output_dep); + } + if (generating_coverage) { + cc_log("Coverage file: %s", output_cov); + } + if (output_dia) { + cc_log("Diagnostic file: %s", output_dia); + } + + if (using_split_dwarf) { + if (!generating_dependencies) { + assert(output_dwo); + } + } else { + assert(!output_dwo); + } + + if (output_dwo) { + cc_log("Split dwarf file: %s", output_dwo); + } + + cc_log("Object file: %s", output_obj); + + struct mdfour common_hash; + hash_start(&common_hash); + calculate_common_hash(preprocessor_args, &common_hash); + + // Try to find the hash using the manifest. + struct mdfour direct_hash = common_hash; + bool put_object_in_manifest = false; + struct file_hash *object_hash = NULL; + struct file_hash *object_hash_from_manifest = NULL; + if (conf->direct_mode) { + cc_log("Trying direct lookup"); + object_hash = calculate_object_hash(preprocessor_args, &direct_hash, 1); + if (object_hash) { + update_cached_result_globals(object_hash); + + // If we can return from cache at this point then do so. + from_cache(FROMCACHE_DIRECT_MODE, 0); + + // Wasn't able to return from cache at this point. However, the object + // was already found in manifest, so don't readd it later. + put_object_in_manifest = false; + + object_hash_from_manifest = object_hash; + } else { + // Add object to manifest later. + put_object_in_manifest = true; + } + } + + if (conf->read_only_direct) { + cc_log("Read-only direct mode; running real compiler"); + failed(); + } + + // Find the hash using the preprocessed output. Also updates included_files. + struct mdfour cpp_hash = common_hash; + object_hash = calculate_object_hash(preprocessor_args, &cpp_hash, 0); + if (!object_hash) { + fatal("internal error: object hash from cpp returned NULL"); + } + update_cached_result_globals(object_hash); + + if (object_hash_from_manifest + && !file_hashes_equal(object_hash_from_manifest, object_hash)) { + // The hash from manifest differs from the hash of the preprocessor output. + // This could be because: + // + // - The preprocessor produces different output for the same input (not + // likely). + // - There's a bug in ccache (maybe incorrect handling of compiler + // arguments). + // - The user has used a different CCACHE_BASEDIR (most likely). + // + // The best thing here would probably be to remove the hash entry from the + // manifest. For now, we use a simpler method: just remove the manifest + // file. + cc_log("Hash from manifest doesn't match preprocessor output"); + cc_log("Likely reason: different CCACHE_BASEDIRs used"); + cc_log("Removing manifest as a safety measure"); + x_unlink(manifest_path); + + put_object_in_manifest = true; + } + + // If we can return from cache at this point then do. + from_cache(FROMCACHE_CPP_MODE, put_object_in_manifest); + + if (conf->read_only) { + cc_log("Read-only mode; running real compiler"); + failed(); + } + + add_prefix(compiler_args, conf->prefix_command); + + // Run real compiler, sending output to cache. + to_cache(compiler_args); + + x_exit(0); +} + +static void +configuration_printer(const char *descr, const char *origin, void *context) +{ + assert(context); + fprintf(context, "(%s) %s\n", origin, descr); +} + +// The main program when not doing a compile. +static int +ccache_main_options(int argc, char *argv[]) +{ + enum longopts { + DUMP_MANIFEST + }; + static const struct option options[] = { + {"cleanup", no_argument, 0, 'c'}, + {"clear", no_argument, 0, 'C'}, + {"dump-manifest", required_argument, 0, DUMP_MANIFEST}, + {"help", no_argument, 0, 'h'}, + {"max-files", required_argument, 0, 'F'}, + {"max-size", required_argument, 0, 'M'}, + {"set-config", required_argument, 0, 'o'}, + {"print-config", no_argument, 0, 'p'}, + {"show-stats", no_argument, 0, 's'}, + {"version", no_argument, 0, 'V'}, + {"zero-stats", no_argument, 0, 'z'}, + {0, 0, 0, 0} + }; + + int c; + while ((c = getopt_long(argc, argv, "cChF:M:o:psVz", options, NULL)) != -1) { + switch (c) { + case DUMP_MANIFEST: + manifest_dump(optarg, stdout); + break; + + case 'c': // --cleanup + initialize(); + cleanup_all(conf); + printf("Cleaned cache\n"); + break; + + case 'C': // --clear + initialize(); + wipe_all(conf); + printf("Cleared cache\n"); + break; + + case 'h': // --help + fputs(USAGE_TEXT, stdout); + x_exit(0); + + case 'F': // --max-files + { + initialize(); + char *errmsg; + if (conf_set_value_in_file(primary_config_path, "max_files", optarg, + &errmsg)) { + unsigned files = atoi(optarg); + if (files == 0) { + printf("Unset cache file limit\n"); + } else { + printf("Set cache file limit to %u\n", files); + } + } else { + fatal("could not set cache file limit: %s", errmsg); + } + } + break; + + case 'M': // --max-size + { + initialize(); + uint64_t size; + if (!parse_size_with_suffix(optarg, &size)) { + fatal("invalid size: %s", optarg); + } + char *errmsg; + if (conf_set_value_in_file(primary_config_path, "max_size", optarg, + &errmsg)) { + if (size == 0) { + printf("Unset cache size limit\n"); + } else { + char *s = format_human_readable_size(size); + printf("Set cache size limit to %s\n", s); + free(s); + } + } else { + fatal("could not set cache size limit: %s", errmsg); + } + } + break; + + case 'o': // --set-config + { + initialize(); + char *p = strchr(optarg, '='); + if (!p) { + fatal("missing equal sign in \"%s\"", optarg); + } + char *key = x_strndup(optarg, p - optarg); + char *value = p + 1; + char *errmsg; + if (!conf_set_value_in_file(primary_config_path, key, value, &errmsg)) { + fatal("%s", errmsg); + } + free(key); + } + break; + + case 'p': // --print-config + initialize(); + conf_print_items(conf, configuration_printer, stdout); + break; + + case 's': // --show-stats + initialize(); + stats_summary(conf); + break; + + case 'V': // --version + fprintf(stdout, VERSION_TEXT, CCACHE_VERSION); + x_exit(0); + + case 'z': // --zero-stats + initialize(); + stats_zero(); + printf("Statistics cleared\n"); + break; + + default: + fputs(USAGE_TEXT, stderr); + x_exit(1); + } + } + + return 0; +} + +int +ccache_main(int argc, char *argv[]) +{ + // Check if we are being invoked as "ccache". + char *program_name = basename(argv[0]); + if (same_executable_name(program_name, MYNAME)) { + if (argc < 2) { + fputs(USAGE_TEXT, stderr); + x_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_options(argc, argv); + } + } + free(program_name); + + ccache(argc, argv); + return 1; +} |
