From: Joel Rosdahl Date: Sun, 14 Apr 2024 08:03:24 +0000 (+0200) Subject: refactor: fs::path-ify some functions X-Git-Tag: v4.10~36 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6a5c4de998af28a4b00c1c31a368ec7bf8412b0b;p=thirdparty%2Fccache.git refactor: fs::path-ify some functions Fixes #1417. --- diff --git a/src/ccache/ArgsInfo.hpp b/src/ccache/ArgsInfo.hpp index 2a46a4fb..fe7c0f41 100644 --- a/src/ccache/ArgsInfo.hpp +++ b/src/ccache/ArgsInfo.hpp @@ -37,9 +37,6 @@ struct ArgsInfo // Prefix to the input file when adding it to a command line. std::string input_file_prefix; - // The source file path run through Util::normalize_concrete_absolute_path. - std::string normalized_input_file; - // In normal compiler operation an output file is created if there is no // compiler error. However certain flags like -fsyntax-only change this // behavior. diff --git a/src/ccache/CMakeLists.txt b/src/ccache/CMakeLists.txt index 876156ce..a80f41eb 100644 --- a/src/ccache/CMakeLists.txt +++ b/src/ccache/CMakeLists.txt @@ -6,7 +6,6 @@ set( Depfile.cpp Hash.cpp ProgressBar.cpp - Util.cpp argprocessing.cpp ccache.cpp compopt.cpp diff --git a/src/ccache/Config.cpp b/src/ccache/Config.cpp index bb2c4ac2..dbf89d07 100644 --- a/src/ccache/Config.cpp +++ b/src/ccache/Config.cpp @@ -18,12 +18,12 @@ #include "Config.hpp" -#include #include #include #include #include #include +#include #include #include #include @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -60,7 +61,9 @@ const char k_sysconfdir[4096 + 1] = SYSCONFDIR; namespace fs = util::filesystem; +using pstr = util::PathString; using util::DirEntry; +using util::make_path; namespace { @@ -383,9 +386,9 @@ format_umask(std::optional umask) } void -verify_absolute_path(const std::string& value) +verify_absolute_path(const fs::path& value) { - if (!fs::path(value).is_absolute()) { + if (!value.is_absolute()) { throw core::Error(FMT("not an absolute path: \"{}\"", value)); } } @@ -548,8 +551,8 @@ Config::read(const std::vector& cmdline_config_settings) auto cmdline_settings_map = create_cmdline_settings_map(cmdline_config_settings); - const std::string home_dir = home_directory(); - const std::string legacy_ccache_dir = Util::make_path(home_dir, ".ccache"); + const fs::path home_dir = home_directory(); + const fs::path legacy_ccache_dir = home_dir / ".ccache"; const bool legacy_ccache_dir_exists = DirEntry(legacy_ccache_dir).is_directory(); #ifdef _WIN32 @@ -567,22 +570,21 @@ Config::read(const std::vector& cmdline_config_settings) // Only used for ccache tests: const char* const env_ccache_configpath2 = getenv("CCACHE_CONFIGPATH2"); - std::string sysconfdir = Util::make_path(k_sysconfdir); + fs::path sysconfdir(k_sysconfdir); #ifdef _WIN32 if (const char* program_data = getenv("ALLUSERSPROFILE")) - sysconfdir = Util::make_path(program_data, "ccache"); + sysconfdir = fs::path(program_data) / "ccache"; #endif - set_system_config_path(env_ccache_configpath2 - ? env_ccache_configpath2 - : Util::make_path(sysconfdir, "ccache.conf")); + set_system_config_path(env_ccache_configpath2 ? env_ccache_configpath2 + : sysconfdir / "ccache.conf"); // A missing config file in SYSCONFDIR is OK so don't check return value. update_from_file(system_config_path()); const char* const env_ccache_dir = getenv("CCACHE_DIR"); auto cmdline_cache_dir = cmdline_settings_map.find("cache_dir"); - std::string config_dir; + fs::path config_dir; if (cmdline_cache_dir != cmdline_settings_map.end()) { config_dir = cmdline_cache_dir->second; } else if (env_ccache_dir && *env_ccache_dir) { @@ -594,14 +596,13 @@ Config::read(const std::vector& cmdline_config_settings) #ifdef _WIN32 } else if (env_local_appdata && DirEntry( - Util::make_path(env_local_appdata, "ccache", "ccache.conf"))) { - config_dir = Util::make_path(env_local_appdata, "ccache"); + make_path(env_local_appdata, "ccache", "ccache.conf"))) { + config_dir = make_path(env_local_appdata, "ccache"); } else if (env_appdata - && DirEntry( - Util::make_path(env_appdata, "ccache", "ccache.conf"))) { - config_dir = Util::make_path(env_appdata, "ccache"); + && DirEntry(make_path(env_appdata, "ccache", "ccache.conf"))) { + config_dir = make_path(env_appdata, "ccache"); } else if (env_local_appdata) { - config_dir = Util::make_path(env_local_appdata, "ccache"); + config_dir = make_path(env_local_appdata, "ccache"); } else { throw core::Fatal( "could not find configuration file and the LOCALAPPDATA environment" @@ -609,12 +610,12 @@ Config::read(const std::vector& cmdline_config_settings) } #else } else if (env_xdg_config_home) { - config_dir = Util::make_path(env_xdg_config_home, "ccache"); + config_dir = make_path(env_xdg_config_home, "ccache"); } else { config_dir = default_config_dir(home_dir); } #endif - set_config_path(Util::make_path(config_dir, "ccache.conf")); + set_config_path(config_dir / "ccache.conf"); } const std::string& cache_dir_before_config_file_was_read = cache_dir(); @@ -631,10 +632,10 @@ Config::read(const std::vector& cmdline_config_settings) if (cache_dir().empty()) { if (legacy_ccache_dir_exists) { - set_cache_dir(legacy_ccache_dir); + set_cache_dir(pstr(legacy_ccache_dir)); #ifdef _WIN32 } else if (env_local_appdata) { - set_cache_dir(Util::make_path(env_local_appdata, "ccache")); + set_cache_dir(pstr(fs::path(env_local_appdata) / "ccache")); } else { throw core::Fatal( "could not find cache directory and the LOCALAPPDATA environment" @@ -642,7 +643,7 @@ Config::read(const std::vector& cmdline_config_settings) } #else } else if (env_xdg_cache_home) { - set_cache_dir(Util::make_path(env_xdg_cache_home, "ccache")); + set_cache_dir(make_path(env_xdg_cache_home, "ccache")); } else { set_cache_dir(default_cache_dir(home_dir)); } @@ -655,37 +656,37 @@ Config::read(const std::vector& cmdline_config_settings) // system config). } -const std::string& +const fs::path& Config::config_path() const { return m_config_path; } -const std::string& +const fs::path& Config::system_config_path() const { return m_system_config_path; } void -Config::set_config_path(std::string path) +Config::set_config_path(const fs::path& path) { - m_config_path = std::move(path); + m_config_path = path; } void -Config::set_system_config_path(std::string path) +Config::set_system_config_path(const fs::path& path) { - m_system_config_path = std::move(path); + m_system_config_path = path; } bool -Config::update_from_file(const std::string& path) +Config::update_from_file(const fs::path& path) { return parse_config_file( - path, [&](const auto& /*line*/, const auto& key, const auto& value) { + pstr(path), [&](const auto& /*line*/, const auto& key, const auto& value) { if (!key.empty()) { - set_item(key, value, std::nullopt, false, path); + set_item(key, value, std::nullopt, false, pstr(path)); } }); } @@ -753,7 +754,7 @@ Config::get_string_value(const std::string& key) const return format_bool(m_absolute_paths_in_stderr); case ConfigItem::base_dir: - return m_base_dir; + return pstr(m_base_dir).str(); case ConfigItem::cache_dir: return m_cache_dir; @@ -982,7 +983,7 @@ Config::set_item(const std::string& key, m_base_dir = value; if (!m_base_dir.empty()) { // The empty string means "disable" verify_absolute_path(m_base_dir); - m_base_dir = Util::normalize_abstract_absolute_path(m_base_dir); + m_base_dir = m_base_dir.lexically_normal(); } break; diff --git a/src/ccache/Config.hpp b/src/ccache/Config.hpp index 2ac11185..60ed5254 100644 --- a/src/ccache/Config.hpp +++ b/src/ccache/Config.hpp @@ -53,7 +53,7 @@ public: void read(const std::vector& cmdline_config_settings = {}); bool absolute_paths_in_stderr() const; - const std::string& base_dir() const; + const std::filesystem::path& base_dir() const; const std::string& cache_dir() const; const std::string& compiler() const; const std::string& compiler_check() const; @@ -106,7 +106,7 @@ public: util::SizeUnitPrefixType size_unit_prefix_type() const; std::string default_temporary_dir() const; - void set_base_dir(const std::string& value); + void set_base_dir(const std::filesystem::path& value); void set_cache_dir(const std::string& value); void set_compiler(const std::string& value); void set_compiler_type(CompilerType value); @@ -124,12 +124,12 @@ public: void set_temporary_dir(const std::string& value); // Where to write configuration changes. - const std::string& config_path() const; + const std::filesystem::path& config_path() const; // System (read-only) configuration file (if any). - const std::string& system_config_path() const; + const std::filesystem::path& system_config_path() const; - void set_config_path(std::string path); - void set_system_config_path(std::string path); + void set_config_path(const std::filesystem::path& path); + void set_system_config_path(const std::filesystem::path& path); using ItemVisitor = std::function -#include #include #include #include +#include #include #include #include @@ -37,8 +37,10 @@ #include #include +namespace fs = util::filesystem; + Context::Context() - : actual_cwd(util::actual_cwd()), + : actual_cwd(fs::current_path().value_or("")), apparent_cwd(util::apparent_cwd(actual_cwd)), storage(config), #ifdef INODE_CACHE_SUPPORTED diff --git a/src/ccache/Context.hpp b/src/ccache/Context.hpp index 72261626..144d135b 100644 --- a/src/ccache/Context.hpp +++ b/src/ccache/Context.hpp @@ -59,10 +59,10 @@ public: Config config; // Current working directory as returned by getcwd(3). - std::string actual_cwd; + std::filesystem::path actual_cwd; // Current working directory according to $PWD (falling back to getcwd(3)). - std::string apparent_cwd; + std::filesystem::path apparent_cwd; // The original argument list. Args orig_args; diff --git a/src/ccache/Depfile.cpp b/src/ccache/Depfile.cpp index c6a6feea..36fb32b7 100644 --- a/src/ccache/Depfile.cpp +++ b/src/ccache/Depfile.cpp @@ -20,8 +20,9 @@ #include #include -#include +#include #include +#include #include #include #include @@ -34,6 +35,8 @@ namespace fs = util::filesystem; +using pstr = util::PathString; + namespace Depfile { std::string @@ -76,10 +79,10 @@ rewrite_source_paths(const Context& ctx, std::string_view content) if (token.empty() || token == ":") { continue; } - auto rel_path = Util::make_relative_path(ctx, token); + auto rel_path = core::make_relative_path(ctx, token); if (rel_path != token) { rewritten = true; - token = std::move(rel_path); + token = pstr(rel_path).str(); } } diff --git a/src/ccache/InodeCache.cpp b/src/ccache/InodeCache.cpp index 774633b5..d4ce3838 100644 --- a/src/ccache/InodeCache.cpp +++ b/src/ccache/InodeCache.cpp @@ -20,7 +20,6 @@ #include #include -#include #include #include #include diff --git a/src/ccache/Util.cpp b/src/ccache/Util.cpp deleted file mode 100644 index 3d35aa68..00000000 --- a/src/ccache/Util.cpp +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright (C) 2019-2024 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 "Util.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_UNISTD_H -# include -#endif - -#include - -namespace fs = util::filesystem; - -using util::DirEntry; - -namespace Util { - -size_t -common_dir_prefix_length(std::string_view dir, std::string_view path) -{ - if (dir.empty() || path.empty() || dir == "/" || path == "/") { - return 0; - } - - ASSERT(dir[0] == '/'); - ASSERT(path[0] == '/'); - - const size_t limit = std::min(dir.length(), path.length()); - size_t i = 0; - - while (i < limit && dir[i] == path[i]) { - ++i; - } - - if ((i == dir.length() && i == path.length()) - || (i == dir.length() && path[i] == '/') - || (i == path.length() && dir[i] == '/')) { - return i; - } - - do { - --i; - } while (i > 0 && dir[i] != '/' && path[i] != '/'); - - return i; -} - -std::string -get_relative_path(std::string_view dir, std::string_view path) -{ - ASSERT(fs::path(dir).is_absolute()); - ASSERT(fs::path(path).is_absolute()); - -#ifdef _WIN32 - // Paths can be escaped by a slash for use with e.g. -isystem. - if (dir.length() >= 3 && dir[0] == '/' && dir[2] == ':') { - dir = dir.substr(1); - } - if (path.length() >= 3 && path[0] == '/' && path[2] == ':') { - path = path.substr(1); - } - if (dir[0] != path[0]) { - // Drive letters differ. - return std::string(path); - } - dir = dir.substr(2); - path = path.substr(2); -#endif - - std::string result; - size_t common_prefix_len = Util::common_dir_prefix_length(dir, path); - if (common_prefix_len > 0 || dir != "/") { - for (size_t i = common_prefix_len; i < dir.length(); ++i) { - if (dir[i] == '/') { - if (!result.empty()) { - result += '/'; - } - result += ".."; - } - } - } - if (path.length() > common_prefix_len) { - if (!result.empty()) { - result += '/'; - } - result += std::string(path.substr(common_prefix_len + 1)); - } - result.erase(result.find_last_not_of('/') + 1); - return result.empty() ? "." : result; -} - -std::string -make_relative_path(const std::string& base_dir, - const std::string& actual_cwd, - const std::string& apparent_cwd, - std::string_view path) -{ - if (base_dir.empty() || !util::path_starts_with(path, base_dir)) { - return std::string(path); - } - -#ifdef _WIN32 - std::string winpath; - if (path.length() >= 3 && path[0] == '/') { - if (isalpha(path[1]) && path[2] == '/') { - // Transform /c/path... to c:/path... - winpath = FMT("{}:/{}", path[1], path.substr(3)); - path = winpath; - } else if (path[2] == ':') { - // Transform /c:/path to c:/path - winpath = std::string(path.substr(1)); - path = winpath; - } - } -#endif - - // The algorithm for computing relative paths below only works for existing - // paths. If the path doesn't exist, find the first ancestor directory that - // does exist and assemble the path again afterwards. - - std::vector relpath_candidates; - const auto original_path = path; - fs::path path_path = path; - while (!fs::exists(path_path)) { - path_path = path_path.parent_path(); - } - std::string path_str = path_path.string(); - path = path_str; - const auto path_suffix = std::string(original_path.substr(path.length())); - const fs::path real_path = fs::canonical(path).value_or(fs::path(path)); - - const auto add_relpath_candidates = [&](auto p) { - const std::string normalized_path = - Util::normalize_abstract_absolute_path(p); - relpath_candidates.push_back( - Util::get_relative_path(actual_cwd, normalized_path)); - if (apparent_cwd != actual_cwd) { - relpath_candidates.emplace_back( - Util::get_relative_path(apparent_cwd, normalized_path)); - } - }; - add_relpath_candidates(path); - if (real_path != path) { - add_relpath_candidates(real_path.string()); - } - - // Find best (i.e. shortest existing) match: - std::sort(relpath_candidates.begin(), - relpath_candidates.end(), - [](const auto& path1, const auto& path2) { - return path1.length() < path2.length(); - }); - for (const auto& relpath : relpath_candidates) { - if (fs::equivalent(relpath, path_path)) { - return relpath + path_suffix; - } - } - - // No match so nothing else to do than to return the unmodified path. - return std::string(original_path); -} - -std::string -make_relative_path(const Context& ctx, std::string_view path) -{ - return make_relative_path( - ctx.config.base_dir(), ctx.actual_cwd, ctx.apparent_cwd, path); -} - -static std::string -do_normalize_abstract_absolute_path(std::string_view path) -{ - if (!fs::path(path).is_absolute()) { - return std::string(path); - } - -#ifdef _WIN32 - std::string drive(path.substr(0, 2)); - path = path.substr(2); -#endif - - std::string result = "/"; - const size_t npos = std::string_view::npos; - size_t left = 1; - - while (true) { - if (left >= path.length()) { - break; - } - const auto right = path.find('/', left); - std::string_view part = - path.substr(left, right == npos ? npos : right - left); - if (part == "..") { - if (result.length() > 1) { - // "/x/../part" -> "/part" - result.erase(result.rfind('/', result.length() - 2) + 1); - } else { - // "/../part" -> "/part" - } - } else if (part == ".") { - // "/x/." -> "/x" - } else { - result.append(part.begin(), part.end()); - if (result[result.length() - 1] != '/') { - result += '/'; - } - } - if (right == npos) { - break; - } - left = right + 1; - } - if (result.length() > 1) { - result.erase(result.find_last_not_of('/') + 1); - } - -#ifdef _WIN32 - return drive + result; -#else - return result; -#endif -} - -std::string -normalize_abstract_absolute_path(std::string_view path) -{ -#ifdef _WIN32 - std::string new_path(path); - std::replace(new_path.begin(), new_path.end(), '\\', '/'); - return do_normalize_abstract_absolute_path(new_path); -#else - return do_normalize_abstract_absolute_path(path); -#endif -} - -std::string -normalize_concrete_absolute_path(const std::string& path) -{ - const auto normalized_path = normalize_abstract_absolute_path(path); - return (normalized_path == path - || DirEntry(normalized_path).same_inode_as(DirEntry(path))) - ? normalized_path - : path; -} - -} // namespace Util diff --git a/src/ccache/Util.hpp b/src/ccache/Util.hpp deleted file mode 100644 index 77c4653c..00000000 --- a/src/ccache/Util.hpp +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (C) 2019-2024 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 -#include -#include - -class Context; - -namespace Util { - -// Compute the length of the longest directory path that is common to paths -// `dir` (a directory) and `path` (any path). -size_t common_dir_prefix_length(std::string_view dir, std::string_view path); - -// Compute a relative path from `dir` (an absolute path to a directory) to -// `path` (an absolute path). Assumes that both `dir` and `path` are normalized. -// The algorithm does *not* follow symlinks, so the result may not actually -// resolve to the same file as `path`. -std::string get_relative_path(std::string_view dir, std::string_view path); - -// Construct a normalized native path. -// -// Example: -// -// std::string path = Util::make_path("usr", "local", "bin"); -template -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, - const std::string& actual_cwd, - const std::string& apparent_cwd, - std::string_view path); - -// Like above but with base directory and apparent/actual CWD taken from `ctx`. -std::string make_relative_path(const Context& ctx, std::string_view path); - -// Normalize absolute path `path`, not taking symlinks into account. -// -// Normalization here means syntactically removing redundant slashes and -// resolving "." and ".." parts. The algorithm does however *not* follow -// symlinks, so the result may not actually resolve to the same filesystem entry -// as `path` (nor to any existing file system entry for that matter). -// -// On Windows: Backslashes are replaced with forward slashes. -std::string normalize_abstract_absolute_path(std::string_view path); - -// Like normalize_abstract_absolute_path, but returns `path` unchanged if the -// normalized result doesn't resolve to the same file system entry as `path`. -std::string normalize_concrete_absolute_path(const std::string& path); - -} // namespace Util diff --git a/src/ccache/argprocessing.cpp b/src/ccache/argprocessing.cpp index ee9eb2d1..f8f454eb 100644 --- a/src/ccache/argprocessing.cpp +++ b/src/ccache/argprocessing.cpp @@ -20,8 +20,8 @@ #include #include -#include #include +#include #include #include #include @@ -252,7 +252,7 @@ process_profiling_option(const Context& ctx, new_profile_path = "."; } else { // GCC uses $PWD/$(basename $obj). - new_profile_path = ctx.apparent_cwd; + new_profile_path = pstr(ctx.apparent_cwd).str(); } } else if (util::starts_with(arg, "-fprofile-generate=") || util::starts_with(arg, "-fprofile-instr-generate=")) { @@ -737,7 +737,8 @@ process_option_arg(const Context& ctx, if (state.output_dep_origin <= OutputDepOrigin::mf) { state.output_dep_origin = OutputDepOrigin::mf; - args_info.output_dep = Util::make_relative_path(ctx, dep_file); + args_info.output_dep = + pstr(core::make_relative_path(ctx, dep_file)).str(); } // Keep the format of the args the same. if (separate_argument) { @@ -869,8 +870,8 @@ process_option_arg(const Context& ctx, if (util::starts_with(arg, "--sysroot=")) { auto path = std::string_view(arg).substr(10); - auto relpath = Util::make_relative_path(ctx, path); - state.common_args.push_back("--sysroot=" + relpath); + auto relpath = core::make_relative_path(ctx, path); + state.common_args.push_back(FMT("--sysroot={}", relpath)); return Statistic::none; } @@ -881,8 +882,8 @@ process_option_arg(const Context& ctx, return Statistic::bad_compiler_arguments; } state.common_args.push_back(args[i]); - auto relpath = Util::make_relative_path(ctx, args[i + 1]); - state.common_args.push_back(relpath); + auto relpath = core::make_relative_path(ctx, args[i + 1]); + state.common_args.push_back(pstr(relpath).str()); i++; return Statistic::none; } @@ -974,7 +975,8 @@ process_option_arg(const Context& ctx, return Statistic::bad_compiler_arguments; } args_info.generating_diagnostics = true; - args_info.output_dia = Util::make_relative_path(ctx, args[i + 1]); + args_info.output_dia = + pstr(core::make_relative_path(ctx, args[i + 1])).str(); i++; return Statistic::none; } @@ -1080,14 +1082,14 @@ process_option_arg(const Context& ctx, // Potentially rewrite path argument to relative path to get better hit // rate. A secondary effect is that paths in the standard error output // produced by the compiler will be normalized. - std::string relpath = Util::make_relative_path(ctx, args[i + next]); + fs::path relpath = core::make_relative_path(ctx, args[i + next]); auto& dest_args = compopt_affects_cpp_output(arg) ? state.cpp_args : state.common_args; dest_args.push_back(args[i]); if (next == 2) { dest_args.push_back(args[i + 1]); } - dest_args.push_back(relpath); + dest_args.push_back(pstr(relpath).str()); i += next; return Statistic::none; @@ -1116,7 +1118,7 @@ process_option_arg(const Context& ctx, const auto [option, path] = util::split_option_with_concat_path(arg); if (path) { if (compopt_takes_concat_arg(option) && compopt_takes_path(option)) { - const auto relpath = Util::make_relative_path(ctx, *path); + const auto relpath = core::make_relative_path(ctx, *path); std::string new_option = FMT("{}{}", option, relpath); if (compopt_affects_cpp_output(option)) { state.cpp_args.push_back(std::move(new_option)); @@ -1268,9 +1270,7 @@ process_args(Context& ctx) args_info.orig_input_file = pstr(state.input_files.front()).str(); // Rewrite to relative to increase hit rate. args_info.input_file = - Util::make_relative_path(ctx, args_info.orig_input_file); - args_info.normalized_input_file = - Util::normalize_concrete_absolute_path(args_info.input_file); + pstr(core::make_relative_path(ctx, args_info.orig_input_file)).str(); // Bail out on too hard combinations of options. if (state.found_mf_opt && state.found_wp_md_or_mmd_opt) { @@ -1323,7 +1323,8 @@ process_args(Context& ctx) } args_info.orig_output_obj = args_info.output_obj; - args_info.output_obj = Util::make_relative_path(ctx, args_info.output_obj); + args_info.output_obj = + pstr(core::make_relative_path(ctx, args_info.output_obj)).str(); // Determine output dependency file. @@ -1362,7 +1363,7 @@ process_args(Context& ctx) } if (args_info.profile_path.empty()) { - args_info.profile_path = ctx.apparent_cwd; + args_info.profile_path = pstr(ctx.apparent_cwd).str(); } if (!state.explicit_language.empty() && state.explicit_language == "none") { @@ -1386,7 +1387,7 @@ process_args(Context& ctx) if (args_info.output_is_precompiled_header && output_obj_by_source) { args_info.orig_output_obj = args_info.orig_input_file + ".gch"; args_info.output_obj = - Util::make_relative_path(ctx, args_info.orig_output_obj); + pstr(core::make_relative_path(ctx, args_info.orig_output_obj)).str(); } if (args_info.output_is_precompiled_header @@ -1564,13 +1565,15 @@ process_args(Context& ctx) if (args_info.generating_stackusage) { std::string default_sufile_name = fs::path(args_info.output_obj).replace_extension(".su").string(); - args_info.output_su = Util::make_relative_path(ctx, default_sufile_name); + args_info.output_su = + pstr(core::make_relative_path(ctx, default_sufile_name)).str(); } if (args_info.generating_callgraphinfo) { std::string default_cifile_name = fs::path(args_info.output_obj).replace_extension(".ci").string(); - args_info.output_ci = Util::make_relative_path(ctx, default_cifile_name); + args_info.output_ci = + pstr(core::make_relative_path(ctx, default_cifile_name)).str(); } Args compiler_args = state.common_args; diff --git a/src/ccache/ccache.cpp b/src/ccache/ccache.cpp index a6beb202..594c0d8a 100644 --- a/src/ccache/ccache.cpp +++ b/src/ccache/ccache.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -313,7 +312,7 @@ remember_include_file(Context& ctx, return {}; } - if (path == ctx.args_info.normalized_input_file) { + if (fs::path(path) == ctx.args_info.input_file) { // Don't remember the input file. return {}; } @@ -550,18 +549,23 @@ process_preprocessed_file(Context& ctx, Hash& hash, const std::string& path) // p and q span the include file path. std::string inc_path(p, q - p); - auto it = relative_inc_path_cache.find(inc_path); - if (it == relative_inc_path_cache.end()) { - auto rel_inc_path = Util::make_relative_path( - ctx, Util::normalize_concrete_absolute_path(inc_path)); - relative_inc_path_cache.emplace(inc_path, rel_inc_path); - inc_path = std::move(rel_inc_path); - } else { - inc_path = it->second; + while (!inc_path.empty() && inc_path.back() == '/') { + inc_path.pop_back(); + } + if (!ctx.config.base_dir().empty()) { + auto it = relative_inc_path_cache.find(inc_path); + if (it == relative_inc_path_cache.end()) { + std::string rel_inc_path = + pstr(core::make_relative_path(ctx, inc_path)).str(); + relative_inc_path_cache.emplace(inc_path, rel_inc_path); + inc_path = pstr(rel_inc_path).str(); + } else { + inc_path = it->second; + } } - if ((inc_path != ctx.apparent_cwd) || ctx.config.hash_dir()) { - hash.hash(inc_path); + if (inc_path != ctx.apparent_cwd || ctx.config.hash_dir()) { + hash.hash(pstr(inc_path).str()); } TRY(remember_include_file(ctx, inc_path, hash, system, nullptr)); @@ -615,7 +619,7 @@ process_preprocessed_file(Context& ctx, Hash& hash, const std::string& path) if (!ctx.args_info.included_pch_file.empty() && !ctx.args_info.generating_pch) { std::string pch_path = - Util::make_relative_path(ctx, ctx.args_info.included_pch_file); + pstr(core::make_relative_path(ctx, ctx.args_info.included_pch_file)); hash.hash(pch_path); TRY(remember_include_file(ctx, pch_path, hash, false, nullptr)); } @@ -654,8 +658,8 @@ result_key_from_depfile(Context& ctx, Hash& hash) continue; } if (seen_colon) { - std::string path = Util::make_relative_path(ctx, token); - TRY(remember_include_file(ctx, path, hash, false, &hash)); + fs::path path = core::make_relative_path(ctx, token); + TRY(remember_include_file(ctx, pstr(path), hash, false, &hash)); } else if (token == ":") { seen_colon = true; } @@ -665,10 +669,10 @@ result_key_from_depfile(Context& ctx, Hash& hash) // dependencies output. if (!ctx.args_info.included_pch_file.empty() && !ctx.args_info.generating_pch) { - std::string pch_path = - Util::make_relative_path(ctx, ctx.args_info.included_pch_file); - hash.hash(pch_path); - TRY(remember_include_file(ctx, pch_path, hash, false, nullptr)); + fs::path pch_path = + core::make_relative_path(ctx, ctx.args_info.included_pch_file); + hash.hash(pstr(pch_path).str()); + TRY(remember_include_file(ctx, pstr(pch_path), hash, false, nullptr)); } bool debug_included = getenv("CCACHE_DEBUG_INCLUDED"); @@ -716,19 +720,18 @@ result_key_from_includes(Context& ctx, Hash& hash, std::string_view stdout_data) { for (std::string_view include : core::MsvcShowIncludesOutput::get_includes( stdout_data, ctx.config.msvc_dep_prefix())) { - const std::string path = Util::make_relative_path( - ctx, Util::normalize_abstract_absolute_path(include)); - TRY(remember_include_file(ctx, path, hash, false, &hash)); + const fs::path path = core::make_relative_path(ctx, include); + TRY(remember_include_file(ctx, pstr(path), hash, false, &hash)); } // Explicitly check the .pch file as it is not mentioned in the // includes output. if (!ctx.args_info.included_pch_file.empty() && !ctx.args_info.generating_pch) { - std::string pch_path = - Util::make_relative_path(ctx, ctx.args_info.included_pch_file); - hash.hash(pch_path); - TRY(remember_include_file(ctx, pch_path, hash, false, nullptr)); + fs::path pch_path = + core::make_relative_path(ctx, ctx.args_info.included_pch_file); + hash.hash(pstr(pch_path).str()); + TRY(remember_include_file(ctx, pstr(pch_path), hash, false, nullptr)); } const bool debug_included = getenv("CCACHE_DEBUG_INCLUDED"); @@ -1017,10 +1020,9 @@ rewrite_stdout_from_compiler(const Context& ctx, util::Bytes&& stdout_data) std::string abs_inc_path = util::replace_first(orig_line, ctx.config.msvc_dep_prefix(), ""); abs_inc_path = util::strip_whitespace(abs_inc_path); - std::string rel_inc_path = Util::make_relative_path( - ctx, Util::normalize_concrete_absolute_path(abs_inc_path)); - std::string line_with_rel_inc = - util::replace_first(orig_line, abs_inc_path, rel_inc_path); + fs::path rel_inc_path = core::make_relative_path(ctx, abs_inc_path); + std::string line_with_rel_inc = util::replace_first( + orig_line, abs_inc_path, pstr(rel_inc_path).str()); new_stdout_data.insert(new_stdout_data.end(), line_with_rel_inc.data(), line_with_rel_inc.size()); @@ -1506,7 +1508,7 @@ hash_common_info(const Context& ctx, // Possibly hash the current working directory. if (args_info.generating_debuginfo && ctx.config.hash_dir()) { - std::string dir_to_hash = ctx.apparent_cwd; + std::string dir_to_hash = pstr(ctx.apparent_cwd).str(); for (const auto& map : args_info.debug_prefix_maps) { size_t sep_pos = map.find('='); if (sep_pos != std::string::npos) { @@ -1516,8 +1518,9 @@ hash_common_info(const Context& ctx, old_path, new_path, ctx.apparent_cwd); - if (util::starts_with(ctx.apparent_cwd, old_path)) { - dir_to_hash = new_path + ctx.apparent_cwd.substr(old_path.size()); + if (util::starts_with(pstr(ctx.apparent_cwd), old_path)) { + dir_to_hash = + new_path + pstr(ctx.apparent_cwd).str().substr(old_path.size()); } } } @@ -1533,7 +1536,7 @@ hash_common_info(const Context& ctx, const std::string output_obj_dir = fs::path(args_info.output_obj).is_absolute() ? fs::path(args_info.output_obj).parent_path().string() - : ctx.actual_cwd; + : pstr(ctx.actual_cwd); LOG("Hashing object file directory {}", output_obj_dir); hash.hash_delimiter("source path"); hash.hash(output_obj_dir); @@ -1558,7 +1561,7 @@ hash_common_info(const Context& ctx, // the directory in the hash for now. LOG_RAW("Hashing apparent CWD due to generating a .gcno file"); hash.hash_delimiter("CWD in .gcno"); - hash.hash(ctx.apparent_cwd); + hash.hash(pstr(ctx.apparent_cwd).str()); } // Possibly hash the coverage data file path. @@ -1940,7 +1943,7 @@ hash_profile_data_file(const Context& ctx, Hash& hash) const std::string& profile_path = ctx.args_info.profile_path; const std::string base_name = pstr(fs::path(ctx.args_info.output_obj).replace_extension("")).str(); - std::string hashified_cwd = ctx.apparent_cwd; + std::string hashified_cwd = pstr(ctx.apparent_cwd).str(); std::replace(hashified_cwd.begin(), hashified_cwd.end(), '/', '#'); std::vector paths_to_try{ @@ -2599,7 +2602,7 @@ do_cache_compilation(Context& ctx) if (processed.hash_actual_cwd) { common_hash.hash_delimiter("actual_cwd"); - common_hash.hash(ctx.actual_cwd); + common_hash.hash(pstr(ctx.actual_cwd).str()); } // Try to find the hash using the manifest. diff --git a/src/ccache/core/FileRecompressor.cpp b/src/ccache/core/FileRecompressor.cpp index 945d902c..cb3f6fea 100644 --- a/src/ccache/core/FileRecompressor.cpp +++ b/src/ccache/core/FileRecompressor.cpp @@ -18,7 +18,6 @@ #include "FileRecompressor.hpp" -#include #include #include #include diff --git a/src/ccache/core/ResultExtractor.cpp b/src/ccache/core/ResultExtractor.cpp index f765da4b..18c58d70 100644 --- a/src/ccache/core/ResultExtractor.cpp +++ b/src/ccache/core/ResultExtractor.cpp @@ -18,7 +18,6 @@ #include "ResultExtractor.hpp" -#include #include #include #include diff --git a/src/ccache/core/Statistics.cpp b/src/ccache/core/Statistics.cpp index 1f012a42..ab368af8 100644 --- a/src/ccache/core/Statistics.cpp +++ b/src/ccache/core/Statistics.cpp @@ -19,7 +19,7 @@ #include "Statistics.hpp" #include -#include +#include #include #include #include @@ -30,6 +30,7 @@ namespace core { +using pstr = util::PathString; using core::Statistic; const unsigned FLAG_NOZERO = 1U << 0; // don't zero with --zero-stats @@ -390,9 +391,10 @@ Statistics::format_human_readable(const Config& config, if (verbosity > 0 && !from_log) { table.add_row({"Cache directory:", C(config.cache_dir()).colspan(4)}); - table.add_row({"Config file:", C(config.config_path()).colspan(4)}); table.add_row( - {"System config file:", C(config.system_config_path()).colspan(4)}); + {"Config file:", C(pstr(config.config_path()).str()).colspan(4)}); + table.add_row({"System config file:", + C(pstr(config.system_config_path()).str()).colspan(4)}); table.add_row( {"Stats updated:", C(format_timestamp(last_updated)).colspan(4)}); if (verbosity > 1) { diff --git a/src/ccache/core/common.cpp b/src/ccache/core/common.cpp index 40181234..a716b544 100644 --- a/src/ccache/core/common.cpp +++ b/src/ccache/core/common.cpp @@ -81,6 +81,17 @@ ensure_dir_exists(const fs::path& dir) } } +std::filesystem::path +make_relative_path(const Context& ctx, const std::filesystem::path& path) +{ + if (!ctx.config.base_dir().empty() && path.is_absolute() + && util::path_starts_with(path, ctx.config.base_dir())) { + return util::make_relative_path(ctx.actual_cwd, ctx.apparent_cwd, path); + } else { + return path; + } +} + std::string rewrite_stderr_to_absolute_paths(std::string_view text) { diff --git a/src/ccache/core/common.hpp b/src/ccache/core/common.hpp index f8d0b339..78734a8f 100644 --- a/src/ccache/core/common.hpp +++ b/src/ccache/core/common.hpp @@ -1,4 +1,4 @@ -// Copyright (C) 2023 Joel Rosdahl and other contributors +// Copyright (C) 2023-2024 Joel Rosdahl and other contributors // // See doc/AUTHORS.adoc for a complete list of contributors. // @@ -29,6 +29,10 @@ namespace core { // Like std::filesystem::create_directories but throws core::Fatal on error. void ensure_dir_exists(const std::filesystem::path& dir); +// Make a `path` relative to CWD if it's under base_dir. +std::filesystem::path make_relative_path(const Context& ctx, + const std::filesystem::path& path); + // Rewrite path to absolute path in `text` in the following two cases, where X // may be optional ANSI CSI sequences: // diff --git a/src/ccache/core/mainoptions.cpp b/src/ccache/core/mainoptions.cpp index f180dccf..2137939b 100644 --- a/src/ccache/core/mainoptions.cpp +++ b/src/ccache/core/mainoptions.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -717,7 +716,8 @@ process_main_options(int argc, const char* const* argv) case 'F': { // --max-files auto files = util::value_or_throw(util::parse_unsigned(arg)); - config.set_value_in_file(config.config_path(), "max_files", arg); + config.set_value_in_file( + pstr(config.config_path()).str(), "max_files", arg); if (files == 0) { PRINT_RAW(stdout, "Unset cache file limit\n"); } else { @@ -730,7 +730,8 @@ process_main_options(int argc, const char* const* argv) auto [size, suffix_type] = util::value_or_throw(util::parse_size(arg)); uint64_t max_size = size; - config.set_value_in_file(config.config_path(), "max_size", arg); + config.set_value_in_file( + pstr(config.config_path()).str(), "max_size", arg); if (max_size == 0) { PRINT_RAW(stdout, "Unset cache size limit\n"); } else { @@ -750,7 +751,7 @@ process_main_options(int argc, const char* const* argv) } std::string key = arg.substr(0, eq_pos); std::string value = arg.substr(eq_pos + 1); - config.set_value_in_file(config.config_path(), key, value); + config.set_value_in_file(pstr(config.config_path()).str(), key, value); break; } diff --git a/src/ccache/execute.cpp b/src/ccache/execute.cpp index 11c2750d..198fbe5e 100644 --- a/src/ccache/execute.cpp +++ b/src/ccache/execute.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include diff --git a/src/ccache/storage/Storage.cpp b/src/ccache/storage/Storage.cpp index 3d6f82f5..dc8cf00c 100644 --- a/src/ccache/storage/Storage.cpp +++ b/src/ccache/storage/Storage.cpp @@ -19,7 +19,6 @@ #include "Storage.hpp" #include -#include #include #include #include diff --git a/src/ccache/storage/local/util.cpp b/src/ccache/storage/local/util.cpp index 7e9b979c..00469e88 100644 --- a/src/ccache/storage/local/util.cpp +++ b/src/ccache/storage/local/util.cpp @@ -18,7 +18,6 @@ #include "util.hpp" -#include #include #include #include diff --git a/src/ccache/storage/remote/FileStorage.cpp b/src/ccache/storage/remote/FileStorage.cpp index 51d7f1d6..b7a6375f 100644 --- a/src/ccache/storage/remote/FileStorage.cpp +++ b/src/ccache/storage/remote/FileStorage.cpp @@ -18,7 +18,6 @@ #include "FileStorage.hpp" -#include #include #include #include diff --git a/src/ccache/util/LockFile.cpp b/src/ccache/util/LockFile.cpp index b92a2354..2767dd0b 100644 --- a/src/ccache/util/LockFile.cpp +++ b/src/ccache/util/LockFile.cpp @@ -18,7 +18,6 @@ #include "LockFile.hpp" -#include #include #include #include diff --git a/src/ccache/util/LongLivedLockFileManager.cpp b/src/ccache/util/LongLivedLockFileManager.cpp index bf6e2279..e0122eb6 100644 --- a/src/ccache/util/LongLivedLockFileManager.cpp +++ b/src/ccache/util/LongLivedLockFileManager.cpp @@ -18,7 +18,6 @@ #include "LongLivedLockFileManager.hpp" -#include #include #include #include diff --git a/src/ccache/util/assertions.cpp b/src/ccache/util/assertions.cpp index c0609eae..f430fb67 100644 --- a/src/ccache/util/assertions.cpp +++ b/src/ccache/util/assertions.cpp @@ -16,7 +16,6 @@ // this program; if not, write to the Free Software Foundation, Inc., 51 // Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -#include #include #include #include diff --git a/src/ccache/util/path.cpp b/src/ccache/util/path.cpp index b32056f5..824fa17d 100644 --- a/src/ccache/util/path.cpp +++ b/src/ccache/util/path.cpp @@ -18,8 +18,8 @@ #include "path.hpp" -#include #include +#include #include #include #include @@ -32,21 +32,9 @@ const char k_dev_null_path[] = "/dev/null"; namespace fs = util::filesystem; -namespace util { +using pstr = util::PathString; -std::string -actual_cwd() -{ - auto cwd = fs::current_path(); - if (!cwd) { - return {}; - } - auto cwd_str = cwd->string(); -#ifdef _WIN32 - std::replace(cwd_str.begin(), cwd_str.end(), '\\', '/'); -#endif - return cwd_str; -} +namespace util { std::string add_exe_suffix(const std::string& program) @@ -59,8 +47,8 @@ add_exe_suffix(const std::string& program) } } -std::string -apparent_cwd(const std::string& actual_cwd) +fs::path +apparent_cwd(const fs::path& actual_cwd) { #ifdef _WIN32 return actual_cwd; @@ -72,9 +60,7 @@ apparent_cwd(const std::string& actual_cwd) DirEntry pwd_de(pwd); DirEntry cwd_de(actual_cwd); - return !pwd_de || !cwd_de || !pwd_de.same_inode_as(cwd_de) - ? actual_cwd - : Util::normalize_concrete_absolute_path(pwd); + return !pwd_de || !cwd_de || !pwd_de.same_inode_as(cwd_de) ? actual_cwd : pwd; #endif } @@ -84,40 +70,74 @@ get_dev_null_path() return k_dev_null_path; } -bool -path_starts_with(std::string_view path, std::string_view prefix) +std::filesystem::path +make_relative_path(const fs::path& actual_cwd, + const fs::path& apparent_cwd, + const fs::path& path) { - if (path.empty()) { - return false; - } - for (size_t i = 0, j = 0; i < path.length() && j < prefix.length(); - ++i, ++j) { -#ifdef _WIN32 - // Skip escaped backslashes \\\\ as seen by the preprocessor. - if (i > 0 && path[i] == '\\' && path[i - 1] == '\\') { - ++i; - } - if (j > 0 && prefix[j] == '\\' && prefix[j - 1] == '\\') { - ++j; - } + DEBUG_ASSERT(actual_cwd.is_absolute()); + DEBUG_ASSERT(apparent_cwd.is_absolute()); + DEBUG_ASSERT(path.is_absolute()); - // Handle back and forward slashes as equal. - if (path[i] == '/' && prefix[j] == '\\') { - continue; + fs::path normalized_path = path.lexically_normal(); + fs::path closest_existing_path = normalized_path; + std::vector relpath_candidates; + fs::path path_suffix; + while (!fs::exists(closest_existing_path)) { + if (path_suffix.empty()) { + path_suffix = closest_existing_path.filename(); + } else { + path_suffix = closest_existing_path.filename() / path_suffix; } - if (path[i] == '\\' && prefix[j] == '/') { - continue; + closest_existing_path = closest_existing_path.parent_path(); + } + + const auto add_relpath_candidates = [&](auto p) { + relpath_candidates.push_back(p.lexically_relative(actual_cwd)); + if (apparent_cwd != actual_cwd) { + relpath_candidates.emplace_back(p.lexically_relative(apparent_cwd)); } - if (std::tolower(path[i]) != std::tolower(prefix[j])) { - return false; + }; + + add_relpath_candidates(closest_existing_path); + const fs::path real_closest_existing_path = + fs::canonical(closest_existing_path).value_or(closest_existing_path); + if (real_closest_existing_path != closest_existing_path) { + add_relpath_candidates(real_closest_existing_path); + } + + // Find best (i.e. shortest existing) match: + std::sort(relpath_candidates.begin(), + relpath_candidates.end(), + [](const auto& path1, const auto& path2) { + return pstr(path1).str().length() < pstr(path2).str().length(); + }); + for (const auto& relpath : relpath_candidates) { + if (fs::equivalent(relpath, closest_existing_path)) { + return path_suffix.empty() ? relpath + : (relpath / path_suffix).lexically_normal(); } + } + + // No match so nothing else to do than to return the unmodified path. + return path; +} + +bool +path_starts_with(const fs::path& path, const fs::path& prefix) +{ +#ifdef _WIN32 + // Note: Not all paths on Windows are case insensitive, but for our purposes + // (checking whether a path is below the base directory) users will expect + // them to be. + fs::path p1 = util::to_lowercase(path.string()); + fs::path p2 = util::to_lowercase(prefix.string()); #else - if (path[i] != prefix[j]) { - return false; - } + const fs::path& p1 = path; + const fs::path& p2 = prefix; #endif - } - return true; + return std::mismatch(p1.begin(), p1.end(), p2.begin(), p2.end()).second + == p2.end(); } } // namespace util diff --git a/src/ccache/util/path.hpp b/src/ccache/util/path.hpp index c445d37d..039c58a2 100644 --- a/src/ccache/util/path.hpp +++ b/src/ccache/util/path.hpp @@ -29,10 +29,6 @@ namespace util { // --- Interface --- -// Return current working directory (CWD) as returned from getcwd(3) (i.e., -// normalized path without symlink parts). Returns the empty string on error. -std::string actual_cwd(); - // Add ".exe" suffix to `program` if it doesn't already end with ".exe", ".bat" // or ".sh". std::string add_exe_suffix(const std::string& program); @@ -41,7 +37,7 @@ std::string add_exe_suffix(const std::string& program); // PWD (thus keeping any symlink parts in the path and potentially ".." or "//" // parts). If PWD does not resolve to the same inode as `actual_cwd` then // `actual_cwd` is returned instead. -std::string apparent_cwd(const std::string& actual_cwd); +std::filesystem::path apparent_cwd(const std::filesystem::path& actual_cwd); const char* get_dev_null_path(); @@ -51,9 +47,29 @@ bool is_dev_null_path(const std::filesystem::path& path); // Return whether `path` includes at least one directory separator. bool is_full_path(std::string_view path); +// Make a relative path from current working directory (either `actual_cwd` or +// `apparent_cwd`) to `path` if `path` is under `base_dir`. +std::filesystem::path +make_relative_path(const std::filesystem::path& actual_cwd, + const std::filesystem::path& apparent_cwd, + const std::filesystem::path& path); + +// Construct a normalized native path. +// +// Example: +// +// std::string path = make_path("usr", "local", "bin"); +template +std::filesystem::path +make_path(const T&... args) +{ + return (std::filesystem::path{} / ... / args).lexically_normal(); +} + // Return whether `path` starts with `prefix` considering path specifics on -// Windows -bool path_starts_with(std::string_view path, std::string_view prefix); +// Windows. +bool path_starts_with(const std::filesystem::path& path, + const std::filesystem::path& prefix); // --- Inline implementations --- diff --git a/test/run b/test/run index 7a27d38a..f62521f5 100755 --- a/test/run +++ b/test/run @@ -3,7 +3,7 @@ # A simple test suite for ccache. # # Copyright (C) 2002-2007 Andrew Tridgell -# Copyright (C) 2009-2023 Joel Rosdahl and other contributors +# Copyright (C) 2009-2024 Joel Rosdahl and other contributors # # See doc/AUTHORS.adoc for a complete list of contributors. # diff --git a/test/suites/depend.bash b/test/suites/depend.bash index 37cff130..0ff0d3d3 100644 --- a/test/suites/depend.bash +++ b/test/suites/depend.bash @@ -162,8 +162,8 @@ SUITE_depend() { # ------------------------------------------------------------------------- TEST "Dependency file paths converted to relative if CCACHE_BASEDIR specified" - CCACHE_DEPEND=1 CCACHE_BASEDIR="`pwd`" $CCACHE_COMPILE $DEPSFLAGS_CCACHE -c "`pwd`/test.c" - if grep -q "[^.]/test.c" "test.d"; then + CCACHE_DEPEND=1 CCACHE_BASEDIR="$(pwd)" $CCACHE_COMPILE $DEPSFLAGS_CCACHE -c "$(pwd)/test.c" + if ! grep -q " test.c" test.d; then test_failed "Dependency file does not contain relative path to test.c" fi diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt index 5414267f..842f3d4d 100644 --- a/unittest/CMakeLists.txt +++ b/unittest/CMakeLists.txt @@ -6,7 +6,6 @@ set( test_Config.cpp test_Depfile.cpp test_Hash.cpp - test_Util.cpp test_argprocessing.cpp test_ccache.cpp test_compopt.cpp diff --git a/unittest/main.cpp b/unittest/main.cpp index 6314de88..0baf62ae 100644 --- a/unittest/main.cpp +++ b/unittest/main.cpp @@ -39,7 +39,7 @@ main(int argc, char** argv) #endif util::unsetenv("GCC_COLORS"); // Don't confuse argument processing tests. - std::string dir_before = util::actual_cwd(); + auto dir_before = *fs::current_path(); std::string testdir = FMT("testdir/{}", getpid()); fs::remove_all(testdir); fs::create_directories(testdir); diff --git a/unittest/test_Config.cpp b/unittest/test_Config.cpp index 1fac22c6..a6cfddcd 100644 --- a/unittest/test_Config.cpp +++ b/unittest/test_Config.cpp @@ -19,7 +19,6 @@ #include "TestUtil.hpp" #include -#include #include #include #include @@ -393,7 +392,7 @@ TEST_CASE("Config::visit_items") #ifndef _WIN32 "base_dir = /bd\n" #else - "base_dir = C:/bd\n" + "base_dir = C:\\bd\n" #endif "cache_dir = cd\n" "compiler = c\n" @@ -455,7 +454,7 @@ TEST_CASE("Config::visit_items") #ifndef _WIN32 "(test.conf) base_dir = /bd", #else - "(test.conf) base_dir = C:/bd", + "(test.conf) base_dir = C:\\bd", #endif "(test.conf) cache_dir = cd", "(test.conf) compiler = c", diff --git a/unittest/test_Depfile.cpp b/unittest/test_Depfile.cpp index 6e5d91a9..2576b082 100644 --- a/unittest/test_Depfile.cpp +++ b/unittest/test_Depfile.cpp @@ -84,12 +84,14 @@ TEST_CASE("Depfile::rewrite_source_paths") const auto expected = FMT( "{0}/foo.o: \\\n" " bar.c \\\n" - " ./bar/bar.h \\\n" + " {2} \\\n" " {1}/fie.h\n" - "./bar/bar.h:\n" + "{2}:\n" "{1}/fie.h:\n", Depfile::escape_filename(pstr(cwd).str()), - Depfile::escape_filename(pstr(cwd.parent_path()).str())); + Depfile::escape_filename(pstr(cwd.parent_path()).str()), + Depfile::escape_filename( + pstr(fs::path("bar/bar.h").lexically_normal()).str())); REQUIRE(actual); CHECK(*actual == expected); } diff --git a/unittest/test_InodeCache.cpp b/unittest/test_InodeCache.cpp index a045fdab..66073437 100644 --- a/unittest/test_InodeCache.cpp +++ b/unittest/test_InodeCache.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -37,13 +38,15 @@ namespace fs = util::filesystem; using TestUtil::TestContext; +using pstr = util::PathString; namespace { bool inode_cache_available() { - auto tmp_file = util::TemporaryFile::create(util::actual_cwd() + "/fs_test"); + auto tmp_file = + util::TemporaryFile::create((*fs::current_path()) / "fs_test"); if (!tmp_file) { return false; } @@ -57,7 +60,7 @@ init(Config& config) { config.set_debug(true); config.set_inode_cache(true); - config.set_temporary_dir(util::actual_cwd()); + config.set_temporary_dir(pstr(*fs::current_path()).str()); } bool diff --git a/unittest/test_Util.cpp b/unittest/test_Util.cpp deleted file mode 100644 index a669c1dd..00000000 --- a/unittest/test_Util.cpp +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright (C) 2019-2024 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 "TestUtil.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include -#include - -#ifdef HAVE_UNISTD_H -# include -#endif - -#include - -using TestUtil::TestContext; - -namespace fs = util::filesystem; - -TEST_SUITE_BEGIN("Util"); - -TEST_CASE("Util::common_dir_prefix_length") -{ - CHECK(Util::common_dir_prefix_length("", "") == 0); - CHECK(Util::common_dir_prefix_length("/", "") == 0); - CHECK(Util::common_dir_prefix_length("", "/") == 0); - CHECK(Util::common_dir_prefix_length("/", "/") == 0); - CHECK(Util::common_dir_prefix_length("/", "/b") == 0); - CHECK(Util::common_dir_prefix_length("/a", "/") == 0); - CHECK(Util::common_dir_prefix_length("/a", "/b") == 0); - CHECK(Util::common_dir_prefix_length("/a", "/a") == 2); - CHECK(Util::common_dir_prefix_length("/a", "/a/b") == 2); - CHECK(Util::common_dir_prefix_length("/a/b", "/a") == 2); - CHECK(Util::common_dir_prefix_length("/a/b", "/a/c") == 2); - CHECK(Util::common_dir_prefix_length("/a/b", "/a/b") == 4); - CHECK(Util::common_dir_prefix_length("/a/bc", "/a/b") == 2); - CHECK(Util::common_dir_prefix_length("/a/b", "/a/bc") == 2); -} - -TEST_CASE("Util::get_relative_path") -{ -#ifdef _WIN32 - CHECK(Util::get_relative_path("C:/a", "C:/a") == "."); - CHECK(Util::get_relative_path("C:/a", "Z:/a") == "Z:/a"); - CHECK(Util::get_relative_path("C:/a/b", "C:/a") == ".."); - CHECK(Util::get_relative_path("C:/a", "C:/a/b") == "b"); - CHECK(Util::get_relative_path("C:/a", "C:/a/b/c") == "b/c"); - CHECK(Util::get_relative_path("C:/a/b", "C:/a/c") == "../c"); - CHECK(Util::get_relative_path("C:/a/b", "C:/a/c/d") == "../c/d"); - CHECK(Util::get_relative_path("C:/a/b/c", "C:/a/c/d") == "../../c/d"); - CHECK(Util::get_relative_path("C:/a/b", "C:/") == "../.."); - CHECK(Util::get_relative_path("C:/a/b", "C:/c") == "../../c"); - CHECK(Util::get_relative_path("C:/", "C:/a/b") == "a/b"); - CHECK(Util::get_relative_path("C:/a", "D:/a/b") == "D:/a/b"); -#else - CHECK(Util::get_relative_path("/a", "/a") == "."); - CHECK(Util::get_relative_path("/a/b", "/a") == ".."); - CHECK(Util::get_relative_path("/a", "/a/b") == "b"); - CHECK(Util::get_relative_path("/a", "/a/b/c") == "b/c"); - CHECK(Util::get_relative_path("/a/b", "/a/c") == "../c"); - CHECK(Util::get_relative_path("/a/b", "/a/c/d") == "../c/d"); - CHECK(Util::get_relative_path("/a/b/c", "/a/c/d") == "../../c/d"); - CHECK(Util::get_relative_path("/a/b", "/") == "../.."); - CHECK(Util::get_relative_path("/a/b", "/c") == "../../c"); - CHECK(Util::get_relative_path("/", "/a/b") == "a/b"); -#endif -} - -TEST_CASE("Util::make_relative_path") -{ - using Util::make_relative_path; - - const TestContext test_context; - - const std::string cwd = util::actual_cwd(); - const std::string actual_cwd = FMT("{}/d", cwd); -#if defined(_WIN32) || defined(__CYGWIN__) - const std::string apparent_cwd = actual_cwd; -#else - const std::string apparent_cwd = FMT("{}/s", cwd); -#endif - - REQUIRE(fs::create_directory("d")); -#ifndef _WIN32 - REQUIRE(symlink("d", "s") == 0); -#endif - REQUIRE(fs::current_path("d")); - util::setenv("PWD", apparent_cwd); - SUBCASE("No base directory") - { - CHECK(make_relative_path("", "/a", "/a", "/a/x") == "/a/x"); - } - - SUBCASE("Path matches neither actual nor apparent CWD") - { -#ifdef _WIN32 - CHECK(make_relative_path("C:/", "C:/a", "C:/b", "C:/x") == "C:/x"); -#else - CHECK(make_relative_path("/", "/a", "/b", "/x") == "/x"); -#endif - } - - SUBCASE("Match of actual CWD") - { -#ifdef _WIN32 - CHECK( - make_relative_path( - actual_cwd.substr(0, 3), actual_cwd, apparent_cwd, actual_cwd + "/x") - == "./x"); - CHECK( - make_relative_path( - actual_cwd.substr(0, 3), actual_cwd, apparent_cwd, actual_cwd + "\\x") - == ".\\x"); - CHECK( - make_relative_path( - actual_cwd.substr(0, 3), actual_cwd, apparent_cwd, actual_cwd + "\\\\x") - == ".\\\\x"); -#else - CHECK(make_relative_path("/", actual_cwd, apparent_cwd, actual_cwd + "/x") - == "./x"); -#endif - } - -#ifndef _WIN32 - SUBCASE("Match of apparent CWD") - { - CHECK(make_relative_path("/", actual_cwd, apparent_cwd, apparent_cwd + "/x") - == "./x"); - } - - SUBCASE("Match if using resolved (using realpath(3)) path") - { - CHECK(make_relative_path("/", actual_cwd, actual_cwd, apparent_cwd + "/x") - == "./x"); - } -#endif -} - -TEST_CASE("Util::normalize_abstract_absolute_path") -{ - CHECK(Util::normalize_abstract_absolute_path("") == ""); - CHECK(Util::normalize_abstract_absolute_path(".") == "."); - CHECK(Util::normalize_abstract_absolute_path("..") == ".."); - CHECK(Util::normalize_abstract_absolute_path("...") == "..."); - CHECK(Util::normalize_abstract_absolute_path("x/./") == "x/./"); - -#ifdef _WIN32 - CHECK(Util::normalize_abstract_absolute_path("c:/") == "c:/"); - CHECK(Util::normalize_abstract_absolute_path("c:\\") == "c:/"); - CHECK(Util::normalize_abstract_absolute_path("c:/.") == "c:/"); - CHECK(Util::normalize_abstract_absolute_path("c:\\..") == "c:/"); - CHECK(Util::normalize_abstract_absolute_path("c:\\x/..") == "c:/"); - CHECK(Util::normalize_abstract_absolute_path("c:\\x/./y\\..\\\\z") - == "c:/x/z"); -#else - CHECK(Util::normalize_abstract_absolute_path("/") == "/"); - CHECK(Util::normalize_abstract_absolute_path("/.") == "/"); - CHECK(Util::normalize_abstract_absolute_path("/..") == "/"); - CHECK(Util::normalize_abstract_absolute_path("/./") == "/"); - CHECK(Util::normalize_abstract_absolute_path("//") == "/"); - CHECK(Util::normalize_abstract_absolute_path("/../x") == "/x"); - CHECK(Util::normalize_abstract_absolute_path("/x/./y/z") == "/x/y/z"); - CHECK(Util::normalize_abstract_absolute_path("/x/../y/z/") == "/y/z"); - CHECK(Util::normalize_abstract_absolute_path("/x/.../y/z") == "/x/.../y/z"); - CHECK(Util::normalize_abstract_absolute_path("/x/yyy/../zz") == "/x/zz"); - CHECK(Util::normalize_abstract_absolute_path("//x/yyy///.././zz") == "/x/zz"); -#endif -} - -TEST_CASE("Util::normalize_concrete_absolute_path") -{ -#if !defined(_WIN32) && !defined(__CYGWIN__) - TestContext test_context; - - util::write_file("file", ""); - REQUIRE(fs::create_directories("dir1/dir2")); - REQUIRE(symlink("dir1/dir2", "symlink") == 0); - const auto cwd = util::actual_cwd(); - - CHECK(Util::normalize_concrete_absolute_path(FMT("{}/file", cwd)) - == FMT("{}/file", cwd)); - CHECK(Util::normalize_concrete_absolute_path(FMT("{}/dir1/../file", cwd)) - == FMT("{}/file", cwd)); - CHECK(Util::normalize_concrete_absolute_path(FMT("{}/symlink/../file", cwd)) - == FMT("{}/symlink/../file", cwd)); -#endif -} - -TEST_SUITE_END(); diff --git a/unittest/test_argprocessing.cpp b/unittest/test_argprocessing.cpp index 19a6b91e..3de398dd 100644 --- a/unittest/test_argprocessing.cpp +++ b/unittest/test_argprocessing.cpp @@ -21,10 +21,11 @@ #include #include #include -#include #include #include +#include #include +#include #include #include #include @@ -34,8 +35,11 @@ #include +namespace fs = util::filesystem; + using core::Statistic; using TestUtil::TestContext; +using pstr = util::PathString; namespace { @@ -46,34 +50,11 @@ get_root() return "/"; #else char volume[4]; // "C:\" - GetVolumePathName(util::actual_cwd().c_str(), volume, sizeof(volume)); - volume[2] = '/'; // Since base directory is normalized to forward slashes + GetVolumePathName(pstr(*fs::current_path()).c_str(), volume, sizeof(volume)); return volume; #endif } -std::string -get_posix_path(const std::string& path) -{ -#ifndef _WIN32 - return path; -#else - std::string posix; - - // /-escape volume. - if (path[1] == ':' - && ((path[0] >= 'A' && path[0] <= 'Z') - || (path[0] >= 'a' && path[0] <= 'z'))) { - posix = "/" + path; - } else { - posix = path; - } - // Convert slashes. - std::replace(posix.begin(), posix.end(), '\\', '/'); - return posix; -#endif -} - } // namespace TEST_SUITE_BEGIN("argprocessing"); @@ -312,7 +293,11 @@ TEST_CASE("sysroot_should_be_rewritten_if_basedir_is_used") const ProcessArgsResult result = process_args(ctx); CHECK(!result.error); - CHECK(result.preprocessor_args[1] == "--sysroot=./foo/bar"); +#ifdef _WIN32 + CHECK(result.preprocessor_args[1] == "--sysroot=foo\\bar"); +#else + CHECK(result.preprocessor_args[1] == "--sysroot=foo/bar"); +#endif } TEST_CASE( @@ -330,7 +315,7 @@ TEST_CASE( const ProcessArgsResult result = process_args(ctx); CHECK(!result.error); CHECK(result.preprocessor_args[1] == "--sysroot"); - CHECK(result.preprocessor_args[2] == "./foo"); + CHECK(result.preprocessor_args[2] == "foo"); } TEST_CASE("MF_flag_with_immediate_argument_should_work_as_last_argument") @@ -464,9 +449,10 @@ TEST_CASE( const ProcessArgsResult result = process_args(ctx); CHECK(!result.error); - CHECK(result.preprocessor_args[2] == "./foo"); + CHECK(result.preprocessor_args[2] == "foo"); } +#ifndef _WIN32 TEST_CASE("isystem_flag_with_concat_arg_should_be_rewritten_if_basedir_is_used") { TestContext test_context; @@ -474,15 +460,14 @@ TEST_CASE("isystem_flag_with_concat_arg_should_be_rewritten_if_basedir_is_used") Context ctx; util::write_file("foo.c", ""); - ctx.config.set_base_dir("/"); // posix - // Windows path doesn't work concatenated. - std::string cwd = get_posix_path(ctx.actual_cwd); + ctx.config.set_base_dir("/"); + std::string cwd = ctx.actual_cwd; std::string arg_string = FMT("cc -isystem{}/foo -c foo.c", cwd); ctx.orig_args = Args::from_string(arg_string); const ProcessArgsResult result = process_args(ctx); CHECK(!result.error); - CHECK(result.preprocessor_args[1] == "-isystem./foo"); + CHECK(result.preprocessor_args[1] == "-isystemfoo"); } TEST_CASE("I_flag_with_concat_arg_should_be_rewritten_if_basedir_is_used") @@ -492,16 +477,16 @@ TEST_CASE("I_flag_with_concat_arg_should_be_rewritten_if_basedir_is_used") Context ctx; util::write_file("foo.c", ""); - ctx.config.set_base_dir("/"); // posix - // Windows path doesn't work concatenated. - std::string cwd = get_posix_path(ctx.actual_cwd); + ctx.config.set_base_dir("/"); + std::string cwd = *fs::current_path(); std::string arg_string = FMT("cc -I{}/foo -c foo.c", cwd); ctx.orig_args = Args::from_string(arg_string); const ProcessArgsResult result = process_args(ctx); CHECK(!result.error); - CHECK(result.preprocessor_args[1] == "-I./foo"); + CHECK(result.preprocessor_args[1] == "-Ifoo"); } +#endif // _WIN32 TEST_CASE("debug_flag_order_with_known_option_first") { @@ -719,7 +704,8 @@ TEST_CASE("-x") // MSVC's /U option, so disable the test case there. This will be possible to // improve when/if a compiler abstraction is introduced (issue #956). TEST_CASE("MSVC options" - * doctest::skip(util::starts_with(util::actual_cwd(), "/U"))) + * doctest::skip(util::starts_with(fs::current_path()->string(), + "/U"))) { TestContext test_context; Context ctx; diff --git a/unittest/test_ccache.cpp b/unittest/test_ccache.cpp index 9a33346d..ecf21635 100644 --- a/unittest/test_ccache.cpp +++ b/unittest/test_ccache.cpp @@ -195,7 +195,7 @@ TEST_CASE("guess_compiler") #ifndef _WIN32 SUBCASE("Follow symlink to actual compiler") { - const auto cwd = fs::path(util::actual_cwd()); + const auto cwd = *fs::current_path(); util::write_file(cwd / "gcc", ""); CHECK(fs::create_symlink("gcc", cwd / "intermediate")); const auto cc = cwd / "cc"; @@ -206,7 +206,7 @@ TEST_CASE("guess_compiler") SUBCASE("Classify clang-cl symlink to clang") { - const auto cwd = fs::path(util::actual_cwd()); + const auto cwd = *fs::current_path(); util::write_file(cwd / "clang", ""); const auto clang_cl = cwd / "clang-cl"; CHECK(fs::create_symlink("clang", clang_cl)); diff --git a/unittest/test_core_StatsLog.cpp b/unittest/test_core_StatsLog.cpp index 34aaac1d..db262d0f 100644 --- a/unittest/test_core_StatsLog.cpp +++ b/unittest/test_core_StatsLog.cpp @@ -18,7 +18,6 @@ #include "TestUtil.hpp" -#include #include #include diff --git a/unittest/test_storage_local_StatsFile.cpp b/unittest/test_storage_local_StatsFile.cpp index 89f04995..ae0721b1 100644 --- a/unittest/test_storage_local_StatsFile.cpp +++ b/unittest/test_storage_local_StatsFile.cpp @@ -18,7 +18,6 @@ #include "TestUtil.hpp" -#include #include #include #include diff --git a/unittest/test_util_LockFile.cpp b/unittest/test_util_LockFile.cpp index 0953a4d8..efdec653 100644 --- a/unittest/test_util_LockFile.cpp +++ b/unittest/test_util_LockFile.cpp @@ -18,7 +18,6 @@ #include "TestUtil.hpp" -#include #include #include #include diff --git a/unittest/test_util_Tokenizer.cpp b/unittest/test_util_Tokenizer.cpp index 3daedc48..d8f34a16 100644 --- a/unittest/test_util_Tokenizer.cpp +++ b/unittest/test_util_Tokenizer.cpp @@ -16,7 +16,6 @@ // this program; if not, write to the Free Software Foundation, Inc., 51 // Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -#include #include #include diff --git a/unittest/test_util_path.cpp b/unittest/test_util_path.cpp index e839c8ae..76738ad8 100644 --- a/unittest/test_util_path.cpp +++ b/unittest/test_util_path.cpp @@ -16,7 +16,11 @@ // this program; if not, write to the Free Software Foundation, Inc., 51 // Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -#include +#include "TestUtil.hpp" + +#include +#include +#include #include #include @@ -24,6 +28,11 @@ #include // https://github.com/doctest/doctest/issues/618 +namespace fs = util::filesystem; + +using pstr = util::PathString; +using TestUtil::TestContext; + TEST_CASE("util::add_exe_suffix") { CHECK(util::add_exe_suffix("foo") == "foo.exe"); @@ -56,9 +65,66 @@ TEST_CASE("util::is_dev_null_path") #endif } +TEST_CASE("util::make_relative_path") +{ + using util::make_relative_path; + + const TestContext test_context; + + const std::string cwd = pstr(*fs::current_path()).str(); + const std::string actual_cwd = FMT("{}/d", cwd); +#if defined(_WIN32) || defined(__CYGWIN__) + const std::string apparent_cwd = actual_cwd; +#else + const std::string apparent_cwd = FMT("{}/s", cwd); +#endif + + REQUIRE(fs::create_directory("d")); +#ifndef _WIN32 + REQUIRE(fs::create_symlink("d", "s")); +#endif + REQUIRE(fs::current_path("d")); + util::setenv("PWD", apparent_cwd); + + SUBCASE("Path matches neither actual nor apparent CWD") + { +#ifdef _WIN32 + CHECK(make_relative_path("C:/a", "C:/b", "C:/x") == "C:/x"); +#else + CHECK(make_relative_path("/a", "/b", "/x") == "/x"); +#endif + } + + SUBCASE("Match of actual CWD") + { + CHECK(make_relative_path(actual_cwd, apparent_cwd, actual_cwd + "/x") + == "x"); +#ifdef _WIN32 + CHECK(make_relative_path(actual_cwd, apparent_cwd, actual_cwd + "\\x") + == "x"); + CHECK(make_relative_path(actual_cwd, apparent_cwd, actual_cwd + "\\\\x") + == "x"); +#endif + } + +#ifndef _WIN32 + SUBCASE("Match of apparent CWD") + { + CHECK(make_relative_path(actual_cwd, apparent_cwd, apparent_cwd + "/x") + == "x"); + } + + SUBCASE("Match if using resolved (using realpath(3)) path") + { + CHECK(make_relative_path(actual_cwd, actual_cwd, apparent_cwd + "/x") + == "x"); + } +#endif +} + TEST_CASE("util::path_starts_with") { - CHECK(!util::path_starts_with("", "")); + CHECK(util::path_starts_with("", "")); CHECK(!util::path_starts_with("", "/")); CHECK(util::path_starts_with("/foo/bar", "/foo")); CHECK(!util::path_starts_with("/batz/bar", "/foo"));