Compare commits
30 Commits
main
...
server-plu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f8168577b | ||
|
|
60cab39f92 | ||
|
|
67bedd1ae0 | ||
|
|
7ab8ad9513 | ||
|
|
c11178fd77 | ||
|
|
150f47c590 | ||
|
|
b7987fded3 | ||
|
|
44a4bd4107 | ||
|
|
044e4c4b27 | ||
|
|
68eb63ba04 | ||
|
|
39d3648fdd | ||
|
|
6640c05d66 | ||
|
|
69ac764ef2 | ||
|
|
04c6ca5337 | ||
|
|
2408978d2d | ||
|
|
74f75f107f | ||
|
|
0e2e487bc9 | ||
|
|
6be48bccd2 | ||
|
|
4262734d21 | ||
|
|
ed0d7db3db | ||
|
|
824b305f93 | ||
|
|
f89793c66d | ||
|
|
4425fb5244 | ||
|
|
25a85a3b8e | ||
|
|
0f95344abe | ||
|
|
3511915886 | ||
|
|
6e50451c43 | ||
|
|
99d9363b95 | ||
|
|
7533d4d15e | ||
|
|
a118c16e96 |
@@ -150,6 +150,28 @@ add_custom_target(
|
||||
DEPENDS ${CONFIG_GENERATED_FILE}
|
||||
)
|
||||
|
||||
add_library(clice_core_deps INTERFACE)
|
||||
target_include_directories(clice_core_deps INTERFACE
|
||||
"${PROJECT_SOURCE_DIR}/include"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/generated"
|
||||
)
|
||||
target_link_libraries(clice_core_deps INTERFACE
|
||||
clice_options
|
||||
libuv::libuv
|
||||
spdlog::spdlog
|
||||
tomlplusplus::tomlplusplus
|
||||
roaring::roaring
|
||||
flatbuffers
|
||||
llvm-libs
|
||||
)
|
||||
|
||||
add_library(clice_builtin_api INTERFACE)
|
||||
target_link_libraries(clice_builtin_api INTERFACE clice_core_deps)
|
||||
|
||||
include("${PROJECT_SOURCE_DIR}/cmake/builtin-libraries.cmake")
|
||||
|
||||
clice_include_builtin_library_modules()
|
||||
|
||||
file(GLOB_RECURSE CLICE_SOURCES CONFIGURE_DEPENDS
|
||||
"${PROJECT_SOURCE_DIR}/src/AST/*.cpp"
|
||||
"${PROJECT_SOURCE_DIR}/src/Async/*.cpp"
|
||||
@@ -162,20 +184,8 @@ file(GLOB_RECURSE CLICE_SOURCES CONFIGURE_DEPENDS
|
||||
)
|
||||
add_library(clice-core STATIC "${CLICE_SOURCES}")
|
||||
add_dependencies(clice-core generate_flatbuffers_schema generate_config)
|
||||
|
||||
target_include_directories(clice-core PUBLIC
|
||||
"${PROJECT_SOURCE_DIR}/include"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/generated"
|
||||
)
|
||||
target_link_libraries(clice-core PUBLIC
|
||||
clice_options
|
||||
libuv::libuv
|
||||
spdlog::spdlog
|
||||
tomlplusplus::tomlplusplus
|
||||
roaring::roaring
|
||||
flatbuffers
|
||||
llvm-libs
|
||||
)
|
||||
target_link_libraries(clice-core PUBLIC clice_core_deps)
|
||||
clice_finalize_builtin_libraries(TARGET clice-core)
|
||||
|
||||
add_executable(clice "${PROJECT_SOURCE_DIR}/src/clice.cc")
|
||||
target_link_libraries(clice PRIVATE clice-core)
|
||||
|
||||
158
cmake/builtin-libraries.cmake
Normal file
158
cmake/builtin-libraries.cmake
Normal file
@@ -0,0 +1,158 @@
|
||||
include_guard(GLOBAL)
|
||||
|
||||
set(
|
||||
CLICE_BUILTIN_LIBRARY_MODULES
|
||||
"${CLICE_BUILTIN_LIBRARY_MODULES}"
|
||||
CACHE STRING
|
||||
"Semicolon-separated list of CMake modules that register extra builtin clice libraries"
|
||||
)
|
||||
|
||||
function(clice_add_builtin_library)
|
||||
set(options)
|
||||
set(oneValueArgs NAME ENTRYPOINT)
|
||||
set(multiValueArgs SOURCES INCLUDE_DIRECTORIES LINK_LIBRARIES COMPILE_DEFINITIONS COMPILE_OPTIONS)
|
||||
cmake_parse_arguments(CBL "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
|
||||
if(CBL_UNPARSED_ARGUMENTS)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"clice_add_builtin_library got unexpected arguments: ${CBL_UNPARSED_ARGUMENTS}"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(NOT CBL_NAME)
|
||||
message(FATAL_ERROR "clice_add_builtin_library requires NAME")
|
||||
endif()
|
||||
|
||||
if(NOT CBL_SOURCES)
|
||||
message(FATAL_ERROR "clice_add_builtin_library(${CBL_NAME}) requires SOURCES")
|
||||
endif()
|
||||
|
||||
if(NOT CBL_ENTRYPOINT)
|
||||
message(FATAL_ERROR "clice_add_builtin_library(${CBL_NAME}) requires ENTRYPOINT")
|
||||
endif()
|
||||
|
||||
if(NOT TARGET clice_builtin_api)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"clice_builtin_api must be defined before calling clice_add_builtin_library(${CBL_NAME})"
|
||||
)
|
||||
endif()
|
||||
|
||||
set(target "clice_builtin_${CBL_NAME}")
|
||||
if(TARGET "${target}")
|
||||
message(FATAL_ERROR "builtin library target '${target}' already exists")
|
||||
endif()
|
||||
|
||||
add_library("${target}" OBJECT)
|
||||
target_sources("${target}" PRIVATE ${CBL_SOURCES})
|
||||
target_link_libraries("${target}" PUBLIC clice_builtin_api)
|
||||
add_dependencies("${target}" generate_flatbuffers_schema generate_config)
|
||||
|
||||
if(CBL_INCLUDE_DIRECTORIES)
|
||||
target_include_directories("${target}" PRIVATE ${CBL_INCLUDE_DIRECTORIES})
|
||||
endif()
|
||||
|
||||
if(CBL_LINK_LIBRARIES)
|
||||
target_link_libraries("${target}" PUBLIC ${CBL_LINK_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(CBL_COMPILE_DEFINITIONS)
|
||||
target_compile_definitions("${target}" PRIVATE ${CBL_COMPILE_DEFINITIONS})
|
||||
endif()
|
||||
|
||||
if(CBL_COMPILE_OPTIONS)
|
||||
target_compile_options("${target}" PRIVATE ${CBL_COMPILE_OPTIONS})
|
||||
endif()
|
||||
|
||||
set_property(GLOBAL APPEND PROPERTY CLICE_BUILTIN_LIBRARY_TARGETS "${target}")
|
||||
set_property(GLOBAL APPEND PROPERTY CLICE_BUILTIN_LIBRARY_ENTRYPOINTS "${CBL_ENTRYPOINT}")
|
||||
endfunction()
|
||||
|
||||
function(clice_include_builtin_library_modules)
|
||||
foreach(module_path IN LISTS CLICE_BUILTIN_LIBRARY_MODULES)
|
||||
cmake_path(
|
||||
ABSOLUTE_PATH module_path
|
||||
BASE_DIRECTORY "${PROJECT_SOURCE_DIR}"
|
||||
NORMALIZE
|
||||
OUTPUT_VARIABLE module_abs_path
|
||||
)
|
||||
|
||||
if(NOT EXISTS "${module_abs_path}")
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"builtin library module '${module_path}' does not exist: ${module_abs_path}"
|
||||
)
|
||||
endif()
|
||||
|
||||
include("${module_abs_path}")
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
function(clice_finalize_builtin_libraries)
|
||||
set(options)
|
||||
set(oneValueArgs TARGET REGISTRATION_SOURCE)
|
||||
cmake_parse_arguments(CBL "${options}" "${oneValueArgs}" "" ${ARGN})
|
||||
|
||||
if(CBL_UNPARSED_ARGUMENTS)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"clice_finalize_builtin_libraries got unexpected arguments: ${CBL_UNPARSED_ARGUMENTS}"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(NOT CBL_TARGET)
|
||||
message(FATAL_ERROR "clice_finalize_builtin_libraries requires TARGET")
|
||||
endif()
|
||||
|
||||
if(NOT TARGET "${CBL_TARGET}")
|
||||
message(FATAL_ERROR "target '${CBL_TARGET}' does not exist")
|
||||
endif()
|
||||
|
||||
if(NOT CBL_REGISTRATION_SOURCE)
|
||||
set(CBL_REGISTRATION_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/generated/builtin-libraries.cpp")
|
||||
endif()
|
||||
|
||||
get_property(builtin_targets GLOBAL PROPERTY CLICE_BUILTIN_LIBRARY_TARGETS)
|
||||
get_property(builtin_entrypoints GLOBAL PROPERTY CLICE_BUILTIN_LIBRARY_ENTRYPOINTS)
|
||||
|
||||
if(NOT builtin_targets)
|
||||
set(builtin_targets "")
|
||||
endif()
|
||||
|
||||
if(NOT builtin_entrypoints)
|
||||
set(builtin_entrypoints "")
|
||||
endif()
|
||||
|
||||
set(registration_source_content "#include \"Server/Plugin.h\"\n\nnamespace clice {\n\n")
|
||||
|
||||
foreach(entrypoint IN LISTS builtin_entrypoints)
|
||||
string(APPEND registration_source_content "::clice::PluginInfo ${entrypoint}();\n")
|
||||
endforeach()
|
||||
|
||||
string(APPEND registration_source_content "\nvoid register_builtin_server_plugins(ServerPluginBuilder& builder) {\n")
|
||||
|
||||
foreach(entrypoint IN LISTS builtin_entrypoints)
|
||||
string(
|
||||
APPEND
|
||||
registration_source_content
|
||||
" ${entrypoint}().register_server_callbacks(builder);\n"
|
||||
)
|
||||
endforeach()
|
||||
|
||||
string(APPEND registration_source_content "}\n\n} // namespace clice\n")
|
||||
|
||||
get_filename_component(registration_source_dir "${CBL_REGISTRATION_SOURCE}" DIRECTORY)
|
||||
file(MAKE_DIRECTORY "${registration_source_dir}")
|
||||
file(CONFIGURE OUTPUT "${CBL_REGISTRATION_SOURCE}" CONTENT "${registration_source_content}" @ONLY)
|
||||
|
||||
target_sources("${CBL_TARGET}" PRIVATE "${CBL_REGISTRATION_SOURCE}")
|
||||
|
||||
if(builtin_targets)
|
||||
foreach(builtin_target IN LISTS builtin_targets)
|
||||
target_sources("${CBL_TARGET}" PRIVATE $<TARGET_OBJECTS:${builtin_target}>)
|
||||
endforeach()
|
||||
|
||||
target_link_libraries("${CBL_TARGET}" PRIVATE ${builtin_targets})
|
||||
endif()
|
||||
endfunction()
|
||||
@@ -66,16 +66,20 @@ set(FLATBUFFERS_BUILD_GRPC OFF CACHE BOOL "" FORCE)
|
||||
set(FLATBUFFERS_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(FLATBUFFERS_BUILD_FLATHASH OFF CACHE BOOL "" FORCE)
|
||||
|
||||
# cpptrace
|
||||
FetchContent_Declare(
|
||||
cpptrace
|
||||
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
|
||||
GIT_TAG v1.0.4
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
set(CPPTRACE_DISABLE_CXX_20_MODULES ON CACHE BOOL "" FORCE)
|
||||
FetchContent_MakeAvailable(libuv spdlog tomlplusplus croaring flatbuffers)
|
||||
|
||||
FetchContent_MakeAvailable(libuv spdlog tomlplusplus croaring flatbuffers cpptrace)
|
||||
if(CLICE_ENABLE_TEST)
|
||||
# cpptrace
|
||||
FetchContent_Declare(
|
||||
cpptrace
|
||||
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
|
||||
GIT_TAG v1.0.4
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
set(CPPTRACE_DISABLE_CXX_20_MODULES ON CACHE BOOL "" FORCE)
|
||||
|
||||
FetchContent_MakeAvailable(cpptrace)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
target_compile_definitions(uv_a PRIVATE _CRT_SECURE_NO_WARNINGS)
|
||||
|
||||
@@ -69,6 +69,7 @@ Optional build options:
|
||||
| CLICE_ENABLE_TEST | OFF | Build clice unit tests |
|
||||
| CLICE_USE_LIBCXX | OFF | Build clice with libc++ (adds `-std=libc++`); if enabled, ensure the LLVM libs are also built with libc++ |
|
||||
| CLICE_CI_ENVIRONMENT | OFF | Enable the `CLICE_CI_ENVIRONMENT` macro; some tests only run in CI |
|
||||
| CLICE_BUILTIN_LIBRARY_MODULES | "" | Semicolon-separated list of CMake modules that register builtin libraries compiled into `clice`; see [Builtin Libraries](./builtin-library.md) |
|
||||
|
||||
### XMake
|
||||
|
||||
|
||||
101
docs/en/dev/builtin-library.md
Normal file
101
docs/en/dev/builtin-library.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# Builtin Libraries
|
||||
|
||||
Builtin libraries are compiled directly into the `clice` binary instead of being loaded later with `--plugin-path`.
|
||||
|
||||
This is useful when:
|
||||
|
||||
- your plugin sources live outside the `clice` source tree
|
||||
- you want the builtin to be part of the default executable
|
||||
- you need extra include directories, compile definitions, or link dependencies during the main build
|
||||
|
||||
## CMake Entry Point
|
||||
|
||||
`clice` now exposes a small helper module at [cmake/builtin-libraries.cmake](/cmake/builtin-libraries.cmake).
|
||||
|
||||
Extra builtin libraries are registered through the cache variable `CLICE_BUILTIN_LIBRARY_MODULES`.
|
||||
|
||||
Each value in `CLICE_BUILTIN_LIBRARY_MODULES` must be a CMake file. During configure, `clice` includes those files, and each file calls `clice_add_builtin_library(...)`.
|
||||
|
||||
## Minimal Module
|
||||
|
||||
Create a CMake file in your external project, for example `/path/to/my-plugin/clice-builtin.cmake`:
|
||||
|
||||
```cmake
|
||||
clice_add_builtin_library(
|
||||
NAME my_plugin
|
||||
SOURCES
|
||||
"${CMAKE_CURRENT_LIST_DIR}/src/MyPlugin.cpp"
|
||||
INCLUDE_DIRECTORIES
|
||||
"${CMAKE_CURRENT_LIST_DIR}/include"
|
||||
ENTRYPOINT
|
||||
clice_get_my_plugin_server_plugin_info
|
||||
)
|
||||
```
|
||||
|
||||
Then configure `clice` with:
|
||||
|
||||
```shell
|
||||
cmake -B build -G Ninja \
|
||||
-DCLICE_BUILTIN_LIBRARY_MODULES="/path/to/my-plugin/clice-builtin.cmake"
|
||||
```
|
||||
|
||||
To load multiple modules, pass a semicolon-separated CMake list:
|
||||
|
||||
```shell
|
||||
cmake -B build -G Ninja \
|
||||
-DCLICE_BUILTIN_LIBRARY_MODULES="/path/to/a.cmake;/path/to/b.cmake"
|
||||
```
|
||||
|
||||
## `clice_add_builtin_library`
|
||||
|
||||
The helper accepts the following arguments:
|
||||
|
||||
| Argument | Required | Description |
|
||||
| --- | --- | --- |
|
||||
| `NAME` | Yes | Logical name used to create an internal object target |
|
||||
| `SOURCES` | Yes | Source files compiled into `clice`; absolute paths and out-of-tree paths are supported |
|
||||
| `ENTRYPOINT` | Yes | Unique function name in namespace `clice` that returns `::clice::PluginInfo` |
|
||||
| `INCLUDE_DIRECTORIES` | No | Extra include directories for this builtin only |
|
||||
| `LINK_LIBRARIES` | No | Extra libraries or targets needed by this builtin |
|
||||
| `COMPILE_DEFINITIONS` | No | Extra compile definitions for this builtin |
|
||||
| `COMPILE_OPTIONS` | No | Extra compile options for this builtin |
|
||||
|
||||
## Entrypoint Requirements
|
||||
|
||||
Builtin libraries share a single final executable, so each builtin must use its own unique entrypoint function inside namespace `clice`.
|
||||
|
||||
Dynamic plugins use:
|
||||
|
||||
```cpp
|
||||
clice_get_server_plugin_info()
|
||||
```
|
||||
|
||||
Builtin libraries should use a unique name such as:
|
||||
|
||||
```cpp
|
||||
clice_get_my_plugin_server_plugin_info()
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```cpp
|
||||
#include "Server/Plugin.h"
|
||||
|
||||
namespace clice {
|
||||
|
||||
::clice::PluginInfo clice_get_my_plugin_server_plugin_info() {
|
||||
return {
|
||||
CLICE_PLUGIN_API_VERSION,
|
||||
"MyPlugin",
|
||||
"v0.0.1",
|
||||
CLICE_PLUGIN_DEF_HASH,
|
||||
[](clice::ServerPluginBuilder& builder) {
|
||||
// register callbacks here
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace clice
|
||||
```
|
||||
|
||||
`clice` generates the static registration glue automatically, so once the module is included, no additional edits to `src/clice.cc` are required.
|
||||
78
docs/en/dev/server-plugin.md
Normal file
78
docs/en/dev/server-plugin.md
Normal file
@@ -0,0 +1,78 @@
|
||||
You can implement a clice server plugin to extend clice's functionality.
|
||||
|
||||
## Use case
|
||||
|
||||
When you use `clice` as LSP backend of LLM agents, e.g. claude code, you can add plugin to provide some extra features.
|
||||
|
||||
## Writing a plugin
|
||||
|
||||
When a plugin is loaded by the server, it will call `clice_get_server_plugin_info` to obtain information about this plugin and about how to register its customization points.
|
||||
|
||||
This function needs to be implemented by the plugin, see the example below:
|
||||
|
||||
```c++
|
||||
extern "C" ::clice::PluginInfo LLVM_ATTRIBUTE_WEAK
|
||||
clice_get_server_plugin_info() {
|
||||
return {
|
||||
CLICE_PLUGIN_API_VERSION, "MyPlugin", "v0.1", CLICE_PLUGIN_DEF_HASH,
|
||||
[](ServerPluginBuilder builder) { ... }
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
See [PluginProtocol.h](/include/Server/PluginProtocol.h) for more details.
|
||||
|
||||
## Compiling a plugin
|
||||
|
||||
The plugin must be compiled with the same dependencies and compiler options as clice, otherwise it will cause undefined behavior. [config/llvm-manifest.json](/config/llvm-manifest.json) defines the build information used by clice.
|
||||
|
||||
## Loading plugins
|
||||
|
||||
For security reasons, clice does not allow loading plugins through configuration files, but must specify the plugin path through command line options.
|
||||
|
||||
When `clice` starts, it will load all plugins specified in the command line. You can specify the plugin path through the `--plugin-path` option.
|
||||
|
||||
```shell
|
||||
$ clice --plugin-path /path/to/my-plugin.so
|
||||
```
|
||||
|
||||
## Getting content of `CLICE_PLUGIN_DEF_HASH`
|
||||
|
||||
There are two values to return in the `clice_get_server_plugin_info` function.
|
||||
|
||||
- `CLICE_PLUGIN_API_VERSION` is used to ensure compability of the `clice_get_server_plugin_info` function between the plugin and the server.
|
||||
- `CLICE_PLUGIN_DEF_HASH` is used to ensure the consistency of the C++ declarations between the plugin and the server.
|
||||
|
||||
To debug the content of `CLICE_PLUGIN_DEF_HASH`, you can run following command:
|
||||
|
||||
```shell
|
||||
$ git clone https://github.com/clice-io/clice.git
|
||||
$ cd clice
|
||||
$ git checkout `clice --version --git-describe`
|
||||
$ python scripts/plugin-def.py content
|
||||
```
|
||||
|
||||
You will get a C source code file, content of which is like this:
|
||||
|
||||
```cpp
|
||||
#if 0
|
||||
// begin of config/llvm-manifest.json
|
||||
[
|
||||
{
|
||||
"version": "21.1.4+r1",
|
||||
"filename": "arm64-macos-clang-debug-asan.tar.xz",
|
||||
"sha256": "7da4b7d63edefecaf11773e7e701c575140d1a07329bbbb038673b6ee4516ff5",
|
||||
"lto": false,
|
||||
"asan": true,
|
||||
"platform": "macosx",
|
||||
"build_type": "Debug"
|
||||
},
|
||||
...
|
||||
]
|
||||
...
|
||||
#endif
|
||||
```
|
||||
|
||||
## Builtin libraries
|
||||
|
||||
If you want to compile a plugin directly into the `clice` binary instead of loading it dynamically, see [Builtin Libraries](./builtin-library.md).
|
||||
@@ -69,6 +69,7 @@ cmake -B build -G Ninja \
|
||||
| CLICE_ENABLE_TEST | OFF | 是否构建 clice 的单元测试 |
|
||||
| CLICE_USE_LIBCXX | OFF | 是否使用 libc++ 来构建 clice(添加 `-std=libc++`),如果开启,请确保 LLVM 库也是使用 libc++ 编译的 |
|
||||
| CLICE_CI_ENVIRONMENT | OFF | 是否打开 `CLICE_CI_ENVIRONMENT` 这个宏,有些测试在 CI 环境才会执行 |
|
||||
| CLICE_BUILTIN_LIBRARY_MODULES | "" | 以分号分隔的 CMake 模块列表,用于注册会被编译进 `clice` 的 builtin library;详见 [Builtin Libraries](./builtin-library.md) |
|
||||
|
||||
### XMake
|
||||
|
||||
|
||||
101
docs/zh/dev/builtin-library.md
Normal file
101
docs/zh/dev/builtin-library.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# Builtin Libraries
|
||||
|
||||
Builtin library 会被直接编译进 `clice` 可执行文件,而不是在运行时通过 `--plugin-path` 动态加载。
|
||||
|
||||
这种方式适合以下场景:
|
||||
|
||||
- 插件源码位于 `clice` 源码树之外
|
||||
- 希望该 builtin 默认随 `clice` 可执行文件一起发布
|
||||
- 需要为该 builtin 单独补充 include 路径、编译宏或链接依赖
|
||||
|
||||
## CMake 入口
|
||||
|
||||
`clice` 现在提供了一个辅助模块:[cmake/builtin-libraries.cmake](/cmake/builtin-libraries.cmake)。
|
||||
|
||||
额外的 builtin library 通过缓存变量 `CLICE_BUILTIN_LIBRARY_MODULES` 注册。
|
||||
|
||||
`CLICE_BUILTIN_LIBRARY_MODULES` 中的每一项都必须是一个 CMake 文件。配置阶段 `clice` 会 `include()` 这些文件,而每个文件都需要调用 `clice_add_builtin_library(...)`。
|
||||
|
||||
## 最小示例
|
||||
|
||||
你可以在外部项目中创建一个 CMake 文件,例如 `/path/to/my-plugin/clice-builtin.cmake`:
|
||||
|
||||
```cmake
|
||||
clice_add_builtin_library(
|
||||
NAME my_plugin
|
||||
SOURCES
|
||||
"${CMAKE_CURRENT_LIST_DIR}/src/MyPlugin.cpp"
|
||||
INCLUDE_DIRECTORIES
|
||||
"${CMAKE_CURRENT_LIST_DIR}/include"
|
||||
ENTRYPOINT
|
||||
clice_get_my_plugin_server_plugin_info
|
||||
)
|
||||
```
|
||||
|
||||
然后在配置 `clice` 时传入:
|
||||
|
||||
```shell
|
||||
cmake -B build -G Ninja \
|
||||
-DCLICE_BUILTIN_LIBRARY_MODULES="/path/to/my-plugin/clice-builtin.cmake"
|
||||
```
|
||||
|
||||
如果要加载多个模块,可以传入以分号分隔的 CMake 列表:
|
||||
|
||||
```shell
|
||||
cmake -B build -G Ninja \
|
||||
-DCLICE_BUILTIN_LIBRARY_MODULES="/path/to/a.cmake;/path/to/b.cmake"
|
||||
```
|
||||
|
||||
## `clice_add_builtin_library`
|
||||
|
||||
该辅助函数支持以下参数:
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `NAME` | 是 | 逻辑名称,会用于创建内部 object target |
|
||||
| `SOURCES` | 是 | 要编译进 `clice` 的源文件,支持绝对路径和源码树外路径 |
|
||||
| `ENTRYPOINT` | 是 | `clice` 命名空间中的唯一函数名,返回值类型为 `::clice::PluginInfo` |
|
||||
| `INCLUDE_DIRECTORIES` | 否 | 仅对当前 builtin 生效的额外头文件目录 |
|
||||
| `LINK_LIBRARIES` | 否 | 当前 builtin 额外需要的库或 target |
|
||||
| `COMPILE_DEFINITIONS` | 否 | 当前 builtin 额外需要的编译宏 |
|
||||
| `COMPILE_OPTIONS` | 否 | 当前 builtin 额外需要的编译选项 |
|
||||
|
||||
## Entrypoint 要求
|
||||
|
||||
所有 builtin library 最终都会被链接进同一个可执行文件,因此每个 builtin 都必须在 `clice` 命名空间中使用唯一的入口函数名。
|
||||
|
||||
动态插件通常使用:
|
||||
|
||||
```cpp
|
||||
clice_get_server_plugin_info()
|
||||
```
|
||||
|
||||
builtin library 应该改用类似下面的唯一名字:
|
||||
|
||||
```cpp
|
||||
clice_get_my_plugin_server_plugin_info()
|
||||
```
|
||||
|
||||
例如:
|
||||
|
||||
```cpp
|
||||
#include "Server/Plugin.h"
|
||||
|
||||
namespace clice {
|
||||
|
||||
::clice::PluginInfo clice_get_my_plugin_server_plugin_info() {
|
||||
return {
|
||||
CLICE_PLUGIN_API_VERSION,
|
||||
"MyPlugin",
|
||||
"v0.0.1",
|
||||
CLICE_PLUGIN_DEF_HASH,
|
||||
[](clice::ServerPluginBuilder& builder) {
|
||||
// 在这里注册回调
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace clice
|
||||
```
|
||||
|
||||
`clice` 会自动生成静态注册代码,因此只要模块被包含进来,就不需要再手动修改 `src/clice.cc`。
|
||||
78
docs/zh/dev/server-plugin.md
Normal file
78
docs/zh/dev/server-plugin.md
Normal file
@@ -0,0 +1,78 @@
|
||||
你可以在 clice 中实现一个 server plugin 来扩展 clice 的功能。
|
||||
|
||||
## 用例
|
||||
|
||||
当你使用 `clice` 作为 LLM 代理的 LSP 后端时,比如 claude code,你可以添加插件来提供一些额外功能。
|
||||
|
||||
## 编写插件
|
||||
|
||||
当一个插件被服务器加载时,它会调用 `clice_get_server_plugin_info` 来获取关于这个插件的信息以及如何注册它的定制点。
|
||||
|
||||
这个函数需要由插件实现,请参考下面的示例:
|
||||
|
||||
```cpp
|
||||
extern "C" ::clice::PluginInfo LLVM_ATTRIBUTE_WEAK
|
||||
clice_get_server_plugin_info() {
|
||||
return {
|
||||
CLICE_PLUGIN_API_VERSION, "MyPlugin", "v0.1", CLICE_PLUGIN_DEF_HASH,
|
||||
[](ServerPluginBuilder builder) { ... }
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
请参考 [PluginProtocol.h](/include/Server/PluginProtocol.h) 了解更多细节。
|
||||
|
||||
## 编译插件
|
||||
|
||||
插件必须使用与 clice 一致的依赖和编译器选项来编译,否则会导致 undefined behavior。[config/llvm-manifest.json](/config/llvm-manifest.json) 中定义了 clice 使用的构建信息。
|
||||
|
||||
## 加载插件
|
||||
|
||||
为了安全考虑,clice 不允许通过配置文件来加载插件,而必须通过命令行选项来指定插件的路径。
|
||||
|
||||
在 `clice` 启动时,它会加载所有在命令行中指定的插件。你可以通过 `--plugin-path` 选项来指定插件的路径。
|
||||
|
||||
```shell
|
||||
$ clice --plugin-path /path/to/my-plugin.so
|
||||
```
|
||||
|
||||
## 获取 `CLICE_PLUGIN_DEF_HASH` 的内容
|
||||
|
||||
在 `clice_get_server_plugin_info` 函数中需要返回两个值。
|
||||
|
||||
- `CLICE_PLUGIN_API_VERSION` 用于确保插件和服务器之间的 `clice_get_server_plugin_info` 函数的一致性。
|
||||
- `CLICE_PLUGIN_DEF_HASH` 用于确保插件和服务器之间的 C++ 声明的一致性。
|
||||
|
||||
要调试 `CLICE_PLUGIN_DEF_HASH` 的内容,你可以运行以下命令:
|
||||
|
||||
```shell
|
||||
$ git clone https://github.com/clice-io/clice.git
|
||||
$ cd clice
|
||||
$ git checkout `clice --version --git-describe`
|
||||
$ python scripts/plugin-def.py content > /tmp/plugin-proto.h
|
||||
```
|
||||
|
||||
你将会得到一个 C 源码格式的文件,内容大致如下:
|
||||
|
||||
```cpp
|
||||
#if 0
|
||||
// begin of config/llvm-manifest.json
|
||||
[
|
||||
{
|
||||
"version": "21.1.4+r1",
|
||||
"filename": "arm64-macos-clang-debug-asan.tar.xz",
|
||||
"sha256": "7da4b7d63edefecaf11773e7e701c575140d1a07329bbbb038673b6ee4516ff5",
|
||||
"lto": false,
|
||||
"asan": true,
|
||||
"platform": "macosx",
|
||||
"build_type": "Debug"
|
||||
},
|
||||
...
|
||||
]
|
||||
...
|
||||
#endif
|
||||
```
|
||||
|
||||
## Builtin libraries
|
||||
|
||||
如果你希望把插件直接编译进 `clice` 可执行文件,而不是在运行时动态加载,请参考 [Builtin Libraries](./builtin-library.md)。
|
||||
@@ -37,6 +37,10 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void unset() {
|
||||
ready = false;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
ready = false;
|
||||
awaiters.clear();
|
||||
|
||||
@@ -98,7 +98,7 @@ public:
|
||||
static std::optional<std::uint32_t> get_option_id(llvm::StringRef argument);
|
||||
|
||||
/// FIXME: bad interface design ...
|
||||
std::vector<const char*> files();
|
||||
std::vector<llvm::StringRef> files();
|
||||
|
||||
/// FIXME: remove this api?
|
||||
auto save_string(llvm::StringRef string) -> llvm::StringRef;
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "llvm/Support/JSON.h"
|
||||
|
||||
namespace clice::proto {
|
||||
|
||||
using integer = std::int32_t;
|
||||
@@ -15,6 +17,8 @@ using decimal = double;
|
||||
|
||||
using string = std::string;
|
||||
|
||||
using any = llvm::json::Value;
|
||||
|
||||
template <typename T>
|
||||
using array = std::vector<T>;
|
||||
|
||||
|
||||
17
include/Protocol/Feature/ExecuteCommand.h
Normal file
17
include/Protocol/Feature/ExecuteCommand.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Basic.h"
|
||||
|
||||
namespace clice::proto {
|
||||
|
||||
struct ExecuteCommandParams {
|
||||
string command;
|
||||
array<any> arguments;
|
||||
};
|
||||
|
||||
struct TextDocumentParams {
|
||||
/// The text document.
|
||||
TextDocumentIdentifier textDocument;
|
||||
};
|
||||
|
||||
} // namespace clice::proto
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "Feature/DocumentHighlight.h"
|
||||
#include "Feature/DocumentLink.h"
|
||||
#include "Feature/DocumentSymbol.h"
|
||||
#include "Feature/ExecuteCommand.h"
|
||||
#include "Feature/FoldingRange.h"
|
||||
#include "Feature/Formatting.h"
|
||||
#include "Feature/Hover.h"
|
||||
|
||||
@@ -44,6 +44,11 @@ public:
|
||||
if(it2 != project_index.indices.end()) {
|
||||
auto path = project_index.path_pool.path(it2->second);
|
||||
it->second = index::MergedIndex::load(path);
|
||||
} else {
|
||||
std::println(stderr,
|
||||
"failed to load project index for path_id: {} {}",
|
||||
path_id,
|
||||
project_index.indices.size());
|
||||
}
|
||||
|
||||
return it->second;
|
||||
@@ -67,6 +72,14 @@ public:
|
||||
|
||||
/// TODO: Types ...
|
||||
|
||||
bool empty() const {
|
||||
return project_index.indices.empty();
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
return project_index.indices.size();
|
||||
}
|
||||
|
||||
private:
|
||||
CompilationDatabase& database;
|
||||
|
||||
@@ -87,6 +100,10 @@ private:
|
||||
std::deque<std::uint32_t> waitings;
|
||||
|
||||
async::Event update_event;
|
||||
|
||||
async::Event finish_event;
|
||||
|
||||
size_t finish_cnt = 0;
|
||||
};
|
||||
|
||||
} // namespace clice
|
||||
|
||||
59
include/Server/Plugin.h
Normal file
59
include/Server/Plugin.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
#include <expected>
|
||||
|
||||
#include "PluginProtocol.h"
|
||||
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
|
||||
// clang-format off
|
||||
/// Run `python scripts/plugin-def.py update` to update the hash.
|
||||
#define CLICE_PLUGIN_DEF_HASH "sha256:332cd65741dea17d5168dd1ab564ac73ce230f14fc1e34170438d895eb11881c"
|
||||
// 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;
|
||||
|
||||
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:
|
||||
/// Attempts to load a server plugin from a given file.
|
||||
///
|
||||
/// Returns an error if either the library cannot be found or loaded,
|
||||
/// there is no public entry point, or the plugin implements the wrong API
|
||||
/// version.
|
||||
static std::expected<Plugin, std::string> load(const std::string& file_path);
|
||||
|
||||
/// Gets the file path of the loaded plugin.
|
||||
llvm::StringRef file_path() const;
|
||||
|
||||
/// Gets the name of the loaded plugin.
|
||||
llvm::StringRef name() const;
|
||||
|
||||
/// Gets the version of the loaded plugin.
|
||||
llvm::StringRef version() const;
|
||||
|
||||
/// 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;
|
||||
};
|
||||
|
||||
} // namespace clice
|
||||
113
include/Server/PluginProtocol.h
Normal file
113
include/Server/PluginProtocol.h
Normal file
@@ -0,0 +1,113 @@
|
||||
#pragma once
|
||||
|
||||
/// The API version of the clice plugin.
|
||||
/// Update this version when you change:
|
||||
/// - The definition of struct `PluginInfo`.
|
||||
/// - The definition of function `clice_get_server_plugin_info`.
|
||||
/// Note: you don't have to update this version if you only change other APIs, which is guaranteed
|
||||
/// by the `PluginInfo::definition_hash`.
|
||||
#define CLICE_PLUGIN_API_VERSION 1
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "Async/Async.h"
|
||||
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/Support/Compiler.h"
|
||||
#include "llvm/Support/JSON.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.
|
||||
uint32_t api_version;
|
||||
/// The name of the plugin.
|
||||
const char* name;
|
||||
/// The version of the plugin.
|
||||
const char* version;
|
||||
/// The plugin definition hash.
|
||||
const char* definition_hash;
|
||||
/// Registers the server callbacks for the loaded plugin.
|
||||
void (*register_server_callbacks)(ServerPluginBuilder& builder);
|
||||
};
|
||||
|
||||
/// The public entry point for a server plugin.
|
||||
///
|
||||
/// When a plugin is loaded by the server, it will call this entry point to
|
||||
/// obtain information about this plugin and about how to register its customization points.
|
||||
/// This function needs to be implemented by the plugin, see the example below:
|
||||
///
|
||||
/// ```cpp
|
||||
/// #include "Server/Plugin.h"
|
||||
/// extern "C" ::clice::PluginInfo LLVM_ATTRIBUTE_WEAK
|
||||
/// clice_get_server_plugin_info() {
|
||||
/// return {
|
||||
/// CLICE_PLUGIN_API_VERSION, "MyPlugin", "v0.0.1", CLICE_PLUGIN_DEF_HASH,
|
||||
/// [](clice::ServerPluginBuilder& builder) { ... }
|
||||
/// };
|
||||
/// }
|
||||
/// ```
|
||||
PluginInfo LLVM_ATTRIBUTE_WEAK clice_get_server_plugin_info();
|
||||
}
|
||||
|
||||
struct ServerRef {
|
||||
public:
|
||||
struct Self;
|
||||
|
||||
ServerRef(Server* self) : self(self) {}
|
||||
|
||||
Server* operator->() const {
|
||||
return self;
|
||||
}
|
||||
|
||||
Server& server() const;
|
||||
|
||||
protected:
|
||||
Server* self;
|
||||
};
|
||||
|
||||
/// Defines the library APIs to register callbacks for a plugin.
|
||||
struct ServerPluginBuilder {
|
||||
public:
|
||||
ServerPluginBuilder(ServerRef server_ref) : server_ref(server_ref) {}
|
||||
|
||||
/// 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::json::Value> 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,6 +3,7 @@
|
||||
#include "Config.h"
|
||||
#include "Convert.h"
|
||||
#include "Indexer.h"
|
||||
#include "Plugin.h"
|
||||
#include "Async/Async.h"
|
||||
#include "Compiler/Command.h"
|
||||
#include "Compiler/Diagnostic.h"
|
||||
@@ -10,6 +11,8 @@
|
||||
#include "Feature/DocumentLink.h"
|
||||
#include "Protocol/Protocol.h"
|
||||
|
||||
#include <llvm/ADT/FunctionExtras.h>
|
||||
|
||||
namespace clice {
|
||||
|
||||
struct OpenFile {
|
||||
@@ -76,10 +79,10 @@ public:
|
||||
}
|
||||
|
||||
/// Try get OpenFile from manager, default construct one if not exists.
|
||||
[[nodiscard]] ActiveFile& get_or_add(llvm::StringRef path);
|
||||
[[nodiscard]] ActiveFile get_or_add(llvm::StringRef path);
|
||||
|
||||
/// Add a OpenFile to the manager.
|
||||
ActiveFile& add(llvm::StringRef path, OpenFile file);
|
||||
ActiveFile add(llvm::StringRef path, OpenFile file);
|
||||
|
||||
[[nodiscard]] bool contains(llvm::StringRef path) const {
|
||||
return index.contains(path);
|
||||
@@ -94,7 +97,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
ActiveFile& lru_put_impl(llvm::StringRef path, OpenFile file);
|
||||
ActiveFile lru_put_impl(llvm::StringRef path, OpenFile file);
|
||||
|
||||
private:
|
||||
/// The maximum size of the cache.
|
||||
@@ -167,7 +170,7 @@ private:
|
||||
|
||||
async::Task<> on_exit(proto::ExitParams params);
|
||||
|
||||
private:
|
||||
public:
|
||||
/// Load the cache info from disk.
|
||||
void load_cache_info();
|
||||
|
||||
@@ -192,6 +195,8 @@ private:
|
||||
private:
|
||||
using Result = async::Task<json::Value>;
|
||||
|
||||
auto on_execute_command(proto::ExecuteCommandParams params) -> Result;
|
||||
|
||||
auto on_completion(proto::CompletionParams params) -> Result;
|
||||
|
||||
auto on_hover(proto::HoverParams params) -> Result;
|
||||
@@ -218,7 +223,7 @@ private:
|
||||
|
||||
auto on_inlay_hint(proto::InlayHintParams params) -> Result;
|
||||
|
||||
private:
|
||||
public:
|
||||
/// The current request id.
|
||||
std::uint32_t server_request_id = 0;
|
||||
std::uint32_t client_request_id = 0;
|
||||
@@ -241,6 +246,20 @@ private:
|
||||
config::Config config;
|
||||
|
||||
Indexer indexer;
|
||||
|
||||
public:
|
||||
friend struct ServerPluginBuilder;
|
||||
using lifecycle_hook_t = llvm::unique_function<async::Task<>()>;
|
||||
using command_handler_t = llvm::unique_function<async::Task<llvm::json::Value>(
|
||||
llvm::ArrayRef<llvm::json::Value> arguments)>;
|
||||
|
||||
std::vector<lifecycle_hook_t> initialize_hooks;
|
||||
std::vector<lifecycle_hook_t> initialized_hooks;
|
||||
std::vector<lifecycle_hook_t> shutdown_hooks;
|
||||
std::vector<lifecycle_hook_t> exit_hooks;
|
||||
std::vector<lifecycle_hook_t> did_change_configuration_hooks;
|
||||
llvm::StringMap<command_handler_t> command_handlers;
|
||||
std::vector<Plugin> plugins;
|
||||
};
|
||||
|
||||
} // namespace clice
|
||||
|
||||
3
include/Server/Utility.h
Normal file
3
include/Server/Utility.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define bail(...) std::unexpected(std::format(__VA_ARGS__))
|
||||
@@ -149,7 +149,7 @@ inline std::string toPath(llvm::StringRef uri) {
|
||||
|
||||
llvm::SmallString<128> result;
|
||||
if(auto err = fs::real_path(decoded, result)) {
|
||||
std::println("Failed to get real path: {}, Input is {}\n", err.message(), decoded);
|
||||
std::println(stderr, "Failed to get real path: {}, Input is {}\n", err.message(), decoded);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,20 @@ struct Serde<std::nullopt_t> {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct Serde<std::optional<T>> {
|
||||
static json::Value serialize(const std::optional<T>& v) {
|
||||
return v ? json::serialize(*v) : json::Value(nullptr);
|
||||
}
|
||||
|
||||
static std::optional<T> deserialize(const json::Value& value) {
|
||||
if(value.kind() == json::Value::Null) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return json::deserialize<T>(value);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Serde<bool> {
|
||||
static json::Value serialize(bool v) {
|
||||
|
||||
@@ -22,6 +22,24 @@ def normalize_mode(value: str) -> str:
|
||||
)
|
||||
|
||||
|
||||
def copy_headers(project_root, install_prefix):
|
||||
print("\nCopying internal Sema headers...")
|
||||
clang_sema_dir = project_root / "clang/lib/Sema"
|
||||
install_sema_dir = install_prefix / "include/clang/Sema"
|
||||
install_sema_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
headers_to_copy = ["CoroutineStmtBuilder.h", "TypeLocBuilder.h", "TreeTransform.h"]
|
||||
|
||||
for header in headers_to_copy:
|
||||
src = clang_sema_dir / header
|
||||
dst = install_sema_dir / header
|
||||
if src.exists():
|
||||
shutil.copy(src, dst)
|
||||
print(f" Copied {header}")
|
||||
else:
|
||||
print(f" Warning: {header} not found in source.")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Build LLVM with specific configurations."
|
||||
@@ -293,21 +311,7 @@ def main():
|
||||
print("Build failed!")
|
||||
sys.exit(1)
|
||||
|
||||
print("\nCopying internal Sema headers...")
|
||||
clang_sema_dir = project_root / "clang/lib/Sema"
|
||||
install_sema_dir = install_prefix / "include/clang/Sema"
|
||||
install_sema_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
headers_to_copy = ["CoroutineStmtBuilder.h", "TypeLocBuilder.h", "TreeTransform.h"]
|
||||
|
||||
for header in headers_to_copy:
|
||||
src = clang_sema_dir / header
|
||||
dst = install_sema_dir / header
|
||||
if src.exists():
|
||||
shutil.copy(src, dst)
|
||||
print(f" Copied {header}")
|
||||
else:
|
||||
print(f" Warning: {header} not found in source.")
|
||||
copy_headers(project_root, install_prefix)
|
||||
|
||||
def human_readable(num: int) -> str:
|
||||
for unit in ["B", "KB", "MB", "GB"]:
|
||||
|
||||
98
scripts/plugin-def.py
Normal file
98
scripts/plugin-def.py
Normal file
@@ -0,0 +1,98 @@
|
||||
import hashlib
|
||||
import re
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
clice_apis = []
|
||||
|
||||
# all of the files in `include/`, other than `include/Server/Plugin.h`
|
||||
for path in Path("include/").glob("**/*.h"):
|
||||
if path.name == "Plugin.h":
|
||||
continue
|
||||
clice_apis.append(path)
|
||||
|
||||
clice_apis = [
|
||||
# the dependencies of the clice.
|
||||
Path("config/llvm-manifest.json"),
|
||||
# the clice C/C++ sources.
|
||||
*sorted(clice_apis),
|
||||
]
|
||||
|
||||
|
||||
def fatal(message: str):
|
||||
print(f"error: {message}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def sha256sum(paths: list[Path]) -> str:
|
||||
digest = hashlib.sha256()
|
||||
for path in paths:
|
||||
with path.open("rb") as handle:
|
||||
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def clice_api_content():
|
||||
content = []
|
||||
content.append("#if 0")
|
||||
for path in clice_apis:
|
||||
with path.open("r") as file:
|
||||
content.append(f"// begin of {path}")
|
||||
content.append(file.read())
|
||||
content.append(f"// end of {path}")
|
||||
content.append("#endif")
|
||||
return "\n".join(content)
|
||||
|
||||
|
||||
def plugin_def_hash():
|
||||
hash_val = sha256sum(clice_apis)
|
||||
return f"sha256:{hash_val}"
|
||||
|
||||
|
||||
def update():
|
||||
hash_val = plugin_def_hash()
|
||||
with open("include/Server/Plugin.h", "r") as file:
|
||||
content = file.read()
|
||||
content = re.sub(
|
||||
r"#define CLICE_PLUGIN_DEF_HASH .*",
|
||||
f'#define CLICE_PLUGIN_DEF_HASH "{hash_val}"',
|
||||
content,
|
||||
)
|
||||
with open("include/Server/Plugin.h", "w") as file:
|
||||
file.write(content)
|
||||
|
||||
|
||||
def check():
|
||||
hash_val = plugin_def_hash()
|
||||
with open("include/Server/Plugin.h", "r") as file:
|
||||
content = file.read()
|
||||
match = re.search(r'#define CLICE_PLUGIN_DEF_HASH "(.*)"', content)
|
||||
if match is None:
|
||||
fatal("plugin def hash not found in include/Server/Plugin.h")
|
||||
if match.group(1) != hash_val:
|
||||
fatal(
|
||||
f"plugin def hash mismatch in include/Server/Plugin.h, expected: {hash_val}, actual: {match.group(1)}"
|
||||
)
|
||||
print(f"plugin def hash is up to date: {hash_val}")
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) > 1:
|
||||
if sys.argv[1] == "update":
|
||||
update()
|
||||
check()
|
||||
elif sys.argv[1] == "check":
|
||||
check()
|
||||
elif sys.argv[1] == "content":
|
||||
print(clice_api_content())
|
||||
else:
|
||||
fatal(
|
||||
f"invalid command: {sys.argv[1]}, expected: update, check",
|
||||
)
|
||||
else:
|
||||
fatal("no command provided, expected: update, check")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -643,11 +643,24 @@ std::vector<UpdateInfo> CompilationDatabase::load_compile_database(llvm::StringR
|
||||
llvm::BumpPtrAllocator local;
|
||||
llvm::StringSaver saver(local);
|
||||
llvm::SmallVector<const char*, 32> agrs;
|
||||
auto hasCC1 = false;
|
||||
for(auto& argument: *arguments) {
|
||||
if(argument.kind() == json::Value::String) {
|
||||
agrs.emplace_back(saver.save(*argument.getAsString()).data());
|
||||
if(argument.getAsString() == "-cc1") {
|
||||
hasCC1 = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(hasCC1) {
|
||||
std::println(stderr, "cannot handle arguments: {}", *file);
|
||||
for(auto& arg: *arguments) {
|
||||
std::print(stderr, "{} ", arg);
|
||||
}
|
||||
std::println(stderr, "");
|
||||
continue;
|
||||
}
|
||||
item.info = self->save_compilation_info(*file, *directory, agrs);
|
||||
} else if(command) {
|
||||
item.info = self->save_compilation_info(*file, *directory, *command);
|
||||
@@ -762,10 +775,10 @@ std::optional<std::uint32_t> CompilationDatabase::get_option_id(llvm::StringRef
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<const char*> CompilationDatabase::files() {
|
||||
std::vector<const char*> result;
|
||||
std::vector<llvm::StringRef> CompilationDatabase::files() {
|
||||
std::vector<llvm::StringRef> result;
|
||||
for(auto& [file, _]: self->files) {
|
||||
result.emplace_back(self->strings.get(file).data());
|
||||
result.emplace_back(self->strings.get(file));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -287,7 +287,7 @@ CompilerFamily driver_family(llvm::StringRef driver) {
|
||||
|
||||
std::vector<const char*> query_toolchain(const QueryParams& params) {
|
||||
auto arguments = params.arguments;
|
||||
llvm::StringRef driver = arguments[0];
|
||||
llvm::StringRef driver = "clang"; // arguments[0];
|
||||
|
||||
/// Note: The name used to invoke the compiler driver affects its behavior.
|
||||
/// For example, `/usr/bin/clang++` is often a symbolic link to
|
||||
|
||||
@@ -249,7 +249,7 @@ async::Task<bool> Server::build_pch(std::string file, std::string content) {
|
||||
auto info = database.lookup(file, options);
|
||||
|
||||
auto bound = compute_preamble_bound(content);
|
||||
auto& open_file = opening_files.get_or_add(file);
|
||||
auto open_file = opening_files.get_or_add(file);
|
||||
|
||||
/// Check update ...
|
||||
if(open_file->pch && !check_pch_update(content, bound, info, *open_file->pch)) {
|
||||
@@ -347,7 +347,7 @@ async::Task<> Server::build_ast(std::string path, std::string content) {
|
||||
}
|
||||
|
||||
async::Task<std::shared_ptr<OpenFile>> Server::add_document(std::string path, std::string content) {
|
||||
auto& openFile = opening_files.get_or_add(path);
|
||||
auto openFile = opening_files.get_or_add(path);
|
||||
openFile->version += 1;
|
||||
openFile->content = content;
|
||||
|
||||
|
||||
19
src/Server/Implement.h
Normal file
19
src/Server/Implement.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "Server/Plugin.h"
|
||||
#include "Server/Server.h"
|
||||
|
||||
namespace clice {
|
||||
|
||||
struct ServerRef::Self {
|
||||
public:
|
||||
Self(Server* server_instance) : server_instance(server_instance) {}
|
||||
|
||||
Server& server() const {
|
||||
return *server_instance;
|
||||
}
|
||||
|
||||
Server* server_instance;
|
||||
};
|
||||
|
||||
} // namespace clice
|
||||
@@ -80,13 +80,26 @@ async::Task<> Indexer::schedule_next() {
|
||||
|
||||
co_await workings[i];
|
||||
workings[i].release().destroy();
|
||||
|
||||
finish_cnt += 1;
|
||||
finish_event.set();
|
||||
}
|
||||
}
|
||||
|
||||
async::Task<> Indexer::index_all() {
|
||||
waitings.clear();
|
||||
workings.clear();
|
||||
update_event.clear();
|
||||
finish_event.clear();
|
||||
finish_cnt = 0;
|
||||
|
||||
for(auto& file: database.files()) {
|
||||
waitings.push_back(project_index.path_pool.path_id(file));
|
||||
}
|
||||
auto to_finish = waitings.size();
|
||||
if(to_finish == 0) {
|
||||
co_return;
|
||||
}
|
||||
|
||||
auto max_count = std::max(std::thread::hardware_concurrency(), 4u);
|
||||
|
||||
@@ -100,6 +113,11 @@ async::Task<> Indexer::index_all() {
|
||||
task.dispose();
|
||||
}
|
||||
|
||||
while(finish_cnt < to_finish) {
|
||||
co_await finish_event;
|
||||
finish_event.unset();
|
||||
}
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "Server/Plugin.h"
|
||||
#include "Server/Server.h"
|
||||
|
||||
#include <llvm/Support/FileSystem.h>
|
||||
@@ -46,6 +47,11 @@ async::Task<json::Value> Server::on_initialize(proto::InitializeParams params) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Run initialize hooks.
|
||||
for(auto& hook: initialize_hooks) {
|
||||
co_await hook();
|
||||
}
|
||||
|
||||
/// Load cache info.
|
||||
load_cache_info();
|
||||
|
||||
@@ -108,18 +114,41 @@ async::Task<json::Value> Server::on_initialize(proto::InitializeParams params) {
|
||||
}
|
||||
|
||||
async::Task<> Server::on_initialized(proto::InitializedParams) {
|
||||
// emit indexing notification
|
||||
co_await notify("clice/willIndex", {});
|
||||
|
||||
/// Run initialized hooks.
|
||||
for(auto& hook: initialized_hooks) {
|
||||
co_await hook();
|
||||
}
|
||||
|
||||
indexer.load_from_disk();
|
||||
co_await indexer.index_all();
|
||||
|
||||
// emit indexed notification
|
||||
co_await notify("clice/didIndex", {});
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
async::Task<json::Value> Server::on_shutdown(proto::ShutdownParams params) {
|
||||
/// Run shutdown hooks.
|
||||
for(auto& hook: shutdown_hooks) {
|
||||
co_await hook();
|
||||
}
|
||||
|
||||
co_return json::Value(nullptr);
|
||||
}
|
||||
|
||||
async::Task<> Server::on_exit(proto::ExitParams params) {
|
||||
/// Run exit hooks.
|
||||
for(auto& hook: exit_hooks) {
|
||||
co_await hook();
|
||||
}
|
||||
|
||||
save_cache_info();
|
||||
indexer.save_to_disk();
|
||||
|
||||
async::stop();
|
||||
co_return;
|
||||
}
|
||||
|
||||
163
src/Server/Plugin.cpp
Normal file
163
src/Server/Plugin.cpp
Normal file
@@ -0,0 +1,163 @@
|
||||
#include "Server/Plugin.h"
|
||||
|
||||
#include "Implement.h"
|
||||
#include "Server/Utility.h"
|
||||
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/Support/DynamicLibrary.h"
|
||||
|
||||
namespace clice {
|
||||
|
||||
Server& ServerRef::server() const {
|
||||
return *self;
|
||||
}
|
||||
|
||||
struct Plugin::Self {
|
||||
/// The file path of the plugin.
|
||||
std::string file_path;
|
||||
/// The dynamic library data of the plugin.
|
||||
llvm::sys::DynamicLibrary library;
|
||||
/// The name of the plugin.
|
||||
std::string name;
|
||||
/// The version of the plugin.
|
||||
std::string version;
|
||||
/// Registers the server callbacks for the loaded plugin.
|
||||
void (*register_server_callbacks)(ServerPluginBuilder& builder);
|
||||
};
|
||||
|
||||
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 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) {
|
||||
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 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.
|
||||
std::string definition_hash = info.definition_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 bail("Empty `register_server_callbacks` function in plugin '{}'.", file_path);
|
||||
}
|
||||
|
||||
P->name = info.name;
|
||||
P->version = info.version;
|
||||
P->register_server_callbacks = info.register_server_callbacks;
|
||||
|
||||
return P;
|
||||
}
|
||||
|
||||
llvm::StringRef Plugin::file_path() const {
|
||||
return self->file_path;
|
||||
}
|
||||
|
||||
llvm::StringRef Plugin::name() const {
|
||||
return self->name;
|
||||
}
|
||||
|
||||
llvm::StringRef Plugin::version() const {
|
||||
return self->version;
|
||||
}
|
||||
|
||||
/// Registers the server callbacks for the loaded plugin.
|
||||
void Plugin::register_server_callbacks(ServerPluginBuilder& builder) const {
|
||||
self->register_server_callbacks(builder);
|
||||
}
|
||||
|
||||
using command_handler_t =
|
||||
async::Task<llvm::json::Value> (*)(ServerRef server,
|
||||
const llvm::ArrayRef<llvm::json::Value>& arguments);
|
||||
|
||||
void ServerPluginBuilder::on_initialize(void* plugin_data, lifecycle_hook_t callback) {
|
||||
auto server = server_ref;
|
||||
server_ref.server().initialize_hooks.push_back(
|
||||
[=]() -> async::Task<> { co_await callback(server, plugin_data); });
|
||||
}
|
||||
|
||||
void ServerPluginBuilder::on_initialized(void* plugin_data, lifecycle_hook_t callback) {
|
||||
auto server = server_ref;
|
||||
server_ref.server().initialized_hooks.push_back(
|
||||
[=]() -> async::Task<> { co_await callback(server, plugin_data); });
|
||||
}
|
||||
|
||||
void ServerPluginBuilder::on_shutdown(void* plugin_data, lifecycle_hook_t callback) {
|
||||
auto server = server_ref;
|
||||
server_ref.server().shutdown_hooks.push_back(
|
||||
[=]() -> async::Task<> { co_await callback(server, plugin_data); });
|
||||
}
|
||||
|
||||
void ServerPluginBuilder::on_exit(void* plugin_data, lifecycle_hook_t callback) {
|
||||
auto server = server_ref;
|
||||
server_ref.server().exit_hooks.push_back(
|
||||
[=]() -> async::Task<> { co_await callback(server, plugin_data); });
|
||||
}
|
||||
|
||||
void ServerPluginBuilder::on_did_change_configuration(void* plugin_data,
|
||||
lifecycle_hook_t callback) {
|
||||
auto server = server_ref;
|
||||
server_ref.server().did_change_configuration_hooks.push_back(
|
||||
[=]() -> async::Task<> { co_await callback(server, plugin_data); });
|
||||
}
|
||||
|
||||
void ServerPluginBuilder::register_commmand_handler(void* plugin_data,
|
||||
llvm::StringRef command,
|
||||
command_handler_t callback) {
|
||||
auto server = server_ref;
|
||||
auto [_, inserted] = server_ref.server().command_handlers.try_emplace(
|
||||
command,
|
||||
[=](llvm::ArrayRef<llvm::json::Value> arguments) -> async::Task<llvm::json::Value> {
|
||||
return callback(server, plugin_data, arguments);
|
||||
});
|
||||
if(!inserted) {
|
||||
LOG_ERROR("Command handler already registered for command '{}'.", command);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace clice
|
||||
@@ -1,11 +1,15 @@
|
||||
#include "Server/Server.h"
|
||||
|
||||
#include "Compiler/CompilationUnit.h"
|
||||
#include "Protocol/Basic.h"
|
||||
#include "Support/FileSystem.h"
|
||||
#include "Support/Logging.h"
|
||||
|
||||
#include <clang/AST/Decl.h>
|
||||
|
||||
namespace clice {
|
||||
|
||||
ActiveFileManager::ActiveFile& ActiveFileManager::lru_put_impl(llvm::StringRef path,
|
||||
OpenFile file) {
|
||||
ActiveFileManager::ActiveFile ActiveFileManager::lru_put_impl(llvm::StringRef path, OpenFile file) {
|
||||
/// If the file is not in the chain, create a new OpenFile.
|
||||
if(items.size() >= capability) {
|
||||
/// If the size exceeds the maximum size, remove the last element.
|
||||
@@ -21,7 +25,7 @@ ActiveFileManager::ActiveFile& ActiveFileManager::lru_put_impl(llvm::StringRef p
|
||||
return items.front().second;
|
||||
}
|
||||
|
||||
ActiveFileManager::ActiveFile& ActiveFileManager::get_or_add(llvm::StringRef path) {
|
||||
ActiveFileManager::ActiveFile ActiveFileManager::get_or_add(llvm::StringRef path) {
|
||||
auto iter = index.find(path);
|
||||
if(iter == index.end()) {
|
||||
return lru_put_impl(path, OpenFile{});
|
||||
@@ -32,7 +36,7 @@ ActiveFileManager::ActiveFile& ActiveFileManager::get_or_add(llvm::StringRef pat
|
||||
return iter->second->second;
|
||||
}
|
||||
|
||||
ActiveFileManager::ActiveFile& ActiveFileManager::add(llvm::StringRef path, OpenFile file) {
|
||||
ActiveFileManager::ActiveFile ActiveFileManager::add(llvm::StringRef path, OpenFile file) {
|
||||
auto iter = index.find(path);
|
||||
if(iter == index.end()) {
|
||||
return lru_put_impl(path, std::move(file));
|
||||
@@ -102,6 +106,8 @@ Server::Server() : indexer(database, config, kind) {
|
||||
register_callback<&Server::on_shutdown>("shutdown");
|
||||
register_callback<&Server::on_exit>("exit");
|
||||
|
||||
register_callback<&Server::on_execute_command>("workspace/executeCommand");
|
||||
|
||||
register_callback<&Server::on_did_open>("textDocument/didOpen");
|
||||
register_callback<&Server::on_did_change>("textDocument/didChange");
|
||||
register_callback<&Server::on_did_save>("textDocument/didSave");
|
||||
@@ -184,4 +190,18 @@ async::Task<> Server::on_receive(json::Value value) {
|
||||
co_return;
|
||||
}
|
||||
|
||||
async::Task<json::Value> Server::on_execute_command(proto::ExecuteCommandParams params) {
|
||||
auto& command = params.command;
|
||||
auto& arguments = params.arguments;
|
||||
|
||||
auto it = command_handlers.find(command);
|
||||
if(it == command_handlers.end()) {
|
||||
LOG_ERROR("Command handler not found for command '{}'.", command);
|
||||
co_return json::Value{};
|
||||
}
|
||||
|
||||
auto& handler = it->second;
|
||||
co_return co_await handler(arguments);
|
||||
}
|
||||
|
||||
} // namespace clice
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "spdlog/sinks/ringbuffer_sink.h"
|
||||
#include "spdlog/sinks/stdout_color_sinks.h"
|
||||
#include "spdlog/sinks/stdout_sinks.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
|
||||
namespace clice::logging {
|
||||
|
||||
@@ -34,26 +35,43 @@ void stderr_logger(std::string_view name, const Options& options) {
|
||||
}
|
||||
|
||||
void file_loggger(std::string_view name, std::string_view dir, const Options& options) {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto filename = std::format("{:%Y-%m-%d_%H-%M-%S}.log", now);
|
||||
auto sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(path::join(dir, filename));
|
||||
|
||||
if(options.replay_console && ringbuffer_sink) {
|
||||
sink->set_level(options.level);
|
||||
sink->set_pattern(pattern);
|
||||
|
||||
for(auto& log: ringbuffer_sink->last_raw()) {
|
||||
sink->log(log);
|
||||
}
|
||||
|
||||
ringbuffer_sink.reset();
|
||||
if(auto err = fs::create_directories(dir)) {
|
||||
spdlog::error("Failed to create log directory {}: {}", std::string(dir), err.message());
|
||||
return;
|
||||
}
|
||||
|
||||
auto logger = std::make_shared<spdlog::logger>(std::string(name), std::move(sink));
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto filename = std::format("{:%Y-%m-%d_%H-%M-%S}.log", now);
|
||||
auto filepath = path::join(dir, filename);
|
||||
|
||||
{
|
||||
std::error_code err;
|
||||
llvm::raw_fd_ostream test(filepath, err, fs::OF_Append);
|
||||
if(err) {
|
||||
spdlog::error("Failed to open log file {}: {}", filepath, err.message());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(filepath);
|
||||
auto console_sink = std::make_shared<spdlog::sinks::stderr_color_sink_mt>(options.color);
|
||||
std::array<spdlog::sink_ptr, 2> sinks = {file_sink, console_sink};
|
||||
auto logger = std::make_shared<spdlog::logger>(std::string(name), sinks.begin(), sinks.end());
|
||||
logger->set_level(options.level);
|
||||
logger->set_pattern(pattern);
|
||||
logger->flush_on(Level::trace);
|
||||
spdlog::set_default_logger(std::move(logger));
|
||||
|
||||
if(options.replay_console && ringbuffer_sink) {
|
||||
file_sink->set_level(options.level);
|
||||
file_sink->set_pattern(pattern);
|
||||
|
||||
for(auto& log: ringbuffer_sink->last_raw()) {
|
||||
file_sink->log(log);
|
||||
}
|
||||
|
||||
ringbuffer_sink.reset();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace clice::logging
|
||||
|
||||
37
src/clice.cc
37
src/clice.cc
@@ -1,3 +1,5 @@
|
||||
#include "Server/Implement.h"
|
||||
#include "Server/Plugin.h"
|
||||
#include "Server/Server.h"
|
||||
#include "Server/Version.h"
|
||||
#include "Support/Format.h"
|
||||
@@ -11,6 +13,12 @@
|
||||
namespace cl = llvm::cl;
|
||||
using namespace clice;
|
||||
|
||||
namespace clice {
|
||||
|
||||
void register_builtin_server_plugins(ServerPluginBuilder& builder);
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
static cl::OptionCategory category("clice options");
|
||||
@@ -73,6 +81,31 @@ cl::opt<logging::Level> log_level{
|
||||
cl::desc("The log level, default is info"),
|
||||
};
|
||||
|
||||
cl::list<std::string> plugin_paths{
|
||||
"plugin-path",
|
||||
cl::cat(category),
|
||||
cl::value_desc("path1,path2,path3,..."),
|
||||
cl::desc("The server plugins to load"),
|
||||
cl::CommaSeparated,
|
||||
};
|
||||
|
||||
/// Loads plugins intermediatly.
|
||||
std::vector<Plugin> load_plugins(Server& instance) {
|
||||
ServerPluginBuilder builder{ServerRef{&instance}};
|
||||
std::vector<Plugin> plugins;
|
||||
for(auto& plugin_path: plugin_paths) {
|
||||
auto plugin_instance = Plugin::load(plugin_path);
|
||||
if(!plugin_instance) {
|
||||
LOG_FATAL("Failed to load plugin {}: {}", plugin_path, plugin_instance.error());
|
||||
}
|
||||
plugin_instance->register_server_callbacks(builder);
|
||||
plugins.push_back(std::move(plugin_instance.value()));
|
||||
}
|
||||
::clice::register_builtin_server_plugins(builder);
|
||||
// The llvm::sys::DynamicLibrary will be unloaded when the program exits.
|
||||
return plugins;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, const char** argv) {
|
||||
@@ -105,6 +138,10 @@ int main(int argc, const char** argv) {
|
||||
|
||||
/// The global server instance.
|
||||
static Server instance;
|
||||
|
||||
/// Loads plugins intermediatly before the server starts.
|
||||
static auto plugins = load_plugins(instance);
|
||||
|
||||
auto loop = [&](json::Value value) -> async::Task<> {
|
||||
co_await instance.on_receive(value);
|
||||
};
|
||||
|
||||
@@ -32,12 +32,12 @@ TEST_CASE(LruAlgorithm) {
|
||||
|
||||
ASSERT_EQ(actives.size(), 0U);
|
||||
|
||||
auto& first = actives.add("first", OpenFile{.version = 1});
|
||||
auto first = actives.add("first", OpenFile{.version = 1});
|
||||
ASSERT_EQ(actives.size(), 1U);
|
||||
ASSERT_TRUE(actives.contains("first"));
|
||||
ASSERT_EQ(first->version, 1U);
|
||||
|
||||
auto& second = actives.add("second", OpenFile{.version = 2});
|
||||
auto second = actives.add("second", OpenFile{.version = 2});
|
||||
ASSERT_EQ(actives.size(), 1U);
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ TEST_CASE(IteratorCheck) {
|
||||
std::string fpath = std::format("{}", i);
|
||||
OpenFile object{.version = i};
|
||||
|
||||
auto& inseted = manager.add(fpath, std::move(object));
|
||||
auto inseted = manager.add(fpath, std::move(object));
|
||||
std::optional new_added_entry = manager.get_or_add(fpath);
|
||||
ASSERT_TRUE(new_added_entry.has_value());
|
||||
auto new_added = std::move(new_added_entry).value();
|
||||
|
||||
Reference in New Issue
Block a user