dev: simplify protocol def

This commit is contained in:
Myriad-Dreamin
2026-01-11 15:30:02 +08:00
parent f89793c66d
commit 824b305f93
4 changed files with 100 additions and 67 deletions

View File

@@ -1,6 +1,7 @@
#pragma once
#include <expected>
#include "PluginProtocol.h"
#include "Async/Async.h"
#include "llvm/ADT/ArrayRef.h"
@@ -9,33 +10,22 @@
// clang-format off
/// Run `python scripts/plugin-def.py update` to update the hash.
#define CLICE_PLUGIN_DEF_HASH "sha256:e35ff1adfbc385f7bae605e82ca4e7e70cfbf0f933bb8d57169d0d29abe5b6f7"
#define CLICE_PLUGIN_DEF_HASH "sha256:c46f7edfda0455327c65d40b9315ad5dc39153326c8cc63f1d8de2e2d0e7735a"
// clang-format on
namespace clice {
/// The hash of the definitions exposed to server plugins.
constexpr std::string_view plugin_definition_hash = CLICE_PLUGIN_DEF_HASH;
class Server;
#define CLICE_PLUGIN_PROTOCOL
#include "PluginDef.h"
#undef CLICE_PLUGIN_PROTOCOL
struct ServerPluginBuilder;
/// A loaded server plugin.
///
/// An instance of this class wraps a loaded server plugin and gives access to its interface.
class Plugin {
public:
struct Self;
Plugin(Self* self) : self(self) {}
Self* operator->() {
return self;
}
public:
/// Attempts to load a server plugin from a given file.
///
@@ -56,21 +46,17 @@ public:
/// Registers the server callbacks for the loaded plugin.
void register_server_callbacks(ServerPluginBuilder& builder) const;
public:
struct Self;
Plugin(Self* self) : self(self) {}
Self* operator->() {
return self;
}
protected:
Self* self;
};
struct ServerPluginBuilder {
public:
ServerPluginBuilder(ServerRef server_ref) : server_ref(server_ref) {}
#define CliceServerPluginAPI(METHOD, ...) void METHOD(void* plugin_data, __VA_ARGS__)
#include "PluginDef.h"
#undef CliceServerPluginAPI
protected:
ServerRef server_ref;
};
} // namespace clice

View File

@@ -10,14 +10,23 @@
#error "CLICE_PLUGIN_API_VERSION must be 1, but got " CLICE_PLUGIN_API_VERSION
#endif
/// Defines the library APIs that loads a plugin.
#ifdef CLICE_PLUGIN_PROTOCOL
#include <cstdint>
#include "Async/Async.h"
#include "llvm/Support/Compiler.h"
namespace clice {
class Server;
struct ServerPluginBuilder;
/// Defines the library APIs that loads a plugin.
extern "C" {
/// A C-compatible struct that contains information about the plugin.
struct PluginInfo {
/// The clice API version of the plugin.
std::uint32_t api_version;
uint32_t api_version;
/// The name of the plugin.
const char* name;
/// The version of the plugin.
@@ -61,23 +70,43 @@ public:
protected:
Self* self;
};
#endif
/// Defines the library APIs to register callbacks for a plugin.
#ifdef CliceServerPluginAPI
CliceServerPluginAPI(get_server_ref, ServerRef& server);
using lifecycle_hook_t = async::Task<> (*)(ServerRef server, void* plugin_data);
struct ServerPluginBuilder {
public:
ServerPluginBuilder(ServerRef server_ref) : server_ref(server_ref) {}
CliceServerPluginAPI(on_initialize, lifecycle_hook_t callback);
CliceServerPluginAPI(on_initialized, lifecycle_hook_t callback);
CliceServerPluginAPI(on_shutdown, lifecycle_hook_t callback);
CliceServerPluginAPI(on_exit, lifecycle_hook_t callback);
CliceServerPluginAPI(on_did_change_configuration, lifecycle_hook_t callback);
using command_handler_t =
async::Task<llvm::json::Value> (*)(ServerRef server,
void* plugin_data,
llvm::ArrayRef<llvm::StringRef> arguments);
CliceServerPluginAPI(register_commmand_handler,
llvm::StringRef command,
command_handler_t callback);
#endif
/// Gets a reference to the server.
auto get_server_ref() const -> ServerRef {
return server_ref;
}
#define CliceServerPluginAPI(METHOD, ...) void METHOD(void* plugin_data, __VA_ARGS__)
using lifecycle_hook_t = async::Task<> (*)(ServerRef server, void* plugin_data);
/// Registers a callback to be called when the server is initialized.
CliceServerPluginAPI(on_initialize, lifecycle_hook_t callback);
/// Registers a callback to be called when the server is initialized.
CliceServerPluginAPI(on_initialized, lifecycle_hook_t callback);
/// Registers a callback to be called when the server is shutdown.
CliceServerPluginAPI(on_shutdown, lifecycle_hook_t callback);
/// Registers a callback to be called when the server is exiting.
CliceServerPluginAPI(on_exit, lifecycle_hook_t callback);
/// Registers a callback to be called when the server's configuration is changed.
CliceServerPluginAPI(on_did_change_configuration, lifecycle_hook_t callback);
using command_handler_t =
async::Task<llvm::json::Value> (*)(ServerRef server,
void* plugin_data,
llvm::ArrayRef<llvm::StringRef> arguments);
/// Registers a callback to be called when a command is received from the LSP client.
CliceServerPluginAPI(register_commmand_handler,
llvm::StringRef command,
command_handler_t callback);
#undef CliceServerPluginAPI
protected:
ServerRef server_ref;
};
} // namespace clice

3
include/Server/Utility.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
#define bail(...) std::unexpected(std::format(__VA_ARGS__))

View File

@@ -1,6 +1,7 @@
#include "Server/Plugin.h"
#include "Implement.h"
#include "Server/Utility.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/Support/DynamicLibrary.h"
@@ -28,44 +29,62 @@ std::expected<Plugin, std::string> Plugin::load(const std::string& file_path) {
std::string err;
auto library = llvm::sys::DynamicLibrary::getPermanentLibrary(file_path.c_str(), &err);
if(!library.isValid()) {
return std::unexpected("Could not load library '" + file_path + "': " + err);
return bail("Could not load library '{}': {}", file_path, err);
}
Plugin P{
/// We currently never destroy plugins, so this is not a memory leak.
new Self{file_path, library}
};
/// `clice_get_server_plugin_info` should be resolved to the definition from the plugin
/// we are currently loading.
intptr_t get_details_fn = (intptr_t)library.getAddressOfSymbol("clice_get_server_plugin_info");
if(!get_details_fn) {
/// If the symbol isn't found, this is probably a legacy plugin, which is an
/// error.
return std::unexpected("Plugin entry point not found in '" + file_path +
"'. Is this a clice server plugin?");
return bail(
"The symbol `clice_get_server_plugin_info` is not found in '{}'. Is this a clice server plugin?",
file_path);
}
auto info = reinterpret_cast<decltype(clice_get_server_plugin_info)*>(get_details_fn)();
/// First, we check whether the plugin is compatible with the clice plugin API.
if(info.api_version != CLICE_PLUGIN_API_VERSION) {
return std::unexpected("Wrong API version on plugin '" + file_path + "'. Got version " +
std::to_string(info.api_version) + ", supported version is " +
std::to_string(CLICE_PLUGIN_API_VERSION) + ".");
return bail("Wrong API version on plugin '{}'. Got version {}. Supported version is {}.",
file_path,
info.api_version,
CLICE_PLUGIN_API_VERSION);
}
/// Then, we safely get definition hash from the plugin, and check if it is consistent with
/// the expected hash. This ensures that the plugin has consistent declarations with the server.
/// the expected hash. This ensures that the plugin has consistent declarations with the
/// server.
std::string definition_hash = info.definition_hash;
if(definition_hash != CLICE_PLUGIN_DEF_HASH) {
return std::unexpected("Wrong definition hash on plugin '" + file_path + "'. Got '" +
definition_hash + "', expected '" + CLICE_PLUGIN_DEF_HASH + "'.");
if(plugin_definition_hash.size() != definition_hash.size()) {
return bail("Wrong definition hash size on plugin '{}'. Got {}, expected {} ({}).",
file_path,
definition_hash.size(),
plugin_definition_hash.size(),
plugin_definition_hash);
}
/// If there is any non-printable character in the definition hash, this is likely a bug in the
/// plugin. We cannot even print the `definition_hash` in this case.
if(std::ranges::any_of(definition_hash, [](char c) { return !std::isprint(c); })) {
return bail("Corrupt definition hash on plugin '{}'. This is likely a bug in the plugin.",
file_path);
}
if(definition_hash != CLICE_PLUGIN_DEF_HASH) {
return bail("Wrong definition hash on plugin '{}'. Got '{}', expected '{}'.",
file_path,
definition_hash,
CLICE_PLUGIN_DEF_HASH);
}
/// A plugin must implement the `register_server_callbacks` function.
if(!info.register_server_callbacks) {
return std::unexpected("Empty `register_server_callbacks` function in plugin '" +
file_path + "'.");
return bail("Empty `register_server_callbacks` function in plugin '{}'.", file_path);
}
P->name = info.name;
@@ -91,10 +110,6 @@ using command_handler_t =
async::Task<llvm::json::Value> (*)(ServerRef server,
const llvm::ArrayRef<llvm::StringRef>& arguments);
void ServerPluginBuilder::get_server_ref(void* plugin_data, ServerRef& server) {
server = server_ref;
}
void ServerPluginBuilder::on_initialize(void* plugin_data, lifecycle_hook_t callback) {
auto server = server_ref;
server_ref.server().initialize_hooks.push_back(