]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
C++-ify configuration handling
authorJoel Rosdahl <joel@rosdahl.net>
Wed, 14 Aug 2019 19:58:19 +0000 (21:58 +0200)
committerJoel Rosdahl <joel@rosdahl.net>
Wed, 14 Aug 2019 20:30:09 +0000 (22:30 +0200)
- Instead of using code generated by gperf, use ordinary unordered maps
  for the mapping from config keys (and environment variables) to config
  items.
- Renamed “struct conf* conf” to “class Config g_config”.
- C++-ified conf.cpp and confitems.cpp into Config.cpp.

42 files changed:
.gitignore
.travis.yml
.travis/Dockerfile
Makefile.in
buildenv/alpine/Dockerfile
buildenv/centos/Dockerfile
buildenv/debian/Dockerfile
buildenv/fedora/Dockerfile
buildenv/ubuntu/Dockerfile
configure.ac
dev.mk.in
doc/INSTALL.md
src/Config.cpp [new file with mode: 0644]
src/Config.hpp [new file with mode: 0644]
src/ccache.cpp
src/ccache.hpp
src/cleanup.cpp
src/compress.cpp
src/compression.cpp
src/conf.cpp [deleted file]
src/conf.hpp [deleted file]
src/confitems.cpp [deleted file]
src/confitems.gperf [deleted file]
src/confitems.hpp [deleted file]
src/envtoconfitems.gperf [deleted file]
src/envtoconfitems.hpp [deleted file]
src/execute.cpp
src/hashutil.cpp
src/hashutil.hpp
src/manifest.cpp
src/manifest.hpp
src/result.cpp
src/result.hpp
src/stats.cpp
src/util.cpp
unittest/framework.cpp
unittest/main.cpp
unittest/test_Config.cpp [new file with mode: 0644]
unittest/test_argument_processing.cpp
unittest/test_conf.cpp [deleted file]
unittest/test_legacy_util.cpp
unittest/test_util.cpp

index 8d7ffe1dc5dc02061ef87a6e5859f6c968fc60bb..0c5518e42f7331f18d39cedff5a627891849d0d2 100644 (file)
@@ -21,7 +21,6 @@ dev.mk
 dev_mode_disabled
 doc/ccache.1
 perfdir.*
-src/*_lookup.cpp
 src/version.cpp
 testdir.*
 unittest/run
index cbbf0c194751cf4e47930385eeb35e9b58caec21..756c737b9e202589b04a3ad929c04edd00a06feb 100644 (file)
@@ -18,7 +18,6 @@ matrix:
       apt:
         packages:
         - elfutils
-        - gperf
         - libzstd1-dev
         - libb2-dev
 
@@ -30,7 +29,6 @@ matrix:
       apt:
         packages:
         - elfutils
-        - gperf
         - libzstd1-dev
         - libb2-dev
 
@@ -47,7 +45,6 @@ matrix:
       apt:
         packages:
         - gcc-multilib
-        - gperf
         - lib32stdc++-5-dev
 
   # Job 5: Linux cross-compiled 32-bit MinGW
@@ -58,7 +55,6 @@ matrix:
       apt:
         packages:
         - elfutils
-        - gperf
 
   # Job 6: Linux cross-compiled 64-bit MinGW
   - os: linux
@@ -68,7 +64,6 @@ matrix:
       apt:
         packages:
         - elfutils
-        - gperf
 
   # Job 7: Clang's undefined behavior sanitizer (UBSan)
   - os: linux
@@ -78,7 +73,6 @@ matrix:
       apt:
         packages:
         - elfutils
-        - gperf
         - libzstd1-dev
         - libb2-dev
 
@@ -90,7 +84,6 @@ matrix:
       apt:
         packages:
         - elfutils
-        - gperf
         - libzstd1-dev
         - libb2-dev
 
@@ -101,7 +94,6 @@ matrix:
     addons:
       apt:
         packages:
-        - gperf
         - libzstd1-dev
         - libb2-dev
 
@@ -114,7 +106,6 @@ matrix:
       apt:
         packages:
         - elfutils
-        - gperf
         - libzstd1-dev
         - libb2-dev
     before_install:
index 6b17647ad922a7fb74562db68fe8d1ab44b05255..118aed5f9ecfd5a27426100443e4bf21ee2dbe24 100644 (file)
@@ -30,7 +30,6 @@ RUN apt-get -qq update && apt-get install -y --no-install-recommends \
 
 # ccache specific
 RUN apt-get -qq update && apt-get install -y --no-install-recommends \
-                gperf \
                 elfutils \
                 libzstd1-dev \
                 libb2-dev \
index 8b1a2338c38cc9edc91ed4b5ab68655e0c8e661d..9c0a86c9622aef68348183f978b72fc5110d1669 100644 (file)
@@ -32,6 +32,7 @@ Q=$(if $(quiet),@)
 
 non_third_party_sources = \
     src/AtomicFile.cpp \
+    src/Config.cpp \
     src/args.cpp \
     src/ccache.cpp \
     src/cleanup.cpp \
@@ -41,8 +42,6 @@ non_third_party_sources = \
     src/compr_zstd.cpp \
     src/compress.cpp \
     src/compression.cpp \
-    src/conf.cpp \
-    src/confitems.cpp \
     src/counters.cpp \
     src/decompr_none.cpp \
     src/decompr_zstd.cpp \
@@ -58,8 +57,6 @@ non_third_party_sources = \
     src/unify.cpp \
     src/util.cpp
 generated_sources = \
-    src/confitems_lookup.cpp \
-    src/envtoconfitems_lookup.cpp \
     src/version.cpp
 third_party_sources = \
     src/third_party/format.cpp \
@@ -76,12 +73,12 @@ non_third_party_objs = $(patsubst %.c, %.o, $(patsubst %.cpp, %.o, $(non_third_p
 ccache_sources = src/main.cpp $(base_sources)
 ccache_objs = $(patsubst %.c, %.o, $(patsubst %.cpp, %.o, $(ccache_sources)))
 
+test_suites += unittest/test_Config.cpp
 test_suites += unittest/test_args.cpp
 test_suites += unittest/test_argument_processing.cpp
 test_suites += unittest/test_compopt.cpp
 test_suites += unittest/test_compr_none.cpp
 test_suites += unittest/test_compr_zstd.cpp
-test_suites += unittest/test_conf.cpp
 test_suites += unittest/test_counters.cpp
 test_suites += unittest/test_hash.cpp
 test_suites += unittest/test_hashutil.cpp
index 8c9f88b58914f02c2be16d26d4dbc94b9e9b9196..2aa21fb02641271af5d313e135e023afe35af6f9 100644 (file)
@@ -6,6 +6,5 @@ RUN apk add --no-cache \
                 bash \
                 asciidoc \
                 autoconf \
-                gperf \
                 zstd-dev \
         ##
index d6863e156af05f008febd3dcfa9fe774f5e613bb..1bb01ff74687f67e6f890598cdff11c77615e030 100644 (file)
@@ -8,7 +8,6 @@ RUN yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.n
                 bash \
                 asciidoc \
                 autoconf \
-                gperf \
                 libzstd-devel \
         && rpm -e --nodeps graphviz \
         && yum autoremove -y \
index f897414fc001eb231f238e3452be2ac1f4db58da..28725f8c61328059ab99a878e011d5ae4a97bd17 100644 (file)
@@ -6,6 +6,5 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
                 bash \
                 asciidoc xsltproc docbook-xml docbook-xsl \
                 autoconf \
-                gperf \
                 libzstd-dev \
         && rm -rf /var/lib/apt/lists/*
index c30aa32b30368efdc8974567e0b7899e6d0f0081..e969b13c8050683c6b4cf44c500253b30cb5f410 100644 (file)
@@ -7,7 +7,6 @@ RUN dnf install -y \
                 bash \
                 asciidoc \
                 autoconf \
-                gperf \
                 libzstd-devel \
         && rpm -e --nodeps graphviz \
         && dnf autoremove -y \
index 2b92f2b0dd3a745ff1d852fb9751377c8d8637f0..27c78636f91b7055241df3448c9fefe540f7ad8e 100644 (file)
@@ -6,6 +6,5 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
                 bash \
                 asciidoc xsltproc docbook-xml docbook-xsl \
                 autoconf \
-                gperf \
                 libzstd-dev \
         && rm -rf /var/lib/apt/lists/*
index b900a494cd25e66c9b7712f032f597949442e697..46805ef8e5587280aa16b122a4f67655003bab49 100644 (file)
@@ -61,6 +61,7 @@ if test "$ac_compiler_clang" = yes; then
     more_warnings="$more_warnings -Wno-documentation-unknown-command"
     more_warnings="$more_warnings -Wno-exit-time-destructors"
     more_warnings="$more_warnings -Wno-format-nonliteral"
+    more_warnings="$more_warnings -Wno-global-constructors"
     more_warnings="$more_warnings -Wno-implicit-fallthrough"
     more_warnings="$more_warnings -Wno-padded"
     more_warnings="$more_warnings -Wno-shorten-64-to-32"
@@ -255,10 +256,6 @@ if test ! -f $srcdir/dev_mode_disabled; then
     include_dev_mk='include dev.mk'
     version=`(git --git-dir=$srcdir/.git describe --dirty 2>/dev/null || echo vunknown) | sed -e 's/v//' -e 's/-/+/' -e 's/-/_/g'`
     echo "extern const char CCACHE_VERSION@<:@@:>@; const char CCACHE_VERSION@<:@@:>@ = \"$version\";" >src/version.cpp
-    AC_CHECK_TOOL(GPERF, gperf)
-    if test -z "$GPERF"; then
-        AC_MSG_ERROR(please install gperf)
-    fi
 else
     AC_MSG_NOTICE(developer mode disabled)
 fi
index d4faad47387ec9c4f60288a233848498e3657fc5..b0faaaa3c35fbcecef8756f9b1d28459be8630ec 100644 (file)
--- a/dev.mk.in
+++ b/dev.mk.in
@@ -12,7 +12,6 @@ COMPILEDB = compiledb
 CPPCHECK = cppcheck
 CPPCHECK_SUPPRESSIONS = misc/cppcheck-suppressions.txt
 DOCKER = docker
-GPERF = @GPERF@
 SCAN_BUILD = scan-build
 SHELLCHECK = shellcheck
 SHELLCHECK_EXCLUDES = misc/shellcheck-excludes.txt
@@ -37,15 +36,13 @@ built_dist_files = $(generated_sources) $(generated_docs)
 
 non_third_party_headers = \
     src/AtomicFile.hpp \
+    src/Config.hpp \
     src/Error.hpp \
     src/ccache.hpp \
     src/common_header.hpp \
     src/compopt.hpp \
     src/compression.hpp \
-    src/conf.hpp \
-    src/confitems.hpp \
     src/counters.hpp \
-    src/envtoconfitems.hpp \
     src/hash.hpp \
     src/hashutil.hpp \
     src/int_bytes_conversion.hpp \
@@ -95,10 +92,6 @@ source_dist_files = \
     doc/NEWS.adoc \
     install-sh \
     m4 \
-    src/confitems.gperf \
-    src/confitems_lookup.cpp \
-    src/envtoconfitems.gperf \
-    src/envtoconfitems_lookup.cpp \
     src/main.cpp \
     src/third_party/minitrace.c \
     test/run \
@@ -113,24 +106,6 @@ ifneq ($(shell sed 's/.*"\(.*\)".*/\1/' src/version.cpp 2>/dev/null),$(version))
 endif
 src/version.o: src/version.cpp
 
-# $(1): Name.
-# $(2): Command for fixing up source file before the gperf call.
-define generate_gperf_lookup
-src/$(1)_lookup.cpp: src/$(1).gperf
-       $$(if $$(quiet),@echo "  GEN      $$@")
-       $$(Q)$(2) $$< | tr -d '\r' | $$(GPERF) | sed -e 's/#error/#warning/' -e 's/register//g' >$$@.tmp
-# Fix for gperf < 3.1 (fix parameter type and remove inlining of the get function):
-       $$(Q)perl -00 -pi -e 's/unsigned int len/size_t len/; s/#ifdef __GNUC__.*?gnu_inline.*?#endif\n#endif\n//sg' $$@.tmp
-       $$(Q)echo "size_t $(1)_count(void) { return $$$$(perl -ne '/TOTAL_KEYWORDS = (.+?),/ && print $$$$1' $$@.tmp); }" >>$$@.tmp
-       $$(Q)mv $$@.tmp $$@
-endef
-
-add_confitems_numbers = \
-    perl -pae '$$$$s = 1 if /^%%/; s/ITEM/$$$$n++ . ", ITEM"/e if $$$$s == 1'
-
-$(eval $(call generate_gperf_lookup,confitems,$(add_confitems_numbers)))
-$(eval $(call generate_gperf_lookup,envtoconfitems,cat))
-
 .PHONY: dist
 dist: $(dist_archives)
 
@@ -212,7 +187,7 @@ cppcheck:
        $(CPPCHECK) --suppressions-list=$(CPPCHECK_SUPPRESSIONS) \
          --inline-suppr -q --enable=all --force -I . \
          --template='cppcheck: warning: {id}:{file}:{line}: {message}' \
-         $(non_third_party_sources) src/confitems_lookup.cpp src/main.cpp $(test_sources)
+         $(non_third_party_sources) src/main.cpp $(test_sources)
 
 .PHONY: shellcheck
 shellcheck: test/suites/*.bash
index 76c874dc651b4ec9954eaafcd297aed062fb09e4..70afb9f9dd2d0fdb65e1282610c1ddc9aae3bbba 100644 (file)
@@ -14,7 +14,6 @@ To build ccache from a source repository, you need:
 - [xsltproc](http://xmlsoft.org/XSLT/xsltproc2.html) to build the man page.
 - [Autoconf](https://www.gnu.org/software/autoconf/) to generate the configure
   script and related files.
-- [gperf](https://www.gnu.org/software/gperf/) to create lookup tables.
 - [libb2](https://github.com/BLAKE2/libb2). If you don't have libb2 installed
   and can't or don't want to install it on your system, you can pass
   `--with-libb2-from-internet` to the configure script, which will make the
diff --git a/src/Config.cpp b/src/Config.cpp
new file mode 100644 (file)
index 0000000..d2beaff
--- /dev/null
@@ -0,0 +1,830 @@
+// Copyright (C) 2019 Joel Rosdahl and other contributors
+//
+// See doc/AUTHORS.adoc for a complete list of contributors.
+//
+// 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 "Config.hpp"
+
+#include "AtomicFile.hpp"
+#include "Error.hpp"
+#include "util.hpp"
+
+#include <algorithm>
+#include <cassert>
+#include <fstream>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+Config g_config;
+
+namespace {
+
+enum class ConfigItem {
+  base_dir,
+  cache_dir,
+  cache_dir_levels,
+  compiler,
+  compiler_check,
+  compression,
+  compression_level,
+  cpp_extension,
+  debug,
+  depend_mode,
+  direct_mode,
+  disable,
+  extra_files_to_hash,
+  file_clone,
+  hard_link,
+  hash_dir,
+  ignore_headers_in_manifest,
+  keep_comments_cpp,
+  limit_multiple,
+  log_file,
+  max_files,
+  max_size,
+  path,
+  pch_external_checksum,
+  prefix_command,
+  prefix_command_cpp,
+  read_only,
+  read_only_direct,
+  recache,
+  run_second_cpp,
+  sloppiness,
+  stats,
+  temporary_dir,
+  umask,
+  unify,
+};
+
+const std::unordered_map<std::string, ConfigItem> k_config_key_table = {
+  {"base_dir", ConfigItem::base_dir},
+  {"cache_dir", ConfigItem::cache_dir},
+  {"cache_dir_levels", ConfigItem::cache_dir_levels},
+  {"compiler", ConfigItem::compiler},
+  {"compiler_check", ConfigItem::compiler_check},
+  {"compression", ConfigItem::compression},
+  {"compression_level", ConfigItem::compression_level},
+  {"cpp_extension", ConfigItem::cpp_extension},
+  {"debug", ConfigItem::debug},
+  {"depend_mode", ConfigItem::depend_mode},
+  {"direct_mode", ConfigItem::direct_mode},
+  {"disable", ConfigItem::disable},
+  {"extra_files_to_hash", ConfigItem::extra_files_to_hash},
+  {"file_clone", ConfigItem::file_clone},
+  {"hard_link", ConfigItem::hard_link},
+  {"hash_dir", ConfigItem::hash_dir},
+  {"ignore_headers_in_manifest", ConfigItem::ignore_headers_in_manifest},
+  {"keep_comments_cpp", ConfigItem::keep_comments_cpp},
+  {"limit_multiple", ConfigItem::limit_multiple},
+  {"log_file", ConfigItem::log_file},
+  {"max_files", ConfigItem::max_files},
+  {"max_size", ConfigItem::max_size},
+  {"path", ConfigItem::path},
+  {"pch_external_checksum", ConfigItem::pch_external_checksum},
+  {"prefix_command", ConfigItem::prefix_command},
+  {"prefix_command_cpp", ConfigItem::prefix_command_cpp},
+  {"read_only", ConfigItem::read_only},
+  {"read_only_direct", ConfigItem::read_only_direct},
+  {"recache", ConfigItem::recache},
+  {"run_second_cpp", ConfigItem::run_second_cpp},
+  {"sloppiness", ConfigItem::sloppiness},
+  {"stats", ConfigItem::stats},
+  {"temporary_dir", ConfigItem::temporary_dir},
+  {"umask", ConfigItem::umask},
+  {"unify", ConfigItem::unify},
+};
+
+const std::unordered_map<std::string, std::string> k_env_variable_table = {
+  {"BASEDIR", "base_dir"},
+  {"CC", "compiler"}, // Alias for CCACHE_COMPILER
+  {"COMMENTS", "keep_comments_cpp"},
+  {"COMPILER", "compiler"},
+  {"COMPILERCHECK", "compiler_check"},
+  {"COMPRESS", "compression"},
+  {"COMPRESSLEVEL", "compression_level"},
+  {"CPP2", "run_second_cpp"},
+  {"DEBUG", "debug"},
+  {"DEPEND", "depend_mode"},
+  {"DIR", "cache_dir"},
+  {"DIRECT", "direct_mode"},
+  {"DISABLE", "disable"},
+  {"EXTENSION", "cpp_extension"},
+  {"EXTRAFILES", "extra_files_to_hash"},
+  {"FILECLONE", "file_clone"},
+  {"HARDLINK", "hard_link"},
+  {"HASHDIR", "hash_dir"},
+  {"IGNOREHEADERS", "ignore_headers_in_manifest"},
+  {"LIMIT_MULTIPLE", "limit_multiple"},
+  {"LOGFILE", "log_file"},
+  {"MAXFILES", "max_files"},
+  {"MAXSIZE", "max_size"},
+  {"NLEVELS", "cache_dir_levels"},
+  {"PATH", "path"},
+  {"PCH_EXTSUM", "pch_external_checksum"},
+  {"PREFIX", "prefix_command"},
+  {"PREFIX_CPP", "prefix_command_cpp"},
+  {"READONLY", "read_only"},
+  {"READONLY_DIRECT", "read_only_direct"},
+  {"RECACHE", "recache"},
+  {"SLOPPINESS", "sloppiness"},
+  {"STATS", "stats"},
+  {"TEMPDIR", "temporary_dir"},
+  {"UMASK", "umask"},
+  {"UNIFY", "unify"},
+};
+
+typedef std::function<void(
+  const std::string& line, const std::string& key, const std::string& value)>
+  ConfigLineHandler;
+
+bool
+parse_bool(const std::string& value, bool from_env_variable, bool negate)
+{
+  if (from_env_variable) {
+    // Special rule for boolean settings from the environment: "0", "false",
+    // "disable" and "no" (case insensitive) are invalid, and all other values
+    // mean true.
+    //
+    // Previously any value meant true, but this was surprising to users, who
+    // might do something like CCACHE_DISABLE=0 and expect ccache to be
+    // enabled.
+    std::string lower_value = util::to_lowercase(value);
+    if (value == "0" || lower_value == "false" || lower_value == "disable"
+        || lower_value == "no") {
+      throw Error(fmt::format(
+        "invalid boolean environment variable value \"{}\"", value));
+    }
+    return !negate;
+  } else if (value == "true") {
+    return true;
+  } else if (value == "false") {
+    return false;
+  } else {
+    throw Error(fmt::format("not a boolean value: \"{}\"", value));
+  }
+}
+
+std::string
+format_bool(bool value)
+{
+  return value ? "true" : "false";
+}
+
+std::string
+parse_env_string(const std::string& value)
+{
+  char* errmsg = nullptr;
+  char* substituted = subst_env_in_string(value.c_str(), &errmsg);
+  if (!substituted) {
+    std::string error_message = errmsg;
+    free(errmsg);
+    throw Error(error_message);
+  }
+  std::string result = substituted;
+  free(substituted);
+  return result;
+}
+
+double
+parse_double(const std::string& value)
+{
+  size_t end;
+  double result;
+  try {
+    result = std::stod(value, &end);
+  } catch (std::exception& e) {
+    throw Error(e.what());
+  }
+  if (end != value.size()) {
+    throw Error(fmt::format("invalid floating point: \"{}\"", value));
+  }
+  return result;
+}
+
+uint64_t
+parse_cache_size(const std::string& value)
+{
+  uint64_t result;
+  if (!parse_size_with_suffix(value.c_str(), &result)) {
+    throw Error(fmt::format("invalid size: \"{}\"", value));
+  }
+  return result;
+}
+
+std::string
+format_cache_size(uint64_t value)
+{
+  char* string = format_parsable_size_with_suffix(value);
+  std::string result = string;
+  free(string);
+  return result;
+}
+
+uint32_t
+parse_sloppiness(const std::string& value)
+{
+  size_t start = 0;
+  size_t end = 0;
+  uint32_t result = 0;
+  while (end != std::string::npos) {
+    end = value.find_first_of(", ", start);
+    std::string token =
+      util::strip_whitespace(value.substr(start, end - start));
+    if (token == "file_macro") {
+      result |= SLOPPY_FILE_MACRO;
+    } else if (token == "file_stat_matches") {
+      result |= SLOPPY_FILE_STAT_MATCHES;
+    } else if (token == "file_stat_matches_ctime") {
+      result |= SLOPPY_FILE_STAT_MATCHES_CTIME;
+    } else if (token == "include_file_ctime") {
+      result |= SLOPPY_INCLUDE_FILE_CTIME;
+    } else if (token == "include_file_mtime") {
+      result |= SLOPPY_INCLUDE_FILE_MTIME;
+    } else if (token == "system_headers" || token == "no_system_headers") {
+      result |= SLOPPY_SYSTEM_HEADERS;
+    } else if (token == "pch_defines") {
+      result |= SLOPPY_PCH_DEFINES;
+    } else if (token == "time_macros") {
+      result |= SLOPPY_TIME_MACROS;
+    } else if (token == "clang_index_store") {
+      result |= SLOPPY_CLANG_INDEX_STORE;
+    } else if (token == "locale") {
+      result |= SLOPPY_LOCALE;
+    } else if (token != "") {
+      throw Error(fmt::format("unknown sloppiness: \"{}\"", token));
+    }
+    start = value.find_first_not_of(", ", end);
+  }
+  return result;
+}
+
+std::string
+format_sloppiness(uint32_t sloppiness)
+{
+  std::string result;
+  if (sloppiness & SLOPPY_FILE_MACRO) {
+    result += "file_macro, ";
+  }
+  if (sloppiness & SLOPPY_INCLUDE_FILE_MTIME) {
+    result += "include_file_mtime, ";
+  }
+  if (sloppiness & SLOPPY_INCLUDE_FILE_CTIME) {
+    result += "include_file_ctime, ";
+  }
+  if (sloppiness & SLOPPY_TIME_MACROS) {
+    result += "time_macros, ";
+  }
+  if (sloppiness & SLOPPY_PCH_DEFINES) {
+    result += "pch_defines, ";
+  }
+  if (sloppiness & SLOPPY_FILE_STAT_MATCHES) {
+    result += "file_stat_matches, ";
+  }
+  if (sloppiness & SLOPPY_FILE_STAT_MATCHES_CTIME) {
+    result += "file_stat_matches_ctime, ";
+  }
+  if (sloppiness & SLOPPY_SYSTEM_HEADERS) {
+    result += "system_headers, ";
+  }
+  if (sloppiness & SLOPPY_CLANG_INDEX_STORE) {
+    result += "clang_index_store, ";
+  }
+  if (sloppiness & SLOPPY_LOCALE) {
+    result += "locale, ";
+  }
+  if (!result.empty()) {
+    // Strip last ", ".
+    result.resize(result.size() - 2);
+  }
+  return result;
+}
+
+uint32_t
+parse_umask(const std::string& value)
+{
+  if (value.empty()) {
+    return std::numeric_limits<uint32_t>::max();
+  }
+
+  size_t end;
+  uint32_t result = std::stoul(value, &end, 8);
+  if (end != value.size()) {
+    throw Error(fmt::format("not an octal integer: \"{}\"", value));
+  }
+  return result;
+}
+
+std::string
+format_umask(uint32_t umask)
+{
+  if (umask == std::numeric_limits<uint32_t>::max()) {
+    return {};
+  } else {
+    return fmt::format("{:03o}", umask);
+  }
+}
+
+int
+parse_int(const std::string& value)
+{
+  size_t end;
+  long result;
+  bool failed = false;
+  try {
+    result = std::stol(value, &end, 10);
+  } catch (std::exception&) {
+    failed = true;
+  }
+  if (failed || end != value.size() || result < std::numeric_limits<int>::min()
+      || result > std::numeric_limits<int>::max()) {
+    throw Error(fmt::format("invalid integer: \"{}\"", value));
+  }
+  return result;
+}
+
+unsigned
+parse_unsigned(const std::string& value)
+{
+  size_t end;
+  long result;
+  bool failed = false;
+  try {
+    result = std::stol(value, &end, 10);
+  } catch (std::exception&) {
+    failed = true;
+  }
+  if (failed || end != value.size() || result < 0) {
+    throw Error(fmt::format("invalid unsigned integer: \"{}\"", value));
+  }
+  return result;
+}
+
+void
+verify_absolute_path(const std::string& value)
+{
+  if (!is_absolute_path(value.c_str())) {
+    throw Error(fmt::format("not an absolute path: \"{}\"", value));
+  }
+}
+
+bool
+parse_line(const std::string& line,
+           std::string* key,
+           std::string* value,
+           std::string* error_message)
+{
+  std::string stripped_line = util::strip_whitespace(line);
+  if (stripped_line.empty() || stripped_line[0] == '#') {
+    return true;
+  }
+  size_t equal_pos = stripped_line.find("=");
+  if (equal_pos == std::string::npos) {
+    *error_message = "missing equal sign";
+    return false;
+  }
+  *key = stripped_line.substr(0, equal_pos);
+  *value = stripped_line.substr(equal_pos + 1);
+  *key = util::strip_whitespace(*key);
+  *value = util::strip_whitespace(*value);
+  return true;
+}
+
+bool
+parse_config_file(const std::string& path,
+                  const ConfigLineHandler& config_line_handler)
+{
+  std::ifstream file(path);
+  if (!file) {
+    return false;
+  }
+
+  std::string line;
+
+  size_t line_number = 0;
+  while (std::getline(file, line)) {
+    ++line_number;
+
+    try {
+      std::string key;
+      std::string value;
+      std::string error_message;
+      if (!parse_line(line, &key, &value, &error_message)) {
+        throw Error(error_message);
+      }
+      if (!key.empty()) {
+        // key is empty if comment or blank line.
+        config_line_handler(line, key, value);
+      }
+    } catch (const Error& e) {
+      throw Error(fmt::format("{}:{}: {}", path, line_number, e.what()));
+    }
+  }
+  return true;
+}
+
+} // namespace
+
+bool
+Config::update_from_file(const std::string& file_path)
+{
+  return parse_config_file(file_path,
+                           [&](const std::string& /*line*/,
+                               const std::string& key,
+                               const std::string& value) {
+                             set_item(key, value, false, false, file_path);
+                           });
+}
+
+void
+Config::update_from_environment()
+{
+  for (char** env = environ; *env; ++env) {
+    std::string setting = *env;
+    const std::string prefix = "CCACHE_";
+    if (!util::starts_with(setting, prefix)) {
+      continue;
+    }
+    size_t equal_pos = setting.find('=');
+    if (equal_pos == std::string::npos) {
+      continue;
+    }
+
+    std::string key = setting.substr(prefix.size(), equal_pos - prefix.size());
+    std::string value = setting.substr(equal_pos + 1);
+    bool negate = util::starts_with(key, "NO");
+    if (negate) {
+      key = key.substr(2);
+    }
+
+    auto it = k_env_variable_table.find(key);
+    if (it == k_env_variable_table.end()) {
+      // Ignore unknown keys.
+      continue;
+    }
+    const auto& config_key = it->second;
+
+    try {
+      set_item(config_key, value, true, negate, "environment");
+    } catch (const Error& e) {
+      throw Error(fmt::format("CCACHE_{}: {}", key, e.what()));
+    }
+  }
+}
+
+std::string
+Config::get_string_value(const std::string& key) const
+{
+  auto it = k_config_key_table.find(key);
+  if (it == k_config_key_table.end()) {
+    throw Error(fmt::format("unknown configuration option \"{}\"", key));
+  }
+
+  switch (it->second) {
+  case ConfigItem::base_dir:
+    return m_base_dir;
+
+  case ConfigItem::cache_dir:
+    return m_cache_dir;
+
+  case ConfigItem::cache_dir_levels:
+    return fmt::format("{}", m_cache_dir_levels);
+
+  case ConfigItem::compiler:
+    return m_compiler;
+
+  case ConfigItem::compiler_check:
+    return m_compiler_check;
+
+  case ConfigItem::compression:
+    return format_bool(m_compression);
+
+  case ConfigItem::compression_level:
+    return fmt::format("{}", m_compression_level);
+
+  case ConfigItem::cpp_extension:
+    return m_cpp_extension;
+
+  case ConfigItem::debug:
+    return format_bool(m_debug);
+
+  case ConfigItem::depend_mode:
+    return format_bool(m_depend_mode);
+
+  case ConfigItem::direct_mode:
+    return format_bool(m_direct_mode);
+
+  case ConfigItem::disable:
+    return format_bool(m_disable);
+
+  case ConfigItem::extra_files_to_hash:
+    return m_extra_files_to_hash;
+
+  case ConfigItem::file_clone:
+    return format_bool(m_file_clone);
+
+  case ConfigItem::hard_link:
+    return format_bool(m_hard_link);
+
+  case ConfigItem::hash_dir:
+    return format_bool(m_hash_dir);
+
+  case ConfigItem::ignore_headers_in_manifest:
+    return m_ignore_headers_in_manifest;
+
+  case ConfigItem::keep_comments_cpp:
+    return format_bool(m_keep_comments_cpp);
+
+  case ConfigItem::limit_multiple:
+    return fmt::format("{:.1f}", m_limit_multiple);
+
+  case ConfigItem::log_file:
+    return m_log_file;
+
+  case ConfigItem::max_files:
+    return fmt::format("{}", m_max_files);
+
+  case ConfigItem::max_size:
+    return format_cache_size(m_max_size);
+
+  case ConfigItem::path:
+    return m_path;
+
+  case ConfigItem::pch_external_checksum:
+    return format_bool(m_pch_external_checksum);
+
+  case ConfigItem::prefix_command:
+    return m_prefix_command;
+
+  case ConfigItem::prefix_command_cpp:
+    return m_prefix_command_cpp;
+
+  case ConfigItem::read_only:
+    return format_bool(m_read_only);
+
+  case ConfigItem::read_only_direct:
+    return format_bool(m_read_only_direct);
+
+  case ConfigItem::recache:
+    return format_bool(m_recache);
+
+  case ConfigItem::run_second_cpp:
+    return format_bool(m_run_second_cpp);
+
+  case ConfigItem::sloppiness:
+    return format_sloppiness(m_sloppiness);
+
+  case ConfigItem::stats:
+    return format_bool(m_stats);
+
+  case ConfigItem::temporary_dir:
+    return m_temporary_dir;
+
+  case ConfigItem::umask:
+    return format_umask(m_umask);
+
+  case ConfigItem::unify:
+    return format_bool(m_unify);
+  }
+
+  assert(false);
+  return {}; // Never reached
+}
+
+void
+Config::set_value_in_file(const std::string& path,
+                          const std::string& key,
+                          const std::string& value)
+{
+  if (k_config_key_table.find(key) == k_config_key_table.end()) {
+    throw Error(fmt::format("unknown configuration option \"{}\"", key));
+  }
+
+  // Verify that the value is valid; set_item will throw if not.
+  Config dummy_config;
+  dummy_config.set_item(key, value, false, false, "");
+
+  std::string content = util::read_file(path);
+
+  AtomicFile output(path, AtomicFile::Mode::Text);
+  bool found = false;
+
+  if (!parse_config_file(path,
+                         [&](const std::string& c_line,
+                             const std::string& c_key,
+                             const std::string& /*c_value*/) {
+                           if (c_key == key) {
+                             output.write(fmt::format("{} = {}\n", key, value));
+                             found = true;
+                           } else {
+                             output.write(fmt::format("{}\n", c_line));
+                           }
+                         })) {
+    throw Error(fmt::format("failed to open {}: {}", path, strerror(errno)));
+  }
+
+  if (!found) {
+    output.write(fmt::format("{} = {}\n", key, value));
+  }
+
+  output.close();
+}
+
+void
+Config::visit_items(const ItemVisitor& item_visitor) const
+{
+  std::vector<std::string> keys;
+  for (const auto& item : k_config_key_table) {
+    keys.emplace_back(item.first);
+  }
+  std::sort(keys.begin(), keys.end());
+  for (const auto& key : keys) {
+    auto it = m_origins.find(key);
+    std::string origin = it != m_origins.end() ? it->second : "default";
+    item_visitor(key, get_string_value(key), origin);
+  }
+}
+
+void
+Config::set_item(const std::string& key,
+                 const std::string& value,
+                 bool from_env_variable,
+                 bool negate,
+                 const std::string& origin)
+{
+  auto it = k_config_key_table.find(key);
+  if (it == k_config_key_table.end()) {
+    // Ignore unknown keys.
+    return;
+  }
+
+  switch (it->second) {
+  case ConfigItem::base_dir:
+    m_base_dir = parse_env_string(value);
+    if (!m_base_dir.empty()) { // The empty string means "disable"
+      verify_absolute_path(m_base_dir);
+    }
+    break;
+
+  case ConfigItem::cache_dir:
+    m_cache_dir = parse_env_string(value);
+    break;
+
+  case ConfigItem::cache_dir_levels:
+    m_cache_dir_levels = parse_unsigned(value);
+    if (m_cache_dir_levels < 1 || m_cache_dir_levels > 8) {
+      throw Error("cache directory levels must be between 1 and 8");
+    }
+    break;
+
+  case ConfigItem::compiler:
+    m_compiler = value;
+    break;
+
+  case ConfigItem::compiler_check:
+    m_compiler_check = value;
+    break;
+
+  case ConfigItem::compression:
+    m_compression = parse_bool(value, from_env_variable, negate);
+    break;
+
+  case ConfigItem::compression_level: {
+    auto level = parse_int(value);
+    if (level < -128 || level > 127) {
+      throw Error("compression level must be between -128 and 127");
+    }
+    m_compression_level = level;
+    break;
+  }
+
+  case ConfigItem::cpp_extension:
+    m_cpp_extension = value;
+    break;
+
+  case ConfigItem::debug:
+    m_debug = parse_bool(value, from_env_variable, negate);
+    break;
+
+  case ConfigItem::depend_mode:
+    m_depend_mode = parse_bool(value, from_env_variable, negate);
+    break;
+
+  case ConfigItem::direct_mode:
+    m_direct_mode = parse_bool(value, from_env_variable, negate);
+    break;
+
+  case ConfigItem::disable:
+    m_disable = parse_bool(value, from_env_variable, negate);
+    break;
+
+  case ConfigItem::extra_files_to_hash:
+    m_extra_files_to_hash = parse_env_string(value);
+    break;
+
+  case ConfigItem::file_clone:
+    m_file_clone = parse_bool(value, from_env_variable, negate);
+    break;
+
+  case ConfigItem::hard_link:
+    m_hard_link = parse_bool(value, from_env_variable, negate);
+    break;
+
+  case ConfigItem::hash_dir:
+    m_hash_dir = parse_bool(value, from_env_variable, negate);
+    break;
+
+  case ConfigItem::ignore_headers_in_manifest:
+    m_ignore_headers_in_manifest = parse_env_string(value);
+    break;
+
+  case ConfigItem::keep_comments_cpp:
+    m_keep_comments_cpp = parse_bool(value, from_env_variable, negate);
+    break;
+
+  case ConfigItem::limit_multiple:
+    m_limit_multiple = parse_double(value);
+    break;
+
+  case ConfigItem::log_file:
+    m_log_file = parse_env_string(value);
+    break;
+
+  case ConfigItem::max_files:
+    m_max_files = parse_unsigned(value);
+    break;
+
+  case ConfigItem::max_size:
+    m_max_size = parse_cache_size(value);
+    break;
+
+  case ConfigItem::path:
+    m_path = parse_env_string(value);
+    break;
+
+  case ConfigItem::pch_external_checksum:
+    m_pch_external_checksum = parse_bool(value, from_env_variable, negate);
+    break;
+
+  case ConfigItem::prefix_command:
+    m_prefix_command = parse_env_string(value);
+    break;
+
+  case ConfigItem::prefix_command_cpp:
+    m_prefix_command_cpp = parse_env_string(value);
+    break;
+
+  case ConfigItem::read_only:
+    m_read_only = parse_bool(value, from_env_variable, negate);
+    break;
+
+  case ConfigItem::read_only_direct:
+    m_read_only_direct = parse_bool(value, from_env_variable, negate);
+    break;
+
+  case ConfigItem::recache:
+    m_recache = parse_bool(value, from_env_variable, negate);
+    break;
+
+  case ConfigItem::run_second_cpp:
+    m_run_second_cpp = parse_bool(value, from_env_variable, negate);
+    break;
+
+  case ConfigItem::sloppiness:
+    m_sloppiness = parse_sloppiness(value);
+    break;
+
+  case ConfigItem::stats:
+    m_stats = parse_bool(value, from_env_variable, negate);
+    break;
+
+  case ConfigItem::temporary_dir:
+    m_temporary_dir = parse_env_string(value);
+    break;
+
+  case ConfigItem::umask:
+    m_umask = parse_umask(value);
+    break;
+
+  case ConfigItem::unify:
+    m_unify = parse_bool(value, from_env_variable, negate);
+    break;
+  }
+
+  m_origins.emplace(key, origin);
+}
diff --git a/src/Config.hpp b/src/Config.hpp
new file mode 100644 (file)
index 0000000..a05ba4f
--- /dev/null
@@ -0,0 +1,423 @@
+// Copyright (C) 2019 Joel Rosdahl and other contributors
+//
+// See doc/AUTHORS.adoc for a complete list of contributors.
+//
+// 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
+
+#pragma once
+
+#include "system.hpp"
+
+#include "ccache.hpp"
+
+#include <fmt/core.h>
+#include <functional>
+#include <limits>
+#include <string>
+#include <unordered_map>
+
+class Config;
+extern Config g_config;
+
+class Config
+{
+public:
+  const std::string& base_dir() const;
+  const std::string& cache_dir() const;
+  uint32_t cache_dir_levels() const;
+  const std::string& compiler() const;
+  const std::string& compiler_check() const;
+  bool compression() const;
+  int8_t compression_level() const;
+  const std::string& cpp_extension() const;
+  bool debug() const;
+  bool depend_mode() const;
+  bool direct_mode() const;
+  bool disable() const;
+  const std::string& extra_files_to_hash() const;
+  bool file_clone() const;
+  bool hard_link() const;
+  bool hash_dir() const;
+  const std::string& ignore_headers_in_manifest() const;
+  bool keep_comments_cpp() const;
+  double limit_multiple() const;
+  const std::string& log_file() const;
+  uint32_t max_files() const;
+  uint64_t max_size() const;
+  const std::string& path() const;
+  bool pch_external_checksum() const;
+  const std::string& prefix_command() const;
+  const std::string& prefix_command_cpp() const;
+  bool read_only() const;
+  bool read_only_direct() const;
+  bool recache() const;
+  bool run_second_cpp() const;
+  uint32_t sloppiness() const;
+  bool stats() const;
+  const std::string& temporary_dir() const;
+  uint32_t umask() const;
+  bool unify() const;
+
+  void set_base_dir(const std::string& value);
+  void set_cache_dir(const std::string& value);
+  void set_cpp_extension(const std::string& value);
+  void set_depend_mode(bool value);
+  void set_direct_mode(bool value);
+  void set_limit_multiple(double value);
+  void set_max_files(uint32_t value);
+  void set_max_size(uint64_t value);
+  void set_run_second_cpp(bool value);
+  void set_unify(bool value);
+
+  typedef std::function<void(const std::string& key,
+                             const std::string& value,
+                             const std::string& origin)>
+    ItemVisitor;
+
+  // Set config values from a configuration file.
+  //
+  // Returns false if the file can't be opened, otherwise true. Throws Error on
+  // invalid configuration values.
+  bool update_from_file(const std::string& path);
+
+  // Set config values from environment variables.
+  //
+  // Throws Error on invalid configuration values.
+  void update_from_environment();
+
+  // Get a config value in string form given a key.
+  std::string get_string_value(const std::string& key) const;
+
+  void visit_items(const ItemVisitor& item_visitor) const;
+
+  static void set_value_in_file(const std::string& path,
+                                const std::string& key,
+                                const std::string& value);
+
+private:
+  std::string m_base_dir = "";
+  std::string m_cache_dir = fmt::format("{}/.ccache", get_home_directory());
+  uint32_t m_cache_dir_levels = 2;
+  std::string m_compiler = "";
+  std::string m_compiler_check = "mtime";
+  bool m_compression = true;
+  int8_t m_compression_level = 0; // Use default level
+  std::string m_cpp_extension = "";
+  bool m_debug = false;
+  bool m_depend_mode = false;
+  bool m_direct_mode = true;
+  bool m_disable = false;
+  std::string m_extra_files_to_hash = "";
+  bool m_file_clone = false;
+  bool m_hard_link = false;
+  bool m_hash_dir = true;
+  std::string m_ignore_headers_in_manifest = "";
+  bool m_keep_comments_cpp = false;
+  double m_limit_multiple = 0.8;
+  std::string m_log_file = "";
+  uint32_t m_max_files = 0;
+  uint64_t m_max_size = 5ULL * 1000 * 1000 * 1000;
+  std::string m_path = "";
+  bool m_pch_external_checksum = false;
+  std::string m_prefix_command = "";
+  std::string m_prefix_command_cpp = "";
+  bool m_read_only = false;
+  bool m_read_only_direct = false;
+  bool m_recache = false;
+  bool m_run_second_cpp = true;
+  uint32_t m_sloppiness = 0;
+  bool m_stats = true;
+  std::string m_temporary_dir = "";
+  uint32_t m_umask = std::numeric_limits<uint32_t>::max(); // Don't set umask
+  bool m_unify = false;
+
+  std::unordered_map<std::string /*key*/, std::string /*origin*/> m_origins;
+
+  void set_item(const std::string& key,
+                const std::string& value,
+                bool from_env_variable,
+                bool negate,
+                const std::string& origin);
+};
+
+inline const std::string&
+Config::base_dir() const
+{
+  return m_base_dir;
+}
+
+inline const std::string&
+Config::cache_dir() const
+{
+  return m_cache_dir;
+}
+
+inline uint32_t
+Config::cache_dir_levels() const
+{
+  return m_cache_dir_levels;
+}
+
+inline const std::string&
+Config::compiler() const
+{
+  return m_compiler;
+}
+
+inline const std::string&
+Config::compiler_check() const
+{
+  return m_compiler_check;
+}
+
+inline bool
+Config::compression() const
+{
+  return m_compression;
+}
+
+inline int8_t
+Config::compression_level() const
+{
+  return m_compression_level;
+}
+
+inline const std::string&
+Config::cpp_extension() const
+{
+  return m_cpp_extension;
+}
+
+inline bool
+Config::debug() const
+{
+  return m_debug;
+}
+
+inline bool
+Config::depend_mode() const
+{
+  return m_depend_mode;
+}
+
+inline bool
+Config::direct_mode() const
+{
+  return m_direct_mode;
+}
+
+inline bool
+Config::disable() const
+{
+  return m_disable;
+}
+
+inline const std::string&
+Config::extra_files_to_hash() const
+{
+  return m_extra_files_to_hash;
+}
+
+inline bool
+Config::file_clone() const
+{
+  return m_file_clone;
+}
+
+inline bool
+Config::hard_link() const
+{
+  return m_hard_link;
+}
+
+inline bool
+Config::hash_dir() const
+{
+  return m_hash_dir;
+}
+
+inline const std::string&
+Config::ignore_headers_in_manifest() const
+{
+  return m_ignore_headers_in_manifest;
+}
+
+inline bool
+Config::keep_comments_cpp() const
+{
+  return m_keep_comments_cpp;
+}
+
+inline double
+Config::limit_multiple() const
+{
+  return m_limit_multiple;
+}
+
+inline const std::string&
+Config::log_file() const
+{
+  return m_log_file;
+}
+
+inline uint32_t
+Config::max_files() const
+{
+  return m_max_files;
+}
+
+inline uint64_t
+Config::max_size() const
+{
+  return m_max_size;
+}
+
+inline const std::string&
+Config::path() const
+{
+  return m_path;
+}
+
+inline bool
+Config::pch_external_checksum() const
+{
+  return m_pch_external_checksum;
+}
+
+inline const std::string&
+Config::prefix_command() const
+{
+  return m_prefix_command;
+}
+
+inline const std::string&
+Config::prefix_command_cpp() const
+{
+  return m_prefix_command_cpp;
+}
+
+inline bool
+Config::read_only() const
+{
+  return m_read_only;
+}
+
+inline bool
+Config::read_only_direct() const
+{
+  return m_read_only_direct;
+}
+
+inline bool
+Config::recache() const
+{
+  return m_recache;
+}
+
+inline bool
+Config::run_second_cpp() const
+{
+  return m_run_second_cpp;
+}
+
+inline uint32_t
+Config::sloppiness() const
+{
+  return m_sloppiness;
+}
+
+inline bool
+Config::stats() const
+{
+  return m_stats;
+}
+
+inline const std::string&
+Config::temporary_dir() const
+{
+  return m_temporary_dir;
+}
+
+inline uint32_t
+Config::umask() const
+{
+  return m_umask;
+}
+
+inline bool
+Config::unify() const
+{
+  return m_unify;
+}
+
+inline void
+Config::set_base_dir(const std::string& value)
+{
+  m_base_dir = value;
+}
+
+inline void
+Config::set_cache_dir(const std::string& value)
+{
+  m_cache_dir = value;
+}
+
+inline void
+Config::set_cpp_extension(const std::string& value)
+{
+  m_cpp_extension = value;
+}
+
+inline void
+Config::set_depend_mode(bool value)
+{
+  m_depend_mode = value;
+}
+
+inline void
+Config::set_direct_mode(bool value)
+{
+  m_direct_mode = value;
+}
+
+inline void
+Config::set_limit_multiple(double value)
+{
+  m_limit_multiple = value;
+}
+
+inline void
+Config::set_max_files(uint32_t value)
+{
+  m_max_files = value;
+}
+
+inline void
+Config::set_max_size(uint64_t value)
+{
+  m_max_size = value;
+}
+
+inline void
+Config::set_run_second_cpp(bool value)
+{
+  m_run_second_cpp = value;
+}
+
+inline void
+Config::set_unify(bool value)
+{
+  m_unify = value;
+}
index 74207601124ca33d515e6445e443741559f26ab6..2063492298deff5b1fd462cf118641f0cfd13797 100644 (file)
 
 #include "ccache.hpp"
 
+#include "Error.hpp"
 #include "compopt.hpp"
+#include "util.hpp"
+
+#include <fmt/core.h>
+#include <limits>
+
 #ifdef HAVE_GETOPT_LONG
 #  include <getopt.h>
 #else
 #include "third_party/hashtable.h"
 #include "third_party/hashtable_itr.h"
 
-#define STRINGIFY(x) #x
-#define TO_STRING(x) STRINGIFY(x)
-
 // Global variables used by other compilation units.
-extern struct conf* conf;
 extern char* primary_config_path;
 extern char* secondary_config_path;
 extern char* current_working_dir;
@@ -111,9 +113,6 @@ static const char USAGE_TEXT[] =
   "\n"
   "See also <https://ccache.dev>.\n";
 
-// Global configuration data.
-struct conf* conf = NULL;
-
 // Where to write configuration changes.
 char* primary_config_path = NULL;
 
@@ -288,7 +287,7 @@ static pid_t compiler_pid = 0;
 static const char HASH_PREFIX[] = "3";
 
 static void
-add_prefix(struct args* args, char* prefix_command)
+add_prefix(struct args* args, const char* prefix_command)
 {
   if (str_eq(prefix_command, "")) {
     return;
@@ -327,7 +326,7 @@ failed(void)
   assert(orig_args);
 
   args_strip(orig_args, "--ccache-");
-  add_prefix(orig_args, conf->prefix_command);
+  add_prefix(orig_args, g_config.prefix_command().c_str());
 
   cc_log("Failed; falling back to running the real compiler");
   cc_log_argv("Executing ", orig_args->argv);
@@ -339,13 +338,13 @@ failed(void)
 static const char*
 temp_dir()
 {
-  static char* path = NULL;
+  static const char* path = NULL;
   if (path) {
     return path; // Memoize
   }
-  path = conf->temporary_dir;
+  path = g_config.temporary_dir().c_str();
   if (str_eq(path, "")) {
-    path = format("%s/tmp", conf->cache_dir);
+    path = format("%s/tmp", g_config.cache_dir().c_str());
   }
   return path;
 }
@@ -469,12 +468,13 @@ clean_up_internal_tempdir(void)
 {
   time_t now = time(NULL);
   struct stat st;
-  if (x_stat(conf->cache_dir, &st) != 0 || st.st_mtime + 3600 >= now) {
+  if (x_stat(g_config.cache_dir().c_str(), &st) != 0
+      || st.st_mtime + 3600 >= now) {
     // No cleanup needed.
     return;
   }
 
-  update_mtime(conf->cache_dir);
+  update_mtime(g_config.cache_dir().c_str());
 
   DIR* dir = opendir(temp_dir());
   if (!dir) {
@@ -506,7 +506,7 @@ fclose_exitfn(void* context)
 static void
 dump_debug_log_buffer_exitfn(void* context)
 {
-  if (!conf->debug) {
+  if (!g_config.debug()) {
     return;
   }
 
@@ -522,7 +522,7 @@ init_hash_debug(struct hash* hash,
                 const char* section_name,
                 FILE* debug_text_file)
 {
-  if (!conf->debug) {
+  if (!g_config.debug()) {
     return;
   }
 
@@ -596,7 +596,7 @@ remember_include_file(char* path,
     goto out;
   }
 
-  if (system && (conf->sloppiness & SLOPPY_SYSTEM_HEADERS)) {
+  if (system && (g_config.sloppiness() & SLOPPY_SYSTEM_HEADERS)) {
     // Don't remember this system header.
     goto out;
   }
@@ -658,14 +658,14 @@ remember_include_file(char* path,
   // The comparison using >= is intentional, due to a possible race between
   // starting compilation and writing the include file. See also the notes
   // under "Performance" in doc/MANUAL.adoc.
-  if (!(conf->sloppiness & SLOPPY_INCLUDE_FILE_MTIME)
+  if (!(g_config.sloppiness() & SLOPPY_INCLUDE_FILE_MTIME)
       && st.st_mtime >= time_of_compilation) {
     cc_log("Include file %s too new", path);
     goto failure;
   }
 
   // The same >= logic as above applies to the change time of the file.
-  if (!(conf->sloppiness & SLOPPY_INCLUDE_FILE_CTIME)
+  if (!(g_config.sloppiness() & SLOPPY_INCLUDE_FILE_CTIME)
       && st.st_ctime >= time_of_compilation) {
     cc_log("Include file %s ctime too new", path);
     goto failure;
@@ -680,7 +680,7 @@ remember_include_file(char* path,
       cc_log("Detected use of precompiled header: %s", path);
     }
     bool using_pch_sum = false;
-    if (conf->pch_external_checksum) {
+    if (g_config.pch_external_checksum()) {
       // hash pch.sum instead of pch when it exists
       // to prevent hashing a very large .pch file every time
       char* pch_sum_path = format("%s.sum", path);
@@ -703,7 +703,7 @@ remember_include_file(char* path,
     hash_string(cpp_hash, pch_digest);
   }
 
-  if (conf->direct_mode) {
+  if (g_config.direct_mode()) {
     if (!is_pch) { // else: the file has already been hashed.
       char* source = NULL;
       size_t size;
@@ -716,7 +716,7 @@ remember_include_file(char* path,
         size = 0;
       }
 
-      int result = hash_source_code_string(conf, fhash, source, size, path);
+      int result = hash_source_code_string(g_config, fhash, source, size, path);
       free(source);
       if (result & HASH_SOURCE_CODE_ERROR
           || result & HASH_SOURCE_CODE_FOUND_TIME) {
@@ -740,9 +740,9 @@ remember_include_file(char* path,
   goto out;
 
 failure:
-  if (conf->direct_mode) {
+  if (g_config.direct_mode()) {
     cc_log("Disabling direct mode");
-    conf->direct_mode = false;
+    g_config.set_direct_mode(false);
   }
   // Fall through.
 out:
@@ -765,7 +765,8 @@ print_included_files(FILE* fp)
 static char*
 make_relative_path(char* path)
 {
-  if (str_eq(conf->base_dir, "") || !str_startswith(path, conf->base_dir)) {
+  if (g_config.base_dir().empty()
+      || !str_startswith(path, g_config.base_dir().c_str())) {
     return path;
   }
 
@@ -850,9 +851,9 @@ process_preprocessed_file(struct hash* hash, const char* path, bool pump)
 
   ignore_headers = NULL;
   ignore_headers_len = 0;
-  if (!str_eq(conf->ignore_headers_in_manifest, "")) {
+  if (!g_config.ignore_headers_in_manifest().empty()) {
     char *header, *p, *q, *saveptr = NULL;
-    p = x_strdup(conf->ignore_headers_in_manifest);
+    p = x_strdup(g_config.ignore_headers_in_manifest().c_str());
     q = p;
     while ((header = strtok_r(q, PATH_DELIM, &saveptr))) {
       ignore_headers = static_cast<char**>(
@@ -966,7 +967,7 @@ process_preprocessed_file(struct hash* hash, const char* path, bool pump)
       inc_path = make_relative_path(inc_path);
 
       bool should_hash_inc_path = true;
-      if (!conf->hash_dir) {
+      if (!g_config.hash_dir()) {
         if (str_startswith(inc_path, cwd) && str_endswith(inc_path, "//")) {
           // When compiling with -g or similar, GCC adds the absolute path to
           // CWD like this:
@@ -1038,7 +1039,7 @@ process_preprocessed_file(struct hash* hash, const char* path, bool pump)
 static void
 use_relative_paths_in_depfile(const char* depfile)
 {
-  if (str_eq(conf->base_dir, "")) {
+  if (g_config.base_dir().empty()) {
     cc_log("Base dir not set, skip using relative paths");
     return; // nothing to do
   }
@@ -1066,7 +1067,8 @@ use_relative_paths_in_depfile(const char* depfile)
     char* token = strtok_r(buf, " \t", &saveptr);
     while (token) {
       char* relpath;
-      if (is_absolute_path(token) && str_startswith(token, conf->base_dir)) {
+      if (is_absolute_path(token)
+          && str_startswith(token, g_config.base_dir().c_str())) {
         relpath = make_relative_path(x_strdup(token));
         result = true;
       } else {
@@ -1187,8 +1189,8 @@ send_cached_stderr(const char* path_stderr)
 static void
 update_manifest_file(void)
 {
-  if (!conf->direct_mode || !included_files || conf->read_only
-      || conf->read_only_direct) {
+  if (!g_config.direct_mode() || !included_files || g_config.read_only()
+      || g_config.read_only_direct()) {
     return;
   }
 
@@ -1218,7 +1220,8 @@ update_cached_result_globals(struct digest* result_name)
   digest_as_string(result_name, result_name_string);
   cached_result_name = result_name;
   cached_result_path = get_path_in_cache(result_name_string, ".result");
-  stats_file = format("%s/%c/stats", conf->cache_dir, result_name_string[0]);
+  stats_file =
+    format("%s/%c/stats", g_config.cache_dir().c_str(), result_name_string[0]);
 }
 
 // Run the real compiler and put the result in cache.
@@ -1228,7 +1231,7 @@ to_cache(struct args* args, struct hash* depend_mode_hash)
   args_add(args, "-o");
   args_add(args, output_obj);
 
-  if (conf->hard_link) {
+  if (g_config.hard_link()) {
     // Workaround for Clang bug where it overwrites an existing object file
     // when it's compiling an assembler file, see
     // <https://bugs.llvm.org/show_bug.cgi?id=39782>.
@@ -1247,7 +1250,7 @@ to_cache(struct args* args, struct hash* depend_mode_hash)
   x_unsetenv("DEPENDENCIES_OUTPUT");
   x_unsetenv("SUNPRO_DEPENDENCIES");
 
-  if (conf->run_second_cpp) {
+  if (g_config.run_second_cpp()) {
     args_add(args, input_file);
   } else {
     args_add(args, i_tmpfile);
@@ -1271,7 +1274,7 @@ to_cache(struct args* args, struct hash* depend_mode_hash)
   char* tmp_stderr;
   int tmp_stderr_fd;
   int status;
-  if (!conf->depend_mode) {
+  if (!g_config.depend_mode()) {
     tmp_stdout = format("%s/tmp.stdout", temp_dir());
     tmp_stdout_fd = create_tmp_fd(&tmp_stdout);
     tmp_stderr = format("%s/tmp.stderr", temp_dir());
@@ -1290,7 +1293,7 @@ to_cache(struct args* args, struct hash* depend_mode_hash)
     assert(orig_args);
     struct args* depend_mode_args = args_copy(orig_args);
     args_strip(depend_mode_args, "--ccache-");
-    add_prefix(depend_mode_args, conf->prefix_command);
+    add_prefix(depend_mode_args, g_config.prefix_command().c_str());
 
     time_of_compilation = time(NULL);
     status = execute(
@@ -1377,7 +1380,7 @@ to_cache(struct args* args, struct hash* depend_mode_hash)
     failed();
   }
 
-  if (conf->depend_mode) {
+  if (g_config.depend_mode()) {
     struct digest* result_name =
       result_name_from_depfile(output_dep, depend_mode_hash);
     if (!result_name) {
@@ -1467,7 +1470,7 @@ to_cache(struct args* args, struct hash* depend_mode_hash)
     // Remove any CACHEDIR.TAG on the cache_dir level where it was located in
     // previous ccache versions.
     if (getpid() % 1000 == 0) {
-      char* path = format("%s/CACHEDIR.TAG", conf->cache_dir);
+      char* path = format("%s/CACHEDIR.TAG", g_config.cache_dir().c_str());
       x_unlink(path);
       free(path);
     }
@@ -1523,12 +1526,12 @@ get_result_name_from_cpp(struct args* args, struct hash* hash)
 
     int args_added = 2;
     args_add(args, "-E");
-    if (conf->keep_comments_cpp) {
+    if (g_config.keep_comments_cpp()) {
       args_add(args, "-C");
       args_added = 3;
     }
     args_add(args, input_file);
-    add_prefix(args, conf->prefix_command_cpp);
+    add_prefix(args, g_config.prefix_command_cpp().c_str());
     cc_log("Running preprocessor");
     MTR_BEGIN("execute", "preprocessor");
     status = execute(args->argv, path_stdout_fd, path_stderr_fd, &compiler_pid);
@@ -1542,7 +1545,7 @@ get_result_name_from_cpp(struct args* args, struct hash* hash)
     failed();
   }
 
-  if (conf->unify) {
+  if (g_config.unify()) {
     // When we are doing the unifying tricks we need to include the input file
     // name in the hash to get the warnings right.
     hash_delimiter(hash, "unifyfilename");
@@ -1575,12 +1578,12 @@ get_result_name_from_cpp(struct args* args, struct hash* hash)
   } else {
     // i_tmpfile needs the proper cpp_extension for the compiler to do its
     // thing correctly
-    i_tmpfile = format("%s.%s", path_stdout, conf->cpp_extension);
+    i_tmpfile = format("%s.%s", path_stdout, g_config.cpp_extension().c_str());
     x_rename(path_stdout, i_tmpfile);
     add_pending_tmp_file(i_tmpfile);
   }
 
-  if (conf->run_second_cpp) {
+  if (g_config.run_second_cpp()) {
     free(path_stderr);
   } else {
     // If we are using the CPP trick, we need to remember this stderr data and
@@ -1603,23 +1606,24 @@ hash_compiler(struct hash* hash,
               const char* path,
               bool allow_command)
 {
-  if (str_eq(conf->compiler_check, "none")) {
+  if (g_config.compiler_check() == "none") {
     // Do nothing.
-  } else if (str_eq(conf->compiler_check, "mtime")) {
+  } else if (g_config.compiler_check() == "mtime") {
     hash_delimiter(hash, "cc_mtime");
     hash_int(hash, st->st_size);
     hash_int(hash, st->st_mtime);
-  } else if (str_startswith(conf->compiler_check, "string:")) {
+  } else if (util::starts_with(g_config.compiler_check(), "string:")) {
     hash_delimiter(hash, "cc_hash");
-    hash_string(hash, conf->compiler_check + strlen("string:"));
-  } else if (str_eq(conf->compiler_check, "content") || !allow_command) {
+    hash_string(hash, g_config.compiler_check().c_str() + strlen("string:"));
+  } else if (g_config.compiler_check() == "content" || !allow_command) {
     hash_delimiter(hash, "cc_content");
     hash_file(hash, path);
   } else { // command string
-    bool ok =
-      hash_multicommand_output(hash, conf->compiler_check, orig_args->argv[0]);
+    bool ok = hash_multicommand_output(
+      hash, g_config.compiler_check().c_str(), orig_args->argv[0]);
     if (!ok) {
-      fatal("Failure running compiler check command: %s", conf->compiler_check);
+      fatal("Failure running compiler check command: %s",
+            g_config.compiler_check().c_str());
     }
   }
 }
@@ -1686,7 +1690,7 @@ hash_common_info(struct args* args, struct hash* hash)
   // We have to hash the extension, as a .i file isn't treated the same by the
   // compiler as a .ii file.
   hash_delimiter(hash, "ext");
-  hash_string(hash, conf->cpp_extension);
+  hash_string(hash, g_config.cpp_extension().c_str());
 
 #ifdef _WIN32
   const char* ext = strrchr(args->argv[0], '.');
@@ -1714,7 +1718,7 @@ hash_common_info(struct args* args, struct hash* hash)
   hash_string(hash, base);
   free(base);
 
-  if (!(conf->sloppiness & SLOPPY_LOCALE)) {
+  if (!(g_config.sloppiness() & SLOPPY_LOCALE)) {
     // Hash environment variables that may affect localization of compiler
     // warning messages.
     const char* envvars[] = {"LANG", "LC_ALL", "LC_CTYPE", "LC_MESSAGES", NULL};
@@ -1728,7 +1732,7 @@ hash_common_info(struct args* args, struct hash* hash)
   }
 
   // Possibly hash the current working directory.
-  if (generating_debuginfo && conf->hash_dir) {
+  if (generating_debuginfo && g_config.hash_dir()) {
     char* cwd = gnu_getcwd();
     for (size_t i = 0; i < debug_prefix_maps_len; i++) {
       char* map = debug_prefix_maps[i];
@@ -1798,8 +1802,8 @@ hash_common_info(struct args* args, struct hash* hash)
     }
   }
 
-  if (!str_eq(conf->extra_files_to_hash, "")) {
-    char* p = x_strdup(conf->extra_files_to_hash);
+  if (!g_config.extra_files_to_hash().empty()) {
+    char* p = x_strdup(g_config.extra_files_to_hash().c_str());
     char* q = p;
     char* path;
     char* saveptr = NULL;
@@ -2049,7 +2053,7 @@ calculate_result_name(struct args* args, struct hash* hash, int direct_mode)
       }
     }
 
-    if (!(conf->sloppiness & SLOPPY_FILE_MACRO)) {
+    if (!(g_config.sloppiness() & SLOPPY_FILE_MACRO)) {
       // The source code file or an include file may contain __FILE__, so make
       // sure that the hash is unique for the file name.
       hash_delimiter(hash, "inputfile");
@@ -2057,25 +2061,25 @@ calculate_result_name(struct args* args, struct hash* hash, int direct_mode)
     }
 
     hash_delimiter(hash, "sourcecode");
-    int result = hash_source_code_file(conf, hash, input_file);
+    int result = hash_source_code_file(g_config, hash, input_file);
     if (result & HASH_SOURCE_CODE_ERROR) {
       failed();
     }
     if (result & HASH_SOURCE_CODE_FOUND_TIME) {
       cc_log("Disabling direct mode");
-      conf->direct_mode = false;
+      g_config.set_direct_mode(false);
       return NULL;
     }
 
     char manifest_name_string[DIGEST_STRING_BUFFER_SIZE];
     hash_result_as_string(hash, manifest_name_string);
     manifest_path = get_path_in_cache(manifest_name_string, ".manifest");
-    manifest_stats_file =
-      format("%s/%c/stats", conf->cache_dir, manifest_name_string[0]);
+    manifest_stats_file = format(
+      "%s/%c/stats", g_config.cache_dir().c_str(), manifest_name_string[0]);
 
     cc_log("Looking for result name in %s", manifest_path);
     MTR_BEGIN("manifest", "manifest_get");
-    result_name = manifest_get(conf, manifest_path);
+    result_name = manifest_get(g_config, manifest_path);
     MTR_END("manifest", "manifest_get");
     if (result_name) {
       cc_log("Got result name from manifest");
@@ -2117,7 +2121,7 @@ static void
 from_cache(enum fromcache_call_mode mode, bool put_result_in_manifest)
 {
   // The user might be disabling cache hits.
-  if (conf->recache) {
+  if (g_config.recache()) {
     return;
   }
 
@@ -2225,8 +2229,8 @@ find_compiler(char** argv)
   }
 
   // Support user override of the compiler.
-  if (!str_eq(conf->compiler, "")) {
-    base = conf->compiler;
+  if (!g_config.compiler().empty()) {
+    base = x_strdup(g_config.compiler().c_str());
   }
 
   char* compiler = find_executable(base, MYNAME);
@@ -2465,9 +2469,9 @@ cc_process_args(struct args* args,
     }
 
     // These are too hard in direct mode.
-    if (conf->direct_mode && compopt_too_hard_for_direct_mode(argv[i])) {
+    if (g_config.direct_mode() && compopt_too_hard_for_direct_mode(argv[i])) {
       cc_log("Unsupported compiler option for direct mode: %s", argv[i]);
-      conf->direct_mode = false;
+      g_config.set_direct_mode(false);
     }
 
     // -Xarch_* options are too hard.
@@ -2492,7 +2496,7 @@ cc_process_args(struct args* args,
       arch_args[arch_args_size] = x_strdup(argv[i]); // It will leak.
       ++arch_args_size;
       if (arch_args_size == 2) {
-        conf->run_second_cpp = true;
+        g_config.set_run_second_cpp(true);
       }
       continue;
     }
@@ -2798,11 +2802,11 @@ cc_process_args(struct args* args,
         // TODO: Make argument to MF/MQ/MT relative.
         args_add(dep_args, argv[i]);
         continue;
-      } else if (conf->direct_mode) {
+      } else if (g_config.direct_mode()) {
         // -Wp, can be used to pass too hard options to the preprocessor.
         // Hence, disable direct mode.
         cc_log("Unsupported compiler option for direct mode: %s", argv[i]);
-        conf->direct_mode = false;
+        g_config.set_direct_mode(false);
       }
 
       // Any other -Wp,* arguments are only relevant for the preprocessor.
@@ -2919,7 +2923,7 @@ cc_process_args(struct args* args,
       continue;
     }
 
-    if (conf->sloppiness & SLOPPY_CLANG_INDEX_STORE
+    if (g_config.sloppiness() & SLOPPY_CLANG_INDEX_STORE
         && str_eq(argv[i], "-index-store-path")) {
       // Xcode 9 or later calls Clang with this option. The given path includes
       // a UUID that might lead to cache misses, especially when cache is
@@ -3067,14 +3071,14 @@ cc_process_args(struct args* args,
     }
   } // for
 
-  if (generating_debuginfo && conf->unify) {
+  if (generating_debuginfo && g_config.unify()) {
     cc_log("Generating debug info; disabling unify mode");
-    conf->unify = false;
+    g_config.set_unify(false);
   }
 
-  if (generating_debuginfo_level_3 && !conf->run_second_cpp) {
+  if (generating_debuginfo_level_3 && !g_config.run_second_cpp()) {
     cc_log("Generating debug info level 3; not compiling preprocessed code");
-    conf->run_second_cpp = true;
+    g_config.set_run_second_cpp(true);
   }
 
   // See <http://gcc.gnu.org/onlinedocs/cpp/Environment-Variables.html>.
@@ -3137,7 +3141,7 @@ cc_process_args(struct args* args,
 
   if (found_pch || found_fpch_preprocess) {
     using_precompiled_header = true;
-    if (!(conf->sloppiness & SLOPPY_TIME_MACROS)) {
+    if (!(g_config.sloppiness() & SLOPPY_TIME_MACROS)) {
       cc_log(
         "You have to specify \"time_macros\" sloppiness when using"
         " precompiled headers to get direct hits");
@@ -3168,7 +3172,7 @@ cc_process_args(struct args* args,
     actual_language && strstr(actual_language, "-header");
 
   if (output_is_precompiled_header
-      && !(conf->sloppiness & SLOPPY_PCH_DEFINES)) {
+      && !(g_config.sloppiness() & SLOPPY_PCH_DEFINES)) {
     cc_log(
       "You have to specify \"pch_defines,time_macros\" sloppiness when"
       " creating precompiled headers");
@@ -3201,23 +3205,22 @@ cc_process_args(struct args* args,
     goto out;
   }
 
-  if (!conf->run_second_cpp && str_eq(actual_language, "cu")) {
+  if (!g_config.run_second_cpp() && str_eq(actual_language, "cu")) {
     cc_log("Using CUDA compiler; not compiling preprocessed code");
-    conf->run_second_cpp = true;
+    g_config.set_run_second_cpp(true);
   }
 
   direct_i_file = language_is_preprocessed(actual_language);
 
-  if (output_is_precompiled_header && !conf->run_second_cpp) {
+  if (output_is_precompiled_header && !g_config.run_second_cpp()) {
     // It doesn't work to create the .gch from preprocessed source.
     cc_log("Creating precompiled header; not compiling preprocessed code");
-    conf->run_second_cpp = true;
+    g_config.set_run_second_cpp(true);
   }
 
-  if (str_eq(conf->cpp_extension, "")) {
+  if (g_config.cpp_extension().empty()) {
     const char* p_language = p_language_for_language(actual_language);
-    free(conf->cpp_extension);
-    conf->cpp_extension = x_strdup(extension_for_language(p_language) + 1);
+    g_config.set_cpp_extension(extension_for_language(p_language) + 1);
   }
 
   // Don't try to second guess the compilers heuristics for stdout handling.
@@ -3351,7 +3354,7 @@ cc_process_args(struct args* args,
   *compiler_args = args_copy(common_args);
   args_extend(*compiler_args, compiler_only_args);
 
-  if (conf->run_second_cpp) {
+  if (g_config.run_second_cpp()) {
     args_extend(*compiler_args, cpp_args);
   } else if (found_directives_only || found_rewrite_includes) {
     // Need to pass the macros and any other preprocessor directives again.
@@ -3414,7 +3417,7 @@ create_initial_config_file(const char* path)
 
   unsigned max_files;
   uint64_t max_size;
-  char* stats_dir = format("%s/0", conf->cache_dir);
+  char* stats_dir = format("%s/0", g_config.cache_dir().c_str());
   struct stat st;
   if (stat(stats_dir, &st) == 0) {
     stats_get_obsolete_limits(stats_dir, &max_files, &max_size);
@@ -3423,7 +3426,7 @@ create_initial_config_file(const char* path)
     max_size *= 16;
   } else {
     max_files = 0;
-    max_size = conf->max_size;
+    max_size = g_config.max_size();
   }
   free(stats_dir);
 
@@ -3433,13 +3436,13 @@ create_initial_config_file(const char* path)
   }
   if (max_files != 0) {
     fprintf(f, "max_files = %u\n", max_files);
-    conf->max_files = max_files;
+    g_config.set_max_files(max_files);
   }
   if (max_size != 0) {
     char* size = format_parsable_size_with_suffix(max_size);
     fprintf(f, "max_size = %s\n", size);
     free(size);
-    conf->max_size = max_size;
+    g_config.set_max_size(max_size);
   }
   fclose(f);
 }
@@ -3510,59 +3513,39 @@ initialize(void)
 #endif
   }
 
-  conf_free(conf);
-  MTR_BEGIN("config", "conf_create");
-  conf = conf_create();
-  MTR_END("config", "conf_create");
-
-  char* errmsg;
   char* p = getenv("CCACHE_CONFIGPATH");
   if (p) {
     primary_config_path = x_strdup(p);
   } else {
     secondary_config_path = format("%s/ccache.conf", TO_STRING(SYSCONFDIR));
     MTR_BEGIN("config", "conf_read_secondary");
-    if (!conf_read(conf, secondary_config_path, &errmsg)) {
-      if (errno == 0) {
-        // We could read the file but it contained errors.
-        fatal("%s", errmsg);
-      }
-      // A missing config file in SYSCONFDIR is OK.
-      free(errmsg);
-    }
+    // A missing config file in SYSCONFDIR is OK so don't check return value.
+    g_config.update_from_file(secondary_config_path);
     MTR_END("config", "conf_read_secondary");
 
-    if (str_eq(conf->cache_dir, "")) {
+    if (g_config.cache_dir().empty()) {
       fatal("configuration setting \"cache_dir\" must not be the empty string");
     }
     if ((p = getenv("CCACHE_DIR"))) {
-      free(conf->cache_dir);
-      conf->cache_dir = strdup(p);
+      g_config.set_cache_dir(p);
     }
-    if (str_eq(conf->cache_dir, "")) {
+    if (g_config.cache_dir().empty()) {
       fatal("CCACHE_DIR must not be the empty string");
     }
 
-    primary_config_path = format("%s/ccache.conf", conf->cache_dir);
+    primary_config_path =
+      format("%s/ccache.conf", g_config.cache_dir().c_str());
   }
 
   bool should_create_initial_config = false;
   MTR_BEGIN("config", "conf_read_primary");
-  if (!conf_read(conf, primary_config_path, &errmsg)) {
-    if (errno == 0) {
-      // We could read the file but it contained errors.
-      fatal("%s", errmsg);
-    }
-    if (!conf->disable) {
-      should_create_initial_config = true;
-    }
+  if (!g_config.update_from_file(primary_config_path) && !g_config.disable()) {
+    should_create_initial_config = true;
   }
   MTR_END("config", "conf_read_primary");
 
   MTR_BEGIN("config", "conf_update_from_environment");
-  if (!conf_update_from_environment(conf, &errmsg)) {
-    fatal("%s", errmsg);
-  }
+  g_config.update_from_environment();
   MTR_END("config", "conf_update_from_environment");
 
   if (should_create_initial_config) {
@@ -3576,8 +3559,8 @@ initialize(void)
   cc_log("=== CCACHE %s STARTED =========================================",
          CCACHE_VERSION);
 
-  if (conf->umask != UINT_MAX) {
-    umask(conf->umask);
+  if (g_config.umask() != std::numeric_limits<uint32_t>::max()) {
+    umask(g_config.umask());
   }
 
   if (enable_internal_trace) {
@@ -3594,8 +3577,9 @@ initialize(void)
 void
 cc_reset(void)
 {
-  conf_free(conf);
-  conf = NULL;
+  Config new_config;
+  std::swap(g_config, new_config);
+
   free(primary_config_path);
   primary_config_path = NULL;
   free(secondary_config_path);
@@ -3671,7 +3655,6 @@ cc_reset(void)
   stats_file = NULL;
   output_is_precompiled_header = false;
 
-  conf = conf_create();
   seen_split_dwarf = false;
 }
 
@@ -3695,10 +3678,20 @@ set_up_uncached_err(void)
 }
 
 static void
-configuration_logger(const char* descr, const char* origin, void* context)
+configuration_logger(const std::string& key,
+                     const std::string& value,
+                     const std::string& origin)
+{
+  cc_bulklog(
+    "Config: (%s) %s = %s", origin.c_str(), key.c_str(), value.c_str());
+}
+
+static void
+configuration_printer(const std::string& key,
+                      const std::string& value,
+                      const std::string& origin)
 {
-  (void)context;
-  cc_bulklog("Config: (%s) %s", origin, descr);
+  fmt::print("({}) {} = {}\n", origin, key, value);
 }
 
 static void ccache(int argc, char* argv[]) ATTR_NORETURN;
@@ -3722,16 +3715,16 @@ ccache(int argc, char* argv[])
   MTR_END("main", "find_compiler");
 
   MTR_BEGIN("main", "clean_up_internal_tempdir");
-  if (str_eq(conf->temporary_dir, "")) {
+  if (g_config.temporary_dir().empty()) {
     clean_up_internal_tempdir();
   }
   MTR_END("main", "clean_up_internal_tempdir");
 
-  if (!str_eq(conf->log_file, "") || conf->debug) {
-    conf_print_items(conf, configuration_logger, NULL);
+  if (!g_config.log_file().empty() || g_config.debug()) {
+    g_config.visit_items(configuration_logger);
   }
 
-  if (conf->disable) {
+  if (g_config.disable()) {
     cc_log("ccache is disabled");
     failed();
   }
@@ -3744,7 +3737,7 @@ ccache(int argc, char* argv[])
   cc_log("Hostname: %s", get_hostname());
   cc_log("Working directory: %s", get_current_working_dir());
 
-  conf->limit_multiple = MIN(MAX(conf->limit_multiple, 0.0), 1.0);
+  g_config.set_limit_multiple(MIN(MAX(g_config.limit_multiple(), 0.0), 1.0));
 
   MTR_BEGIN("main", "guess_compiler");
   guessed_compiler = guess_compiler(orig_args->argv[0]);
@@ -3760,11 +3753,11 @@ ccache(int argc, char* argv[])
   }
   MTR_END("main", "process_args");
 
-  if (conf->depend_mode
+  if (g_config.depend_mode()
       && (!generating_dependencies || str_eq(output_dep, "/dev/null")
-          || !conf->run_second_cpp || conf->unify)) {
+          || !g_config.run_second_cpp() || g_config.unify())) {
     cc_log("Disabling depend mode");
-    conf->depend_mode = false;
+    g_config.set_depend_mode(false);
   }
 
   cc_log("Source file: %s", input_file);
@@ -3791,7 +3784,7 @@ ccache(int argc, char* argv[])
   exitfn_add_last(dump_debug_log_buffer_exitfn, output_obj);
 
   FILE* debug_text_file = NULL;
-  if (conf->debug) {
+  if (g_config.debug()) {
     char* path = format("%s.ccache-input-text", output_obj);
     debug_text_file = fopen(path, "w");
     if (debug_text_file) {
@@ -3816,7 +3809,7 @@ ccache(int argc, char* argv[])
   bool put_result_in_manifest = false;
   struct digest* result_name = NULL;
   struct digest* result_name_from_manifest = NULL;
-  if (conf->direct_mode) {
+  if (g_config.direct_mode()) {
     cc_log("Trying direct lookup");
     MTR_BEGIN("hash", "direct_hash");
     result_name = calculate_result_name(preprocessor_args, direct_hash, 1);
@@ -3838,12 +3831,12 @@ ccache(int argc, char* argv[])
     }
   }
 
-  if (conf->read_only_direct) {
+  if (g_config.read_only_direct()) {
     cc_log("Read-only direct mode; running real compiler");
     failed();
   }
 
-  if (!conf->depend_mode) {
+  if (!g_config.depend_mode()) {
     // Find the hash using the preprocessed output. Also updates
     // included_files.
     struct hash* cpp_hash = hash_copy(common_hash);
@@ -3884,15 +3877,15 @@ ccache(int argc, char* argv[])
     from_cache(FROMCACHE_CPP_MODE, put_result_in_manifest);
   }
 
-  if (conf->read_only) {
+  if (g_config.read_only()) {
     cc_log("Read-only mode; running real compiler");
     failed();
   }
 
-  add_prefix(compiler_args, conf->prefix_command);
+  add_prefix(compiler_args, g_config.prefix_command().c_str());
 
   // In depend_mode, extend the direct hash.
-  struct hash* depend_mode_hash = conf->depend_mode ? direct_hash : NULL;
+  struct hash* depend_mode_hash = g_config.depend_mode() ? direct_hash : NULL;
 
   // Run real compiler, sending output to cache.
   MTR_BEGIN("cache", "to_cache");
@@ -3902,14 +3895,6 @@ ccache(int argc, char* argv[])
   x_exit(0);
 }
 
-static void
-configuration_printer(const char* descr, const char* origin, void* context)
-{
-  assert(context);
-  auto f = static_cast<FILE*>(context);
-  fprintf(f, "(%s) %s\n", origin, descr);
-}
-
 // The main program when not doing a compile.
 static int
 ccache_main_options(int argc, char* argv[])
@@ -3977,13 +3962,13 @@ ccache_main_options(int argc, char* argv[])
 
     case 'c': // --cleanup
       initialize();
-      clean_up_all(conf);
+      clean_up_all(g_config);
       printf("Cleaned cache\n");
       break;
 
     case 'C': // --clear
       initialize();
-      wipe_all(conf);
+      wipe_all(g_config);
       printf("Cleared cache\n");
       break;
 
@@ -3992,55 +3977,40 @@ ccache_main_options(int argc, char* argv[])
       x_exit(0);
 
     case 'k': // --get-config
-    {
       initialize();
-      char* errmsg;
-      if (!conf_print_value(conf, optarg, stdout, &errmsg)) {
-        fatal("%s", errmsg);
-      }
-    } break;
+      fmt::print("{}\n", g_config.get_string_value(optarg));
+      break;
 
-    case 'F': // --max-files
-    {
+    case 'F': { // --max-files
       initialize();
-      char* errmsg;
-      if (conf_set_value_in_file(
-            primary_config_path, "max_files", optarg, &errmsg)) {
-        unsigned files = atoi(optarg);
-        if (files == 0) {
-          printf("Unset cache file limit\n");
-        } else {
-          printf("Set cache file limit to %u\n", files);
-        }
+      g_config.set_value_in_file(primary_config_path, "max_files", optarg);
+      unsigned files = atoi(optarg);
+      if (files == 0) {
+        printf("Unset cache file limit\n");
       } else {
-        fatal("could not set cache file limit: %s", errmsg);
+        printf("Set cache file limit to %u\n", files);
       }
-    } break;
+      break;
+    }
 
-    case 'M': // --max-size
-    {
+    case 'M': { // --max-size
       initialize();
       uint64_t size;
       if (!parse_size_with_suffix(optarg, &size)) {
         fatal("invalid size: %s", optarg);
       }
-      char* errmsg;
-      if (conf_set_value_in_file(
-            primary_config_path, "max_size", optarg, &errmsg)) {
-        if (size == 0) {
-          printf("Unset cache size limit\n");
-        } else {
-          char* s = format_human_readable_size(size);
-          printf("Set cache size limit to %s\n", s);
-          free(s);
-        }
+      g_config.set_value_in_file(primary_config_path, "max_size", optarg);
+      if (size == 0) {
+        printf("Unset cache size limit\n");
       } else {
-        fatal("could not set cache size limit: %s", errmsg);
+        char* s = format_human_readable_size(size);
+        printf("Set cache size limit to %s\n", s);
+        free(s);
       }
-    } break;
+      break;
+    }
 
-    case 'o': // --set-config
-    {
+    case 'o': { // --set-config
       initialize();
       char* p = strchr(optarg, '=');
       if (!p) {
@@ -4048,16 +4018,14 @@ ccache_main_options(int argc, char* argv[])
       }
       char* key = x_strndup(optarg, p - optarg);
       char* value = p + 1;
-      char* errmsg;
-      if (!conf_set_value_in_file(primary_config_path, key, value, &errmsg)) {
-        fatal("%s", errmsg);
-      }
+      g_config.set_value_in_file(primary_config_path, key, value);
       free(key);
-    } break;
+      break;
+    }
 
     case 'p': // --show-config
       initialize();
-      conf_print_items(conf, configuration_printer, stdout);
+      g_config.visit_items(configuration_printer);
       break;
 
     case 's': // --show-stats
@@ -4071,7 +4039,7 @@ ccache_main_options(int argc, char* argv[])
 
     case 'x': // --show-compression
       initialize();
-      compress_stats(conf);
+      compress_stats(g_config);
       break;
 
     case 'z': // --zero-stats
@@ -4094,20 +4062,25 @@ int ccache_main(int argc, char* argv[]);
 int
 ccache_main(int argc, char* argv[])
 {
-  // Check if we are being invoked as "ccache".
-  char* program_name = x_basename(argv[0]);
-  if (same_executable_name(program_name, MYNAME)) {
-    if (argc < 2) {
-      fputs(USAGE_TEXT, stderr);
-      x_exit(1);
-    }
-    // If the first argument isn't an option, then assume we are being passed a
-    // compiler name and options.
-    if (argv[1][0] == '-') {
-      return ccache_main_options(argc, argv);
+  try {
+    // Check if we are being invoked as "ccache".
+    char* program_name = x_basename(argv[0]);
+    if (same_executable_name(program_name, MYNAME)) {
+      if (argc < 2) {
+        fputs(USAGE_TEXT, stderr);
+        x_exit(1);
+      }
+      // If the first argument isn't an option, then assume we are being passed
+      // a compiler name and options.
+      if (argv[1][0] == '-') {
+        return ccache_main_options(argc, argv);
+      }
     }
-  }
-  free(program_name);
+    free(program_name);
 
-  ccache(argc, argv);
+    ccache(argc, argv);
+  } catch (const Error& e) {
+    fmt::print("ccache: error: {}\n", e.what());
+    return 1;
+  }
 }
index e8be9244dbab3fadf160b0cf2cc6d8f0168b8b30..4052e6f93b5c28cf1671e1057ba7a86dd8d78633 100644 (file)
@@ -21,7 +21,6 @@
 
 #include "system.hpp"
 
-#include "conf.hpp"
 #include "counters.hpp"
 
 #include "third_party/minitrace.h"
@@ -39,6 +38,9 @@
 #  define MYNAME "ccache"
 #endif
 
+#define STRINGIFY(x) #x
+#define TO_STRING(x) STRINGIFY(x)
+
 extern const char CCACHE_VERSION[];
 
 // Statistics fields in storage order.
@@ -119,6 +121,8 @@ extern enum guessed_compiler guessed_compiler;
 // Buffer size for I/O operations. Should be a multiple of 4 KiB.
 #define READ_BUFFER_SIZE 65536
 
+class Config;
+
 // ----------------------------------------------------------------------------
 // args.c
 
@@ -250,14 +254,14 @@ void exitfn_call(void);
 // ----------------------------------------------------------------------------
 // cleanup.c
 
-void clean_up_dir(struct conf* conf, const char* dir, double limit_multiple);
-void clean_up_all(struct conf* conf);
-void wipe_all(struct conf* conf);
+void clean_up_dir(const Config& config, const char* dir, double limit_multiple);
+void clean_up_all(const Config& config);
+void wipe_all(const Config& config);
 
 // ----------------------------------------------------------------------------
 // compress.c
 
-void compress_stats(struct conf* conf);
+void compress_stats(const Config& config);
 
 // ----------------------------------------------------------------------------
 // execute.c
index 4ef6665ab9c6dbb2b76f6b85e6d2ba5ea9fa271f..955a937bea024e26d6ebd16cebb3d62b2a97443c 100644 (file)
@@ -17,6 +17,7 @@
 // this program; if not, write to the Free Software Foundation, Inc., 51
 // Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
+#include "Config.hpp"
 #include "ccache.hpp"
 
 #include <math.h>
@@ -161,16 +162,16 @@ sort_and_clean(void)
 
 // Clean up one cache subdirectory.
 void
-clean_up_dir(struct conf* conf, const char* dir, double limit_multiple)
+clean_up_dir(const Config& config, const char* dir, double limit_multiple)
 {
   cc_log("Cleaning up cache directory %s", dir);
 
   // When "max files" or "max cache size" is reached, one of the 16 cache
   // subdirectories is cleaned up. When doing so, files are deleted (in LRU
   // order) until the levels are below limit_multiple.
-  double cache_size_float = round(conf->max_size * limit_multiple / 16);
+  double cache_size_float = round(config.max_size() * limit_multiple / 16);
   cache_size_threshold = (uint64_t)cache_size_float;
-  double files_in_cache_float = round(conf->max_files * limit_multiple / 16);
+  double files_in_cache_float = round(config.max_files() * limit_multiple / 16);
   files_in_cache_threshold = (size_t)files_in_cache_float;
 
   num_files = 0;
@@ -215,11 +216,11 @@ clean_up_dir(struct conf* conf, const char* dir, double limit_multiple)
 
 // Clean up all cache subdirectories.
 void
-clean_up_all(struct conf* conf)
+clean_up_all(const Config& config)
 {
   for (int i = 0; i <= 0xF; i++) {
-    char* dname = format("%s/%1x", conf->cache_dir, i);
-    clean_up_dir(conf, dname, 1.0);
+    char* dname = format("%s/%1x", config.cache_dir().c_str(), i);
+    clean_up_dir(config, dname, 1.0);
     free(dname);
   }
 }
@@ -264,14 +265,14 @@ wipe_dir(const char* dir)
 
 // Wipe all cached files in all subdirectories.
 void
-wipe_all(struct conf* conf)
+wipe_all(const Config& config)
 {
   for (int i = 0; i <= 0xF; i++) {
-    char* dname = format("%s/%1x", conf->cache_dir, i);
+    char* dname = format("%s/%1x", config.cache_dir().c_str(), i);
     wipe_dir(dname);
     free(dname);
   }
 
   // Fix the counters.
-  clean_up_all(conf);
+  clean_up_all(config);
 }
index d7656fc9ddb0668d27f8137bb859470917414102..bd066608678f7af5ae04575c92db3879792f63f0 100644 (file)
@@ -105,7 +105,7 @@ measure_fn(const char* fname, struct stat* st)
 
 // Process up all cache subdirectories.
 void
-compress_stats(struct conf* conf)
+compress_stats(const Config& config)
 {
   on_disk_size = 0;
   compr_size = 0;
@@ -113,7 +113,7 @@ compress_stats(struct conf* conf)
   incompr_size = 0;
 
   for (int i = 0; i <= 0xF; i++) {
-    char* dname = format("%s/%1x", conf->cache_dir, i);
+    char* dname = format("%s/%1x", config.cache_dir().c_str(), i);
     traverse(dname, measure_fn);
     free(dname);
   }
index 766c45cf3eefdc12ccd0b598a40bc0b18bf4b149..1fb682a2158a926c48137efc8f157003a37ea581 100644 (file)
 
 #include "compression.hpp"
 
-#include "conf.hpp"
-
-extern struct conf* conf;
+#include "Config.hpp"
 
 int8_t
 compression_level_from_config(void)
 {
-  return conf->compression ? conf->compression_level : 0;
+  return g_config.compression() ? g_config.compression_level() : 0;
 }
 
 enum compression_type
 compression_type_from_config(void)
 {
-  return conf->compression ? COMPR_TYPE_ZSTD : COMPR_TYPE_NONE;
+  return g_config.compression() ? COMPR_TYPE_ZSTD : COMPR_TYPE_NONE;
 }
 
 const char*
diff --git a/src/conf.cpp b/src/conf.cpp
deleted file mode 100644 (file)
index bf0a454..0000000
+++ /dev/null
@@ -1,459 +0,0 @@
-// Copyright (C) 2011-2019 Joel Rosdahl and other contributors
-//
-// See doc/AUTHORS.adoc for a complete list of contributors.
-//
-// 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.hpp"
-
-#include "ccache.hpp"
-#include "confitems.hpp"
-#include "envtoconfitems.hpp"
-
-enum handle_conf_result {
-  HANDLE_CONF_OK,
-  HANDLE_CONF_UNKNOWN,
-  HANDLE_CONF_FAIL
-};
-
-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 enum handle_conf_result
-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 = find_conf(key);
-  if (!item) {
-    return HANDLE_CONF_UNKNOWN;
-  }
-
-  if (from_env_variable && item->parser == confitem_parse_bool) {
-    // Special rule for boolean settings from the environment: "0", "false",
-    // "disable" and "no" (case insensitive) are invalid, and all other values
-    // mean true.
-    //
-    // Previously any value meant true, but this was surprising to users, who
-    // might do something like CCACHE_DISABLE=0 and expect ccache to be
-    // enabled.
-    if (str_eq(value, "0") || strcasecmp(value, "false") == 0
-        || strcasecmp(value, "disable") == 0 || strcasecmp(value, "no") == 0) {
-      fatal("invalid boolean environment variable value \"%s\"", value);
-    }
-
-    bool* boolvalue = (bool*)((char*)conf + item->offset);
-    *boolvalue = !negate_boolean;
-    goto out;
-  }
-
-  if (!item->parser(value, (char*)conf + item->offset, errmsg)) {
-    return HANDLE_CONF_FAIL;
-  }
-  if (item->verifier && !item->verifier((char*)conf + item->offset, errmsg)) {
-    return HANDLE_CONF_FAIL;
-  }
-
-out:
-  conf->item_origins[item->number] = origin;
-  return HANDLE_CONF_OK;
-}
-
-static bool
-parse_line(const char* line, char** key, char** value, char** errmsg)
-{
-#define SKIP_WS(x)                                                             \
-  do {                                                                         \
-    while (isspace(*x)) {                                                      \
-      ++x;                                                                     \
-    }                                                                          \
-  } while (false)
-
-  *key = NULL;
-  *value = NULL;
-
-  const char* p = line;
-  SKIP_WS(p);
-  if (*p == '\0' || *p == '#') {
-    return true;
-  }
-  const char* 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)
-{
-  auto conf = static_cast<struct conf*>(x_malloc(sizeof(struct 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 = true;
-  conf->compression_level = 0;
-  conf->cpp_extension = x_strdup("");
-  conf->debug = false;
-  conf->depend_mode = false;
-  conf->direct_mode = true;
-  conf->disable = false;
-  conf->extra_files_to_hash = x_strdup("");
-  conf->file_clone = false;
-  conf->hard_link = false;
-  conf->hash_dir = true;
-  conf->ignore_headers_in_manifest = x_strdup("");
-  conf->keep_comments_cpp = false;
-  conf->limit_multiple = 0.8;
-  conf->log_file = x_strdup("");
-  conf->max_files = 0;
-  conf->max_size = (uint64_t)5 * 1000 * 1000 * 1000;
-  conf->path = x_strdup("");
-  conf->pch_external_checksum = false;
-  conf->prefix_command = x_strdup("");
-  conf->prefix_command_cpp = x_strdup("");
-  conf->read_only = false;
-  conf->read_only_direct = false;
-  conf->recache = false;
-  conf->run_second_cpp = true;
-  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 =
-    static_cast<const char**>(x_malloc(confitems_count() * sizeof(char*)));
-  for (size_t i = 0; i < confitems_count(); ++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->ignore_headers_in_manifest);
-  free(conf->log_file);
-  free(conf->path);
-  free(conf->prefix_command);
-  free(conf->prefix_command_cpp);
-  free(conf->temporary_dir);
-  free((void*)conf->item_origins); // Workaround for MSVC warning
-  free(conf);
-}
-
-// Note: The path pointer is stored in conf, so path must outlive conf.
-//
-// On failure, if an I/O error occurred errno is set appropriately, otherwise
-// errno is set to zero indicating that config itself was invalid.
-bool
-conf_read(struct conf* conf, const char* path, char** errmsg)
-{
-  assert(errmsg);
-  *errmsg = NULL;
-
-  FILE* f = fopen(path, "r");
-  if (!f) {
-    *errmsg = format("%s: %s", path, strerror(errno));
-    return false;
-  }
-
-  unsigned line_number = 0;
-  bool result = true;
-  char buf[10000];
-  while (fgets(buf, sizeof(buf), f)) {
-    ++line_number;
-
-    char* key;
-    char* value;
-    char* errmsg2;
-    enum handle_conf_result hcr = HANDLE_CONF_OK;
-    bool ok = parse_line(buf, &key, &value, &errmsg2);
-    if (ok && key) { // key == NULL if comment or blank line.
-      hcr = handle_conf_setting(conf, key, value, &errmsg2, false, false, path);
-      ok = hcr != HANDLE_CONF_FAIL; // unknown is OK
-    }
-    free(key);
-    free(value);
-    if (!ok) {
-      *errmsg = format("%s:%u: %s", path, line_number, errmsg2);
-      free(errmsg2);
-      errno = 0;
-      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)
-{
-  for (char** p = environ; *p; ++p) {
-    if (!str_startswith(*p, "CCACHE_")) {
-      continue;
-    }
-    char* q = strchr(*p, '=');
-    if (!q) {
-      continue;
-    }
-
-    bool negate;
-    size_t key_start;
-    if (str_startswith(*p + 7, "NO")) {
-      negate = true;
-      key_start = 9;
-    } else {
-      negate = false;
-      key_start = 7;
-    }
-    char* key = x_strndup(*p + key_start, q - *p - key_start);
-
-    ++q; // Now points to the value.
-
-    const struct env_to_conf_item* env_to_conf_item = find_env_to_conf(key);
-    if (!env_to_conf_item) {
-      free(key);
-      continue;
-    }
-
-    char* errmsg2 = NULL;
-    enum handle_conf_result hcr =
-      handle_conf_setting(conf,
-                          env_to_conf_item->conf_name,
-                          q,
-                          &errmsg2,
-                          true,
-                          negate,
-                          "environment");
-    if (hcr != HANDLE_CONF_OK) {
-      *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)
-{
-  const struct conf_item* item = find_conf(key);
-  if (!item) {
-    *errmsg = format("unknown configuration option \"%s\"", key);
-    return false;
-  }
-
-  char dummy[8] = {0}; // The maximum entry size in struct conf.
-  if (!item->parser(value, (void*)dummy, errmsg)
-      || (item->verifier && !item->verifier(value, errmsg))) {
-    return false;
-  }
-
-  FILE* infile = fopen(path, "r");
-  if (!infile) {
-    *errmsg = format("%s: %s", path, strerror(errno));
-    return false;
-  }
-
-  char* outpath = format("%s.tmp", path);
-  FILE* outfile = create_tmp_file(&outpath, "w");
-  if (!outfile) {
-    *errmsg = format("%s: %s", outpath, strerror(errno));
-    free(outpath);
-    fclose(infile);
-    return false;
-  }
-
-  bool found = false;
-  char buf[10000];
-  while (fgets(buf, sizeof(buf), infile)) {
-    char* key2;
-    char* value2;
-    char* errmsg2;
-    bool 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_value(struct conf* conf, const char* key, FILE* file, char** errmsg)
-{
-  const struct conf_item* item = find_conf(key);
-  if (!item) {
-    *errmsg = format("unknown configuration option \"%s\"", key);
-    return false;
-  }
-  void* value = (char*)conf + item->offset;
-  char* str = item->formatter(value);
-  fprintf(file, "%s\n", str);
-  free(str);
-  return true;
-}
-
-static bool
-print_item(struct conf* conf,
-           const char* key,
-           void (*printer)(const char* descr,
-                           const char* origin,
-                           void* context),
-           void* context)
-{
-  const struct conf_item* item = find_conf(key);
-  if (!item) {
-    return false;
-  }
-  void* value = (char*)conf + item->offset;
-  char* str = item->formatter(value);
-  char* buf = x_strdup("");
-  reformat(&buf, "%s = %s", key, str);
-  printer(buf, conf->item_origins[item->number], context);
-  free(buf);
-  free(str);
-  return true;
-}
-
-bool
-conf_print_items(struct conf* conf,
-                 void (*printer)(const char* descr,
-                                 const char* origin,
-                                 void* context),
-                 void* context)
-{
-  bool ok = true;
-  ok &= print_item(conf, "base_dir", printer, context);
-  ok &= print_item(conf, "cache_dir", printer, context);
-  ok &= print_item(conf, "cache_dir_levels", printer, context);
-  ok &= print_item(conf, "compiler", printer, context);
-  ok &= print_item(conf, "compiler_check", printer, context);
-  ok &= print_item(conf, "compression", printer, context);
-  ok &= print_item(conf, "compression_level", printer, context);
-  ok &= print_item(conf, "cpp_extension", printer, context);
-  ok &= print_item(conf, "debug", printer, context);
-  ok &= print_item(conf, "depend_mode", printer, context);
-  ok &= print_item(conf, "direct_mode", printer, context);
-  ok &= print_item(conf, "disable", printer, context);
-  ok &= print_item(conf, "extra_files_to_hash", printer, context);
-  ok &= print_item(conf, "file_clone", printer, context);
-  ok &= print_item(conf, "hard_link", printer, context);
-  ok &= print_item(conf, "hash_dir", printer, context);
-  ok &= print_item(conf, "ignore_headers_in_manifest", printer, context);
-  ok &= print_item(conf, "keep_comments_cpp", printer, context);
-  ok &= print_item(conf, "limit_multiple", printer, context);
-  ok &= print_item(conf, "log_file", printer, context);
-  ok &= print_item(conf, "max_files", printer, context);
-  ok &= print_item(conf, "max_size", printer, context);
-  ok &= print_item(conf, "path", printer, context);
-  ok &= print_item(conf, "pch_external_checksum", printer, context);
-  ok &= print_item(conf, "prefix_command", printer, context);
-  ok &= print_item(conf, "prefix_command_cpp", printer, context);
-  ok &= print_item(conf, "read_only", printer, context);
-  ok &= print_item(conf, "read_only_direct", printer, context);
-  ok &= print_item(conf, "recache", printer, context);
-  ok &= print_item(conf, "run_second_cpp", printer, context);
-  ok &= print_item(conf, "sloppiness", printer, context);
-  ok &= print_item(conf, "stats", printer, context);
-  ok &= print_item(conf, "temporary_dir", printer, context);
-  ok &= print_item(conf, "umask", printer, context);
-  ok &= print_item(conf, "unify", printer, context);
-  return ok;
-}
diff --git a/src/conf.hpp b/src/conf.hpp
deleted file mode 100644 (file)
index 54c27f0..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright (C) 2011-2019 Joel Rosdahl and other contributors
-//
-// See doc/AUTHORS.adoc for a complete list of contributors.
-//
-// 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
-
-#pragma once
-
-#include "system.hpp"
-
-struct conf
-{
-  char* base_dir;
-  char* cache_dir;
-  unsigned cache_dir_levels;
-  char* compiler;
-  char* compiler_check;
-  bool compression;
-  int compression_level;
-  char* cpp_extension;
-  bool debug;
-  bool depend_mode;
-  bool direct_mode;
-  bool disable;
-  char* extra_files_to_hash;
-  bool file_clone;
-  bool hard_link;
-  bool hash_dir;
-  char* ignore_headers_in_manifest;
-  bool keep_comments_cpp;
-  double limit_multiple;
-  char* log_file;
-  unsigned max_files;
-  uint64_t max_size;
-  char* path;
-  bool pch_external_checksum;
-  char* prefix_command;
-  char* prefix_command_cpp;
-  bool read_only;
-  bool read_only_direct;
-  bool recache;
-  bool run_second_cpp;
-  unsigned sloppiness;
-  bool stats;
-  char* temporary_dir;
-  unsigned umask;
-  bool unify;
-
-  const char** item_origins;
-};
-
-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);
-bool
-conf_print_value(struct conf* conf, const char* key, FILE* file, char** errmsg);
-bool conf_set_value_in_file(const char* path,
-                            const char* key,
-                            const char* value,
-                            char** errmsg);
-bool conf_print_items(struct conf* conf,
-                      void (*printer)(const char* descr,
-                                      const char* origin,
-                                      void* context),
-                      void* context);
diff --git a/src/confitems.cpp b/src/confitems.cpp
deleted file mode 100644 (file)
index 386a430..0000000
+++ /dev/null
@@ -1,334 +0,0 @@
-// Copyright (C) 2018-2019 Joel Rosdahl and other contributors
-//
-// See doc/AUTHORS.adoc for a complete list of contributors.
-//
-// 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 "confitems.hpp"
-
-#include "ccache.hpp"
-
-static char*
-format_string(const void* value)
-{
-  const char* const* str = (const char* const*)value;
-  return x_strdup(*str);
-}
-
-bool
-confitem_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;
-  }
-}
-
-char*
-confitem_format_bool(const void* value)
-{
-  const bool* b = (const bool*)value;
-  return x_strdup(*b ? "true" : "false");
-}
-
-bool
-confitem_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 != NULL;
-}
-
-char*
-confitem_format_env_string(const void* value)
-{
-  return format_string(value);
-}
-
-bool
-confitem_parse_double(const char* str, void* result, char** errmsg)
-{
-  double* value = (double*)result;
-  errno = 0;
-  char* endptr;
-  double x = strtod(str, &endptr);
-  if (errno == 0 && *str != '\0' && *endptr == '\0') {
-    *value = x;
-    return true;
-  } else {
-    *errmsg = format("invalid floating point: \"%s\"", str);
-    return false;
-  }
-}
-
-char*
-confitem_format_double(const void* value)
-{
-  const double* x = (const double*)value;
-  return format("%.1f", *x);
-}
-
-bool
-confitem_parse_size(const char* str, void* result, char** errmsg)
-{
-  uint64_t* value = (uint64_t*)result;
-  uint64_t size;
-  if (parse_size_with_suffix(str, &size)) {
-    *value = size;
-    return true;
-  } else {
-    *errmsg = format("invalid size: \"%s\"", str);
-    return false;
-  }
-}
-
-char*
-confitem_format_size(const void* value)
-{
-  const uint64_t* size = (const uint64_t*)value;
-  return format_parsable_size_with_suffix(*size);
-}
-
-bool
-confitem_parse_sloppiness(const char* str, void* result, char** errmsg)
-{
-  unsigned* value = (unsigned*)result;
-  if (!str) {
-    return *value;
-  }
-
-  char* p = x_strdup(str);
-  char* q = p;
-  char* word;
-  char* saveptr = NULL;
-  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_ctime")) {
-      *value |= SLOPPY_FILE_STAT_MATCHES_CTIME;
-    } 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, "system_headers")
-               || str_eq(word, "no_system_headers")) {
-      *value |= SLOPPY_SYSTEM_HEADERS;
-    } else if (str_eq(word, "pch_defines")) {
-      *value |= SLOPPY_PCH_DEFINES;
-    } else if (str_eq(word, "time_macros")) {
-      *value |= SLOPPY_TIME_MACROS;
-    } else if (str_eq(word, "clang_index_store")) {
-      *value |= SLOPPY_CLANG_INDEX_STORE;
-    } else if (str_eq(word, "locale")) {
-      *value |= SLOPPY_LOCALE;
-    } else {
-      *errmsg = format("unknown sloppiness: \"%s\"", word);
-      free(p);
-      return false;
-    }
-    q = NULL;
-  }
-  free(p);
-  return true;
-}
-
-char*
-confitem_format_sloppiness(const void* value)
-{
-  const unsigned* sloppiness = (const unsigned*)value;
-  char* s = x_strdup("");
-  if (*sloppiness & SLOPPY_FILE_MACRO) {
-    reformat(&s, "%sfile_macro, ", s);
-  }
-  if (*sloppiness & SLOPPY_INCLUDE_FILE_MTIME) {
-    reformat(&s, "%sinclude_file_mtime, ", s);
-  }
-  if (*sloppiness & SLOPPY_INCLUDE_FILE_CTIME) {
-    reformat(&s, "%sinclude_file_ctime, ", s);
-  }
-  if (*sloppiness & SLOPPY_TIME_MACROS) {
-    reformat(&s, "%stime_macros, ", s);
-  }
-  if (*sloppiness & SLOPPY_PCH_DEFINES) {
-    reformat(&s, "%spch_defines, ", s);
-  }
-  if (*sloppiness & SLOPPY_FILE_STAT_MATCHES) {
-    reformat(&s, "%sfile_stat_matches, ", s);
-  }
-  if (*sloppiness & SLOPPY_FILE_STAT_MATCHES_CTIME) {
-    reformat(&s, "%sfile_stat_matches_ctime, ", s);
-  }
-  if (*sloppiness & SLOPPY_SYSTEM_HEADERS) {
-    reformat(&s, "%ssystem_headers, ", s);
-  }
-  if (*sloppiness & SLOPPY_CLANG_INDEX_STORE) {
-    reformat(&s, "%sclang_index_store, ", s);
-  }
-  if (*sloppiness & SLOPPY_LOCALE) {
-    reformat(&s, "%slocale, ", s);
-  }
-  if (*sloppiness) {
-    // Strip last ", ".
-    s[strlen(s) - 2] = '\0';
-  }
-  return s;
-}
-
-bool
-confitem_parse_string(const char* str, void* result, char** errmsg)
-{
-  (void)errmsg;
-
-  char** value = (char**)result;
-  free(*value);
-  *value = x_strdup(str);
-  return true;
-}
-
-char*
-confitem_format_string(const void* value)
-{
-  return format_string(value);
-}
-
-bool
-confitem_parse_umask(const char* str, void* result, char** errmsg)
-{
-  unsigned* value = (unsigned*)result;
-  if (str_eq(str, "")) {
-    *value = UINT_MAX;
-    return true;
-  }
-
-  errno = 0;
-  char* endptr;
-  *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;
-  }
-}
-
-char*
-confitem_format_umask(const void* value)
-{
-  const unsigned* umask = (const unsigned*)value;
-  if (*umask == UINT_MAX) {
-    return x_strdup("");
-  } else {
-    return format("%03o", *umask);
-  }
-}
-
-bool
-confitem_parse_int(const char* str, void* result, char** errmsg)
-{
-  int* value = (int*)result;
-  errno = 0;
-  char* endptr;
-  long x = strtol(str, &endptr, 10);
-  if (errno == 0 && *str != '\0' && *endptr == '\0') {
-    *value = x;
-    return true;
-  } else {
-    *errmsg = format("invalid integer: \"%s\"", str);
-    return false;
-  }
-}
-
-char*
-confitem_format_int(const void* value)
-{
-  const int* i = (const int*)value;
-  return format("%d", *i);
-}
-
-bool
-confitem_parse_unsigned(const char* str, void* result, char** errmsg)
-{
-  unsigned* value = (unsigned*)result;
-  errno = 0;
-  char* endptr;
-  long 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;
-  }
-}
-
-char*
-confitem_format_unsigned(const void* value)
-{
-  const unsigned* i = (const unsigned*)value;
-  return format("%u", *i);
-}
-
-bool
-confitem_verify_absolute_path(const void* value, char** errmsg)
-{
-  const char* const* path = (const char* const*)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;
-  }
-}
-
-bool
-confitem_verify_compression_level(const void* value, char** errmsg)
-{
-  const int* level = (const int*)value;
-  assert(level);
-  if (*level >= -128 && *level <= 127) {
-    return true;
-  } else {
-    *errmsg = format("compression level must be between -128 and 127");
-    return false;
-  }
-}
-
-bool
-confitem_verify_dir_levels(const void* value, char** errmsg)
-{
-  const unsigned* levels = (const 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;
-  }
-}
diff --git a/src/confitems.gperf b/src/confitems.gperf
deleted file mode 100644 (file)
index fe9a120..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-%language=ANSI-C
-%enum
-%struct-type
-%readonly-tables
-%define hash-function-name confitems_hash
-%define lookup-function-name confitems_get
-%define initializer-suffix ,0,0,NULL,NULL,NULL
-%{
-#include "confitems.hpp"
-#include "conf.hpp"
-
-#undef bool
-#define ITEM_ENTRY(name, type, verify_fn) \
-       offsetof(struct conf, name), confitem_parse_ ## type, \
-       confitem_format_ ## type, verify_fn
-#define ITEM(name, type) \
-       ITEM_ENTRY(name, type, NULL)
-#define ITEM_V(name, type, verification) \
-       ITEM_ENTRY(name, type, confitem_verify_ ## verification)
-%}
-struct conf_item;
-%%
-base_dir,                   ITEM_V(base_dir, env_string, absolute_path)
-cache_dir,                  ITEM(cache_dir, env_string)
-cache_dir_levels,           ITEM_V(cache_dir_levels, unsigned, dir_levels)
-compiler,                   ITEM(compiler, string)
-compiler_check,             ITEM(compiler_check, string)
-compression,                ITEM(compression, bool)
-compression_level,          ITEM_V(compression_level, int, compression_level)
-cpp_extension,              ITEM(cpp_extension, string)
-debug,                      ITEM(debug, bool)
-depend_mode,                ITEM(depend_mode, bool)
-direct_mode,                ITEM(direct_mode, bool)
-disable,                    ITEM(disable, bool)
-extra_files_to_hash,        ITEM(extra_files_to_hash, env_string)
-file_clone,                 ITEM(file_clone, bool)
-hard_link,                  ITEM(hard_link, bool)
-hash_dir,                   ITEM(hash_dir, bool)
-ignore_headers_in_manifest, ITEM(ignore_headers_in_manifest, env_string)
-keep_comments_cpp,          ITEM(keep_comments_cpp, bool)
-limit_multiple,             ITEM(limit_multiple, double)
-log_file,                   ITEM(log_file, env_string)
-max_files,                  ITEM(max_files, unsigned)
-max_size,                   ITEM(max_size, size)
-path,                       ITEM(path, env_string)
-pch_external_checksum,      ITEM(pch_external_checksum, bool)
-prefix_command,             ITEM(prefix_command, env_string)
-prefix_command_cpp,         ITEM(prefix_command_cpp, env_string)
-read_only,                  ITEM(read_only, bool)
-read_only_direct,           ITEM(read_only_direct, bool)
-recache,                    ITEM(recache, bool)
-run_second_cpp,             ITEM(run_second_cpp, bool)
-sloppiness,                 ITEM(sloppiness, sloppiness)
-stats,                      ITEM(stats, bool)
-temporary_dir,              ITEM(temporary_dir, env_string)
-umask,                      ITEM(umask, umask)
-unify,                      ITEM(unify, bool)
diff --git a/src/confitems.hpp b/src/confitems.hpp
deleted file mode 100644 (file)
index aba12d9..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (C) 2018-2019 Joel Rosdahl and other contributors
-//
-// See doc/AUTHORS.adoc for a complete list of contributors.
-//
-// 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
-
-#pragma once
-
-#include "system.hpp"
-
-typedef bool (*conf_item_parser)(const char* str, void* result, char** errmsg);
-typedef bool (*conf_item_verifier)(const void* value, char** errmsg);
-typedef char* (*conf_item_formatter)(const void* value);
-
-struct conf_item
-{
-  const char* name;
-  size_t number;
-  size_t offset;
-  conf_item_parser parser;
-  conf_item_formatter formatter;
-  conf_item_verifier verifier;
-};
-
-bool confitem_parse_bool(const char* str, void* result, char** errmsg);
-char* confitem_format_bool(const void* value);
-
-bool confitem_parse_env_string(const char* str, void* result, char** errmsg);
-char* confitem_format_env_string(const void* value);
-
-bool confitem_parse_double(const char* str, void* result, char** errmsg);
-char* confitem_format_double(const void* value);
-
-bool confitem_parse_size(const char* str, void* result, char** errmsg);
-char* confitem_format_size(const void* value);
-
-bool confitem_parse_sloppiness(const char* str, void* result, char** errmsg);
-char* confitem_format_sloppiness(const void* value);
-
-bool confitem_parse_string(const char* str, void* result, char** errmsg);
-char* confitem_format_string(const void* value);
-
-bool confitem_parse_umask(const char* str, void* result, char** errmsg);
-char* confitem_format_umask(const void* value);
-
-bool confitem_parse_int(const char* str, void* result, char** errmsg);
-char* confitem_format_int(const void* value);
-
-bool confitem_parse_unsigned(const char* str, void* result, char** errmsg);
-char* confitem_format_unsigned(const void* value);
-
-bool confitem_verify_absolute_path(const void* value, char** errmsg);
-bool confitem_verify_compression_level(const void* value, char** errmsg);
-bool confitem_verify_dir_levels(const void* value, char** errmsg);
-
-const struct conf_item* confitems_get(const char* str, size_t len);
-size_t confitems_count(void);
diff --git a/src/envtoconfitems.gperf b/src/envtoconfitems.gperf
deleted file mode 100644 (file)
index f7703f1..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-%language=ANSI-C
-%enum
-%struct-type
-%readonly-tables
-%define hash-function-name envtoconfitems_hash
-%define lookup-function-name envtoconfitems_get
-%define slot-name env_name
-%define initializer-suffix ,""
-%{
-#include "envtoconfitems.hpp"
-%}
-struct env_to_conf_item;
-%%
-BASEDIR, "base_dir"
-CC, "compiler"
-COMMENTS, "keep_comments_cpp"
-COMPILER, "compiler"
-COMPILERCHECK, "compiler_check"
-COMPRESS, "compression"
-COMPRESSLEVEL, "compression_level"
-CPP2, "run_second_cpp"
-DEBUG, "debug"
-DEPEND, "depend_mode"
-DIR, "cache_dir"
-DIRECT, "direct_mode"
-DISABLE, "disable"
-EXTENSION, "cpp_extension"
-EXTRAFILES, "extra_files_to_hash"
-FILECLONE, "file_clone"
-HARDLINK, "hard_link"
-HASHDIR, "hash_dir"
-IGNOREHEADERS, "ignore_headers_in_manifest"
-LIMIT_MULTIPLE, "limit_multiple"
-LOGFILE, "log_file"
-MAXFILES, "max_files"
-MAXSIZE, "max_size"
-NLEVELS, "cache_dir_levels"
-PATH, "path"
-PCH_EXTSUM, "pch_external_checksum"
-PREFIX, "prefix_command"
-PREFIX_CPP, "prefix_command_cpp"
-READONLY, "read_only"
-READONLY_DIRECT, "read_only_direct"
-RECACHE, "recache"
-SLOPPINESS, "sloppiness"
-STATS, "stats"
-TEMPDIR, "temporary_dir"
-UMASK, "umask"
-UNIFY, "unify"
diff --git a/src/envtoconfitems.hpp b/src/envtoconfitems.hpp
deleted file mode 100644 (file)
index 7885b48..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (C) 2018-2019 Joel Rosdahl and other contributors
-//
-// See doc/AUTHORS.adoc for a complete list of contributors.
-//
-// 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
-
-#pragma once
-
-#include "system.hpp"
-
-struct env_to_conf_item
-{
-  const char* env_name;
-  const char* conf_name;
-};
-
-const struct env_to_conf_item* envtoconfitems_get(const char* str, size_t len);
-
-size_t envtoconfitems_count(void);
index fb5c638ff70665b1de1e9badac15cc14b8b10183..f69e681768d158d73256b9dd1a712b0edee0dbb2 100644 (file)
 // this program; if not, write to the Free Software Foundation, Inc., 51
 // Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
+#include "Config.hpp"
 #include "ccache.hpp"
 
-extern struct conf* conf;
-
-static char*
-find_executable_in_path(const char* name, const char* exclude_name, char* path);
+static char* find_executable_in_path(const char* name,
+                                     const char* exclude_name,
+                                     const char* path);
 
 #ifdef _WIN32
 // Re-create a win32 command line string based on **argv.
@@ -298,7 +298,7 @@ find_executable(const char* name, const char* exclude_name)
     return x_strdup(name);
   }
 
-  char* path = conf->path;
+  const char* path = g_config.path().c_str();
   if (str_eq(path, "")) {
     path = getenv("PATH");
   }
@@ -311,14 +311,16 @@ find_executable(const char* name, const char* exclude_name)
 }
 
 static char*
-find_executable_in_path(const char* name, const char* exclude_name, char* path)
+find_executable_in_path(const char* name,
+                        const char* exclude_name,
+                        const char* path)
 {
-  path = x_strdup(path);
+  char* path_buf = x_strdup(path);
 
   // Search the path looking for the first compiler of the right name that
   // isn't us.
   char* saveptr = NULL;
-  for (char* tok = strtok_r(path, PATH_DELIM, &saveptr); tok;
+  for (char* tok = strtok_r(path_buf, PATH_DELIM, &saveptr); tok;
        tok = strtok_r(NULL, PATH_DELIM, &saveptr)) {
 #ifdef _WIN32
     char namebuf[MAX_PATH];
@@ -330,7 +332,7 @@ find_executable_in_path(const char* name, const char* exclude_name, char* path)
     }
     (void)exclude_name;
     if (ret) {
-      free(path);
+      free(path_buf);
       return x_strdup(namebuf);
     }
 #else
@@ -355,14 +357,14 @@ find_executable_in_path(const char* name, const char* exclude_name, char* path)
       }
 
       // Found it!
-      free(path);
+      free(path_buf);
       return fname;
     }
     free(fname);
 #endif
   }
 
-  free(path);
+  free(path_buf);
   return NULL;
 }
 
index 99888296d5d9d7929ff565d3e784265047315252..e9c56c70fce40e26b433e77049854060bcdc53be 100644 (file)
@@ -82,7 +82,7 @@ check_for_temporal_macros(const char* str, size_t len)
 
 // Hash a string. Returns a bitmask of HASH_SOURCE_CODE_* results.
 int
-hash_source_code_string(struct conf* conf,
+hash_source_code_string(const Config& config,
                         struct hash* hash,
                         const char* str,
                         size_t len,
@@ -92,7 +92,7 @@ hash_source_code_string(struct conf* conf,
 
   // Check for __DATE__ and __TIME__ if the sloppiness configuration tells us
   // we should.
-  if (!(conf->sloppiness & SLOPPY_TIME_MACROS)) {
+  if (!(config.sloppiness() & SLOPPY_TIME_MACROS)) {
     result |= check_for_temporal_macros(str, len);
   }
 
@@ -127,7 +127,7 @@ hash_source_code_string(struct conf* conf,
 // Hash a file ignoring comments. Returns a bitmask of HASH_SOURCE_CODE_*
 // results.
 int
-hash_source_code_file(struct conf* conf, struct hash* hash, const char* path)
+hash_source_code_file(const Config& config, struct hash* hash, const char* path)
 {
   if (is_precompiled_header(path)) {
     if (hash_file(hash, path)) {
@@ -141,7 +141,7 @@ hash_source_code_file(struct conf* conf, struct hash* hash, const char* path)
     if (!read_file(path, 0, &data, &size)) {
       return HASH_SOURCE_CODE_ERROR;
     }
-    int result = hash_source_code_string(conf, hash, data, size, path);
+    int result = hash_source_code_string(config, hash, data, size, path);
     free(data);
     return result;
   }
index 32ebeec2eed3e9e7d82528c210fed8c2c51069ce..9bd34aa6761a5e17e33309fd65e3935eed9ab827 100644 (file)
@@ -20,7 +20,7 @@
 
 #include "system.hpp"
 
-#include "conf.hpp"
+#include "Config.hpp"
 #include "hash.hpp"
 
 #include <inttypes.h>
@@ -35,13 +35,14 @@ int strings_equal(void* str1, void* str2);
 #define HASH_SOURCE_CODE_FOUND_TIME 4
 
 int check_for_temporal_macros(const char* str, size_t len);
-int hash_source_code_string(struct conf* conf,
+int hash_source_code_string(const Config& config,
                             struct hash* hash,
                             const char* str,
                             size_t len,
                             const char* path);
-int
-hash_source_code_file(struct conf* conf, struct hash* hash, const char* path);
+int hash_source_code_file(const Config& config,
+                          struct hash* hash,
+                          const char* path);
 bool hash_command_output(struct hash* hash,
                          const char* command,
                          const char* compiler);
index babc6cf8ad8195dc2e96182bb95d7ef97f6f6aa5..2b9091f9cedb4806f5e55eac6ce84470876c1140 100644 (file)
@@ -451,7 +451,7 @@ out:
 }
 
 static bool
-verify_result(struct conf* conf,
+verify_result(const Config& config,
               struct manifest* mf,
               struct result* result,
               struct hashtable* stated_files,
@@ -486,8 +486,8 @@ verify_result(struct conf* conf,
       return false;
     }
 
-    if (conf->sloppiness & SLOPPY_FILE_STAT_MATCHES) {
-      if (!(conf->sloppiness & SLOPPY_FILE_STAT_MATCHES_CTIME)) {
+    if (config.sloppiness() & SLOPPY_FILE_STAT_MATCHES) {
+      if (!(config.sloppiness() & SLOPPY_FILE_STAT_MATCHES_CTIME)) {
         if (fi->mtime == st->mtime && fi->ctime == st->ctime) {
           cc_log("mtime/ctime hit for %s", path);
           continue;
@@ -507,7 +507,7 @@ verify_result(struct conf* conf,
     auto actual = static_cast<digest*>(hashtable_search(hashed_files, path));
     if (!actual) {
       struct hash* hash = hash_init();
-      int ret = hash_source_code_file(conf, hash, path);
+      int ret = hash_source_code_file(config, hash, path);
       if (ret & HASH_SOURCE_CODE_ERROR) {
         cc_log("Failed hashing %s", path);
         hash_free(hash);
@@ -674,7 +674,7 @@ add_result_entry(struct manifest* mf,
 // Try to get the result name from a manifest file. Caller frees. Returns NULL
 // on failure.
 struct digest*
-manifest_get(struct conf* conf, const char* manifest_path)
+manifest_get(const Config& config, const char* manifest_path)
 {
   char* errmsg;
   struct manifest* mf = read_manifest(manifest_path, &errmsg);
@@ -694,7 +694,7 @@ manifest_get(struct conf* conf, const char* manifest_path)
   struct digest* name = NULL;
   for (uint32_t i = mf->n_results; i > 0; i--) {
     if (verify_result(
-          conf, mf, &mf->results[i - 1], stated_files, hashed_files)) {
+          config, mf, &mf->results[i - 1], stated_files, hashed_files)) {
       name = static_cast<digest*>(x_malloc(sizeof(digest)));
       *name = mf->results[i - 1].name;
       goto out;
index f46270a3a6e27ac74faa262d9760b07df5bbe045..21a5587040b30d30a4627b047f99443b387e051e 100644 (file)
@@ -20,7 +20,7 @@
 
 #include "system.hpp"
 
-#include "conf.hpp"
+#include "Config.hpp"
 #include "hashutil.hpp"
 
 #include "third_party/hashtable.h"
@@ -28,7 +28,7 @@
 extern const char MANIFEST_MAGIC[4];
 #define MANIFEST_VERSION 2
 
-struct digest* manifest_get(struct conf* conf, const char* manifest_path);
+struct digest* manifest_get(const Config& config, const char* manifest_path);
 bool manifest_put(const char* manifest_path,
                   struct digest* result_digest,
                   struct hashtable* included_files);
index 147ecaf53f7c8a82740239000fd6c2821260a93b..25762310a1595ee1577a1285e311401fabdacb5f 100644 (file)
@@ -18,6 +18,7 @@
 
 #include "result.hpp"
 
+#include "Config.hpp"
 #include "ccache.hpp"
 #include "common_header.hpp"
 #include "compression.hpp"
@@ -82,7 +83,6 @@
 //
 // 1: Introduced in ccache 4.0.
 
-extern const struct conf* conf;
 extern char* stats_file;
 
 const char RESULT_MAGIC[4] = {'c', 'C', 'r', 'S'};
@@ -282,14 +282,14 @@ get_raw_file_path(const char* result_path_in_cache, uint32_t entry_number)
 static bool
 copy_raw_file(const char* source, const char* dest, bool to_cache)
 {
-  if (conf->file_clone) {
+  if (g_config.file_clone()) {
     cc_log("Cloning %s to %s", source, dest);
     if (clone_file(source, dest, to_cache)) {
       return true;
     }
     cc_log("Failed to clone: %s", strerror(errno));
   }
-  if (conf->hard_link) {
+  if (g_config.hard_link()) {
     x_try_unlink(dest);
     cc_log("Hard linking %s to %s", source, dest);
     int ret = link(source, dest);
@@ -584,7 +584,7 @@ error:
 static bool
 should_store_raw_file(const char* suffix)
 {
-  if (!conf->file_clone && !conf->hard_link) {
+  if (!g_config.file_clone() && !g_config.hard_link()) {
     return false;
   }
 
index 98b4b49831f4e1c3092a10af006efe6af5c67d3b..e49d407ebbfc054a50cc3fa37f6eeae4f72ccc96 100644 (file)
@@ -20,7 +20,7 @@
 
 #include "system.hpp"
 
-#include "conf.hpp"
+#include <cstdio>
 
 extern const char RESULT_MAGIC[4];
 #define RESULT_VERSION 1
index 0a6b9c604aa3083644c3ca12feddfe2176407779..863f09c22a5f6fb31124dc70dde0b4cb040121e0 100644 (file)
@@ -32,7 +32,6 @@
 #include <unistd.h>
 
 extern char* stats_file;
-extern struct conf* conf;
 extern unsigned lock_staleness_limit;
 extern char* primary_config_path;
 extern char* secondary_config_path;
@@ -266,9 +265,9 @@ stats_collect(struct counters* counters, time_t* last_updated)
     char* fname;
 
     if (dir == -1) {
-      fname = format("%s/stats", conf->cache_dir);
+      fname = format("%s/stats", g_config.cache_dir().c_str());
     } else {
-      fname = format("%s/%1x/stats", conf->cache_dir, dir);
+      fname = format("%s/%1x/stats", g_config.cache_dir().c_str(), dir);
     }
 
     counters->data[STATS_ZEROTIMESTAMP] = 0; // Don't add
@@ -318,9 +317,7 @@ stats_read(const char* sfile, struct counters* counters)
 static void
 stats_flush_to_file(const char* sfile, struct counters* updates)
 {
-  assert(conf);
-
-  if (!conf->stats) {
+  if (!g_config.stats()) {
     return;
   }
 
@@ -344,7 +341,8 @@ stats_flush_to_file(const char* sfile, struct counters* updates)
 
     // A NULL sfile means that we didn't get past calculate_object_hash(), so
     // we just choose one of stats files in the 16 subdirectories.
-    stats_dir = format("%s/%x", conf->cache_dir, hash_from_int(getpid()) % 16);
+    stats_dir = format(
+      "%s/%x", g_config.cache_dir().c_str(), hash_from_int(getpid()) % 16);
     sfile = format("%s/stats", stats_dir);
     free(stats_dir);
   }
@@ -361,7 +359,7 @@ stats_flush_to_file(const char* sfile, struct counters* updates)
   stats_write(sfile, counters);
   lockfile_release(sfile);
 
-  if (!str_eq(conf->log_file, "") || conf->debug) {
+  if (!g_config.log_file().empty() || g_config.debug()) {
     for (int i = 0; i < STATS_END; ++i) {
       if (updates->data[stats_info[i].stat] != 0
           && !(stats_info[i].flags & FLAG_NOZERO)) {
@@ -373,25 +371,25 @@ stats_flush_to_file(const char* sfile, struct counters* updates)
   char* subdir = x_dirname(sfile);
   bool need_cleanup = false;
 
-  if (conf->max_files != 0
-      && counters->data[STATS_NUMFILES] > conf->max_files / 16) {
+  if (g_config.max_files() != 0
+      && counters->data[STATS_NUMFILES] > g_config.max_files() / 16) {
     cc_log("Need to clean up %s since it holds %u files (limit: %u files)",
            subdir,
            counters->data[STATS_NUMFILES],
-           conf->max_files / 16);
+           g_config.max_files() / 16);
     need_cleanup = true;
   }
-  if (conf->max_size != 0
-      && counters->data[STATS_TOTALSIZE] > conf->max_size / 1024 / 16) {
+  if (g_config.max_size() != 0
+      && counters->data[STATS_TOTALSIZE] > g_config.max_size() / 1024 / 16) {
     cc_log("Need to clean up %s since it holds %u KiB (limit: %lu KiB)",
            subdir,
            counters->data[STATS_TOTALSIZE],
-           (unsigned long)conf->max_size / 1024 / 16);
+           (unsigned long)g_config.max_size() / 1024 / 16);
     need_cleanup = true;
   }
 
   if (need_cleanup) {
-    clean_up_dir(conf, subdir, conf->limit_multiple);
+    clean_up_dir(g_config, subdir, g_config.limit_multiple());
   }
 
   free(subdir);
@@ -428,13 +426,12 @@ stats_get_pending(enum stats stat)
 void
 stats_summary(void)
 {
-  assert(conf);
-
   struct counters* counters = counters_init(STATS_END);
   time_t last_updated;
   stats_collect(counters, &last_updated);
 
-  printf("cache directory                     %s\n", conf->cache_dir);
+  printf("cache directory                     %s\n",
+         g_config.cache_dir().c_str());
   printf("primary config                      %s\n",
          primary_config_path ? primary_config_path : "");
   printf("secondary config      (readonly)    %s\n",
@@ -475,11 +472,11 @@ stats_summary(void)
     }
   }
 
-  if (conf->max_files != 0) {
-    printf("max files                       %8u\n", conf->max_files);
+  if (g_config.max_files() != 0) {
+    printf("max files                       %8u\n", g_config.max_files());
   }
-  if (conf->max_size != 0) {
-    char* value = format_size(conf->max_size);
+  if (g_config.max_size() != 0) {
+    char* value = format_size(g_config.max_size());
     printf("max cache size                  %s\n", value);
     free(value);
   }
@@ -491,8 +488,6 @@ stats_summary(void)
 void
 stats_print(void)
 {
-  assert(conf);
-
   struct counters* counters = counters_init(STATS_END);
   time_t last_updated;
   stats_collect(counters, &last_updated);
@@ -512,9 +507,7 @@ stats_print(void)
 void
 stats_zero(void)
 {
-  assert(conf);
-
-  char* fname = format("%s/stats", conf->cache_dir);
+  char* fname = format("%s/stats", g_config.cache_dir().c_str());
   x_unlink(fname);
   free(fname);
 
@@ -523,7 +516,7 @@ stats_zero(void)
   for (int dir = 0; dir <= 0xF; dir++) {
     struct counters* counters = counters_init(STATS_END);
     struct stat st;
-    fname = format("%s/%1x/stats", conf->cache_dir, dir);
+    fname = format("%s/%1x/stats", g_config.cache_dir().c_str(), dir);
     if (stat(fname, &st) != 0) {
       // No point in trying to reset the stats file if it doesn't exist.
       free(fname);
index b50bf23c41c1aac593fa3e4d0b6c513dae326b7c..7d5153d7d960c5e90d391cd9c21ae2949c4e53bf 100644 (file)
@@ -19,6 +19,7 @@
 
 #include "util.hpp"
 
+#include "Config.hpp"
 #include "Error.hpp"
 #include "ccache.hpp"
 
 #  include <windows.h>
 #endif
 
-extern const struct conf* conf;
-
-// Destination for conf->log_file.
+// Destination for g_config.log_file.
 static FILE* logfile;
 
 // Whether to use syslog() instead.
 static bool use_syslog;
 
-// Buffer used for logs in conf->debug mode.
+// Buffer used for logs in debug mode.
 static char* debug_log_buffer;
 
 // Allocated debug_log_buffer size.
@@ -89,23 +88,22 @@ init_log(void)
   if (debug_log_buffer || logfile || use_syslog) {
     return true;
   }
-  assert(conf);
-  if (conf->debug) {
+  if (g_config.debug()) {
     debug_log_buffer_capacity = DEBUG_LOG_BUFFER_MARGIN;
     debug_log_buffer = static_cast<char*>(x_malloc(debug_log_buffer_capacity));
     debug_log_size = 0;
   }
-  if (str_eq(conf->log_file, "")) {
-    return conf->debug;
+  if (g_config.log_file().empty()) {
+    return g_config.debug();
   }
 #ifdef HAVE_SYSLOG
-  if (str_eq(conf->log_file, "syslog")) {
+  if (g_config.log_file() == "syslog") {
     use_syslog = true;
     openlog("ccache", LOG_PID, LOG_USER);
     return true;
   }
 #endif
-  logfile = fopen(conf->log_file, "a");
+  logfile = fopen(g_config.log_file().c_str(), "a");
   if (logfile) {
 #ifndef _WIN32
     set_cloexec_flag(fileno(logfile));
@@ -192,7 +190,7 @@ warn_log_fail(void)
   // Note: Can't call fatal() since that would lead to recursion.
   fprintf(stderr,
           "ccache: error: Failed to write to %s: %s\n",
-          conf->log_file,
+          g_config.log_file().c_str(),
           strerror(errno));
   x_exit(EXIT_FAILURE);
 }
@@ -320,14 +318,15 @@ fatal(const char* format, ...)
 char*
 get_path_in_cache(const char* name, const char* suffix)
 {
-  char* path = x_strdup(conf->cache_dir);
-  for (unsigned i = 0; i < conf->cache_dir_levels; ++i) {
+  char* path = x_strdup(g_config.cache_dir().c_str());
+  for (unsigned i = 0; i < g_config.cache_dir_levels(); ++i) {
     char* p = format("%s/%c", path, name[i]);
     free(path);
     path = p;
   }
 
-  char* result = format("%s/%s%s", path, name + conf->cache_dir_levels, suffix);
+  char* result =
+    format("%s/%s%s", path, name + g_config.cache_dir_levels(), suffix);
   free(path);
   return result;
 }
index 4cab44be6d4dc48a4f2e28c0281d6c7aa86c810a..5a219c707db60ed8adeef25a1aaa4c4af8cadd50 100644 (file)
@@ -40,7 +40,6 @@ static int verbose;
 static const char COLOR_END[] = "\x1b[m";
 static const char COLOR_GREEN[] = "\x1b[1;32m";
 static const char COLOR_RED[] = "\x1b[1;31m";
-static char CONFIG_PATH_ENV[] = "CCACHE_CONFIG_PATH=/dev/null";
 
 #define COLOR(tty, color) ((tty) ? COLOR_##color : "")
 
@@ -135,7 +134,7 @@ cct_test_begin(const char* name)
   cct_chdir(name);
   current_test = name;
 
-  putenv(CONFIG_PATH_ENV);
+  x_setenv("CCACHE_CONFIG_PATH", "/dev/null");
   cc_reset();
 }
 
index 2c28d9d89f6e9bb1175ad6d73261236aa7ae922c..cd16e88ab856ad13e1c7184b2c39965ed44f975e 100644 (file)
@@ -19,7 +19,7 @@
 #include "framework.hpp"
 
 #define CATCH_CONFIG_RUNNER
-#include "third_party/catch.hpp"
+#include <catch.hpp>
 
 unsigned suite_args(unsigned);
 unsigned suite_argument_processing(unsigned);
@@ -42,7 +42,6 @@ main(int argc, char** argv)
                               &suite_compopt,
                               &suite_compr_type_none,
                               &suite_compr_type_zstd,
-                              &suite_conf,
                               &suite_counters,
                               &suite_hash,
                               &suite_hashutil,
@@ -52,7 +51,7 @@ main(int argc, char** argv)
                               NULL};
 
 #ifdef _WIN32
-  putenv("CCACHE_DETECT_SHEBANG=1");
+  x_setenv("CCACHE_DETECT_SHEBANG", "1");
 #endif
 
   char* testdir = format("testdir.%d", (int)getpid());
diff --git a/unittest/test_Config.cpp b/unittest/test_Config.cpp
new file mode 100644 (file)
index 0000000..9b66024
--- /dev/null
@@ -0,0 +1,454 @@
+// Copyright (C) 2011-2019 Joel Rosdahl and other contributors
+//
+// See doc/AUTHORS.adoc for a complete list of contributors.
+//
+// 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 "../src/Config.hpp"
+#include "../src/Error.hpp"
+#include "../src/ccache.hpp"
+#include "../src/util.hpp"
+
+#include <catch.hpp>
+#include <fmt/core.h>
+#include <limits>
+#include <string>
+#include <vector>
+
+using Catch::Equals;
+
+TEST_CASE("Config: default values")
+{
+  Config config;
+  CHECK(config.base_dir().empty());
+  CHECK(config.cache_dir() == std::string(get_home_directory()) + "/.ccache");
+  CHECK(config.cache_dir_levels() == 2);
+  CHECK(config.compiler().empty());
+  CHECK(config.compiler_check() == "mtime");
+  CHECK(config.compression());
+  CHECK(config.compression_level() == 0);
+  CHECK(config.cpp_extension().empty());
+  CHECK(!config.debug());
+  CHECK(!config.depend_mode());
+  CHECK(config.direct_mode());
+  CHECK(!config.disable());
+  CHECK(config.extra_files_to_hash().empty());
+  CHECK(!config.file_clone());
+  CHECK(!config.hard_link());
+  CHECK(config.hash_dir());
+  CHECK(config.ignore_headers_in_manifest().empty());
+  CHECK_FALSE(config.keep_comments_cpp());
+  CHECK(config.limit_multiple() == Approx(0.8));
+  CHECK(config.log_file().empty());
+  CHECK(config.max_files() == 0);
+  CHECK(config.max_size() == static_cast<uint64_t>(5) * 1000 * 1000 * 1000);
+  CHECK(config.path().empty());
+  CHECK_FALSE(config.pch_external_checksum());
+  CHECK(config.prefix_command().empty());
+  CHECK(config.prefix_command_cpp().empty());
+  CHECK_FALSE(config.read_only());
+  CHECK_FALSE(config.read_only_direct());
+  CHECK_FALSE(config.recache());
+  CHECK(config.run_second_cpp());
+  CHECK(config.sloppiness() == 0);
+  CHECK(config.stats());
+  CHECK(config.temporary_dir().empty());
+  CHECK(config.umask() == std::numeric_limits<uint32_t>::max());
+  CHECK_FALSE(config.unify());
+}
+
+TEST_CASE("Config::update_from_file")
+{
+  const char user[] = "rabbit";
+  x_setenv("USER", user);
+
+#ifndef _WIN32
+  std::string base_dir = fmt::format("/{0}/foo/{0}", user);
+#else
+  std::string base_dir = fmt::format("C:/{0}/foo/{0}", user);
+#endif
+
+  util::write_file(
+    "ccache.conf",
+    "base_dir = " + base_dir + "\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=false\n"
+    "compression_level= 2\n"
+    "cpp_extension = .foo\n"
+    "depend_mode = true\n"
+    "direct_mode = false\n"
+    "disable = true\n"
+    "extra_files_to_hash = a:b c:$USER\n"
+    "file_clone = true\n"
+    "hard_link = true\n"
+    "hash_dir = false\n"
+    "ignore_headers_in_manifest = a:b/c\n"
+    "keep_comments_cpp = true\n"
+    "limit_multiple = 1.0\n"
+    "log_file = $USER${USER} \n"
+    "max_files = 17\n"
+    "max_size = 123M\n"
+    "path = $USER.x\n"
+    "pch_external_checksum = true\n"
+    "prefix_command = x$USER\n"
+    "prefix_command_cpp = y\n"
+    "read_only = true\n"
+    "read_only_direct = true\n"
+    "recache = true\n"
+    "run_second_cpp = false\n"
+    "sloppiness =     file_macro   ,time_macros,  "
+    "include_file_mtime,include_file_ctime,file_stat_matches,file_stat_"
+    "matches_ctime,pch_defines ,  "
+    "no_system_headers,system_headers,clang_index_store\n"
+    "stats = false\n"
+    "temporary_dir = ${USER}_foo\n"
+    "umask = 777\n"
+    "unify = true"); // Note: no newline.
+
+  Config config;
+  REQUIRE(config.update_from_file("ccache.conf"));
+  CHECK(config.base_dir() == base_dir);
+  CHECK(config.cache_dir() == fmt::format("{0}$/{0}/.ccache", user));
+  CHECK(config.cache_dir_levels() == 4);
+  CHECK(config.compiler() == "foo");
+  CHECK(config.compiler_check() == "none");
+  CHECK_FALSE(config.compression());
+  CHECK(config.compression_level() == 2);
+  CHECK(config.cpp_extension() == ".foo");
+  CHECK(config.depend_mode());
+  CHECK_FALSE(config.direct_mode());
+  CHECK(config.disable());
+  CHECK(config.extra_files_to_hash() == fmt::format("a:b c:{}", user));
+  CHECK(config.file_clone());
+  CHECK(config.hard_link());
+  CHECK_FALSE(config.hash_dir());
+  CHECK(config.ignore_headers_in_manifest() == "a:b/c");
+  CHECK(config.keep_comments_cpp());
+  CHECK(config.limit_multiple() == Approx(1.0));
+  CHECK(config.log_file() == fmt::format("{0}{0}", user));
+  CHECK(config.max_files() == 17);
+  CHECK(config.max_size() == 123 * 1000 * 1000);
+  CHECK(config.path() == fmt::format("{}.x", user));
+  CHECK(config.pch_external_checksum());
+  CHECK(config.prefix_command() == fmt::format("x{}", user));
+  CHECK(config.prefix_command_cpp() == "y");
+  CHECK(config.read_only());
+  CHECK(config.read_only_direct());
+  CHECK(config.recache());
+  CHECK_FALSE(config.run_second_cpp());
+  CHECK(config.sloppiness()
+        == (SLOPPY_INCLUDE_FILE_MTIME | SLOPPY_INCLUDE_FILE_CTIME
+            | SLOPPY_FILE_MACRO | SLOPPY_TIME_MACROS | SLOPPY_FILE_STAT_MATCHES
+            | SLOPPY_FILE_STAT_MATCHES_CTIME | SLOPPY_SYSTEM_HEADERS
+            | SLOPPY_PCH_DEFINES | SLOPPY_CLANG_INDEX_STORE));
+  CHECK_FALSE(config.stats());
+  CHECK(config.temporary_dir() == fmt::format("{}_foo", user));
+  CHECK(config.umask() == 0777);
+  CHECK(config.unify());
+}
+
+TEST_CASE("Config::update_from_file, error handling")
+{
+  Config config;
+  unlink("ccache.conf"); // Make sure it doesn't exist.
+
+  SECTION("missing equal sign")
+  {
+    util::write_file("ccache.conf", "no equal sign");
+    REQUIRE_THROWS_WITH(config.update_from_file("ccache.conf"),
+                        Equals("ccache.conf:1: missing equal sign"));
+  }
+
+  SECTION("unknown key")
+  {
+    util::write_file("ccache.conf", "# Comment\nfoo = bar");
+    CHECK(config.update_from_file("ccache.conf"));
+  }
+
+  SECTION("invalid bool")
+  {
+    util::write_file("ccache.conf", "disable=");
+    REQUIRE_THROWS_WITH(config.update_from_file("ccache.conf"),
+                        Equals("ccache.conf:1: not a boolean value: \"\""));
+
+    util::write_file("ccache.conf", "disable=foo");
+    REQUIRE_THROWS_WITH(config.update_from_file("ccache.conf"),
+                        Equals("ccache.conf:1: not a boolean value: \"foo\""));
+  }
+
+  SECTION("invalid variable reference")
+  {
+    util::write_file("ccache.conf", "base_dir = ${foo");
+    REQUIRE_THROWS_WITH(
+      config.update_from_file("ccache.conf"),
+      Equals("ccache.conf:1: syntax error: missing '}' after \"foo\""));
+    // Other cases tested in test_util.c.
+  }
+
+  SECTION("empty umask")
+  {
+    util::write_file("ccache.conf", "umask = ");
+    CHECK(config.update_from_file("ccache.conf"));
+    CHECK(config.umask() == std::numeric_limits<uint32_t>::max());
+  }
+
+  SECTION("invalid size")
+  {
+    util::write_file("ccache.conf", "max_size = foo");
+    REQUIRE_THROWS_WITH(config.update_from_file("ccache.conf"),
+                        Equals("ccache.conf:1: invalid size: \"foo\""));
+    // Other cases tested in test_util.c.
+  }
+
+  SECTION("invalid sloppiness")
+  {
+    util::write_file("ccache.conf", "sloppiness = file_macro, foo");
+    REQUIRE_THROWS_WITH(config.update_from_file("ccache.conf"),
+                        Equals("ccache.conf:1: unknown sloppiness: \"foo\""));
+  }
+
+  SECTION("invalid unsigned")
+  {
+    util::write_file("ccache.conf", "max_files =");
+    REQUIRE_THROWS_WITH(
+      config.update_from_file("ccache.conf"),
+      Equals("ccache.conf:1: invalid unsigned integer: \"\""));
+
+    util::write_file("ccache.conf", "max_files = -42");
+    REQUIRE_THROWS_WITH(
+      config.update_from_file("ccache.conf"),
+      Equals("ccache.conf:1: invalid unsigned integer: \"-42\""));
+
+    util::write_file("ccache.conf", "max_files = foo");
+    REQUIRE_THROWS_WITH(
+      config.update_from_file("ccache.conf"),
+      Equals("ccache.conf:1: invalid unsigned integer: \"foo\""));
+  }
+
+  SECTION("missing file")
+  {
+    CHECK(!config.update_from_file("ccache.conf"));
+  }
+
+  SECTION("relative base dir")
+  {
+    util::write_file("ccache.conf", "base_dir = relative/path");
+    REQUIRE_THROWS_WITH(
+      config.update_from_file("ccache.conf"),
+      Equals("ccache.conf:1: not an absolute path: \"relative/path\""));
+
+    util::write_file("ccache.conf", "base_dir =");
+    CHECK(config.update_from_file("ccache.conf"));
+  }
+
+  SECTION("bad dir levels")
+  {
+    util::write_file("ccache.conf", "cache_dir_levels = 0");
+    try {
+      config.update_from_file("ccache.conf");
+      CHECK(false);
+    } catch (const Error& e) {
+      CHECK(std::string(e.what())
+            == "ccache.conf:1: cache directory levels must be between 1 and 8");
+    }
+
+    util::write_file("ccache.conf", "cache_dir_levels = 9");
+    try {
+      config.update_from_file("ccache.conf");
+      CHECK(false);
+    } catch (const Error& e) {
+      CHECK(std::string(e.what())
+            == "ccache.conf:1: cache directory levels must be between 1 and 8");
+    }
+  }
+}
+
+TEST_CASE("Config::update_from_environment")
+{
+  Config config;
+
+  x_setenv("CCACHE_COMPRESS", "1");
+  config.update_from_environment();
+  CHECK(config.compression());
+
+  x_unsetenv("CCACHE_COMPRESS");
+
+  x_setenv("CCACHE_NOCOMPRESS", "1");
+  config.update_from_environment();
+  CHECK(!config.compression());
+}
+
+TEST_CASE("Config::set_value_in_file")
+{
+  SECTION("set new value")
+  {
+    util::write_file("ccache.conf", "path = vanilla\n");
+    Config::set_value_in_file("ccache.conf", "compiler", "chocolate");
+    std::string content = util::read_file("ccache.conf");
+    CHECK(content == "path = vanilla\ncompiler = chocolate\n");
+  }
+
+  SECTION("existing value")
+  {
+    util::write_file("ccache.conf", "path = chocolate\nstats = chocolate\n");
+    Config::set_value_in_file("ccache.conf", "path", "vanilla");
+    std::string content = util::read_file("ccache.conf");
+    CHECK(content == "path = vanilla\nstats = chocolate\n");
+  }
+
+  SECTION("unknown option")
+  {
+    util::write_file("ccache.conf", "path = chocolate\nstats = chocolate\n");
+    try {
+      Config::set_value_in_file("ccache.conf", "foo", "bar");
+      CHECK(false);
+    } catch (const Error& e) {
+      CHECK(std::string(e.what()) == "unknown configuration option \"foo\"");
+    }
+
+    std::string content = util::read_file("ccache.conf");
+    CHECK(content == "path = chocolate\nstats = chocolate\n");
+  }
+}
+
+TEST_CASE("Config::get_string_value")
+{
+  Config config;
+
+  SECTION("base case")
+  {
+    config.set_max_files(42);
+    CHECK(config.get_string_value("max_files") == "42");
+  }
+
+  SECTION("unknown key")
+  {
+    try {
+      config.get_string_value("foo");
+      CHECK(false);
+    } catch (const Error& e) {
+      CHECK(std::string(e.what()) == "unknown configuration option \"foo\"");
+    }
+  }
+}
+
+TEST_CASE("Config::visit_items")
+{
+  util::write_file(
+    "test.conf",
+    "base_dir = /bd\n"
+    "cache_dir = cd\n"
+    "cache_dir_levels = 7\n"
+    "compiler = c\n"
+    "compiler_check = cc\n"
+    "compression = true\n"
+    "compression_level = 8\n"
+    "cpp_extension = ce\n"
+    "debug = false\n"
+    "depend_mode = true\n"
+    "direct_mode = false\n"
+    "disable = true\n"
+    "extra_files_to_hash = efth\n"
+    "file_clone = true\n"
+    "hard_link = true\n"
+    "hash_dir = false\n"
+    "ignore_headers_in_manifest = ihim\n"
+    "keep_comments_cpp = true\n"
+    "limit_multiple = 0.0\n"
+    "log_file = lf\n"
+    "max_files = 4711\n"
+    "max_size = 98.7M\n"
+    "path = p\n"
+    "pch_external_checksum = true\n"
+    "prefix_command = pc\n"
+    "prefix_command_cpp = pcc\n"
+    "read_only = true\n"
+    "read_only_direct = true\n"
+    "recache = true\n"
+    "run_second_cpp = false\n"
+    "sloppiness = file_macro, include_file_mtime, include_file_ctime,"
+    " time_macros, file_stat_matches, file_stat_matches_ctime, pch_defines,"
+    " system_headers, clang_index_store\n"
+    "stats = false\n"
+    "temporary_dir = td\n"
+    "umask = 022\n"
+    "unify = true\n");
+
+  Config config;
+  config.update_from_file("test.conf");
+
+  std::vector<std::string> received_items;
+
+  config.visit_items([&](const std::string& key,
+                         const std::string& value,
+                         const std::string& origin) {
+    received_items.push_back(fmt::format("({}) {} = {}", origin, key, value));
+  });
+
+  std::vector<std::string> expected = {
+    "(test.conf) base_dir = /bd",
+    "(test.conf) cache_dir = cd",
+    "(test.conf) cache_dir_levels = 7",
+    "(test.conf) compiler = c",
+    "(test.conf) compiler_check = cc",
+    "(test.conf) compression = true",
+    "(test.conf) compression_level = 8",
+    "(test.conf) cpp_extension = ce",
+    "(test.conf) debug = false",
+    "(test.conf) depend_mode = true",
+    "(test.conf) direct_mode = false",
+    "(test.conf) disable = true",
+    "(test.conf) extra_files_to_hash = efth",
+    "(test.conf) file_clone = true",
+    "(test.conf) hard_link = true",
+    "(test.conf) hash_dir = false",
+    "(test.conf) ignore_headers_in_manifest = ihim",
+    "(test.conf) keep_comments_cpp = true",
+    "(test.conf) limit_multiple = 0.0",
+    "(test.conf) log_file = lf",
+    "(test.conf) max_files = 4711",
+    "(test.conf) max_size = 98.7M",
+    "(test.conf) path = p",
+    "(test.conf) pch_external_checksum = true",
+    "(test.conf) prefix_command = pc",
+    "(test.conf) prefix_command_cpp = pcc",
+    "(test.conf) read_only = true",
+    "(test.conf) read_only_direct = true",
+    "(test.conf) recache = true",
+    "(test.conf) run_second_cpp = false",
+    "(test.conf) sloppiness = file_macro, include_file_mtime,"
+    " include_file_ctime, time_macros, pch_defines, file_stat_matches,"
+    " file_stat_matches_ctime, system_headers, clang_index_store",
+    "(test.conf) stats = false",
+    "(test.conf) temporary_dir = td",
+    "(test.conf) umask = 022",
+    "(test.conf) unify = true",
+  };
+
+  REQUIRE(received_items.size() == expected.size());
+  for (size_t i = 0; i < expected.size(); ++i) {
+    CHECK(received_items[i] == expected[i]);
+  }
+}
+
+// TODO Test that values in k_env_variable_table map to keys in
+//   k_config_item_table.;
index 6f46cc7c64700c17cb318d18895e37eee9d46c26..c42a4895b8ef2b488d9b9902821db0c5bc312ee6 100644 (file)
 
 // This file contains tests for the processing of compiler arguments.
 
+#include "../src/Config.hpp"
 #include "../src/ccache.hpp"
-#include "../src/conf.hpp"
 #include "framework.hpp"
 #include "util.hpp"
 
-extern struct conf* conf;
-
-static char*
-get_root(void)
+static std::string
+get_root()
 {
 #ifndef _WIN32
-  return x_strdup("/");
+  return "/";
 #else
   char volume[4]; // "C:\"
   GetVolumePathName(get_cwd(), volume, sizeof(volume));
-  return x_strdup(volume);
+  return volume;
 #endif
 }
 
@@ -124,7 +122,7 @@ TEST(cpp_only_flags_to_preprocessor_if_run_second_cpp_is_false)
   struct args *act_cpp = NULL, *act_cc = NULL;
   create_file("foo.c", "");
 
-  conf->run_second_cpp = false;
+  g_config.set_run_second_cpp(false);
   CHECK(cc_process_args(orig, &act_cpp, &act_cc));
   CHECK_ARGS_EQ_FREE12(exp_cpp, act_cpp);
   CHECK_ARGS_EQ_FREE12(exp_cc, act_cc);
@@ -150,7 +148,7 @@ TEST(cpp_only_flags_to_preprocessor_and_compiler_if_run_second_cpp_is_true)
   struct args *act_cpp = NULL, *act_cc = NULL;
   create_file("foo.c", "");
 
-  conf->run_second_cpp = true;
+  g_config.set_run_second_cpp(true);
   CHECK(cc_process_args(orig, &act_cpp, &act_cc));
   CHECK_ARGS_EQ_FREE12(exp_cpp, act_cpp);
   CHECK_ARGS_EQ_FREE12(exp_cc, act_cc);
@@ -183,8 +181,7 @@ TEST(sysroot_should_be_rewritten_if_basedir_is_used)
   struct args *act_cpp = NULL, *act_cc = NULL;
 
   create_file("foo.c", "");
-  free(conf->base_dir);
-  conf->base_dir = get_root();
+  g_config.set_base_dir(get_root());
   current_working_dir = get_cwd();
   arg_string = format("cc --sysroot=%s/foo/bar -c foo.c", current_working_dir);
   orig = args_init_from_string(arg_string);
@@ -206,8 +203,7 @@ TEST(sysroot_with_separate_argument_should_be_rewritten_if_basedir_is_used)
   struct args *act_cpp = NULL, *act_cc = NULL;
 
   create_file("foo.c", "");
-  free(conf->base_dir);
-  conf->base_dir = get_root();
+  g_config.set_base_dir(get_root());
   current_working_dir = get_cwd();
   arg_string = format("cc --sysroot %s/foo -c foo.c", current_working_dir);
   orig = args_init_from_string(arg_string);
@@ -392,8 +388,7 @@ TEST(isystem_flag_with_separate_arg_should_be_rewritten_if_basedir_is_used)
   struct args *act_cpp = NULL, *act_cc = NULL;
 
   create_file("foo.c", "");
-  free(conf->base_dir);
-  conf->base_dir = get_root();
+  g_config.set_base_dir(get_root());
   current_working_dir = get_cwd();
   arg_string = format("cc -isystem %s/foo -c foo.c", current_working_dir);
   orig = args_init_from_string(arg_string);
@@ -416,8 +411,7 @@ TEST(isystem_flag_with_concat_arg_should_be_rewritten_if_basedir_is_used)
   struct args *act_cpp = NULL, *act_cc = NULL;
 
   create_file("foo.c", "");
-  free(conf->base_dir);
-  conf->base_dir = x_strdup("/"); // posix
+  g_config.set_base_dir("/"); // posix
   current_working_dir = get_cwd();
   // Windows path doesn't work concatenated.
   cwd = get_posix_path(current_working_dir);
@@ -443,8 +437,7 @@ TEST(I_flag_with_concat_arg_should_be_rewritten_if_basedir_is_used)
   struct args *act_cpp = NULL, *act_cc = NULL;
 
   create_file("foo.c", "");
-  free(conf->base_dir);
-  conf->base_dir = x_strdup("/"); // posix
+  g_config.set_base_dir(x_strdup("/")); // posix
   current_working_dir = get_cwd();
   // Windows path doesn't work concatenated.
   cwd = get_posix_path(current_working_dir);
diff --git a/unittest/test_conf.cpp b/unittest/test_conf.cpp
deleted file mode 100644 (file)
index d4c10bc..0000000
+++ /dev/null
@@ -1,562 +0,0 @@
-// Copyright (C) 2011-2019 Joel Rosdahl and other contributors
-//
-// See doc/AUTHORS.adoc for a complete list of contributors.
-//
-// 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 "../src/conf.hpp"
-#include "framework.hpp"
-#include "util.hpp"
-
-#define N_CONFIG_ITEMS 35
-static struct
-{
-  char* descr;
-  char* origin;
-} received_conf_items[N_CONFIG_ITEMS];
-static size_t n_received_conf_items = 0;
-
-static char USER_ENV[] = "USER=rabbit";
-static char COMPRESS_1_ENV[] = "CCACHE_COMPRESS=1";
-static char NOCOMPRESS_1_ENV[] = "CCACHE_NOCOMPRESS=1";
-
-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 = x_strdup(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);
-    free(received_conf_items[n_received_conf_items].origin);
-  }
-}
-
-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(0, conf->compression_level);
-  CHECK_STR_EQ("", conf->cpp_extension);
-  CHECK(!conf->debug);
-  CHECK(!conf->depend_mode);
-  CHECK(conf->direct_mode);
-  CHECK(!conf->disable);
-  CHECK_STR_EQ("", conf->extra_files_to_hash);
-  CHECK(!conf->file_clone);
-  CHECK(!conf->hard_link);
-  CHECK(conf->hash_dir);
-  CHECK_STR_EQ("", conf->ignore_headers_in_manifest);
-  CHECK(!conf->keep_comments_cpp);
-  CHECK_DOUBLE_EQ(0.8, conf->limit_multiple);
-  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(!conf->pch_external_checksum);
-  CHECK_STR_EQ("", conf->prefix_command);
-  CHECK_STR_EQ("", conf->prefix_command_cpp);
-  CHECK(!conf->read_only);
-  CHECK(!conf->read_only_direct);
-  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_ENV);
-  user = getenv("USER");
-  CHECK_STR_EQ("rabbit", user);
-  create_file(
-    "ccache.conf",
-#ifndef _WIN32
-    "base_dir =  /$USER/foo/${USER} \n"
-#else
-    "base_dir = C:/$USER/foo/${USER}\n"
-#endif
-    "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=false\n"
-    "compression_level= 2\n"
-    "cpp_extension = .foo\n"
-    "depend_mode = true\n"
-    "direct_mode = false\n"
-    "disable = true\n"
-    "extra_files_to_hash = a:b c:$USER\n"
-    "file_clone = true\n"
-    "hard_link = true\n"
-    "hash_dir = false\n"
-    "ignore_headers_in_manifest = a:b/c\n"
-    "keep_comments_cpp = true\n"
-    "limit_multiple = 1.0\n"
-    "log_file = $USER${USER} \n"
-    "max_files = 17\n"
-    "max_size = 123M\n"
-    "path = $USER.x\n"
-    "pch_external_checksum = true\n"
-    "prefix_command = x$USER\n"
-    "prefix_command_cpp = y\n"
-    "read_only = true\n"
-    "read_only_direct = true\n"
-    "recache = true\n"
-    "run_second_cpp = false\n"
-    "sloppiness =     file_macro   ,time_macros,  "
-    "include_file_mtime,include_file_ctime,file_stat_matches,file_stat_"
-    "matches_ctime,pch_defines ,  "
-    "no_system_headers,system_headers,clang_index_store\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);
-
-#ifndef _WIN32
-  CHECK_STR_EQ_FREE1(format("/%s/foo/%s", user, user), conf->base_dir);
-#else
-  CHECK_STR_EQ_FREE1(format("C:/%s/foo/%s", user, user), conf->base_dir);
-#endif
-  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->depend_mode);
-  CHECK(!conf->direct_mode);
-  CHECK(conf->disable);
-  CHECK_STR_EQ_FREE1(format("a:b c:%s", user), conf->extra_files_to_hash);
-  CHECK(conf->file_clone);
-  CHECK(conf->hard_link);
-  CHECK(!conf->hash_dir);
-  CHECK_STR_EQ("a:b/c", conf->ignore_headers_in_manifest);
-  CHECK(conf->keep_comments_cpp);
-  CHECK_DOUBLE_EQ(1.0, conf->limit_multiple);
-  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(conf->pch_external_checksum);
-  CHECK_STR_EQ_FREE1(format("x%s", user), conf->prefix_command);
-  CHECK_STR_EQ("y", conf->prefix_command_cpp);
-  CHECK(conf->read_only);
-  CHECK(conf->read_only_direct);
-  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_FILE_STAT_MATCHES_CTIME
-                 | SLOPPY_SYSTEM_HEADERS | SLOPPY_PCH_DEFINES
-                 | SLOPPY_CLANG_INDEX_STORE,
-               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_INT_EQ(errno, 0);
-  CHECK_STR_EQ_FREE2("ccache.conf:1: missing equal sign", errmsg);
-  conf_free(conf);
-}
-
-TEST(conf_read_with_unknown_config_key)
-{
-  struct conf* conf = conf_create();
-  char* errmsg;
-  create_file("ccache.conf", "# Comment\nfoo = bar");
-  CHECK(conf_read(conf, "ccache.conf", &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_INT_EQ(errno, 0);
-  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_INT_EQ(errno, 0);
-  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_INT_EQ(errno, 0);
-  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_INT_EQ(errno, 0);
-  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_INT_EQ(errno, 0);
-  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_INT_EQ(errno, 0);
-  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(conf_read_missing_config_file)
-{
-  struct conf* conf = conf_create();
-  char* errmsg;
-  CHECK(!conf_read(conf, "ccache.conf", &errmsg));
-  CHECK_INT_EQ(errno, ENOENT);
-  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(COMPRESS_1_ENV);
-  CHECK(conf_update_from_environment(conf, &errmsg));
-  CHECK(conf->compression);
-
-  x_unsetenv("CCACHE_COMPRESS");
-  putenv(NOCOMPRESS_1_ENV);
-  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");
-  CHECKM(
-    conf_set_value_in_file("ccache.conf", "compiler", "chocolate", &errmsg),
-    errmsg);
-  data = read_text_file("ccache.conf", 0);
-  CHECK(data);
-  CHECK_STR_EQ_FREE2("path = vanilla\ncompiler = chocolate\n", data);
-}
-
-TEST(conf_set_existing_value)
-{
-  char* errmsg;
-  char* data;
-
-  create_file("ccache.conf", "path = chocolate\nstats = chocolate\n");
-  CHECKM(conf_set_value_in_file("ccache.conf", "path", "vanilla", &errmsg),
-         errmsg);
-  data = read_text_file("ccache.conf", 0);
-  CHECK(data);
-  CHECK_STR_EQ_FREE2("path = vanilla\nstats = chocolate\n", data);
-}
-
-TEST(conf_set_unknown_option)
-{
-  char* errmsg;
-  char* data;
-
-  create_file("ccache.conf", "path = chocolate\nstats = chocolate\n");
-  CHECKM(!conf_set_value_in_file("ccache.conf", "foo", "bar", &errmsg), errmsg);
-  CHECK_STR_EQ_FREE2("unknown configuration option \"foo\"", errmsg);
-
-  data = read_text_file("ccache.conf", 0);
-  CHECK(data);
-  CHECK_STR_EQ_FREE2("path = chocolate\nstats = chocolate\n", data);
-}
-
-TEST(conf_print_existing_value)
-{
-  struct conf* conf = conf_create();
-  conf->max_files = 42;
-  char* errmsg;
-  {
-    FILE* log = fopen("log", "w");
-    CHECK(log);
-    CHECK(conf_print_value(conf, "max_files", log, &errmsg));
-    fclose(log);
-  }
-  {
-    FILE* log = fopen("log", "r");
-    CHECK(log);
-    char buf[100];
-    CHECK(fgets(buf, sizeof(buf), log));
-    CHECK_STR_EQ("42\n", buf);
-    fclose(log);
-  }
-  conf_free(conf);
-}
-
-TEST(conf_print_unknown_value)
-{
-  struct conf* conf = conf_create();
-  char* errmsg;
-  {
-    FILE* log = fopen("log", "w");
-    CHECK(log);
-    CHECK(!conf_print_value(conf, "foo", log, &errmsg));
-    CHECK_STR_EQ_FREE2("unknown configuration option \"foo\"", errmsg);
-    fclose(log);
-  }
-  {
-    FILE* log = fopen("log", "r");
-    CHECK(log);
-    char buf[100];
-    CHECK(!fgets(buf, sizeof(buf), log));
-    fclose(log);
-  }
-  conf_free(conf);
-}
-
-TEST(conf_print_items)
-{
-  struct conf* conf = conf_create();
-  conf->base_dir = x_strdup("bd");
-  conf->cache_dir = x_strdup("cd");
-  conf->cache_dir_levels = 7;
-  conf->compiler = x_strdup("c");
-  conf->compiler_check = x_strdup("cc");
-  conf->compression = true;
-  conf->compression_level = 8;
-  conf->cpp_extension = x_strdup("ce");
-  conf->debug = false;
-  conf->depend_mode = true;
-  conf->direct_mode = false;
-  conf->disable = true;
-  conf->extra_files_to_hash = x_strdup("efth");
-  conf->file_clone = true;
-  conf->hard_link = true;
-  conf->hash_dir = false;
-  conf->ignore_headers_in_manifest = x_strdup("ihim");
-  conf->keep_comments_cpp = true;
-  conf->limit_multiple = 0.0;
-  conf->log_file = x_strdup("lf");
-  conf->max_files = 4711;
-  conf->max_size = 98.7 * 1000 * 1000;
-  conf->path = x_strdup("p");
-  conf->pch_external_checksum = true;
-  conf->prefix_command = x_strdup("pc");
-  conf->prefix_command_cpp = x_strdup("pcc");
-  conf->read_only = true;
-  conf->read_only_direct = true;
-  conf->recache = true;
-  conf->run_second_cpp = false;
-  conf->sloppiness = SLOPPY_FILE_MACRO | SLOPPY_INCLUDE_FILE_MTIME
-                     | SLOPPY_INCLUDE_FILE_CTIME | SLOPPY_TIME_MACROS
-                     | SLOPPY_FILE_STAT_MATCHES | SLOPPY_FILE_STAT_MATCHES_CTIME
-                     | SLOPPY_PCH_DEFINES | SLOPPY_SYSTEM_HEADERS
-                     | SLOPPY_CLANG_INDEX_STORE;
-  conf->stats = false;
-  conf->temporary_dir = x_strdup("td");
-  conf->umask = 022;
-  conf->unify = true;
-
-  conf->item_origins =
-    static_cast<const char**>(x_malloc(N_CONFIG_ITEMS * sizeof(char*)));
-  for (size_t i = 0; i < N_CONFIG_ITEMS; ++i) {
-#ifndef __MINGW32__
-    conf->item_origins[i] = format("origin%zu", i);
-#else
-    conf->item_origins[i] = format("origin%u", (unsigned)i);
-#endif
-  }
-
-  size_t n = 0;
-  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("debug = false", received_conf_items[n++].descr);
-  CHECK_STR_EQ("depend_mode = true", 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("file_clone = true", received_conf_items[n++].descr);
-  CHECK_STR_EQ("hard_link = true", received_conf_items[n++].descr);
-  CHECK_STR_EQ("hash_dir = false", received_conf_items[n++].descr);
-  CHECK_STR_EQ("ignore_headers_in_manifest = ihim",
-               received_conf_items[n++].descr);
-  CHECK_STR_EQ("keep_comments_cpp = true", received_conf_items[n++].descr);
-  CHECK_STR_EQ("limit_multiple = 0.0", 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("pch_external_checksum = true", received_conf_items[n++].descr);
-  CHECK_STR_EQ("prefix_command = pc", received_conf_items[n++].descr);
-  CHECK_STR_EQ("prefix_command_cpp = pcc", received_conf_items[n++].descr);
-  CHECK_STR_EQ("read_only = true", received_conf_items[n++].descr);
-  CHECK_STR_EQ("read_only_direct = true", received_conf_items[n++].descr);
-  CHECK_STR_EQ("recache = true", received_conf_items[n++].descr);
-  CHECK_STR_EQ("run_second_cpp = false", received_conf_items[n++].descr);
-  CHECK_STR_EQ(
-    "sloppiness = file_macro, include_file_mtime,"
-    " include_file_ctime, time_macros, pch_defines,"
-    " file_stat_matches, file_stat_matches_ctime, system_headers,"
-    " clang_index_store",
-    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 (size_t i = 0; i < N_CONFIG_ITEMS; ++i) {
-#ifndef __MINGW32__
-    char* expected = format("origin%zu", i);
-#else
-    char* expected = format("origin%u", (unsigned)i);
-#endif
-    CHECK_STR_EQ_FREE1(expected, received_conf_items[i].origin);
-  }
-
-  free_received_conf_items();
-  conf_free(conf);
-}
-
-TEST_SUITE_END
index 06fac0d66ffa0cc0d418f16d7decd954dd8e39a6..d0311fbc11338137c42f20579c090cfa828465f3 100644 (file)
@@ -21,8 +21,6 @@
 #include "../src/ccache.hpp"
 #include "framework.hpp"
 
-static char FOO_ENV[] = "FOO=bar";
-
 TEST_SUITE(legacy_util)
 
 TEST(x_basename)
@@ -93,7 +91,7 @@ TEST(subst_env_in_string)
 {
   char* errmsg;
 
-  putenv(FOO_ENV);
+  x_setenv("FOO", "bar");
 
   CHECK_STR_EQ_FREE2("bar", subst_env_in_string("$FOO", &errmsg));
   CHECK(!errmsg);
index fcd4d1527e209b3db2fda9ea9696bda3c2fad2dc..588a1ba474766c3ad067ed2f27de446bd7b1aae8 100644 (file)
@@ -18,7 +18,7 @@
 
 #include "../src/util.hpp"
 
-#include "third_party/catch.hpp"
+#include <catch.hpp>
 
 TEST_CASE("util::read_file and util::write_file")
 {