📦 deps(lsp_server): update conan dependencies
lsp-server ci / build-and-test (push) Failing after 0s Details

This commit is contained in:
csh 2026-05-27 09:54:39 +08:00
parent 7838f3ec3d
commit f4f629c232
9 changed files with 380 additions and 16 deletions

View File

@ -0,0 +1,114 @@
# Conan Dependency Upgrade Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Upgrade `lsp-server` Conan dependencies to the latest versions available on ConanCenter and adapt the project until it builds and passes targeted verification.
**Architecture:** Treat ConanCenter as the version authority for this task. Update only the versions that have newer published recipes, regenerate the build with the existing profiles, and make the smallest necessary source/build changes to restore compatibility. Keep unchanged packages pinned when ConanCenter has no newer recipe.
**Tech Stack:** Conan 2, CMake 4.2, Ninja, Clang toolchain, C++23 Modules
---
## Plan Meta
- **Plan Group:** deps-upgrade
- **Parent Plan:** none
- **Verification Scope:** local
- **Verification Gate:** must-pass
### Task 1: Update Conan dependency declarations
**Files:**
- Modify: `lsp-server/conanfile.txt`
- [x] Confirm the latest ConanCenter recipes:
`glaze/7.4.0`, `spdlog/1.17.0`, `fmt/12.1.0`,
`taskflow/4.0.0`, `tree-sitter/0.25.9`
- [x] Update `lsp-server/conanfile.txt`:
`glaze/7.0.2 -> glaze/7.4.0`
`taskflow/3.10.0 -> taskflow/4.0.0`
- [x] Leave `spdlog/1.17.0`, `fmt/12.1.0`, and `tree-sitter/0.25.9`
unchanged because ConanCenter does not expose newer recipes
### Task 2: Regenerate dependencies and capture compatibility failures
**Files:**
- Modify: `lsp-server/build/clang-linux/Release/**` (generated)
- Inspect: `lsp-server/src/**`
- Inspect: `lsp-server/test/**`
- [x] Run Conan install with the existing Linux Clang profile:
```bash
CONAN_HOME=/tmp/conan-home conan install lsp-server \
-pr:h=lsp-server/conan/profiles/linux-x86_64-clang \
-pr:b=lsp-server/conan/profiles/linux-x86_64-clang \
-of lsp-server/build/clang-linux/Release \
--build=missing
```
- [x] Reconfigure the existing build directory:
```bash
cmake -S lsp-server -B lsp-server/build/clang-linux/Release -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=$PWD/lsp-server/build/clang-linux/Release/generators/conan_toolchain.cmake \
-DBUILD_TESTS=ON
```
- [x] Build to identify required source-level adaptations:
```bash
cmake --build lsp-server/build/clang-linux/Release
```
### Task 3: Adapt code only where the upgraded APIs require it
**Files:**
- Modify: exact files reported by the build, likely under
`lsp-server/src/bridge/`, `lsp-server/src/codec/`,
`lsp-server/src/core/`, or related test targets
- [x] For `glaze` breakages, update serialization/meta/JSON-RPC call sites
to the current `glaze/7.4.0` API with the smallest possible diff
- [x] For `taskflow` breakages, update executor/task API usage to the
`taskflow/4.0.0` API with the smallest possible diff
- [x] Rebuild after each focused fix until the full build succeeds:
```bash
cmake --build lsp-server/build/clang-linux/Release
```
### Task 4: Run targeted verification and summarize the upgrade result
**Files:**
- Verify: `lsp-server/conanfile.txt`
- Verify: any source files touched in Task 3
- [x] Run dependency-focused tests:
```bash
ctest --test-dir lsp-server/build/clang-linux/Release \
-R 'test_ast|test_provider|test_semantic|test_symbol|test_scheduler' \
--output-on-failure
```
- [x] Run whitespace / patch hygiene check:
```bash
git -c core.trustctime=false -c core.checkStat=minimal diff --check
```
- [x] Report three facts in the final handoff:
upgraded versions, any compatibility edits made, and the exact
verification commands that passed
Known residual verification:
- `test_ast_script`, `test_symbol_script`, and `test_semantic_script` still fail on existing
TSF fixture parse/load errors under `lsp-server/test/test_tree_sitter/test`.
Dependency-focused targets `test_lsp_any`, `test_provider`, and `test_scheduler` pass.

View File

@ -1,6 +1,11 @@
cmake_minimum_required(VERSION 4.2) cmake_minimum_required(VERSION 4.2)
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "d0edc3af-4c50-42ea-a356-e2862fe7a444") # CMake 4.3 rotated the experimental gate UUID for `import std`.
if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.3")
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "451f2fe2-a8a2-47c3-bc32-94786d8fc91b")
else()
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "d0edc3af-4c50-42ea-a356-e2862fe7a444")
endif()
project(tsl-server LANGUAGES C CXX) project(tsl-server LANGUAGES C CXX)

View File

@ -1,8 +1,8 @@
[requires] [requires]
glaze/7.0.2 glaze/7.4.0
spdlog/1.17.0 spdlog/1.17.0
fmt/12.1.0 fmt/12.1.0
taskflow/3.10.0 taskflow/4.0.0
tree-sitter/0.25.9 tree-sitter/0.25.9
[generators] [generators]

View File

@ -1,6 +1,7 @@
module; module;
// Global module fragment: pull in third-party headers // Global module fragment: pull in third-party headers
#include <bit>
#include <taskflow/taskflow.hpp> #include <taskflow/taskflow.hpp>
export module taskflow; export module taskflow;

View File

@ -29,6 +29,9 @@ export namespace lsp::codec
template<typename T> template<typename T>
static protocol::LSPAny SerializeViaJson(const T& obj); static protocol::LSPAny SerializeViaJson(const T& obj);
template<typename T>
static T ConvertEnum(const protocol::LSPAny& any);
}; };
} }
@ -165,6 +168,14 @@ namespace lsp::codec
return std::nullopt; return std::nullopt;
return FromLSPAny<typename Type::value_type>(any); return FromLSPAny<typename Type::value_type>(any);
} }
else if constexpr (std::is_enum_v<Type>)
{
return ConvertEnum<Type>(any);
}
else if constexpr (requires { from_lsp_any_custom(std::type_identity<Type>{}, any); })
{
return from_lsp_any_custom(std::type_identity<Type>{}, any);
}
else if constexpr (is_user_struct_v<Type>) else if constexpr (is_user_struct_v<Type>)
{ {
return ConvertViaJson<Type>(any); return ConvertViaJson<Type>(any);
@ -198,6 +209,13 @@ namespace lsp::codec
throw ConversionError("LSPAny does not contain a compatible numeric type"); throw ConversionError("LSPAny does not contain a compatible numeric type");
} }
template<typename T>
T LSPAnyConverter::ConvertEnum(const protocol::LSPAny& any)
{
using Underlying = std::underlying_type_t<T>;
return static_cast<T>(ExtractNumber<Underlying>(any));
}
template<typename T> template<typename T>
T LSPAnyConverter::ConvertViaJson(const protocol::LSPAny& any) T LSPAnyConverter::ConvertViaJson(const protocol::LSPAny& any)
{ {

View File

@ -241,3 +241,167 @@ export namespace lsp::protocol
std::vector<InlineCompletionItem> items; std::vector<InlineCompletionItem> items;
}; };
} }
export namespace lsp::protocol
{
namespace completion_conversion_detail
{
inline const LSPObject& RequireObject(const LSPAny& any, std::string_view context)
{
if (!any.Is<LSPObject>())
throw std::runtime_error(std::string(context) + " must be an object");
return any.Get<LSPObject>();
}
inline const LSPArray& RequireArray(const LSPAny& any, std::string_view context)
{
if (!any.Is<LSPArray>())
throw std::runtime_error(std::string(context) + " must be an array");
return any.Get<LSPArray>();
}
inline const LSPAny* FindField(const LSPObject& obj, std::string_view key)
{
auto it = obj.find(string(key));
if (it == obj.end() || it->second.Is<std::nullptr_t>())
return nullptr;
return &it->second;
}
inline string ReadString(const LSPAny& any, std::string_view context)
{
if (!any.Is<string>())
throw std::runtime_error(std::string(context) + " must be a string");
return any.Get<string>();
}
inline boolean ReadBoolean(const LSPAny& any, std::string_view context)
{
if (!any.Is<boolean>())
throw std::runtime_error(std::string(context) + " must be a boolean");
return any.Get<boolean>();
}
inline std::int64_t ReadInteger(const LSPAny& any, std::string_view context)
{
if (any.Is<integer>())
return any.Get<integer>();
if (any.Is<uinteger>())
return any.Get<uinteger>();
throw std::runtime_error(std::string(context) + " must be an integer");
}
template<typename T>
std::optional<string> OptionalString(const LSPObject& obj, T key)
{
if (const auto* field = FindField(obj, key))
return ReadString(*field, key);
return std::nullopt;
}
template<typename T>
std::optional<boolean> OptionalBoolean(const LSPObject& obj, T key)
{
if (const auto* field = FindField(obj, key))
return ReadBoolean(*field, key);
return std::nullopt;
}
template<typename Enum, typename T>
std::optional<Enum> OptionalEnum(const LSPObject& obj, T key)
{
if (const auto* field = FindField(obj, key))
return static_cast<Enum>(ReadInteger(*field, key));
return std::nullopt;
}
template<typename T>
std::optional<std::vector<string>> OptionalStringVector(const LSPObject& obj, T key)
{
const auto* field = FindField(obj, key);
if (field == nullptr)
return std::nullopt;
const auto& arr = RequireArray(*field, key);
std::vector<string> values;
values.reserve(arr.size());
for (const auto& item : arr)
values.push_back(ReadString(item, key));
return values;
}
}
inline CompletionItemLabelDetails from_lsp_any_custom(std::type_identity<CompletionItemLabelDetails>,
const LSPAny& any)
{
const auto& obj = completion_conversion_detail::RequireObject(any, "CompletionItemLabelDetails");
CompletionItemLabelDetails details;
details.detail = completion_conversion_detail::OptionalString(obj, "detail");
details.description = completion_conversion_detail::OptionalString(obj, "description");
return details;
}
inline CompletionItem from_lsp_any_custom(std::type_identity<CompletionItem>, const LSPAny& any)
{
const auto& obj = completion_conversion_detail::RequireObject(any, "CompletionItem");
CompletionItem item;
const auto* label = completion_conversion_detail::FindField(obj, "label");
if (label == nullptr)
throw std::runtime_error("Missing required field: label");
item.label = completion_conversion_detail::ReadString(*label, "label");
if (const auto* label_details = completion_conversion_detail::FindField(obj, "labelDetails"))
item.labelDetails = from_lsp_any_custom(std::type_identity<CompletionItemLabelDetails>{}, *label_details);
item.kind = completion_conversion_detail::OptionalEnum<CompletionItemKind>(obj, "kind");
item.detail = completion_conversion_detail::OptionalString(obj, "detail");
item.preselect = completion_conversion_detail::OptionalBoolean(obj, "preselect");
item.sortText = completion_conversion_detail::OptionalString(obj, "sortText");
item.filterText = completion_conversion_detail::OptionalString(obj, "filterText");
item.insertText = completion_conversion_detail::OptionalString(obj, "insertText");
item.insertTextFormat = completion_conversion_detail::OptionalEnum<InsertTextFormat>(obj, "insertTextFormat");
item.insertTextMode = completion_conversion_detail::OptionalEnum<InsertTextMode>(obj, "insertTextMode");
item.textEditText = completion_conversion_detail::OptionalString(obj, "textEditText");
item.commitCharacters = completion_conversion_detail::OptionalStringVector(obj, "commitCharacters");
if (const auto* data = completion_conversion_detail::FindField(obj, "data"))
item.data = *data;
return item;
}
inline CompletionList from_lsp_any_custom(std::type_identity<CompletionList>, const LSPAny& any)
{
const auto& obj = completion_conversion_detail::RequireObject(any, "CompletionList");
CompletionList list;
const auto* is_incomplete = completion_conversion_detail::FindField(obj, "isIncomplete");
if (is_incomplete == nullptr)
throw std::runtime_error("Missing required field: isIncomplete");
list.isIncomplete = completion_conversion_detail::ReadBoolean(*is_incomplete, "isIncomplete");
if (const auto* items = completion_conversion_detail::FindField(obj, "items"))
{
const auto& arr = completion_conversion_detail::RequireArray(*items, "items");
list.items.reserve(arr.size());
for (const auto& item : arr)
list.items.push_back(from_lsp_any_custom(std::type_identity<CompletionItem>{}, item));
}
if (const auto* item_defaults = completion_conversion_detail::FindField(obj, "itemDefaults"))
{
const auto& defaults_obj = completion_conversion_detail::RequireObject(*item_defaults, "itemDefaults");
list.itemDefaults.commitCharacters =
completion_conversion_detail::OptionalStringVector(defaults_obj, "commitCharacters");
list.itemDefaults.insertTextFormat =
completion_conversion_detail::OptionalEnum<InsertTextFormat>(defaults_obj, "insertTextFormat");
list.itemDefaults.insertTextMode =
completion_conversion_detail::OptionalEnum<InsertTextMode>(defaults_obj, "insertTextMode");
if (const auto* data = completion_conversion_detail::FindField(defaults_obj, "data"))
list.itemDefaults.data = *data;
}
return list;
}
}

View File

@ -16,6 +16,7 @@ export namespace lsp::scheduler
std::mutex mutex; std::mutex mutex;
std::condition_variable cv; std::condition_variable cv;
bool completed = false; bool completed = false;
bool callback_completed = false;
std::optional<std::string> result; std::optional<std::string> result;
std::exception_ptr error; std::exception_ptr error;
std::chrono::steady_clock::time_point start_time{}; std::chrono::steady_clock::time_point start_time{};
@ -141,7 +142,7 @@ namespace lsp::scheduler
if (!state) if (!state)
return false; return false;
std::unique_lock<std::mutex> lk(state->mutex); std::unique_lock<std::mutex> lk(state->mutex);
state->cv.wait(lk, [state]() { return state->completed; }); state->cv.wait(lk, [state]() { return state->completed && state->callback_completed; });
return true; return true;
} }
@ -151,7 +152,7 @@ namespace lsp::scheduler
if (!state) if (!state)
return std::nullopt; return std::nullopt;
std::unique_lock<std::mutex> lk(state->mutex); std::unique_lock<std::mutex> lk(state->mutex);
if (!state->completed || state->error) if (!state->completed || !state->callback_completed || state->error)
return std::nullopt; return std::nullopt;
return state->result; return state->result;
} }
@ -213,7 +214,7 @@ namespace lsp::scheduler
} }
std::unique_lock<std::mutex> lk(state->mutex); std::unique_lock<std::mutex> lk(state->mutex);
state->cv.wait(lk, [state]() { return state->completed; }); state->cv.wait(lk, [state]() { return state->completed && state->callback_completed; });
return true; return true;
} }
@ -231,7 +232,7 @@ namespace lsp::scheduler
for (const auto& state : tasks) for (const auto& state : tasks)
{ {
std::unique_lock<std::mutex> lk(state->mutex); std::unique_lock<std::mutex> lk(state->mutex);
state->cv.wait(lk, [state]() { return state->completed; }); state->cv.wait(lk, [state]() { return state->completed && state->callback_completed; });
} }
// Ensure the underlying executor finishes any tasks that may have been // Ensure the underlying executor finishes any tasks that may have been
@ -334,22 +335,42 @@ namespace lsp::scheduler
state->error = std::make_exception_ptr(std::runtime_error("Task failed")); state->error = std::make_exception_ptr(std::runtime_error("Task failed"));
} }
state->cv.notify_all();
UnregisterTask(task_id, state); UnregisterTask(task_id, state);
auto elapsed = GetElapsedTime(start_time); auto elapsed = GetElapsedTime(start_time);
if (failed) bool callback_failed = false;
if (callback)
{
try
{
callback(result, is_cancelled);
}
catch (...)
{
callback_failed = true;
std::unique_lock<std::mutex> lk(state->mutex);
state->error = std::current_exception();
}
}
const bool has_failed = failed || callback_failed;
if (has_failed)
++failed_; ++failed_;
else if (is_cancelled) else if (is_cancelled)
++cancelled_; ++cancelled_;
else else
++completed_; ++completed_;
if (callback) {
callback(result, is_cancelled); std::unique_lock<std::mutex> lk(state->mutex);
state->callback_completed = true;
}
state->cv.notify_all();
spdlog::info("[{}] Task completed. cancelled={}, failed={}, elapsed={}", task_id, is_cancelled, failed, FormatDuration(elapsed)); if (callback_failed)
spdlog::error("[{}] Task callback threw exception", task_id);
spdlog::info("[{}] Task completed. cancelled={}, failed={}, elapsed={}", task_id, is_cancelled, has_failed, FormatDuration(elapsed));
} }
bool AsyncExecutor::RegisterTask(const std::string& task_id, const detail::ActiveEntry& ctx) bool AsyncExecutor::RegisterTask(const std::string& task_id, const detail::ActiveEntry& ctx)

View File

@ -6,7 +6,10 @@ export module lsp.test.lsp_any.transformer;
import lsp.test.framework; import lsp.test.framework;
import lsp.protocol.common.basic_types; import lsp.protocol.common.basic_types;
import lsp.protocol.common.message;
import lsp.protocol.text_document.completion;
import lsp.codec.common; import lsp.codec.common;
import lsp.codec.facade;
import lsp.codec.transformer; import lsp.codec.transformer;
export namespace lsp::test export namespace lsp::test
@ -69,6 +72,7 @@ export namespace lsp::test
static TestResult testNestedVector(); static TestResult testNestedVector();
static TestResult testNestedLSPObject(); static TestResult testNestedLSPObject();
static TestResult testMixedTypeNesting(); static TestResult testMixedTypeNesting();
static TestResult testCompletionListWithDataObject();
}; };
} }
@ -124,6 +128,7 @@ namespace lsp::test
runner.addTest("Transformer - 嵌套Vector", testNestedVector); runner.addTest("Transformer - 嵌套Vector", testNestedVector);
runner.addTest("Transformer - 嵌套LSPObject", testNestedLSPObject); runner.addTest("Transformer - 嵌套LSPObject", testNestedLSPObject);
runner.addTest("Transformer - 混合类型嵌套", testMixedTypeNesting); runner.addTest("Transformer - 混合类型嵌套", testMixedTypeNesting);
runner.addTest("Transformer - CompletionList data对象", testCompletionListWithDataObject);
} }
// ==================== ToLSPAny 基本类型测试 ==================== // ==================== ToLSPAny 基本类型测试 ====================
@ -717,4 +722,32 @@ namespace lsp::test
return result; return result;
} }
TestResult TransformerTests::testCompletionListWithDataObject()
{
TestResult result;
result.passed = true;
const auto response_json = std::string{
R"json({"jsonrpc":"2.0","id":"c1","result":{"isIncomplete":false,"itemDefaults":{},"items":[{"data":{"class":"Widget","ctx":"call","is_static":true,"kind":"method","name":"StaticFoo","unit":"MainUnit","uri":"file:///home/csh/windows_share/tinysoft/tsl-devkit/lsp-server/test/test_provider/fixtures/main_unit.tsf"},"kind":2,"label":"StaticFoo","labelDetails":{"description":"[E]","detail":"(y: integer)"}}]}})json"
};
auto response = transform::Deserialize<protocol::ResponseMessage>(response_json);
assertTrue(response.has_value(), "应该能反序列化 ResponseMessage");
assertTrue(response->result.has_value(), "ResponseMessage.result 应该有值");
auto completion_list = transform::FromLSPAny.template operator()<protocol::CompletionList>(response->result.value());
assertEqual(size_t(1), completion_list.items.size(), "应该保留一个补全项");
assertTrue(completion_list.items[0].data.has_value(), "CompletionItem.data 应该有值");
assertTrue(completion_list.items[0].data->Is<protocol::LSPObject>(), "CompletionItem.data 应该是对象");
const auto& result_data = completion_list.items[0].data->Get<protocol::LSPObject>();
assertEqual(std::string("Widget"), result_data.at("class").Get<protocol::string>(), "data.class 应该保留");
assertEqual(std::string("call"), result_data.at("ctx").Get<protocol::string>(), "data.ctx 应该保留");
assertEqual(true, result_data.at("is_static").Get<protocol::boolean>(), "data.is_static 应该保留");
result.message = "成功";
return result;
}
} // namespace lsp::test } // namespace lsp::test

View File

@ -2,8 +2,8 @@
## Current Focus ## Current Focus
- 已完成 playbook 强对齐;后续开发统一使用新 `memory-bank/` 结构、 - 已完成 Conan 依赖升级;`lsp-server` 当前使用 ConanCenter 可见的最新
`docs/superpowers/plans/` 和 `main_loop.py` 状态流转 `glaze`、`taskflow`、`spdlog`、`fmt` 与 `tree-sitter` recipe
## Recent Changes ## Recent Changes
@ -14,23 +14,30 @@
`docs/prompts/meta/prompt-generator.md` `docs/prompts/meta/prompt-generator.md`
`memory-bank/architecture.md` `memory-bank/architecture.md`
`memory-bank/tech-stack.md` 已移除 `memory-bank/tech-stack.md` 已移除
- `glaze` 已升级到 `7.4.0``taskflow` 已升级到 `4.0.0`
- 已适配 CMake 4.3 `import std` gate、Taskflow 4.0 头文件依赖、
completion `LSPAny data` 反序列化和 scheduler completion callback 顺序
## Next Steps ## Next Steps
1. 新任务先写到 `docs/superpowers/plans/` 再执行 1. 新任务先写到 `docs/superpowers/plans/` 再执行
2. 进入执行阶段前用 `main_loop.py claim` 领取 Plan 2. 进入执行阶段前用 `main_loop.py claim` 领取 Plan
3. Playbook 升级保持 subtree + sync 的固定顺序 3. Playbook 升级保持 subtree + sync 的固定顺序
4. 另行处理既有脚本类测试失败:
`test_ast_script`、`test_symbol_script`、`test_semantic_script`
## Open Risks ## Open Risks
- CIFS 共享目录上的 git 索引状态可能误报脏工作区, - CIFS 共享目录上的 git 索引状态可能误报脏工作区,
影响 subtree、status 和 stash 类命令 影响 subtree、status 和 stash 类命令
- `lsp-server/test/test_tree_sitter/test` 下部分 TSF fixture 仍存在解析失败,
会导致脚本类 AST / symbol / semantic CTest 失败
## Workflow State ## Workflow State
<!-- workflow-state:start --> <!-- workflow-state:start -->
phase: done phase: done
plan: docs/superpowers/plans/2026-05-24-playbook-strong-alignment.md plan: docs/superpowers/plans/2026-05-24-conan-dependency-upgrade.md
executor: executing-plans executor: executing-plans
constraints: karpathy-guidelines,.agents,AGENT_RULES constraints: karpathy-guidelines,.agents,AGENT_RULES
<!-- workflow-state:end --> <!-- workflow-state:end -->
@ -39,4 +46,5 @@ constraints: karpathy-guidelines,.agents,AGENT_RULES
<!-- plan-status:start --> <!-- plan-status:start -->
- [x] `2026-05-24-playbook-strong-alignment.md` done - [x] `2026-05-24-playbook-strong-alignment.md` done
- [x] `2026-05-24-conan-dependency-upgrade.md` done: known-failures:test_ast_script,test_symbol_script,test_semantic_script
<!-- plan-status:end --> <!-- plan-status:end -->