From 451d92c801d6463bcc54c090b871a1d77f26d9d4 Mon Sep 17 00:00:00 2001 From: Joel Rosdahl Date: Fri, 10 Jun 2022 16:17:03 +0200 Subject: [PATCH] feat: Support masquerading as a compiler via copy or hard link Setting up ccache to masquerade as a compiler has always meant using symbolic links, but there is no technical reason why that has to be the case. This commit adds support for masquerading via a copy or hard link of the ccache executable as well. This is mostly useful on platforms or file systems where symbolic links are not supported. --- doc/INSTALL.md | 50 +++++++------- doc/MANUAL.adoc | 57 ++++++++------- src/Util.cpp | 23 +++---- src/Util.hpp | 9 +-- src/ccache.cpp | 30 +++----- src/ccache.hpp | 5 +- src/core/mainoptions.cpp | 16 ++--- src/core/mainoptions.hpp | 5 +- src/execute.cpp | 94 +++++++++++++------------ src/execute.hpp | 14 ++-- src/hashutil.cpp | 2 +- src/storage/secondary/HttpStorage.cpp | 2 +- src/util/file.hpp | 1 + test/suites/masquerading.bash | 31 +++++++-- unittest/test_Util.cpp | 27 ++++---- unittest/test_ccache.cpp | 99 +++++++++++++-------------- 16 files changed, 238 insertions(+), 227 deletions(-) diff --git a/doc/INSTALL.md b/doc/INSTALL.md index 5ce8c2afe..641d01697 100644 --- a/doc/INSTALL.md +++ b/doc/INSTALL.md @@ -63,26 +63,30 @@ You can set the installation directory to e.g. `/usr` by adding where the secondary configuration file should be located to e.g. `/etc` by adding `-DCMAKE_INSTALL_SYSCONFDIR=/etc`. -There are two ways to use ccache. You can either prefix your compilation -commands with `ccache` or you can create a symbolic link (named as your -compiler) to ccache. The first method is most convenient if you just want to -try out ccache or wish to use it for some specific projects. The second method -is most useful for when you wish to use ccache for all your compilations. - -To install for usage by the first method just copy ccache to somewhere in your -path. - -To install for the second method, do something like this: - - cp ccache /usr/local/bin/ - ln -s ccache /usr/local/bin/gcc - ln -s ccache /usr/local/bin/g++ - ln -s ccache /usr/local/bin/cc - ln -s ccache /usr/local/bin/c++ - -And so forth. This will work as long as `/usr/local/bin` comes before the path -to the compiler (which is usually in `/usr/bin`). After installing you may wish -to run `which gcc` to make sure that the correct link is being used. - -NOTE: Do not use a hard link, use a symbolic link. A hard link will cause -"interesting" problems. +There are two different ways to use ccache to cache a compilation: + +1. Prefix your compilation command with `ccache`. This method is most convenient + if you just want to try out ccache or wish to use it for some specific + projects. +2. Let ccache masquerade as the compiler. This method is most useful when you + wish to use ccache for all your compilations. To do this, create a symbolic + link to ccache named as the compiler. For example, here is set up ccache to + masquerade as `gcc` and `g++`: ++ +------------------------------------------------------------------------------- +cp ccache /usr/local/bin/ +ln -s ccache /usr/local/bin/gcc +ln -s ccache /usr/local/bin/g++ +------------------------------------------------------------------------------- ++ +On platforms that don't support symbolic links you can simply copy ccache to the +compiler name instead for a similar effect: ++ +------------------------------------------------------------------------------- +cp ccache /usr/local/bin/gcc +cp ccache /usr/local/bin/g++ +------------------------------------------------------------------------------- ++ +And so forth. This will work as long as the directory with symbolic links or +ccache copies comes before the directory with the compiler (typically +`/usr/bin`) in `PATH`. diff --git a/doc/MANUAL.adoc b/doc/MANUAL.adoc index 99ffa6b97..248d65459 100644 --- a/doc/MANUAL.adoc +++ b/doc/MANUAL.adoc @@ -11,7 +11,7 @@ ccache - a fast C/C++ compiler cache [verse] *ccache* [_options_] *ccache* _compiler_ [_compiler options_] -_compiler_ [_compiler options_] (via symbolic link) +_compiler_ [_compiler options_] (ccache masquerading as the compiler) == Description @@ -29,43 +29,50 @@ ccache changes the output of your compiler, please let us know. == Run modes -There are two ways to use ccache. You can either prefix your compilation -commands with `ccache` or you can let ccache masquerade as the compiler by -creating a symbolic link (named as the compiler) to ccache. The first method is -most convenient if you just want to try out ccache or wish to use it for some -specific projects. The second method is most useful for when you wish to use -ccache for all your compilations. - -To use the first method, just make sure that `ccache` is in your `PATH`. - -To use the symlinks method, do something like this: +There are two different ways to use ccache to cache a compilation: +1. Prefix your compilation command with `ccache`. This method is most convenient + if you just want to try out ccache or wish to use it for some specific + projects. Example: ++ +------------------------------------------------------------------------------- +ccache gcc -c example.c +------------------------------------------------------------------------------- ++ +2. Let ccache masquerade as the compiler. This method is most useful when you + wish to use ccache for all your compilations. To do this, create a symbolic + link to ccache named as the compiler. For example, here is set up ccache to + masquerade as `gcc` and `g++`: ++ ------------------------------------------------------------------------------- cp ccache /usr/local/bin/ ln -s ccache /usr/local/bin/gcc ln -s ccache /usr/local/bin/g++ -ln -s ccache /usr/local/bin/cc -ln -s ccache /usr/local/bin/c++ ------------------------------------------------------------------------------- - -And so forth. This will work as long as the directory with symlinks comes -before the path to the compiler (which is usually in `/usr/bin`). After -installing you may wish to run "`which gcc`" to make sure that the correct link -is being used. - ++ +On platforms that don't support symbolic links you can simply copy ccache to the +compiler name instead for a similar effect: ++ +------------------------------------------------------------------------------- +cp ccache /usr/local/bin/gcc +cp ccache /usr/local/bin/g++ +------------------------------------------------------------------------------- ++ +And so forth. This will work as long as the directory with symbolic links or +ccache copies comes before the directory with the compiler (typically +`/usr/bin`) in `PATH`. ++ WARNING: The technique of letting ccache masquerade as the compiler works well, but currently doesn't interact well with other tools that do the same thing. See _<>_. -WARNING: Use a symbolic links for masquerading, not hard links. - == Command line options -These command line options only apply when you invoke ccache as "`ccache`". -When invoked as a compiler (via a symlink as described in the previous -section), the normal compiler options apply and you should refer to the -compiler's documentation. +These command line options only apply when you invoke ccache as "`ccache`". When +ccache masquerades as a compiler (as described in the previous section), the +normal compiler options apply and you should refer to the compiler's +documentation. === Common options diff --git a/src/Util.cpp b/src/Util.cpp index c2ce61dca..19e147999 100644 --- a/src/Util.cpp +++ b/src/Util.cpp @@ -800,6 +800,16 @@ is_absolute_path_with_prefix(std::string_view path) return std::nullopt; } +bool +is_ccache_executable(const std::string_view path) +{ + std::string name(Util::base_name(path)); +#ifdef _WIN32 + name = Util::to_lowercase(name); +#endif + return util::starts_with(name, "ccache"); +} + #if defined(HAVE_LINUX_FS_H) || defined(HAVE_STRUCT_STATFS_F_FSTYPENAME) int is_nfs_fd(int fd, bool* is_nfs) @@ -1239,19 +1249,6 @@ rename(const std::string& oldpath, const std::string& newpath) #endif } -bool -same_program_name(std::string_view program_name, - std::string_view canonical_program_name) -{ -#ifdef _WIN32 - std::string lowercase_program_name = Util::to_lowercase(program_name); - return lowercase_program_name == canonical_program_name - || lowercase_program_name == FMT("{}.exe", canonical_program_name); -#else - return program_name == canonical_program_name; -#endif -} - void send_to_fd(const Context& ctx, const std::string& text, int fd) { diff --git a/src/Util.hpp b/src/Util.hpp index c8231107f..28597d49d 100644 --- a/src/Util.hpp +++ b/src/Util.hpp @@ -223,6 +223,9 @@ int_to_big_endian(int8_t value, uint8_t* buffer) // point. std::optional is_absolute_path_with_prefix(std::string_view path); +// Detmine if `path` refers to a ccache executable. +bool is_ccache_executable(std::string_view path); + // Test if a file is on nfs. // // Sets is_nfs to the result if fstatfs is available and no error occurred. @@ -326,12 +329,6 @@ std::string_view remove_extension(std::string_view path); // error. void rename(const std::string& oldpath, const std::string& newpath); -// Detmine if `program_name` is equal to `canonical_program_name`. On Windows, -// this means performing a case insensitive equality check with and without a -// ".exe" suffix. On non-Windows, it is a simple equality check. -bool same_program_name(std::string_view program_name, - std::string_view canonical_program_name); - // Send `text` to file descriptor `fd`, optionally stripping ANSI color // sequences if `ctx.args_info.strip_diagnostics_colors` is true and rewriting // paths to absolute if `ctx.config.absolute_paths_in_stderr` is true. Throws diff --git a/src/ccache.cpp b/src/ccache.cpp index 630513818..f1f5f0609 100644 --- a/src/ccache.cpp +++ b/src/ccache.cpp @@ -78,11 +78,6 @@ #include #include -#ifndef MYNAME -# define MYNAME "ccache" -#endif -const char CCACHE_NAME[] = MYNAME; - using core::Statistic; // This is a string that identifies the current "version" of the hash sum @@ -151,7 +146,7 @@ add_prefix(const Context& ctx, Args& args, const std::string& prefix_command) Args prefix; for (const auto& word : Util::split_into_strings(prefix_command, " ")) { - std::string path = find_executable(ctx, word, CCACHE_NAME); + std::string path = find_executable(ctx, word, ctx.orig_args[0]); if (path.empty()) { throw core::Fatal("{}: {}", word, strerror(errno)); } @@ -1257,7 +1252,7 @@ hash_nvcc_host_compiler(const Context& ctx, TRY(hash_compiler(ctx, hash, st, path, false)); } } else { - std::string path = find_executable(ctx, compiler, CCACHE_NAME); + std::string path = find_executable(ctx, compiler, ctx.orig_args[0]); if (!path.empty()) { auto st = Stat::stat(path, Stat::OnError::log); TRY(hash_compiler(ctx, hash, st, ccbin, false)); @@ -1918,8 +1913,7 @@ from_cache(Context& ctx, FromCacheCallMode mode, const Digest& result_key) } // Find the real compiler and put it into ctx.orig_args[0]. We just search the -// PATH to find an executable of the same name that isn't a link to ourselves. -// Pass find_executable function as second parameter. +// PATH to find an executable of the same name that isn't ourselves. void find_compiler(Context& ctx, const FindExecutableFunction& find_executable_function) @@ -1929,8 +1923,7 @@ find_compiler(Context& ctx, // ccache ccache gcc --> 2 size_t compiler_pos = 0; while (compiler_pos < ctx.orig_args.size() - && Util::same_program_name( - Util::base_name(ctx.orig_args[compiler_pos]), CCACHE_NAME)) { + && Util::is_ccache_executable(ctx.orig_args[compiler_pos])) { ++compiler_pos; } @@ -1946,17 +1939,14 @@ find_compiler(Context& ctx, const std::string resolved_compiler = util::is_full_path(compiler) ? compiler - : find_executable_function(ctx, compiler, CCACHE_NAME); + : find_executable_function(ctx, compiler, ctx.orig_args[0]); if (resolved_compiler.empty()) { throw core::Fatal("Could not find compiler \"{}\" in PATH", compiler); } - if (Util::same_program_name(Util::base_name(resolved_compiler), - CCACHE_NAME)) { - throw core::Fatal( - "Recursive invocation (the name of the ccache binary must be \"{}\")", - CCACHE_NAME); + if (Util::is_ccache_executable(resolved_compiler)) { + throw core::Fatal("Recursive invocation of ccache"); } ctx.orig_args.pop_front(compiler_pos); @@ -2396,11 +2386,9 @@ int ccache_main(int argc, const char* const* argv) { try { - // Check if we are being invoked as "ccache". - std::string program_name(Util::base_name(argv[0])); - if (Util::same_program_name(program_name, CCACHE_NAME)) { + if (Util::is_ccache_executable(argv[0])) { if (argc < 2) { - PRINT_RAW(stderr, core::get_usage_text()); + PRINT_RAW(stderr, core::get_usage_text(Util::base_name(argv[0]))); exit(EXIT_FAILURE); } // If the first argument isn't an option, then assume we are being diff --git a/src/ccache.hpp b/src/ccache.hpp index 7590ad584..510563a4b 100644 --- a/src/ccache.hpp +++ b/src/ccache.hpp @@ -1,5 +1,5 @@ // Copyright (C) 2002-2007 Andrew Tridgell -// Copyright (C) 2009-2021 Joel Rosdahl and other contributors +// Copyright (C) 2009-2022 Joel Rosdahl and other contributors // // See doc/AUTHORS.adoc for a complete list of contributors. // @@ -27,13 +27,12 @@ class Context; -extern const char CCACHE_NAME[]; extern const char CCACHE_VERSION[]; using FindExecutableFunction = std::function; + const std::string& exclude_path)>; int ccache_main(int argc, const char* const* argv); diff --git a/src/core/mainoptions.cpp b/src/core/mainoptions.cpp index 830d29006..b7466c844 100644 --- a/src/core/mainoptions.cpp +++ b/src/core/mainoptions.cpp @@ -82,7 +82,7 @@ constexpr const char USAGE_TEXT[] = R"(Usage: {0} [options] {0} compiler [compiler options] - compiler [compiler options] (via symbolic link) + compiler [compiler options] (ccache masquerading as the compiler) Common options: -c, --cleanup delete old files and recalculate size counters @@ -282,16 +282,16 @@ trim_dir(const std::string& dir, } static std::string -get_version_text() +get_version_text(const std::string_view ccache_name) { return FMT( - VERSION_TEXT, CCACHE_NAME, CCACHE_VERSION, storage::get_features()); + VERSION_TEXT, ccache_name, CCACHE_VERSION, storage::get_features()); } std::string -get_usage_text() +get_usage_text(const std::string_view ccache_name) { - return FMT(USAGE_TEXT, CCACHE_NAME); + return FMT(USAGE_TEXT, ccache_name); } enum { @@ -499,7 +499,7 @@ process_main_options(int argc, const char* const* argv) } case 'h': // --help - PRINT(stdout, USAGE_TEXT, CCACHE_NAME, CCACHE_NAME); + PRINT(stdout, USAGE_TEXT, Util::base_name(argv[0])); return EXIT_SUCCESS; case 'k': // --get-config @@ -580,7 +580,7 @@ process_main_options(int argc, const char* const* argv) break; case 'V': // --version - PRINT_RAW(stdout, get_version_text()); + PRINT_RAW(stdout, get_version_text(Util::base_name(argv[0]))); break; case 'x': // --show-compression @@ -618,7 +618,7 @@ process_main_options(int argc, const char* const* argv) break; default: - PRINT(stderr, USAGE_TEXT, CCACHE_NAME, CCACHE_NAME); + PRINT(stderr, USAGE_TEXT, Util::base_name(argv[0])); return EXIT_FAILURE; } } diff --git a/src/core/mainoptions.hpp b/src/core/mainoptions.hpp index e0f33e60b..2cf84cd8f 100644 --- a/src/core/mainoptions.hpp +++ b/src/core/mainoptions.hpp @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Joel Rosdahl and other contributors +// Copyright (C) 2021-2022 Joel Rosdahl and other contributors // // See doc/AUTHORS.adoc for a complete list of contributors. // @@ -19,12 +19,13 @@ #pragma once #include +#include namespace core { // The main program when not doing a compile. int process_main_options(int argc, const char* const* argv); -std::string get_usage_text(); +std::string get_usage_text(std::string_view ccache_name); } // namespace core diff --git a/src/execute.cpp b/src/execute.cpp index 801349b83..850bae4fd 100644 --- a/src/execute.cpp +++ b/src/execute.cpp @@ -74,10 +74,10 @@ execute_noreturn(const char* const* argv, const std::string& temp_dir) std::string win32getshell(const std::string& path) { - const char* path_env = getenv("PATH"); + const char* path_list = getenv("PATH"); std::string sh; - if (Util::to_lowercase(Util::get_extension(path)) == ".sh" && path_env) { - sh = find_executable_in_path("sh.exe", "", path_env); + if (Util::to_lowercase(Util::get_extension(path)) == ".sh" && path_list) { + sh = find_executable_in_path("sh.exe", path_list); } if (sh.empty() && getenv("CCACHE_DETECT_SHEBANG")) { // Detect shebang. @@ -85,8 +85,8 @@ win32getshell(const std::string& path) if (fp) { char buf[10] = {0}; fgets(buf, sizeof(buf) - 1, fp.get()); - if (std::string(buf) == "#!/bin/sh" && path_env) { - sh = find_executable_in_path("sh.exe", "", path_env); + if (std::string(buf) == "#!/bin/sh" && path_list) { + sh = find_executable_in_path("sh.exe", path_list); } } } @@ -249,72 +249,70 @@ execute_noreturn(const char* const* argv, const std::string& /*temp_dir*/) std::string find_executable(const Context& ctx, const std::string& name, - const std::string& exclude_name) + const std::string& exclude_path) { if (util::is_absolute_path(name)) { return name; } - std::string path = ctx.config.path(); - if (path.empty()) { - path = getenv("PATH"); + std::string path_list = ctx.config.path(); + if (path_list.empty()) { + path_list = getenv("PATH"); } - if (path.empty()) { + if (path_list.empty()) { LOG_RAW("No PATH variable"); return {}; } - return find_executable_in_path(name, exclude_name, path); + return find_executable_in_path(name, path_list, exclude_path); } std::string find_executable_in_path(const std::string& name, - const std::string& exclude_name, - const std::string& path) + const std::string& path_list, + std::optional exclude_path) { - if (path.empty()) { + if (path_list.empty()) { return {}; } - // Search the path looking for the first compiler of the right name that isn't - // us. - for (const std::string& dir : util::split_path_list(path)) { + const auto real_exclude_path = + exclude_path ? Util::real_path(*exclude_path) : ""; + + // Search the path list looking for the first compiler of the right name that + // isn't us. + for (const std::string& dir : util::split_path_list(path_list)) { + const std::vector candidates = { + FMT("{}/{}", dir, name), #ifdef _WIN32 - char namebuf[MAX_PATH]; - int ret = SearchPath( - dir.c_str(), name.c_str(), nullptr, sizeof(namebuf), namebuf, nullptr); - if (!ret) { - std::string exename = FMT("{}.exe", name); - ret = SearchPath(dir.c_str(), - exename.c_str(), - nullptr, - sizeof(namebuf), - namebuf, - nullptr); - } - (void)exclude_name; - if (ret) { - return namebuf; - } + FMT("{}/{}.exe", dir, name), +#endif + }; + for (const auto& candidate : candidates) { + // A valid candidate: + // + // 1. Must exist (e.g., should not be a broken symlink) and be an + // executable. + // 2. Must not resolve to the same program as argv[0] (i.e., + // exclude_path). This can happen if ccache is masquerading as the + // compiler (with or without using a symlink). + // 3. As an extra safety measure: must not be a ccache executable after + // resolving symlinks. This can happen if the candidate compiler is a + // symlink to another ccache executeable. + const bool candidate_exists = +#ifdef _WIN32 + Stat::stat(candidate); #else - ASSERT(!exclude_name.empty()); - std::string fname = FMT("{}/{}", dir, name); - auto st1 = Stat::lstat(fname); - auto st2 = Stat::stat(fname); - // Look for a normal executable file. - if (st1 && st2 && st2.is_regular() && access(fname.c_str(), X_OK) == 0) { - if (st1.is_symlink()) { - std::string real_path = Util::real_path(fname, true); - if (Util::base_name(real_path) == exclude_name) { - // It's a link to "ccache"! - continue; + access(candidate.c_str(), X_OK) == 0; +#endif + if (candidate_exists) { + const auto real_candidate = Util::real_path(candidate); + if ((real_exclude_path.empty() || real_candidate != real_exclude_path) + && !Util::is_ccache_executable(real_candidate)) { + return candidate; } } - - // Found it! - return fname; } -#endif } return {}; diff --git a/src/execute.hpp b/src/execute.hpp index 8e4922639..c44a465ab 100644 --- a/src/execute.hpp +++ b/src/execute.hpp @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Joel Rosdahl and other contributors +// Copyright (C) 2020-2022 Joel Rosdahl and other contributors // // See doc/AUTHORS.adoc for a complete list of contributors. // @@ -20,6 +20,7 @@ #include "Fd.hpp" +#include #include class Context; @@ -29,14 +30,15 @@ int execute(Context& ctx, const char* const* argv, Fd&& fd_out, Fd&& fd_err); void execute_noreturn(const char* const* argv, const std::string& temp_dir); // Find an executable named `name` in `$PATH`. Exclude any executables that are -// links to `exclude_name`. +// links to `exclude_path`. std::string find_executable(const Context& ctx, const std::string& name, - const std::string& exclude_name); + const std::string& exclude_path); -std::string find_executable_in_path(const std::string& name, - const std::string& exclude_name, - const std::string& path); +std::string +find_executable_in_path(const std::string& name, + const std::string& path_list, + std::optional exclude_path = std::nullopt); #ifdef _WIN32 std::string win32getshell(const std::string& path); diff --git a/src/hashutil.cpp b/src/hashutil.cpp index 7e0ca23f7..4598ec8d7 100644 --- a/src/hashutil.cpp +++ b/src/hashutil.cpp @@ -418,7 +418,7 @@ hash_command_output(Hash& hash, STARTUPINFO si; memset(&si, 0x00, sizeof(si)); - std::string path = find_executable_in_path(args[0], "", getenv("PATH")); + std::string path = find_executable_in_path(args[0], getenv("PATH")); if (path.empty()) { path = args[0]; } diff --git a/src/storage/secondary/HttpStorage.cpp b/src/storage/secondary/HttpStorage.cpp index 671a80139..d95ba62c1 100644 --- a/src/storage/secondary/HttpStorage.cpp +++ b/src/storage/secondary/HttpStorage.cpp @@ -108,7 +108,7 @@ HttpStorageBackend::HttpStorageBackend(const Params& params) } m_http_client.set_default_headers({ - {"User-Agent", FMT("{}/{}", CCACHE_NAME, CCACHE_VERSION)}, + {"User-Agent", FMT("ccache/{}", CCACHE_VERSION)}, }); m_http_client.set_keep_alive(true); diff --git a/src/util/file.hpp b/src/util/file.hpp index d9adfbbda..61a66bd1e 100644 --- a/src/util/file.hpp +++ b/src/util/file.hpp @@ -18,6 +18,7 @@ #pragma once +#include #include #include diff --git a/test/suites/masquerading.bash b/test/suites/masquerading.bash index bfba17699..daec9587f 100644 --- a/test/suites/masquerading.bash +++ b/test/suites/masquerading.bash @@ -11,23 +11,24 @@ SUITE_masquerading_PROBE() { } SUITE_masquerading_SETUP() { - ln -s "$CCACHE" $COMPILER_BIN generate_code 1 test1.c } SUITE_masquerading() { +if ! $HOST_OS_WINDOWS && ! $HOST_OS_CYGWIN; then # ------------------------------------------------------------------------- TEST "Masquerading via symlink, relative path" + ln -s "$CCACHE" $COMPILER_BIN $COMPILER -c -o reference_test1.o test1.c - ./$COMPILER_BIN $COMPILER_ARGS -c test1.c + PATH="${PWD}:${PATH}" ./$COMPILER_BIN $COMPILER_ARGS -c test1.c expect_stat preprocessed_cache_hit 0 expect_stat cache_miss 1 expect_stat files_in_cache 1 expect_equal_object_files reference_test1.o test1.o - ./$COMPILER_BIN $COMPILER_ARGS -c test1.c + PATH="${PWD}:${PATH}" ./$COMPILER_BIN $COMPILER_ARGS -c test1.c expect_stat preprocessed_cache_hit 1 expect_stat cache_miss 1 expect_stat files_in_cache 1 @@ -36,15 +37,35 @@ SUITE_masquerading() { # ------------------------------------------------------------------------- TEST "Masquerading via symlink, absolute path" + ln -s "$CCACHE" $COMPILER_BIN + $COMPILER -c -o reference_test1.o test1.c + + PATH="${PWD}:${PATH}" $PWD/$COMPILER_BIN $COMPILER_ARGS -c test1.c + expect_stat preprocessed_cache_hit 0 + expect_stat cache_miss 1 + expect_stat files_in_cache 1 + expect_equal_object_files reference_test1.o test1.o + + PATH="${PWD}:${PATH}" $PWD/$COMPILER_BIN $COMPILER_ARGS -c test1.c + expect_stat preprocessed_cache_hit 1 + expect_stat cache_miss 1 + expect_stat files_in_cache 1 + expect_equal_object_files reference_test1.o test1.o +fi + + # ------------------------------------------------------------------------- + TEST "Masquerading via copy or hard link" + + cp "$CCACHE" $COMPILER_BIN $COMPILER -c -o reference_test1.o test1.c - $PWD/$COMPILER_BIN $COMPILER_ARGS -c test1.c + PATH="${PWD}:${PATH}" ./$COMPILER_BIN $COMPILER_ARGS -c test1.c expect_stat preprocessed_cache_hit 0 expect_stat cache_miss 1 expect_stat files_in_cache 1 expect_equal_object_files reference_test1.o test1.o - $PWD/$COMPILER_BIN $COMPILER_ARGS -c test1.c + PATH="${PWD}:${PATH}" ./$COMPILER_BIN $COMPILER_ARGS -c test1.c expect_stat preprocessed_cache_hit 1 expect_stat cache_miss 1 expect_stat files_in_cache 1 diff --git a/unittest/test_Util.cpp b/unittest/test_Util.cpp index 552f5c36b..39eafaa09 100644 --- a/unittest/test_Util.cpp +++ b/unittest/test_Util.cpp @@ -450,6 +450,21 @@ TEST_CASE("Util::is_absolute_path_with_prefix") #endif } +TEST_CASE("Util::is_ccache_executable") +{ + CHECK(Util::is_ccache_executable("ccache")); + CHECK(Util::is_ccache_executable("ccache-1.2.3")); + CHECK(!Util::is_ccache_executable("fooccache")); + CHECK(!Util::is_ccache_executable("gcc")); +#ifdef _WIN32 + CHECK(Util::is_ccache_executable("CCACHE")); + CHECK(Util::is_ccache_executable("CCACHE.exe")); + CHECK(Util::is_ccache_executable("CCACHE-1.2.3")); + CHECK(Util::is_ccache_executable("CCACHE.EXE")); + CHECK(Util::is_ccache_executable("CCACHE-1.2.3.EXE")); +#endif +} + TEST_CASE("Util::is_dir_separator") { CHECK(!Util::is_dir_separator('x')); @@ -744,18 +759,6 @@ TEST_CASE("Util::remove_extension") CHECK(Util::remove_extension("/foo/bar/f.abc.txt") == "/foo/bar/f.abc"); } -TEST_CASE("Util::same_program_name") -{ - CHECK(Util::same_program_name("foo", "foo")); -#ifdef _WIN32 - CHECK(Util::same_program_name("FOO", "foo")); - CHECK(Util::same_program_name("FOO.exe", "foo")); -#else - CHECK(!Util::same_program_name("FOO", "foo")); - CHECK(!Util::same_program_name("FOO.exe", "foo")); -#endif -} - // Util::split_into_strings and Util::split_into_views are tested implicitly in // test_util_Tokenizer.cpp. diff --git a/unittest/test_ccache.cpp b/unittest/test_ccache.cpp index 9c1628835..b8d41197a 100644 --- a/unittest/test_ccache.cpp +++ b/unittest/test_ccache.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Joel Rosdahl and other contributors +// Copyright (C) 2020-2022 Joel Rosdahl and other contributors // // See doc/AUTHORS.adoc for a complete list of contributors. // @@ -31,12 +31,6 @@ # include #endif -#ifdef MYNAME -# define CCACHE_NAME MYNAME -#else -# define CCACHE_NAME "ccache" -#endif - using TestUtil::TestContext; TEST_SUITE_BEGIN("ccache"); @@ -73,55 +67,54 @@ TEST_CASE("find_compiler") // In case the first parameter is ccache, resolve the second parameter to // the real compiler unless it's a relative or absolute path. - CHECK(helper(CCACHE_NAME " gcc", "") == "resolved_gcc"); - CHECK(helper(CCACHE_NAME " rel/gcc", "") == "rel/gcc"); - CHECK(helper(CCACHE_NAME " /abs/gcc", "") == "/abs/gcc"); + CHECK(helper("ccache gcc", "") == "resolved_gcc"); + CHECK(helper("ccache rel/gcc", "") == "rel/gcc"); + CHECK(helper("ccache /abs/gcc", "") == "/abs/gcc"); - CHECK(helper("rel/" CCACHE_NAME " gcc", "") == "resolved_gcc"); - CHECK(helper("rel/" CCACHE_NAME " rel/gcc", "") == "rel/gcc"); - CHECK(helper("rel/" CCACHE_NAME " /abs/gcc", "") == "/abs/gcc"); + CHECK(helper("rel/ccache gcc", "") == "resolved_gcc"); + CHECK(helper("rel/ccache rel/gcc", "") == "rel/gcc"); + CHECK(helper("rel/ccache /abs/gcc", "") == "/abs/gcc"); - CHECK(helper("/abs/" CCACHE_NAME " gcc", "") == "resolved_gcc"); - CHECK(helper("/abs/" CCACHE_NAME " rel/gcc", "") == "rel/gcc"); - CHECK(helper("/abs/" CCACHE_NAME " /abs/gcc", "") == "/abs/gcc"); + CHECK(helper("/abs/ccache gcc", "") == "resolved_gcc"); + CHECK(helper("/abs/ccache rel/gcc", "") == "rel/gcc"); + CHECK(helper("/abs/ccache /abs/gcc", "") == "/abs/gcc"); // If gcc points back to ccache throw, unless either ccache or gcc is a // relative or absolute path. - CHECK_THROWS(helper(CCACHE_NAME " gcc", "", CCACHE_NAME)); - CHECK(helper(CCACHE_NAME " rel/gcc", "", CCACHE_NAME) == "rel/gcc"); - CHECK(helper(CCACHE_NAME " /abs/gcc", "", CCACHE_NAME) == "/abs/gcc"); + CHECK_THROWS(helper("ccache gcc", "", "ccache")); + CHECK(helper("ccache rel/gcc", "", "ccache") == "rel/gcc"); + CHECK(helper("ccache /abs/gcc", "", "ccache") == "/abs/gcc"); - CHECK_THROWS(helper("rel/" CCACHE_NAME " gcc", "", CCACHE_NAME)); - CHECK(helper("rel/" CCACHE_NAME " rel/gcc", "", CCACHE_NAME) == "rel/gcc"); - CHECK(helper("rel/" CCACHE_NAME " /a/gcc", "", CCACHE_NAME) == "/a/gcc"); + CHECK_THROWS(helper("rel/ccache gcc", "", "ccache")); + CHECK(helper("rel/ccache rel/gcc", "", "ccache") == "rel/gcc"); + CHECK(helper("rel/ccache /a/gcc", "", "ccache") == "/a/gcc"); - CHECK_THROWS(helper("/abs/" CCACHE_NAME " gcc", "", CCACHE_NAME)); - CHECK(helper("/abs/" CCACHE_NAME " rel/gcc", "", CCACHE_NAME) == "rel/gcc"); - CHECK(helper("/abs/" CCACHE_NAME " /a/gcc", "", CCACHE_NAME) == "/a/gcc"); + CHECK_THROWS(helper("/abs/ccache gcc", "", "ccache")); + CHECK(helper("/abs/ccache rel/gcc", "", "ccache") == "rel/gcc"); + CHECK(helper("/abs/ccache /a/gcc", "", "ccache") == "/a/gcc"); // If compiler is not found then throw, unless the compiler has a relative // or absolute path. - CHECK_THROWS(helper(CCACHE_NAME " gcc", "", "")); - CHECK(helper(CCACHE_NAME " rel/gcc", "", "") == "rel/gcc"); - CHECK(helper(CCACHE_NAME " /abs/gcc", "", "") == "/abs/gcc"); + CHECK_THROWS(helper("ccache gcc", "", "")); + CHECK(helper("ccache rel/gcc", "", "") == "rel/gcc"); + CHECK(helper("ccache /abs/gcc", "", "") == "/abs/gcc"); - CHECK_THROWS(helper("rel/" CCACHE_NAME " gcc", "", "")); - CHECK(helper("rel/" CCACHE_NAME " rel/gcc", "", "") == "rel/gcc"); - CHECK(helper("rel/" CCACHE_NAME " /abs/gcc", "", "") == "/abs/gcc"); + CHECK_THROWS(helper("rel/ccache gcc", "", "")); + CHECK(helper("rel/ccache rel/gcc", "", "") == "rel/gcc"); + CHECK(helper("rel/ccache /abs/gcc", "", "") == "/abs/gcc"); - CHECK_THROWS(helper("/abs/" CCACHE_NAME " gcc", "", "")); - CHECK(helper("/abs/" CCACHE_NAME " rel/gcc", "", "") == "rel/gcc"); - CHECK(helper("/abs/" CCACHE_NAME " /abs/gcc", "", "") == "/abs/gcc"); + CHECK_THROWS(helper("/abs/ccache gcc", "", "")); + CHECK(helper("/abs/ccache rel/gcc", "", "") == "rel/gcc"); + CHECK(helper("/abs/ccache /abs/gcc", "", "") == "/abs/gcc"); } SUBCASE("double ccache") { // E.g. due to some suboptimal setup, scripts etc. Source: // https://github.com/ccache/ccache/issues/686 - CHECK(helper(CCACHE_NAME " gcc", "") == "resolved_gcc"); - CHECK(helper(CCACHE_NAME " " CCACHE_NAME " gcc", "") == "resolved_gcc"); - CHECK(helper(CCACHE_NAME " " CCACHE_NAME " " CCACHE_NAME " gcc", "") - == "resolved_gcc"); + CHECK(helper("ccache gcc", "") == "resolved_gcc"); + CHECK(helper("ccache ccache gcc", "") == "resolved_gcc"); + CHECK(helper("ccache ccache-1.2.3 ccache gcc", "") == "resolved_gcc"); } SUBCASE("config") @@ -141,25 +134,25 @@ TEST_CASE("find_compiler") // In case the first parameter is ccache, use the configuration value. Don't // resolve configuration value if it's a relative or absolute path. - CHECK(helper(CCACHE_NAME " gcc", "config") == "resolved_config"); - CHECK(helper(CCACHE_NAME " gcc", "rel/config") == "rel/config"); - CHECK(helper(CCACHE_NAME " gcc", "/abs/config") == "/abs/config"); - CHECK(helper(CCACHE_NAME " rel/gcc", "config") == "resolved_config"); - CHECK(helper(CCACHE_NAME " /abs/gcc", "config") == "resolved_config"); + CHECK(helper("ccache gcc", "config") == "resolved_config"); + CHECK(helper("ccache gcc", "rel/config") == "rel/config"); + CHECK(helper("ccache gcc", "/abs/config") == "/abs/config"); + CHECK(helper("ccache rel/gcc", "config") == "resolved_config"); + CHECK(helper("ccache /abs/gcc", "config") == "resolved_config"); // Same as above with relative path to ccache. - CHECK(helper("r/" CCACHE_NAME " gcc", "conf") == "resolved_conf"); - CHECK(helper("r/" CCACHE_NAME " gcc", "rel/conf") == "rel/conf"); - CHECK(helper("r/" CCACHE_NAME " gcc", "/abs/conf") == "/abs/conf"); - CHECK(helper("r/" CCACHE_NAME " rel/gcc", "conf") == "resolved_conf"); - CHECK(helper("r/" CCACHE_NAME " /abs/gcc", "conf") == "resolved_conf"); + CHECK(helper("r/ccache gcc", "conf") == "resolved_conf"); + CHECK(helper("r/ccache gcc", "rel/conf") == "rel/conf"); + CHECK(helper("r/ccache gcc", "/abs/conf") == "/abs/conf"); + CHECK(helper("r/ccache rel/gcc", "conf") == "resolved_conf"); + CHECK(helper("r/ccache /abs/gcc", "conf") == "resolved_conf"); // Same as above with absolute path to ccache. - CHECK(helper("/a/" CCACHE_NAME " gcc", "conf") == "resolved_conf"); - CHECK(helper("/a/" CCACHE_NAME " gcc", "rel/conf") == "rel/conf"); - CHECK(helper("/a/" CCACHE_NAME " gcc", "/a/conf") == "/a/conf"); - CHECK(helper("/a/" CCACHE_NAME " rel/gcc", "conf") == "resolved_conf"); - CHECK(helper("/a/" CCACHE_NAME " /abs/gcc", "conf") == "resolved_conf"); + CHECK(helper("/a/ccache gcc", "conf") == "resolved_conf"); + CHECK(helper("/a/ccache gcc", "rel/conf") == "rel/conf"); + CHECK(helper("/a/ccache gcc", "/a/conf") == "/a/conf"); + CHECK(helper("/a/ccache rel/gcc", "conf") == "resolved_conf"); + CHECK(helper("/a/ccache /abs/gcc", "conf") == "resolved_conf"); } } -- 2.47.2