1. Make resource dir unique and global.
2. Use our custom CDB loader.
This commit is contained in:
69
src/Compiler/Command.cpp
Normal file
69
src/Compiler/Command.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
#include "Compiler/Command.h"
|
||||
#include "llvm/Support/Program.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
|
||||
namespace clice {
|
||||
|
||||
llvm::Error mangleCommand(llvm::StringRef command,
|
||||
llvm::SmallVectorImpl<const char*>& out,
|
||||
llvm::SmallVectorImpl<char>& buffer) {
|
||||
llvm::SmallString<128> current;
|
||||
llvm::SmallVector<uint32_t> indices;
|
||||
bool inSingleQuote = false;
|
||||
bool inDoubleQuote = false;
|
||||
|
||||
for(size_t i = 0; i < command.size(); ++i) {
|
||||
char c = command[i];
|
||||
if(c == ' ' && !inSingleQuote && !inDoubleQuote) {
|
||||
if(!current.empty()) {
|
||||
indices.push_back(buffer.size());
|
||||
buffer.append(current);
|
||||
buffer.push_back('\0');
|
||||
current.clear();
|
||||
}
|
||||
} else if(c == '\'' && !inDoubleQuote) {
|
||||
inSingleQuote = !inSingleQuote;
|
||||
} else if(c == '"' && !inSingleQuote) {
|
||||
inDoubleQuote = !inDoubleQuote;
|
||||
} else {
|
||||
current.push_back(c);
|
||||
}
|
||||
}
|
||||
|
||||
if(!current.empty()) {
|
||||
indices.push_back(buffer.size());
|
||||
buffer.append(current);
|
||||
buffer.push_back('\0');
|
||||
}
|
||||
|
||||
/// Add resource directory.
|
||||
indices.push_back(buffer.size());
|
||||
current = std::format("-resource-dir={}", fs::resource_dir);
|
||||
buffer.append(current);
|
||||
buffer.push_back('\0');
|
||||
|
||||
/// FIXME: use better way to remove args.
|
||||
for(size_t i = 0; i < indices.size(); ++i) {
|
||||
llvm::StringRef arg(buffer.data() + indices[i]);
|
||||
|
||||
/// Skip `-c` and `-o` arguments.
|
||||
if(arg == "-c") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(arg.starts_with("-o")) {
|
||||
if(arg == "-o") {
|
||||
++i;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/// TODO: remove PCH.
|
||||
|
||||
out.push_back(arg.data());
|
||||
}
|
||||
|
||||
return llvm::Error::success();
|
||||
}
|
||||
|
||||
} // namespace clice
|
||||
@@ -1,4 +1,6 @@
|
||||
#include <Compiler/Command.h>
|
||||
#include <Compiler/Compiler.h>
|
||||
|
||||
#include <clang/Lex/PreprocessorOptions.h>
|
||||
#include <clang/Frontend/TextDiagnosticPrinter.h>
|
||||
|
||||
@@ -17,24 +19,32 @@ bool PCHInfo::needUpdate(llvm::StringRef content) {
|
||||
|
||||
namespace {
|
||||
|
||||
void adjustInvocation(clang::CompilerInvocation& invocation) {
|
||||
auto& frontOpts = invocation.getFrontendOpts();
|
||||
auto createInvocation(CompliationParams& params) {
|
||||
llvm::SmallString<1024> buffer;
|
||||
llvm::SmallVector<const char*, 16> args;
|
||||
|
||||
if(auto error = mangleCommand(params.command, args, buffer)) {
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
clang::CreateInvocationOptions options = {};
|
||||
options.VFS = params.vfs;
|
||||
auto invocation = clang::createInvocation(args, options);
|
||||
|
||||
auto& frontOpts = invocation->getFrontendOpts();
|
||||
frontOpts.DisableFree = false;
|
||||
|
||||
clang::LangOptions& langOpts = invocation.getLangOpts();
|
||||
clang::LangOptions& langOpts = invocation->getLangOpts();
|
||||
langOpts.CommentOpts.ParseAllComments = true;
|
||||
langOpts.RetainCommentsFromSystemHeaders = true;
|
||||
|
||||
// FIXME: add more.
|
||||
return invocation;
|
||||
}
|
||||
|
||||
auto createInstance(CompliationParams& params) {
|
||||
auto instance = std::make_unique<clang::CompilerInstance>();
|
||||
|
||||
/// TODO: Figure out `CreateInvocationOptions`.
|
||||
clang::CreateInvocationOptions options = {};
|
||||
options.VFS = params.vfs;
|
||||
instance->setInvocation(clang::createInvocation(params.args, options));
|
||||
instance->setInvocation(createInvocation(params));
|
||||
|
||||
/// TODO: use a thread safe filesystem and our customized `DiagnosticConsumer`.
|
||||
instance->createDiagnostics(
|
||||
@@ -44,8 +54,6 @@ auto createInstance(CompliationParams& params) {
|
||||
|
||||
instance->createFileManager(params.vfs);
|
||||
|
||||
adjustInvocation(instance->getInvocation());
|
||||
|
||||
/// Add remapped files, if bounds is provided, cut off the content.
|
||||
std::size_t size =
|
||||
params.bounds.has_value() ? params.bounds.value().Size : params.content.size();
|
||||
@@ -140,7 +148,7 @@ void CompliationParams::computeBounds(llvm::StringRef header) {
|
||||
assert(!content.empty() && "Source content is required to compute bounds");
|
||||
|
||||
if(header.empty()) {
|
||||
auto invocation = clang::createInvocation(args, {});
|
||||
auto invocation = createInvocation(*this);
|
||||
bounds = clang::Lexer::ComputePreamble(content, invocation->getLangOpts());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5,45 +5,114 @@
|
||||
namespace clice {
|
||||
|
||||
void CommandManager::update(llvm::StringRef dir) {
|
||||
std::string error;
|
||||
auto CDB = clang::tooling::CompilationDatabase::loadFromDirectory(dir, error);
|
||||
if(!CDB) {
|
||||
log::fatal("Failed to load compilation database from {0}, because {1}", dir, error);
|
||||
llvm::SmallString<128> path;
|
||||
path::append(path, dir, "compile_commands.json");
|
||||
|
||||
auto buffer = llvm::MemoryBuffer::getFile(path);
|
||||
if(!buffer) {
|
||||
log::warn("Failed to read compile_commands.json from {}, because {}",
|
||||
dir,
|
||||
buffer.getError().message());
|
||||
return;
|
||||
}
|
||||
|
||||
CDBs.try_emplace(dir, std::move(CDB));
|
||||
log::info("Successfully loaded compilation database from {0}.", dir);
|
||||
}
|
||||
auto json = json::parse(buffer.get()->getBuffer());
|
||||
if(!json) {
|
||||
log::warn("Failed to parse json file at {}, because {}", path, json.takeError());
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::string> CommandManager::lookup(llvm::StringRef file) {
|
||||
std::vector<std::string> result;
|
||||
for(const auto& [dir, CDB]: CDBs) {
|
||||
auto commands = CDB->getCompileCommands(file);
|
||||
if(commands.empty()) {
|
||||
if(!json->getAsArray()) {
|
||||
log::warn("Invalid JSON format at {}, Compilation Database requires Array, but get {}",
|
||||
path,
|
||||
refl::enum_name(json->kind()));
|
||||
return;
|
||||
}
|
||||
|
||||
auto& CDB = CDBs[dir];
|
||||
|
||||
/// Clear old commands.
|
||||
/// FIXME: it would be better to have cache in some way.
|
||||
CDB.clear();
|
||||
|
||||
for(auto& value: *json->getAsArray()) {
|
||||
auto element = value.getAsObject();
|
||||
if(!element) {
|
||||
log::warn("Invalid JSON format at {}, Compilation Database requires Object, but get {}",
|
||||
path,
|
||||
refl::enum_name(value.kind()));
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& args = commands[0].CommandLine;
|
||||
for(auto iter = args.begin(); iter != args.end(); ++iter) {
|
||||
if(iter->starts_with("-o")) {
|
||||
iter++;
|
||||
continue;
|
||||
} else if(iter->starts_with("-c")) {
|
||||
continue;
|
||||
} else if(*iter == "--") {
|
||||
continue;
|
||||
/// Source file path.
|
||||
llvm::SmallString<128> path;
|
||||
|
||||
auto file = element->getString("file");
|
||||
if(!file) {
|
||||
log::warn("the element in {} does not have a file field",
|
||||
path,
|
||||
refl::enum_name(value.kind()));
|
||||
continue;
|
||||
}
|
||||
|
||||
if(path::is_relative(file.value())) {
|
||||
auto working = element->getString("directory");
|
||||
if(!working) {
|
||||
log::warn(
|
||||
"Invalid JSON format at {}, {} is relative path, but directory is not provided",
|
||||
path,
|
||||
file.value());
|
||||
}
|
||||
|
||||
result.push_back(std::move(*iter));
|
||||
path::append(path, working.value(), file.value());
|
||||
} else {
|
||||
path::append(path, file.value());
|
||||
}
|
||||
break;
|
||||
|
||||
/// Command to compile the source file.
|
||||
llvm::SmallString<1024> rawCommand;
|
||||
|
||||
if(auto command = element->getString("command")) {
|
||||
rawCommand = command.value();
|
||||
} else if(auto arguments = element->getArray("arguments")) {
|
||||
/// FIXME:
|
||||
std::terminate();
|
||||
} else {
|
||||
log::warn(
|
||||
"Invalid JSON format, the element in {} does not have a command or arguments field",
|
||||
path,
|
||||
refl::enum_name(value.kind()));
|
||||
continue;
|
||||
}
|
||||
|
||||
CDB[path].emplace_back(rawCommand.str());
|
||||
}
|
||||
|
||||
result.push_back("-resource-dir=");
|
||||
result.back() += config::frontend().resource_dictionary;
|
||||
log::info("Compilation Database at {} is up-to-date", dir);
|
||||
}
|
||||
|
||||
return result;
|
||||
llvm::StringRef CommandManager::lookupFirst(llvm::StringRef file) {
|
||||
for(auto& [dir, cdb]: CDBs) {
|
||||
auto iter = cdb.find(file);
|
||||
if(iter != cdb.end()) {
|
||||
return iter->second.front();
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
llvm::ArrayRef<std::string> CommandManager::lookup(llvm::StringRef file) {
|
||||
for(auto& [dir, cdb]: CDBs) {
|
||||
/// FIXME: currently we directly return the first match.
|
||||
/// it is better to provide a callback to handle all matches.
|
||||
auto iter = cdb.find(file);
|
||||
if(iter != cdb.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace clice
|
||||
|
||||
@@ -6,16 +6,17 @@ namespace clice {
|
||||
|
||||
async::promise<void> Scheduler::updatePCH(llvm::StringRef filepath,
|
||||
llvm::StringRef content,
|
||||
llvm::ArrayRef<const char*> args) {
|
||||
llvm::StringRef command) {
|
||||
auto [iter, success] = pchs.try_emplace(filepath);
|
||||
if(success || iter->second.needUpdate(content)) {
|
||||
Tracer tracer;
|
||||
|
||||
CompliationParams params;
|
||||
params.srcPath = filepath;
|
||||
params.content = content;
|
||||
params.args = args;
|
||||
params.srcPath = filepath;
|
||||
params.outPath = filepath;
|
||||
params.command = command;
|
||||
|
||||
path::replace_path_prefix(params.outPath,
|
||||
config::workplace(),
|
||||
config::frontend().cache_directory);
|
||||
@@ -77,32 +78,20 @@ async::promise<void> Scheduler::buildAST(llvm::StringRef filepath, llvm::StringR
|
||||
initCmd = true;
|
||||
}
|
||||
|
||||
/// FIXME: lookup from CDB file and adjust and remove unnecessary arguments.1
|
||||
auto cmd = cmdMgr.lookup(filepath);
|
||||
llvm::SmallVector<const char*> args;
|
||||
for(auto& arg: cmd) {
|
||||
args.push_back(arg.c_str());
|
||||
}
|
||||
|
||||
/// through arguments to judge is it a module.
|
||||
bool isModule = false;
|
||||
co_await (isModule ? updatePCM() : updatePCH(filepath, content, args));
|
||||
|
||||
Tracer tracer;
|
||||
|
||||
llvm::SmallString<128> command;
|
||||
llvm::raw_svector_ostream os(command);
|
||||
for(auto arg: args) {
|
||||
os << arg << " ";
|
||||
}
|
||||
log::info("Start building AST for {0}, command: [{1}]", filepath, command.str());
|
||||
|
||||
CompliationParams params;
|
||||
params.srcPath = path;
|
||||
params.content = content;
|
||||
params.args = args;
|
||||
params.command = cmdMgr.lookupFirst(filepath);
|
||||
params.addPCH(pchs.at(filepath));
|
||||
|
||||
/// through arguments to judge is it a module.
|
||||
bool isModule = false;
|
||||
co_await (isModule ? updatePCM() : updatePCH(filepath, content, params.command));
|
||||
|
||||
Tracer tracer;
|
||||
|
||||
log::info("Start building AST for {0}, command: [{1}]", filepath, params.command.str());
|
||||
|
||||
auto task = [&] {
|
||||
/// FIXME: We cannot use reference capture the `pch` here, beacuse the reference may be
|
||||
/// Invalid Because other changed the `pchs` map. We also cannot to retrieve the `pch` from
|
||||
@@ -153,22 +142,15 @@ async::promise<proto::CompletionResult> Scheduler::codeComplete(llvm::StringRef
|
||||
|
||||
llvm::SmallString<128> path = filepath;
|
||||
/// FIXME: lookup from CDB file and adjust and remove unnecessary arguments.1
|
||||
llvm::SmallVector<const char*> args = {
|
||||
"clang++",
|
||||
"-std=c++20",
|
||||
path.c_str(),
|
||||
"-resource-dir",
|
||||
"/home/ykiko/C++/clice2/build/lib/clang/20",
|
||||
};
|
||||
|
||||
CompliationParams params;
|
||||
params.srcPath = path;
|
||||
params.args = args;
|
||||
params.content = iter->second.content;
|
||||
params.srcPath = path;
|
||||
params.command = cmdMgr.lookupFirst(filepath);
|
||||
|
||||
/// through arguments to judge is it a module.
|
||||
bool isModule = false;
|
||||
co_await (isModule ? updatePCM() : updatePCH(params.srcPath, params.content, args));
|
||||
co_await (isModule ? updatePCM() : updatePCH(params.srcPath, params.content, params.command));
|
||||
params.addPCH(pchs.at(filepath));
|
||||
|
||||
Tracer tracer;
|
||||
|
||||
@@ -22,6 +22,12 @@ int Server::run(int argc, const char** argv) {
|
||||
log::info("Successfully loaded configuration file from {0}.", cl::config.getValue());
|
||||
}
|
||||
|
||||
/// Get the resource directory.
|
||||
if(auto error = fs::init_resource_dir(argv[0])) {
|
||||
log::fatal("Failed to get resource directory, because {0}", error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto dispatch = [this](json::Value value) -> async::promise<void> {
|
||||
assert(value.kind() == json::Value::Object);
|
||||
auto object = value.getAsObject();
|
||||
|
||||
Reference in New Issue
Block a user