Closes #597.
[#config_base_dir]
*base_dir* (*CCACHE_BASEDIR*)::
- This option should be an absolute path to a directory. If set, ccache will
+ This option is a list of absolute directory paths. The list separator is
+ semicolon on Windows systems and colon on other systems. If set, ccache will
rewrite absolute paths into paths relative to the current working directory,
- but only absolute paths that begin with *base_dir*. Cache results can then
- be shared for compilations in different directories even if the project uses
- absolute paths in the compiler command line. See also the discussion under
- _<<Compiling in different directories>>_. If set to the empty string (which
- is the default), no rewriting is done.
+ but only absolute paths that begin with one of the *base_dir* paths. Cache
+ results can then be shared for compilations in different directories even if
+ the project uses absolute paths in the compiler command line. See also the
+ discussion under _<<Compiling in different directories>>_. If set to the
+ empty string (which is the default), no rewriting is done.
+
A typical path to use as *base_dir* is your home directory or another directory
that is a parent of your project directories. Don't use `/` as the base
while (!inc_path.empty() && inc_path.back() == '/') {
inc_path.pop_back();
}
- if (!ctx.config.base_dir().empty()) {
+ if (!ctx.config.base_dirs().empty()) {
auto it = relative_inc_path_cache.find(inc_path);
if (it == relative_inc_path_cache.end()) {
std::string rel_inc_path =
// paths because otherwise Ninja will use the abs path to original header
// to check if a file needs to be recompiled.
else if (ctx.config.compiler_type() == CompilerType::msvc
- && !ctx.config.base_dir().empty()
+ && !ctx.config.base_dirs().empty()
&& util::starts_with(line, ctx.config.msvc_dep_prefix())) {
std::string orig_line(line.data(), line.length());
std::string abs_inc_path =
// The MSVC /FC option causes paths in diagnostics messages to become
// absolute. Those within basedir need to be changed into relative paths.
else if (ctx.config.compiler_type() == CompilerType::msvc
- && !ctx.config.base_dir().empty()) {
+ && !ctx.config.base_dirs().empty()) {
size_t path_end = core::get_diagnostics_path_length(line);
if (path_end != 0) {
std::string_view abs_path = line.substr(0, path_end);
}
void
-verify_absolute_path(const fs::path& value)
+verify_absolute_paths(const std::vector<fs::path>& paths)
{
- if (!value.is_absolute()) {
- throw core::Error(FMT("not an absolute path: \"{}\"", value));
+ for (const auto& path : paths) {
+ if (!path.is_absolute()) {
+ throw core::Error(FMT("not an absolute path: \"{}\"", path));
+ }
}
}
return format_bool(m_absolute_paths_in_stderr);
case ConfigItem::base_dir:
- return util::pstr(m_base_dir);
+ return util::join_path_list(m_base_dirs);
case ConfigItem::cache_dir:
return m_cache_dir.string();
break;
case ConfigItem::base_dir:
- m_base_dir = value;
- if (!m_base_dir.empty()) { // The empty string means "disable"
- verify_absolute_path(m_base_dir);
- m_base_dir = util::lexically_normal(m_base_dir);
- }
+ set_base_dirs(util::split_path_list(value));
+ verify_absolute_paths(m_base_dirs);
break;
case ConfigItem::cache_dir:
bool absolute_paths_in_stderr() const;
util::Args::ResponseFileFormat response_file_format() const;
- const std::filesystem::path& base_dir() const;
+ const std::vector<std::filesystem::path>& base_dirs() const;
const std::filesystem::path& cache_dir() const;
const std::string& compiler() const;
const std::string& compiler_check() const;
std::filesystem::path default_temporary_dir() const;
void set_base_dir(const std::filesystem::path& value);
+ void set_base_dirs(const std::vector<std::filesystem::path>& value);
void set_cache_dir(const std::filesystem::path& value);
void set_compiler(const std::string& value);
void set_compiler_type(CompilerType value);
bool m_absolute_paths_in_stderr = false;
util::Args::ResponseFileFormat m_response_file_format =
util::Args::ResponseFileFormat::auto_guess;
- std::filesystem::path m_base_dir;
+ std::vector<std::filesystem::path> m_base_dirs;
std::filesystem::path m_cache_dir;
std::string m_compiler;
std::string m_compiler_check = "mtime";
: util::Args::ResponseFileFormat::posix;
}
-inline const std::filesystem::path&
-Config::base_dir() const
+inline const std::vector<std::filesystem::path>&
+Config::base_dirs() const
{
- return m_base_dir;
+ return m_base_dirs;
}
inline const std::filesystem::path&
inline void
Config::set_base_dir(const std::filesystem::path& value)
{
- m_base_dir = util::lexically_normal(value);
+ set_base_dirs({value});
+}
+
+inline void
+Config::set_base_dirs(const std::vector<std::filesystem::path>& value)
+{
+ m_base_dirs.clear();
+ for (const auto& path : value) {
+ m_base_dirs.push_back(util::lexically_normal(path));
+ }
}
inline void
-// Copyright (C) 2023-2024 Joel Rosdahl and other contributors
+// Copyright (C) 2023-2025 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
fs::path
make_relative_path(const Context& ctx, const fs::path& path)
{
- if (!ctx.config.base_dir().empty() && path.is_absolute()
- && util::path_starts_with(path, ctx.config.base_dir())) {
+ if (!ctx.config.base_dirs().empty() && path.is_absolute()
+ && util::path_starts_with(path, ctx.config.base_dirs())) {
return util::make_relative_path(ctx.actual_cwd, ctx.apparent_cwd, path);
} else {
return path;
std::optional<std::string>
rewrite_source_paths(const Context& ctx, std::string_view content)
{
- ASSERT(!ctx.config.base_dir().empty());
+ ASSERT(!ctx.config.base_dirs().empty());
bool rewritten = false;
bool first = true;
tl::expected<void, std::string>
make_paths_relative_in_output_dep(const Context& ctx)
{
- if (ctx.config.base_dir().empty()) {
+ if (ctx.config.base_dirs().empty()) {
LOG_RAW("Base dir not set, skip using relative paths");
return {}; // nothing to do
}
== p2_end;
}
+bool
+path_starts_with(const std::filesystem::path& path,
+ const std::vector<std::filesystem::path>& prefixes)
+{
+ return std::any_of(
+ std::begin(prefixes), std::end(prefixes), [&](const fs::path& prefix) {
+ return path_starts_with(path, prefix);
+ });
+}
+
} // namespace util
#pragma once
#include <ccache/util/pathstring.hpp>
+#ifdef _WIN32
+# include <ccache/util/string.hpp>
+#endif
#include <filesystem>
#include <string>
#include <string_view>
-#ifdef _WIN32
-# include <ccache/util/string.hpp>
-#endif
+#include <vector>
namespace util {
bool path_starts_with(const std::filesystem::path& path,
const std::filesystem::path& prefix);
+// Return whether `path` starts with any of `prefixes` considering path
+// specifics on Windows.
+bool path_starts_with(const std::filesystem::path& path,
+ const std::vector<std::filesystem::path>& prefixes);
+
// Access the underlying path string without having to copy it if
// std::filesystem::path::value_type is char (that is, not wchar_t).
using pstr = PathString;
expect_stat preprocessed_cache_hit 0
expect_stat cache_miss 2
+ # -------------------------------------------------------------------------
+ TEST "Several entries in CCACHE_BASEDIR"
+
+ basedir="$(pwd)/dir1:$(pwd)/dir2"
+
+ cd dir1
+ CCACHE_BASEDIR="${basedir}" $CCACHE_COMPILE -I"$(pwd)"/include -c src/test.c
+ expect_stat direct_cache_hit 0
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+
+ cd ../dir2
+ CCACHE_BASEDIR="${basedir}" $CCACHE_COMPILE -I"$(pwd)"/include -c src/test.c
+ expect_stat direct_cache_hit 1
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+
# -------------------------------------------------------------------------
if ! $HOST_OS_WINDOWS && ! $HOST_OS_CYGWIN; then
TEST "Path normalization"
#include <ccache/core/exceptions.hpp>
#include <ccache/util/environment.hpp>
#include <ccache/util/file.hpp>
+#include <ccache/util/filesystem.hpp>
#include <ccache/util/format.hpp>
#include <doctest/doctest.h>
#include <string>
#include <vector>
+namespace fs = util::filesystem;
+
using doctest::Approx;
using TestUtil::TestContext;
{
Config config;
- CHECK(config.base_dir().empty());
+ CHECK(config.base_dirs().empty());
CHECK(config.cache_dir().empty()); // Set later
CHECK(config.compiler().empty());
CHECK(config.compiler_check() == "mtime");
Config config;
REQUIRE(config.update_from_file("ccache.conf"));
- CHECK(config.base_dir() == base_dir);
+ CHECK(config.base_dirs() == std::vector<fs::path>{base_dir});
CHECK(config.cache_dir() == FMT("{0}$/{0}/.ccache", user));
CHECK(config.compiler() == "foo");
CHECK(config.compiler_check() == "none");
SUBCASE("relative base dir")
{
- REQUIRE(util::write_file("ccache.conf", "base_dir = relative/path"));
- REQUIRE_THROWS_WITH(
- config.update_from_file("ccache.conf"),
- "ccache.conf:1: not an absolute path: \"relative/path\"");
+ REQUIRE(util::write_file("ccache.conf", "base_dir = relative"));
+ REQUIRE_THROWS_WITH(config.update_from_file("ccache.conf"),
+ "ccache.conf:1: not an absolute path: \"relative\"");
REQUIRE(util::write_file("ccache.conf", "base_dir ="));
CHECK(config.update_from_file("ccache.conf"));
SUBCASE("Base directory not in dep file content")
{
- ctx.config.set_base_dir("/foo/bar");
+ ctx.config.set_base_dirs({"/foo/bar"});
CHECK(!depfile::rewrite_source_paths(ctx, ""));
CHECK(!depfile::rewrite_source_paths(ctx, content));
}
SUBCASE("Base directory in dep file content but not matching")
{
- ctx.config.set_base_dir((cwd.parent_path() / "other").string());
+ ctx.config.set_base_dirs({(cwd.parent_path() / "other").string()});
CHECK(!depfile::rewrite_source_paths(ctx, ""));
CHECK(!depfile::rewrite_source_paths(ctx, content));
}
SUBCASE("Absolute paths under base directory rewritten")
{
- ctx.config.set_base_dir(cwd.string());
+ ctx.config.set_base_dirs({cwd.string()});
const auto actual = depfile::rewrite_source_paths(ctx, content);
const auto expected = FMT(
"{0}/foo.o: \\\n"