]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
feat: Support masquerading as a compiler via copy or hard link
authorJoel Rosdahl <joel@rosdahl.net>
Fri, 10 Jun 2022 14:17:03 +0000 (16:17 +0200)
committerJoel Rosdahl <joel@rosdahl.net>
Fri, 10 Jun 2022 17:54:37 +0000 (19:54 +0200)
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.

16 files changed:
doc/INSTALL.md
doc/MANUAL.adoc
src/Util.cpp
src/Util.hpp
src/ccache.cpp
src/ccache.hpp
src/core/mainoptions.cpp
src/core/mainoptions.hpp
src/execute.cpp
src/execute.hpp
src/hashutil.cpp
src/storage/secondary/HttpStorage.cpp
src/util/file.hpp
test/suites/masquerading.bash
unittest/test_Util.cpp
unittest/test_ccache.cpp

index 5ce8c2afe3f0cf59e32d7cd92b4a3b85c3f4897a..641d0169738cca44116e50d6f9fd1c9c4e9e06b3 100644 (file)
@@ -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`.
index 99ffa6b97d48abecee6ce903fb3784cd32a784dc..248d654599500d0198d90e0082586ba2d9afeeeb 100644 (file)
@@ -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
 _<<Using ccache with other compiler wrappers>>_.
 
-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
index c2ce61dca9dff11355a5f672e4589a4473b7f3f3..19e147999342dd6814fc240dc221414ec18b58e4 100644 (file)
@@ -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)
 {
index c8231107f6e6f8f49ef3e4e573b8533abfbccc39..28597d49d45bf303fd1a54417f51175c212141fa 100644 (file)
@@ -223,6 +223,9 @@ int_to_big_endian(int8_t value, uint8_t* buffer)
 // point.
 std::optional<size_t> 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
index 63051381895c0d28ac73a7b7c250967ded1a3147..f1f5f060925a98deb372becc159ccf52a1e50aec 100644 (file)
 #include <limits>
 #include <memory>
 
-#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
index 7590ad584ee58bf43ded064015aa2414c96b89a0..510563a4b01249504f5c978f36789bebb47f7217 100644 (file)
@@ -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.
 //
 
 class Context;
 
-extern const char CCACHE_NAME[];
 extern const char CCACHE_VERSION[];
 
 using FindExecutableFunction =
   std::function<std::string(const Context& ctx,
                             const std::string& name,
-                            const std::string& exclude_name)>;
+                            const std::string& exclude_path)>;
 
 int ccache_main(int argc, const char* const* argv);
 
index 830d290066fa0b1a5e443d054365476643df56c8..b7466c8444a9a4a997e936daa45caf93cb6f0f04 100644 (file)
@@ -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;
     }
   }
index e0f33e60be974eb37a1eb918ccbee4f458f0ffda..2cf84cd8f12cb19106d5a97b7c019999dc473bca 100644 (file)
@@ -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.
 //
 #pragma once
 
 #include <string>
+#include <string_view>
 
 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
index 801349b8329d0c752088dcd139f725d9ebd8c21c..850bae4fd2868659dec87ae5495c068e9c2d8c5c 100644 (file)
@@ -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<std::string> 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<std::string> 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 {};
index 8e492263981d002c3b1e583112da8c8283f26fbe..c44a465abefd9272fb7d54fb191bbeaf53a0c9ae 100644 (file)
@@ -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 <optional>
 #include <string>
 
 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<std::string> exclude_path = std::nullopt);
 
 #ifdef _WIN32
 std::string win32getshell(const std::string& path);
index 7e0ca23f70ccc939749a1fb07e46bcfd16d8eaa4..4598ec8d7a13f9986a3522c8bf05f6e0f7f49469 100644 (file)
@@ -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];
   }
index 671a8013905170bc16ebc52c2b9a482afcc1c5af..d95ba62c14cb8257a977ccc420e991a9b6c2ed41 100644 (file)
@@ -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);
 
index d9adfbbdabcde3af46c7bd07b61996f73f4db98f..61a66bd1e4bbed98af93ee7e87a73dd213ee9da8 100644 (file)
@@ -18,6 +18,7 @@
 
 #pragma once
 
+#include <ctime>
 #include <optional>
 #include <string>
 
index bfba176995f580c371e8c5c1a0f7f8135e8196af..daec9587fdc0e273f1b0d3b262f49822aae1c8d5 100644 (file)
@@ -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
index 552f5c36b123133cb443019e75fd8d7892598f17..39eafaa0905063977e251f60ce60d146102bef7c 100644 (file)
@@ -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.
 
index 9c16288352f97e9d9c09771eb7fde2236811f117..b8d41197a4505ce05c18051de566f386bf7b77e2 100644 (file)
@@ -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.
 //
 #  include <unistd.h>
 #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");
   }
 }