From eed8ef91d62fc19c33c9d8891a5881d5c3c7d3ab Mon Sep 17 00:00:00 2001 From: Guo-Shiyu Date: Mon, 25 Aug 2025 15:18:54 +0800 Subject: [PATCH 1/8] Feat: better load and fetch compile command. --- include/Compiler/Command.h | 8 +++- include/Protocol/Lifecycle.h | 2 +- src/Compiler/Command.cpp | 49 ++++++++++++++++------ src/Server/Document.cpp | 9 ++-- src/Server/Lifecycle.cpp | 81 ++++++++++++++++++++++++++++++++---- 5 files changed, 120 insertions(+), 29 deletions(-) diff --git a/include/Compiler/Command.h b/include/Compiler/Command.h index 990a99bd..87c66d07 100644 --- a/include/Compiler/Command.h +++ b/include/Compiler/Command.h @@ -112,6 +112,10 @@ class CompilationDatabase { auto get_command(this Self& self, llvm::StringRef file, CommandOptions options = {}) -> LookupInfo; +private: + /// If file not found in CDB file, try to guess commands or use the default case. + auto guess_or_fallback(this Self& self, llvm::StringRef file) -> LookupInfo; + private: /// The memory pool to hold all cstring and command list. llvm::BumpPtrAllocator allocator; @@ -128,10 +132,10 @@ class CompilationDatabase { llvm::DenseSet filtered_options; /// A map between file path and its canonical command list. - llvm::DenseMap command_infos; + llvm::DenseMap command_infos; /// A map between driver path and its query driver info. - llvm::DenseMap driver_infos; + llvm::DenseMap driver_infos; }; } // namespace clice diff --git a/include/Protocol/Lifecycle.h b/include/Protocol/Lifecycle.h index 9c804ec3..bb48f561 100644 --- a/include/Protocol/Lifecycle.h +++ b/include/Protocol/Lifecycle.h @@ -14,7 +14,7 @@ struct LSPInfo { std::string name; /// The version of server or client. - std::string verion; + std::string version; }; struct WindowCapacities {}; diff --git a/src/Compiler/Command.cpp b/src/Compiler/Command.cpp index 7aadeeb7..eb9680da 100644 --- a/src/Compiler/Command.cpp +++ b/src/Compiler/Command.cpp @@ -148,6 +148,7 @@ auto CompilationDatabase::query_driver(this Self& self, llvm::StringRef driver) bool keep_output_file = true; auto clean_up = llvm::make_scope_exit([&output_path, &keep_output_file]() { if(keep_output_file) { + log::warn("Query driver failed, output file:{}", output_path); return; } @@ -398,11 +399,11 @@ auto CompilationDatabase::load_commands(this Self& self, llvm::StringRef json_co auto json = json::parse(json_content); if(!json) { - return std::unexpected(std::format("Fail to parse json: {}", json.takeError())); + return std::unexpected(std::format("parse json failed: {}", json.takeError())); } if(json->kind() != json::Value::Array) { - return std::unexpected("Compilation Database must be an array of object"); + return std::unexpected("compile_commands.json must be an array of object"); } /// FIXME: warn illegal item. @@ -457,29 +458,24 @@ auto CompilationDatabase::get_command(this Self& self, llvm::StringRef file, Com info.dictionary = it->second.dictionary; info.arguments = it->second.arguments; } else { - /// FIXME: Use a better way to handle fallback command. - info.dictionary = {}; - info.arguments = { - self.save_string("clang++").data(), - self.save_string("-std=c++20").data(), - }; + info = self.guess_or_fallback(file); } - auto append_argument = [&](llvm::StringRef argument) { + auto record = [&info, &self](llvm::StringRef argument) { info.arguments.emplace_back(self.save_string(argument).data()); }; if(options.query_driver) { llvm::StringRef driver = info.arguments[0]; if(auto driver_info = self.query_driver(driver)) { - append_argument("-nostdlibinc"); + record("-nostdlibinc"); /// FIXME: Use target information here, this is useful for cross compilation. /// FIXME: Cache -I so that we can append directly, avoid duplicate lookup. for(auto& system_header: driver_info->system_includes) { - append_argument("-I"); - append_argument(system_header); + record("-I"); + record(system_header); } } else if(!options.suppress_log) { log::warn("Failed to query driver:{}, error:{}", driver, driver_info.error()); @@ -487,10 +483,37 @@ auto CompilationDatabase::get_command(this Self& self, llvm::StringRef file, Com } if(options.resource_dir) { - append_argument(std::format("-resource-dir={}", fs::resource_dir)); + record(std::format("-resource-dir={}", fs::resource_dir)); } info.arguments.emplace_back(file.data()); + /// TODO: apply rules in clice.toml. + return info; +} + +auto CompilationDatabase::guess_or_fallback(this Self& self, llvm::StringRef file) -> LookupInfo { + // Try to guess command from other file in same directory or parent directory + llvm::StringRef dir = path::parent_path(file); + while(!dir.empty()) { + for(const auto& [other_file, info]: self.command_infos) { + if(llvm::StringRef other = other_file; !other.starts_with(dir)) { + continue; + } + log::info("Guess command for:{}, from existed file: {}", file, other_file); + return LookupInfo{info.dictionary, info.arguments}; + } + dir = path::parent_path(dir); + } + + /// FIXME: use a better default case. + // Fallback to default case. + LookupInfo info; + info.dictionary = {}; + info.arguments = { + self.save_string("clang++").data(), + self.save_string("-std=c++20").data(), + }; + return info; } diff --git a/src/Server/Document.cpp b/src/Server/Document.cpp index f37713e1..b0821e23 100644 --- a/src/Server/Document.cpp +++ b/src/Server/Document.cpp @@ -194,8 +194,6 @@ async::Task build_pch_task(CompilationDatabase::LookupInfo& info, params.diagnostics = diagnostics; params.add_remapped_file(path, content, bound); - PCHInfo pch; - std::string command; for(auto argument: params.arguments) { command += " "; @@ -203,13 +201,14 @@ async::Task build_pch_task(CompilationDatabase::LookupInfo& info, } log::info("Start building PCH for {}, command: [{}]", path, command); + command.clear(); - std::string message; + PCHInfo pch; + std::string message = std::move(command); // reuse buffer std::vector links; bool success = co_await async::submit([¶ms, &pch, &message, &links] -> bool { - /// PCH file is written until destructing, Add a single block - /// for it. + /// PCH file is written until destructing, Add a single block for it. auto unit = compile(params, pch); if(!unit) { message = std::move(unit.error()); diff --git a/src/Server/Lifecycle.cpp b/src/Server/Lifecycle.cpp index 8635f8f5..5683a1ab 100644 --- a/src/Server/Lifecycle.cpp +++ b/src/Server/Lifecycle.cpp @@ -1,11 +1,81 @@ #include "Server/Server.h" +#include + namespace clice { +namespace { + +namespace stdfs = std::filesystem; + +/// Load compile commands from given directories. If no valid commands are found, +/// search recursively from the workspace directory. +void load_compile_commands(CompilationDatabase& database, + const std::vector& compile_commands_dirs, + const std::string& workspace) { + auto try_load = [&database](const std::string& dir) { + std::string filepath = dir + "/compile_commands.json"; + auto content = fs::read(filepath); + if(!content) { + log::warn("Failed to read CDB file: {}, {}", filepath, content.error()); + return false; + } + + auto load = database.load_commands(*content); + if(!load) { + log::warn("Failed to load CDB file: {}. {}", filepath, load.error()); + return false; + } + + log::info("Load CDB file: {} successfully, {} items loaded", filepath, load->size()); + return true; + }; + + if(std::ranges::any_of(compile_commands_dirs, try_load)) { + return; + } + + log::info( + "Can not found any valid CDB file from given directories, search recursively from workspace: {} ...", + workspace); + + auto recursive_search = [&try_load](this auto&& self, const stdfs::path& dir) -> bool { + if(!stdfs::exists(dir) || !stdfs::is_directory(dir)) { + return false; + } + + // Skip hidden directories. + if(dir.filename().string().starts_with('.')) { + return false; + } + + if(try_load(dir)) { + return true; + } + + for(const auto& entry: stdfs::directory_iterator(dir)) { + if(self(entry.path())) { + return true; + } + } + + return false; + }; + + if(recursive_search(workspace)) { + return; + } + + /// TODO: Add a default command in clice.toml. Or load commands from .clangd ? + log::warn("Can not found any valid CDB file in current workspace, fallback to default mode."); +} + +} // namespace + async::Task Server::on_initialize(proto::InitializeParams params) { log::info("Initialize from client: {}, version: {}", params.clientInfo.name, - params.clientInfo.verion); + params.clientInfo.version); if(params.workspaceFolders.empty()) { log::fatal("The client should provide one workspace folder at least!"); @@ -22,12 +92,7 @@ async::Task Server::on_initialize(proto::InitializeParams params) { opening_files.set_capability(config::server.max_active_file); /// Load compile commands.json - for(auto& dir: config::server.compile_commands_dirs) { - auto content = fs::read(dir + "/compile_commands.json"); - if(content) { - auto updated = database.load_commands(*content); - } - } + load_compile_commands(database, config::server.compile_commands_dirs, workspace); /// Load cache info. load_cache_info(); @@ -35,7 +100,7 @@ async::Task Server::on_initialize(proto::InitializeParams params) { proto::InitializeResult result; auto& [info, capabilities] = result; info.name = "clice"; - info.verion = "0.0.1"; + info.version = "0.0.1"; capabilities.positionEncoding = "utf-16"; From 3d07ffece7dcf4f4330bb4eae8bdd89a3a1d0804 Mon Sep 17 00:00:00 2001 From: Guo-Shiyu Date: Mon, 25 Aug 2025 15:52:36 +0800 Subject: [PATCH 2/8] Build: fix build on MSVC. --- src/Server/Lifecycle.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Server/Lifecycle.cpp b/src/Server/Lifecycle.cpp index 5683a1ab..5c3131dd 100644 --- a/src/Server/Lifecycle.cpp +++ b/src/Server/Lifecycle.cpp @@ -49,7 +49,7 @@ void load_compile_commands(CompilationDatabase& database, return false; } - if(try_load(dir)) { + if(try_load(dir.string())) { return true; } From 7ed0c8fcd40ec228943e8a644fa8ffadba93984c Mon Sep 17 00:00:00 2001 From: Guo-Shiyu Date: Tue, 26 Aug 2025 15:22:37 +0800 Subject: [PATCH 3/8] Fix: fix by gemini suggestions. --- src/Compiler/Command.cpp | 24 +++++++++++++----------- src/Server/Lifecycle.cpp | 8 +++++++- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/Compiler/Command.cpp b/src/Compiler/Command.cpp index eb9680da..7866fb31 100644 --- a/src/Compiler/Command.cpp +++ b/src/Compiler/Command.cpp @@ -494,26 +494,28 @@ auto CompilationDatabase::get_command(this Self& self, llvm::StringRef file, Com auto CompilationDatabase::guess_or_fallback(this Self& self, llvm::StringRef file) -> LookupInfo { // Try to guess command from other file in same directory or parent directory llvm::StringRef dir = path::parent_path(file); - while(!dir.empty()) { + + // Search up to 3 levels of parent directories + int up_level = 0; + while(!dir.empty() && up_level < 3) { + // If any file in the directory has a command, use that command for(const auto& [other_file, info]: self.command_infos) { - if(llvm::StringRef other = other_file; !other.starts_with(dir)) { - continue; + if(llvm::StringRef other = other_file; other.starts_with(dir)) { + log::info("Guess command for:{}, from existed file: {}", file, other_file); + return LookupInfo{info.dictionary, info.arguments}; } - log::info("Guess command for:{}, from existed file: {}", file, other_file); - return LookupInfo{info.dictionary, info.arguments}; } dir = path::parent_path(dir); + up_level += 1; } /// FIXME: use a better default case. // Fallback to default case. LookupInfo info; - info.dictionary = {}; - info.arguments = { - self.save_string("clang++").data(), - self.save_string("-std=c++20").data(), - }; - + constexpr const char* fallback[] = {"clang++", "-std=c++20"}; + for(const char* arg: fallback) { + info.arguments.emplace_back(self.save_string(arg).data()); + } return info; } diff --git a/src/Server/Lifecycle.cpp b/src/Server/Lifecycle.cpp index 5c3131dd..ec93b60c 100644 --- a/src/Server/Lifecycle.cpp +++ b/src/Server/Lifecycle.cpp @@ -53,7 +53,13 @@ void load_compile_commands(CompilationDatabase& database, return true; } - for(const auto& entry: stdfs::directory_iterator(dir)) { + std::error_code ec; + for(const auto& entry: stdfs::directory_iterator(dir, ec)) { + if(ec) { + log::warn("Error iterating directory: {}, {}, skipped", dir.string(), ec.message()); + continue; + } + if(self(entry.path())) { return true; } From 4c293ba9fd1d7c16830036a772be309f6a3f784a Mon Sep 17 00:00:00 2001 From: Guo-Shiyu Date: Fri, 29 Aug 2025 14:46:55 +0800 Subject: [PATCH 4/8] lint: use llvm::fs api instead of std::filesystem. --- src/Server/Lifecycle.cpp | 44 ++++++++++++++-------------------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/src/Server/Lifecycle.cpp b/src/Server/Lifecycle.cpp index ec93b60c..aacc79c5 100644 --- a/src/Server/Lifecycle.cpp +++ b/src/Server/Lifecycle.cpp @@ -1,20 +1,17 @@ #include "Server/Server.h" -#include - namespace clice { namespace { -namespace stdfs = std::filesystem; - /// Load compile commands from given directories. If no valid commands are found, /// search recursively from the workspace directory. void load_compile_commands(CompilationDatabase& database, const std::vector& compile_commands_dirs, const std::string& workspace) { + auto try_load = [&database](const std::string& dir) { - std::string filepath = dir + "/compile_commands.json"; + std::string filepath = path::join(dir, "compile_commands.json"); auto content = fs::read(filepath); if(!content) { log::warn("Failed to read CDB file: {}, {}", filepath, content.error()); @@ -39,37 +36,26 @@ void load_compile_commands(CompilationDatabase& database, "Can not found any valid CDB file from given directories, search recursively from workspace: {} ...", workspace); - auto recursive_search = [&try_load](this auto&& self, const stdfs::path& dir) -> bool { - if(!stdfs::exists(dir) || !stdfs::is_directory(dir)) { - return false; + std::error_code ec; + for(fs::recursive_directory_iterator it(workspace, ec), end; it != end && !ec; + it.increment(ec)) { + auto status = it->status(); + if(!status) { + continue; } // Skip hidden directories. - if(dir.filename().string().starts_with('.')) { - return false; - } - - if(try_load(dir.string())) { - return true; + llvm::StringRef filename = path::filename(it->path()); + if(fs::is_directory(*status) && filename.starts_with('.')) { + it.no_push(); + continue; } - std::error_code ec; - for(const auto& entry: stdfs::directory_iterator(dir, ec)) { - if(ec) { - log::warn("Error iterating directory: {}, {}, skipped", dir.string(), ec.message()); - continue; - } - - if(self(entry.path())) { - return true; + if(fs::is_regular_file(*status) && filename == "compile_commands.json") { + if(try_load(it->path())) { + return; } } - - return false; - }; - - if(recursive_search(workspace)) { - return; } /// TODO: Add a default command in clice.toml. Or load commands from .clangd ? From ad309eb770a70959a91d0c6b62c4b981c4036394 Mon Sep 17 00:00:00 2001 From: Guo-Shiyu Date: Fri, 29 Aug 2025 20:52:12 +0800 Subject: [PATCH 5/8] fix: store relative path of loaded source file in `CompilationDatabase`. --- include/Compiler/Command.h | 6 +-- src/Compiler/Command.cpp | 59 +++++++++++++++------ src/Server/Lifecycle.cpp | 6 +-- tests/unit/Compiler/Command.cpp | 94 +++++++++++++++++++++++++++++++++ 4 files changed, 144 insertions(+), 21 deletions(-) diff --git a/include/Compiler/Command.h b/include/Compiler/Command.h index 87c66d07..492c0e85 100644 --- a/include/Compiler/Command.h +++ b/include/Compiler/Command.h @@ -35,7 +35,7 @@ class CompilationDatabase { struct CommandInfo { /// TODO: add sysroot or no stdinc command info. - llvm::StringRef dictionary; + llvm::StringRef directory; /// The canonical command list. llvm::ArrayRef arguments; @@ -57,7 +57,7 @@ class CompilationDatabase { }; struct LookupInfo { - llvm::StringRef dictionary; + llvm::StringRef directory; std::vector arguments; }; @@ -106,7 +106,7 @@ class CompilationDatabase { llvm::StringRef command) -> UpdateInfo; /// Update commands from json file and return all updated file. - auto load_commands(this Self& self, llvm::StringRef json_content) + auto load_commands(this Self& self, llvm::StringRef json_content, llvm::StringRef workspace) -> std::expected, std::string>; auto get_command(this Self& self, llvm::StringRef file, CommandOptions options = {}) diff --git a/src/Compiler/Command.cpp b/src/Compiler/Command.cpp index 7866fb31..10c5a772 100644 --- a/src/Compiler/Command.cpp +++ b/src/Compiler/Command.cpp @@ -257,11 +257,11 @@ auto CompilationDatabase::query_driver(this Self& self, llvm::StringRef driver) } auto CompilationDatabase::update_command(this Self& self, - llvm::StringRef dictionary, + llvm::StringRef directory, llvm::StringRef file, llvm::ArrayRef arguments) -> UpdateInfo { file = self.save_string(file); - dictionary = self.save_string(dictionary); + directory = self.save_string(directory); llvm::SmallVector filtered_arguments; @@ -293,6 +293,21 @@ auto CompilationDatabase::update_command(this Self& self, continue; } + /// For arguments -I, convert directory to absolute path. + /// i.e xmake will generate commands in this style. + if(id == clang::driver::options::OPT_I) { + if(arg->getNumValues() == 1) { + add_argument("-I"); + llvm::StringRef value = arg->getValue(0); + if(!value.empty() && !path::is_absolute(value)) { + add_argument(path::join(directory, value)); + } else { + add_argument(value); + } + } + continue; + } + /// A workaround to remove extra PCH when cmake /// generate PCH flags for clang. if(id == clang::driver::options::OPT_Xclang) { @@ -355,16 +370,15 @@ auto CompilationDatabase::update_command(this Self& self, arguments = self.save_cstring_list(filtered_arguments); UpdateKind kind = UpdateKind::Unchange; - CommandInfo info = {dictionary, arguments}; + CommandInfo info = {directory, arguments}; auto [it, success] = self.command_infos.try_emplace(file.data(), info); if(success) { kind = UpdateKind::Create; } else { auto& info = it->second; - if(info.dictionary.data() != dictionary.data() || - info.arguments.data() != arguments.data()) { + if(info.directory.data() != directory.data() || info.arguments.data() != arguments.data()) { kind = UpdateKind::Update; - info.dictionary = dictionary; + info.directory = directory; info.arguments = arguments; } } @@ -373,7 +387,7 @@ auto CompilationDatabase::update_command(this Self& self, } auto CompilationDatabase::update_command(this Self& self, - llvm::StringRef dictionary, + llvm::StringRef directory, llvm::StringRef file, llvm::StringRef command) -> UpdateInfo { llvm::BumpPtrAllocator local; @@ -390,10 +404,12 @@ auto CompilationDatabase::update_command(this Self& self, llvm::cl::TokenizeGNUCommandLine(command, saver, arguments); } - return self.update_command(dictionary, file, arguments); + return self.update_command(directory, file, arguments); } -auto CompilationDatabase::load_commands(this Self& self, llvm::StringRef json_content) +auto CompilationDatabase::load_commands(this Self& self, + llvm::StringRef json_content, + llvm::StringRef workspace) -> std::expected, std::string> { std::vector infos; @@ -415,9 +431,22 @@ auto CompilationDatabase::load_commands(this Self& self, llvm::StringRef json_co auto& object = *item.getAsObject(); - auto file = object.getString("file"); auto directory = object.getString("directory"); - if(!file || !directory) { + if(!directory) { + continue; + } + + /// Always store relative path of source file. + std::string source; + if(auto file = object.getString("file")) { + if(path::is_absolute(*file)) { + llvm::SmallString<256> buffer = *file; + path::replace_path_prefix(buffer, workspace, ""); + source = path::relative_path(buffer).str(); + } else { + source = file->str(); + } + } else { continue; } @@ -433,12 +462,12 @@ auto CompilationDatabase::load_commands(this Self& self, llvm::StringRef json_co } } - auto info = self.update_command(*directory, *file, carguments); + auto info = self.update_command(*directory, source, carguments); if(info.kind != UpdateKind::Unchange) { infos.emplace_back(info); } } else if(auto command = object.getString("command")) { - auto info = self.update_command(*directory, *file, *command); + auto info = self.update_command(*directory, source, *command); if(info.kind != UpdateKind::Unchange) { infos.emplace_back(info); } @@ -455,7 +484,7 @@ auto CompilationDatabase::get_command(this Self& self, llvm::StringRef file, Com file = self.save_string(file); auto it = self.command_infos.find(file.data()); if(it != self.command_infos.end()) { - info.dictionary = it->second.dictionary; + info.directory = it->second.directory; info.arguments = it->second.arguments; } else { info = self.guess_or_fallback(file); @@ -502,7 +531,7 @@ auto CompilationDatabase::guess_or_fallback(this Self& self, llvm::StringRef fil for(const auto& [other_file, info]: self.command_infos) { if(llvm::StringRef other = other_file; other.starts_with(dir)) { log::info("Guess command for:{}, from existed file: {}", file, other_file); - return LookupInfo{info.dictionary, info.arguments}; + return LookupInfo{info.directory, info.arguments}; } } dir = path::parent_path(dir); diff --git a/src/Server/Lifecycle.cpp b/src/Server/Lifecycle.cpp index aacc79c5..a7feb3bc 100644 --- a/src/Server/Lifecycle.cpp +++ b/src/Server/Lifecycle.cpp @@ -8,9 +8,9 @@ namespace { /// search recursively from the workspace directory. void load_compile_commands(CompilationDatabase& database, const std::vector& compile_commands_dirs, - const std::string& workspace) { + llvm::StringRef workspace) { - auto try_load = [&database](const std::string& dir) { + auto try_load = [&database, workspace](const std::string& dir) { std::string filepath = path::join(dir, "compile_commands.json"); auto content = fs::read(filepath); if(!content) { @@ -18,7 +18,7 @@ void load_compile_commands(CompilationDatabase& database, return false; } - auto load = database.load_commands(*content); + auto load = database.load_commands(*content, workspace); if(!load) { log::warn("Failed to load CDB file: {}. {}", filepath, load.error()); return false; diff --git a/tests/unit/Compiler/Command.cpp b/tests/unit/Compiler/Command.cpp index 828b0c38..f14efefb 100644 --- a/tests/unit/Compiler/Command.cpp +++ b/tests/unit/Compiler/Command.cpp @@ -216,6 +216,100 @@ suite<"Command"> command = [] { /// expect(that % command[2] == "test.cpp"sv); /// expect(that % command[3] == std::format("-resource-dir={}", fs::resource_dir)); }; + + auto expect_load = [](llvm::StringRef content, + llvm::StringRef workspace, + llvm::StringRef file, + llvm::StringRef directory, + llvm::ArrayRef arguments) { + CompilationDatabase database; + auto loaded = database.load_commands(content, workspace); + expect(that % loaded.has_value()); + + CommandOptions options; + options.query_driver = false; + options.resource_dir = false; + options.suppress_log = true; + auto info = database.get_command(file, options); + + expect(that % info.directory == directory); + expect(that % info.arguments.size() == arguments.size()); + for(size_t i = 0; i < arguments.size(); i++) { + llvm::StringRef arg = info.arguments[i]; + llvm::StringRef expect_arg = arguments[i]; + expect(that % arg == expect_arg); + } + }; + + test("LoadAbsoluteStyle") = [expect_load] { + constexpr const char* cmake = R"([ + { + "directory": "/home/developer/clice/build", + "command": "/usr/bin/c++ -I/home/developer/clice/include -I/home/developer/clice/build/_deps/libuv-src/include -isystem /home/developer/clice/build/_deps/tomlplusplus-src/include -std=gnu++23 -fno-rtti -fno-exceptions -Wno-deprecated-declarations -Wno-undefined-inline -O3 -o CMakeFiles/clice-core.dir/src/Driver/clice.cpp.o -c /home/developer/clice/src/Driver/clice.cpp", + "file": "/home/developer/clice/src/Driver/clice.cpp", + "output": "CMakeFiles/clice-core.dir/src/Driver/clice.cpp.o" + } + ])"; + + expect_load(cmake, + "/home/developer/clice", + "src/Driver/clice.cpp", + "/home/developer/clice/build", + { + "/usr/bin/c++", + "-I", + "/home/developer/clice/include", + "-I", + "/home/developer/clice/build/_deps/libuv-src/include", + "-isystem", + "/home/developer/clice/build/_deps/tomlplusplus-src/include", + "-std=gnu++23", + "-fno-rtti", + "-fno-exceptions", + "-Wno-deprecated-declarations", + "-Wno-undefined-inline", + "-O3", + "src/Driver/clice.cpp", + }); + }; + + test("LoadRelativeStyle") = [expect_load] { + constexpr const char* xmake = R"([ + { + "directory": "/home/developer/clice", + "arguments": ["/usr/bin/clang", "-c", "-Qunused-arguments", "-m64", "-g", "-O0", "-std=c++23", "-Iinclude", "-I/home/developer/clice/include", "-fno-exceptions", "-fno-cxx-exceptions", "-isystem", "/home/developer/.xmake/packages/l/libuv/v1.51.0/3ca1562e6c5d485f9ccafec8e0c50b6f/include", "-isystem", "/home/developer/.xmake/packages/t/toml++/v3.4.0/bde7344d843e41928b1d325fe55450e0/include", "-fsanitize=address", "-fno-rtti", "-o", "build/.objs/clice/linux/x86_64/debug/src/Driver/clice.cc.o", "src/Driver/clice.cc"], + "file": "src/Driver/clice.cc" + } + ])"; + + expect_load( + xmake, + "/home/developer/clice", + "src/Driver/clice.cc", + "/home/developer/clice", + { + "/usr/bin/clang", + "-Qunused-arguments", + "-m64", + "-g", + "-O0", + "-std=c++23", + // parameter "-Iinclude" in CDB, should be convert to absolute path + "-I", + "/home/developer/clice/include", + "-I", + "/home/developer/clice/include", + "-fno-exceptions", + "-fno-cxx-exceptions", + "-isystem", + "/home/developer/.xmake/packages/l/libuv/v1.51.0/3ca1562e6c5d485f9ccafec8e0c50b6f/include", + "-isystem", + "/home/developer/.xmake/packages/t/toml++/v3.4.0/bde7344d843e41928b1d325fe55450e0/include", + "-fsanitize=address", + "-fno-rtti", + "src/Driver/clice.cc", + }); + }; }; } // namespace From e2c279bef4b19476c5fa4f0904e05bcc3e210ff1 Mon Sep 17 00:00:00 2001 From: Guo-Shiyu Date: Fri, 29 Aug 2025 21:16:38 +0800 Subject: [PATCH 6/8] Chore: fix test case on Windows. --- include/Compiler/Command.h | 1 + tests/unit/Compiler/Command.cpp | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/include/Compiler/Command.h b/include/Compiler/Command.h index 492c0e85..8665ec54 100644 --- a/include/Compiler/Command.h +++ b/include/Compiler/Command.h @@ -109,6 +109,7 @@ class CompilationDatabase { auto load_commands(this Self& self, llvm::StringRef json_content, llvm::StringRef workspace) -> std::expected, std::string>; + /// Get compile command from database. `file` should has relative path of workspace. auto get_command(this Self& self, llvm::StringRef file, CommandOptions options = {}) -> LookupInfo; diff --git a/tests/unit/Compiler/Command.cpp b/tests/unit/Compiler/Command.cpp index f14efefb..952ee5b5 100644 --- a/tests/unit/Compiler/Command.cpp +++ b/tests/unit/Compiler/Command.cpp @@ -227,8 +227,6 @@ suite<"Command"> command = [] { expect(that % loaded.has_value()); CommandOptions options; - options.query_driver = false; - options.resource_dir = false; options.suppress_log = true; auto info = database.get_command(file, options); @@ -241,7 +239,9 @@ suite<"Command"> command = [] { } }; - test("LoadAbsoluteStyle") = [expect_load] { +#if defined(__unix__) || defined(__APPLE__) + /// TODO: add windows path testcase + test("LoadAbsoluteUnixStyle") = [expect_load] { constexpr const char* cmake = R"([ { "directory": "/home/developer/clice/build", @@ -273,7 +273,7 @@ suite<"Command"> command = [] { }); }; - test("LoadRelativeStyle") = [expect_load] { + test("LoadRelativeUnixStyle") = [expect_load] { constexpr const char* xmake = R"([ { "directory": "/home/developer/clice", @@ -310,6 +310,7 @@ suite<"Command"> command = [] { "src/Driver/clice.cc", }); }; +#endif }; } // namespace From a029a8b9cd8fadc219b8c715e15203d4f0137afd Mon Sep 17 00:00:00 2001 From: Guo-Shiyu Date: Fri, 29 Aug 2025 21:58:23 +0800 Subject: [PATCH 7/8] Fix: fix by gemini suggestions. --- src/Compiler/Command.cpp | 5 ++++- src/Server/Lifecycle.cpp | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Compiler/Command.cpp b/src/Compiler/Command.cpp index 10c5a772..142eb4a4 100644 --- a/src/Compiler/Command.cpp +++ b/src/Compiler/Command.cpp @@ -529,7 +529,10 @@ auto CompilationDatabase::guess_or_fallback(this Self& self, llvm::StringRef fil while(!dir.empty() && up_level < 3) { // If any file in the directory has a command, use that command for(const auto& [other_file, info]: self.command_infos) { - if(llvm::StringRef other = other_file; other.starts_with(dir)) { + llvm::StringRef other = other_file; + // Filter case that dir is /path/to/foo and there's another directory /path/to/foobar + if(other.starts_with(dir) && + (other.size() == dir.size() || path::is_separator(other[dir.size()]))) { log::info("Guess command for:{}, from existed file: {}", file, other_file); return LookupInfo{info.directory, info.arguments}; } diff --git a/src/Server/Lifecycle.cpp b/src/Server/Lifecycle.cpp index a7feb3bc..f3f5cd90 100644 --- a/src/Server/Lifecycle.cpp +++ b/src/Server/Lifecycle.cpp @@ -10,7 +10,7 @@ void load_compile_commands(CompilationDatabase& database, const std::vector& compile_commands_dirs, llvm::StringRef workspace) { - auto try_load = [&database, workspace](const std::string& dir) { + auto try_load = [&database, workspace](llvm::StringRef dir) { std::string filepath = path::join(dir, "compile_commands.json"); auto content = fs::read(filepath); if(!content) { @@ -52,7 +52,7 @@ void load_compile_commands(CompilationDatabase& database, } if(fs::is_regular_file(*status) && filename == "compile_commands.json") { - if(try_load(it->path())) { + if(try_load(path::parent_path(it->path()))) { return; } } From 5f47ab67407ac166aceb67482f03d2b5ce1f1783 Mon Sep 17 00:00:00 2001 From: Guo-Shiyu Date: Tue, 2 Sep 2025 13:30:58 +0800 Subject: [PATCH 8/8] Chore: move `load_compile_commands()` to `CompilationDatabase` method. --- include/Compiler/Command.h | 6 ++++ src/Compiler/Command.cpp | 55 ++++++++++++++++++++++++++++++++ src/Server/Lifecycle.cpp | 64 +------------------------------------- 3 files changed, 62 insertions(+), 63 deletions(-) diff --git a/include/Compiler/Command.h b/include/Compiler/Command.h index 8665ec54..6f0391e5 100644 --- a/include/Compiler/Command.h +++ b/include/Compiler/Command.h @@ -113,6 +113,12 @@ class CompilationDatabase { auto get_command(this Self& self, llvm::StringRef file, CommandOptions options = {}) -> LookupInfo; + /// Load compile commands from given directories. If no valid commands are found, + /// search recursively from the workspace directory. + auto load_compile_commands(this Self& self, + llvm::ArrayRef compile_commands_dirs, + llvm::StringRef workspace) -> void; + private: /// If file not found in CDB file, try to guess commands or use the default case. auto guess_or_fallback(this Self& self, llvm::StringRef file) -> LookupInfo; diff --git a/src/Compiler/Command.cpp b/src/Compiler/Command.cpp index 142eb4a4..1c3d2b73 100644 --- a/src/Compiler/Command.cpp +++ b/src/Compiler/Command.cpp @@ -551,4 +551,59 @@ auto CompilationDatabase::guess_or_fallback(this Self& self, llvm::StringRef fil return info; } +auto CompilationDatabase::load_compile_commands(this Self& self, + llvm::ArrayRef compile_commands_dirs, + llvm::StringRef workspace) -> void { + auto try_load = [&self, workspace](llvm::StringRef dir) { + std::string filepath = path::join(dir, "compile_commands.json"); + auto content = fs::read(filepath); + if(!content) { + log::warn("Failed to read CDB file: {}, {}", filepath, content.error()); + return false; + } + + auto load = self.load_commands(*content, workspace); + if(!load) { + log::warn("Failed to load CDB file: {}. {}", filepath, load.error()); + return false; + } + + log::info("Load CDB file: {} successfully, {} items loaded", filepath, load->size()); + return true; + }; + + if(std::ranges::any_of(compile_commands_dirs, try_load)) { + return; + } + + log::info( + "Can not found any valid CDB file from given directories, search recursively from workspace: {} ...", + workspace); + + std::error_code ec; + for(fs::recursive_directory_iterator it(workspace, ec), end; it != end && !ec; + it.increment(ec)) { + auto status = it->status(); + if(!status) { + continue; + } + + // Skip hidden directories. + llvm::StringRef filename = path::filename(it->path()); + if(fs::is_directory(*status) && filename.starts_with('.')) { + it.no_push(); + continue; + } + + if(fs::is_regular_file(*status) && filename == "compile_commands.json") { + if(try_load(path::parent_path(it->path()))) { + return; + } + } + } + + /// TODO: Add a default command in clice.toml. Or load commands from .clangd ? + log::warn("Can not found any valid CDB file in current workspace, fallback to default mode."); +} + } // namespace clice diff --git a/src/Server/Lifecycle.cpp b/src/Server/Lifecycle.cpp index 63004c69..bd5ac680 100644 --- a/src/Server/Lifecycle.cpp +++ b/src/Server/Lifecycle.cpp @@ -2,68 +2,6 @@ namespace clice { -namespace { - -/// Load compile commands from given directories. If no valid commands are found, -/// search recursively from the workspace directory. -void load_compile_commands(CompilationDatabase& database, - const std::vector& compile_commands_dirs, - llvm::StringRef workspace) { - - auto try_load = [&database, workspace](llvm::StringRef dir) { - std::string filepath = path::join(dir, "compile_commands.json"); - auto content = fs::read(filepath); - if(!content) { - log::warn("Failed to read CDB file: {}, {}", filepath, content.error()); - return false; - } - - auto load = database.load_commands(*content, workspace); - if(!load) { - log::warn("Failed to load CDB file: {}. {}", filepath, load.error()); - return false; - } - - log::info("Load CDB file: {} successfully, {} items loaded", filepath, load->size()); - return true; - }; - - if(std::ranges::any_of(compile_commands_dirs, try_load)) { - return; - } - - log::info( - "Can not found any valid CDB file from given directories, search recursively from workspace: {} ...", - workspace); - - std::error_code ec; - for(fs::recursive_directory_iterator it(workspace, ec), end; it != end && !ec; - it.increment(ec)) { - auto status = it->status(); - if(!status) { - continue; - } - - // Skip hidden directories. - llvm::StringRef filename = path::filename(it->path()); - if(fs::is_directory(*status) && filename.starts_with('.')) { - it.no_push(); - continue; - } - - if(fs::is_regular_file(*status) && filename == "compile_commands.json") { - if(try_load(path::parent_path(it->path()))) { - return; - } - } - } - - /// TODO: Add a default command in clice.toml. Or load commands from .clangd ? - log::warn("Can not found any valid CDB file in current workspace, fallback to default mode."); -} - -} // namespace - async::Task Server::on_initialize(proto::InitializeParams params) { log::info("Initialize from client: {}, version: {}", params.clientInfo.name, @@ -89,7 +27,7 @@ async::Task Server::on_initialize(proto::InitializeParams params) { opening_files.set_capability(config::server.max_active_file); /// Load compile commands.json - load_compile_commands(database, config::server.compile_commands_dirs, workspace); + database.load_compile_commands(config::server.compile_commands_dirs, workspace); /// Load cache info. load_cache_info();