bindir = @bindir@
mandir = @mandir@
datarootdir = @datarootdir@
+sysconfdir = @sysconfdir@
installcmd = @INSTALL@
AR = @AR@
CC = @CC@
CFLAGS = @CFLAGS@
-CPPFLAGS = @DEFS@ @CPPFLAGS@ -I. -I$(srcdir)
+CPPFLAGS = @DEFS@ @CPPFLAGS@ -DSYSCONFDIR=$(sysconfdir) -I. -I$(srcdir)
LDFLAGS = @LDFLAGS@
EXEEXT = @EXEEXT@
RANLIB = @RANLIB@
ccache.c mdfour.c hash.c execute.c util.c args.c stats.c version.c \
cleanup.c snprintf.c unify.c manifest.c hashtable.c hashtable_itr.c \
murmurhashneutral2.c hashutil.c getopt_long.c exitfn.c lockfile.c \
- counters.c language.c compopt.c
+ counters.c language.c compopt.c conf.c
base_objs = $(base_sources:.c=.o)
ccache_sources = main.c $(base_sources)
#include "ccache.h"
#include "compopt.h"
+#include "conf.h"
#ifdef HAVE_GETOPT_LONG
#include <getopt.h>
#else
#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"
"\n"
"See also <http://ccache.samba.org>.\n";
+/* Global configuration data. */
+struct conf *conf = NULL;
+
/* current working directory taken from $PWD, or getcwd() if $PWD is bad */
char *current_working_dir = NULL;
/* the debug logfile name, if set */
char *cache_logfile = NULL;
-/* base directory (from CCACHE_BASEDIR) */
-char *base_dir = NULL;
-
/* the original argument list */
static struct args *orig_args;
{
char *relpath;
- if (!base_dir || !str_startswith(path, base_dir)) {
+ if (str_eq(conf->base_dir, "") || !str_startswith(path, conf->base_dir)) {
return path;
}
return result;
}
+/*
+ * Read config file(s), populate variables, create cache directory if missing,
+ * etc.
+ */
+static void
+initialize(void)
+{
+ char *errmsg;
+ char *p;
+
+ conf_free(conf);
+ conf = conf_create();
+
+ p = getenv("CCACHE_CONFIG_PATH");
+ if (p) {
+ if (!conf_read(conf, p, &errmsg)) {
+ fatal("%s", errmsg);
+ }
+ } else {
+ char *sysconf_config_path = format("%s/ccache.conf", TO_STRING(SYSCONFDIR));
+ if (!conf_read(conf, sysconf_config_path, &errmsg)) {
+ fatal("%s", errmsg);
+ }
+ free(sysconf_config_path);
+
+ if ((p = getenv("CCACHE_DIR"))) {
+ free(conf->cache_dir);
+ conf->cache_dir = strdup(p);
+ }
+
+ char *cachedir_config_path = format("%s/ccache.conf", conf->cache_dir);
+ if (!conf_read(conf, cachedir_config_path, &errmsg)) {
+ fatal("%s", errmsg);
+ }
+ free(cachedir_config_path);
+ }
+
+ conf_update_from_environment(conf, &errmsg);
+
+ exitfn_init();
+ exitfn_add_nullary(stats_flush);
+ exitfn_add_nullary(clean_up_tmp_files);
+
+ /* check for logging early so cc_log messages start working ASAP */
+ cache_logfile = getenv("CCACHE_LOGFILE");
+ cc_log("=== CCACHE STARTED =========================================");
+
+ /* the user might have set CCACHE_UMASK */
+ p = getenv("CCACHE_UMASK");
+ if (p) {
+ mode_t mask;
+ errno = 0;
+ mask = strtol(p, NULL, 8);
+ if (errno == 0) {
+ umask(mask);
+ }
+ }
+
+ current_working_dir = get_cwd();
+ cache_dir = getenv("CCACHE_DIR");
+ if (cache_dir) {
+ cache_dir = x_strdup(cache_dir);
+ } else {
+ const char *home_directory = get_home_directory();
+ if (home_directory) {
+ cache_dir = format("%s/.ccache", home_directory);
+ }
+ }
+}
+
/* Reset the global state. Used by the test suite. */
void
cc_reset(void)
{
+ conf_free(conf); conf = NULL;
free(current_working_dir); current_working_dir = NULL;
free(cache_dir); cache_dir = NULL;
cache_logfile = NULL;
- base_dir = NULL;
args_free(orig_args); orig_args = NULL;
free(input_file); input_file = NULL;
free(output_obj); output_obj = NULL;
nlevels = 2;
compile_preprocessed_source_code = false;
output_is_precompiled_header = false;
+
+ initialize();
}
static unsigned
return result;
}
+/* Make a copy of stderr that will not be cached, so things like
+ distcc can send networking errors to it. */
+static void
+setup_uncached_err(void)
+{
+ char *buf;
+ int uncached_fd;
+
+ uncached_fd = dup(2);
+ if (uncached_fd == -1) {
+ cc_log("dup(2) failed: %s", strerror(errno));
+ failed();
+ }
+
+ /* leak a pointer to the environment */
+ buf = format("UNCACHED_ERR_FD=%d", uncached_fd);
+
+ if (putenv(buf) == -1) {
+ cc_log("putenv failed: %s", strerror(errno));
+ failed();
+ }
+}
+
/* the main ccache driver function */
static void
ccache(int argc, char *argv[])
/* Arguments to send to the real compiler. */
struct args *compiler_args;
+ initialize();
+
+ compile_preprocessed_source_code = !getenv("CCACHE_CPP2");
+
+ setup_uncached_err();
+
find_compiler(argc, argv);
if (getenv("CCACHE_DISABLE")) {
cc_log("Hostname: %s", get_hostname());
cc_log("Working directory: %s", current_working_dir);
- if (base_dir) {
- cc_log("Base directory: %s", base_dir);
+ if (!str_eq(conf->base_dir, "")) {
+ cc_log("Base directory: %s", conf->base_dir);
}
if (getenv("CCACHE_UNIFY")) {
failed();
}
-static void
-check_cache_dir(void)
-{
- if (!cache_dir) {
- fatal("Unable to determine cache directory");
- }
-}
-
/* the main program when not doing a compile */
static int
ccache_main_options(int argc, char *argv[])
break;
case 'c': /* --cleanup */
- check_cache_dir();
+ initialize();
cleanup_all(cache_dir);
printf("Cleaned cache\n");
break;
case 'C': /* --clear */
- check_cache_dir();
+ initialize();
wipe_all(cache_dir);
printf("Cleared cache\n");
break;
exit(0);
case 'F': /* --max-files */
- check_cache_dir();
+ initialize();
v = atoi(optarg);
if (stats_set_limits(v, -1) == 0) {
if (v == 0) {
break;
case 'M': /* --max-size */
- check_cache_dir();
+ initialize();
parse_size_with_suffix(optarg, &v);
if (stats_set_limits(-1, v) == 0) {
if (v == 0) {
break;
case 's': /* --show-stats */
- check_cache_dir();
+ initialize();
stats_summary();
break;
exit(0);
case 'z': /* --zero-stats */
- check_cache_dir();
+ initialize();
stats_zero();
printf("Statistics cleared\n");
break;
return 0;
}
-
-/* Make a copy of stderr that will not be cached, so things like
- distcc can send networking errors to it. */
-static void
-setup_uncached_err(void)
-{
- char *buf;
- int uncached_fd;
-
- uncached_fd = dup(2);
- if (uncached_fd == -1) {
- cc_log("dup(2) failed: %s", strerror(errno));
- failed();
- }
-
- /* leak a pointer to the environment */
- buf = format("UNCACHED_ERR_FD=%d", uncached_fd);
-
- if (putenv(buf) == -1) {
- cc_log("putenv failed: %s", strerror(errno));
- failed();
- }
-}
-
int
ccache_main(int argc, char *argv[])
{
- char *p;
- char *program_name;
-
- exitfn_init();
- exitfn_add_nullary(stats_flush);
- exitfn_add_nullary(clean_up_tmp_files);
-
- /* check for logging early so cc_log messages start working ASAP */
- cache_logfile = getenv("CCACHE_LOGFILE");
- cc_log("=== CCACHE STARTED =========================================");
-
- /* the user might have set CCACHE_UMASK */
- p = getenv("CCACHE_UMASK");
- if (p) {
- mode_t mask;
- errno = 0;
- mask = strtol(p, NULL, 8);
- if (errno == 0) {
- umask(mask);
- }
- }
-
- current_working_dir = get_cwd();
- cache_dir = getenv("CCACHE_DIR");
- if (cache_dir) {
- cache_dir = x_strdup(cache_dir);
- } else {
- const char *home_directory = get_home_directory();
- if (home_directory) {
- cache_dir = format("%s/.ccache", home_directory);
- }
- }
-
/* check if we are being invoked as "ccache" */
- program_name = basename(argv[0]);
+ char *program_name = basename(argv[0]);
if (same_executable_name(program_name, MYNAME)) {
if (argc < 2) {
fputs(USAGE_TEXT, stderr);
}
free(program_name);
- check_cache_dir();
-
- base_dir = getenv("CCACHE_BASEDIR");
- if (base_dir && base_dir[0] != '/') {
- cc_log("Ignoring non-absolute base directory %s", base_dir);
- base_dir = NULL;
- }
-
- compile_preprocessed_source_code = !getenv("CCACHE_CPP2");
-
- setup_uncached_err();
-
ccache(argc, argv);
return 1;
}
--- /dev/null
+/*
+ * Copyright (C) 2011 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 "conf.h"
+#include "ccache.h"
+
+typedef bool (*conf_item_parser)(const char *str, void *result, char **errmsg);
+typedef bool (*conf_item_verifier)(void *value, char **errmsg);
+
+struct conf_item {
+ const char *name;
+ conf_item_parser parser;
+ size_t offset;
+ conf_item_verifier verifier;
+};
+
+struct env_to_conf_item {
+ const char *env_name;
+ const char *conf_name;
+};
+
+static bool
+parse_bool(const char *str, void *result, char **errmsg)
+{
+ bool *value = (bool *)result;
+
+ if (str_eq(str, "true")) {
+ *value = true;
+ return true;
+ } else if (str_eq(str, "false")) {
+ *value = false;
+ return true;
+ } else {
+ *errmsg = format("not a boolean value: \"%s\"", str);
+ return false;
+ }
+}
+
+static bool
+parse_env_string(const char *str, void *result, char **errmsg)
+{
+ char **value = (char **)result;
+ free(*value);
+ *value = subst_env_in_string(str, errmsg);
+ return *value;
+}
+
+static bool
+parse_octal(const char *str, void *result, char **errmsg)
+{
+ unsigned *value = (unsigned *)result;
+ char *endptr;
+ errno = 0;
+ *value = strtoul(str, &endptr, 8);
+ if (errno == 0 && *str != '\0' && *endptr == '\0') {
+ return true;
+ } else {
+ *errmsg = format("not an octal integer: \"%s\"", str);
+ return false;
+ }
+}
+
+static bool
+parse_size(const char *str, void *result, char **errmsg)
+{
+ unsigned *value = (unsigned *)result;
+ size_t size;
+ *errmsg = NULL;
+ if (parse_size_with_suffix(str, &size)) {
+ *value = size;
+ return true;
+ } else {
+ *errmsg = format("invalid size: \"%s\"", str);
+ return false;
+ }
+}
+
+static bool
+parse_sloppiness(const char *str, void *result, char **errmsg)
+{
+ unsigned *value = (unsigned *)result;
+ char *word, *p, *q, *saveptr = NULL;
+
+ if (!str) {
+ return *value;
+ }
+ p = x_strdup(str);
+ q = p;
+ while ((word = strtok_r(q, ", ", &saveptr))) {
+ if (str_eq(word, "file_macro")) {
+ *value |= SLOPPY_FILE_MACRO;
+ } else if (str_eq(word, "include_file_mtime")) {
+ *value |= SLOPPY_INCLUDE_FILE_MTIME;
+ } else if (str_eq(word, "time_macros")) {
+ *value |= SLOPPY_TIME_MACROS;
+ } else {
+ *errmsg = format("unknown sloppiness: \"%s\"", word);
+ free(p);
+ return false;
+ }
+ q = NULL;
+ }
+ free(p);
+ return true;
+}
+
+static bool
+parse_string(const char *str, void *result, char **errmsg)
+{
+ char **value = (char **)result;
+ (void)errmsg;
+ free(*value);
+ *value = x_strdup(str);
+ return true;
+}
+
+static bool
+parse_unsigned(const char *str, void *result, char **errmsg)
+{
+ unsigned *value = (unsigned *)result;
+ long x;
+ char *endptr;
+ errno = 0;
+ x = strtol(str, &endptr, 10);
+ if (errno == 0 && x >= 0 && x <= (unsigned)-1 && *str != '\0'
+ && *endptr == '\0') {
+ *value = x;
+ return true;
+ } else {
+ *errmsg = format("invalid unsigned integer: \"%s\"", str);
+ return false;
+ }
+}
+
+static bool
+verify_absolute_path(void *value, char **errmsg)
+{
+ char **path = (char **)value;
+ assert(*path);
+ if (is_absolute_path(*path)) {
+ return true;
+ } else {
+ *errmsg = format("not an absolute path: \"%s\"", *path);
+ return false;
+ }
+}
+
+#define ITEM(name, type) \
+ {#name, parse_##type, offsetof(struct conf, name), NULL}
+#define ITEM_V(name, type, verification) \
+ {#name, parse_##type, offsetof(struct conf, name), verify_##verification}
+
+static const struct conf_item conf_items[] = {
+ ITEM_V(base_dir, env_string, absolute_path),
+ ITEM(cache_dir, env_string),
+ ITEM(cache_dir_levels, unsigned),
+ ITEM(compiler, string),
+ ITEM(compiler_check, string),
+ ITEM(compression, bool),
+ ITEM(cpp_extension, string),
+ ITEM(detect_shebang, bool),
+ ITEM(direct_mode, bool),
+ ITEM(disable, bool),
+ ITEM(extra_files_to_hash, env_string),
+ ITEM(hard_link, bool),
+ ITEM(hash_dir, bool),
+ ITEM(log_file, env_string),
+ ITEM(max_files, unsigned),
+ ITEM(max_size, size),
+ ITEM(path, env_string),
+ ITEM(prefix_command, env_string),
+ ITEM(read_only, bool),
+ ITEM(recache, bool),
+ ITEM(run_second_cpp, bool),
+ ITEM(sloppiness, sloppiness),
+ ITEM(stats, bool),
+ ITEM(temporary_dir, env_string),
+ ITEM(umask, octal),
+ ITEM(unify, bool)
+};
+
+#define ENV_TO_CONF(env_name, conf_name) \
+ {#env_name, #conf_name}
+
+static const struct env_to_conf_item env_to_conf_items[] = {
+ ENV_TO_CONF(BASEDIR, base_dir),
+ ENV_TO_CONF(CC, compiler),
+ ENV_TO_CONF(COMPILERCHECK, compiler_check),
+ ENV_TO_CONF(COMPRESS, compression),
+ ENV_TO_CONF(CPP2, run_second_cpp),
+ ENV_TO_CONF(DETECT_SHEBANG, detect_shebang),
+ ENV_TO_CONF(DIR, cache_dir),
+ ENV_TO_CONF(DIRECT, direct_mode),
+ ENV_TO_CONF(DISABLE, disable),
+ ENV_TO_CONF(EXTENSION, cpp_extension),
+ ENV_TO_CONF(EXTRAFILES, extra_files_to_hash),
+ ENV_TO_CONF(HARDLINK, hard_link),
+ ENV_TO_CONF(HASHDIR, hash_dir),
+ ENV_TO_CONF(LOGFILE, log_file),
+ ENV_TO_CONF(MAXFILES, max_files),
+ ENV_TO_CONF(MAXSIZE, max_size),
+ ENV_TO_CONF(NLEVELS, cache_dir_levels),
+ ENV_TO_CONF(PATH, path),
+ ENV_TO_CONF(PREFIX, prefix_command),
+ ENV_TO_CONF(READONLY, read_only),
+ ENV_TO_CONF(RECACHE, recache),
+ ENV_TO_CONF(SLOPPINESS, sloppiness),
+ ENV_TO_CONF(STATS, stats),
+ ENV_TO_CONF(TEMPDIR, temporary_dir),
+ ENV_TO_CONF(UMASK, umask),
+ ENV_TO_CONF(UNIFY, unify)
+};
+
+static int
+compare_conf_items(const void *key1, const void *key2)
+{
+ const struct conf_item *conf1 = (const struct conf_item *)key1;
+ const struct conf_item *conf2 = (const struct conf_item *)key2;
+ return strcmp(conf1->name, conf2->name);
+}
+
+static const struct conf_item *
+find_conf(const char *name)
+{
+ struct conf_item key;
+ key.name = name;
+ return bsearch(
+ &key, conf_items, sizeof(conf_items) / sizeof(conf_items[0]),
+ sizeof(conf_items[0]), compare_conf_items);
+}
+
+static int
+compare_env_to_conf_items(const void *key1, const void *key2)
+{
+ const struct env_to_conf_item *conf1 = (const struct env_to_conf_item *)key1;
+ const struct env_to_conf_item *conf2 = (const struct env_to_conf_item *)key2;
+ return strcmp(conf1->env_name, conf2->env_name);
+}
+
+static const struct env_to_conf_item *
+find_env_to_conf(const char *name)
+{
+ struct env_to_conf_item key;
+ key.env_name = name;
+ return bsearch(
+ &key,
+ env_to_conf_items,
+ sizeof(env_to_conf_items) / sizeof(env_to_conf_items[0]),
+ sizeof(env_to_conf_items[0]),
+ compare_env_to_conf_items);
+}
+
+static bool
+handle_conf_setting(struct conf *conf, const char *key, const char *value,
+ char **errmsg)
+{
+ const struct conf_item *item;
+
+ item = find_conf(key);
+ if (!item) {
+ *errmsg = format("unknown configuration option \"%s\"", key);
+ return false;
+ }
+
+ if (!item->parser(value, (void *)conf + item->offset, errmsg)) {
+ return false;
+ }
+ if (item->verifier && !item->verifier((void *)conf + item->offset, errmsg)) {
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+parse_line(struct conf *conf, const char *line, char **errmsg)
+{
+ const char *p, *q;
+ char *key, *value;
+ bool result;
+
+#define SKIP_WS(x) while (isspace(*x)) { ++x; }
+
+ p = line;
+ SKIP_WS(p);
+ if (*p == '\0' || *p == '#') {
+ return true;
+ }
+ q = p;
+ while (isalpha(*q) || *q == '_') {
+ ++q;
+ }
+ key = x_strndup(p, q - p);
+ p = q;
+ SKIP_WS(p);
+ if (*p != '=') {
+ *errmsg = x_strdup("missing equal sign");
+ free(key);
+ return false;
+ }
+ ++p;
+
+ /* Skip leading whitespace. */
+ SKIP_WS(p);
+ q = p;
+ while (*q) {
+ ++q;
+ }
+ /* Skip trailing whitespace. */
+ while (isspace(q[-1])) {
+ --q;
+ }
+ value = x_strndup(p, q - p);
+
+#undef SKIP_WS
+
+ result = handle_conf_setting(conf, key, value, errmsg);
+
+ free(key);
+ free(value);
+ return result;
+}
+
+/* For test purposes. */
+bool
+conf_verify_sortedness(void)
+{
+ size_t i;
+ for (i = 1; i < sizeof(conf_items)/sizeof(conf_items[0]); i++) {
+ if (strcmp(conf_items[i-1].name, conf_items[i].name) >= 0) {
+ fprintf(stderr,
+ "conf_verify_sortedness: %s >= %s\n",
+ conf_items[i-1].name,
+ conf_items[i].name);
+ return false;
+ }
+ }
+ return true;
+}
+
+/* For test purposes. */
+bool
+conf_verify_env_table_correctness(void)
+{
+ size_t i;
+ for (i = 0;
+ i < sizeof(env_to_conf_items) / sizeof(env_to_conf_items[0]);
+ i++) {
+ if (i > 0
+ && strcmp(env_to_conf_items[i-1].env_name,
+ env_to_conf_items[i].env_name) >= 0) {
+ fprintf(stderr,
+ "conf_verify_env_table_correctness: %s >= %s\n",
+ env_to_conf_items[i-1].env_name,
+ env_to_conf_items[i].env_name);
+ return false;
+ }
+ if (!find_conf(env_to_conf_items[i].conf_name)) {
+ fprintf(stderr,
+ "conf_verify_env_table_correctness: %s -> %s,"
+ " which doesn't exist\n",
+ env_to_conf_items[i].env_name,
+ env_to_conf_items[i].conf_name);
+ return false;
+ }
+ }
+ return true;
+}
+
+/* Create a conf struct with default values. */
+struct conf *
+conf_create(void)
+{
+ struct conf *conf = x_malloc(sizeof(*conf));
+ conf->base_dir = x_strdup("");
+ conf->cache_dir = format("%s/.ccache", get_home_directory());
+ conf->cache_dir_levels = 2;
+ conf->compiler = x_strdup("");
+ conf->compiler_check = x_strdup("mtime");
+ conf->compression = false;
+ conf->cpp_extension = x_strdup("");
+ conf->detect_shebang = false;
+ conf->direct_mode = true;
+ conf->disable = false;
+ conf->extra_files_to_hash = x_strdup("");
+ conf->hard_link = false;
+ conf->hash_dir = false;
+ conf->log_file = x_strdup("");
+ conf->max_files = 0;
+ conf->max_size = 1024 * 1024; /* kilobyte */
+ conf->path = x_strdup("");
+ conf->prefix_command = x_strdup("");
+ conf->read_only = false;
+ conf->recache = false;
+ conf->run_second_cpp = false;
+ conf->sloppiness = 0;
+ conf->stats = true;
+ conf->temporary_dir = x_strdup("");
+ conf->umask = 0;
+ conf->unify = false;
+ return conf;
+}
+
+void
+conf_free(struct conf *conf)
+{
+ if (!conf) {
+ return;
+ }
+ free(conf->base_dir);
+ free(conf->cache_dir);
+ free(conf->compiler);
+ free(conf->compiler_check);
+ free(conf->cpp_extension);
+ free(conf->extra_files_to_hash);
+ free(conf->log_file);
+ free(conf->path);
+ free(conf->prefix_command);
+ free(conf->temporary_dir);
+ free(conf);
+}
+
+bool
+conf_read(struct conf *conf, const char *path, char **errmsg)
+{
+ FILE *f;
+ char buf[10000];
+ bool result = true;
+ unsigned line_number;
+
+ assert(errmsg);
+ *errmsg = NULL;
+
+ f = fopen(path, "r");
+ if (!f) {
+ *errmsg = format("%s: %s", path, strerror(errno));
+ return false;
+ }
+
+ line_number = 0;
+ while (fgets(buf, sizeof(buf), f)) {
+ char *errmsg2;
+ ++line_number;
+ if (!parse_line(conf, buf, &errmsg2)) {
+ *errmsg = format("%s:%u: %s", path, line_number, errmsg2);
+ free(errmsg2);
+ result = false;
+ goto out;
+ }
+ }
+ if (ferror(f)) {
+ *errmsg = x_strdup(strerror(errno));
+ result = false;
+ }
+
+out:
+ fclose(f);
+ return result;
+}
+
+bool
+conf_update_from_environment(struct conf *conf, char **errmsg)
+{
+ char **p;
+ char *q;
+ char *key;
+ char *errmsg2;
+ const struct env_to_conf_item *env_to_conf_item;
+
+ for (p = environ; *p; ++p) {
+ if (!str_startswith(*p, "CCACHE_")) {
+ continue;
+ }
+ q = strchr(*p, '=');
+ if (!q) {
+ continue;
+ }
+
+ key = x_strndup(*p + 7, q - *p - 7);
+ ++q; /* Now points to the value. */
+
+ env_to_conf_item = find_env_to_conf(key);
+ if (!env_to_conf_item) {
+ free(key);
+ continue;
+ }
+
+ if (!handle_conf_setting(conf, env_to_conf_item->conf_name, q, &errmsg2)) {
+ *errmsg = format("%s: %s", key, errmsg2);
+ free(errmsg2);
+ free(key);
+ return false;
+ }
+
+ free(key);
+ }
+
+ return true;
+}
--- /dev/null
+#ifndef CONF_H
+#define CONF_H
+
+#include "system.h"
+
+struct conf {
+ char *base_dir;
+ char *cache_dir;
+ unsigned cache_dir_levels;
+ char *compiler;
+ char *compiler_check;
+ bool compression;
+ char *cpp_extension;
+ bool detect_shebang;
+ bool direct_mode;
+ bool disable;
+ char *extra_files_to_hash;
+ bool hard_link;
+ bool hash_dir;
+ char *log_file;
+ unsigned max_files;
+ unsigned max_size;
+ char *path;
+ char *prefix_command;
+ bool read_only;
+ bool recache;
+ bool run_second_cpp;
+ unsigned sloppiness;
+ bool stats;
+ char *temporary_dir;
+ unsigned umask;
+ bool unify;
+};
+
+struct conf *conf_create(void);
+void conf_free(struct conf *conf);
+bool conf_read(struct conf *conf, const char *path, char **errmsg);
+bool conf_update_from_environment(struct conf *conf, char **errmsg);
+
+#endif
export CCACHE_DIR
CCACHE_LOGFILE=`pwd`/ccache.log
export CCACHE_LOGFILE
+CCACHE_CONFIG_PATH=/dev/null
+export CCACHE_CONFIG_PATH
# ---------------------------------------
/*
- * Copyright (C) 2010 Joel Rosdahl
+ * Copyright (C) 2010-2011 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
cct_chdir(name);
current_test = name;
+ putenv("CCACHE_CONFIG_PATH=/dev/null");
cc_reset();
cache_logfile = getenv("CCACHE_LOGFILE");
}
*/
#include "ccache.h"
+#include "conf.h"
#include "test/framework.h"
#include "test/util.h"
+extern struct conf *conf;
+
TEST_SUITE(argument_processing)
TEST(dash_E_should_result_in_called_for_preprocessing)
TEST(sysroot_should_be_rewritten_if_basedir_is_used)
{
- extern char *base_dir;
extern char *current_working_dir;
struct args *orig =
args_init_from_string("cc --sysroot=/some/directory -c foo.c");
create_file("foo.c", "");
CHECK(cc_process_args(orig, &act_cpp, &act_cc));
- CHECK_STR_EQ(act_cpp->argv[1], "--sysroot=/some/directory");
+ CHECK_STR_EQ("--sysroot=/some/directory", act_cpp->argv[1]);
args_free(act_cpp);
args_free(act_cc);
cc_reset();
- base_dir = "/some";
+ free(conf->base_dir);
+ conf->base_dir = x_strdup("/some");
current_working_dir = get_cwd();
CHECK(cc_process_args(orig, &act_cpp, &act_cc));
CHECK(str_startswith(act_cpp->argv[1], "--sysroot=../"));
args_free(orig);
args_free(act_cpp);
args_free(act_cc);
- cc_reset();
}
TEST(MF_flag_with_immediate_argument_should_work_as_last_argument)
--- /dev/null
+/*
+ * Copyright (C) 2011 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 "conf.h"
+#include "test/framework.h"
+#include "test/util.h"
+
+TEST_SUITE(conf)
+
+TEST(conf_item_table_should_be_sorted)
+{
+ bool conf_verify_sortedness();
+ CHECK(conf_verify_sortedness());
+}
+
+TEST(conf_env_item_table_should_be_sorted_and_otherwise_correct)
+{
+ bool conf_verify_env_table_correctness();
+ CHECK(conf_verify_env_table_correctness());
+}
+
+TEST(conf_create)
+{
+ struct conf *conf = conf_create();
+ CHECK_STR_EQ("", conf->base_dir);
+ CHECK_STR_EQ_FREE1(format("%s/.ccache", get_home_directory()),
+ conf->cache_dir);
+ CHECK_UNS_EQ(2, conf->cache_dir_levels);
+ CHECK_STR_EQ("", conf->compiler);
+ CHECK_STR_EQ("mtime", conf->compiler_check);
+ CHECK(!conf->compression);
+ CHECK_STR_EQ("", conf->cpp_extension);
+ CHECK(!conf->detect_shebang);
+ CHECK(conf->direct_mode);
+ CHECK(!conf->disable);
+ CHECK_STR_EQ("", conf->extra_files_to_hash);
+ CHECK(!conf->hard_link);
+ CHECK(!conf->hash_dir);
+ CHECK_STR_EQ("", conf->log_file);
+ CHECK_UNS_EQ(0, conf->max_files);
+ CHECK_UNS_EQ(1024*1024, conf->max_size);
+ CHECK_STR_EQ("", conf->path);
+ CHECK_STR_EQ("", conf->prefix_command);
+ CHECK(!conf->read_only);
+ CHECK(!conf->recache);
+ CHECK(!conf->run_second_cpp);
+ CHECK_UNS_EQ(0, conf->sloppiness);
+ CHECK(conf->stats);
+ CHECK_STR_EQ("", conf->temporary_dir);
+ CHECK_UNS_EQ(0, conf->umask);
+ CHECK(!conf->unify);
+ conf_free(conf);
+}
+
+TEST(conf_read_valid_config)
+{
+ struct conf *conf = conf_create();
+ char *errmsg;
+ const char *user = getenv("USER");
+ CHECK(user);
+ create_file(
+ "ccache.conf",
+ "base_dir = /$USER/foo/${USER} \n"
+ "cache_dir=\n"
+ "cache_dir = $USER$/${USER}/.ccache\n"
+ "\n"
+ "\n"
+ " #A comment\n"
+ " cache_dir_levels = 4\n"
+ "\t compiler = foo\n"
+ "compiler_check = none\n"
+ "compression=true\n"
+ "cpp_extension = .foo\n"
+ "detect_shebang = true\n"
+ "direct_mode = false\n"
+ "disable = true\n"
+ "extra_files_to_hash = a:b c:$USER\n"
+ "hard_link = true\n"
+ "hash_dir = true\n"
+ "log_file = $USER${USER} \n"
+ "max_files = 17\n"
+ "max_size = 123M\n"
+ "path = $USER.x\n"
+ "prefix_command = x$USER\n"
+ "read_only = true\n"
+ "recache = true\n"
+ "run_second_cpp = true\n"
+ "sloppiness = file_macro ,time_macros, include_file_mtime \n"
+ "stats = false\n"
+ "temporary_dir = ${USER}_foo\n"
+ "umask = 777\n"
+ "unify = true"); /* Note: no newline */
+ CHECK(conf_read(conf, "ccache.conf", &errmsg));
+ CHECK(!errmsg);
+
+ CHECK_STR_EQ_FREE1(format("/%s/foo/%s", user, user), conf->base_dir);
+ CHECK_STR_EQ_FREE1(format("%s$/%s/.ccache", user, user), conf->cache_dir);
+ CHECK_UNS_EQ(4, conf->cache_dir_levels);
+ CHECK_STR_EQ("foo", conf->compiler);
+ CHECK_STR_EQ("none", conf->compiler_check);
+ CHECK(conf->compression);
+ CHECK_STR_EQ(".foo", conf->cpp_extension);
+ CHECK(conf->detect_shebang);
+ CHECK(!conf->direct_mode);
+ CHECK(conf->disable);
+ CHECK_STR_EQ_FREE1(format("a:b c:%s", user), conf->extra_files_to_hash);
+ CHECK(conf->hard_link);
+ CHECK(conf->hash_dir);
+ CHECK_STR_EQ_FREE1(format("%s%s", user, user), conf->log_file);
+ CHECK_UNS_EQ(17, conf->max_files);
+ CHECK_UNS_EQ(123 * 1024, conf->max_size);
+ CHECK_STR_EQ_FREE1(format("%s.x", user), conf->path);
+ CHECK_STR_EQ_FREE1(format("x%s", user), conf->prefix_command);
+ CHECK(conf->read_only);
+ CHECK(conf->recache);
+ CHECK(conf->run_second_cpp);
+ CHECK_UNS_EQ(SLOPPY_INCLUDE_FILE_MTIME|SLOPPY_FILE_MACRO|SLOPPY_TIME_MACROS,
+ conf->sloppiness);
+ CHECK(!conf->stats);
+ CHECK_STR_EQ_FREE1(format("%s_foo", user), conf->temporary_dir);
+ CHECK_UNS_EQ(0777, conf->umask);
+ CHECK(conf->unify);
+
+ conf_free(conf);
+}
+
+TEST(conf_read_with_missing_equal_sign)
+{
+ struct conf *conf = conf_create();
+ char *errmsg;
+ create_file("ccache.conf", "no equal sign");
+ CHECK(!conf_read(conf, "ccache.conf", &errmsg));
+ CHECK_STR_EQ_FREE2("ccache.conf:1: missing equal sign",
+ errmsg);
+ conf_free(conf);
+}
+
+TEST(conf_read_with_bad_config_key)
+{
+ struct conf *conf = conf_create();
+ char *errmsg;
+ create_file("ccache.conf", "# Comment\nfoo = bar");
+ CHECK(!conf_read(conf, "ccache.conf", &errmsg));
+ CHECK_STR_EQ_FREE2("ccache.conf:2: unknown configuration option \"foo\"",
+ errmsg);
+ conf_free(conf);
+}
+
+TEST(conf_read_invalid_bool)
+{
+ struct conf *conf = conf_create();
+ char *errmsg;
+
+ create_file("ccache.conf", "disable=");
+ CHECK(!conf_read(conf, "ccache.conf", &errmsg));
+ CHECK_STR_EQ_FREE2("ccache.conf:1: not a boolean value: \"\"",
+ errmsg);
+
+ create_file("ccache.conf", "disable=foo");
+ CHECK(!conf_read(conf, "ccache.conf", &errmsg));
+ CHECK_STR_EQ_FREE2("ccache.conf:1: not a boolean value: \"foo\"",
+ errmsg);
+ conf_free(conf);
+}
+
+TEST(conf_read_invalid_env_string)
+{
+ struct conf *conf = conf_create();
+ char *errmsg;
+ create_file("ccache.conf", "base_dir = ${foo");
+ CHECK(!conf_read(conf, "ccache.conf", &errmsg));
+ CHECK_STR_EQ_FREE2("ccache.conf:1: syntax error: missing '}' after \"foo\"",
+ errmsg);
+ /* Other cases tested in test_util.c. */
+ conf_free(conf);
+}
+
+TEST(conf_read_invalid_octal)
+{
+ struct conf *conf = conf_create();
+ char *errmsg;
+ create_file("ccache.conf", "umask = 890x");
+ CHECK(!conf_read(conf, "ccache.conf", &errmsg));
+ CHECK_STR_EQ_FREE2("ccache.conf:1: not an octal integer: \"890x\"",
+ errmsg);
+ conf_free(conf);
+}
+
+TEST(conf_read_invalid_size)
+{
+ struct conf *conf = conf_create();
+ char *errmsg;
+ create_file("ccache.conf", "max_size = foo");
+ CHECK(!conf_read(conf, "ccache.conf", &errmsg));
+ CHECK_STR_EQ_FREE2("ccache.conf:1: invalid size: \"foo\"",
+ errmsg);
+ /* Other cases tested in test_util.c. */
+ conf_free(conf);
+}
+
+TEST(conf_read_invalid_sloppiness)
+{
+ struct conf *conf = conf_create();
+ char *errmsg;
+ create_file("ccache.conf", "sloppiness = file_macro, foo");
+ CHECK(!conf_read(conf, "ccache.conf", &errmsg));
+ CHECK_STR_EQ_FREE2("ccache.conf:1: unknown sloppiness: \"foo\"",
+ errmsg);
+ conf_free(conf);
+}
+
+TEST(conf_read_invalid_unsigned)
+{
+ struct conf *conf = conf_create();
+ char *errmsg;
+
+ create_file("ccache.conf", "max_files =");
+ CHECK(!conf_read(conf, "ccache.conf", &errmsg));
+ CHECK_STR_EQ_FREE2("ccache.conf:1: invalid unsigned integer: \"\"",
+ errmsg);
+
+ create_file("ccache.conf", "max_files = -42");
+ CHECK(!conf_read(conf, "ccache.conf", &errmsg));
+ CHECK_STR_EQ_FREE2("ccache.conf:1: invalid unsigned integer: \"-42\"",
+ errmsg);
+
+ create_file("ccache.conf", "max_files = 4294967296");
+ CHECK(!conf_read(conf, "ccache.conf", &errmsg));
+ CHECK_STR_EQ_FREE2("ccache.conf:1: invalid unsigned integer: \"4294967296\"",
+ errmsg);
+
+ create_file("ccache.conf", "max_files = foo");
+ CHECK(!conf_read(conf, "ccache.conf", &errmsg));
+ CHECK_STR_EQ_FREE2("ccache.conf:1: invalid unsigned integer: \"foo\"",
+ errmsg);
+
+ conf_free(conf);
+}
+
+TEST(verify_absolute_base_dir)
+{
+ struct conf *conf = conf_create();
+ char *errmsg;
+
+ create_file("ccache.conf", "base_dir = relative/path");
+ CHECK(!conf_read(conf, "ccache.conf", &errmsg));
+ CHECK_STR_EQ_FREE2("ccache.conf:1: not an absolute path: \"relative/path\"",
+ errmsg);
+
+ conf_free(conf);
+}
+
+TEST_SUITE_END
/*
- * Copyright (C) 2010 Joel Rosdahl
+ * Copyright (C) 2010-2011 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