|
|
|
|
#include "compile_commands.hpp"
|
|
|
|
|
#include "clangmm.hpp"
|
|
|
|
|
#include "config.hpp"
|
|
|
|
|
#include "filesystem.hpp"
|
|
|
|
|
#include "json.hpp"
|
|
|
|
|
#include "utility.hpp"
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <regex>
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> CompileCommands::Command::get_argument_values(const std::string &argument_name) const {
|
|
|
|
|
std::vector<std::string> argument_values;
|
|
|
|
|
|
|
|
|
|
bool found_argument = false;
|
|
|
|
|
for(auto &argument : arguments) {
|
|
|
|
|
if(found_argument) {
|
|
|
|
|
argument_values.emplace_back(argument);
|
|
|
|
|
found_argument = false;
|
|
|
|
|
}
|
|
|
|
|
else if(argument == argument_name)
|
|
|
|
|
found_argument = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return argument_values;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CompileCommands::CompileCommands(const boost::filesystem::path &build_path) {
|
|
|
|
|
clangmm::CompilationDatabase db(build_path.string());
|
|
|
|
|
if(db) {
|
|
|
|
|
clangmm::CompileCommands compile_commands({}, db);
|
|
|
|
|
for(auto &command : compile_commands.get_commands())
|
|
|
|
|
commands.emplace_back(Command{clangmm::to_string(clang_CompileCommand_getDirectory(command.cx_command)),
|
|
|
|
|
command.get_arguments(),
|
|
|
|
|
filesystem::get_absolute_path(clangmm::to_string(clang_CompileCommand_getFilename(command.cx_command)), build_path)});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> CompileCommands::get_arguments(const boost::filesystem::path &build_path, const boost::filesystem::path &file_path) {
|
|
|
|
|
auto extension = file_path.extension().string();
|
|
|
|
|
bool is_header = CompileCommands::is_header(file_path) || extension.empty(); // Include std C++ headers that are without extensions
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> arguments;
|
|
|
|
|
|
|
|
|
|
if(!build_path.empty()) {
|
|
|
|
|
clangmm::CompilationDatabase db(build_path.string());
|
|
|
|
|
if(db) {
|
|
|
|
|
std::vector<std::vector<std::string>> compile_commands_arguments;
|
|
|
|
|
// If header file, use source file flags if they are in the same folder
|
|
|
|
|
if(is_header && !extension.empty()) {
|
|
|
|
|
auto parent_path = file_path.parent_path();
|
|
|
|
|
clangmm::CompileCommands compile_commands({}, db);
|
|
|
|
|
for(auto &command : compile_commands.get_commands()) {
|
|
|
|
|
boost::filesystem::path file = filesystem::get_absolute_path(clangmm::to_string(clang_CompileCommand_getFilename(command.cx_command)), build_path);
|
|
|
|
|
if(file.parent_path() == parent_path)
|
|
|
|
|
compile_commands_arguments.emplace_back(command.get_arguments());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(compile_commands_arguments.empty()) {
|
|
|
|
|
clangmm::CompileCommands compile_commands(file_path.string(), db);
|
|
|
|
|
for(auto &command : compile_commands.get_commands())
|
|
|
|
|
compile_commands_arguments.emplace_back(command.get_arguments());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for(auto &command_arguments : compile_commands_arguments) {
|
|
|
|
|
bool ignore_next = false;
|
|
|
|
|
for(size_t i = 1; i + 1 < command_arguments.size(); i++) { // Exclude first and last argument
|
|
|
|
|
if(ignore_next)
|
|
|
|
|
ignore_next = false;
|
|
|
|
|
else if(command_arguments[i] == "-o" ||
|
|
|
|
|
command_arguments[i] == "-x" || // Remove language arguments since some tools add languages not understood by clang
|
|
|
|
|
(is_header && command_arguments[i] == "-include-pch") || // Header files should not use precompiled headers
|
|
|
|
|
command_arguments[i] == "-MF") { // Exclude dependency file generation
|
|
|
|
|
ignore_next = true;
|
|
|
|
|
}
|
|
|
|
|
else if(command_arguments[i] == "-c") {
|
|
|
|
|
}
|
|
|
|
|
else if(command_arguments[i] == "--") // End of command options
|
|
|
|
|
break;
|
|
|
|
|
else
|
|
|
|
|
arguments.emplace_back(command_arguments[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static std::string resource_path = []() -> std::string {
|
|
|
|
|
auto find_resource_path = [](const boost::filesystem::path &base_path, std::string version) -> std::string {
|
|
|
|
|
while(!version.empty() && version.front() != '.' && version.back() != '.') {
|
|
|
|
|
auto path = base_path / "clang" / version;
|
|
|
|
|
boost::system::error_code ec;
|
|
|
|
|
if(boost::filesystem::is_directory(path / "include", ec))
|
|
|
|
|
return path.string();
|
|
|
|
|
auto pos = version.rfind('.');
|
|
|
|
|
if(pos == std::string::npos)
|
|
|
|
|
break;
|
|
|
|
|
version.erase(pos);
|
|
|
|
|
}
|
|
|
|
|
return "";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Try based on clang_getClangVersion first, even though its format is not guaranteed to be stable.
|
|
|
|
|
auto clang_version = clangmm::to_string(clang_getClangVersion());
|
|
|
|
|
std::regex clang_version_regex("([0-9]+\\.[0-9]+\\.[0-9]+)");
|
|
|
|
|
std::smatch sm;
|
|
|
|
|
if(std::regex_search(clang_version, sm, clang_version_regex)) {
|
|
|
|
|
auto version = sm[1].str();
|
|
|
|
|
auto path = find_resource_path(boost::filesystem::path(LIBCLANG_LIBRARY_DIR), version);
|
|
|
|
|
if(path.empty()) // For e.g. Fedora the LibClang is located in /usr/lib64/libclang.so while the resource dir is in /usr/lib/clang/<version>
|
|
|
|
|
path = find_resource_path(boost::filesystem::path(LIBCLANG_LIBRARY_DIR) / ".." / "lib", version);
|
|
|
|
|
if(!path.empty())
|
|
|
|
|
return path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
boost::system::error_code ec;
|
|
|
|
|
for(boost::filesystem::directory_iterator it(boost::filesystem::path(LIBCLANG_LIBRARY_DIR) / "clang", ec), end; it != end; ++it) {
|
|
|
|
|
if(boost::filesystem::is_directory(it->path() / "include", ec))
|
|
|
|
|
return it->path().string();
|
|
|
|
|
}
|
|
|
|
|
return {};
|
|
|
|
|
}();
|
|
|
|
|
if(!resource_path.empty()) {
|
|
|
|
|
arguments.emplace_back("-resource-dir");
|
|
|
|
|
arguments.emplace_back(resource_path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Do not add -fretain-comments-from-system-headers if pch is used, since the pch was most likely made without this flag
|
|
|
|
|
if(std::none_of(arguments.begin(), arguments.end(), [](const std::string &argument) { return argument == "-include-pch"; }))
|
|
|
|
|
arguments.emplace_back("-fretain-comments-from-system-headers");
|
|
|
|
|
|
|
|
|
|
if(is_header) {
|
|
|
|
|
arguments.emplace_back("-Wno-pragma-once-outside-header");
|
|
|
|
|
arguments.emplace_back("-Wno-pragma-system-header-outside-header");
|
|
|
|
|
arguments.emplace_back("-Wno-include-next-outside-header");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(extension == ".cu" || extension == ".cuh") {
|
|
|
|
|
arguments.emplace_back("-xcuda");
|
|
|
|
|
arguments.emplace_back("-D__CUDACC__");
|
|
|
|
|
arguments.emplace_back("-include");
|
|
|
|
|
arguments.emplace_back("cuda_runtime.h");
|
|
|
|
|
arguments.emplace_back("-ferror-limit=1000"); // CUDA headers redeclares some std functions
|
|
|
|
|
}
|
|
|
|
|
else if(extension == ".cl") {
|
|
|
|
|
arguments.emplace_back("-xcl");
|
|
|
|
|
arguments.emplace_back("-cl-std=CL2.0");
|
|
|
|
|
arguments.emplace_back("-Xclang");
|
|
|
|
|
arguments.emplace_back("-finclude-default-header");
|
|
|
|
|
arguments.emplace_back("-Wno-gcc-compat");
|
|
|
|
|
}
|
|
|
|
|
else if(is_header && extension != ".h") // libclang crashes if it tries to parse .h files as C++ file in a C project
|
|
|
|
|
arguments.emplace_back("-xc++");
|
|
|
|
|
|
|
|
|
|
if(!build_path.empty()) {
|
|
|
|
|
arguments.emplace_back("-working-directory");
|
|
|
|
|
arguments.emplace_back(build_path.string());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(Config::get().source.clang_tidy_enable) {
|
|
|
|
|
arguments.emplace_back("-Xclang");
|
|
|
|
|
arguments.emplace_back("-add-plugin");
|
|
|
|
|
arguments.emplace_back("-Xclang");
|
|
|
|
|
arguments.emplace_back("clang-tidy");
|
|
|
|
|
if(!Config::get().source.clang_tidy_checks.empty()) {
|
|
|
|
|
arguments.emplace_back("-Xclang");
|
|
|
|
|
arguments.emplace_back("-plugin-arg-clang-tidy");
|
|
|
|
|
arguments.emplace_back("-Xclang");
|
|
|
|
|
arguments.emplace_back("-checks=" + Config::get().source.clang_tidy_checks);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return arguments;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CompileCommands::is_header(const boost::filesystem::path &path) {
|
|
|
|
|
auto ext = path.extension();
|
|
|
|
|
if(ext == ".h" || // c headers
|
|
|
|
|
ext == ".hh" || ext == ".hp" || ext == ".hpp" || ext == ".h++" || ext == ".tcc" || // c++ headers
|
|
|
|
|
ext == ".cuh") // CUDA headers
|
|
|
|
|
return true;
|
|
|
|
|
else
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CompileCommands::is_source(const boost::filesystem::path &path) {
|
|
|
|
|
auto ext = path.extension();
|
|
|
|
|
if(ext == ".c" || // c sources
|
|
|
|
|
ext == ".cpp" || ext == ".cxx" || ext == ".cc" || ext == ".C" || ext == ".c++" || // c++ sources
|
|
|
|
|
ext == ".cu" || // CUDA sources
|
|
|
|
|
ext == ".cl") // OpenCL sources
|
|
|
|
|
return true;
|
|
|
|
|
else
|
|
|
|
|
return false;
|
|
|
|
|
}
|