Remove CompileCommandsEntry and reduce clangDriver invocations

This commit is contained in:
Fangrui Song 2018-08-17 22:20:51 -07:00
parent 84e18e51b2
commit 82962e565b

View File

@ -32,22 +32,6 @@ using namespace llvm;
#include <unordered_set> #include <unordered_set>
#include <vector> #include <vector>
struct CompileCommandsEntry {
std::string directory;
std::string file;
std::string command;
std::vector<std::string> args;
std::string ResolveIfRelative(std::string path) const {
if (sys::path::is_absolute(path))
return path;
SmallString<256> Ret;
sys::path::append(Ret, directory, path);
return NormalizePath(Ret.str());
}
};
MAKE_REFLECT_STRUCT(CompileCommandsEntry, directory, file, command, args);
namespace { namespace {
enum class ProjectMode { CompileCommandsJson, DotCcls, ExternalCommand }; enum class ProjectMode { CompileCommandsJson, DotCcls, ExternalCommand };
@ -67,108 +51,131 @@ enum OptionClass {
Separate, Separate,
}; };
Project::Entry std::string ResolveIfRelative(const std::string &directory,
GetCompilationEntryFromCompileCommandEntry(ProjectConfig *config, const std::string &path) {
const CompileCommandsEntry &entry) { if (sys::path::is_absolute(path))
Project::Entry result; return path;
result.filename = entry.file; SmallString<256> Ret;
const std::string base_name = sys::path::filename(entry.file); sys::path::append(Ret, directory, path);
return NormalizePath(Ret.str());
}
// Expand %c %cpp %clang struct ProjectProcessor {
std::vector<std::string> args; ProjectConfig *config;
const LanguageId lang = SourceFileLanguage(entry.file); std::unordered_set<size_t> command_set;
for (const std::string &arg : entry.args) { ProjectProcessor(ProjectConfig *config) : config(config) {}
if (arg.compare(0, 3, "%c ") == 0) {
if (lang == LanguageId::C) void Process(Project::Entry &entry) {
args.push_back(arg.substr(3)); const std::string base_name = sys::path::filename(entry.filename);
} else if (arg.compare(0, 5, "%cpp ") == 0) {
if (lang == LanguageId::Cpp) // Expand %c %cpp %clang
args.push_back(arg.substr(5)); std::vector<std::string> args;
} else if (arg == "%clang") { args.reserve(entry.args.size() + config->extra_flags.size() + 3);
args.push_back(lang == LanguageId::Cpp ? "clang++" : "clang"); const LanguageId lang = SourceFileLanguage(entry.filename);
} else { for (const std::string &arg : entry.args) {
args.push_back(arg); if (arg.compare(0, 3, "%c ") == 0) {
if (lang == LanguageId::C)
args.push_back(arg.substr(3));
} else if (arg.compare(0, 5, "%cpp ") == 0) {
if (lang == LanguageId::Cpp)
args.push_back(arg.substr(5));
} else if (arg == "%clang") {
args.push_back(lang == LanguageId::Cpp ? "clang++" : "clang");
} else {
args.push_back(arg);
}
} }
} if (args.empty())
if (args.empty()) return;
return result; args.insert(args.end(), config->extra_flags.begin(),
args.insert(args.end(), config->extra_flags.begin(), config->extra_flags.end());
config->extra_flags.end());
// a weird C++ deduction guide heap-use-after-free causes libclang to crash. size_t hash = std::hash<std::string>{}(entry.directory);
IgnoringDiagConsumer DiagC; for (auto &arg : args) {
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions()); if (arg[0] != '-' && EndsWith(arg, base_name)) {
DiagnosticsEngine Diags( const LanguageId lang = SourceFileLanguage(arg);
if (lang != LanguageId::Unknown) {
hash_combine(hash, size_t(lang));
continue;
}
}
hash_combine(hash, std::hash<std::string>{}(arg));
}
for (size_t i = 1; i < args.size(); i++)
// This is most likely the file path we will be passing to clang. The
// path needs to be absolute, otherwise clang_codeCompleteAt is extremely
// slow. See
// https://github.com/cquery-project/cquery/commit/af63df09d57d765ce12d40007bf56302a0446678.
if (args[i][0] != '-' && EndsWith(args[i], base_name)) {
args[i] = ResolveIfRelative(entry.directory, args[i]);
continue;
}
args.push_back("-resource-dir=" + g_config->clang.resourceDir);
args.push_back("-working-directory=" + entry.directory);
// There could be a clang version mismatch between what the project uses and
// what ccls uses. Make sure we do not emit warnings for mismatched options.
args.push_back("-Wno-unknown-warning-option");
if (!command_set.insert(hash).second) {
entry.args = std::move(args);
return;
}
// a weird C++ deduction guide heap-use-after-free causes libclang to crash.
IgnoringDiagConsumer DiagC;
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions());
DiagnosticsEngine Diags(
IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), &*DiagOpts, IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), &*DiagOpts,
&DiagC, false); &DiagC, false);
driver::Driver Driver(args[0], llvm::sys::getDefaultTargetTriple(), Diags); driver::Driver Driver(args[0], llvm::sys::getDefaultTargetTriple(), Diags);
auto TargetAndMode = auto TargetAndMode =
driver::ToolChain::getTargetAndModeFromProgramName(args[0]); driver::ToolChain::getTargetAndModeFromProgramName(args[0]);
if (!TargetAndMode.TargetPrefix.empty()) { if (!TargetAndMode.TargetPrefix.empty()) {
const char *arr[] = {"-target", TargetAndMode.TargetPrefix.c_str()}; const char *arr[] = {"-target", TargetAndMode.TargetPrefix.c_str()};
args.insert(args.begin() + 1, std::begin(arr), std::end(arr)); args.insert(args.begin() + 1, std::begin(arr), std::end(arr));
Driver.setTargetAndMode(TargetAndMode); Driver.setTargetAndMode(TargetAndMode);
}
Driver.setCheckInputsExist(false);
std::vector<const char *> cargs;
for (auto &arg : args)
cargs.push_back(arg.c_str());
cargs.push_back("-fsyntax-only");
std::unique_ptr<driver::Compilation> C(Driver.BuildCompilation(cargs));
const driver::JobList &Jobs = C->getJobs();
if (Jobs.size() != 1)
return result;
const driver::ArgStringList &CCArgs = Jobs.begin()->getArguments();
auto CI = std::make_unique<CompilerInvocation>();
CompilerInvocation::CreateFromArgs(*CI, CCArgs.data(),
CCArgs.data() + CCArgs.size(), Diags);
CI->getFrontendOpts().DisableFree = false;
CI->getCodeGenOpts().DisableFree = false;
HeaderSearchOptions &HeaderOpts = CI->getHeaderSearchOpts();
for (auto &E : HeaderOpts.UserEntries) {
std::string path = entry.ResolveIfRelative(E.Path);
switch (E.Group) {
default:
config->angle_dirs.insert(path);
break;
case frontend::Quoted:
config->quote_dirs.insert(path);
break;
case frontend::Angled:
config->angle_dirs.insert(path);
config->quote_dirs.insert(path);
break;
} }
} Driver.setCheckInputsExist(false);
for (size_t i = 1; i < args.size(); i++) std::vector<const char *> cargs;
// This is most likely the file path we will be passing to clang. The cargs.reserve(args.size() + 1);
// path needs to be absolute, otherwise clang_codeCompleteAt is extremely for (auto &arg : args)
// slow. See cargs.push_back(arg.c_str());
// https://github.com/cquery-project/cquery/commit/af63df09d57d765ce12d40007bf56302a0446678. cargs.push_back("-fsyntax-only");
if (args[i][0] != '-' && EndsWith(args[i], base_name)) {
args[i] = entry.ResolveIfRelative(args[i]); std::unique_ptr<driver::Compilation> C(Driver.BuildCompilation(cargs));
continue; const driver::JobList &Jobs = C->getJobs();
if (Jobs.size() != 1)
return;
const driver::ArgStringList &CCArgs = Jobs.begin()->getArguments();
auto CI = std::make_unique<CompilerInvocation>();
CompilerInvocation::CreateFromArgs(*CI, CCArgs.data(),
CCArgs.data() + CCArgs.size(), Diags);
CI->getFrontendOpts().DisableFree = false;
CI->getCodeGenOpts().DisableFree = false;
HeaderSearchOptions &HeaderOpts = CI->getHeaderSearchOpts();
for (auto &E : HeaderOpts.UserEntries) {
std::string path = ResolveIfRelative(entry.directory, E.Path);
switch (E.Group) {
default:
config->angle_dirs.insert(path);
break;
case frontend::Quoted:
config->quote_dirs.insert(path);
break;
case frontend::Angled:
config->angle_dirs.insert(path);
config->quote_dirs.insert(path);
break;
}
} }
entry.args = std::move(args);
// if (!sys::fs::exists(HeaderOpts.ResourceDir) && }
// HeaderOpts.UseBuiltinIncludes) };
args.push_back("-resource-dir=" + g_config->clang.resourceDir);
if (CI->getFileSystemOpts().WorkingDir.empty())
args.push_back("-working-directory=" + entry.directory);
// There could be a clang version mismatch between what the project uses and
// what ccls uses. Make sure we do not emit warnings for mismatched options.
args.push_back("-Wno-unknown-warning-option");
result.directory = entry.directory;
result.args = std::move(args);
return result;
}
std::vector<std::string> std::vector<std::string>
ReadCompilerArgumentsFromFile(const std::string &path) { ReadCompilerArgumentsFromFile(const std::string &path) {
@ -226,23 +233,25 @@ std::vector<Project::Entry> LoadFromDirectoryListing(ProjectConfig *config) {
return folder_args[project_dir]; return folder_args[project_dir];
}; };
ProjectProcessor proc(config);
for (const std::string &file : files) { for (const std::string &file : files) {
CompileCommandsEntry e; Project::Entry e;
e.directory = config->project_dir; e.directory = config->project_dir;
e.file = file; e.filename = file;
e.args = GetCompilerArgumentForFile(file); e.args = GetCompilerArgumentForFile(file);
if (e.args.empty()) if (e.args.empty())
e.args.push_back("%clang"); // Add a Dummy. e.args.push_back("%clang"); // Add a Dummy.
e.args.push_back(e.file); e.args.push_back(e.filename);
result.push_back(GetCompilationEntryFromCompileCommandEntry(config, e)); proc.Process(e);
result.push_back(e);
} }
return result; return result;
} }
std::vector<Project::Entry> std::vector<Project::Entry>
LoadCompilationEntriesFromDirectory(ProjectConfig *project, LoadEntriesFromDirectory(ProjectConfig *project,
const std::string &opt_compilation_db_dir) { const std::string &opt_compdb_dir) {
// If there is a .ccls file always load using directory listing. // If there is a .ccls file always load using directory listing.
SmallString<256> Path; SmallString<256> Path;
sys::path::append(Path, project->project_dir, ".ccls"); sys::path::append(Path, project->project_dir, ".ccls");
@ -255,8 +264,8 @@ LoadCompilationEntriesFromDirectory(ProjectConfig *project,
if (g_config->compilationDatabaseCommand.empty()) { if (g_config->compilationDatabaseCommand.empty()) {
project->mode = ProjectMode::CompileCommandsJson; project->mode = ProjectMode::CompileCommandsJson;
// Try to load compile_commands.json, but fallback to a project listing. // Try to load compile_commands.json, but fallback to a project listing.
comp_db_dir = opt_compilation_db_dir.empty() ? project->project_dir comp_db_dir =
: opt_compilation_db_dir; opt_compdb_dir.empty() ? project->project_dir : opt_compdb_dir;
sys::path::append(Path, comp_db_dir, "compile_commands.json"); sys::path::append(Path, comp_db_dir, "compile_commands.json");
} else { } else {
project->mode = ProjectMode::ExternalCommand; project->mode = ProjectMode::ExternalCommand;
@ -302,14 +311,15 @@ LoadCompilationEntriesFromDirectory(ProjectConfig *project,
StringSet<> Seen; StringSet<> Seen;
std::vector<Project::Entry> result; std::vector<Project::Entry> result;
ProjectProcessor proc(project);
for (tooling::CompileCommand &Cmd : CDB->getAllCompileCommands()) { for (tooling::CompileCommand &Cmd : CDB->getAllCompileCommands()) {
CompileCommandsEntry entry; Project::Entry entry;
entry.directory = std::move(Cmd.Directory); entry.directory = std::move(Cmd.Directory);
entry.file = entry.ResolveIfRelative(Cmd.Filename); entry.filename = ResolveIfRelative(entry.directory, Cmd.Filename);
entry.args = std::move(Cmd.CommandLine); entry.args = std::move(Cmd.CommandLine);
auto entry1 = GetCompilationEntryFromCompileCommandEntry(project, entry); proc.Process(entry);
if (Seen.insert(entry1.filename).second) if (Seen.insert(entry.filename).second)
result.push_back(entry1); result.push_back(entry);
} }
return result; return result;
} }
@ -335,12 +345,11 @@ bool Project::loaded = false;
void Project::Load(const std::string &root_directory) { void Project::Load(const std::string &root_directory) {
Project::loaded = false; Project::loaded = false;
// Load data.
ProjectConfig project; ProjectConfig project;
project.extra_flags = g_config->clang.extraArgs; project.extra_flags = g_config->clang.extraArgs;
project.project_dir = root_directory; project.project_dir = root_directory;
entries = LoadCompilationEntriesFromDirectory( entries = LoadEntriesFromDirectory(&project,
&project, g_config->compilationDatabaseDirectory); g_config->compilationDatabaseDirectory);
// Cleanup / postprocess include directories. // Cleanup / postprocess include directories.
quote_include_directories.assign(project.quote_dirs.begin(), quote_include_directories.assign(project.quote_dirs.begin(),
@ -355,14 +364,6 @@ void Project::Load(const std::string &root_directory) {
EnsureEndsInSlash(path); EnsureEndsInSlash(path);
LOG_S(INFO) << "angle_include_dir: " << path; LOG_S(INFO) << "angle_include_dir: " << path;
} }
// Setup project entries.
std::lock_guard<std::mutex> lock(mutex_);
absolute_path_to_entry_index_.reserve(entries.size());
for (size_t i = 0; i < entries.size(); ++i) {
entries[i].id = i;
absolute_path_to_entry_index_[entries[i].filename] = i;
}
} }
void Project::SetFlagsForFile(const std::vector<std::string> &flags, void Project::SetFlagsForFile(const std::vector<std::string> &flags,