From d0dea008d976761b7c2e292cd311f4681cd890d7 Mon Sep 17 00:00:00 2001 From: Joel Rosdahl Date: Sat, 8 Aug 2020 20:41:18 +0200 Subject: [PATCH] Use XDG base directories by default 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 | 39 +++++++++--- src/Config.hpp | 8 ++- src/ccache.cpp | 92 ++++++++++++++++++++++----- test/run | 2 + test/suites/upgrade.bash | 118 +++++++++++++++++++++++++++++++++++ unittest/test_Config.cpp | 15 +---- unittest/test_InodeCache.cpp | 22 ++++--- 7 files changed, 246 insertions(+), 50 deletions(-) diff --git a/doc/MANUAL.adoc b/doc/MANUAL.adoc index c2d800de0..5b866421b 100644 --- a/doc/MANUAL.adoc +++ b/doc/MANUAL.adoc @@ -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 *__/ccache.conf* (typically - *$HOME/.ccache/ccache.conf*). -3. The system-wide configuration file *__/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 + *__/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 <> (*CCACHE_DIR*) is set then + use */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*):: diff --git a/src/Config.hpp b/src/Config.hpp index 896e3de18..cb7a0f84d 100644 --- a/src/Config.hpp +++ b/src/Config.hpp @@ -32,10 +32,12 @@ 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::max(); // Don't set umask bool m_temporary_dir_configured_explicitly = false; diff --git a/src/ccache.cpp b/src/ccache.cpp index d02fc4e6e..e3f333255 100644 --- a/src/ccache.cpp +++ b/src/ccache.cpp @@ -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); } diff --git a/test/run b/test/run index c0214659a..86191129e 100755 --- 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 diff --git a/test/suites/upgrade.bash b/test/suites/upgrade.bash index 81fd13364..bbe636568 100644 --- a/test/suites/upgrade.bash +++ b/test/suites/upgrade.bash @@ -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 } diff --git a/unittest/test_Config.cpp b/unittest/test_Config.cpp index 7653dc267..d5d4fbd30 100644 --- a/unittest/test_Config.cpp +++ b/unittest/test_Config.cpp @@ -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::max()); } diff --git a/unittest/test_InodeCache.cpp b/unittest/test_InodeCache.cpp index 124d337c9..1c8a15d6a 100644 --- a/unittest/test_InodeCache.cpp +++ b/unittest/test_InodeCache.cpp @@ -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(); -- 2.47.3