]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
Add ability to ignore specified compiler options when hashing (#620)
authorRyan Egesdahl <deriamis@gmail.com>
Thu, 9 Jul 2020 18:17:54 +0000 (11:17 -0700)
committerGitHub <noreply@github.com>
Thu, 9 Jul 2020 18:17:54 +0000 (20:17 +0200)
Closes #578.

doc/MANUAL.adoc
src/Config.cpp
src/Config.hpp
src/Context.cpp
src/Context.hpp
src/ccache.cpp
src/ccache.hpp
test/suites/direct.bash
unittest/test_Config.cpp

index e5811f7bdecfc1664ca4401b83c964690d7469aa..d8da30915aa4e9dcca21c9eb55c91e42ecd93cf2 100644 (file)
@@ -515,6 +515,16 @@ might be incorrect.
     change. The list separator is semicolon on Windows systems and colon on
     other systems.
 
+[[config_ignore_options]] *ignore_options* (*CCACHE_IGNOREOPTIONS*)::
+
+    This setting is a space-delimited list of options that ccache will exclude
+    from the hash. Excluding a compiler option from the hash can be useful when
+    you know it doesn't affect the result (but ccache doesn't know that), or
+    when it does and you don't care. If an option in the list is suffixed with
+    an asterisk (`*`), the option is matched as a prefix. For example,
+    `-fmessage-length=*` will match both `-fmessage-length=20` and
+    `-fmessage-length=70`.
+
 [[config_inode_cache]] *inode_cache* (*CCACHE_INODECACHE* or *CCACHE_NOINODECACHE*, see <<_boolean_values,Boolean values>> above)::
 
     If true, enables caching of source file hashes based on device, inode and
index cab2aa71a41e04adeb03f239d5417c28513efa8e..a8fdfc805fabbb1a089df10a5d98a681f9860e6a 100644 (file)
@@ -54,6 +54,7 @@ enum class ConfigItem {
   hard_link,
   hash_dir,
   ignore_headers_in_manifest,
+  ignore_options,
   inode_cache,
   keep_comments_cpp,
   limit_multiple,
@@ -92,6 +93,7 @@ const std::unordered_map<std::string, ConfigItem> k_config_key_table = {
   {"hard_link", ConfigItem::hard_link},
   {"hash_dir", ConfigItem::hash_dir},
   {"ignore_headers_in_manifest", ConfigItem::ignore_headers_in_manifest},
+  {"ignore_options", ConfigItem::ignore_options},
   {"inode_cache", ConfigItem::inode_cache},
   {"keep_comments_cpp", ConfigItem::keep_comments_cpp},
   {"limit_multiple", ConfigItem::limit_multiple},
@@ -132,6 +134,7 @@ const std::unordered_map<std::string, std::string> k_env_variable_table = {
   {"HARDLINK", "hard_link"},
   {"HASHDIR", "hash_dir"},
   {"IGNOREHEADERS", "ignore_headers_in_manifest"},
+  {"IGNOREOPTIONS", "ignore_options"},
   {"INODECACHE", "inode_cache"},
   {"LIMIT_MULTIPLE", "limit_multiple"},
   {"LOGFILE", "log_file"},
@@ -559,6 +562,9 @@ Config::get_string_value(const std::string& key) const
   case ConfigItem::ignore_headers_in_manifest:
     return m_ignore_headers_in_manifest;
 
+  case ConfigItem::ignore_options:
+    return m_ignore_options;
+
   case ConfigItem::inode_cache:
     return format_bool(m_inode_cache);
 
@@ -766,6 +772,10 @@ Config::set_item(const std::string& key,
     m_ignore_headers_in_manifest = parse_env_string(value);
     break;
 
+  case ConfigItem::ignore_options:
+    m_ignore_options = parse_env_string(value);
+    break;
+
   case ConfigItem::inode_cache:
     m_inode_cache = parse_bool(value, env_var_key, negate);
     break;
index b7a3c80a1642ad77866d25d86957178c26f1ce74..76e10dc33c77d74f2a56b9088cc1e3aadb0109e1 100644 (file)
@@ -55,6 +55,7 @@ public:
   bool hard_link() const;
   bool hash_dir() const;
   const std::string& ignore_headers_in_manifest() const;
+  const std::string& ignore_options() const;
   bool inode_cache() const;
   bool keep_comments_cpp() const;
   double limit_multiple() const;
@@ -80,6 +81,7 @@ public:
   void set_depend_mode(bool value);
   void set_debug(bool value);
   void set_direct_mode(bool value);
+  void set_ignore_options(const std::string& value);
   void set_inode_cache(bool value);
   void set_limit_multiple(double value);
   void set_max_files(uint32_t value);
@@ -142,6 +144,7 @@ private:
   bool m_hard_link = false;
   bool m_hash_dir = true;
   std::string m_ignore_headers_in_manifest = "";
+  std::string m_ignore_options = "";
   bool m_inode_cache = false;
   bool m_keep_comments_cpp = false;
   double m_limit_multiple = 0.8;
@@ -276,6 +279,12 @@ Config::ignore_headers_in_manifest() const
   return m_ignore_headers_in_manifest;
 }
 
+inline const std::string&
+Config::ignore_options() const
+{
+  return m_ignore_options;
+}
+
 inline bool
 Config::inode_cache() const
 {
@@ -423,6 +432,12 @@ Config::set_direct_mode(bool value)
   m_direct_mode = value;
 }
 
+inline void
+Config::set_ignore_options(const std::string& value)
+{
+  m_ignore_options = value;
+}
+
 inline void
 Config::set_inode_cache(bool value)
 {
index 9dc5458792d00a4841a486a79ec5c35c8bec8bc7..654b098d5d37d3fe670532f3baaf67c5b5b2db39 100644 (file)
 #include "logging.hpp"
 #include "stats.hpp"
 
+#include <algorithm>
+#include <string>
+#include <vector>
+
 using nonstd::string_view;
 
 Context::Context()
@@ -118,3 +122,17 @@ Context::unlink_pending_tmp_files()
   }
   m_pending_tmp_files.clear();
 }
+
+void
+Context::set_ignore_options(const std::vector<std::string>& options)
+{
+  for (const std::string& option : options) {
+    std::size_t n_wildcards = std::count(option.cbegin(), option.cend(), '*');
+    if (n_wildcards == 0 || (n_wildcards == 1 && option.back() == '*')) {
+      m_ignore_options.push_back(option);
+    } else {
+      cc_log("Skipping malformed ignore_options item: %s", option.c_str());
+      continue;
+    }
+  }
+}
index 9e8937376222d01ef1bab481508196677f3a2c3d..8e6c332e77df0d10657e320de45c874935d8f876 100644 (file)
@@ -126,6 +126,10 @@ public:
   // Files used by the hash debugging functionality.
   std::vector<File> hash_debug_files;
 
+  // Options to ignore for the hash.
+  const std::vector<std::string>& ignore_options() const;
+  void set_ignore_options(const std::vector<std::string>& options);
+
 #ifdef MTR_ENABLED
   // Internal tracing.
   std::unique_ptr<MiniTrace> mini_trace;
@@ -146,6 +150,9 @@ private:
   std::string m_result_path;
   mutable std::string m_result_stats_file;
 
+  // Options to ignore for the hash.
+  std::vector<std::string> m_ignore_options;
+
   // [Start of variables touched by the signal handler]
 
   // Temporary files to remove at program exit.
@@ -195,3 +202,9 @@ Context::result_path() const
   assert(m_result_name); // set_result_name must have been called
   return m_result_path;
 }
+
+inline const std::vector<std::string>&
+Context::ignore_options() const
+{
+  return m_ignore_options;
+}
index 6d2a821ae79032b940422bcfaaf2daaf66ccc82e..41cd21bd3210af9b3cd57dd0437531578142dee9 100644 (file)
@@ -57,6 +57,7 @@
 #  include "third_party/getopt_long.h"
 #endif
 
+#include <algorithm>
 #include <limits>
 
 using nonstd::nullopt;
@@ -1374,6 +1375,19 @@ hash_profile_data_file(const Context& ctx, struct hash* hash)
   return found;
 }
 
+static bool
+option_should_be_ignored(const std::string& arg,
+                         const std::vector<std::string>& ignore_options)
+{
+  auto pred = [&arg](const std::string& option) {
+    const auto& prefix = string_view(option).substr(0, option.length() - 1);
+    return (
+      option == arg
+      || (Util::ends_with(option, "*") && Util::starts_with(arg, prefix)));
+  };
+  return std::any_of(ignore_options.cbegin(), ignore_options.cend(), pred);
+}
+
 // Update a hash sum with information specific to the direct and preprocessor
 // modes and calculate the result name. Returns the result name on success,
 // otherwise NULL. Caller frees.
@@ -1401,6 +1415,16 @@ calculate_result_name(Context& ctx,
 
   // First the arguments.
   for (size_t i = 1; i < args.size(); i++) {
+    // Trust the user if they've said we should not hash a given option.
+    if (option_should_be_ignored(args[i], ctx.ignore_options())) {
+      cc_log("Not hashing ignored option: %s", args[i].c_str());
+      if (i + 1 < args.size() && compopt_takes_arg(args[i])) {
+        i++;
+        cc_log("Not hashing argument of ignored option: %s", args[i].c_str());
+      }
+      continue;
+    }
+
     // -L doesn't affect compilation (except for clang).
     if (i < args.size() - 1 && args[i] == "-L" && !is_clang) {
       i++;
@@ -1847,6 +1871,8 @@ set_up_context(Context& ctx, int argc, const char* const* argv)
   ctx.orig_args = Args::from_argv(argc, argv);
   ctx.ignore_header_paths = Util::split_into_strings(
     ctx.config.ignore_headers_in_manifest(), PATH_DELIM);
+  ctx.set_ignore_options(
+    Util::split_into_strings(ctx.config.ignore_options(), " "));
 }
 
 // Initialize ccache, must be called once before anything else is run.
index 8a09b3fc359a1ae35e155893d179f0a7ee7cfa98..c69db323eaa8e65d69207f7e83837527d52a201c 100644 (file)
@@ -23,6 +23,7 @@
 
 #include "Args.hpp"
 #include "Counters.hpp"
+#include "Digest.hpp"
 #include "stats.hpp"
 
 #include "third_party/nonstd/optional.hpp"
index 493cde0b483f48bc70cc9262dd647ae3c3a1c1e3..ad601791c21290c0e47ee7d40e57c9431da39328 100644 (file)
@@ -1042,4 +1042,27 @@ EOF
     if [ -n "$data" ]; then
         test_failed "$manifest contained ignored header: $data"
     fi
+
+    # -------------------------------------------------------------------------
+    TEST "CCACHE_IGNOREOPTIONS"
+
+    CCACHE_IGNOREOPTIONS="-DTEST=1" $CCACHE_COMPILE -DTEST=1 -c test.c
+    expect_stat 'cache hit (direct)' 0
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    CCACHE_IGNOREOPTIONS="-DTEST=1*" $CCACHE_COMPILE -DTEST=1 -c test.c
+    expect_stat 'cache hit (direct)' 1
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    CCACHE_IGNOREOPTIONS="-DTEST=1*" $CCACHE_COMPILE -DTEST=12 -c test.c
+    expect_stat 'cache hit (direct)' 2
+    expect_stat 'cache hit (preprocessed)' 0
+    expect_stat 'cache miss' 1
+
+    CCACHE_IGNOREOPTIONS="-DTEST=2*" $CCACHE_COMPILE -DTEST=12 -c test.c
+    expect_stat 'cache hit (direct)' 2
+    expect_stat 'cache hit (preprocessed)' 1
+    expect_stat 'cache miss' 1
 }
index effb0c16a5a6ec15f3652d315aad58d0c4aeff94..b720312f2d92423089f1f9bf174d5d71d5c7be27 100644 (file)
@@ -58,6 +58,7 @@ TEST_CASE("Config: default values")
   CHECK(!config.hard_link());
   CHECK(config.hash_dir());
   CHECK(config.ignore_headers_in_manifest().empty());
+  CHECK(config.ignore_options().empty());
   CHECK_FALSE(config.keep_comments_cpp());
   CHECK(config.limit_multiple() == Approx(0.8));
   CHECK(config.log_file().empty());
@@ -121,6 +122,7 @@ TEST_CASE("Config::update_from_file")
     "hard_link = true\n"
     "hash_dir = false\n"
     "ignore_headers_in_manifest = a:b/c\n"
+    "ignore_options = -a=* -b\n"
     "keep_comments_cpp = true\n"
     "limit_multiple = 1.0\n"
     "log_file = $USER${USER} \n"
@@ -159,6 +161,7 @@ TEST_CASE("Config::update_from_file")
   CHECK(config.hard_link());
   CHECK_FALSE(config.hash_dir());
   CHECK(config.ignore_headers_in_manifest() == "a:b/c");
+  CHECK(config.ignore_options() == "-a=* -b");
   CHECK(config.keep_comments_cpp());
   CHECK(config.limit_multiple() == Approx(1.0));
   CHECK(config.log_file() == fmt::format("{0}{0}", user));
@@ -402,6 +405,7 @@ TEST_CASE("Config::visit_items")
     "hard_link = true\n"
     "hash_dir = false\n"
     "ignore_headers_in_manifest = ihim\n"
+    "ignore_options = -a=* -b\n"
     "inode_cache = false\n"
     "keep_comments_cpp = true\n"
     "limit_multiple = 0.0\n"
@@ -456,6 +460,7 @@ TEST_CASE("Config::visit_items")
     "(test.conf) hard_link = true",
     "(test.conf) hash_dir = false",
     "(test.conf) ignore_headers_in_manifest = ihim",
+    "(test.conf) ignore_options = -a=* -b",
     "(test.conf) inode_cache = false",
     "(test.conf) keep_comments_cpp = true",
     "(test.conf) limit_multiple = 0.0",