Update PCH handling (#172)

This commit is contained in:
ykiko
2025-08-10 22:31:00 +08:00
committed by GitHub
parent 3615c5a194
commit a738ad536c
37 changed files with 1076 additions and 679 deletions

View File

@@ -2,46 +2,176 @@
#include "Server/Server.h"
#include "Compiler/Compilation.h"
#include "Feature/Diagnostic.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/FileOutputBuffer.h"
#include "llvm/ADT/ScopeExit.h"
namespace clice {
async::Task<OpenFile*> Server::add_document(std::string path, std::string content) {
auto& openFile = opening_files[path];
openFile.content = content;
auto& task = openFile.ast_build_task;
/// If there is already an AST build task, cancel it.
if(!task.empty()) {
task.cancel();
task.dispose();
void Server::load_cache_info() {
auto path = path::join(config::cache.dir, "cache.json");
auto file = llvm::MemoryBuffer::getFile(path);
if(!file) {
log::warn("Fail to load cache info, because: {}", file.getError());
return;
}
/// Create and schedule a new task.
task = build_ast(std::move(path), std::move(content));
co_await task;
llvm::StringRef content = file.get()->getBuffer();
auto json = json::parse(content);
if(!json) {
log::warn("Fail to load cache info, invalid json: {}", json.takeError());
return;
}
co_return &opening_files[path];
auto object = json->getAsObject();
if(!object) {
return;
}
auto version = object->getString("version");
if(!version) {
log::info("Fail to load cache info, the cache info is outdated");
return;
}
if(auto array = object->getArray("pchs")) {
for(auto& pch: *array) {
auto object = pch.getAsObject();
if(!object) {
continue;
}
auto file = object->getString("file");
auto path = object->getString("path");
auto preamble = object->getString("preamble");
auto mtime = object->getNumber("mtime");
auto deps = object->getArray("deps");
auto arguments = object->getArray("arguments");
if(!file || !path || !preamble || !mtime || !deps || !arguments) {
continue;
}
PCHInfo info;
info.path = *path;
info.preamble = *preamble;
info.mtime = *mtime;
for(auto& dep: *deps) {
info.deps.push_back(dep.getAsString()->str());
}
for(auto& argument: *arguments) {
auto carg = database.save_string(*argument.getAsString());
info.arguments.emplace_back(carg.data());
}
/// Update the PCH info.
opening_files[*file].pch = std::move(info);
}
}
log::info("Load cache info successfully");
}
async::Task<> Server::build_pch(std::string path, std::string content) {
auto bound = compute_preamble_bound(content);
void Server::save_cache_info() {
json::Object json;
json["version"] = "0.0.1";
json["pchs"] = json::Array();
auto openFile = &opening_files[path];
bool outdated = true;
if(openFile->pch) {
/// FIXME:
/// outdated = co_await isPCHOutdated(path, llvm::StringRef(content).substr(0, bound));
for(auto& [file, open_file]: opening_files) {
if(!open_file.pch) {
continue;
}
auto& pch = *open_file.pch;
json::Object object;
object["file"] = file;
object["path"] = pch.path;
object["preamble"] = pch.preamble;
object["mtime"] = pch.mtime;
object["deps"] = json::serialize(pch.deps);
object["arguments"] = json::serialize(pch.arguments);
json["pchs"].getAsArray()->emplace_back(std::move(object));
}
/// If not need update, return directly.
if(!outdated) {
co_return;
auto final_path = path::join(config::cache.dir, "cache.json");
llvm::SmallString<128> temp_path;
if(auto error = llvm::sys::fs::createTemporaryFile("cache", "json", temp_path)) {
log::warn("Fail to create temporary file for cache info: {}", error.message());
return;
}
auto clean_up = llvm::make_scope_exit([&temp_path]() { llvm::sys::fs::remove(temp_path); });
std::error_code EC;
llvm::raw_fd_ostream os(temp_path, EC, llvm::sys::fs::OF_None);
if(EC) {
log::warn("Fail to open temporary file for writing: {}", EC.message());
return;
}
os << json::Value(std::move(json));
os.flush();
os.close();
if(os.has_error()) {
log::warn("Fail to write cache info to temporary file");
return;
}
if(auto error = llvm::sys::fs::rename(temp_path, final_path)) {
log::warn("Fail to rename temporary file to final cache file: {}", error.message());
return;
}
clean_up.release();
log::info("Save cache info successfully");
}
async::Task<bool> Server::build_pch(std::string file, std::string content) {
auto bound = compute_preamble_bound(content);
auto open_file = &opening_files[file];
auto info = database.get_command(file, true, true);
auto check_pch_update = [&content, &bound, &info](PCHInfo& pch) {
if(content.substr(0, bound) != pch.preamble) {
return true;
}
if(info.arguments != pch.arguments) {
return true;
}
/// Check deps.
for(auto& dep: pch.deps) {
fs::file_status status;
auto error = fs::status(dep, status, true);
if(error || std::chrono::duration_cast<std::chrono::milliseconds>(
status.getLastModificationTime().time_since_epoch())
.count() > pch.mtime) {
return true;
}
}
return false;
};
/// Check update ...
if(open_file->pch && !check_pch_update(*open_file->pch)) {
/// If not need update, return directly.
log::info("PCH is already up-to-date for {}", file);
co_return true;
}
/// The actual PCH build task.
constexpr static auto PCHBuildTask =
[](Server& server,
[](CompilationDatabase::LookupInfo& info,
OpenFile* open_file,
std::string path,
std::uint32_t bound,
std::string content,
@@ -59,11 +189,11 @@ async::Task<> Server::build_pch(std::string path, std::string content) {
CompilationParams params;
params.output_file = path::join(config::cache.dir, path::filename(path) + ".pch");
params.arguments = server.database.get_command(path, true, true).arguments;
params.arguments = std::move(info.arguments);
params.diagnostics = diagnostics;
params.add_remapped_file(path, content, bound);
PCHInfo info;
PCHInfo pch;
std::string command;
for(auto argument: params.arguments) {
@@ -73,64 +203,72 @@ async::Task<> Server::build_pch(std::string path, std::string content) {
log::info("Start building PCH for {}, command: [{}]", path, command);
std::string message;
std::vector<feature::DocumentLink> links;
/// PCH file is written until destructing, Add a single block
/// for it.
bool cond = co_await async::submit([&] {
auto result = compile(params, info);
if(!result) {
log::warn("Building PCH fails for {}, Because: {}", path, result.error());
for(auto& diagnostic: *diagnostics) {
log::warn("{}", diagnostic.message);
}
bool success = co_await async::submit([&params, &pch, &message, &links] -> bool {
/// PCH file is written until destructing, Add a single block
/// for it.
auto unit = compile(params, pch);
if(!unit) {
message = std::move(unit.error());
return false;
}
links = feature::document_links(*result);
/// TODO: index PCH.
links = feature::document_links(*unit);
/// TODO: index PCH file, etc
return true;
});
if(!cond) {
if(!success) {
log::warn("Building PCH fails for {}, Because: {}", path, message);
for(auto& diagnostic: *diagnostics) {
log::warn("{}", diagnostic.message);
}
co_return false;
}
auto& openFile = server.opening_files[path];
log::info("Building PCH successfully for {}", path);
/// Update the built PCH info.
openFile.pch = std::move(info);
openFile.pch_includes = std::move(links);
open_file->pch = std::move(pch);
open_file->pch_includes = std::move(links);
/// Resume waiters on this event.
openFile.pch_built_event.set();
openFile.pch_built_event.clear();
open_file->pch_built_event.set();
open_file->pch_built_event.clear();
co_return true;
};
openFile = &opening_files[path];
open_file = &opening_files[file];
/// If there is already an PCH build task, cancel it.
auto& task = openFile->pch_build_task;
auto& task = open_file->pch_build_task;
if(!task.empty()) {
task.cancel();
task.dispose();
if(task.finished()) {
task.release().destroy();
log::info("Release old pch task!");
} else {
task.cancel();
task.dispose();
}
log::info("Cancel old PCH building task!");
}
/// Schedule the new building task.
task = PCHBuildTask(*this, path, bound, std::move(content), openFile->diagnostics);
task = PCHBuildTask(info, open_file, file, bound, std::move(content), open_file->diagnostics);
if(co_await task) {
log::info("Building PCH successfully for {}", path);
/// FIXME: At this point, task has already been finished, destroy it
/// directly.
task.release().destroy();
/// Dispose the task so that it will destroyed when task complete.
task.dispose();
co_return true;
}
/// TODO: report diagnostics in the preamble.
/// FIXME: report diagnostics in the preamble.
co_return false;
}
async::Task<> Server::build_ast(std::string path, std::string content) {
@@ -141,7 +279,10 @@ async::Task<> Server::build_ast(std::string path, std::string content) {
auto guard = co_await file->ast_built_lock.try_lock();
/// PCH is already updated.
co_await build_pch(path, content);
bool success = co_await build_pch(path, content);
if(!success) {
co_return;
}
auto pch = opening_files[path].pch;
if(!pch) {
@@ -173,18 +314,42 @@ async::Task<> Server::build_ast(std::string path, std::string content) {
file = &opening_files[path];
/// Update built AST info.
file->ast = std::make_shared<CompilationUnit>(std::move(*ast));
/// Dispose the task so that it will destroyed when task complete.
file->ast_build_task.dispose();
log::info("Building AST successfully for {}", path);
}
async::Task<> Server::on_did_open(proto::DidOpenTextDocumentParams params) {
auto path = mapping.to_path(params.textDocument.uri);
auto file = co_await add_document(path, std::move(params.textDocument.text));
if(file->diagnostics) {
auto guard = co_await file->ast_built_lock.try_lock();
file = &opening_files[path];
async::Task<OpenFile*> Server::add_document(std::string path, std::string content) {
auto& openFile = opening_files[path];
openFile.content = content;
auto& task = openFile.ast_build_task;
/// If there is already an AST build task, cancel it.
if(!task.empty()) {
if(task.finished()) {
task.release().destroy();
log::info("Release old AST building Task!");
} else {
task.cancel();
task.dispose();
}
log::info("Cancel old AST building Task!");
}
/// Create and schedule a new task.
task = build_ast(std::move(path), std::move(content));
task.schedule();
co_return &opening_files[path];
}
async::Task<> Server::publish_diagnostics(std::string path, OpenFile* file) {
auto guard = co_await file->ast_built_lock.try_lock();
file = &opening_files[path];
if(file->ast) {
auto diagnostics = feature::diagnostics(kind, mapping, *file->ast);
co_await notify("textDocument/publishDiagnostics",
json::Object{
@@ -192,6 +357,14 @@ async::Task<> Server::on_did_open(proto::DidOpenTextDocumentParams params) {
{"diagnostics", std::move(diagnostics)},
});
}
}
async::Task<> Server::on_did_open(proto::DidOpenTextDocumentParams params) {
auto path = mapping.to_path(params.textDocument.uri);
auto file = co_await add_document(path, std::move(params.textDocument.text));
if(file->diagnostics) {
co_await publish_diagnostics(path, file);
}
co_return;
}
@@ -199,14 +372,7 @@ async::Task<> Server::on_did_change(proto::DidChangeTextDocumentParams params) {
auto path = mapping.to_path(params.textDocument.uri);
auto file = co_await add_document(path, std::move(params.contentChanges[0].text));
if(file->diagnostics) {
auto guard = co_await file->ast_built_lock.try_lock();
file = &opening_files[path];
auto diagnostics = feature::diagnostics(kind, mapping, *file->ast);
co_await notify("textDocument/publishDiagnostics",
json::Object{
{"uri", mapping.to_uri(path) },
{"diagnostics", std::move(diagnostics)},
});
co_await publish_diagnostics(path, file);
}
co_return;
}