]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
Use XDG base directories by default
authorJoel Rosdahl <joel@rosdahl.net>
Sat, 8 Aug 2020 18:41:18 +0000 (20:41 +0200)
committerJoel Rosdahl <joel@rosdahl.net>
Sun, 9 Aug 2020 18:32:11 +0000 (20:32 +0200)
ccache will now follow the XDG Base Directory Specification
(https://specifications.freedesktop.org/basedir-spec/) by default so
that:

- the cache directory is $XDG_CACHE_HOME/ccache (or $HOME/.cache/ccache
  if XDG_CACHE_HOME is not set), and
- the (primary) configuration file is
  $XDG_CONFIG_HOME/ccache/ccache.conf (or
  $HOME/.config/ccache/ccache.conf if XDG_CONFIG_HOME is not set).

On macOS, the fallback cache directory is $HOME/Library/Caches/ccache
and the fallback configuration file is
$HOME/Library/Preferences/ccache/ccache.conf. On Windows, the fallback
cache directory is %APPDATA%/ccache and the fallback configuration file
is %APPDATA%/ccache/ccache.conf.

Exceptions:

1. If CCACHE_CONFIGPATH is set, use that path for the configuration
   file.
2. If CCACHE_DIR is set (or ccache_dir in the secondary configuration),
   use that path for the cache directory and $CCACHE_DIR/ccache.conf for
   the configuration file.
3. If the legacy $HOME/.ccache directory exists, use that cache
   directory and $HOME/.ccache/ccache.conf for the configuration file.

Closes #191.

doc/MANUAL.adoc
src/Config.hpp
src/ccache.cpp
test/run
test/suites/upgrade.bash
unittest/test_Config.cpp
unittest/test_InodeCache.cpp

index c2d800de04d0370263e0a629fd463f5b4c27555d..5b866421bc4d8c35d88061b8e8bc0470d8381cc9 100644 (file)
@@ -241,15 +241,33 @@ file. The priority of configuration settings is as follows (where 1 is
 highest):
 
 1. Environment variables.
-2. The cache-specific configuration file *_<ccachedir>_/ccache.conf* (typically
-   *$HOME/.ccache/ccache.conf*).
-3. The system-wide configuration file *_<sysconfdir>_/ccache.conf* (typically
-   */etc/ccache.conf* or */usr/local/etc/ccache.conf*).
+2. The primary (cache-specific) configuration file (see below).
+3. The secondary (system-wide read-only) configuration file
+   *_<sysconfdir>_/ccache.conf* (typically */etc/ccache.conf* or
+    */usr/local/etc/ccache.conf*).
 4. Compile-time defaults.
 
-As a special case, if the environment variable *CCACHE_CONFIGPATH* is set,
-ccache reads configuration from the specified path instead of the default
-paths.
+As a special case, if the the environment variable *CCACHE_CONFIGPATH* is set
+it specifies the primary configuration file and the secondary (system-wide)
+configuration file won't be read.
+
+
+Location of the primary configuration file
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The location of the primary (cache-specific) configuration is determined like
+this:
+
+1. If *CCACHE_CONFIGPATH* is set, use that path.
+2. Otherwise, if <<config_ccache_dir,*ccache_dir*>> (*CCACHE_DIR*) is set then
+   use *<ccache_dir>/ccache.conf*.
+3. Otherwise, if there is a legacy *$HOME/.ccache* directory then use
+   *$HOME/.ccache/ccache.conf.
+4. Otherwise, if *XDG_CONFIG_HOME* is set then use
+   *$XDG_CONFIG_HOME/ccache/ccache.conf*.
+5. Otherwise, use *%APPDATA%/ccache/ccache.conf* (Windows),
+   *$HOME/Library/Preferences/ccache/ccache.conf (macOS) or
+   *$HOME/.config/ccache/ccache.conf* (other systems).
 
 
 Configuration file syntax
@@ -304,7 +322,12 @@ IN DIFFERENT DIRECTORIES>>.
 
     This setting specifies where ccache will keep its cached compiler outputs.
     It will only take effect if set in the system-wide configuration file or as
-    an environment variable. The default is *$HOME/.ccache*.
+    an environment variable. The default is *$XDG_CACHE_HOME/ccache* if
+    *XDG_CACHE_HOME* is set, otherwise *$HOME/.cache/ccache*. Exception: If the
+    legacy directory *$HOME/.ccache* exists then that directory is the default.
+
+    See also <<_location_of_the_primary_configuration_file,LOCATION OF THE
+    PRIMARY CONFIGURATION FILE>>.
 
 [[config_cache_dir_levels]] *cache_dir_levels* (*CCACHE_NLEVELS*)::
 
index 896e3de18abbca4cf001ec5bc0d3ef46e94e57df..cb7a0f84d03ff302db46197e857659966a82ddf2 100644 (file)
 
 class Config;
 
-class Config : NonCopyable
+class Config
 {
 public:
   Config() = default;
+  Config(Config&) = default;
+  Config& operator=(const Config&) = default;
 
   const std::string& base_dir() const;
   const std::string& cache_dir() const;
@@ -127,7 +129,7 @@ private:
   std::string m_secondary_config_path;
 
   std::string m_base_dir = "";
-  std::string m_cache_dir = Util::get_home_directory() + "/.ccache";
+  std::string m_cache_dir;
   uint32_t m_cache_dir_levels = 2;
   std::string m_compiler = "";
   std::string m_compiler_check = "mtime";
@@ -160,7 +162,7 @@ private:
   bool m_run_second_cpp = true;
   uint32_t m_sloppiness = 0;
   bool m_stats = true;
-  std::string m_temporary_dir = default_temporary_dir(m_cache_dir);
+  std::string m_temporary_dir;
   uint32_t m_umask = std::numeric_limits<uint32_t>::max(); // Don't set umask
 
   bool m_temporary_dir_configured_explicitly = false;
index d02fc4e6e5aa4db1add807f5ea8ff3f1b7ba6c7e..e3f333255fb97b781e2447bb2d1f8f7a68423148 100644 (file)
@@ -1755,46 +1755,103 @@ create_initial_config_file(Config& config)
   fclose(f);
 }
 
+static std::string
+default_cache_dir(const std::string& home_dir)
+{
+#ifdef _WIN32
+  return home_dir + "/ccache";
+#elif defined(__APPLE__)
+  return home_dir + "/Library/Caches/ccache";
+#else
+  return home_dir + "/.cache/ccache";
+#endif
+}
+
+static std::string
+default_config_dir(const std::string& home_dir)
+{
+#ifdef _WIN32
+  return home_dir + "/ccache";
+#elif defined(__APPLE__)
+  return home_dir + "/Library/Preferences/ccache";
+#else
+  return home_dir + "/.config/ccache";
+#endif
+}
+
 // Read config file(s), populate variables, create configuration file in cache
 // directory if missing, etc. Returns whether the primary configuration file
 // exists.
 static bool
 set_up_config(Config& config)
 {
-  const char* p = getenv("CCACHE_CONFIGPATH");
-  if (p) {
-    config.set_primary_config_path(p);
+  const std::string home_dir = Util::get_home_directory();
+  const std::string legacy_ccache_dir = home_dir + "/.ccache";
+  const bool legacy_ccache_dir_exists =
+    Stat::stat(legacy_ccache_dir).is_directory();
+  const char* const env_xdg_cache_home = getenv("XDG_CACHE_HOME");
+  const char* const env_xdg_config_home = getenv("XDG_CONFIG_HOME");
+
+  const char* env_ccache_configpath = getenv("CCACHE_CONFIGPATH");
+  if (env_ccache_configpath) {
+    config.set_primary_config_path(env_ccache_configpath);
   } else {
-    config.set_secondary_config_path(fmt::format("{}/ccache.conf", SYSCONFDIR));
+    // Only used for ccache tests:
+    const char* const env_ccache_configpath2 = getenv("CCACHE_CONFIGPATH2");
+
+    config.set_secondary_config_path(
+      env_ccache_configpath2 ? env_ccache_configpath2
+                             : fmt::format("{}/ccache.conf", SYSCONFDIR));
     MTR_BEGIN("config", "conf_read_secondary");
     // A missing config file in SYSCONFDIR is OK so don't check return value.
     config.update_from_file(config.secondary_config_path());
     MTR_END("config", "conf_read_secondary");
 
-    if (config.cache_dir().empty()) {
-      throw FatalError(
-        "configuration setting \"cache_dir\" must not be the empty string");
-    }
-    if ((p = getenv("CCACHE_DIR"))) {
-      config.set_cache_dir(p);
-    }
-    if (config.cache_dir().empty()) {
-      throw FatalError("CCACHE_DIR must not be the empty string");
+    const char* const env_ccache_dir = getenv("CCACHE_DIR");
+    std::string primary_config_dir;
+    if (env_ccache_dir && *env_ccache_dir) {
+      primary_config_dir = env_ccache_dir;
+    } else if (!config.cache_dir().empty() && !env_ccache_dir) {
+      primary_config_dir = config.cache_dir();
+    } else if (legacy_ccache_dir_exists) {
+      primary_config_dir = legacy_ccache_dir;
+    } else if (env_xdg_config_home) {
+      primary_config_dir = fmt::format("{}/ccache", env_xdg_config_home);
+    } else {
+      primary_config_dir = default_config_dir(home_dir);
     }
-
-    config.set_primary_config_path(
-      fmt::format("{}/ccache.conf", config.cache_dir()));
+    config.set_primary_config_path(primary_config_dir + "/ccache.conf");
   }
 
+  const std::string& cache_dir_before_primary_config = config.cache_dir();
+
   MTR_BEGIN("config", "conf_read_primary");
-  bool primary_config_exists =
+  const bool primary_config_exists =
     config.update_from_file(config.primary_config_path());
   MTR_END("config", "conf_read_primary");
 
+  // Ignore cache_dir set in primary config.
+  config.set_cache_dir(cache_dir_before_primary_config);
+
   MTR_BEGIN("config", "conf_update_from_environment");
   config.update_from_environment();
+  // (config.cache_dir is set above if CCACHE_DIR is set.)
   MTR_END("config", "conf_update_from_environment");
 
+  if (config.cache_dir().empty()) {
+    if (legacy_ccache_dir_exists) {
+      config.set_cache_dir(legacy_ccache_dir);
+    } else if (env_xdg_cache_home) {
+      config.set_cache_dir(fmt::format("{}/ccache", env_xdg_cache_home));
+    } else {
+      config.set_cache_dir(default_cache_dir(home_dir));
+    }
+  }
+  // else: cache_dir was set explicitly via environment or via secondary config.
+
+  // We have now determined config.cache_dir and populated the rest of config in
+  // prio order (1. environment, 2. primary config, 3. secondary config).
+
   return primary_config_exists;
 }
 
@@ -2388,6 +2445,7 @@ handle_main_options(int argc, const char* const* argv)
 
     // Some of the above switches might have changed config settings, so run the
     // setup again.
+    ctx.config = Config();
     set_up_config(ctx.config);
   }
 
index c0214659ae5c07f2b4ed32cc986f61bb9c3dea97..86191129ea3c15a861688a9a9ca72282bdc9f219 100755 (executable)
--- a/test/run
+++ b/test/run
@@ -321,6 +321,8 @@ $(env | sed -n 's/^\(CCACHE_[A-Z0-9_]*\)=.*$/\1/p')
 EOF
     unset GCC_COLORS
     unset TERM
+    unset XDG_CACHE_HOME
+    unset XDG_CONFIG_HOME
 
     export CCACHE_DETECT_SHEBANG=1
     export CCACHE_DIR=$ABS_TESTDIR/.ccache
index 81fd133640c2aeaac49d1bb1fbf7c7f62d08ff38..bbe636568f4653ee45d0c4aba07f424547da9db0 100644 (file)
@@ -1,4 +1,5 @@
 SUITE_upgrade() {
+    # -------------------------------------------------------------------------
     TEST "Keep maxfiles and maxsize settings"
 
     rm -f $CCACHE_CONFIGPATH
@@ -6,4 +7,121 @@ SUITE_upgrade() {
     echo "0 0 0 0 0 0 0 0 0 0 0 0 0 2000 131072" >$CCACHE_DIR/0/stats
     expect_stat 'max files' 32000
     expect_stat 'max cache size' '2.1 GB'
+
+    # -------------------------------------------------------------------------
+    TEST "Default cache config/directory without XDG variables"
+
+    unset CCACHE_CONFIGPATH
+    unset CCACHE_DIR
+    export HOME=/home/user
+
+    if $HOST_OS_APPLE; then
+        expected=$HOME/Library/Caches/ccache
+    else
+        expected=$HOME/.cache/ccache
+    fi
+    actual=$($CCACHE -s | sed -n 's/^cache directory *//p')
+    if [ "$actual" != "$expected" ]; then
+        test_failed "expected cache directory $expected, actual $actual"
+    fi
+
+    if $HOST_OS_APPLE; then
+        expected=$HOME/Library/Preferences/ccache/ccache.conf
+    else
+        expected=$HOME/.config/ccache/ccache.conf
+    fi
+    actual=$($CCACHE -s | sed -n 's/^primary config *//p')
+    if [ "$actual" != "$expected" ]; then
+        test_failed "expected primary config $expected actual $actual"
+    fi
+
+    # -------------------------------------------------------------------------
+    TEST "Default cache config/directory with XDG variables"
+
+    unset CCACHE_CONFIGPATH
+    unset CCACHE_DIR
+    export HOME=$PWD
+    export XDG_CACHE_HOME=/somewhere/cache
+    export XDG_CONFIG_HOME=/elsewhere/config
+
+    expected=$XDG_CACHE_HOME/ccache
+    actual=$($CCACHE -s | sed -n 's/^cache directory *//p')
+    if [ "$actual" != "$expected" ]; then
+        test_failed "expected cache directory $expected, actual $actual"
+    fi
+
+    expected=$XDG_CONFIG_HOME/ccache/ccache.conf
+    actual=$($CCACHE -s | sed -n 's/^primary config *//p')
+    if [ "$actual" != "$expected" ]; then
+        test_failed "expected primary config $expected actual $actual"
+    fi
+
+    # -------------------------------------------------------------------------
+    TEST "Cache config/directory with XDG variables and legacy directory"
+
+    unset CCACHE_CONFIGPATH
+    unset CCACHE_DIR
+    export HOME=$PWD
+    export XDG_CACHE_HOME=/somewhere/cache
+    export XDG_CONFIG_HOME=/elsewhere/config
+    mkdir $HOME/.ccache
+
+    expected=$HOME/.ccache
+    actual=$($CCACHE -s | sed -n 's/^cache directory *//p')
+    if [ "$actual" != "$expected" ]; then
+        test_failed "expected cache directory $expected, actual $actual"
+    fi
+
+    expected=$HOME/.ccache/ccache.conf
+    actual=$($CCACHE -s | sed -n 's/^primary config *//p')
+    if [ "$actual" != "$expected" ]; then
+        test_failed "expected primary config $expected actual $actual"
+    fi
+
+    # -------------------------------------------------------------------------
+    TEST "Cache config/directory with XDG variables and CCACHE_DIR"
+
+    unset CCACHE_CONFIGPATH
+    export CCACHE_DIR=$PWD/test
+    export HOME=/home/user
+    export XDG_CACHE_HOME=/somewhere/cache
+    export XDG_CONFIG_HOME=/elsewhere/config
+
+    expected=$CCACHE_DIR
+    actual=$($CCACHE -s | sed -n 's/^cache directory *//p')
+    if [ "$actual" != "$expected" ]; then
+        test_failed "expected cache directory $expected, actual $actual"
+    fi
+
+    expected=$CCACHE_DIR/ccache.conf
+    actual=$($CCACHE -s | sed -n 's/^primary config *//p')
+    if [ "$actual" != "$expected" ]; then
+        test_failed "expected primary config $expected actual $actual"
+    fi
+
+    # -------------------------------------------------------------------------
+    TEST "Cache config/directory with empty CCACHE_DIR"
+
+    # Empty (but set) CCACHE_DIR means "use defaults" and should thus override
+    # cache_dir set in the secondary config.
+
+    unset CCACHE_CONFIGPATH
+    export CCACHE_CONFIGPATH2=$PWD/ccache.conf2
+    export HOME=/home/user
+    export XDG_CACHE_HOME=/somewhere/cache
+    export XDG_CONFIG_HOME=/elsewhere/config
+    export CCACHE_DIR= # Set but empty
+    echo 'cache_dir = /nowhere' > $CCACHE_CONFIGPATH2
+
+    expected=$XDG_CACHE_HOME/ccache
+    actual=$($CCACHE -s | sed -n 's/^cache directory *//p')
+    if [ "$actual" != "$expected" ]; then
+        test_failed "expected cache directory $expected, actual $actual"
+    fi
+
+    expected=$XDG_CONFIG_HOME/ccache/ccache.conf
+    actual=$($CCACHE -s | sed -n 's/^primary config *//p')
+    if [ "$actual" != "$expected" ]; then
+        test_failed "expected primary config $expected actual $actual"
+    fi
 }
index 7653dc2679e3fc150c00afb9741ad47e9b5d62c6..d5d4fbd30ccc61cf0ec6bca6ada032d953d5a9a1 100644 (file)
@@ -38,10 +38,8 @@ TEST_CASE("Config: default values")
 {
   Config config;
 
-  std::string expected_cache_dir = Util::get_home_directory() + "/.ccache";
-
   CHECK(config.base_dir().empty());
-  CHECK(config.cache_dir() == expected_cache_dir);
+  CHECK(config.cache_dir().empty()); // Set later
   CHECK(config.cache_dir_levels() == 2);
   CHECK(config.compiler().empty());
   CHECK(config.compiler_check() == "mtime");
@@ -73,16 +71,7 @@ TEST_CASE("Config: default values")
   CHECK(config.run_second_cpp());
   CHECK(config.sloppiness() == 0);
   CHECK(config.stats());
-#ifdef HAVE_GETEUID
-  if (Stat::stat(fmt::format("/run/user/{}", geteuid())).is_directory()) {
-    CHECK(config.temporary_dir()
-          == fmt::format("/run/user/{}/ccache-tmp", geteuid()));
-  } else {
-#endif
-    CHECK(config.temporary_dir() == expected_cache_dir + "/tmp");
-#ifdef HAVE_GETEUID
-  }
-#endif
+  CHECK(config.temporary_dir().empty()); // Set later
   CHECK(config.umask() == std::numeric_limits<uint32_t>::max());
 }
 
index 124d337c9c375b0809ee04a7f80f7cde92f00d49..1c8a15d6a6ff11af3c3d8d6c45a4e797a4c43b97 100644 (file)
@@ -29,6 +29,14 @@ using TestUtil::TestContext;
 
 namespace {
 
+void
+init(Context& ctx)
+{
+  ctx.config.set_debug(true);
+  ctx.config.set_inode_cache(true);
+  ctx.config.set_cache_dir(Util::get_home_directory());
+}
+
 bool
 put(const Context& ctx,
     const std::string& filename,
@@ -50,7 +58,7 @@ TEST_CASE("Test disabled")
   TestContext test_context;
 
   Context ctx;
-  ctx.config.set_debug(true);
+  init(ctx);
   ctx.config.set_inode_cache(false);
 
   Digest digest;
@@ -70,8 +78,7 @@ TEST_CASE("Test lookup nonexistent")
   TestContext test_context;
 
   Context ctx;
-  ctx.config.set_debug(true);
-  ctx.config.set_inode_cache(true);
+  init(ctx);
   ctx.inode_cache.drop();
   Util::write_file("a", "");
 
@@ -90,8 +97,7 @@ TEST_CASE("Test put and lookup")
   TestContext test_context;
 
   Context ctx;
-  ctx.config.set_debug(true);
-  ctx.config.set_inode_cache(true);
+  init(ctx);
   ctx.inode_cache.drop();
   Util::write_file("a", "a text");
 
@@ -132,8 +138,7 @@ TEST_CASE("Drop file")
   TestContext test_context;
 
   Context ctx;
-  ctx.config.set_debug(true);
-  ctx.config.set_inode_cache(true);
+  init(ctx);
 
   Digest digest;
 
@@ -149,9 +154,8 @@ TEST_CASE("Test content type")
   TestContext test_context;
 
   Context ctx;
-  ctx.config.set_debug(true);
+  init(ctx);
   ctx.inode_cache.drop();
-  ctx.config.set_inode_cache(true);
   Util::write_file("a", "a text");
   Digest binary_digest = Hash().hash("binary").digest();
   Digest code_digest = Hash().hash("code").digest();