--
*file_macro*::
Ignore *\_\_FILE__* being present in the source.
- *include_file_mtime*::
- By default, ccache will not cache a file if it includes a header whose
- mtime is too new. This option disables that check.
++*file_stat_matches*::
++ ccache normally examines a file's contents to determine whether it matches
++ the cached version. With this option set, ccache will consider a file as
++ matching its cached version if the sizes, mtimes and ctimes match.
+*include_file_ctime*::
+ By default, ccache also will not cache a file if it includes a header whose
+ ctime is too new. This option disables that check.
+ *include_file_mtime*::
- Don't check the modification time of include files in the direct mode.
++ By default, ccache will not cache a file if it includes a header whose
++ mtime is too new. This option disables that check.
+ *pch_defines*::
+ Be sloppy about #defines when precompiling a header file. See
+ <<_precompiled_headers,PRECOMPILED HEADERS>> for more information.
*time_macros*::
Ignore *\_\_DATE\__* and *\_\_TIME__* being present in the source code.
- *file_stat_matches*::
- ccache normally examines a file's contents to determine whether it matches
- the cached version. With this option set, ccache will consider a file as
- matching its cached version if the sizes, mtimes and ctimes match.
--
+
See the discussion under <<_troubleshooting,TROUBLESHOOTING>> for more
ccache has support for GCC's precompiled headers. However, you have to do some
things to make it work properly:
- * You must set *sloppiness* to *time_macros*. The reason is that ccache can't
- tell whether *\_\_TIME\__* or *\_\_DATE__* is used when using a precompiled
- header.
-* You must set *CCACHE_SLOPPINESS* to *pch_defines,time_macros*. The reason is
- that ccache can't tell whether *\_\_TIME\__* or *\_\_DATE__* is used when
- using a precompiled header. Further, it can't detect changes in #defines in
- the source code because of how preprocessing works in combination with
++* You must set *sloppiness* to *pch_defines,time_macros*. The reason is that
++ ccache can't tell whether *\_\_TIME\__* or *\_\_DATE__* is used when using a
++ precompiled header. Further, it can't detect changes in #defines in the
++ source code because of how preprocessing works in combination with
+ precompiled headers.
* You must either:
+
--
if (found_pch || found_fpch_preprocess) {
using_precompiled_header = true;
- if (!(conf->sloppiness & SLOPPY_TIME_MACROS)) {
- if (!(sloppiness & SLOPPY_PCH_DEFINES)
- || !(sloppiness & SLOPPY_TIME_MACROS)) {
- cc_log("You have to specify \"pch_defines,time_macros\" sloppiness when"
- " using precompiled headers to get direct hits");
++ if (!(conf->sloppiness & SLOPPY_PCH_DEFINES)
++ || !(conf->sloppiness & SLOPPY_TIME_MACROS)) {
+ cc_log("You have to specify \"time_macros\" sloppiness when using"
+ " precompiled headers to get direct hits");
cc_log("Disabling direct mode");
stats_update(STATS_CANTUSEPCH);
result = false;
};
#define SLOPPY_INCLUDE_FILE_MTIME 1
-#define SLOPPY_FILE_MACRO 2
-#define SLOPPY_TIME_MACROS 4
-#define SLOPPY_PCH_DEFINES 8
+#define SLOPPY_INCLUDE_FILE_CTIME 2
+#define SLOPPY_FILE_MACRO 4
+#define SLOPPY_TIME_MACROS 8
-
++#define SLOPPY_PCH_DEFINES 16
+/*
+ * Allow us to match files based on their stats (size, mtime, ctime), without
+ * looking at their contents.
+ */
- #define SLOPPY_FILE_STAT_MATCHES 16
++#define SLOPPY_FILE_STAT_MATCHES 32
#define str_eq(s1, s2) (strcmp((s1), (s2)) == 0)
#define str_startswith(s, p) (strncmp((s), (p), strlen((p))) == 0)
--- /dev/null
- * Copyright (C) 2011-2013 Joel Rosdahl
+/*
- } else if (str_eq(word, "include_file_mtime")) {
- *value |= SLOPPY_INCLUDE_FILE_MTIME;
++ * Copyright (C) 2011-2014 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;
+ size_t number;
+ 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_size(const char *str, void *result, char **errmsg)
+{
+ uint64_t *value = (uint64_t *)result;
+ uint64_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, "file_stat_matches")) {
- *value |= SLOPPY_FILE_STAT_MATCHES;
++ } else if (str_eq(word, "file_stat_matches")) {
++ *value |= SLOPPY_FILE_STAT_MATCHES;
+ } else if (str_eq(word, "include_file_ctime")) {
+ *value |= SLOPPY_INCLUDE_FILE_CTIME;
++ } else if (str_eq(word, "include_file_mtime")) {
++ *value |= SLOPPY_INCLUDE_FILE_MTIME;
++ } else if (str_eq(word, "pch_defines")) {
++ *value |= SLOPPY_PCH_DEFINES;
+ } 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_umask(const char *str, void *result, char **errmsg)
+{
+ unsigned *value = (unsigned *)result;
+ char *endptr;
+ if (str_eq(str, "")) {
+ *value = UINT_MAX;
+ return true;
+ }
+ 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_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 && *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 (str_eq(*path, "")) {
+ /* The empty string means "disable" in this case. */
+ return true;
+ } else if (is_absolute_path(*path)) {
+ return true;
+ } else {
+ *errmsg = format("not an absolute path: \"%s\"", *path);
+ return false;
+ }
+}
+
+static bool
+verify_dir_levels(void *value, char **errmsg)
+{
+ unsigned *levels = (unsigned *)value;
+ assert(levels);
+ if (*levels >= 1 && *levels <= 8) {
+ return true;
+ } else {
+ *errmsg = format("cache directory levels must be between 1 and 8");
+ return false;
+ }
+}
+
+#define ITEM(name, type) \
+ parse_##type, offsetof(struct conf, name), NULL
+#define ITEM_V(name, type, verification) \
+ parse_##type, offsetof(struct conf, name), verify_##verification
+
+#include "confitems_lookup.c"
+#include "envtoconfitems_lookup.c"
+
+static const struct conf_item *
+find_conf(const char *name)
+{
+ return confitems_get(name, strlen(name));
+}
+
+static const struct env_to_conf_item *
+find_env_to_conf(const char *name)
+{
+ return envtoconfitems_get(name, strlen(name));
+}
+
+static bool
+handle_conf_setting(struct conf *conf, const char *key, const char *value,
+ char **errmsg, bool from_env_variable, bool negate_boolean,
+ const char *origin)
+{
+ const struct conf_item *item;
+
+ item = find_conf(key);
+ if (!item) {
+ *errmsg = format("unknown configuration option \"%s\"", key);
+ return false;
+ }
+
+ if (from_env_variable && item->parser == parse_bool) {
+ /*
+ * Special rule for boolean settings from the environment: any value means
+ * true.
+ */
+ bool *value = (bool *)((char *)conf + item->offset);
+ *value = !negate_boolean;
+ goto out;
+ }
+
+ if (!item->parser(value, (char *)conf + item->offset, errmsg)) {
+ return false;
+ }
+ if (item->verifier && !item->verifier((char *)conf + item->offset, errmsg)) {
+ return false;
+ }
+
+out:
+ conf->item_origins[item->number] = origin;
+ return true;
+}
+
+static bool
+parse_line(const char *line, char **key, char **value, char **errmsg)
+{
+ const char *p, *q;
+
+#define SKIP_WS(x) while (isspace(*x)) { ++x; }
+
+ *key = NULL;
+ *value = NULL;
+
+ 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);
+ *key = NULL;
+ 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);
+
+ return true;
+
+#undef SKIP_WS
+}
+
+/* Create a conf struct with default values. */
+struct conf *
+conf_create(void)
+{
+ size_t i;
+ 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->compression_level = 6;
+ conf->cpp_extension = x_strdup("");
+ 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 = (uint64_t)5 * 1000 * 1000 * 1000;
+ 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 = UINT_MAX; /* default: don't set umask */
+ conf->unify = false;
+ conf->item_origins = x_malloc(CONFITEMS_TOTAL_KEYWORDS * sizeof(char *));
+ for (i = 0; i < CONFITEMS_TOTAL_KEYWORDS; ++i) {
+ conf->item_origins[i] = "default";
+ }
+ 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->item_origins);
+ free(conf);
+}
+
+/* Note: The path pointer is stored in conf, so path must outlive 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, *key, *value;
+ bool ok;
+ ++line_number;
+ ok = parse_line(buf, &key, &value, &errmsg2);
+ if (ok && key) { /* key == NULL if comment or blank line */
+ ok = handle_conf_setting(conf, key, value, &errmsg2, false, false, path);
+ }
+ free(key);
+ free(value);
+ if (!ok) {
+ *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;
+ bool negate;
+ size_t key_start;
+
+ for (p = environ; *p; ++p) {
+ if (!str_startswith(*p, "CCACHE_")) {
+ continue;
+ }
+ q = strchr(*p, '=');
+ if (!q) {
+ continue;
+ }
+
+ if (str_startswith(*p + 7, "NO")) {
+ negate = true;
+ key_start = 9;
+ } else {
+ negate = false;
+ key_start = 7;
+ }
+ key = x_strndup(*p + key_start, q - *p - key_start);
+
+ ++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, true, negate,
+ "environment")) {
+ *errmsg = format("%s: %s", key, errmsg2);
+ free(errmsg2);
+ free(key);
+ return false;
+ }
+
+ free(key);
+ }
+
+ return true;
+}
+
+bool
+conf_set_value_in_file(const char *path, const char *key, const char *value,
+ char **errmsg)
+{
+ FILE *infile, *outfile;
+ char *outpath;
+ char buf[10000];
+ bool found;
+ const struct conf_item *item;
+
+ item = find_conf(key);
+ if (!item) {
+ *errmsg = format("unknown configuration option \"%s\"", key);
+ return false;
+ }
+
+ infile = fopen(path, "r");
+ if (!infile) {
+ *errmsg = format("%s: %s", path, strerror(errno));
+ return false;
+ }
+
+ outpath = format("%s.tmp.%s", path, tmp_string());
+ outfile = fopen(outpath, "w");
+ if (!outfile) {
+ *errmsg = format("%s: %s", outpath, strerror(errno));
+ free(outpath);
+ fclose(infile);
+ return false;
+ }
+
+ found = false;
+ while (fgets(buf, sizeof(buf), infile)) {
+ char *errmsg2, *key2, *value2;
+ bool ok;
+ ok = parse_line(buf, &key2, &value2, &errmsg2);
+ if (ok && key2 && str_eq(key2, key)) {
+ found = true;
+ fprintf(outfile, "%s = %s\n", key, value);
+ } else {
+ fputs(buf, outfile);
+ }
+ free(key2);
+ free(value2);
+ }
+
+ if (!found) {
+ fprintf(outfile, "%s = %s\n", key, value);
+ }
+
+ fclose(infile);
+ fclose(outfile);
+ if (x_rename(outpath, path) != 0) {
+ *errmsg = format("rename %s to %s: %s", outpath, path, strerror(errno));
+ return false;
+ }
+ free(outpath);
+
+ return true;
+}
+
+bool
+conf_print_items(struct conf *conf,
+ void (*printer)(const char *descr, const char *origin,
+ void *context),
+ void *context)
+{
+ char *s = x_strdup("");
+ char *s2;
+
+ reformat(&s, "base_dir = %s", conf->base_dir);
+ printer(s, conf->item_origins[find_conf("base_dir")->number], context);
+
+ reformat(&s, "cache_dir = %s", conf->cache_dir);
+ printer(s, conf->item_origins[find_conf("cache_dir")->number], context);
+
+ reformat(&s, "cache_dir_levels = %u", conf->cache_dir_levels);
+ printer(s, conf->item_origins[find_conf("cache_dir_levels")->number],
+ context);
+
+ reformat(&s, "compiler = %s", conf->compiler);
+ printer(s, conf->item_origins[find_conf("compiler")->number], context);
+
+ reformat(&s, "compiler_check = %s", conf->compiler_check);
+ printer(s, conf->item_origins[find_conf("compiler_check")->number], context);
+
+ reformat(&s, "compression = %s", conf->compression ? "true" : "false");
+ printer(s, conf->item_origins[find_conf("compression")->number], context);
+
+ reformat(&s, "compression_level = %u", conf->compression_level);
+ printer(s, conf->item_origins[find_conf("compression_level")->number],
+ context);
+
+ reformat(&s, "cpp_extension = %s", conf->cpp_extension);
+ printer(s, conf->item_origins[find_conf("cpp_extension")->number], context);
+
+ reformat(&s, "direct_mode = %s", conf->direct_mode ? "true" : "false");
+ printer(s, conf->item_origins[find_conf("direct_mode")->number], context);
+
+ reformat(&s, "disable = %s", conf->disable ? "true" : "false");
+ printer(s, conf->item_origins[find_conf("disable")->number], context);
+
+ reformat(&s, "extra_files_to_hash = %s", conf->extra_files_to_hash);
+ printer(s, conf->item_origins[find_conf("extra_files_to_hash")->number],
+ context);
+
+ reformat(&s, "hard_link = %s", conf->hard_link ? "true" : "false");
+ printer(s, conf->item_origins[find_conf("hard_link")->number], context);
+
+ reformat(&s, "hash_dir = %s", conf->hash_dir ? "true" : "false");
+ printer(s, conf->item_origins[find_conf("hash_dir")->number], context);
+
+ reformat(&s, "log_file = %s", conf->log_file);
+ printer(s, conf->item_origins[find_conf("log_file")->number], context);
+
+ reformat(&s, "max_files = %u", conf->max_files);
+ printer(s, conf->item_origins[find_conf("max_files")->number], context);
+
+ s2 = format_parsable_size_with_suffix(conf->max_size);
+ reformat(&s, "max_size = %s", s2);
+ printer(s, conf->item_origins[find_conf("max_size")->number], context);
+ free(s2);
+
+ reformat(&s, "path = %s", conf->path);
+ printer(s, conf->item_origins[find_conf("path")->number], context);
+
+ reformat(&s, "prefix_command = %s", conf->prefix_command);
+ printer(s, conf->item_origins[find_conf("prefix_command")->number], context);
+
+ reformat(&s, "read_only = %s", conf->read_only ? "true" : "false");
+ printer(s, conf->item_origins[find_conf("read_only")->number], context);
+
+ reformat(&s, "recache = %s", conf->recache ? "true" : "false");
+ printer(s, conf->item_origins[find_conf("recache")->number], context);
+
+ reformat(&s, "run_second_cpp = %s", conf->run_second_cpp ? "true" : "false");
+ printer(s, conf->item_origins[find_conf("run_second_cpp")->number], context);
+
+ reformat(&s, "sloppiness = ");
+ if (conf->sloppiness & SLOPPY_FILE_MACRO) {
+ reformat(&s, "%sfile_macro, ", s);
+ }
+ if (conf->sloppiness & SLOPPY_INCLUDE_FILE_MTIME) {
+ reformat(&s, "%sinclude_file_mtime, ", s);
+ }
+ if (conf->sloppiness & SLOPPY_INCLUDE_FILE_CTIME) {
+ reformat(&s, "%sinclude_file_ctime, ", s);
+ }
+ if (conf->sloppiness & SLOPPY_TIME_MACROS) {
+ reformat(&s, "%stime_macros, ", s);
+ }
+ if (conf->sloppiness & SLOPPY_FILE_STAT_MATCHES) {
+ reformat(&s, "%sfile_stat_matches, ", s);
+ }
+ if (conf->sloppiness) {
+ /* Strip last ", ". */
+ s[strlen(s) - 2] = '\0';
+ }
+ printer(s, conf->item_origins[find_conf("sloppiness")->number], context);
+
+ reformat(&s, "stats = %s", conf->stats ? "true" : "false");
+ printer(s, conf->item_origins[find_conf("stats")->number], context);
+
+ reformat(&s, "temporary_dir = %s", conf->temporary_dir);
+ printer(s, conf->item_origins[find_conf("temporary_dir")->number], context);
+
+ if (conf->umask == UINT_MAX) {
+ reformat(&s, "umask = ");
+ } else {
+ reformat(&s, "umask = %03o", conf->umask);
+ }
+ printer(s, conf->item_origins[find_conf("umask")->number], context);
+
+ reformat(&s, "unify = %s", conf->unify ? "true" : "false");
+ printer(s, conf->item_origins[find_conf("unify")->number], context);
+
+ free(s);
+ return true;
+}
#define time __TIME__
int test;
EOF
- CCACHE_SLOPPINESS="$default_sloppiness time_macros" $CCACHE $COMPILER -c time.c
- CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER -c time.c
++ CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER -c time.c
checkstat 'cache hit (direct)' 0
checkstat 'cache hit (preprocessed)' 0
checkstat 'cache miss' 1
- CCACHE_SLOPPINESS="$default_sloppiness time_macros" $CCACHE $COMPILER -c time.c
- CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER -c time.c
++ CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER -c time.c
checkstat 'cache hit (direct)' 1
checkstat 'cache hit (preprocessed)' 0
checkstat 'cache miss' 1
cat <<EOF >time_h.c
#include "time.h"
EOF
- CCACHE_SLOPPINESS="$default_sloppiness time_macros" $CCACHE $COMPILER -c time_h.c
- CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER -c time_h.c
++ CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER -c time_h.c
checkstat 'cache hit (direct)' 0
checkstat 'cache hit (preprocessed)' 0
checkstat 'cache miss' 1
- CCACHE_SLOPPINESS="$default_sloppiness time_macros" $CCACHE $COMPILER -c time_h.c
- CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER -c time_h.c
++ CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER -c time_h.c
checkstat 'cache hit (direct)' 1
checkstat 'cache hit (preprocessed)' 0
checkstat 'cache miss' 1
# trying to preprocess:
checkstat 'preprocessor error' 1
- testname="no -fpch-preprocess, -include, no sloppy time macros"
+ testname="no -fpch-preprocess, -include, no sloppiness"
$CCACHE -Cz >/dev/null
- $CCACHE $COMPILER -c -include pch.h pch2.c 2>/dev/null
+ $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
checkstat 'cache hit (direct)' 0
checkstat 'cache hit (preprocessed)' 0
checkstat 'cache miss' 0
testname="no -fpch-preprocess, -include"
$CCACHE -Cz >/dev/null
- CCACHE_SLOPPINESS="$default_sloppiness time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
- CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER -c -include pch.h pch2.c 2>/dev/null
++ CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
checkstat 'cache hit (direct)' 0
checkstat 'cache hit (preprocessed)' 0
checkstat 'cache miss' 1
- CCACHE_SLOPPINESS="$default_sloppiness time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
- CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER -c -include pch.h pch2.c 2>/dev/null
++ CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
checkstat 'cache hit (direct)' 1
checkstat 'cache hit (preprocessed)' 0
checkstat 'cache miss' 1
- testname="-fpch-preprocess, #include, no sloppy time macros"
+ testname="-fpch-preprocess, #include, no sloppiness"
$CCACHE -Cz >/dev/null
- $CCACHE $COMPILER -c -fpch-preprocess pch.c
+ $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
checkstat 'cache hit (direct)' 0
checkstat 'cache hit (preprocessed)' 0
# Must enable sloppy time macros:
checkstat "can't use precompiled header" 1
- testname="-fpch-preprocess, #include, sloppy time macros"
+ testname="-fpch-preprocess, #include, sloppiness"
$CCACHE -Cz >/dev/null
- CCACHE_SLOPPINESS="$default_sloppiness time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
- CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER -c -fpch-preprocess pch.c
++ CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
checkstat 'cache hit (direct)' 0
checkstat 'cache hit (preprocessed)' 0
checkstat 'cache miss' 1
- CCACHE_SLOPPINESS="$default_sloppiness time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
- CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER -c -fpch-preprocess pch.c
++ CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
checkstat 'cache hit (direct)' 1
checkstat 'cache hit (preprocessed)' 0
checkstat 'cache miss' 1
testname="-fpch-preprocess, #include, file changed"
echo "updated" >>pch.h.gch # GCC seems to cope with this...
backdate pch.h.gch
- CCACHE_SLOPPINESS="$default_sloppiness time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
- CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER -c -fpch-preprocess pch.c
++ CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
checkstat 'cache hit (direct)' 1
checkstat 'cache hit (preprocessed)' 0
checkstat 'cache miss' 2
testname="preprocessor mode"
$CCACHE -Cz >/dev/null
- CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
- CCACHE_NODIRECT=1 CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER -c -fpch-preprocess pch.c
++ CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
checkstat 'cache hit (direct)' 0
checkstat 'cache hit (preprocessed)' 0
checkstat 'cache miss' 1
- CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
- CCACHE_NODIRECT=1 CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER -c -fpch-preprocess pch.c
++ CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
checkstat 'cache hit (direct)' 0
checkstat 'cache hit (preprocessed)' 1
checkstat 'cache miss' 1
testname="preprocessor mode, file changed"
echo "updated" >>pch.h.gch # GCC seems to cope with this...
backdate pch.h.gch
- CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
- CCACHE_NODIRECT=1 CCACHE_SLOPPINESS=pch_defines,time_macros $CCACHE $COMPILER -c -fpch-preprocess pch.c
++ CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
+ checkstat 'cache hit (direct)' 0
+ checkstat 'cache hit (preprocessed)' 1
+ checkstat 'cache miss' 2
- CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
++ CCACHE_NODIRECT=1 CCACHE_SLOPPINESS="$default_sloppiness pch_defines time_macros" $CCACHE $COMPILER $SYSROOT -c -fpch-preprocess pch.c
+ checkstat 'cache hit (direct)' 0
+ checkstat 'cache hit (preprocessed)' 2
+ checkstat 'cache miss' 2
+}
+
+clang_pch_suite() {
+ ##################################################################
+ # Tests for creating a .gch.
+
+ backdate pch.h
+
+ testname="create .gch, -c, no -o"
+ $CCACHE -zC >/dev/null
+ $CCACHE $COMPILER $SYSROOT -c pch.h
+ checkstat 'cache hit (direct)' 0
+ checkstat 'cache hit (preprocessed)' 0
+ checkstat 'cache miss' 1
+ rm -f pch.h.gch
+ $CCACHE $COMPILER $SYSROOT -c pch.h
+ checkstat 'cache hit (direct)' 1
+ checkstat 'cache hit (preprocessed)' 0
+ checkstat 'cache miss' 1
+ if [ ! -f pch.h.gch ]; then
+ test_failed "pch.h.gch missing"
+ fi
+
+ testname="create .gch, no -c, -o"
+ $CCACHE -Cz >/dev/null
+ $CCACHE $COMPILER $SYSROOT pch.h -o pch.gch
+ checkstat 'cache hit (direct)' 0
+ checkstat 'cache hit (preprocessed)' 0
+ checkstat 'cache miss' 1
+ $CCACHE $COMPILER $SYSROOT pch.h -o pch.gch
+ checkstat 'cache hit (direct)' 1
+ checkstat 'cache hit (preprocessed)' 0
+ checkstat 'cache miss' 1
+ if [ ! -f pch.gch ]; then
+ test_failed "pch.gch missing"
+ fi
+ rm pch.gch
+
+ ##################################################################
+ # Tests for using a .gch.
+
+ backdate pch.h.gch
+
+ testname="gch, no -fpch-preprocess, -include, no sloppy time macros"
+ $CCACHE -Cz >/dev/null
+ $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
+ checkstat 'cache hit (direct)' 0
+ checkstat 'cache hit (preprocessed)' 0
+ checkstat 'cache miss' 0
+ # Must enable sloppy time macros:
+ checkstat "can't use precompiled header" 1
+
+ testname="gch, no -fpch-preprocess, -include, sloppy time macros"
+ $CCACHE -Cz >/dev/null
+ CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
+ checkstat 'cache hit (direct)' 0
+ checkstat 'cache hit (preprocessed)' 0
+ checkstat 'cache miss' 1
+ CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
+ checkstat 'cache hit (direct)' 1
+ checkstat 'cache hit (preprocessed)' 0
+ checkstat 'cache miss' 1
+
+ testname="gch, -fpch-preprocess, -include, file changed"
+ echo "updated" >>pch.h.gch # clang seems to cope with this...
+ backdate pch.h.gch
+ CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
+ checkstat 'cache hit (direct)' 1
+ checkstat 'cache hit (preprocessed)' 0
+ checkstat 'cache miss' 2
+
+ testname="gch, preprocessor mode"
+ $CCACHE -Cz >/dev/null
+ CCACHE_NODIRECT=1 CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
+ checkstat 'cache hit (direct)' 0
+ checkstat 'cache hit (preprocessed)' 0
+ checkstat 'cache miss' 1
+ CCACHE_NODIRECT=1 CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
+ checkstat 'cache hit (direct)' 0
+ checkstat 'cache hit (preprocessed)' 1
+ checkstat 'cache miss' 1
+
+ testname="gch, preprocessor mode, file changed"
+ echo "updated" >>pch.h.gch # clang seems to cope with this...
+ backdate pch.h.gch
+ CCACHE_NODIRECT=1 CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
+ checkstat 'cache hit (direct)' 0
+ checkstat 'cache hit (preprocessed)' 1
+ checkstat 'cache miss' 2
+ CCACHE_NODIRECT=1 CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
+ checkstat 'cache hit (direct)' 0
+ checkstat 'cache hit (preprocessed)' 2
+ checkstat 'cache miss' 2
+
+ rm pch.h.gch
+
+ ##################################################################
+ # Tests for creating a .pth.
+
+ backdate pch.h
+
+ testname="create .pth, -c, -o"
+ $CCACHE -zC >/dev/null
+ $CCACHE $COMPILER $SYSROOT -c pch.h -o pch.h.pth
+ checkstat 'cache hit (direct)' 0
+ checkstat 'cache hit (preprocessed)' 0
+ checkstat 'cache miss' 1
+ rm -f pch.h.pth
+ $CCACHE $COMPILER $SYSROOT -c pch.h -o pch.h.pth
+ checkstat 'cache hit (direct)' 1
+ checkstat 'cache hit (preprocessed)' 0
+ checkstat 'cache miss' 1
+ if [ ! -f pch.h.pth ]; then
+ test_failed "pch.h.pth missing"
+ fi
+
+ ##################################################################
+ # Tests for using a .pth.
+
+ backdate pch.h.pth
+
+ testname="pth, no -fpch-preprocess, -include, no sloppy time macros"
+ $CCACHE -Cz >/dev/null
+ $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c
+ checkstat 'cache hit (direct)' 0
+ checkstat 'cache hit (preprocessed)' 0
+ checkstat 'cache miss' 0
+ # Must enable sloppy time macros:
+ checkstat "can't use precompiled header" 1
+
+ testname="pth, no -fpch-preprocess, -include, sloppy time macros"
+ $CCACHE -Cz >/dev/null
+ CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
+ checkstat 'cache hit (direct)' 0
+ checkstat 'cache hit (preprocessed)' 0
+ checkstat 'cache miss' 1
+ CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h pch2.c 2>/dev/null
+ checkstat 'cache hit (direct)' 1
+ checkstat 'cache hit (preprocessed)' 0
+ checkstat 'cache miss' 1
+
+ testname="pth, -fpch-preprocess, -include, file changed"
+ echo "updated" >>pch.h.pth # clang seems to cope with this...
+ backdate pch.h.pth
+ CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
+ checkstat 'cache hit (direct)' 1
+ checkstat 'cache hit (preprocessed)' 0
+ checkstat 'cache miss' 2
+
+ testname="pth, preprocessor mode"
+ $CCACHE -Cz >/dev/null
+ CCACHE_NODIRECT=1 CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
+ checkstat 'cache hit (direct)' 0
+ checkstat 'cache hit (preprocessed)' 0
+ checkstat 'cache miss' 1
+ CCACHE_NODIRECT=1 CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
+ checkstat 'cache hit (direct)' 0
+ checkstat 'cache hit (preprocessed)' 1
+ checkstat 'cache miss' 1
+
+ testname="pth, preprocessor mode, file changed"
+ echo "updated" >>pch.h.pth # clang seems to cope with this...
+ backdate pch.h.pth
+ CCACHE_NODIRECT=1 CCACHE_SLOPPINESS=time_macros $CCACHE $COMPILER $SYSROOT -c -include pch.h -fpch-preprocess pch.c
checkstat 'cache hit (direct)' 0
checkstat 'cache hit (preprocessed)' 1
checkstat 'cache miss' 2
--- /dev/null
- * Copyright (C) 2011-2013 Joel Rosdahl
+/*
- "sloppiness = file_macro ,time_macros, include_file_mtime,include_file_ctime,file_stat_matches \n"
++ * Copyright (C) 2011-2014 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"
+
+#define N_CONFIG_ITEMS 26
+static struct {
+ char *descr;
+ const char *origin;
+} received_conf_items[N_CONFIG_ITEMS];
+static size_t n_received_conf_items = 0;
+
+static void
+conf_item_receiver(const char *descr, const char *origin, void *context)
+{
+ (void)context;
+ received_conf_items[n_received_conf_items].descr = x_strdup(descr);
+ received_conf_items[n_received_conf_items].origin = origin;
+ ++n_received_conf_items;
+}
+
+static void
+free_received_conf_items(void)
+{
+ while (n_received_conf_items > 0) {
+ --n_received_conf_items;
+ free(received_conf_items[n_received_conf_items].descr);
+ }
+}
+
+TEST_SUITE(conf)
+
+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_INT_EQ(2, conf->cache_dir_levels);
+ CHECK_STR_EQ("", conf->compiler);
+ CHECK_STR_EQ("mtime", conf->compiler_check);
+ CHECK(!conf->compression);
+ CHECK_INT_EQ(6, conf->compression_level);
+ CHECK_STR_EQ("", conf->cpp_extension);
+ 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_INT_EQ(0, conf->max_files);
+ CHECK_INT_EQ((uint64_t)5 * 1000 * 1000 * 1000, 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_INT_EQ(0, conf->sloppiness);
+ CHECK(conf->stats);
+ CHECK_STR_EQ("", conf->temporary_dir);
+ CHECK_INT_EQ(UINT_MAX, conf->umask);
+ CHECK(!conf->unify);
+ conf_free(conf);
+}
+
+TEST(conf_read_valid_config)
+{
+ struct conf *conf = conf_create();
+ char *errmsg, *user;
+ putenv("USER=rabbit");
+ user = getenv("USER");
+ CHECK_STR_EQ("rabbit", 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"
+ "compression_level= 2\n"
+ "cpp_extension = .foo\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"
- SLOPPY_FILE_STAT_MATCHES,
++ "sloppiness = file_macro ,time_macros, include_file_mtime,include_file_ctime,file_stat_matches pch_defines \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_INT_EQ(4, conf->cache_dir_levels);
+ CHECK_STR_EQ("foo", conf->compiler);
+ CHECK_STR_EQ("none", conf->compiler_check);
+ CHECK(conf->compression);
+ CHECK_INT_EQ(2, conf->compression_level);
+ CHECK_STR_EQ(".foo", conf->cpp_extension);
+ 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_INT_EQ(17, conf->max_files);
+ CHECK_INT_EQ(123 * 1000 * 1000, 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_INT_EQ(SLOPPY_INCLUDE_FILE_MTIME|SLOPPY_INCLUDE_FILE_CTIME|
+ SLOPPY_FILE_MACRO|SLOPPY_TIME_MACROS|
++ SLOPPY_FILE_STAT_MATCHES|SLOPPY_PCH_DEFINES,
+ conf->sloppiness);
+ CHECK(!conf->stats);
+ CHECK_STR_EQ_FREE1(format("%s_foo", user), conf->temporary_dir);
+ CHECK_INT_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_empty_umask)
+{
+ struct conf *conf = conf_create();
+ char *errmsg;
+ create_file("ccache.conf", "umask = ");
+ CHECK(conf_read(conf, "ccache.conf", &errmsg));
+ CHECK_INT_EQ(conf->umask, UINT_MAX);
+ 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 = 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);
+
+ create_file("ccache.conf", "base_dir =");
+ CHECK(conf_read(conf, "ccache.conf", &errmsg));
+
+ conf_free(conf);
+}
+
+TEST(verify_dir_levels)
+{
+ struct conf *conf = conf_create();
+ char *errmsg;
+
+ create_file("ccache.conf", "cache_dir_levels = 0");
+ CHECK(!conf_read(conf, "ccache.conf", &errmsg));
+ CHECK_STR_EQ_FREE2("ccache.conf:1: cache directory levels must be between 1 and 8",
+ errmsg);
+ create_file("ccache.conf", "cache_dir_levels = 9");
+ CHECK(!conf_read(conf, "ccache.conf", &errmsg));
+ CHECK_STR_EQ_FREE2("ccache.conf:1: cache directory levels must be between 1 and 8",
+ errmsg);
+
+ conf_free(conf);
+}
+
+TEST(conf_update_from_environment)
+{
+ struct conf *conf = conf_create();
+ char *errmsg;
+
+ putenv("CCACHE_COMPRESS=1");
+ CHECK(conf_update_from_environment(conf, &errmsg));
+ CHECK(conf->compression);
+
+ x_unsetenv("CCACHE_COMPRESS");
+ putenv("CCACHE_NOCOMPRESS=1");
+ CHECK(conf_update_from_environment(conf, &errmsg));
+ CHECK(!conf->compression);
+
+ conf_free(conf);
+}
+
+TEST(conf_set_new_value)
+{
+ char *errmsg;
+ char *data;
+
+ create_file("ccache.conf", "path = vanilla\n");
+ CHECK(conf_set_value_in_file("ccache.conf", "stats", "chocolate", &errmsg));
+ data = read_text_file("ccache.conf", 0);
+ CHECK(data);
+ CHECK_STR_EQ_FREE2("path = vanilla\nstats = chocolate\n", data);
+}
+
+TEST(conf_set_existing_value)
+{
+ char *errmsg;
+ char *data;
+
+ create_file("ccache.conf", "path = chocolate\nstats = chocolate\n");
+ CHECK(conf_set_value_in_file("ccache.conf", "path", "vanilla", &errmsg));
+ data = read_text_file("ccache.conf", 0);
+ CHECK(data);
+ CHECK_STR_EQ_FREE2("path = vanilla\nstats = chocolate\n", data);
+}
+
+TEST(conf_print_items)
+{
+ size_t i;
+ struct conf conf = {
+ "bd",
+ "cd",
+ 7,
+ "c",
+ "cc",
+ true,
+ 8,
+ "ce",
+ false,
+ true,
+ "efth",
+ true,
+ true,
+ "lf",
+ 4711,
+ 98.7 * 1000 * 1000,
+ "p",
+ "pc",
+ true,
+ true,
+ true,
+ SLOPPY_FILE_MACRO|SLOPPY_INCLUDE_FILE_MTIME|
+ SLOPPY_INCLUDE_FILE_CTIME|SLOPPY_TIME_MACROS|
+ SLOPPY_FILE_STAT_MATCHES,
+ false,
+ "td",
+ 022,
+ true,
+ NULL
+ };
+ size_t n = 0;
+
+ conf.item_origins = x_malloc(N_CONFIG_ITEMS * sizeof(char *));
+ for (i = 0; i < N_CONFIG_ITEMS; ++i) {
+ conf.item_origins[i] = format("origin%zu", i);
+ }
+
+ conf_print_items(&conf, conf_item_receiver, NULL);
+ CHECK_INT_EQ(N_CONFIG_ITEMS, n_received_conf_items);
+ CHECK_STR_EQ("base_dir = bd", received_conf_items[n++].descr);
+ CHECK_STR_EQ("cache_dir = cd", received_conf_items[n++].descr);
+ CHECK_STR_EQ("cache_dir_levels = 7", received_conf_items[n++].descr);
+ CHECK_STR_EQ("compiler = c", received_conf_items[n++].descr);
+ CHECK_STR_EQ("compiler_check = cc", received_conf_items[n++].descr);
+ CHECK_STR_EQ("compression = true", received_conf_items[n++].descr);
+ CHECK_STR_EQ("compression_level = 8", received_conf_items[n++].descr);
+ CHECK_STR_EQ("cpp_extension = ce", received_conf_items[n++].descr);
+ CHECK_STR_EQ("direct_mode = false", received_conf_items[n++].descr);
+ CHECK_STR_EQ("disable = true", received_conf_items[n++].descr);
+ CHECK_STR_EQ("extra_files_to_hash = efth", received_conf_items[n++].descr);
+ CHECK_STR_EQ("hard_link = true", received_conf_items[n++].descr);
+ CHECK_STR_EQ("hash_dir = true", received_conf_items[n++].descr);
+ CHECK_STR_EQ("log_file = lf", received_conf_items[n++].descr);
+ CHECK_STR_EQ("max_files = 4711", received_conf_items[n++].descr);
+ CHECK_STR_EQ("max_size = 98.7M", received_conf_items[n++].descr);
+ CHECK_STR_EQ("path = p", received_conf_items[n++].descr);
+ CHECK_STR_EQ("prefix_command = pc", received_conf_items[n++].descr);
+ CHECK_STR_EQ("read_only = true", received_conf_items[n++].descr);
+ CHECK_STR_EQ("recache = true", received_conf_items[n++].descr);
+ CHECK_STR_EQ("run_second_cpp = true", received_conf_items[n++].descr);
+ CHECK_STR_EQ("sloppiness = file_macro, include_file_mtime,"
+ " include_file_ctime, time_macros, file_stat_matches",
+ received_conf_items[n++].descr);
+ CHECK_STR_EQ("stats = false", received_conf_items[n++].descr);
+ CHECK_STR_EQ("temporary_dir = td", received_conf_items[n++].descr);
+ CHECK_STR_EQ("umask = 022", received_conf_items[n++].descr);
+ CHECK_STR_EQ("unify = true", received_conf_items[n++].descr);
+
+ for (i = 0; i < N_CONFIG_ITEMS; ++i) {
+ char *expected = format("origin%zu", i);
+ CHECK_STR_EQ(expected, received_conf_items[i].origin);
+ }
+
+ free_received_conf_items();
+ free(conf.item_origins);
+}
+
+TEST_SUITE_END