Skip to content

Commit

Permalink
fix: Move Windows config and cache to LOCALAPPDATA
Browse files Browse the repository at this point in the history
Add a variadic template helper function Util::make_path(...) to
construct normalized native paths using std::filesystem.

Fix-up default cmake install directories and set system-wide config to
C:\ProgramData\ccache\ccache.conf on Windows.

Update manual to describe and clarify configuration file finding
behavior specific to Windows and cache directory finding behavior on all
systems.

Fix ccache#1023
Fix ccache#946

Signed-off-by: Rafael Kitover <rkitover@gmail.com>
  • Loading branch information
rkitover committed Jul 29, 2022
1 parent 61c70a6 commit 37d993f
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 37 deletions.
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ include(DefaultBuildType)
#
# Configuration
#
include(GNUInstallDirs)

include(InstallDirs)
include(GenerateConfigurationFile)
include(GenerateVersionFile)

Expand Down
4 changes: 4 additions & 0 deletions cmake/GenerateConfigurationFile.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -102,5 +102,9 @@ if(HAVE_SYS_MMAN_H AND HAVE_PTHREAD_MUTEXATTR_SETPSHARED)
set(INODE_CACHE_SUPPORTED 1)
endif()

# Escape backslashes in SYSCONFDIR for C.
file(TO_NATIVE_PATH "${CMAKE_INSTALL_FULL_SYSCONFDIR}" CONFIG_SYSCONFDIR_C_ESCAPED)
string(REPLACE "\\" "\\\\" CONFIG_SYSCONFDIR_C_ESCAPED "${CONFIG_SYSCONFDIR_C_ESCAPED}")

configure_file(${CMAKE_SOURCE_DIR}/cmake/config.h.in
${CMAKE_BINARY_DIR}/config.h @ONLY)
41 changes: 41 additions & 0 deletions cmake/InstallDirs.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
if(WIN32)
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(program_files "$ENV{ProgramFiles}")

# For 32 bit builds.
if(CMAKE_SIZEOF_VOID_P EQUAL 4 AND ENV{ProgramFiles\(x86\)})
set(program_files "$ENV{ProgramFiles\(x86\)}")
endif()

if(NOT program_files)
if(NOT CMAKE_SIZEOF_VOID_P EQUAL 4)
set(program_files "/Program Files")
else()
set(program_files "/Program Files (x86)")
endif()
endif()

file(TO_CMAKE_PATH "${program_files}/ccache" CMAKE_INSTALL_PREFIX)

set(CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" CACHE STRING "System-wide installation prefix" FORCE)
endif()

if(NOT CMAKE_INSTALL_SYSCONFDIR)
set(program_data "$ENV{ALLUSERSPROFILE}")

if(NOT program_data)
set(program_data "/ProgramData")
endif()

file(TO_CMAKE_PATH "${program_data}/ccache" CMAKE_INSTALL_SYSCONFDIR)

set(CMAKE_INSTALL_SYSCONFDIR "${CMAKE_INSTALL_SYSCONFDIR}" CACHE PATH "System-wide config file location" FORCE)
endif()

set(CMAKE_INSTALL_BINDIR "" CACHE PATH "executables subdirectory" FORCE)
set(CMAKE_INSTALL_SBINDIR "" CACHE PATH "system administration executables subdirectory" FORCE)
set(CMAKE_INSTALL_LIBEXECDIR "" CACHE PATH "dependent executables subdirectory" FORCE)
set(CMAKE_INSTALL_LIBDIR "" CACHE PATH "object libraries subdirectory" FORCE)
endif()

include(GNUInstallDirs)
2 changes: 1 addition & 1 deletion cmake/config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ typedef int pid_t;
# define ESTALE -1
#endif

#define SYSCONFDIR "@CMAKE_INSTALL_FULL_SYSCONFDIR@"
#define SYSCONFDIR "@CONFIG_SYSCONFDIR_C_ESCAPED@"

#cmakedefine INODE_CACHE_SUPPORTED

Expand Down
57 changes: 49 additions & 8 deletions doc/MANUAL.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -302,8 +302,8 @@ configuration file won't be read.

=== Location of the primary configuration file

The location of the primary (cache-specific) configuration is determined like
this:
The location of the primary (cache-specific) configuration is determined as
follows on non-Windows systems:

1. If `CCACHE_CONFIGPATH` is set, use that path.
2. Otherwise, if the environment variable `CCACHE_DIR` is set then use
Expand All @@ -314,10 +314,27 @@ this:
`$HOME/.ccache/ccache.conf`.
5. Otherwise, if `XDG_CONFIG_HOME` is set then use
`$XDG_CONFIG_HOME/ccache/ccache.conf`.
6. Otherwise, use `%APPDATA%/ccache/ccache.conf` (Windows),
6. Otherwise, use
`$HOME/Library/Preferences/ccache/ccache.conf` (macOS) or
`$HOME/.config/ccache/ccache.conf` (other systems).

On Windows, this is the method used to find the configuration file:

1. If `CCACHE_CONFIGPATH` is set, use that path.
2. Otherwise, if the environment variable `CCACHE_DIR` is set then use
`%CCACHE_DIR%/ccache.conf`.
3. Otherwise, if <<config_cache_dir,*cache_dir*>> is set in the secondary
(system-wide) configuration file then use `<cache_dir>\ccache.conf`. The
system-wide configuration on Windows is
`%ALLUSERSPROFILE%\ccache\ccache.conf` by default, the `ALLUSERSPROFILE`
environment variable is usually `C:\ProgramData`.
4. Otherwise, if there is a legacy `%USERPROFILE%\.ccache` directory then use
`%USERPROFILE%\.ccache\ccache.conf`.
5. Otherwise, if `%LOCALAPPDATA%\ccache\ccache.conf` exists it is used.
6. Otherwise, if `%APPDATA%\ccache\ccache.conf` exists it is used.

See also the <<config_cache_dir,*cache_dir*>> configuration option for how the
cache directory location is determined.

=== Configuration file syntax

Expand Down Expand Up @@ -428,11 +445,35 @@ relative paths in the first place instead instead of using *base_dir*.
[#config_cache_dir]
*cache_dir* (*CCACHE_DIR*)::

This option specifies where ccache will keep its cached compiler outputs.
The default is `$XDG_CACHE_HOME/ccache` if `XDG_CACHE_HOME` is set,
otherwise `%APPDATA%/ccache` (Windows), `$HOME/Library/Caches/ccache`
(macOS) or `$HOME/.config/ccache` (other systems). Exception: If the legacy
directory `$HOME/.ccache` exists then that directory is the default.
This option specifies where ccache will keep its cached compiler outputs if
specified in the first found configuration files, with the `$CCACHE_DIR`
environment variable taking precedence over it.
+
The location of the cache directory is determined as follows on non-Windows
systems:
+
--
1. `$CCACHE_DIR` if set.
2. `cache_dir` in the first found configuration if specified.
3. `$HOME/.ccache` if it exists.
4. `$XDG_CACHE_HOME/ccache` if `$XDG_CACHE_HOME` is set.
5. Otherwise `~/.cache/ccache` or `~/Library/Caches/ccache` on Apple systems.
--
+
The cache directory on Windows is determined as follows:
+
--
1. `%CCACHE_DIR%` if set.
2. `cache_dir` in the first found configuration if specified.
3. `%USERPROFILE%\.ccache` if it exists.
4. Otherwise `%LOCALAPPDATA%\ccache`.
--
+
WARNING: Previous builds of ccache for Windows defaulted to storing the cache in
`%APPDATA%\ccache`. This can result in large network file transfers of the cache
in domain environments and similar problems. Please check this directory for
cache directories and either delete them or the whole directory, or move them to
the `%LOCALAPPDATA%\ccache` directory.
+
See also _<<Location of the primary configuration file>>_.
+
Expand Down
68 changes: 53 additions & 15 deletions src/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -424,29 +424,27 @@ parse_config_file(const std::string& path,

} // namespace

#ifndef _WIN32
static std::string
default_cache_dir(const std::string& home_dir)
{
#ifdef _WIN32
return home_dir + "/ccache";
#elif defined(__APPLE__)
# ifdef __APPLE__
return home_dir + "/Library/Caches/ccache";
#else
# else
return home_dir + "/.cache/ccache";
#endif
# endif
}

static std::string
default_config_dir(const std::string& home_dir)
{
#ifdef _WIN32
return home_dir + "/ccache";
#elif defined(__APPLE__)
# ifdef __APPLE__
return home_dir + "/Library/Preferences/ccache";
#else
# else
return home_dir + "/.config/ccache";
#endif
# endif
}
#endif

std::string
compiler_type_to_string(CompilerType compiler_type)
Expand Down Expand Up @@ -477,11 +475,16 @@ void
Config::read()
{
const std::string home_dir = Util::get_home_directory();
const std::string legacy_ccache_dir = home_dir + "/.ccache";
const std::string legacy_ccache_dir = Util::make_path(home_dir, ".ccache");
const bool legacy_ccache_dir_exists =
Stat::stat(legacy_ccache_dir).is_directory();
#ifdef _WIN32
const char* const env_appdata = getenv("APPDATA");
const char* const env_local_appdata = getenv("LOCALAPPDATA");
#else
const char* const env_xdg_cache_home = getenv("XDG_CACHE_HOME");
const char* const env_xdg_config_home = getenv("XDG_CONFIG_HOME");
#endif

const char* env_ccache_configpath = getenv("CCACHE_CONFIGPATH");
if (env_ccache_configpath) {
Expand All @@ -490,9 +493,15 @@ Config::read()
// Only used for ccache tests:
const char* const env_ccache_configpath2 = getenv("CCACHE_CONFIGPATH2");

std::string sysconfdir = Util::make_path(k_sysconfdir);
#ifdef _WIN32
if (const char* program_data = getenv("ALLUSERSPROFILE"))
sysconfdir = Util::make_path(program_data, "ccache");
#endif

set_secondary_config_path(env_ccache_configpath2
? env_ccache_configpath2
: FMT("{}/ccache.conf", k_sysconfdir));
: Util::make_path(sysconfdir, "ccache.conf"));
MTR_BEGIN("config", "conf_read_secondary");
// A missing config file in SYSCONFDIR is OK so don't check return value.
update_from_file(secondary_config_path());
Expand All @@ -506,12 +515,30 @@ Config::read()
primary_config_dir = cache_dir();
} else if (legacy_ccache_dir_exists) {
primary_config_dir = legacy_ccache_dir;
#ifdef _WIN32
} else if (env_local_appdata
&& Stat::stat(
Util::make_path(env_local_appdata, "ccache", "ccache.conf"))) {
primary_config_dir = Util::make_path(env_local_appdata, "ccache");
} else if (env_appdata
&& Stat::stat(
Util::make_path(env_appdata, "ccache", "ccache.conf"))) {
primary_config_dir = Util::make_path(env_appdata, "ccache");
} else if (env_local_appdata) {
primary_config_dir = Util::make_path(env_local_appdata, "ccache");
} else {
throw core::Fatal(
"could not find config file and the LOCALAPPDATA "
"environment variable is not set");
}
#else
} else if (env_xdg_config_home) {
primary_config_dir = FMT("{}/ccache", env_xdg_config_home);
primary_config_dir = Util::make_path(env_xdg_config_home, "ccache");
} else {
primary_config_dir = default_config_dir(home_dir);
}
set_primary_config_path(primary_config_dir + "/ccache.conf");
#endif
set_primary_config_path(Util::make_path(primary_config_dir, "ccache.conf"));
}

const std::string& cache_dir_before_primary_config = cache_dir();
Expand All @@ -531,12 +558,23 @@ Config::read()
if (cache_dir().empty()) {
if (legacy_ccache_dir_exists) {
set_cache_dir(legacy_ccache_dir);
#ifdef _WIN32
} else if (env_local_appdata) {
set_cache_dir(Util::make_path(env_local_appdata, "ccache"));
} else {
throw core::Fatal(
"could not find cache dir and the LOCALAPPDATA "
"environment variable is not set");
}
#else
} else if (env_xdg_cache_home) {
set_cache_dir(FMT("{}/ccache", env_xdg_cache_home));
set_cache_dir(Util::make_path(env_xdg_cache_home, "ccache"));
} else {
set_cache_dir(default_cache_dir(home_dir));
}
#endif
}

// else: cache_dir was set explicitly via environment or via secondary
// config.

Expand Down
22 changes: 10 additions & 12 deletions src/Util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,6 @@ extern "C" {

#include <fcntl.h>

#ifndef HAVE_DIRENT_H
# include <filesystem>
#endif

#ifdef HAVE_PWD_H
# include <pwd.h>
#endif
Expand Down Expand Up @@ -663,26 +659,28 @@ get_extension(std::string_view path)
std::string
get_home_directory()
{
const char* p = getenv("HOME");
if (p) {
#ifdef _WIN32
if (const char* p = getenv("USERPROFILE")) {
return p;
}
#ifdef _WIN32
p = getenv("APPDATA");
if (p) {
throw core::Fatal(
"The USERPROFILE environment variable must be set to your user profile "
"folder");
#else
if (const char* p = getenv("HOME")) {
return p;
}
#endif
#ifdef HAVE_GETPWUID
# ifdef HAVE_GETPWUID
{
struct passwd* pwd = getpwuid(getuid());
if (pwd) {
return pwd->pw_dir;
}
}
#endif
# endif
throw core::Fatal(
"Could not determine home directory from $HOME or getpwuid(3)");
#endif
}

const char*
Expand Down
10 changes: 10 additions & 0 deletions src/Util.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <util/Tokenizer.hpp>

#include <cstdint>
#include <filesystem>
#include <functional>
#include <ios>
#include <memory>
Expand Down Expand Up @@ -245,6 +246,15 @@ bool is_precompiled_header(std::string_view path);
// time of day is used.
std::optional<tm> localtime(std::optional<time_t> time = {});

// Construct a normalized native path, used like:
// std::string path = Util::make_path("usr", "local", "bin");
template<typename... T>
std::string
make_path(const T&... args)
{
return (std::filesystem::path{} / ... / args).lexically_normal().string();
}

// Make a relative path from current working directory (either `actual_cwd` or
// `apparent_cwd`) to `path` if `path` is under `base_dir`.
std::string make_relative_path(const std::string& base_dir,
Expand Down

0 comments on commit 37d993f

Please sign in to comment.