// Copyright (C) 2002-2007 Andrew Tridgell // Copyright (C) 2009-2023 Joel Rosdahl and other contributors // // See doc/AUTHORS.adoc for a complete list of contributors. // // This program is free software; you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free // Software Foundation; either version 3 of the License, or (at your option) // any later version. // // This program is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for // more details. // // You should have received a copy of the GNU General Public License along with // this program; if not, write to the Free Software Foundation, Inc., 51 // Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #include "ccache.hpp" #include "Args.hpp" #include "ArgsInfo.hpp" #include "Context.hpp" #include "Depfile.hpp" #include "Fd.hpp" #include "File.hpp" #include "Finalizer.hpp" #include "Hash.hpp" #include "Logging.hpp" #include "MiniTrace.hpp" #include "SignalHandler.hpp" #include "TemporaryFile.hpp" #include "UmaskScope.hpp" #include "Util.hpp" #include "Win32Util.hpp" #include "argprocessing.hpp" #include "compopt.hpp" #include "execute.hpp" #include "fmtmacros.hpp" #include "hashutil.hpp" #include "language.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "third_party/fmt/core.h" #include #include #include #ifdef HAVE_UNISTD_H # include #endif #include #include #include #include #include #include using core::Statistic; // This is a string that identifies the current "version" of the hash sum // computed by ccache. If, for any reason, we want to force the hash sum to be // different for the same input in a new ccache version, we can just change // this string. A typical example would be if the format of one of the files // stored in the cache changes in a backwards-incompatible way. const char HASH_PREFIX[] = "4"; // Search for k_ccache_disable_token within the first // k_ccache_disable_search_limit bytes of the input file. const size_t k_ccache_disable_search_limit = 4096; // String to look for when checking whether to disable ccache for the input // file. const char k_ccache_disable_token[] = "ccache:disable"; namespace { // Return nonstd::make_unexpected if ccache did not succeed in getting // or putting a result in the cache. If `exit_code` is set, ccache will just // exit with that code directly, otherwise execute the real compiler and exit // with its exit code. Statistics counters will also be incremented. class Failure { public: Failure(Statistic statistic); Failure(std::initializer_list statistics); const core::StatisticsCounters& counters() const; std::optional exit_code() const; void set_exit_code(int exit_code); private: core::StatisticsCounters m_counters; std::optional m_exit_code; }; inline Failure::Failure(const Statistic statistic) : m_counters({statistic}) { } inline Failure::Failure(const std::initializer_list statistics) : m_counters(statistics) { } inline const core::StatisticsCounters& Failure::counters() const { return m_counters; } inline std::optional Failure::exit_code() const { return m_exit_code; } inline void Failure::set_exit_code(const int exit_code) { m_exit_code = exit_code; } } // namespace static bool should_disable_ccache_for_input_file(const std::string& path) { auto content = util::read_file_part(path, 0, k_ccache_disable_search_limit); return content && content->find(k_ccache_disable_token) != std::string::npos; } static void add_prefix(const Context& ctx, Args& args, const std::string& prefix_command) { if (prefix_command.empty()) { return; } Args prefix; for (const auto& word : Util::split_into_strings(prefix_command, " ")) { std::string path = find_executable(ctx, word, ctx.orig_args[0]); if (path.empty()) { throw core::Fatal(FMT("{}: {}", word, strerror(errno))); } prefix.push_back(path); } LOG("Using command-line prefix {}", prefix_command); for (size_t i = prefix.size(); i != 0; i--) { args.push_front(prefix[i - 1]); } } static std::string prepare_debug_path(const std::string& debug_dir, const util::TimePoint& time_of_invocation, const std::string& output_obj, std::string_view suffix) { auto prefix = debug_dir.empty() ? output_obj : debug_dir + util::to_absolute_path_no_drive(output_obj); // Ignore any error from create_dir since we can't handle an error in another // way in this context. The caller takes care of logging when trying to open // the path for writing. Util::create_dir(Util::dir_name(prefix)); char timestamp[100]; const auto tm = Util::localtime(time_of_invocation); if (tm) { strftime(timestamp, sizeof(timestamp), "%Y%m%d_%H%M%S", &*tm); } else { snprintf(timestamp, sizeof(timestamp), "%llu", static_cast(time_of_invocation.sec())); } return FMT("{}.{}_{:06}.ccache-{}", prefix, timestamp, time_of_invocation.nsec_decimal_part() / 1000, suffix); } static void init_hash_debug(Context& ctx, Hash& hash, char type, std::string_view section_name, FILE* debug_text_file) { if (!ctx.config.debug()) { return; } const auto path = prepare_debug_path(ctx.config.debug_dir(), ctx.time_of_invocation, ctx.args_info.output_obj, FMT("input-{}", type)); File debug_binary_file(path, "wb"); if (debug_binary_file) { hash.enable_debug(section_name, debug_binary_file.get(), debug_text_file); ctx.hash_debug_files.push_back(std::move(debug_binary_file)); } else { LOG("Failed to open {}: {}", path, strerror(errno)); } } CompilerType guess_compiler(std::string_view path) { std::string compiler_path(path); #ifndef _WIN32 // Follow symlinks to the real compiler to learn its name. We're not using // Util::real_path in order to save some unnecessary stat calls. while (true) { std::string symlink_value = Util::read_link(compiler_path); if (symlink_value.empty()) { break; } if (util::is_absolute_path(symlink_value)) { compiler_path = symlink_value; } else { compiler_path = FMT("{}/{}", Util::dir_name(compiler_path), symlink_value); } } #endif const auto name = Util::to_lowercase(Util::remove_extension(Util::base_name(compiler_path))); if (name.find("clang-cl") != std::string_view::npos) { return CompilerType::clang_cl; } else if (name.find("clang") != std::string_view::npos) { return CompilerType::clang; } else if (name.find("gcc") != std::string_view::npos || name.find("g++") != std::string_view::npos) { return CompilerType::gcc; } else if (name.find("nvcc") != std::string_view::npos) { return CompilerType::nvcc; } else if (name == "icl") { return CompilerType::icl; } else if (name == "cl") { return CompilerType::msvc; } else { return CompilerType::other; } } static bool include_file_too_new(const Context& ctx, const std::string& path, const Stat& path_stat) { // The comparison using >= is intentional, due to a possible race between // starting compilation and writing the include file. See also the notes under // "Performance" in doc/MANUAL.adoc. if (!(ctx.config.sloppiness().contains(core::Sloppy::include_file_mtime)) && path_stat.mtime() >= ctx.time_of_compilation) { LOG("Include file {} too new", path); return true; } // The same >= logic as above applies to the change time of the file. if (!(ctx.config.sloppiness().contains(core::Sloppy::include_file_ctime)) && path_stat.ctime() >= ctx.time_of_compilation) { LOG("Include file {} ctime too new", path); return true; } return false; } // Returns false if the include file was "too new" and therefore should disable // the direct mode (or, in the case of a preprocessed header, fall back to just // running the real compiler), otherwise true. static bool do_remember_include_file(Context& ctx, std::string path, Hash& cpp_hash, bool system, Hash* depend_mode_hash) { if (path.length() >= 2 && path[0] == '<' && path[path.length() - 1] == '>') { // Typically or . return true; } if (path == ctx.args_info.normalized_input_file) { // Don't remember the input file. return true; } if (system && (ctx.config.sloppiness().contains(core::Sloppy::system_headers))) { // Don't remember this system header. return true; } // Canonicalize path for comparison; Clang uses ./header.h. if (util::starts_with(path, "./")) { path.erase(0, 2); } if (ctx.included_files.find(path) != ctx.included_files.end()) { // Already known include file. return true; } #ifdef _WIN32 { // stat fails on directories on win32. DWORD attributes = GetFileAttributes(path.c_str()); if (attributes != INVALID_FILE_ATTRIBUTES && attributes & FILE_ATTRIBUTE_DIRECTORY) { return true; } } #endif auto st = Stat::stat(path, Stat::OnError::log); if (!st) { return false; } if (st.is_directory()) { // Ignore directory, typically $PWD. return true; } if (!st.is_regular()) { // Device, pipe, socket or other strange creature. LOG("Non-regular include file {}", path); return false; } for (const auto& ignore_header_path : ctx.ignore_header_paths) { if (Util::matches_dir_prefix_or_file(ignore_header_path, path)) { return true; } } const bool is_pch = Util::is_precompiled_header(path); const bool too_new = include_file_too_new(ctx, path, st); if (too_new) { // Opt out of direct mode because of a race condition. // // The race condition consists of these events: // // - the preprocessor is run // - an include file is modified by someone // - the new include file is hashed by ccache // - the real compiler is run on the preprocessor's output, which contains // data from the old header file // - the wrong object file is stored in the cache. return false; } // Let's hash the include file content. Digest file_digest; if (is_pch) { if (ctx.args_info.included_pch_file.empty()) { LOG("Detected use of precompiled header: {}", path); } bool using_pch_sum = false; if (ctx.config.pch_external_checksum()) { // hash pch.sum instead of pch when it exists // to prevent hashing a very large .pch file every time std::string pch_sum_path = FMT("{}.sum", path); if (Stat::stat(pch_sum_path, Stat::OnError::log)) { path = std::move(pch_sum_path); using_pch_sum = true; LOG("Using pch.sum file {}", path); } } if (!hash_binary_file(ctx, file_digest, path)) { return false; } cpp_hash.hash_delimiter(using_pch_sum ? "pch_sum_hash" : "pch_hash"); cpp_hash.hash(file_digest.to_string()); } if (ctx.config.direct_mode()) { if (!is_pch) { // else: the file has already been hashed. auto ret = hash_source_code_file(ctx, file_digest, path); if (ret.contains(HashSourceCode::error) || ret.contains(HashSourceCode::found_time)) { return false; } } ctx.included_files.emplace(path, file_digest); if (depend_mode_hash) { depend_mode_hash->hash_delimiter("include"); depend_mode_hash->hash(file_digest.to_string()); } } return true; } enum class RememberIncludeFileResult { ok, cannot_use_pch }; // This function hashes an include file and stores the path and hash in // ctx.included_files. If the include file is a PCH, cpp_hash is also updated. static RememberIncludeFileResult remember_include_file(Context& ctx, const std::string& path, Hash& cpp_hash, bool system, Hash* depend_mode_hash) { if (!do_remember_include_file( ctx, path, cpp_hash, system, depend_mode_hash)) { if (Util::is_precompiled_header(path)) { return RememberIncludeFileResult::cannot_use_pch; } else if (ctx.config.direct_mode()) { LOG_RAW("Disabling direct mode"); ctx.config.set_direct_mode(false); } } return RememberIncludeFileResult::ok; } static void print_included_files(const Context& ctx, FILE* fp) { for (const auto& [path, digest] : ctx.included_files) { PRINT(fp, "{}\n", path); } } // This function reads and hashes a file. While doing this, it also does these // things: // // - Makes include file paths for which the base directory is a prefix relative // when computing the hash sum. // - Stores the paths and hashes of included files in ctx.included_files. static nonstd::expected process_preprocessed_file(Context& ctx, Hash& hash, const std::string& path) { auto data = util::read_file(path); if (!data) { LOG("Failed to read {}: {}", path, data.error()); return nonstd::make_unexpected(Statistic::internal_error); } std::unordered_map relative_inc_path_cache; // Bytes between p and q are pending to be hashed. char* q = &(*data)[0]; const char* p = q; const char* end = p + data->length(); // There must be at least 7 characters (# 1 "x") left to potentially find an // include file path. while (q < end - 7) { static const std::string_view pragma_gcc_pch_preprocess = "pragma GCC pch_preprocess "; static const std::string_view hash_31_command_line_newline = "# 31 \"\"\n"; static const std::string_view hash_32_command_line_2_newline = "# 32 \"\" 2\n"; // Note: Intentionally not using the string form to avoid false positive // match by ccache itself. static const char incbin_directive[] = {'.', 'i', 'n', 'c', 'b', 'i', 'n'}; // Check if we look at a line containing the file name of an included file. // At least the following formats exist (where N is a positive integer): // // GCC: // // # N "file" // # N "file" N // #pragma GCC pch_preprocess "file" // // HP's compiler: // // #line N "file" // // AIX's compiler: // // #line N "file" // #line N // // Note that there may be other lines starting with '#' left after // preprocessing as well, for instance "# pragma". if (q[0] == '#' // GCC: && ((q[1] == ' ' && q[2] >= '0' && q[2] <= '9') // GCC precompiled header: || util::starts_with(&q[1], pragma_gcc_pch_preprocess) // HP/AIX: || (q[1] == 'l' && q[2] == 'i' && q[3] == 'n' && q[4] == 'e' && q[5] == ' ')) && (q == data->data() || q[-1] == '\n')) { // Workarounds for preprocessor linemarker bugs in GCC version 6. if (q[2] == '3') { if (util::starts_with(q, hash_31_command_line_newline)) { // Bogus extra line with #31, after the regular #1: Ignore the whole // line, and continue parsing. hash.hash(p, q - p); while (q < end && *q != '\n') { q++; } q++; p = q; continue; } else if (util::starts_with(q, hash_32_command_line_2_newline)) { // Bogus wrong line with #32, instead of regular #1: Replace the line // number with the usual one. hash.hash(p, q - p); q += 1; q[0] = '#'; q[1] = ' '; q[2] = '1'; p = q; } } while (q < end && *q != '"' && *q != '\n') { q++; } if (q < end && *q == '\n') { // A newline before the quotation mark -> no match. continue; } q++; if (q >= end) { LOG_RAW("Failed to parse included file path"); return nonstd::make_unexpected(Statistic::internal_error); } // q points to the beginning of an include file path hash.hash(p, q - p); p = q; while (q < end && *q != '"') { q++; } if (p == q) { // Skip empty file name. continue; } // Look for preprocessor flags, after the "filename". bool system = false; const char* r = q + 1; while (r < end && *r != '\n') { if (*r == '3') { // System header. system = true; } r++; } // p and q span the include file path. std::string inc_path(p, q - p); auto it = relative_inc_path_cache.find(inc_path); if (it == relative_inc_path_cache.end()) { auto rel_inc_path = Util::make_relative_path( ctx, Util::normalize_concrete_absolute_path(inc_path)); relative_inc_path_cache.emplace(inc_path, rel_inc_path); inc_path = std::move(rel_inc_path); } else { inc_path = it->second; } if ((inc_path != ctx.apparent_cwd) || ctx.config.hash_dir()) { hash.hash(inc_path); } if (remember_include_file(ctx, inc_path, hash, system, nullptr) == RememberIncludeFileResult::cannot_use_pch) { return nonstd::make_unexpected( Statistic::could_not_use_precompiled_header); } p = q; // Everything of interest between p and q has been hashed now. } else if (strncmp(q, incbin_directive, sizeof(incbin_directive)) == 0 && ((q[7] == ' ' && (q[8] == '"' || (q[8] == '\\' && q[9] == '"'))) || q[7] == '"')) { // An assembler .inc bin (without the space) statement, which could be // part of inline assembly, refers to an external file. If the file // changes, the hash should change as well, but finding out what file to // hash is too hard for ccache, so just bail out. LOG_RAW( "Found potential unsupported .inc" "bin directive in source code"); return nonstd::make_unexpected( Failure(Statistic::unsupported_code_directive)); } else if (strncmp(q, "___________", 10) == 0 && (q == data->data() || q[-1] == '\n')) { // Unfortunately the distcc-pump wrapper outputs standard output lines: // __________Using distcc-pump from /usr/bin // __________Using # distcc servers in pump mode // __________Shutting down distcc-pump include server hash.hash(p, q - p); while (q < end && *q != '\n') { q++; } if (*q == '\n') { q++; } p = q; continue; } else { q++; } } hash.hash(p, (end - p)); // Explicitly check the .gch/.pch/.pth file as Clang does not include any // mention of it in the preprocessed output. if (!ctx.args_info.included_pch_file.empty()) { std::string pch_path = Util::make_relative_path(ctx, ctx.args_info.included_pch_file); hash.hash(pch_path); remember_include_file(ctx, pch_path, hash, false, nullptr); } bool debug_included = getenv("CCACHE_DEBUG_INCLUDED"); if (debug_included) { print_included_files(ctx, stdout); } return {}; } // Extract the used includes from the dependency file. Note that we cannot // distinguish system headers from other includes here. static std::optional result_key_from_depfile(Context& ctx, Hash& hash) { // Make sure that result hash will always be different from the manifest hash // since there otherwise may a storage key collision (in case the dependency // file is empty). hash.hash_delimiter("result"); const auto file_content = util::read_file(ctx.args_info.output_dep); if (!file_content) { LOG("Failed to read dependency file {}: {}", ctx.args_info.output_dep, file_content.error()); return std::nullopt; } for (std::string_view token : Depfile::tokenize(*file_content)) { if (util::ends_with(token, ":")) { continue; } std::string path = Util::make_relative_path(ctx, token); remember_include_file(ctx, path, hash, false, &hash); } // Explicitly check the .gch/.pch/.pth file as it may not be mentioned in the // dependencies output. if (!ctx.args_info.included_pch_file.empty()) { std::string pch_path = Util::make_relative_path(ctx, ctx.args_info.included_pch_file); hash.hash(pch_path); remember_include_file(ctx, pch_path, hash, false, nullptr); } bool debug_included = getenv("CCACHE_DEBUG_INCLUDED"); if (debug_included) { print_included_files(ctx, stdout); } return hash.digest(); } struct GetTmpFdResult { Fd fd; std::string path; }; static GetTmpFdResult get_tmp_fd(Context& ctx, const std::string_view description, const bool capture_output) { if (capture_output) { TemporaryFile tmp_stdout( FMT("{}/{}", ctx.config.temporary_dir(), description)); ctx.register_pending_tmp_file(tmp_stdout.path); return {std::move(tmp_stdout.fd), std::move(tmp_stdout.path)}; } else { const auto dev_null_path = util::get_dev_null_path(); return {Fd(open(dev_null_path, O_WRONLY | O_BINARY)), dev_null_path}; } } struct DoExecuteResult { int exit_status; util::Bytes stdout_data; util::Bytes stderr_data; }; // Extract the used includes from /showIncludes output in stdout. Note that we // cannot distinguish system headers from other includes here. static std::optional result_key_from_includes(Context& ctx, Hash& hash, std::string_view stdout_data) { for (std::string_view include : core::MsvcShowIncludesOutput::get_includes( stdout_data, ctx.config.msvc_dep_prefix())) { const std::string path = Util::make_relative_path( ctx, Util::normalize_abstract_absolute_path(include)); remember_include_file(ctx, path, hash, false, &hash); } // Explicitly check the .pch file as it is not mentioned in the // includes output. if (!ctx.args_info.included_pch_file.empty()) { std::string pch_path = Util::make_relative_path(ctx, ctx.args_info.included_pch_file); hash.hash(pch_path); remember_include_file(ctx, pch_path, hash, false, nullptr); } const bool debug_included = getenv("CCACHE_DEBUG_INCLUDED"); if (debug_included) { print_included_files(ctx, stdout); } return hash.digest(); } // Execute the compiler/preprocessor, with logic to retry without requesting // colored diagnostics messages if that fails. static nonstd::expected do_execute(Context& ctx, Args& args, const bool capture_stdout = true) { UmaskScope umask_scope(ctx.original_umask); if (ctx.diagnostics_color_failed) { DEBUG_ASSERT(ctx.config.compiler_type() == CompilerType::gcc); args.erase_last("-fdiagnostics-color"); } auto tmp_stdout = get_tmp_fd(ctx, "stdout", capture_stdout); auto tmp_stderr = get_tmp_fd(ctx, "stderr", true); int status = execute(ctx, args.to_argv().data(), std::move(tmp_stdout.fd), std::move(tmp_stderr.fd)); if (status != 0 && !ctx.diagnostics_color_failed && ctx.config.compiler_type() == CompilerType::gcc) { const auto errors = util::read_file(tmp_stderr.path); if (errors && errors->find("fdiagnostics-color") != std::string::npos) { // GCC versions older than 4.9 don't understand -fdiagnostics-color, and // non-GCC compilers misclassified as CompilerType::gcc might not do it // either. We assume that if the error message contains // "fdiagnostics-color" then the compilation failed due to // -fdiagnostics-color being unsupported and we then retry without the // flag. (Note that there intentionally is no leading dash in // "fdiagnostics-color" since some compilers don't include the dash in the // error message.) LOG_RAW("-fdiagnostics-color is unsupported; trying again without it"); ctx.diagnostics_color_failed = true; return do_execute(ctx, args, capture_stdout); } } util::Bytes stdout_data; if (capture_stdout) { auto stdout_data_result = util::read_file(tmp_stdout.path); if (!stdout_data_result) { LOG("Failed to read {} (cleanup in progress?): {}", tmp_stdout.path, stdout_data_result.error()); return nonstd::make_unexpected(Statistic::missing_cache_file); } stdout_data = *stdout_data_result; } auto stderr_data_result = util::read_file(tmp_stderr.path); if (!stderr_data_result) { LOG("Failed to read {} (cleanup in progress?): {}", tmp_stderr.path, stderr_data_result.error()); return nonstd::make_unexpected(Statistic::missing_cache_file); } return DoExecuteResult{status, stdout_data, *stderr_data_result}; } static void read_manifest(Context& ctx, nonstd::span cache_entry_data) { try { core::CacheEntry cache_entry(cache_entry_data); cache_entry.verify_checksum(); ctx.manifest.read(cache_entry.payload()); } catch (const core::Error& e) { LOG("Error reading manifest: {}", e.what()); } } static void update_manifest(Context& ctx, const Digest& manifest_key, const Digest& result_key) { if (ctx.config.read_only() || ctx.config.read_only_direct()) { return; } ASSERT(ctx.config.direct_mode()); MTR_SCOPE("manifest", "manifest_put"); // ctime() may be 0, so we have to check time_of_compilation against // MAX(mtime, ctime). // // ccache only reads mtime/ctime if file_stat_matches sloppiness is enabled, // so mtimes/ctimes are stored as a dummy value (-1) if not enabled. This // reduces the number of file_info entries for the common case. const bool save_timestamp = (ctx.config.sloppiness().contains(core::Sloppy::file_stat_matches)) || ctx.args_info.output_is_precompiled_header; const bool added = ctx.manifest.add_result( result_key, ctx.included_files, [&](const std::string& path) { auto stat = Stat::stat(path, Stat::OnError::log); bool cache_time = save_timestamp && ctx.time_of_compilation > std::max(stat.mtime(), stat.ctime()); return core::Manifest::FileStats{ stat.size(), stat && cache_time ? stat.mtime() : util::TimePoint(), stat && cache_time ? stat.ctime() : util::TimePoint(), }; }); if (added) { LOG("Added result key to manifest {}", manifest_key.to_string()); core::CacheEntry::Header header(ctx.config, core::CacheEntryType::manifest); ctx.storage.put(manifest_key, core::CacheEntryType::manifest, core::CacheEntry::serialize(header, ctx.manifest)); } else { LOG("Did not add result key to manifest {}", manifest_key.to_string()); } } struct FindCoverageFileResult { bool found; std::string path; bool mangled; }; static FindCoverageFileResult find_coverage_file(const Context& ctx) { // GCC 9+ writes coverage data for /dir/to/example.o to #dir#to#example.gcno // (in CWD) if -fprofile-dir=DIR is present (regardless of DIR) instead of the // traditional /dir/to/example.gcno. std::string mangled_form = core::Result::gcno_file_in_mangled_form(ctx); std::string unmangled_form = core::Result::gcno_file_in_unmangled_form(ctx); std::string found_file; if (Stat::stat(mangled_form)) { LOG("Found coverage file {}", mangled_form); found_file = mangled_form; } if (Stat::stat(unmangled_form)) { LOG("Found coverage file {}", unmangled_form); if (!found_file.empty()) { LOG_RAW("Found two coverage files, cannot continue"); return {}; } found_file = unmangled_form; } if (found_file.empty()) { LOG("No coverage file found (tried {} and {}), cannot continue", unmangled_form, mangled_form); return {}; } return {true, found_file, found_file == mangled_form}; } [[nodiscard]] static bool write_result(Context& ctx, const Digest& result_key, const Stat& obj_stat, const util::Bytes& stdout_data, const util::Bytes& stderr_data) { core::Result::Serializer serializer(ctx.config); if (!stderr_data.empty()) { serializer.add_data(core::Result::FileType::stderr_output, stderr_data); } // Write stdout only after stderr (better with MSVC), as ResultRetriever // will later print process them in the order they are read. if (!stdout_data.empty()) { serializer.add_data(core::Result::FileType::stdout_output, stdout_data); } if (obj_stat && !serializer.add_file(core::Result::FileType::object, ctx.args_info.output_obj)) { LOG("Object file {} missing", ctx.args_info.output_obj); return false; } if (ctx.args_info.generating_dependencies && !serializer.add_file(core::Result::FileType::dependency, ctx.args_info.output_dep)) { LOG("Dependency file {} missing", ctx.args_info.output_dep); return false; } if (ctx.args_info.generating_coverage) { const auto coverage_file = find_coverage_file(ctx); if (!coverage_file.found) { LOG_RAW("Coverage file not found"); return false; } if (!serializer.add_file(coverage_file.mangled ? core::Result::FileType::coverage_mangled : core::Result::FileType::coverage_unmangled, coverage_file.path)) { LOG("Coverage file {} missing", coverage_file.path); return false; } } if (ctx.args_info.generating_stackusage && !serializer.add_file(core::Result::FileType::stackusage, ctx.args_info.output_su)) { LOG("Stack usage file {} missing", ctx.args_info.output_su); return false; } if (ctx.args_info.generating_diagnostics && !serializer.add_file(core::Result::FileType::diagnostic, ctx.args_info.output_dia)) { LOG("Diagnostics file {} missing", ctx.args_info.output_dia); return false; } if (ctx.args_info.seen_split_dwarf // Only store .dwo file if it was created by the compiler (GCC and Clang // behave differently e.g. for "-gsplit-dwarf -g1"). && Stat::stat(ctx.args_info.output_dwo) && !serializer.add_file(core::Result::FileType::dwarf_object, ctx.args_info.output_dwo)) { LOG("Split dwarf file {} missing", ctx.args_info.output_dwo); return false; } if (!ctx.args_info.output_al.empty() && !serializer.add_file(core::Result::FileType::assembler_listing, ctx.args_info.output_al)) { LOG("Assembler listing file {} missing", ctx.args_info.output_al); return false; } core::CacheEntry::Header header(ctx.config, core::CacheEntryType::result); const auto cache_entry_data = core::CacheEntry::serialize(header, serializer); if (!ctx.config.remote_only()) { const auto& raw_files = serializer.get_raw_files(); if (!raw_files.empty()) { ctx.storage.local.put_raw_files(result_key, raw_files); } } ctx.storage.put(result_key, core::CacheEntryType::result, cache_entry_data); return true; } static util::Bytes rewrite_stdout_from_compiler(const Context& ctx, util::Bytes&& stdout_data) { using util::Tokenizer; using Mode = Tokenizer::Mode; using IncludeDelimiter = Tokenizer::IncludeDelimiter; if (!stdout_data.empty()) { util::Bytes new_stdout_data; for (const auto line : Tokenizer(util::to_string_view(stdout_data), "\n", Mode::include_empty, IncludeDelimiter::yes)) { if (util::starts_with(line, "__________")) { Util::send_to_fd(ctx, line, STDOUT_FILENO); } // Ninja uses the lines with 'Note: including file: ' to determine the // used headers. Headers within basedir need to be changed into relative // paths because otherwise Ninja will use the abs path to original header // to check if a file needs to be recompiled. else if (ctx.config.compiler_type() == CompilerType::msvc && !ctx.config.base_dir().empty() && util::starts_with(line, ctx.config.msvc_dep_prefix())) { std::string orig_line(line.data(), line.length()); std::string abs_inc_path = util::replace_first(orig_line, ctx.config.msvc_dep_prefix(), ""); abs_inc_path = util::strip_whitespace(abs_inc_path); std::string rel_inc_path = Util::make_relative_path( ctx, Util::normalize_concrete_absolute_path(abs_inc_path)); std::string line_with_rel_inc = util::replace_first(orig_line, abs_inc_path, rel_inc_path); new_stdout_data.insert(new_stdout_data.begin(), line_with_rel_inc.data(), line_with_rel_inc.size()); } else { new_stdout_data.insert(new_stdout_data.end(), line.data(), line.size()); } } return new_stdout_data; } else { return std::move(stdout_data); } } // Run the real compiler and put the result in cache. Returns the result key. static nonstd::expected to_cache(Context& ctx, Args& args, std::optional result_key, const Args& depend_extra_args, Hash* depend_mode_hash) { if (ctx.config.is_compiler_group_msvc()) { args.push_back(fmt::format("-Fo{}", ctx.args_info.output_obj)); } else { args.push_back("-o"); args.push_back(ctx.args_info.output_obj); } if (ctx.config.hard_link() && ctx.args_info.output_obj != "/dev/null") { // Workaround for Clang bug where it overwrites an existing object file // when it's compiling an assembler file, see // . Util::unlink_safe(ctx.args_info.output_obj); } if (ctx.args_info.generating_diagnostics) { args.push_back("--serialize-diagnostics"); args.push_back(ctx.args_info.output_dia); } if (ctx.args_info.seen_double_dash) { args.push_back("--"); } if (ctx.config.run_second_cpp()) { args.push_back(ctx.args_info.input_file); } else { args.push_back(ctx.i_tmpfile); } if (ctx.args_info.seen_split_dwarf) { // Remove any pre-existing .dwo file since we want to check if the compiler // produced one, intentionally not using x_unlink or tmp_unlink since we're // not interested in logging successful deletions or failures due to // nonexistent .dwo files. if (unlink(ctx.args_info.output_dwo.c_str()) != 0 && errno != ENOENT && errno != ESTALE) { LOG("Failed to unlink {}: {}", ctx.args_info.output_dwo, strerror(errno)); return nonstd::make_unexpected(Statistic::bad_output_file); } } LOG_RAW("Running real compiler"); MTR_BEGIN("execute", "compiler"); nonstd::expected result; if (!ctx.config.depend_mode()) { result = do_execute(ctx, args); args.pop_back(3); } else { // Use the original arguments (including dependency options) in depend // mode. Args depend_mode_args = ctx.orig_args; depend_mode_args.erase_with_prefix("--ccache-"); // Add depend_mode_args directly after the compiler. We can't add them last // since options then may be placed after a "--" option. depend_mode_args.insert(1, depend_extra_args); add_prefix(ctx, depend_mode_args, ctx.config.prefix_command()); ctx.time_of_compilation = util::TimePoint::now(); result = do_execute(ctx, depend_mode_args); } MTR_END("execute", "compiler"); if (!result) { return nonstd::make_unexpected(result.error()); } // Merge stderr from the preprocessor (if any) and stderr from the real // compiler. if (!ctx.cpp_stderr_data.empty()) { result->stderr_data.insert(result->stderr_data.begin(), ctx.cpp_stderr_data.begin(), ctx.cpp_stderr_data.end()); } result->stdout_data = rewrite_stdout_from_compiler(ctx, std::move(result->stdout_data)); if (result->exit_status != 0) { LOG("Compiler gave exit status {}", result->exit_status); // We can output stderr immediately instead of rerunning the compiler. Util::send_to_fd( ctx, util::to_string_view(result->stderr_data), STDERR_FILENO); Util::send_to_fd( ctx, util::to_string_view(core::MsvcShowIncludesOutput::strip_includes( ctx, std::move(result->stdout_data))), STDOUT_FILENO); auto failure = Failure(Statistic::compile_failed); failure.set_exit_code(result->exit_status); return nonstd::make_unexpected(failure); } if (ctx.config.depend_mode()) { ASSERT(depend_mode_hash); if (ctx.args_info.generating_dependencies) { result_key = result_key_from_depfile(ctx, *depend_mode_hash); } else if (ctx.args_info.generating_includes) { result_key = result_key_from_includes( ctx, *depend_mode_hash, util::to_string_view(result->stdout_data)); } else { ASSERT(false); } if (!result_key) { return nonstd::make_unexpected(Statistic::internal_error); } LOG_RAW("Got result key from dependency file"); LOG("Result key: {}", result_key->to_string()); } ASSERT(result_key); bool produce_dep_file = ctx.args_info.generating_dependencies && ctx.args_info.output_dep != "/dev/null"; if (produce_dep_file) { Depfile::make_paths_relative_in_output_dep(ctx); } Stat obj_stat; if (!ctx.args_info.expect_output_obj) { // Don't probe for object file when we don't expect one since we otherwise // will be fooled by an already existing object file. LOG_RAW("Compiler not expected to produce an object file"); } else { obj_stat = Stat::stat(ctx.args_info.output_obj); if (!obj_stat) { LOG_RAW("Compiler didn't produce an object file"); return nonstd::make_unexpected(Statistic::compiler_produced_no_output); } else if (obj_stat.size() == 0) { LOG_RAW("Compiler produced an empty object file"); return nonstd::make_unexpected(Statistic::compiler_produced_empty_output); } } MTR_BEGIN("result", "result_put"); if (!write_result( ctx, *result_key, obj_stat, result->stdout_data, result->stderr_data)) { return nonstd::make_unexpected(Statistic::compiler_produced_no_output); } MTR_END("result", "result_put"); // Everything OK. Util::send_to_fd( ctx, util::to_string_view(result->stderr_data), STDERR_FILENO); // Send stdout after stderr, it makes the output clearer with MSVC. Util::send_to_fd( ctx, util::to_string_view(core::MsvcShowIncludesOutput::strip_includes( ctx, std::move(result->stdout_data))), STDOUT_FILENO); return *result_key; } // Find the result key by running the compiler in preprocessor mode and // hashing the result. static nonstd::expected get_result_key_from_cpp(Context& ctx, Args& args, Hash& hash) { ctx.time_of_compilation = util::TimePoint::now(); std::string preprocessed_path; util::Bytes cpp_stderr_data; if (ctx.args_info.direct_i_file) { // We are compiling a .i or .ii file - that means we can skip the cpp stage // and directly form the correct i_tmpfile. preprocessed_path = ctx.args_info.input_file; } else { // Run cpp on the input file to obtain the .i. // preprocessed_path needs the proper cpp_extension for the compiler to do // its thing correctly. TemporaryFile tmp_stdout(FMT("{}/cpp_stdout", ctx.config.temporary_dir()), FMT(".{}", ctx.config.cpp_extension())); preprocessed_path = tmp_stdout.path; tmp_stdout.fd.close(); // We're only using the path. ctx.register_pending_tmp_file(preprocessed_path); const size_t orig_args_size = args.size(); if (ctx.config.keep_comments_cpp()) { args.push_back("-C"); } // Send preprocessor output to a file instead of stdout to work around // compilers that don't exit with a proper status on write error to stdout. // See also . if (ctx.config.is_compiler_group_msvc()) { args.push_back("-P"); args.push_back(FMT("-Fi{}", preprocessed_path)); } else { args.push_back("-E"); args.push_back("-o"); args.push_back(preprocessed_path); } args.push_back(ctx.args_info.input_file); add_prefix(ctx, args, ctx.config.prefix_command_cpp()); LOG_RAW("Running preprocessor"); MTR_BEGIN("execute", "preprocessor"); const auto result = do_execute(ctx, args, false); MTR_END("execute", "preprocessor"); args.pop_back(args.size() - orig_args_size); if (!result) { return nonstd::make_unexpected(result.error()); } else if (result->exit_status != 0) { LOG("Preprocessor gave exit status {}", result->exit_status); return nonstd::make_unexpected(Statistic::preprocessor_error); } cpp_stderr_data = result->stderr_data; } hash.hash_delimiter("cpp"); TRY(process_preprocessed_file(ctx, hash, preprocessed_path)); hash.hash_delimiter("cppstderr"); hash.hash(util::to_string_view(cpp_stderr_data)); ctx.i_tmpfile = preprocessed_path; if (!ctx.config.run_second_cpp()) { // If we are using the CPP trick, we need to remember this stderr data and // output it just before the main stderr from the compiler pass. ctx.cpp_stderr_data = std::move(cpp_stderr_data); hash.hash_delimiter("runsecondcpp"); hash.hash("false"); } return hash.digest(); } // Hash mtime or content of a file, or the output of a command, according to // the CCACHE_COMPILERCHECK setting. static nonstd::expected hash_compiler(const Context& ctx, Hash& hash, const Stat& st, const std::string& path, bool allow_command) { if (ctx.config.compiler_check() == "none") { // Do nothing. } else if (ctx.config.compiler_check() == "mtime") { hash.hash_delimiter("cc_mtime"); hash.hash(st.size()); hash.hash(st.mtime().nsec()); } else if (util::starts_with(ctx.config.compiler_check(), "string:")) { hash.hash_delimiter("cc_hash"); hash.hash(&ctx.config.compiler_check()[7]); } else if (ctx.config.compiler_check() == "content" || !allow_command) { hash.hash_delimiter("cc_content"); hash_binary_file(ctx, hash, path); } else { // command string if (!hash_multicommand_output( hash, ctx.config.compiler_check(), ctx.orig_args[0])) { LOG("Failure running compiler check command: {}", ctx.config.compiler_check()); return nonstd::make_unexpected(Statistic::compiler_check_failed); } } return {}; } // Hash the host compiler(s) invoked by nvcc. // // If `ccbin_st` and `ccbin` are set, they refer to a directory or compiler set // with -ccbin/--compiler-bindir. If `ccbin_st` is nullptr or `ccbin` is the // empty string, the compilers are looked up in PATH instead. static nonstd::expected hash_nvcc_host_compiler(const Context& ctx, Hash& hash, const Stat* ccbin_st = nullptr, const std::string& ccbin = {}) { // From : // // "[...] Specify the directory in which the compiler executable resides. // The host compiler executable name can be also specified to ensure that // the correct host compiler is selected." // // and // // "On all platforms, the default host compiler executable (gcc and g++ on // Linux, clang and clang++ on Mac OS X, and cl.exe on Windows) found in // the current execution search path will be used". if (ccbin.empty() || !ccbin_st || ccbin_st->is_directory()) { #if defined(__APPLE__) const char* compilers[] = {"clang", "clang++"}; #elif defined(_WIN32) const char* compilers[] = {"cl.exe"}; #else const char* compilers[] = {"gcc", "g++"}; #endif for (const char* compiler : compilers) { if (!ccbin.empty()) { std::string path = FMT("{}/{}", ccbin, compiler); auto st = Stat::stat(path); if (st) { TRY(hash_compiler(ctx, hash, st, path, false)); } } else { 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)); } } } } else { TRY(hash_compiler(ctx, hash, *ccbin_st, ccbin, false)); } return {}; } // update a hash with information common for the direct and preprocessor modes. static nonstd::expected hash_common_info(const Context& ctx, const Args& args, Hash& hash, const ArgsInfo& args_info) { hash.hash(HASH_PREFIX); if (!ctx.config.namespace_().empty()) { hash.hash_delimiter("namespace"); hash.hash(ctx.config.namespace_()); } // We have to hash the extension, as a .i file isn't treated the same by the // compiler as a .ii file. hash.hash_delimiter("ext"); hash.hash(ctx.config.cpp_extension()); #ifdef _WIN32 const std::string compiler_path = Win32Util::add_exe_suffix(args[0]); #else const std::string compiler_path = args[0]; #endif auto st = Stat::stat(compiler_path, Stat::OnError::log); if (!st) { return nonstd::make_unexpected(Statistic::could_not_find_compiler); } // Hash information about the compiler. TRY(hash_compiler(ctx, hash, st, compiler_path, true)); // Also hash the compiler name as some compilers use hard links and behave // differently depending on the real name. hash.hash_delimiter("cc_name"); hash.hash(Util::base_name(args[0])); // Hash variables that may affect the compilation. const char* always_hash_env_vars[] = { // From // (note: SOURCE_DATE_EPOCH is handled in hash_source_code_string()): "COMPILER_PATH", "GCC_COMPARE_DEBUG", "GCC_EXEC_PREFIX", // Variables that affect which underlying compiler ICC uses. Reference: // "I_MPI_CC", "I_MPI_CXX", #ifdef __APPLE__ // On macOS, /usr/bin/clang is a compiler wrapper that switches compiler // based on at least these variables: "DEVELOPER_DIR", "MACOSX_DEPLOYMENT_TARGET", #endif }; for (const char* name : always_hash_env_vars) { const char* value = getenv(name); if (value) { hash.hash_delimiter(name); hash.hash(value); } } if (!(ctx.config.sloppiness().contains(core::Sloppy::locale))) { // Hash environment variables that may affect localization of compiler // warning messages. const char* envvars[] = { "LANG", "LC_ALL", "LC_CTYPE", "LC_MESSAGES", nullptr}; for (const char** p = envvars; *p; ++p) { const char* v = getenv(*p); if (v) { hash.hash_delimiter(*p); hash.hash(v); } } } // Possibly hash the current working directory. if (args_info.generating_debuginfo && ctx.config.hash_dir()) { std::string dir_to_hash = ctx.apparent_cwd; for (const auto& map : args_info.debug_prefix_maps) { size_t sep_pos = map.find('='); if (sep_pos != std::string::npos) { std::string old_path = map.substr(0, sep_pos); std::string new_path = map.substr(sep_pos + 1); LOG("Relocating debuginfo from {} to {} (CWD: {})", old_path, new_path, ctx.apparent_cwd); if (util::starts_with(ctx.apparent_cwd, old_path)) { dir_to_hash = new_path + ctx.apparent_cwd.substr(old_path.size()); } } } LOG("Hashing CWD {}", dir_to_hash); hash.hash_delimiter("cwd"); hash.hash(dir_to_hash); } // The object file produced by MSVC includes the full path to the source file // even without debug flags. Hashing the directory should be enough since the // filename is included in the hash anyway. if (ctx.config.is_compiler_group_msvc() && ctx.config.hash_dir()) { const std::string output_obj_dir = util::is_absolute_path(args_info.output_obj) ? std::string(Util::dir_name(args_info.output_obj)) : ctx.actual_cwd; LOG("Hashing object file directory {}", output_obj_dir); hash.hash_delimiter("source path"); hash.hash(output_obj_dir); } if (ctx.args_info.seen_split_dwarf || ctx.args_info.profile_arcs) { // When using -gsplit-dwarf: Object files include a link to the // corresponding .dwo file based on the target object filename, so hashing // the object file path will do it, although just hashing the object file // base name would be enough. // // When using -fprofile-arcs (including implicitly via --coverage): the // object file contains a .gcda path based on the object file path. hash.hash_delimiter("object file"); hash.hash(ctx.args_info.output_obj); } if (ctx.args_info.generating_coverage && !(ctx.config.sloppiness().contains(core::Sloppy::gcno_cwd))) { // GCC 9+ includes $PWD in the .gcno file. Since we don't have knowledge // about compiler version we always (unless sloppiness is wanted) include // the directory in the hash for now. LOG_RAW("Hashing apparent CWD due to generating a .gcno file"); hash.hash_delimiter("CWD in .gcno"); hash.hash(ctx.apparent_cwd); } // Possibly hash the coverage data file path. if (ctx.args_info.generating_coverage && ctx.args_info.profile_arcs) { std::string dir; if (!ctx.args_info.profile_path.empty()) { dir = ctx.args_info.profile_path; } else { dir = Util::real_path(std::string(Util::dir_name(ctx.args_info.output_obj))); } std::string_view stem = Util::remove_extension(Util::base_name(ctx.args_info.output_obj)); std::string gcda_path = FMT("{}/{}.gcda", dir, stem); LOG("Hashing coverage path {}", gcda_path); hash.hash_delimiter("gcda"); hash.hash(gcda_path); } // Possibly hash the sanitize blacklist file path. for (const auto& sanitize_blacklist : args_info.sanitize_blacklists) { LOG("Hashing sanitize blacklist {}", sanitize_blacklist); hash.hash_delimiter("sanitizeblacklist"); if (!hash_binary_file(ctx, hash, sanitize_blacklist)) { return nonstd::make_unexpected(Statistic::error_hashing_extra_file); } } if (!ctx.config.extra_files_to_hash().empty()) { for (const std::string& path : util::split_path_list(ctx.config.extra_files_to_hash())) { LOG("Hashing extra file {}", path); hash.hash_delimiter("extrafile"); if (!hash_binary_file(ctx, hash, path)) { return nonstd::make_unexpected(Statistic::error_hashing_extra_file); } } } // Possibly hash GCC_COLORS (for color diagnostics). if (ctx.config.compiler_type() == CompilerType::gcc) { const char* gcc_colors = getenv("GCC_COLORS"); if (gcc_colors) { hash.hash_delimiter("gcccolors"); hash.hash(gcc_colors); } } return {}; } static bool option_should_be_ignored(const std::string& arg, const std::vector& patterns) { return std::any_of( patterns.cbegin(), patterns.cend(), [&arg](const auto& pattern) { const auto& prefix = std::string_view(pattern).substr(0, pattern.length() - 1); return ( pattern == arg || (util::ends_with(pattern, "*") && util::starts_with(arg, prefix))); }); } static std::tuple, std::optional> get_option_and_value(std::string_view option, const Args& args, size_t& i) { if (args[i] == option) { if (i + 1 < args.size()) { ++i; return {option, args[i]}; } else { return {std::nullopt, std::nullopt}; } } else if (util::starts_with(args[i], option)) { return {option, std::string_view(args[i]).substr(option.length())}; } else { return {std::nullopt, std::nullopt}; } } static nonstd::expected hash_argument(const Context& ctx, const Args& args, size_t& i, Hash& hash, const bool is_clang, const bool direct_mode, bool& found_ccbin) { // Trust the user if they've said we should not hash a given option. if (option_should_be_ignored(args[i], ctx.ignore_options())) { LOG("Not hashing ignored option: {}", args[i]); if (i + 1 < args.size() && compopt_takes_arg(args[i])) { i++; LOG("Not hashing argument of ignored option: {}", args[i]); } return {}; } // -L doesn't affect compilation (except for clang). if (i < args.size() - 1 && args[i] == "-L" && !is_clang) { i++; return {}; } if (util::starts_with(args[i], "-L") && !is_clang) { return {}; } // -Wl,... doesn't affect compilation (except for clang). if (util::starts_with(args[i], "-Wl,") && !is_clang) { return {}; } if (util::starts_with(args[i], "-Wa,")) { // We have to distinguish between three cases: // // Case 1: -Wa,-a (write to stdout) // Case 2: -Wa,-a= (write to stdout and stderr) // Case 3: -Wa,-a=file (write to file) // // No need to include the file part in case 3 in the hash since the filename // is not part of the output. hash.hash_delimiter("arg"); bool first = true; for (const auto part : util::Tokenizer(args[i], ",", util::Tokenizer::Mode::include_empty)) { if (first) { first = false; } else { hash.hash(","); } if (util::starts_with(part, "-a")) { const auto eq_pos = part.find('='); if (eq_pos < part.size() - 1) { // Case 3: hash.hash(part.substr(0, eq_pos + 1)); hash.hash("file"); continue; } } // Case 1 and 2: hash.hash(part); } return {}; } // The -fdebug-prefix-map option may be used in combination with // CCACHE_BASEDIR to reuse results across different directories. Skip using // the value of the option from hashing but still hash the existence of the // option. if (util::starts_with(args[i], "-fdebug-prefix-map=")) { hash.hash_delimiter("arg"); hash.hash("-fdebug-prefix-map="); return {}; } if (util::starts_with(args[i], "-ffile-prefix-map=")) { hash.hash_delimiter("arg"); hash.hash("-ffile-prefix-map="); return {}; } if (util::starts_with(args[i], "-fmacro-prefix-map=")) { hash.hash_delimiter("arg"); hash.hash("-fmacro-prefix-map="); return {}; } if (util::starts_with(args[i], "-frandom-seed=") && ctx.config.sloppiness().contains(core::Sloppy::random_seed)) { LOG("Ignoring {} since random_seed sloppiness is requested", args[i]); return {}; } // When using the preprocessor, some arguments don't contribute to the hash. // The theory is that these arguments will change the output of -E if they are // going to have any effect at all. For precompiled headers this might not be // the case. if (!direct_mode && !ctx.args_info.output_is_precompiled_header && !ctx.args_info.using_precompiled_header) { if (compopt_affects_cpp_output(args[i])) { if (compopt_takes_arg(args[i])) { i++; } return {}; } if (compopt_affects_cpp_output(args[i].substr(0, 2))) { return {}; } } if (ctx.args_info.generating_dependencies) { std::optional option; std::optional value; if (util::starts_with(args[i], "-Wp,")) { // Skip the dependency filename since it doesn't impact the output. if (util::starts_with(args[i], "-Wp,-MD,") && args[i].find(',', 8) == std::string::npos) { hash.hash(args[i].data(), 8); return {}; } else if (util::starts_with(args[i], "-Wp,-MMD,") && args[i].find(',', 9) == std::string::npos) { hash.hash(args[i].data(), 9); return {}; } } else if (std::tie(option, value) = get_option_and_value("-MF", args, i); option) { // Skip the dependency filename since it doesn't impact the output. hash.hash(*option); return {}; } else if (std::tie(option, value) = get_option_and_value("-MQ", args, i); option) { hash.hash(*option); // No need to hash the dependency target since we always calculate it on // a cache hit. return {}; } else if (std::tie(option, value) = get_option_and_value("-MT", args, i); option) { hash.hash(*option); // No need to hash the dependency target since we always calculate it on // a cache hit. return {}; } } if (util::starts_with(args[i], "-specs=") || util::starts_with(args[i], "--specs=") || (args[i] == "-specs" || args[i] == "--specs") || args[i] == "--config") { std::string path; size_t eq_pos = args[i].find('='); if (eq_pos == std::string::npos) { if (i + 1 >= args.size()) { LOG("missing argument for \"{}\"", args[i]); return nonstd::make_unexpected(Statistic::bad_compiler_arguments); } path = args[i + 1]; i++; } else { path = args[i].substr(eq_pos + 1); } auto st = Stat::stat(path, Stat::OnError::log); if (st) { // If given an explicit specs file, then hash that file, but don't // include the path to it in the hash. hash.hash_delimiter("specs"); TRY(hash_compiler(ctx, hash, st, path, false)); return {}; } } if (util::starts_with(args[i], "-fplugin=")) { auto st = Stat::stat(&args[i][9], Stat::OnError::log); if (st) { hash.hash_delimiter("plugin"); TRY(hash_compiler(ctx, hash, st, &args[i][9], false)); return {}; } } if (args[i] == "-Xclang" && i + 3 < args.size() && args[i + 1] == "-load" && args[i + 2] == "-Xclang") { auto st = Stat::stat(args[i + 3], Stat::OnError::log); if (st) { hash.hash_delimiter("plugin"); TRY(hash_compiler(ctx, hash, st, args[i + 3], false)); i += 3; return {}; } } if ((args[i] == "-ccbin" || args[i] == "--compiler-bindir") && i + 1 < args.size()) { auto st = Stat::stat(args[i + 1]); if (st) { found_ccbin = true; hash.hash_delimiter("ccbin"); TRY(hash_nvcc_host_compiler(ctx, hash, &st, args[i + 1])); i++; return {}; } } // All other arguments are included in the hash. hash.hash_delimiter("arg"); hash.hash(args[i]); if (i + 1 < args.size() && compopt_takes_arg(args[i])) { i++; hash.hash_delimiter("arg"); hash.hash(args[i]); } return {}; } static nonstd::expected, Failure> get_manifest_key(Context& ctx, Hash& hash) { // Hash environment variables that affect the preprocessor output. const char* envvars[] = {"CPATH", "C_INCLUDE_PATH", "CPLUS_INCLUDE_PATH", "OBJC_INCLUDE_PATH", "OBJCPLUS_INCLUDE_PATH", // clang nullptr}; for (const char** p = envvars; *p; ++p) { const char* v = getenv(*p); if (v) { hash.hash_delimiter(*p); hash.hash(v); } } // Make sure that the direct mode hash is unique for the input file path. If // this would not be the case: // // * A false cache hit may be produced. Scenario: // - a/r.h exists. // - a/x.c has #include "r.h". // - b/x.c is identical to a/x.c. // - Compiling a/x.c records a/r.h in the manifest. // - Compiling b/x.c results in a false cache hit since a/x.c and b/x.c // share manifests and a/r.h exists. // * The expansion of __FILE__ may be incorrect. hash.hash_delimiter("inputfile"); hash.hash(ctx.args_info.input_file); hash.hash_delimiter("sourcecode hash"); Digest input_file_digest; auto ret = hash_source_code_file(ctx, input_file_digest, ctx.args_info.input_file); if (ret.contains(HashSourceCode::error)) { return nonstd::make_unexpected(Statistic::internal_error); } if (ret.contains(HashSourceCode::found_time)) { LOG_RAW("Disabling direct mode"); ctx.config.set_direct_mode(false); return {}; } hash.hash(input_file_digest.to_string()); return hash.digest(); } static bool hash_profile_data_file(const Context& ctx, Hash& hash) { const std::string& profile_path = ctx.args_info.profile_path; std::string_view base_name = Util::remove_extension(ctx.args_info.output_obj); std::string hashified_cwd = ctx.apparent_cwd; std::replace(hashified_cwd.begin(), hashified_cwd.end(), '/', '#'); std::vector paths_to_try{ // -fprofile-use[=dir]/-fbranch-probabilities (GCC <9) FMT("{}/{}.gcda", profile_path, base_name), // -fprofile-use[=dir]/-fbranch-probabilities (GCC >=9) FMT("{}/{}#{}.gcda", profile_path, hashified_cwd, base_name), // -fprofile(-instr|-sample)-use=file (Clang), -fauto-profile=file (GCC >=5) profile_path, // -fprofile(-instr|-sample)-use=dir (Clang) FMT("{}/default.profdata", profile_path), // -fauto-profile (GCC >=5) "fbdata.afdo", // -fprofile-dir is not used }; bool found = false; for (const std::string& p : paths_to_try) { LOG("Checking for profile data file {}", p); auto st = Stat::stat(p); if (st && !st.is_directory()) { LOG("Adding profile data {} to the hash", p); hash.hash_delimiter("-fprofile-use"); if (hash_binary_file(ctx, hash, p)) { found = true; } } } return found; } static nonstd::expected hash_profiling_related_data(const Context& ctx, Hash& hash) { // For profile generation (-fprofile(-instr)-generate[=path]) // - hash profile path // // For profile usage (-fprofile(-instr|-sample)-use, -fbranch-probabilities): // - hash profile data // // -fbranch-probabilities and -fvpt usage is covered by // -fprofile-generate/-fprofile-use. // // The profile directory can be specified as an argument to // -fprofile(-instr)-generate=, -fprofile(-instr|-sample)-use= or // -fprofile-dir=. if (ctx.args_info.profile_generate) { ASSERT(!ctx.args_info.profile_path.empty()); // For a relative profile directory D the compiler stores $PWD/D as part of // the profile filename so we need to include the same information in the // hash. const std::string profile_path = util::is_absolute_path(ctx.args_info.profile_path) ? ctx.args_info.profile_path : FMT("{}/{}", ctx.apparent_cwd, ctx.args_info.profile_path); LOG("Adding profile directory {} to our hash", profile_path); hash.hash_delimiter("-fprofile-dir"); hash.hash(profile_path); } if (ctx.args_info.profile_use && !hash_profile_data_file(ctx, hash)) { LOG_RAW("No profile data file found"); return nonstd::make_unexpected(Statistic::no_input_file); } return {}; } static std::optional get_result_key_from_manifest(Context& ctx, const Digest& manifest_key) { MTR_BEGIN("manifest", "manifest_get"); std::optional result_key; size_t read_manifests = 0; ctx.storage.get( manifest_key, core::CacheEntryType::manifest, [&](util::Bytes&& value) { try { read_manifest(ctx, value); ++read_manifests; result_key = ctx.manifest.look_up_result_digest(ctx); } catch (const core::Error& e) { LOG("Failed to look up result key in manifest: {}", e.what()); } if (result_key) { LOG_RAW("Got result key from manifest"); return true; } else { LOG_RAW("Did not find result key in manifest"); return false; } }); MTR_END("manifest", "manifest_get"); if (read_manifests > 1 && !ctx.config.remote_only()) { MTR_SCOPE("manifest", "merge"); LOG("Storing merged manifest {} locally", manifest_key.to_string()); core::CacheEntry::Header header(ctx.config, core::CacheEntryType::manifest); ctx.storage.local.put(manifest_key, core::CacheEntryType::manifest, core::CacheEntry::serialize(header, ctx.manifest)); } return result_key; } // Update a hash sum with information specific to the direct and preprocessor // modes and calculate the result key. Returns the result key on success, and // if direct_mode is true also the manifest key. static nonstd::expected, std::optional>, Failure> calculate_result_and_manifest_key(Context& ctx, const Args& args, Args& preprocessor_args, Hash& hash, bool direct_mode) { bool found_ccbin = false; hash.hash_delimiter("cache entry version"); hash.hash(core::CacheEntry::k_format_version); hash.hash_delimiter("result version"); hash.hash(core::Result::k_format_version); if (direct_mode) { hash.hash_delimiter("manifest version"); hash.hash(core::Manifest::k_format_version); } // clang will emit warnings for unused linker flags, so we shouldn't skip // those arguments. int is_clang = ctx.config.is_compiler_group_clang() || ctx.config.compiler_type() == CompilerType::other; // First the arguments. for (size_t i = 1; i < args.size(); i++) { TRY(hash_argument(ctx, args, i, hash, is_clang, direct_mode, found_ccbin)); } // Make results with dependency file /dev/null different from those without // it. if (ctx.args_info.generating_dependencies && ctx.args_info.output_dep == "/dev/null") { hash.hash_delimiter("/dev/null dependency file"); } if (!found_ccbin && ctx.args_info.actual_language == "cu") { TRY(hash_nvcc_host_compiler(ctx, hash)); } TRY(hash_profiling_related_data(ctx, hash)); // Adding -arch to hash since cpp output is affected. for (const auto& arch : ctx.args_info.arch_args) { hash.hash_delimiter("-arch"); hash.hash(arch); } std::optional result_key; std::optional manifest_key; if (direct_mode) { const auto manifest_key_result = get_manifest_key(ctx, hash); if (!manifest_key_result) { return nonstd::make_unexpected(manifest_key_result.error()); } manifest_key = *manifest_key_result; if (manifest_key) { LOG("Manifest key: {}", manifest_key->to_string()); result_key = get_result_key_from_manifest(ctx, *manifest_key); } } else if (ctx.args_info.arch_args.empty()) { const auto digest = get_result_key_from_cpp(ctx, preprocessor_args, hash); if (!digest) { return nonstd::make_unexpected(digest.error()); } result_key = *digest; LOG_RAW("Got result key from preprocessor"); } else { preprocessor_args.push_back("-arch"); for (size_t i = 0; i < ctx.args_info.arch_args.size(); ++i) { preprocessor_args.push_back(ctx.args_info.arch_args[i]); const auto digest = get_result_key_from_cpp(ctx, preprocessor_args, hash); if (!digest) { return nonstd::make_unexpected(digest.error()); } result_key = *digest; LOG("Got result key from preprocessor with -arch {}", ctx.args_info.arch_args[i]); if (i != ctx.args_info.arch_args.size() - 1) { result_key = std::nullopt; } preprocessor_args.pop_back(); } preprocessor_args.pop_back(); } if (result_key) { LOG("Result key: {}", result_key->to_string()); } return std::make_pair(result_key, manifest_key); } enum class FromCacheCallMode { direct, cpp }; // Try to return the compile result from cache. static nonstd::expected from_cache(Context& ctx, FromCacheCallMode mode, const Digest& result_key) { // The user might be disabling cache hits. if (ctx.config.recache()) { return false; } // If we're using Clang, we can't trust a precompiled header object based on // running the preprocessor since clang will produce a fatal error when the // precompiled header is used and one of the included files has an updated // timestamp: // // file 'foo.h' has been modified since the precompiled header 'foo.pch' // was built if ((ctx.config.is_compiler_group_clang() || ctx.config.compiler_type() == CompilerType::other) && ctx.args_info.output_is_precompiled_header && mode == FromCacheCallMode::cpp) { LOG_RAW("Not considering cached precompiled header in preprocessor mode"); return false; } MTR_SCOPE("cache", "from_cache"); // Get result from cache. util::Bytes cache_entry_data; ctx.storage.get( result_key, core::CacheEntryType::result, [&](util::Bytes&& value) { cache_entry_data = std::move(value); return true; }); if (cache_entry_data.empty()) { return false; } try { core::CacheEntry cache_entry(cache_entry_data); cache_entry.verify_checksum(); core::Result::Deserializer deserializer(cache_entry.payload()); core::ResultRetriever result_retriever(ctx, result_key); UmaskScope umask_scope(ctx.original_umask); deserializer.visit(result_retriever); } catch (core::ResultRetriever::WriteError& e) { LOG("Write error when retrieving result from {}: {}", result_key.to_string(), e.what()); return nonstd::make_unexpected(Statistic::bad_output_file); } catch (core::Error& e) { LOG("Failed to get result from {}: {}", result_key.to_string(), e.what()); return false; } LOG_RAW("Succeeded getting cached result"); return true; } // 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 ourselves. void find_compiler(Context& ctx, const FindExecutableFunction& find_executable_function, bool masquerading_as_compiler) { // 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. : (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) ? compiler : find_executable_function(ctx, compiler, ctx.orig_args[0]); if (resolved_compiler.empty()) { throw core::Fatal(FMT("Could not find compiler \"{}\" in PATH", compiler)); } if (Util::is_ccache_executable(resolved_compiler)) { throw core::Fatal("Recursive invocation of ccache"); } ctx.orig_args[0] = resolved_compiler; } static void initialize(Context& ctx, const char* const* argv, bool masquerading_as_compiler) { LOG("=== CCACHE {} STARTED =========================================", CCACHE_VERSION); LOG("Configuration file: {}", ctx.config.config_path()); LOG("System configuration file: {}", ctx.config.system_config_path()); if (getenv("CCACHE_INTERNAL_TRACE")) { #ifdef MTR_ENABLED ctx.mini_trace = std::make_unique(ctx.args_info); #else 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 // send networking errors to it. static nonstd::expected set_up_uncached_err() { int uncached_fd = dup(STDERR_FILENO); // The file descriptor is intentionally leaked. if (uncached_fd == -1) { LOG("dup(2) failed: {}", strerror(errno)); return nonstd::make_unexpected(Statistic::internal_error); } Util::setenv("UNCACHED_ERR_FD", FMT("{}", uncached_fd)); return {}; } static int cache_compilation(int argc, const char* const* argv); static nonstd::expected do_cache_compilation(Context& ctx); static void log_result_to_debug_log(Context& ctx) { if (ctx.config.log_file().empty() && !ctx.config.debug()) { return; } core::Statistics statistics(ctx.storage.local.get_statistics_updates()); for (const auto& message : statistics.get_statistics_ids()) { LOG("Result: {}", message); } } static void log_result_to_stats_log(Context& ctx) { if (ctx.config.stats_log().empty()) { return; } core::Statistics statistics(ctx.storage.local.get_statistics_updates()); const auto ids = statistics.get_statistics_ids(); if (ids.empty()) { return; } core::StatsLog(ctx.config.stats_log()) .log_result(ctx.args_info.input_file, ids); } static void finalize_at_exit(Context& ctx) { try { if (ctx.config.disable()) { // Just log result, don't update statistics. LOG_RAW("Result: disabled"); return; } log_result_to_debug_log(ctx); log_result_to_stats_log(ctx); ctx.storage.finalize(); } catch (const core::ErrorBase& e) { // finalize_at_exit must not throw since it's called by a destructor. LOG("Error while finalizing stats: {}", e.what()); } // Dump log buffer last to not lose any logs. if (ctx.config.debug() && !ctx.args_info.output_obj.empty()) { Logging::dump_log(prepare_debug_path(ctx.config.debug_dir(), ctx.time_of_invocation, ctx.args_info.output_obj, "log")); } } 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) { tzset(); // Needed for localtime_r. bool fall_back_to_original_compiler = false; Args saved_orig_args; std::optional 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(std::move(argv_parts.compiler_and_args), argv_parts.config_settings); SignalHandler signal_handler(ctx); Finalizer finalizer([&ctx] { finalize_at_exit(ctx); }); initialize(ctx, argv, argv_parts.masquerading_as_compiler); 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(); if (counters.get(Statistic::cache_miss) > 0) { if (!ctx.config.remote_only()) { ctx.storage.local.increment_statistic(Statistic::local_storage_miss); } if (ctx.storage.has_remote_storage()) { ctx.storage.local.increment_statistic(Statistic::remote_storage_miss); } } else if ((counters.get(Statistic::direct_cache_hit) > 0 || counters.get(Statistic::preprocessed_cache_hit) > 0) && counters.get(Statistic::remote_storage_hit) > 0 && !ctx.config.remote_only()) { ctx.storage.local.increment_statistic(Statistic::local_storage_miss); } if (!result) { if (result.error().exit_code()) { return *result.error().exit_code(); } // Else: Fall back to running the real compiler. fall_back_to_original_compiler = true; original_umask = ctx.original_umask; ASSERT(!ctx.orig_args.empty()); ctx.orig_args.erase_with_prefix("--ccache-"); add_prefix(ctx, ctx.orig_args, ctx.config.prefix_command()); LOG_RAW("Failed; falling back to running the real compiler"); saved_temp_dir = ctx.config.temporary_dir(); saved_orig_args = std::move(ctx.orig_args); auto execv_argv = saved_orig_args.to_argv(); LOG("Executing {}", Util::format_argv_for_logging(execv_argv.data())); // Execute the original command below after ctx and finalizer have been // destructed. } } if (fall_back_to_original_compiler) { if (original_umask) { Util::set_umask(*original_umask); } auto execv_argv = saved_orig_args.to_argv(); execute_noreturn(execv_argv.data(), saved_temp_dir); throw core::Fatal( FMT("execute_noreturn of {} failed: {}", execv_argv[0], strerror(errno))); } return EXIT_SUCCESS; } static nonstd::expected do_cache_compilation(Context& ctx) { if (ctx.config.disable()) { LOG_RAW("ccache is disabled"); return nonstd::make_unexpected(Statistic::none); } if (ctx.actual_cwd.empty()) { LOG("Unable to determine current working directory: {}", strerror(errno)); return nonstd::make_unexpected(Statistic::internal_error); } // Set CCACHE_DISABLE so no process ccache executes from now on will risk // calling ccache second time. For instance, if the real compiler is a wrapper // script that calls "ccache $compiler ..." we want that inner ccache call to // be disabled. Util::setenv("CCACHE_DISABLE", "1"); MTR_BEGIN("main", "process_args"); ProcessArgsResult processed = process_args(ctx); MTR_END("main", "process_args"); if (processed.error) { return nonstd::make_unexpected(*processed.error); } TRY(set_up_uncached_err()); // VS_UNICODE_OUTPUT prevents capturing stdout/stderr, as the output is sent // directly to Visual Studio. if (ctx.config.compiler_type() == CompilerType::msvc) { Util::unsetenv("VS_UNICODE_OUTPUT"); } for (const auto& name : {"DEPENDENCIES_OUTPUT", "SUNPRO_DEPENDENCIES"}) { if (getenv(name)) { LOG("Unsupported environment variable: {}", name); return Statistic::unsupported_environment_variable; } } if (ctx.config.is_compiler_group_msvc()) { for (const auto& name : {"CL", "_CL_"}) { if (getenv(name)) { LOG("Unsupported environment variable: {}", name); return Statistic::unsupported_environment_variable; } } } if (!ctx.config.run_second_cpp() && ctx.config.is_compiler_group_msvc()) { LOG_RAW("Second preprocessor cannot be disabled"); ctx.config.set_run_second_cpp(true); } if (ctx.config.depend_mode()) { const bool deps = ctx.args_info.generating_dependencies && ctx.args_info.output_dep != "/dev/null"; const bool includes = ctx.args_info.generating_includes; if (!ctx.config.run_second_cpp() || (!deps && !includes)) { LOG_RAW("Disabling depend mode"); ctx.config.set_depend_mode(false); } } if (ctx.storage.has_remote_storage()) { if (ctx.config.file_clone()) { LOG_RAW("Disabling file clone mode since remote storage is enabled"); ctx.config.set_file_clone(false); } if (ctx.config.hard_link()) { LOG_RAW("Disabling hard link mode since remote storage is enabled"); ctx.config.set_hard_link(false); } } LOG("Source file: {}", ctx.args_info.input_file); if (ctx.args_info.generating_dependencies) { LOG("Dependency file: {}", ctx.args_info.output_dep); } if (ctx.args_info.generating_coverage) { LOG_RAW("Coverage file is being generated"); } if (ctx.args_info.generating_stackusage) { LOG("Stack usage file: {}", ctx.args_info.output_su); } if (ctx.args_info.generating_diagnostics) { LOG("Diagnostics file: {}", ctx.args_info.output_dia); } if (!ctx.args_info.output_dwo.empty()) { LOG("Split dwarf file: {}", ctx.args_info.output_dwo); } LOG("Object file: {}", ctx.args_info.output_obj); MTR_META_THREAD_NAME(ctx.args_info.output_obj.c_str()); if (ctx.config.debug()) { const auto path = prepare_debug_path(ctx.config.debug_dir(), ctx.time_of_invocation, ctx.args_info.orig_output_obj, "input-text"); File debug_text_file(path, "w"); if (debug_text_file) { ctx.hash_debug_files.push_back(std::move(debug_text_file)); } else { LOG("Failed to open {}: {}", path, strerror(errno)); } } FILE* debug_text_file = !ctx.hash_debug_files.empty() ? ctx.hash_debug_files.front().get() : nullptr; Hash common_hash; init_hash_debug(ctx, common_hash, 'c', "COMMON", debug_text_file); if (should_disable_ccache_for_input_file(ctx.args_info.input_file)) { LOG("{} found in {}, disabling ccache", k_ccache_disable_token, ctx.args_info.input_file); return nonstd::make_unexpected(Statistic::disabled); } { MTR_SCOPE("hash", "common_hash"); TRY(hash_common_info( ctx, processed.preprocessor_args, common_hash, ctx.args_info)); } if (processed.hash_actual_cwd) { common_hash.hash_delimiter("actual_cwd"); common_hash.hash(ctx.actual_cwd); } // Try to find the hash using the manifest. Hash direct_hash = common_hash; init_hash_debug(ctx, direct_hash, 'd', "DIRECT MODE", debug_text_file); Args args_to_hash = processed.preprocessor_args; args_to_hash.push_back(processed.extra_args_to_hash); bool put_result_in_manifest = false; std::optional result_key; std::optional result_key_from_manifest; std::optional manifest_key; if (ctx.config.direct_mode()) { LOG_RAW("Trying direct lookup"); Args dummy_args; MTR_BEGIN("hash", "direct_hash"); const auto result_and_manifest_key = calculate_result_and_manifest_key( ctx, args_to_hash, dummy_args, direct_hash, true); MTR_END("hash", "direct_hash"); if (!result_and_manifest_key) { return nonstd::make_unexpected(result_and_manifest_key.error()); } std::tie(result_key, manifest_key) = *result_and_manifest_key; if (result_key) { // If we can return from cache at this point then do so. const auto from_cache_result = from_cache(ctx, FromCacheCallMode::direct, *result_key); if (!from_cache_result) { return nonstd::make_unexpected(from_cache_result.error()); } else if (*from_cache_result) { return Statistic::direct_cache_hit; } // Wasn't able to return from cache at this point. However, the result // was already found in manifest, so don't re-add it later. put_result_in_manifest = false; result_key_from_manifest = result_key; } else { // Add result to manifest later. put_result_in_manifest = true; } if (!ctx.config.recache()) { ctx.storage.local.increment_statistic(Statistic::direct_cache_miss); } } if (ctx.config.read_only_direct()) { LOG_RAW("Read-only direct mode; running real compiler"); return nonstd::make_unexpected(Statistic::cache_miss); } if (!ctx.config.depend_mode()) { // Find the hash using the preprocessed output. Also updates // ctx.included_files. Hash cpp_hash = common_hash; init_hash_debug(ctx, cpp_hash, 'p', "PREPROCESSOR MODE", debug_text_file); MTR_BEGIN("hash", "cpp_hash"); const auto result_and_manifest_key = calculate_result_and_manifest_key( ctx, args_to_hash, processed.preprocessor_args, cpp_hash, false); MTR_END("hash", "cpp_hash"); if (!result_and_manifest_key) { return nonstd::make_unexpected(result_and_manifest_key.error()); } result_key = result_and_manifest_key->first; // calculate_result_and_manifest_key always returns a non-nullopt result_key // if the last argument (direct_mode) is false. ASSERT(result_key); if (result_key_from_manifest && result_key_from_manifest != result_key) { // The hash from manifest differs from the hash of the preprocessor // output. This could be because: // // - The preprocessor produces different output for the same input (not // likely). // - There's a bug in ccache (maybe incorrect handling of compiler // arguments). // - The user has used a different CCACHE_BASEDIR (most likely). // // The best thing here would probably be to remove the hash entry from // the manifest. For now, we use a simpler method: just remove the // manifest file. LOG_RAW("Hash from manifest doesn't match preprocessor output"); LOG_RAW("Likely reason: different CCACHE_BASEDIRs used"); LOG_RAW("Removing manifest as a safety measure"); ctx.storage.remove(*result_key, core::CacheEntryType::result); put_result_in_manifest = true; } // If we can return from cache at this point then do. const auto from_cache_result = from_cache(ctx, FromCacheCallMode::cpp, *result_key); if (!from_cache_result) { return nonstd::make_unexpected(from_cache_result.error()); } else if (*from_cache_result) { if (ctx.config.direct_mode() && manifest_key && put_result_in_manifest) { MTR_SCOPE("cache", "update_manifest"); update_manifest(ctx, *manifest_key, *result_key); } return Statistic::preprocessed_cache_hit; } if (!ctx.config.recache()) { ctx.storage.local.increment_statistic(Statistic::preprocessed_cache_miss); } } if (ctx.config.read_only()) { LOG_RAW("Read-only mode; running real compiler"); return nonstd::make_unexpected(Statistic::cache_miss); } add_prefix(ctx, processed.compiler_args, ctx.config.prefix_command()); // In depend_mode, extend the direct hash. Hash* depend_mode_hash = ctx.config.depend_mode() ? &direct_hash : nullptr; // Run real compiler, sending output to cache. MTR_BEGIN("cache", "to_cache"); const auto digest = to_cache(ctx, processed.compiler_args, result_key, ctx.args_info.depend_extra_args, depend_mode_hash); MTR_END("cache", "to_cache"); if (!digest) { return nonstd::make_unexpected(digest.error()); } result_key = *digest; if (ctx.config.direct_mode()) { ASSERT(manifest_key); MTR_SCOPE("cache", "update_manifest"); update_manifest(ctx, *manifest_key, *result_key); } return ctx.config.recache() ? Statistic::recache : Statistic::cache_miss; } int ccache_main(int argc, const char* const* argv) { try { if (Util::is_ccache_executable(argv[0])) { if (argc < 2) { 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 // passed a compiler name and options. if (argv[1][0] == '-') { return core::process_main_options(argc, argv); } } return cache_compilation(argc, argv); } catch (const core::ErrorBase& e) { PRINT(stderr, "ccache: error: {}\n", e.what()); return EXIT_FAILURE; } }