From 231fef47e48e27995c0c3ebc8db0ebd3de96b8fc Mon Sep 17 00:00:00 2001 From: Ryan Egesdahl Date: Thu, 9 Jul 2020 11:17:54 -0700 Subject: [PATCH] Add ability to ignore specified compiler options when hashing (#620) Closes #578. --- doc/MANUAL.adoc | 10 ++++++++++ src/Config.cpp | 10 ++++++++++ src/Config.hpp | 15 +++++++++++++++ src/Context.cpp | 18 ++++++++++++++++++ src/Context.hpp | 13 +++++++++++++ src/ccache.cpp | 26 ++++++++++++++++++++++++++ src/ccache.hpp | 1 + test/suites/direct.bash | 23 +++++++++++++++++++++++ unittest/test_Config.cpp | 5 +++++ 9 files changed, 121 insertions(+) diff --git a/doc/MANUAL.adoc b/doc/MANUAL.adoc index e5811f7bd..d8da30915 100644 --- a/doc/MANUAL.adoc +++ b/doc/MANUAL.adoc @@ -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 diff --git a/src/Config.cpp b/src/Config.cpp index cab2aa71a..a8fdfc805 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -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 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 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; diff --git a/src/Config.hpp b/src/Config.hpp index b7a3c80a1..76e10dc33 100644 --- a/src/Config.hpp +++ b/src/Config.hpp @@ -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) { diff --git a/src/Context.cpp b/src/Context.cpp index 9dc545879..654b098d5 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -25,6 +25,10 @@ #include "logging.hpp" #include "stats.hpp" +#include +#include +#include + 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& 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; + } + } +} diff --git a/src/Context.hpp b/src/Context.hpp index 9e8937376..8e6c332e7 100644 --- a/src/Context.hpp +++ b/src/Context.hpp @@ -126,6 +126,10 @@ public: // Files used by the hash debugging functionality. std::vector hash_debug_files; + // Options to ignore for the hash. + const std::vector& ignore_options() const; + void set_ignore_options(const std::vector& options); + #ifdef MTR_ENABLED // Internal tracing. std::unique_ptr 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 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& +Context::ignore_options() const +{ + return m_ignore_options; +} diff --git a/src/ccache.cpp b/src/ccache.cpp index 6d2a821ae..41cd21bd3 100644 --- a/src/ccache.cpp +++ b/src/ccache.cpp @@ -57,6 +57,7 @@ # include "third_party/getopt_long.h" #endif +#include #include 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& 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. diff --git a/src/ccache.hpp b/src/ccache.hpp index 8a09b3fc3..c69db323e 100644 --- a/src/ccache.hpp +++ b/src/ccache.hpp @@ -23,6 +23,7 @@ #include "Args.hpp" #include "Counters.hpp" +#include "Digest.hpp" #include "stats.hpp" #include "third_party/nonstd/optional.hpp" diff --git a/test/suites/direct.bash b/test/suites/direct.bash index 493cde0b4..ad601791c 100644 --- a/test/suites/direct.bash +++ b/test/suites/direct.bash @@ -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 } diff --git a/unittest/test_Config.cpp b/unittest/test_Config.cpp index effb0c16a..b720312f2 100644 --- a/unittest/test_Config.cpp +++ b/unittest/test_Config.cpp @@ -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", -- 2.47.2