mirror of
https://github.com/encounter/buildcache.git
synced 2026-07-09 18:18:50 -07:00
332 lines
12 KiB
C++
332 lines
12 KiB
C++
//--------------------------------------------------------------------------------------------------
|
|
// Copyright (c) 2018 Marcus Geelnard
|
|
//
|
|
// This software is provided 'as-is', without any express or implied warranty. In no event will the
|
|
// authors be held liable for any damages arising from the use of this software.
|
|
//
|
|
// Permission is granted to anyone to use this software for any purpose, including commercial
|
|
// applications, and to alter it and redistribute it freely, subject to the following restrictions:
|
|
//
|
|
// 1. The origin of this software must not be misrepresented; you must not claim that you wrote
|
|
// the original software. If you use this software in a product, an acknowledgment in the
|
|
// product documentation would be appreciated but is not required.
|
|
//
|
|
// 2. Altered source versions must be plainly marked as such, and must not be misrepresented as
|
|
// being the original software.
|
|
//
|
|
// 3. This notice may not be removed or altered from any source distribution.
|
|
//--------------------------------------------------------------------------------------------------
|
|
|
|
#include <base/debug_utils.hpp>
|
|
#include <base/string_list.hpp>
|
|
#include <base/unicode_utils.hpp>
|
|
#include <cache/local_cache.hpp>
|
|
#include <config/configuration.hpp>
|
|
#include <sys/perf_utils.hpp>
|
|
#include <sys/sys_utils.hpp>
|
|
#include <wrappers/gcc_wrapper.hpp>
|
|
#include <wrappers/ghs_wrapper.hpp>
|
|
#include <wrappers/lua_wrapper.hpp>
|
|
#include <wrappers/msvc_wrapper.hpp>
|
|
#include <wrappers/program_wrapper.hpp>
|
|
|
|
#ifdef ENABLE_REDIS
|
|
#include <hiredis/hiredis.h>
|
|
#endif
|
|
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
|
|
#include "buildcache_version.h"
|
|
|
|
// For program binary identification (e.g. using the "strings", "ident" or "what" tools).
|
|
char PROGRAM_IDENTITY_1[] = "@(#)BuildCache version " BUILDCACHE_VERSION_STRING;
|
|
char PROGRAM_IDENTITY_2[] = "$BuildCacheVersion: " BUILDCACHE_VERSION_STRING " $";
|
|
|
|
namespace {
|
|
// The name of the BuildCache executable (excluding the file extension).
|
|
const std::string BUILDCACHE_EXE_NAME = "buildcache";
|
|
|
|
bool is_lua_script(const std::string& script_path) {
|
|
return (bcache::lower_case(bcache::file::get_extension(script_path)) == ".lua");
|
|
}
|
|
|
|
std::unique_ptr<bcache::program_wrapper_t> find_suitable_wrapper(
|
|
const bcache::string_list_t& args) {
|
|
const auto& true_exe_path = args[0];
|
|
|
|
std::unique_ptr<bcache::program_wrapper_t> wrapper;
|
|
|
|
// Try Lua wrappers first (so you can override internal wrappers).
|
|
// Iterate over the existing Lua paths.
|
|
for (const auto& lua_root_dir : bcache::config::lua_paths()) {
|
|
if (bcache::file::dir_exists(lua_root_dir)) {
|
|
// Find all .lua files in the given directory.
|
|
const auto lua_files = bcache::file::walk_directory(lua_root_dir);
|
|
for (const auto& file_info : lua_files) {
|
|
const auto script_path = file_info.path();
|
|
if ((!file_info.is_dir()) && is_lua_script(script_path)) {
|
|
// Check if the given wrapper can handle this command (first match wins).
|
|
wrapper.reset(new bcache::lua_wrapper_t(args, script_path));
|
|
if (wrapper->can_handle_command()) {
|
|
bcache::debug::log(bcache::debug::DEBUG) << "Found matching Lua wrapper for "
|
|
<< true_exe_path << ": " << script_path;
|
|
break;
|
|
} else {
|
|
wrapper = nullptr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (wrapper) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If no Lua wrappers were found, try built in wrappers.
|
|
if (!wrapper) {
|
|
wrapper.reset(new bcache::gcc_wrapper_t(args));
|
|
if (!wrapper->can_handle_command()) {
|
|
wrapper.reset(new bcache::ghs_wrapper_t(args));
|
|
if (!wrapper->can_handle_command()) {
|
|
wrapper.reset(new bcache::msvc_wrapper_t(args));
|
|
if (!wrapper->can_handle_command()) {
|
|
wrapper = nullptr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return wrapper;
|
|
}
|
|
|
|
[[noreturn]] void clear_cache_and_exit() {
|
|
int return_code = 0;
|
|
try {
|
|
bcache::local_cache_t cache;
|
|
cache.clear();
|
|
} catch (const std::exception& e) {
|
|
std::cerr << "*** Unexpected error: " << e.what() << "\n";
|
|
return_code = 1;
|
|
} catch (...) {
|
|
std::cerr << "*** Unexpected error.\n";
|
|
return_code = 1;
|
|
}
|
|
std::exit(return_code);
|
|
}
|
|
|
|
[[noreturn]] void show_stats_and_exit() {
|
|
int return_code = 0;
|
|
try {
|
|
bcache::local_cache_t cache;
|
|
|
|
// Print the cache stats.
|
|
std::cout << "Cache status:\n";
|
|
cache.show_stats();
|
|
|
|
{
|
|
// Print the configuration.
|
|
#ifdef _WIN32
|
|
const std::string PATH_SEP = ";";
|
|
#else
|
|
const std::string PATH_SEP = ":";
|
|
#endif
|
|
|
|
std::cout << "\nConfiguration:\n";
|
|
std::cout << " Configuration file: " << bcache::config::config_file() << "\n\n";
|
|
|
|
std::cout << " BUILDCACHE_DIR: " << bcache::config::dir() << "\n";
|
|
std::cout << " BUILDCACHE_LUA_PATH: "
|
|
<< bcache::config::lua_paths().join(PATH_SEP, false) << "\n";
|
|
std::cout << " BUILDCACHE_PREFIX: " << bcache::config::prefix() << "\n";
|
|
std::cout << " BUILDCACHE_REMOTE: "
|
|
<< (bcache::config::remote().empty() ? "(disabled)" : bcache::config::remote())
|
|
<< "\n";
|
|
std::cout << " BUILDCACHE_MAX_CACHE_SIZE: " << bcache::config::max_cache_size()
|
|
<< " (" << bcache::file::human_readable_size(bcache::config::max_cache_size())
|
|
<< ")\n";
|
|
std::cout << " BUILDCACHE_MAX_LOCAL_ENTRY_SIZE: " << bcache::config::max_local_entry_size()
|
|
<< " (" << bcache::file::human_readable_size(bcache::config::max_local_entry_size())
|
|
<< ")\n";
|
|
std::cout << " BUILDCACHE_MAX_REMOTE_ENTRY_SIZE: "
|
|
<< bcache::config::max_remote_entry_size() << " ("
|
|
<< bcache::file::human_readable_size(bcache::config::max_remote_entry_size())
|
|
<< ")\n";
|
|
std::cout << " BUILDCACHE_DEBUG: " << bcache::config::debug() << "\n";
|
|
std::cout << " BUILDCACHE_HARD_LINKS: "
|
|
<< (bcache::config::hard_links() ? "true" : "false") << "\n";
|
|
std::cout << " BUILDCACHE_COMPRESS: "
|
|
<< (bcache::config::compress() ? "true" : "false") << "\n";
|
|
std::cout << " BUILDCACHE_PERF: "
|
|
<< (bcache::config::perf() ? "true" : "false") << "\n";
|
|
std::cout << " BUILDCACHE_DISABLE: "
|
|
<< (bcache::config::disable() ? "true" : "false") << "\n";
|
|
}
|
|
} catch (const std::exception& e) {
|
|
std::cerr << "*** Unexpected error: " << e.what() << "\n";
|
|
return_code = 1;
|
|
} catch (...) {
|
|
std::cerr << "*** Unexpected error.\n";
|
|
return_code = 1;
|
|
}
|
|
std::exit(return_code);
|
|
}
|
|
|
|
[[noreturn]] void print_version_and_exit() {
|
|
std::cout << "BuildCache version " BUILDCACHE_VERSION_STRING "\n";
|
|
#ifdef ENABLE_REDIS
|
|
std::cout << "Redis support via hiredis " << HIREDIS_MAJOR << "." << HIREDIS_MINOR << "."
|
|
<< HIREDIS_PATCH << "\n";
|
|
#endif // ENABLE_REDIS
|
|
std::exit(0);
|
|
}
|
|
|
|
[[noreturn]] void wrap_compiler_and_exit(int argc, const char** argv) {
|
|
auto args = bcache::string_list_t(argc, argv);
|
|
bool was_wrapped = false;
|
|
int return_code = 0;
|
|
|
|
try {
|
|
if (args.size() < 1) {
|
|
// Should never happen.
|
|
throw std::runtime_error("Missing arguments.");
|
|
}
|
|
|
|
// Is the caching mechanism disabled?
|
|
if (bcache::config::disable()) {
|
|
// Bypass all the cache logic and call the intended command directly.
|
|
auto result = bcache::sys::run(args, false);
|
|
return_code = result.return_code;
|
|
} else {
|
|
// Find the true path to the executable file. This affects things like if we can match the
|
|
// compiler name or not, and what version string we get. We also want to avoid incorrectly
|
|
// identifying other compiler accelerators (e.g. ccache) as actual compilers.
|
|
// TODO(m): This call may throw an exception, which currently means that we will not even try
|
|
// to run the original command. At the same time this is a protection against endless symlink
|
|
// recursion. Figure something out!
|
|
PERF_START(FIND_EXECUTABLE);
|
|
const auto true_exe_path = bcache::file::find_executable(args[0], BUILDCACHE_EXE_NAME);
|
|
PERF_STOP(FIND_EXECUTABLE);
|
|
|
|
// Replace the command with the true exe path. Most of the following operations rely on having
|
|
// a correct executable path. Also, this is important to avoid recursions when we are invoked
|
|
// from a symlink, for instance.
|
|
args[0] = true_exe_path;
|
|
|
|
try {
|
|
return_code = 1;
|
|
|
|
// Select a matching compiler wrapper.
|
|
PERF_START(FIND_WRAPPER);
|
|
auto wrapper = find_suitable_wrapper(args);
|
|
PERF_STOP(FIND_WRAPPER);
|
|
|
|
// Run the wrapper, if any.
|
|
if (wrapper) {
|
|
was_wrapped = wrapper->handle_command(return_code);
|
|
} else {
|
|
bcache::debug::log(bcache::debug::INFO) << "No suitable wrapper for " << true_exe_path;
|
|
}
|
|
} catch (const std::exception& e) {
|
|
bcache::debug::log(bcache::debug::ERROR) << "Unexpected error: " << e.what();
|
|
return_code = 1;
|
|
} catch (...) {
|
|
bcache::debug::log(bcache::debug::ERROR) << "Unexpected error.";
|
|
return_code = 1;
|
|
}
|
|
|
|
// Fall back to running the command as is.
|
|
if (!was_wrapped) {
|
|
PERF_START(RUN_FOR_FALLBACK);
|
|
auto result = bcache::sys::run_with_prefix(args, false);
|
|
PERF_STOP(RUN_FOR_FALLBACK);
|
|
return_code = result.return_code;
|
|
}
|
|
}
|
|
} catch (const std::exception& e) {
|
|
bcache::debug::log(bcache::debug::FATAL) << "Unexpected error: " << e.what();
|
|
return_code = 1;
|
|
} catch (...) {
|
|
bcache::debug::log(bcache::debug::FATAL) << "Unexpected error.";
|
|
return_code = 1;
|
|
}
|
|
|
|
// Report performance timings.
|
|
if (!bcache::config::disable()) {
|
|
bcache::perf::report();
|
|
}
|
|
|
|
std::exit(return_code);
|
|
}
|
|
|
|
bool compare_arg(const std::string& arg,
|
|
const std::string& short_form,
|
|
const std::string& long_form = "") {
|
|
return (arg == short_form) || (arg == long_form);
|
|
}
|
|
|
|
void print_help(const char* program_name) {
|
|
std::cout << "Usage:\n";
|
|
std::cout << " " << program_name << " [options]\n";
|
|
std::cout << " " << program_name << " compiler [compiler-options]\n";
|
|
std::cout << "\n";
|
|
std::cout << "Options:\n";
|
|
std::cout << " -C, --clear clear the local cache (except configuration)\n";
|
|
std::cout << " -s, --show-stats show statistics summary and configuration\n";
|
|
std::cout << "\n";
|
|
std::cout << " -h, --help print this help text\n";
|
|
std::cout << " -V, --version print version and copyright information\n";
|
|
std::cout << "\n";
|
|
std::cout << "See also https://github.com/mbitsnbites/buildcache\n";
|
|
|
|
}
|
|
} // namespace
|
|
|
|
int main(int argc, const char** argv) {
|
|
try {
|
|
// Initialize the configuration.
|
|
bcache::config::init();
|
|
|
|
// Set the debug log level to what is given by the configuration.
|
|
bcache::debug::set_log_level(bcache::config::debug());
|
|
} catch (const std::exception& e) {
|
|
bcache::debug::log(bcache::debug::FATAL) << e.what();
|
|
} catch (...) {
|
|
bcache::debug::log(bcache::debug::FATAL) << "An exception occurred.";
|
|
}
|
|
|
|
// Handle symlink invokation.
|
|
if (bcache::lower_case(bcache::file::get_file_part(std::string(argv[0]), false)) !=
|
|
BUILDCACHE_EXE_NAME) {
|
|
bcache::debug::log(bcache::debug::DEBUG) << "Invoked as symlink: " << argv[0];
|
|
wrap_compiler_and_exit(argc, &argv[0]);
|
|
}
|
|
|
|
if (argc < 2) {
|
|
print_help(argv[0]);
|
|
std::exit(1);
|
|
}
|
|
|
|
// Check if we are running any BuildCache commands.
|
|
const std::string arg_str(argv[1]);
|
|
if (compare_arg(arg_str, "-C", "--clear")) {
|
|
clear_cache_and_exit();
|
|
} else if (compare_arg(arg_str, "-s", "--show-stats")) {
|
|
show_stats_and_exit();
|
|
} else if (compare_arg(arg_str, "-V", "--version")) {
|
|
print_version_and_exit();
|
|
} else if (compare_arg(arg_str, "-h", "--help")) {
|
|
print_help(argv[0]);
|
|
std::exit(0);
|
|
} else if (arg_str[0] == '-') {
|
|
std::cerr << argv[0] << ": invalid option -- " << arg_str << "\n";
|
|
print_help(argv[0]);
|
|
std::exit(1);
|
|
}
|
|
|
|
// We got this far, so we're running as a compiler wrapper.
|
|
wrap_compiler_and_exit(argc - 1, &argv[1]);
|
|
}
|