From: Joel Rosdahl Date: Tue, 12 Jul 2011 19:30:52 +0000 (+0200) Subject: config: First steps towards supporting config files X-Git-Tag: v3.2~241 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=b55be7ece08f92ce4b47949db4ed046a798df884;p=thirdparty%2Fccache.git config: First steps towards supporting config files --- diff --git a/Makefile.in b/Makefile.in index d3802d056..54bceb2e3 100644 --- a/Makefile.in +++ b/Makefile.in @@ -6,12 +6,13 @@ exec_prefix = @exec_prefix@ 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@ @@ -22,7 +23,7 @@ base_sources = \ 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) diff --git a/ccache.c b/ccache.c index b749c7fe9..e143eb159 100644 --- a/ccache.c +++ b/ccache.c @@ -21,6 +21,7 @@ #include "ccache.h" #include "compopt.h" +#include "conf.h" #ifdef HAVE_GETOPT_LONG #include #else @@ -32,6 +33,9 @@ #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" @@ -66,6 +70,9 @@ static const char USAGE_TEXT[] = "\n" "See also .\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; @@ -75,9 +82,6 @@ char *cache_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; @@ -435,7 +439,7 @@ make_relative_path(char *path) { char *relpath; - if (!base_dir || !str_startswith(path, base_dir)) { + if (str_eq(conf->base_dir, "") || !str_startswith(path, conf->base_dir)) { return path; } @@ -1804,14 +1808,84 @@ out: 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; @@ -1838,6 +1912,8 @@ cc_reset(void) nlevels = 2; compile_preprocessed_source_code = false; output_is_precompiled_header = false; + + initialize(); } static unsigned @@ -1870,6 +1946,29 @@ parse_sloppiness(char *p) 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[]) @@ -1888,6 +1987,12 @@ 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")) { @@ -1901,8 +2006,8 @@ ccache(int argc, char *argv[]) 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")) { @@ -2027,14 +2132,6 @@ ccache(int argc, char *argv[]) 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[]) @@ -2065,13 +2162,13 @@ 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; @@ -2081,7 +2178,7 @@ ccache_main_options(int argc, char *argv[]) exit(0); case 'F': /* --max-files */ - check_cache_dir(); + initialize(); v = atoi(optarg); if (stats_set_limits(v, -1) == 0) { if (v == 0) { @@ -2096,7 +2193,7 @@ ccache_main_options(int argc, char *argv[]) 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) { @@ -2113,7 +2210,7 @@ ccache_main_options(int argc, char *argv[]) break; case 's': /* --show-stats */ - check_cache_dir(); + initialize(); stats_summary(); break; @@ -2122,7 +2219,7 @@ ccache_main_options(int argc, char *argv[]) exit(0); case 'z': /* --zero-stats */ - check_cache_dir(); + initialize(); stats_zero(); printf("Statistics cleared\n"); break; @@ -2136,68 +2233,11 @@ ccache_main_options(int argc, char *argv[]) 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); @@ -2211,18 +2251,6 @@ ccache_main(int argc, char *argv[]) } 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; } diff --git a/conf.c b/conf.c new file mode 100644 index 000000000..ded2f18e6 --- /dev/null +++ b/conf.c @@ -0,0 +1,514 @@ +/* + * 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; +} diff --git a/conf.h b/conf.h new file mode 100644 index 000000000..0890b42bd --- /dev/null +++ b/conf.h @@ -0,0 +1,40 @@ +#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 diff --git a/test.sh b/test.sh index 96bb4fc42..77d206dcf 100755 --- a/test.sh +++ b/test.sh @@ -1809,6 +1809,8 @@ CCACHE_DIR=`pwd`/.ccache export CCACHE_DIR CCACHE_LOGFILE=`pwd`/ccache.log export CCACHE_LOGFILE +CCACHE_CONFIG_PATH=/dev/null +export CCACHE_CONFIG_PATH # --------------------------------------- diff --git a/test/framework.c b/test/framework.c index 53a8d60d2..c366afeec 100644 --- a/test/framework.c +++ b/test/framework.c @@ -1,5 +1,5 @@ /* - * 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 @@ -126,6 +126,7 @@ cct_test_begin(const char *name) cct_chdir(name); current_test = name; + putenv("CCACHE_CONFIG_PATH=/dev/null"); cc_reset(); cache_logfile = getenv("CCACHE_LOGFILE"); } diff --git a/test/test_argument_processing.c b/test/test_argument_processing.c index a1fdb8168..0d8bd491c 100644 --- a/test/test_argument_processing.c +++ b/test/test_argument_processing.c @@ -21,9 +21,12 @@ */ #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) @@ -88,7 +91,6 @@ TEST(dependency_flags_that_take_an_argument_should_not_require_space_delimiter) 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"); @@ -96,19 +98,19 @@ TEST(sysroot_should_be_rewritten_if_basedir_is_used) 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) diff --git a/test/test_conf.c b/test/test_conf.c new file mode 100644 index 000000000..60393f999 --- /dev/null +++ b/test/test_conf.c @@ -0,0 +1,268 @@ +/* + * 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 diff --git a/test/test_util.c b/test/test_util.c index 435720558..39d8f6da9 100644 --- a/test/test_util.c +++ b/test/test_util.c @@ -1,5 +1,5 @@ /* - * 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