]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
config: First steps towards supporting config files
authorJoel Rosdahl <joel@rosdahl.net>
Tue, 12 Jul 2011 19:30:52 +0000 (21:30 +0200)
committerJoel Rosdahl <joel@rosdahl.net>
Sun, 17 Jul 2011 09:57:47 +0000 (11:57 +0200)
Makefile.in
ccache.c
conf.c [new file with mode: 0644]
conf.h [new file with mode: 0644]
test.sh
test/framework.c
test/test_argument_processing.c
test/test_conf.c [new file with mode: 0644]
test/test_util.c

index d3802d056b7a91c10d911f51e8ba56c932290562..54bceb2e3176d107b0bb70f9e88a29dfeb024d72 100644 (file)
@@ -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)
index b749c7fe9146bbea61b82eef7cff1fbb9594c43f..e143eb159d2c459d20099cbefdfc17f173941355 100644 (file)
--- a/ccache.c
+++ b/ccache.c
@@ -21,6 +21,7 @@
 
 #include "ccache.h"
 #include "compopt.h"
+#include "conf.h"
 #ifdef HAVE_GETOPT_LONG
 #include <getopt.h>
 #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 <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;
 
@@ -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 (file)
index 0000000..ded2f18
--- /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 (file)
index 0000000..0890b42
--- /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 96bb4fc42d4f66690b4385ff9f454ae5a0a24942..77d206dcf07c774b668085d7f8944987b5899798 100755 (executable)
--- 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
 
 # ---------------------------------------
 
index 53a8d60d212fac6a555235349901ae43186ae3b3..c366afeec72059888508a150811534ab3a0405e8 100644 (file)
@@ -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");
 }
index a1fdb81689d79ed493093e31095747b6b4a8ae55..0d8bd491c7f7cd2fad0e6d7b7ea301dc3fc4f41a 100644 (file)
  */
 
 #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 (file)
index 0000000..60393f9
--- /dev/null
@@ -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
index 4357205583af958d8c2c1343a04abf53a13ebb57..39d8f6da9e3648f05063050fa203df3d1816b582 100644 (file)
@@ -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