]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
feat: Add support for setting per-compilation config on command line
authorJoel Rosdahl <joel@rosdahl.net>
Fri, 17 Feb 2023 07:26:08 +0000 (08:26 +0100)
committerJoel Rosdahl <joel@rosdahl.net>
Mon, 20 Feb 2023 07:02:33 +0000 (08:02 +0100)
Closes #1035.

doc/MANUAL.adoc
src/Config.cpp
src/Config.hpp
src/Context.cpp
src/Context.hpp
src/ccache.cpp
src/ccache.hpp
src/core/mainoptions.cpp
test/suites/config.bash
unittest/test_ccache.cpp

index e22685ecee81dcd10d1fd933f41762d848fdb592..4645267b6909b8495bd4c3d8c9acbe90b6ed5ac5 100644 (file)
@@ -9,9 +9,14 @@ ccache - a fast C/C++ compiler cache
 == Synopsis
 
 [verse]
-*ccache* [_options_]
-*ccache* _compiler_ [_compiler options_]
-_compiler_ [_compiler options_]            (ccache masquerading as the compiler)
+*ccache* [_ccache options_]
+*ccache* [_KEY_=_VALUE_ ...] _compiler_ [_compiler options_]
+_compiler_ [_compiler options_]
+
+The first form takes options described in <<Command line options>> below. The
+second form invokes the compiler, optionally using <<Configuration,configuration
+options>> as _KEY_=_VALUE_ arguments. In the third form, ccache is masquerading
+as the compiler as described in <<Run modes>>.
 
 
 == Description
@@ -310,11 +315,16 @@ system-level configuration file and secondly a cache-specific configuration
 file. The priorities of configuration options are as follows (where 1 is
 highest):
 
-1. Environment variables.
-2. The cache-specific configuration file (see below).
-3. The system (read-only) configuration file `<sysconfdir>/ccache.conf`
+1. Command line settings in _KEY_=_VALUE_ form. Example:
++
+-------------------------------------------------------------------------------
+ccache debug=true compiler_check="%compiler% --version" gcc -c example.c
+-------------------------------------------------------------------------------
+2. Environment variables.
+3. The cache-specific configuration file (see below).
+4. The system (read-only) configuration file `<sysconfdir>/ccache.conf`
    (typically `/etc/ccache.conf` or `/usr/local/etc/ccache.conf`).
-4. Compile-time defaults.
+5. Compile-time defaults.
 
 As a special case, if the environment variable `CCACHE_CONFIGPATH` is set it
 specifies the configuration file, and the system configuration file won't be
index a12180a86252d8efea0aff92c9e3242d6e6b3dec..a28d56473246af7fad67e0e524c0bd564a253945 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2019-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -439,6 +439,24 @@ parse_config_file(const std::string& path,
   return true;
 }
 
+std::unordered_map<std::string, std::string>
+create_cmdline_settings_map(const std::vector<std::string>& settings)
+{
+  std::unordered_map<std::string, std::string> result;
+  for (const auto& setting : settings) {
+    DEBUG_ASSERT(setting.find('=') != std::string::npos);
+    std::string key;
+    std::string value;
+    std::string error_message;
+    bool ok = parse_line(setting, &key, &value, &error_message);
+    ASSERT(ok);
+    if (!key.empty()) {
+      result.insert_or_assign(std::move(key), std::move(value));
+    }
+  }
+  return result;
+}
+
 } // namespace
 
 #ifndef _WIN32
@@ -489,8 +507,11 @@ compiler_type_to_string(CompilerType compiler_type)
 }
 
 void
-Config::read()
+Config::read(const std::vector<std::string>& cmdline_config_settings)
 {
+  auto cmdline_settings_map =
+    create_cmdline_settings_map(cmdline_config_settings);
+
   const std::string home_dir = Util::get_home_directory();
   const std::string legacy_ccache_dir = Util::make_path(home_dir, ".ccache");
   const bool legacy_ccache_dir_exists =
@@ -525,8 +546,12 @@ Config::read()
     MTR_END("config", "conf_read_system");
 
     const char* const env_ccache_dir = getenv("CCACHE_DIR");
+    auto cmdline_cache_dir = cmdline_settings_map.find("cache_dir");
+
     std::string config_dir;
-    if (env_ccache_dir && *env_ccache_dir) {
+    if (cmdline_cache_dir != cmdline_settings_map.end()) {
+      config_dir = cmdline_cache_dir->second;
+    } else if (env_ccache_dir && *env_ccache_dir) {
       config_dir = env_ccache_dir;
     } else if (!cache_dir().empty() && !env_ccache_dir) {
       config_dir = cache_dir();
@@ -572,6 +597,8 @@ Config::read()
   // (cache_dir is set above if CCACHE_DIR is set.)
   MTR_END("config", "conf_update_from_environment");
 
+  update_from_map(cmdline_settings_map);
+
   if (cache_dir().empty()) {
     if (legacy_ccache_dir_exists) {
       set_cache_dir(legacy_ccache_dir);
@@ -594,7 +621,8 @@ Config::read()
   // else: cache_dir was set explicitly via environment or via system config.
 
   // We have now determined config.cache_dir and populated the rest of config in
-  // prio order (1. environment, 2. cache-specific config, 3. system config).
+  // prio order (1. command line, 2. environment, 3. cache-specific config, 4.
+  // system config).
 }
 
 const std::string&
@@ -632,6 +660,14 @@ Config::update_from_file(const std::string& path)
     });
 }
 
+void
+Config::update_from_map(const std::unordered_map<std::string, std::string>& map)
+{
+  for (const auto& [key, value] : map) {
+    set_item(key, value, std::nullopt, false, "command line");
+  }
+}
+
 void
 Config::update_from_environment()
 {
index b1d2e101c27e22727daeb12f1c595d9de8363dd5..43c6a7fcf56fbbf2c673f71c0f367ca9ab7df027 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2019-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -30,6 +30,7 @@
 #include <optional>
 #include <string>
 #include <unordered_map>
+#include <vector>
 
 enum class CompilerType {
   auto_guess,
@@ -49,7 +50,7 @@ class Config : NonCopyable
 public:
   Config() = default;
 
-  void read();
+  void read(const std::vector<std::string>& cmdline_config_settings = {});
 
   bool absolute_paths_in_stderr() const;
   const std::string& base_dir() const;
@@ -139,6 +140,11 @@ public:
   // invalid configuration values.
   bool update_from_file(const std::string& path);
 
+  // Set config values from a map with key-value pairs.
+  //
+  // Throws Error on invalid configuration values.
+  void update_from_map(const std::unordered_map<std::string, std::string>& map);
+
   // Set config values from environment variables.
   //
   // Throws Error on invalid configuration values.
index c46d3684707631b90c2cfe44128826e9fbe35a44..0a45d9e613ea1ba9562e2436d89d4582ea7e1fa3 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -49,11 +49,12 @@ Context::Context()
 }
 
 void
-Context::initialize()
+Context::initialize(Args&& compiler_and_args,
+                    const std::vector<std::string>& cmdline_config_settings)
 {
-  config.read();
+  orig_args = std::move(compiler_and_args);
+  config.read(cmdline_config_settings);
   Logging::init(config);
-
   ignore_header_paths =
     util::split_path_list(config.ignore_headers_in_manifest());
   set_ignore_options(Util::split_into_strings(config.ignore_options(), " "));
index 883c02f4a7f692bf93298b6bf15144759afded6f..3eee4e5e84912d2f683d1ed8c5c201fc99bce9cb 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -51,7 +51,8 @@ public:
 
   // Read configuration, initialize logging, etc. Typically not called from unit
   // tests.
-  void initialize();
+  void initialize(Args&& compiler_and_args,
+                  const std::vector<std::string>& cmdline_config_settings);
 
   ArgsInfo args_info;
   Config config;
index ff5315c1f81d674c491ee3d3e8547719fd161c92..d867dd1ea13633171a55883c1205a56e737655ba 100644 (file)
@@ -72,6 +72,7 @@
 
 #include <algorithm>
 #include <cmath>
+#include <cstring>
 #include <limits>
 #include <memory>
 #include <unordered_map>
@@ -2109,25 +2110,18 @@ from_cache(Context& ctx, FromCacheCallMode mode, const Digest& result_key)
 // PATH to find an executable of the same name that isn't ourselves.
 void
 find_compiler(Context& ctx,
-              const FindExecutableFunction& find_executable_function)
+              const FindExecutableFunction& find_executable_function,
+              bool masquerading_as_compiler)
 {
-  // gcc --> 0
-  // ccache gcc --> 1
-  // ccache ccache gcc --> 2
-  size_t compiler_pos = 0;
-  while (compiler_pos < ctx.orig_args.size()
-         && Util::is_ccache_executable(ctx.orig_args[compiler_pos])) {
-    ++compiler_pos;
-  }
-
   // Support user override of the compiler.
   const std::string compiler =
     !ctx.config.compiler().empty()
       ? ctx.config.compiler()
       // In case ccache is masquerading as the compiler, use only base_name so
       // the real compiler can be determined.
-      : (compiler_pos == 0 ? std::string(Util::base_name(ctx.orig_args[0]))
-                           : ctx.orig_args[compiler_pos]);
+      : (masquerading_as_compiler
+           ? std::string(Util::base_name(ctx.orig_args[0]))
+           : ctx.orig_args[0]);
 
   const std::string resolved_compiler =
     util::is_full_path(compiler)
@@ -2142,17 +2136,12 @@ find_compiler(Context& ctx,
     throw core::Fatal("Recursive invocation of ccache");
   }
 
-  ctx.orig_args.pop_front(compiler_pos);
   ctx.orig_args[0] = resolved_compiler;
 }
 
-// Initialize ccache. Must be called once before anything else is run.
 static void
-initialize(Context& ctx, int argc, const char* const* argv)
+initialize(Context& ctx, const char* const* argv, bool masquerading_as_compiler)
 {
-  ctx.orig_args = Args::from_argv(argc, argv);
-  ctx.storage.initialize();
-
   LOG("=== CCACHE {} STARTED =========================================",
       CCACHE_VERSION);
 
@@ -2166,6 +2155,42 @@ initialize(Context& ctx, int argc, const char* const* argv)
     LOG_RAW("Error: tracing is not enabled!");
 #endif
   }
+
+  if (!ctx.config.log_file().empty() || ctx.config.debug()) {
+    ctx.config.visit_items([&ctx](const std::string& key,
+                                  const std::string& value,
+                                  const std::string& origin) {
+      const auto& log_value =
+        key == "remote_storage"
+          ? ctx.storage.get_remote_storage_config_for_logging()
+          : value;
+      BULK_LOG("Config: ({}) {} = {}", origin, key, log_value);
+    });
+  }
+
+  LOG("Command line: {}", Util::format_argv_for_logging(argv));
+  LOG("Hostname: {}", Util::get_hostname());
+  LOG("Working directory: {}", ctx.actual_cwd);
+  if (ctx.apparent_cwd != ctx.actual_cwd) {
+    LOG("Apparent working directory: {}", ctx.apparent_cwd);
+  }
+
+  ctx.storage.initialize();
+
+  MTR_BEGIN("main", "find_compiler");
+  find_compiler(ctx, &find_executable, masquerading_as_compiler);
+  MTR_END("main", "find_compiler");
+
+  // Guess compiler after logging the config value in order to be able to
+  // display "compiler_type = auto" before overwriting the value with the
+  // guess.
+  if (ctx.config.compiler_type() == CompilerType::auto_guess) {
+    ctx.config.set_compiler_type(guess_compiler(ctx.orig_args[0]));
+  }
+  DEBUG_ASSERT(ctx.config.compiler_type() != CompilerType::auto_guess);
+
+  LOG("Compiler: {}", ctx.orig_args[0]);
+  LOG("Compiler type: {}", compiler_type_to_string(ctx.config.compiler_type()));
 }
 
 // Make a copy of stderr that will not be cached, so things like distcc can
@@ -2187,7 +2212,7 @@ set_up_uncached_err()
 static int cache_compilation(int argc, const char* const* argv);
 
 static nonstd::expected<core::StatisticsCounters, Failure>
-do_cache_compilation(Context& ctx, const char* const* argv);
+do_cache_compilation(Context& ctx);
 
 static void
 log_result_to_debug_log(Context& ctx)
@@ -2247,6 +2272,23 @@ finalize_at_exit(Context& ctx)
   }
 }
 
+ArgvParts
+split_argv(int argc, const char* const* argv)
+{
+  ArgvParts argv_parts;
+  int i = 0;
+  while (i < argc && Util::is_ccache_executable(argv[i])) {
+    argv_parts.masquerading_as_compiler = false;
+    ++i;
+  }
+  while (i < argc && std::strchr(argv[i], '=')) {
+    argv_parts.config_settings.emplace_back(argv[i]);
+    ++i;
+  }
+  argv_parts.compiler_and_args = Args::from_argv(argc - i, argv + i);
+  return argv_parts;
+}
+
 // The entry point when invoked to cache a compilation.
 static int
 cache_compilation(int argc, const char* const* argv)
@@ -2258,15 +2300,21 @@ cache_compilation(int argc, const char* const* argv)
   std::optional<uint32_t> original_umask;
   std::string saved_temp_dir;
 
+  auto argv_parts = split_argv(argc, argv);
+  if (argv_parts.compiler_and_args.empty()) {
+    throw core::Fatal("no compiler given, see \"ccache --help\"");
+  }
+
   {
     Context ctx;
-    ctx.initialize();
+    ctx.initialize(std::move(argv_parts.compiler_and_args),
+                   argv_parts.config_settings);
     SignalHandler signal_handler(ctx);
     Finalizer finalizer([&ctx] { finalize_at_exit(ctx); });
 
-    initialize(ctx, argc, argv);
+    initialize(ctx, argv, argv_parts.masquerading_as_compiler);
 
-    const auto result = do_cache_compilation(ctx, argv);
+    const auto result = do_cache_compilation(ctx);
     ctx.storage.local.increment_statistics(result ? *result
                                                   : result.error().counters());
     const auto& counters = ctx.storage.local.get_statistics_updates();
@@ -2324,43 +2372,8 @@ cache_compilation(int argc, const char* const* argv)
 }
 
 static nonstd::expected<core::StatisticsCounters, Failure>
-do_cache_compilation(Context& ctx, const char* const* argv)
+do_cache_compilation(Context& ctx)
 {
-  if (!ctx.config.log_file().empty() || ctx.config.debug()) {
-    ctx.config.visit_items([&ctx](const std::string& key,
-                                  const std::string& value,
-                                  const std::string& origin) {
-      const auto& log_value =
-        key == "remote_storage"
-          ? ctx.storage.get_remote_storage_config_for_logging()
-          : value;
-      BULK_LOG("Config: ({}) {} = {}", origin, key, log_value);
-    });
-  }
-
-  LOG("Command line: {}", Util::format_argv_for_logging(argv));
-  LOG("Hostname: {}", Util::get_hostname());
-  LOG("Working directory: {}", ctx.actual_cwd);
-  if (ctx.apparent_cwd != ctx.actual_cwd) {
-    LOG("Apparent working directory: {}", ctx.apparent_cwd);
-  }
-
-  // Note: do_cache_compilation must not return or use ctx.orig_args before
-  // find_compiler is executed.
-  MTR_BEGIN("main", "find_compiler");
-  find_compiler(ctx, &find_executable);
-  MTR_END("main", "find_compiler");
-
-  // Guess compiler after logging the config value in order to be able to
-  // display "compiler_type = auto" before overwriting the value with the
-  // guess.
-  if (ctx.config.compiler_type() == CompilerType::auto_guess) {
-    ctx.config.set_compiler_type(guess_compiler(ctx.orig_args[0]));
-  }
-  DEBUG_ASSERT(ctx.config.compiler_type() != CompilerType::auto_guess);
-
-  LOG("Compiler type: {}", compiler_type_to_string(ctx.config.compiler_type()));
-
   if (ctx.config.disable()) {
     LOG_RAW("ccache is disabled");
     return nonstd::make_unexpected(Statistic::none);
index 510563a4b01249504f5c978f36789bebb47f7217..a3d0be77e8c581ef6cb1a89d60a3b359d534d108 100644 (file)
@@ -1,5 +1,5 @@
 // Copyright (C) 2002-2007 Andrew Tridgell
-// Copyright (C) 2009-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2009-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
 
 #pragma once
 
-#include "Config.hpp"
+#include <Args.hpp>
+#include <Config.hpp>
 
 #include <functional>
 #include <string>
 #include <string_view>
+#include <vector>
 
 class Context;
 
@@ -37,6 +39,16 @@ using FindExecutableFunction =
 int ccache_main(int argc, const char* const* argv);
 
 // Tested by unit tests.
+struct ArgvParts
+{
+  bool masquerading_as_compiler = true;
+  std::vector<std::string> config_settings;
+  Args compiler_and_args;
+};
+
+ArgvParts split_argv(int argc, const char* const* argv);
+
 void find_compiler(Context& ctx,
-                   const FindExecutableFunction& find_executable_function);
+                   const FindExecutableFunction& find_executable_function,
+                   bool masquerading_as_compiler);
 CompilerType guess_compiler(std::string_view path);
index 32a89b4e9e0df41ab3e6b67c46bebf6d0221aa2c..e8b464378549e0ab90a3b233dffdc7ac316bb8e7 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2021-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2021-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -88,9 +88,13 @@ version.
 
 constexpr const char USAGE_TEXT[] =
   R"(Usage:
-    {0} [options]
-    {0} compiler [compiler options]
-    compiler [compiler options]            (ccache masquerading as the compiler)
+    {0} [ccache options]
+    {0} [KEY=VALUE ...] compiler [compiler options]
+    compiler [compiler options]
+
+    The first form takes options described below. The second form invokes the
+    compiler, optionally using configuration options from KEY=VALUE arguments.
+    In the third form, ccache is masquerading as the compiler.
 
 Common options:
     -c, --cleanup              delete old files and recalculate size counters
@@ -115,7 +119,7 @@ Common options:
         --recompress-threads THREADS
                                use up to THREADS threads when recompressing the
                                cache; default: number of CPUs
-    -o, --set-config KEY=VAL   set configuration option KEY to value VAL
+    -o, --set-config KEY=VALUE set configuration option KEY to value VALUE
     -x, --show-compression     show compression statistics
     -p, --show-config          show current configuration options in
                                human-readable format
index 97da52c75fa473007e44e00d3994ee1e204fa937..7a21f4bbdb6249067e202360efed916fdab8aed8 100644 (file)
@@ -8,4 +8,16 @@ SUITE_config() {
     $CCACHE --show-config >config.txt
 
     expect_contains config.txt "(environment) max_size = 40"
+
+    # -------------------------------------------------------------------------
+    TEST "Command line origin"
+
+    export CCACHE_DEBUG="1"
+    export CCACHE_MAXSIZE="40"
+
+    touch test.c
+    $CCACHE debug=true "max_size = 40" $COMPILER -c test.c
+
+    expect_contains test.o.*.ccache-log "(command line) debug = true"
+    expect_contains test.o.*.ccache-log "(command line) max_size = 40"
 }
index 30f3bdd72bb06a9d7ea4563d6b98fc4c266cd83e..61671f844352d0d80faa331482c997c770c488a7 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2023 Joel Rosdahl and other contributors
 //
 // See doc/AUTHORS.adoc for a complete list of contributors.
 //
@@ -38,7 +38,8 @@ TEST_SUITE_BEGIN("ccache");
 
 // Wraps find_compiler in a test friendly interface.
 static std::string
-helper(const char* args,
+helper(bool masquerading_as_compiler,
+       const char* args,
        const char* config_compiler,
        const char* find_executable_return_string = nullptr)
 {
@@ -51,10 +52,60 @@ helper(const char* args,
   Context ctx;
   ctx.config.set_compiler(config_compiler);
   ctx.orig_args = Args::from_string(args);
-  find_compiler(ctx, find_executable_stub);
+  find_compiler(ctx, find_executable_stub, masquerading_as_compiler);
   return ctx.orig_args.to_string();
 }
 
+TEST_CASE("split_argv")
+{
+  ArgvParts argv_parts;
+
+  SUBCASE("empty")
+  {
+    argv_parts = split_argv(0, nullptr);
+    CHECK(argv_parts.masquerading_as_compiler);
+    CHECK(argv_parts.config_settings.empty());
+    CHECK(argv_parts.compiler_and_args.empty());
+  }
+
+  SUBCASE("ccache")
+  {
+    const char* const argv[] = {"ccache"};
+    argv_parts = split_argv(std::size(argv), argv);
+    CHECK(!argv_parts.masquerading_as_compiler);
+    CHECK(argv_parts.config_settings.empty());
+    CHECK(argv_parts.compiler_and_args.empty());
+  }
+
+  SUBCASE("normal compilation")
+  {
+    const char* const argv[] = {"ccache", "gcc", "-c", "test.c"};
+    argv_parts = split_argv(std::size(argv), argv);
+    CHECK(!argv_parts.masquerading_as_compiler);
+    CHECK(argv_parts.config_settings.empty());
+    CHECK(argv_parts.compiler_and_args == Args::from_string("gcc -c test.c"));
+  }
+
+  SUBCASE("only config options")
+  {
+    const char* const argv[] = {"ccache", "foo=bar"};
+    argv_parts = split_argv(std::size(argv), argv);
+    CHECK(!argv_parts.masquerading_as_compiler);
+    CHECK(argv_parts.config_settings == std::vector<std::string>{"foo=bar"});
+    CHECK(argv_parts.compiler_and_args.empty());
+  }
+
+  SUBCASE("compilation with config options")
+  {
+    const char* const argv[] = {"ccache", "a=b", "c = d", "/usr/bin/gcc"};
+    argv_parts = split_argv(std::size(argv), argv);
+    CHECK(!argv_parts.masquerading_as_compiler);
+    CHECK(argv_parts.config_settings
+          == std::vector<std::string>{"a=b", "c = d"});
+    CHECK(argv_parts.compiler_and_args == Args::from_string("/usr/bin/gcc"));
+  }
+}
+
 TEST_CASE("find_compiler")
 {
   SUBCASE("no config")
@@ -62,60 +113,27 @@ TEST_CASE("find_compiler")
     // In case the first parameter is gcc it must be a link to ccache, so
     // find_compiler should call find_executable to locate the next best "gcc"
     // and return that value.
-    CHECK(helper("gcc", "") == "resolved_gcc");
-    CHECK(helper("relative/gcc", "") == "resolved_gcc");
-    CHECK(helper("/absolute/gcc", "") == "resolved_gcc");
+    CHECK(helper(true, "gcc", "") == "resolved_gcc");
+    CHECK(helper(true, "relative/gcc", "") == "resolved_gcc");
+    CHECK(helper(true, "/absolute/gcc", "") == "resolved_gcc");
 
     // 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 gcc", "") == "resolved_gcc");
-    CHECK(helper("ccache rel/gcc", "") == "rel/gcc");
-    CHECK(helper("ccache /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 gcc", "") == "resolved_gcc");
-    CHECK(helper("/abs/ccache rel/gcc", "") == "rel/gcc");
-    CHECK(helper("/abs/ccache /abs/gcc", "") == "/abs/gcc");
+    CHECK(helper(false, "gcc", "") == "resolved_gcc");
+    CHECK(helper(false, "rel/gcc", "") == "rel/gcc");
+    CHECK(helper(false, "/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 gcc", "", "ccache"));
-    CHECK(helper("ccache rel/gcc", "", "ccache") == "rel/gcc");
-    CHECK(helper("ccache /abs/gcc", "", "ccache") == "/abs/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 gcc", "", "ccache"));
-    CHECK(helper("/abs/ccache rel/gcc", "", "ccache") == "rel/gcc");
-    CHECK(helper("/abs/ccache /a/gcc", "", "ccache") == "/a/gcc");
+    CHECK_THROWS(helper(false, "gcc", "", "ccache"));
+    CHECK(helper(false, "rel/gcc", "", "ccache") == "rel/gcc");
+    CHECK(helper(false, "/abs/gcc", "", "ccache") == "/abs/gcc");
 
     // If compiler is not found then throw, unless the compiler has a relative
     // or absolute path.
-    CHECK_THROWS(helper("ccache gcc", "", ""));
-    CHECK(helper("ccache rel/gcc", "", "") == "rel/gcc");
-    CHECK(helper("ccache /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 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 gcc", "") == "resolved_gcc");
-    CHECK(helper("ccache ccache gcc", "") == "resolved_gcc");
-    CHECK(helper("ccache ccache-1.2.3 ccache gcc", "") == "resolved_gcc");
+    CHECK_THROWS(helper(false, "gcc", "", ""));
+    CHECK(helper(false, "rel/gcc", "", "") == "rel/gcc");
+    CHECK(helper(false, "/abs/gcc", "", "") == "/abs/gcc");
   }
 
   SUBCASE("config")
@@ -123,37 +141,23 @@ TEST_CASE("find_compiler")
     // In case the first parameter is gcc it must be a link to ccache so use
     // config value instead. Don't resolve config if it's a relative or absolute
     // path.
-    CHECK(helper("gcc", "config") == "resolved_config");
-    CHECK(helper("gcc", "rel/config") == "rel/config");
-    CHECK(helper("gcc", "/abs/config") == "/abs/config");
-    CHECK(helper("rel/gcc", "config") == "resolved_config");
-    CHECK(helper("rel/gcc", "rel/config") == "rel/config");
-    CHECK(helper("rel/gcc", "/abs/config") == "/abs/config");
-    CHECK(helper("/abs/gcc", "config") == "resolved_config");
-    CHECK(helper("/abs/gcc", "rel/config") == "rel/config");
-    CHECK(helper("/abs/gcc", "/abs/config") == "/abs/config");
+    CHECK(helper(true, "gcc", "config") == "resolved_config");
+    CHECK(helper(true, "gcc", "rel/config") == "rel/config");
+    CHECK(helper(true, "gcc", "/abs/config") == "/abs/config");
+    CHECK(helper(true, "rel/gcc", "config") == "resolved_config");
+    CHECK(helper(true, "rel/gcc", "rel/config") == "rel/config");
+    CHECK(helper(true, "rel/gcc", "/abs/config") == "/abs/config");
+    CHECK(helper(true, "/abs/gcc", "config") == "resolved_config");
+    CHECK(helper(true, "/abs/gcc", "rel/config") == "rel/config");
+    CHECK(helper(true, "/abs/gcc", "/abs/config") == "/abs/config");
 
     // 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 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 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 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");
+    CHECK(helper(false, "gcc", "config") == "resolved_config");
+    CHECK(helper(false, "gcc", "rel/config") == "rel/config");
+    CHECK(helper(false, "gcc", "/abs/config") == "/abs/config");
+    CHECK(helper(false, "rel/gcc", "config") == "resolved_config");
+    CHECK(helper(false, "/abs/gcc", "config") == "resolved_config");
   }
 }