[clang-tools-extra] [clangd] Make clangd-indexer respect .clangd configuration files (PR #214951)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Sep 25 02:36:34 PDT 2026
https://github.com/Fil-Den updated https://github.com/llvm/llvm-project/pull/214951
>From 92c6aef642d9cedbee379f0d71e4102c28a35af8 Mon Sep 17 00:00:00 2001
From: Fil-Den <ffil.denn at gmail.com>
Date: Sat, 11 Jul 2026 18:46:03 +0300
Subject: [PATCH 01/18] Clangd-indexer respects .clangd files
---
clang-tools-extra/clangd/ConfigProvider.cpp | 27 ++++++++++++++
clang-tools-extra/clangd/ConfigProvider.h | 10 +++++
.../clangd/indexer/IndexerMain.cpp | 37 ++++++++++++++++++-
.../clangd/test/indexer-clangd-config.test | 25 +++++++++++++
clang-tools-extra/clangd/tool/ClangdMain.cpp | 24 ++----------
5 files changed, 100 insertions(+), 23 deletions(-)
create mode 100644 clang-tools-extra/clangd/test/indexer-clangd-config.test
diff --git a/clang-tools-extra/clangd/ConfigProvider.cpp b/clang-tools-extra/clangd/ConfigProvider.cpp
index ac437ee8b6eb1..e26509d8b1c8b 100644
--- a/clang-tools-extra/clangd/ConfigProvider.cpp
+++ b/clang-tools-extra/clangd/ConfigProvider.cpp
@@ -10,10 +10,12 @@
#include "Config.h"
#include "ConfigFragment.h"
#include "support/FileCache.h"
+#include "support/Logger.h"
#include "support/Path.h"
#include "support/ThreadsafeFS.h"
#include "support/Trace.h"
#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Path.h"
@@ -166,6 +168,31 @@ Provider::combine(std::vector<const Provider *> Providers) {
return std::make_unique<CombinedProvider>(std::move(Providers));
}
+std::vector<std::unique_ptr<Provider>>
+Provider::createDefaultProviders(const ThreadsafeFS &TFS) {
+ std::vector<std::unique_ptr<Provider>> Providers;
+ Providers.push_back(fromAncestorRelativeYAMLFiles(".clangd", TFS));
+ llvm::SmallString<256> UserConfig;
+ if (llvm::sys::path::user_config_directory(UserConfig)) {
+ llvm::sys::path::append(UserConfig, "clangd", "config.yaml");
+ vlog("User config file is {0}", UserConfig);
+ Providers.push_back(
+ fromYAMLFile(UserConfig, /*Directory=*/"", TFS, /*Trusted=*/true));
+ } else {
+ elog("Couldn't determine user config file, not loading");
+ }
+ return Providers;
+}
+
+const char *const Provider::EnableConfigFlagDesc =
+ "Read user and project configuration from YAML files.\n"
+ "Project config is from a .clangd file in the project directory.\n"
+ "User config is from clangd/config.yaml in the following directories:\n"
+ "\tWindows: %USERPROFILE%\\AppData\\Local\n"
+ "\tMac OS: ~/Library/Preferences/\n"
+ "\tOthers: $XDG_CONFIG_HOME, usually ~/.config\n"
+ "Configuration is documented at https://clangd.llvm.org/config.html";
+
Config Provider::getConfig(const Params &P, DiagnosticCallback DC) const {
trace::Span Tracer("getConfig");
if (!P.Path.empty())
diff --git a/clang-tools-extra/clangd/ConfigProvider.h b/clang-tools-extra/clangd/ConfigProvider.h
index f268edb1df2a5..e1a52604e7de8 100644
--- a/clang-tools-extra/clangd/ConfigProvider.h
+++ b/clang-tools-extra/clangd/ConfigProvider.h
@@ -84,9 +84,19 @@ class Provider {
/// Order is preserved; later providers take precedence over earlier ones.
static std::unique_ptr<Provider> combine(std::vector<const Provider *>);
+ /// Returns providers for the configuration files that clangd tools read by
+ /// default: project config (ancestor `.clangd` files) and the user's
+ /// global config file.
+ static std::vector<std::unique_ptr<Provider>>
+ createDefaultProviders(const ThreadsafeFS &);
+
/// Build a config based on this provider.
Config getConfig(const Params &, DiagnosticCallback) const;
+ /// Help text for the --enable-config flag, shared by clangd tools that
+ /// expose createDefaultProviders() on the command line.
+ static const char *const EnableConfigFlagDesc;
+
private:
/// Provide fragments that may be relevant to the file.
/// The configuration provider is not responsible for testing conditions.
diff --git a/clang-tools-extra/clangd/indexer/IndexerMain.cpp b/clang-tools-extra/clangd/indexer/IndexerMain.cpp
index 94db860f0b9b5..c330be298a87a 100644
--- a/clang-tools-extra/clangd/indexer/IndexerMain.cpp
+++ b/clang-tools-extra/clangd/indexer/IndexerMain.cpp
@@ -10,21 +10,29 @@
//
//===----------------------------------------------------------------------===//
+#include "ClangdServer.h"
#include "CompileCommands.h"
#include "Compiler.h"
+#include "ConfigProvider.h"
#include "index/IndexAction.h"
#include "index/Merge.h"
#include "index/Ref.h"
#include "index/Serialization.h"
#include "index/Symbol.h"
#include "index/SymbolCollector.h"
+#include "support/Context.h"
#include "support/Logger.h"
+#include "support/ThreadsafeFS.h"
#include "clang/Tooling/ArgumentsAdjusters.h"
#include "clang/Tooling/Execution.h"
#include "clang/Tooling/Tooling.h"
+#include "llvm/ADT/SmallString.h"
#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Signals.h"
+#include <memory>
#include <utility>
+#include <vector>
namespace clang {
namespace clangd {
@@ -48,6 +56,12 @@ static llvm::cl::list<std::string> QueryDriverGlobs{
llvm::cl::CommaSeparated,
};
+static llvm::cl::opt<bool> EnableConfig{
+ "enable-config",
+ llvm::cl::desc(config::Provider::EnableConfigFlagDesc),
+ llvm::cl::init(true),
+};
+
class IndexActionFactory : public tooling::FrontendActionFactory {
public:
IndexActionFactory(IndexFileIn &Result) : Result(Result) {}
@@ -152,6 +166,20 @@ int main(int argc, const char **argv) {
return 1;
}
+ clang::clangd::RealThreadsafeFS TFS;
+ std::vector<std::unique_ptr<clang::clangd::config::Provider>> ProviderStack;
+ if (clang::clangd::EnableConfig)
+ ProviderStack =
+ clang::clangd::config::Provider::createDefaultProviders(TFS);
+ std::vector<const clang::clangd::config::Provider *> ProviderPointers;
+ for (const auto &P : ProviderStack)
+ ProviderPointers.push_back(P.get());
+ auto ConfigProvider =
+ clang::clangd::config::Provider::combine(std::move(ProviderPointers));
+ auto ContextProvider =
+ clang::clangd::ClangdServer::createConfiguredContextProvider(
+ ConfigProvider.get(), /*Callbacks=*/nullptr);
+
// Collect symbols found in each translation unit, merging as we go.
clang::clangd::IndexFileIn Data;
auto Mangler = std::make_shared<clang::clangd::CommandMangler>(
@@ -162,8 +190,13 @@ int main(int argc, const char **argv) {
auto Err = Executor->get()->execute(
std::make_unique<clang::clangd::IndexActionFactory>(Data),
clang::tooling::ArgumentsAdjuster(
- [Mangler = std::move(Mangler)](const std::vector<std::string> &Args,
- llvm::StringRef File) {
+ [Mangler = std::move(Mangler),
+ ContextProvider = std::move(ContextProvider)](
+ const std::vector<std::string> &Args, llvm::StringRef File) {
+ llvm::SmallString<256> AbsFile(File);
+ llvm::sys::fs::make_absolute(AbsFile);
+ clang::clangd::WithContext WithCfg(ContextProvider(AbsFile));
+
clang::tooling::CompileCommand Cmd;
Cmd.CommandLine = Args;
Mangler->operator()(Cmd, File);
diff --git a/clang-tools-extra/clangd/test/indexer-clangd-config.test b/clang-tools-extra/clangd/test/indexer-clangd-config.test
new file mode 100644
index 0000000000000..f99f2db410ee3
--- /dev/null
+++ b/clang-tools-extra/clangd/test/indexer-clangd-config.test
@@ -0,0 +1,25 @@
+# Test that clangd-indexer respects CompileFlags
+
+# RUN: rm -rf %t.dir && mkdir -p %t.dir
+# RUN: split-file %s %t.dir
+
+# By default config is enabled, so the .clangd file's CompileFlags.Add should
+# define FROM_CLANGD_FILE, and the #error below must not fire.
+# RUN: clangd-indexer %t.dir/test.cpp 2>&1 | FileCheck --check-prefix=ENABLED %s
+# ENABLED-NOT: error: "FROM_CLANGD_FILE not defined
+
+# With config disabled, the macro isn't defined by the .clangd file, so the
+# #error must fire.
+# RUN: clangd-indexer --enable-config=0 %t.dir/test.cpp 2>&1 \
+# RUN: | FileCheck --check-prefix=DISABLED %s
+# DISABLED: error: "FROM_CLANGD_FILE not defined - .clangd file was not applied"
+
+#--- .clangd
+CompileFlags:
+ Add: [-DFROM_CLANGD_FILE=1]
+
+#--- test.cpp
+#ifndef FROM_CLANGD_FILE
+#error "FROM_CLANGD_FILE not defined - .clangd file was not applied"
+#endif
+int foo() { return 0; }
diff --git a/clang-tools-extra/clangd/tool/ClangdMain.cpp b/clang-tools-extra/clangd/tool/ClangdMain.cpp
index 13fe4d3911731..f95e2dbf5eb38 100644
--- a/clang-tools-extra/clangd/tool/ClangdMain.cpp
+++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp
@@ -489,14 +489,7 @@ opt<bool> PrettyPrint{
opt<bool> EnableConfig{
"enable-config",
cat(Misc),
- desc(
- "Read user and project configuration from YAML files.\n"
- "Project config is from a .clangd file in the project directory.\n"
- "User config is from clangd/config.yaml in the following directories:\n"
- "\tWindows: %USERPROFILE%\\AppData\\Local\n"
- "\tMac OS: ~/Library/Preferences/\n"
- "\tOthers: $XDG_CONFIG_HOME, usually ~/.config\n"
- "Configuration is documented at https://clangd.llvm.org/config.html"),
+ desc(config::Provider::EnableConfigFlagDesc),
init(true),
};
@@ -998,19 +991,8 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
RealThreadsafeFS TFS;
std::vector<std::unique_ptr<config::Provider>> ProviderStack;
std::unique_ptr<config::Provider> Config;
- if (EnableConfig) {
- ProviderStack.push_back(
- config::Provider::fromAncestorRelativeYAMLFiles(".clangd", TFS));
- llvm::SmallString<256> UserConfig;
- if (llvm::sys::path::user_config_directory(UserConfig)) {
- llvm::sys::path::append(UserConfig, "clangd", "config.yaml");
- vlog("User config file is {0}", UserConfig);
- ProviderStack.push_back(config::Provider::fromYAMLFile(
- UserConfig, /*Directory=*/"", TFS, /*Trusted=*/true));
- } else {
- elog("Couldn't determine user config file, not loading");
- }
- }
+ if (EnableConfig)
+ ProviderStack = config::Provider::createDefaultProviders(TFS);
ProviderStack.push_back(std::make_unique<FlagsConfigProvider>());
std::vector<const config::Provider *> ProviderPointers;
for (const auto &P : ProviderStack)
>From d19475d436d308f26bc71d9fe2f0caffb14674c4 Mon Sep 17 00:00:00 2001
From: Fil-Den <ffil.denn at gmail.com>
Date: Tue, 11 Aug 2026 20:58:31 +0300
Subject: [PATCH 02/18] Disable config by default
---
.../clangd/indexer/IndexerMain.cpp | 2 +-
.../clangd/test/indexer-clangd-config.test | 18 +++++++++---------
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/clang-tools-extra/clangd/indexer/IndexerMain.cpp b/clang-tools-extra/clangd/indexer/IndexerMain.cpp
index c330be298a87a..154374606e38d 100644
--- a/clang-tools-extra/clangd/indexer/IndexerMain.cpp
+++ b/clang-tools-extra/clangd/indexer/IndexerMain.cpp
@@ -59,7 +59,7 @@ static llvm::cl::list<std::string> QueryDriverGlobs{
static llvm::cl::opt<bool> EnableConfig{
"enable-config",
llvm::cl::desc(config::Provider::EnableConfigFlagDesc),
- llvm::cl::init(true),
+ llvm::cl::init(false),
};
class IndexActionFactory : public tooling::FrontendActionFactory {
diff --git a/clang-tools-extra/clangd/test/indexer-clangd-config.test b/clang-tools-extra/clangd/test/indexer-clangd-config.test
index f99f2db410ee3..c87e6fb1f1e28 100644
--- a/clang-tools-extra/clangd/test/indexer-clangd-config.test
+++ b/clang-tools-extra/clangd/test/indexer-clangd-config.test
@@ -3,17 +3,17 @@
# RUN: rm -rf %t.dir && mkdir -p %t.dir
# RUN: split-file %s %t.dir
-# By default config is enabled, so the .clangd file's CompileFlags.Add should
-# define FROM_CLANGD_FILE, and the #error below must not fire.
-# RUN: clangd-indexer %t.dir/test.cpp 2>&1 | FileCheck --check-prefix=ENABLED %s
-# ENABLED-NOT: error: "FROM_CLANGD_FILE not defined
-
-# With config disabled, the macro isn't defined by the .clangd file, so the
-# #error must fire.
-# RUN: clangd-indexer --enable-config=0 %t.dir/test.cpp 2>&1 \
-# RUN: | FileCheck --check-prefix=DISABLED %s
+# By default config is disabled, so the .clangd file's CompileFlags.Add is not
+# applied and the #error below must fire.
+# RUN: clangd-indexer %t.dir/test.cpp 2>&1 | FileCheck --check-prefix=DISABLED %s
# DISABLED: error: "FROM_CLANGD_FILE not defined - .clangd file was not applied"
+# With config enabled, the .clangd file's CompileFlags.Add should define
+# FROM_CLANGD_FILE, so the #error below must not fire.
+# RUN: clangd-indexer --enable-config %t.dir/test.cpp 2>&1 \
+# RUN: | FileCheck --check-prefix=ENABLED %s
+# ENABLED-NOT: error: "FROM_CLANGD_FILE not defined
+
#--- .clangd
CompileFlags:
Add: [-DFROM_CLANGD_FILE=1]
>From 4b135375aaa35082fa0769a4fda6ea46fd3f400e Mon Sep 17 00:00:00 2001
From: Fil-Den <ffil.denn at gmail.com>
Date: Tue, 11 Aug 2026 23:22:05 +0300
Subject: [PATCH 03/18] move some logic into combineOwned
---
clang-tools-extra/clangd/ConfigProvider.cpp | 9 +++++++++
clang-tools-extra/clangd/ConfigProvider.h | 15 +++++++++++++++
clang-tools-extra/clangd/indexer/IndexerMain.cpp | 7 ++-----
clang-tools-extra/clangd/tool/ClangdMain.cpp | 8 ++------
4 files changed, 28 insertions(+), 11 deletions(-)
diff --git a/clang-tools-extra/clangd/ConfigProvider.cpp b/clang-tools-extra/clangd/ConfigProvider.cpp
index e26509d8b1c8b..a5bfee9ec303f 100644
--- a/clang-tools-extra/clangd/ConfigProvider.cpp
+++ b/clang-tools-extra/clangd/ConfigProvider.cpp
@@ -168,6 +168,15 @@ Provider::combine(std::vector<const Provider *> Providers) {
return std::make_unique<CombinedProvider>(std::move(Providers));
}
+Provider::OwningProvider
+Provider::combineOwned(std::vector<std::unique_ptr<Provider>> Sources) {
+ std::vector<const Provider *> Pointers;
+ Pointers.reserve(Sources.size());
+ for (const auto &P : Sources)
+ Pointers.push_back(P.get());
+ return {combine(std::move(Pointers)), std::move(Sources)};
+}
+
std::vector<std::unique_ptr<Provider>>
Provider::createDefaultProviders(const ThreadsafeFS &TFS) {
std::vector<std::unique_ptr<Provider>> Providers;
diff --git a/clang-tools-extra/clangd/ConfigProvider.h b/clang-tools-extra/clangd/ConfigProvider.h
index e1a52604e7de8..9f5eeee2c8456 100644
--- a/clang-tools-extra/clangd/ConfigProvider.h
+++ b/clang-tools-extra/clangd/ConfigProvider.h
@@ -90,6 +90,21 @@ class Provider {
static std::vector<std::unique_ptr<Provider>>
createDefaultProviders(const ThreadsafeFS &);
+ /// The result of combining several providers, bundled together with the
+ /// providers themselves. combine() only stores raw pointers to the
+ /// providers it combines, so those providers must outlive it; keeping
+ /// them together in one movable object (rather than as separate
+ /// same-scope variables at the call site) makes it hard to accidentally
+ /// let them go out of scope before Combined does.
+ struct OwningProvider {
+ std::unique_ptr<Provider> Combined;
+ std::vector<std::unique_ptr<Provider>> Sources;
+ };
+
+ /// Like combine(), but takes ownership of the providers being combined.
+ static OwningProvider
+ combineOwned(std::vector<std::unique_ptr<Provider>> Sources);
+
/// Build a config based on this provider.
Config getConfig(const Params &, DiagnosticCallback) const;
diff --git a/clang-tools-extra/clangd/indexer/IndexerMain.cpp b/clang-tools-extra/clangd/indexer/IndexerMain.cpp
index 154374606e38d..9479106e829ea 100644
--- a/clang-tools-extra/clangd/indexer/IndexerMain.cpp
+++ b/clang-tools-extra/clangd/indexer/IndexerMain.cpp
@@ -171,14 +171,11 @@ int main(int argc, const char **argv) {
if (clang::clangd::EnableConfig)
ProviderStack =
clang::clangd::config::Provider::createDefaultProviders(TFS);
- std::vector<const clang::clangd::config::Provider *> ProviderPointers;
- for (const auto &P : ProviderStack)
- ProviderPointers.push_back(P.get());
auto ConfigProvider =
- clang::clangd::config::Provider::combine(std::move(ProviderPointers));
+ clang::clangd::config::Provider::combineOwned(std::move(ProviderStack));
auto ContextProvider =
clang::clangd::ClangdServer::createConfiguredContextProvider(
- ConfigProvider.get(), /*Callbacks=*/nullptr);
+ ConfigProvider.Combined.get(), /*Callbacks=*/nullptr);
// Collect symbols found in each translation unit, merging as we go.
clang::clangd::IndexFileIn Data;
diff --git a/clang-tools-extra/clangd/tool/ClangdMain.cpp b/clang-tools-extra/clangd/tool/ClangdMain.cpp
index f95e2dbf5eb38..c0bf11a6c8a44 100644
--- a/clang-tools-extra/clangd/tool/ClangdMain.cpp
+++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp
@@ -990,15 +990,11 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
RealThreadsafeFS TFS;
std::vector<std::unique_ptr<config::Provider>> ProviderStack;
- std::unique_ptr<config::Provider> Config;
if (EnableConfig)
ProviderStack = config::Provider::createDefaultProviders(TFS);
ProviderStack.push_back(std::make_unique<FlagsConfigProvider>());
- std::vector<const config::Provider *> ProviderPointers;
- for (const auto &P : ProviderStack)
- ProviderPointers.push_back(P.get());
- Config = config::Provider::combine(std::move(ProviderPointers));
- Opts.ConfigProvider = Config.get();
+ auto Config = config::Provider::combineOwned(std::move(ProviderStack));
+ Opts.ConfigProvider = Config.Combined.get();
// Create an empty clang-tidy option.
TidyProvider ClangTidyOptProvider;
>From 4da0a7b2f22f70d343bee374323270fd0100f560 Mon Sep 17 00:00:00 2001
From: Nathan Ridge <zeratul976 at hotmail.com>
Date: Mon, 31 Aug 2026 03:38:24 -0400
Subject: [PATCH 04/18] Test demonstrating a bug in the patch
---
.../indexer-clangd-config-relative-path.test | 47 +++++++++++++++++++
1 file changed, 47 insertions(+)
create mode 100644 clang-tools-extra/clangd/test/indexer-clangd-config-relative-path.test
diff --git a/clang-tools-extra/clangd/test/indexer-clangd-config-relative-path.test b/clang-tools-extra/clangd/test/indexer-clangd-config-relative-path.test
new file mode 100644
index 0000000000000..261bcd2a3fd38
--- /dev/null
+++ b/clang-tools-extra/clangd/test/indexer-clangd-config-relative-path.test
@@ -0,0 +1,47 @@
+# Test that clangd-indexer finds the .clangd file when the compilation database
+# names its source files relative to the compile command's "directory".
+#
+# We specify a custom path in XDG_CONFIG_HOME, which only works on some
+# systems, so that the developer's own user config can't affect the result.
+# UNSUPPORTED: system-windows
+# UNSUPPORTED: system-darwin
+
+# RUN: rm -rf %t.dir && mkdir -p %t.dir
+# RUN: split-file %s %t.dir
+# RUN: mkdir -p %t.dir/elsewhere %t.dir/xdg
+
+# Two databases for the same project: one naming test.cpp relative to
+# "directory", one naming it absolutely. They should behave the same.
+# RUN: mkdir -p %t.dir/rel %t.dir/abs
+# RUN: echo '[{"directory":"%/t.dir/proj","command":"clang++ -c test.cpp","file":"test.cpp"}]' > %t.dir/rel/compile_commands.json
+# RUN: echo '[{"directory":"%/t.dir/proj","command":"clang++ -c test.cpp","file":"%/t.dir/proj/test.cpp"}]' > %t.dir/abs/compile_commands.json
+
+# Run from a directory that is neither the project nor the build directory, so
+# that a process-CWD-relative lookup is guaranteed to look in the wrong place.
+
+# Relative "file" entry: the .clangd file's CompileFlags.Add should still be
+# applied, so the #error must not fire.
+# RUN: cd %t.dir/elsewhere && env XDG_CONFIG_HOME=%t.dir/xdg \
+# RUN: clangd-indexer --enable-config --executor=all-TUs \
+# RUN: %t.dir/rel/compile_commands.json > %t.dir/rel.idx 2> %t.dir/rel.log
+# RUN: FileCheck --check-prefix=REL --input-file=%t.dir/rel.log %s
+# REL: Processing file {{.*}}test.cpp
+# REL-NOT: error: "FROM_CLANGD_FILE not defined
+
+# Absolute "file" entry: same project, same config, and this one works today.
+# RUN: cd %t.dir/elsewhere && env XDG_CONFIG_HOME=%t.dir/xdg \
+# RUN: clangd-indexer --enable-config --executor=all-TUs \
+# RUN: %t.dir/abs/compile_commands.json > %t.dir/abs.idx 2> %t.dir/abs.log
+# RUN: FileCheck --check-prefix=ABS --input-file=%t.dir/abs.log %s
+# ABS: Processing file {{.*}}test.cpp
+# ABS-NOT: error: "FROM_CLANGD_FILE not defined
+
+#--- proj/.clangd
+CompileFlags:
+ Add: [-DFROM_CLANGD_FILE=1]
+
+#--- proj/test.cpp
+#ifndef FROM_CLANGD_FILE
+#error "FROM_CLANGD_FILE not defined - .clangd file was not applied"
+#endif
+int foo() { return 0; }
>From b1ce4607f8d11bccbe28f7845a4ddfdb538accb7 Mon Sep 17 00:00:00 2001
From: Fil-Den <ffil.denn at gmail.com>
Date: Sat, 5 Sep 2026 11:24:26 +0300
Subject: [PATCH 05/18] config provider now takes ownership
---
clang-tools-extra/clangd/ClangdLSPServer.cpp | 11 ++++++-----
clang-tools-extra/clangd/ClangdLSPServer.h | 4 ++--
clang-tools-extra/clangd/ConfigProvider.cpp | 15 +++------------
clang-tools-extra/clangd/ConfigProvider.h | 18 ++----------------
.../clangd/fuzzer/clangd-fuzzer.cpp | 2 +-
.../clangd/indexer/IndexerMain.cpp | 4 ++--
clang-tools-extra/clangd/tool/Check.cpp | 12 +++++++-----
clang-tools-extra/clangd/tool/ClangdMain.cpp | 9 ++++-----
.../clangd/unittests/ClangdLSPServerTests.cpp | 5 ++---
.../clangd/unittests/ConfigProviderTests.cpp | 7 ++++---
10 files changed, 33 insertions(+), 54 deletions(-)
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index 43e8b35e45c89..296b303bb5123 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -1660,7 +1660,7 @@ void ClangdLSPServer::onAST(const ASTParams &Params,
}
ClangdLSPServer::ClangdLSPServer(Transport &Transp, const ThreadsafeFS &TFS,
- const ClangdLSPServer::Options &Opts)
+ ClangdLSPServer::Options &&Opts)
: ShouldProfile(/*Period=*/std::chrono::minutes(5),
/*Delay=*/std::chrono::minutes(1)),
ShouldCleanupMemory(/*Period=*/std::chrono::minutes(1),
@@ -1668,12 +1668,13 @@ ClangdLSPServer::ClangdLSPServer(Transport &Transp, const ThreadsafeFS &TFS,
BackgroundContext(Context::current().clone()), Transp(Transp),
MsgHandler(new MessageHandler(*this)), TFS(TFS),
SupportedSymbolKinds(defaultSymbolKinds()),
- SupportedCompletionItemKinds(defaultCompletionItemKinds()), Opts(Opts) {
- if (Opts.ConfigProvider) {
- assert(!Opts.ContextProvider &&
+ SupportedCompletionItemKinds(defaultCompletionItemKinds()),
+ Opts(std::move(Opts)) {
+ if (this->Opts.ConfigProvider) {
+ assert(!this->Opts.ContextProvider &&
"Only one of ConfigProvider and ContextProvider allowed!");
this->Opts.ContextProvider = ClangdServer::createConfiguredContextProvider(
- Opts.ConfigProvider, this);
+ this->Opts.ConfigProvider.get(), this);
}
LSPBinder Bind(this->Handlers, *this);
Bind.method("initialize", this, &ClangdLSPServer::onInitialize);
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h
index 6ada3fd9e6e47..395c201b052ed 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.h
+++ b/clang-tools-extra/clangd/ClangdLSPServer.h
@@ -41,7 +41,7 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
public:
struct Options : ClangdServer::Options {
/// Supplies configuration (overrides ClangdServer::ContextProvider).
- config::Provider *ConfigProvider = nullptr;
+ std::unique_ptr<config::Provider> ConfigProvider;
/// Look for compilation databases, rather than using compile commands
/// set via LSP (extensions) only.
bool UseDirBasedCDB = true;
@@ -69,7 +69,7 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
};
ClangdLSPServer(Transport &Transp, const ThreadsafeFS &TFS,
- const ClangdLSPServer::Options &Opts);
+ ClangdLSPServer::Options &&Opts);
/// The destructor blocks on any outstanding background tasks.
~ClangdLSPServer();
diff --git a/clang-tools-extra/clangd/ConfigProvider.cpp b/clang-tools-extra/clangd/ConfigProvider.cpp
index a5bfee9ec303f..b8308aafafab2 100644
--- a/clang-tools-extra/clangd/ConfigProvider.cpp
+++ b/clang-tools-extra/clangd/ConfigProvider.cpp
@@ -146,9 +146,9 @@ Provider::fromAncestorRelativeYAMLFiles(llvm::StringRef RelPath,
}
std::unique_ptr<Provider>
-Provider::combine(std::vector<const Provider *> Providers) {
+Provider::combine(std::vector<std::unique_ptr<Provider>> Providers) {
class CombinedProvider : public Provider {
- std::vector<const Provider *> Providers;
+ std::vector<std::unique_ptr<Provider>> Providers;
std::vector<CompiledFragment>
getFragments(const Params &P, DiagnosticCallback DC) const override {
@@ -161,22 +161,13 @@ Provider::combine(std::vector<const Provider *> Providers) {
}
public:
- CombinedProvider(std::vector<const Provider *> Providers)
+ CombinedProvider(std::vector<std::unique_ptr<Provider>> Providers)
: Providers(std::move(Providers)) {}
};
return std::make_unique<CombinedProvider>(std::move(Providers));
}
-Provider::OwningProvider
-Provider::combineOwned(std::vector<std::unique_ptr<Provider>> Sources) {
- std::vector<const Provider *> Pointers;
- Pointers.reserve(Sources.size());
- for (const auto &P : Sources)
- Pointers.push_back(P.get());
- return {combine(std::move(Pointers)), std::move(Sources)};
-}
-
std::vector<std::unique_ptr<Provider>>
Provider::createDefaultProviders(const ThreadsafeFS &TFS) {
std::vector<std::unique_ptr<Provider>> Providers;
diff --git a/clang-tools-extra/clangd/ConfigProvider.h b/clang-tools-extra/clangd/ConfigProvider.h
index 9f5eeee2c8456..ccd2ed6ecc2cd 100644
--- a/clang-tools-extra/clangd/ConfigProvider.h
+++ b/clang-tools-extra/clangd/ConfigProvider.h
@@ -82,7 +82,8 @@ class Provider {
/// A provider that includes fragments from all the supplied providers.
/// Order is preserved; later providers take precedence over earlier ones.
- static std::unique_ptr<Provider> combine(std::vector<const Provider *>);
+ static std::unique_ptr<Provider>
+ combine(std::vector<std::unique_ptr<Provider>>);
/// Returns providers for the configuration files that clangd tools read by
/// default: project config (ancestor `.clangd` files) and the user's
@@ -90,21 +91,6 @@ class Provider {
static std::vector<std::unique_ptr<Provider>>
createDefaultProviders(const ThreadsafeFS &);
- /// The result of combining several providers, bundled together with the
- /// providers themselves. combine() only stores raw pointers to the
- /// providers it combines, so those providers must outlive it; keeping
- /// them together in one movable object (rather than as separate
- /// same-scope variables at the call site) makes it hard to accidentally
- /// let them go out of scope before Combined does.
- struct OwningProvider {
- std::unique_ptr<Provider> Combined;
- std::vector<std::unique_ptr<Provider>> Sources;
- };
-
- /// Like combine(), but takes ownership of the providers being combined.
- static OwningProvider
- combineOwned(std::vector<std::unique_ptr<Provider>> Sources);
-
/// Build a config based on this provider.
Config getConfig(const Params &, DiagnosticCallback) const;
diff --git a/clang-tools-extra/clangd/fuzzer/clangd-fuzzer.cpp b/clang-tools-extra/clangd/fuzzer/clangd-fuzzer.cpp
index 7295f03852e7d..1ce2f5b6370f8 100644
--- a/clang-tools-extra/clangd/fuzzer/clangd-fuzzer.cpp
+++ b/clang-tools-extra/clangd/fuzzer/clangd-fuzzer.cpp
@@ -35,7 +35,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t *Data, size_t Size) {
Opts.UseDirBasedCDB = false;
// Initialize and run ClangdLSPServer.
- ClangdLSPServer LSPServer(*Transport, FS, Opts);
+ ClangdLSPServer LSPServer(*Transport, FS, std::move(Opts));
LSPServer.run();
return 0;
}
diff --git a/clang-tools-extra/clangd/indexer/IndexerMain.cpp b/clang-tools-extra/clangd/indexer/IndexerMain.cpp
index 9479106e829ea..bbf7200d3b657 100644
--- a/clang-tools-extra/clangd/indexer/IndexerMain.cpp
+++ b/clang-tools-extra/clangd/indexer/IndexerMain.cpp
@@ -172,10 +172,10 @@ int main(int argc, const char **argv) {
ProviderStack =
clang::clangd::config::Provider::createDefaultProviders(TFS);
auto ConfigProvider =
- clang::clangd::config::Provider::combineOwned(std::move(ProviderStack));
+ clang::clangd::config::Provider::combine(std::move(ProviderStack));
auto ContextProvider =
clang::clangd::ClangdServer::createConfiguredContextProvider(
- ConfigProvider.Combined.get(), /*Callbacks=*/nullptr);
+ ConfigProvider.get(), /*Callbacks=*/nullptr);
// Collect symbols found in each translation unit, merging as we go.
clang::clangd::IndexFileIn Data;
diff --git a/clang-tools-extra/clangd/tool/Check.cpp b/clang-tools-extra/clangd/tool/Check.cpp
index 03c4f58a49c9c..8284454c6cb5a 100644
--- a/clang-tools-extra/clangd/tool/Check.cpp
+++ b/clang-tools-extra/clangd/tool/Check.cpp
@@ -142,7 +142,7 @@ std::vector<std::string> listTidyChecks(llvm::StringRef Glob) {
class Checker {
// from constructor
std::string File;
- ClangdLSPServer::Options Opts;
+ const ClangdLSPServer::Options &Opts;
// from buildCommand
tooling::CompileCommand Cmd;
std::unique_ptr<GlobalCompilationDatabase> BaseCDB;
@@ -463,7 +463,7 @@ class Checker {
} // namespace
bool check(llvm::StringRef File, const ThreadsafeFS &TFS,
- const ClangdLSPServer::Options &Opts) {
+ ClangdLSPServer::Options &&Opts) {
std::optional<Range> LineRange;
if (!CheckFileLines.empty()) {
uint32_t Begin = 0, End = std::numeric_limits<uint32_t>::max();
@@ -510,9 +510,11 @@ bool check(llvm::StringRef File, const ThreadsafeFS &TFS,
F.Diagnostics.ClangTidy.FastCheckFilter.emplace("None");
return {std::move(F).compile(Diag)};
}
- } OverrideConfig;
- auto ConfigProvider =
- config::Provider::combine({Opts.ConfigProvider, &OverrideConfig});
+ };
+ std::vector<std::unique_ptr<config::Provider>> ConfigProviders;
+ ConfigProviders.push_back(std::move(Opts.ConfigProvider));
+ ConfigProviders.push_back(std::make_unique<OverrideConfigProvider>());
+ auto ConfigProvider = config::Provider::combine(std::move(ConfigProviders));
auto ContextProvider = ClangdServer::createConfiguredContextProvider(
ConfigProvider.get(), nullptr);
diff --git a/clang-tools-extra/clangd/tool/ClangdMain.cpp b/clang-tools-extra/clangd/tool/ClangdMain.cpp
index c0bf11a6c8a44..cb8bcef990cac 100644
--- a/clang-tools-extra/clangd/tool/ClangdMain.cpp
+++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp
@@ -66,7 +66,7 @@ namespace clangd {
// Implemented in Check.cpp.
bool check(const llvm::StringRef File, const ThreadsafeFS &TFS,
- const ClangdLSPServer::Options &Opts);
+ ClangdLSPServer::Options &&Opts);
namespace {
@@ -993,8 +993,7 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
if (EnableConfig)
ProviderStack = config::Provider::createDefaultProviders(TFS);
ProviderStack.push_back(std::make_unique<FlagsConfigProvider>());
- auto Config = config::Provider::combineOwned(std::move(ProviderStack));
- Opts.ConfigProvider = Config.Combined.get();
+ Opts.ConfigProvider = config::Provider::combine(std::move(ProviderStack));
// Create an empty clang-tidy option.
TidyProvider ClangTidyOptProvider;
@@ -1033,7 +1032,7 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
return 1;
}
log("Entering check mode (no LSP server)");
- return check(Path, TFS, Opts)
+ return check(Path, TFS, std::move(Opts))
? 0
: static_cast<int>(ErrorResultCode::CheckFailed);
}
@@ -1070,7 +1069,7 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
std::move(*Mappings));
}
- ClangdLSPServer LSPServer(*TransportLayer, TFS, Opts);
+ ClangdLSPServer LSPServer(*TransportLayer, TFS, std::move(Opts));
llvm::set_thread_name("clangd.main");
int ExitCode = LSPServer.run()
? 0
diff --git a/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp b/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp
index 5d6a69c953e17..551504ffb6544 100644
--- a/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp
+++ b/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp
@@ -67,7 +67,7 @@ class LSPTest : public ::testing::Test {
LSPClient &start() {
EXPECT_FALSE(Server) << "Already initialized";
- Server.emplace(Client.transport(), FS, Opts);
+ Server.emplace(Client.transport(), FS, std::move(Opts));
ServerThread.emplace([&] { EXPECT_TRUE(Server->run()); });
Client.call("initialize", llvm::json::Object{});
return Client;
@@ -308,9 +308,8 @@ TEST_F(LSPTest, IncomingCalls) {
}
TEST_F(LSPTest, CDBConfigIntegration) {
- auto CfgProvider =
+ Opts.ConfigProvider =
config::Provider::fromAncestorRelativeYAMLFiles(".clangd", FS);
- Opts.ConfigProvider = CfgProvider.get();
// Map bar.cpp to a different compilation database which defines FOO->BAR.
FS.Files[".clangd"] = R"yaml(
diff --git a/clang-tools-extra/clangd/unittests/ConfigProviderTests.cpp b/clang-tools-extra/clangd/unittests/ConfigProviderTests.cpp
index 36a9c383e058e..d69d708a464d5 100644
--- a/clang-tools-extra/clangd/unittests/ConfigProviderTests.cpp
+++ b/clang-tools-extra/clangd/unittests/ConfigProviderTests.cpp
@@ -58,9 +58,10 @@ std::vector<std::string> getAddedArgs(Config &C) {
// cache their results.
TEST(ProviderTest, Combine) {
CapturedDiags Diags;
- FakeProvider Foo("foo");
- FakeProvider Bar("bar");
- auto Combined = Provider::combine({&Foo, &Bar});
+ std::vector<std::unique_ptr<Provider>> Providers;
+ Providers.push_back(std::make_unique<FakeProvider>("foo"));
+ Providers.push_back(std::make_unique<FakeProvider>("bar"));
+ auto Combined = Provider::combine(std::move(Providers));
Config Cfg = Combined->getConfig(Params(), Diags.callback());
EXPECT_THAT(Diags.Diagnostics,
ElementsAre(diagMessage("foo"), diagMessage("bar")));
>From a02e0e6c2295a001465b800560f5194a14392fa2 Mon Sep 17 00:00:00 2001
From: Fil-Den <ffil.denn at gmail.com>
Date: Sat, 5 Sep 2026 11:46:59 +0300
Subject: [PATCH 06/18] Change test on actual behaviour
---
.../clangd/test/indexer-clangd-config-relative-path.test | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang-tools-extra/clangd/test/indexer-clangd-config-relative-path.test b/clang-tools-extra/clangd/test/indexer-clangd-config-relative-path.test
index 261bcd2a3fd38..e2b08fc25b323 100644
--- a/clang-tools-extra/clangd/test/indexer-clangd-config-relative-path.test
+++ b/clang-tools-extra/clangd/test/indexer-clangd-config-relative-path.test
@@ -20,13 +20,13 @@
# that a process-CWD-relative lookup is guaranteed to look in the wrong place.
# Relative "file" entry: the .clangd file's CompileFlags.Add should still be
-# applied, so the #error must not fire.
+# applied, so the #error must not fire, but indexer can't handle it yet.
# RUN: cd %t.dir/elsewhere && env XDG_CONFIG_HOME=%t.dir/xdg \
# RUN: clangd-indexer --enable-config --executor=all-TUs \
# RUN: %t.dir/rel/compile_commands.json > %t.dir/rel.idx 2> %t.dir/rel.log
# RUN: FileCheck --check-prefix=REL --input-file=%t.dir/rel.log %s
# REL: Processing file {{.*}}test.cpp
-# REL-NOT: error: "FROM_CLANGD_FILE not defined
+# REL: error: "FROM_CLANGD_FILE not defined
# Absolute "file" entry: same project, same config, and this one works today.
# RUN: cd %t.dir/elsewhere && env XDG_CONFIG_HOME=%t.dir/xdg \
>From 0b4eb731afb9a319296f3df99f4d3629e85c985a Mon Sep 17 00:00:00 2001
From: Fil-Den <ffil.denn at gmail.com>
Date: Mon, 7 Sep 2026 23:07:43 +0300
Subject: [PATCH 07/18] add info about parsing config files
---
.../clangd/indexer/IndexerMain.cpp | 40 +++++++++++++++++--
1 file changed, 37 insertions(+), 3 deletions(-)
diff --git a/clang-tools-extra/clangd/indexer/IndexerMain.cpp b/clang-tools-extra/clangd/indexer/IndexerMain.cpp
index bbf7200d3b657..47444af3450c1 100644
--- a/clang-tools-extra/clangd/indexer/IndexerMain.cpp
+++ b/clang-tools-extra/clangd/indexer/IndexerMain.cpp
@@ -10,9 +10,9 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangdServer.h"
#include "CompileCommands.h"
#include "Compiler.h"
+#include "Config.h"
#include "ConfigProvider.h"
#include "index/IndexAction.h"
#include "index/Merge.h"
@@ -62,6 +62,41 @@ static llvm::cl::opt<bool> EnableConfig{
llvm::cl::init(false),
};
+std::function<Context(llvm::StringRef)>
+createConfiguredContextProvider(const config::Provider *Provider) {
+ if (!Provider)
+ return [](llvm::StringRef) { return Context::current().clone(); };
+
+ return [Provider](llvm::StringRef File) {
+ config::Params Params;
+ llvm::SmallString<256> PosixPath;
+ if (!File.empty()) {
+ assert(llvm::sys::path::is_absolute(File));
+ llvm::sys::path::native(File, PosixPath, llvm::sys::path::Style::posix);
+ Params.Path = PosixPath.str();
+ }
+
+ Config C = Provider->getConfig(Params, [](const llvm::SMDiagnostic &D) {
+ switch (D.getKind()) {
+ case llvm::SourceMgr::DK_Error:
+ elog("config error at {0}:{1}:{2}: {3}", D.getFilename(), D.getLineNo(),
+ D.getColumnNo(), D.getMessage());
+ break;
+ case llvm::SourceMgr::DK_Warning:
+ log("config warning at {0}:{1}:{2}: {3}", D.getFilename(),
+ D.getLineNo(), D.getColumnNo(), D.getMessage());
+ break;
+ case llvm::SourceMgr::DK_Note:
+ case llvm::SourceMgr::DK_Remark:
+ vlog("config note at {0}:{1}:{2}: {3}", D.getFilename(), D.getLineNo(),
+ D.getColumnNo(), D.getMessage());
+ break;
+ }
+ });
+ return Context::current().derive(Config::Key, std::move(C));
+ };
+}
+
class IndexActionFactory : public tooling::FrontendActionFactory {
public:
IndexActionFactory(IndexFileIn &Result) : Result(Result) {}
@@ -174,8 +209,7 @@ int main(int argc, const char **argv) {
auto ConfigProvider =
clang::clangd::config::Provider::combine(std::move(ProviderStack));
auto ContextProvider =
- clang::clangd::ClangdServer::createConfiguredContextProvider(
- ConfigProvider.get(), /*Callbacks=*/nullptr);
+ clang::clangd::createConfiguredContextProvider(ConfigProvider.get());
// Collect symbols found in each translation unit, merging as we go.
clang::clangd::IndexFileIn Data;
>From ec49f95e59fc15c297738665694de31a8dba6803 Mon Sep 17 00:00:00 2001
From: Fil-Den <ffil.denn at gmail.com>
Date: Tue, 15 Sep 2026 23:39:25 +0300
Subject: [PATCH 08/18] fix test
---
.../clangd/test/indexer-clangd-config.test | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/clang-tools-extra/clangd/test/indexer-clangd-config.test b/clang-tools-extra/clangd/test/indexer-clangd-config.test
index c87e6fb1f1e28..07ff2b6a9511e 100644
--- a/clang-tools-extra/clangd/test/indexer-clangd-config.test
+++ b/clang-tools-extra/clangd/test/indexer-clangd-config.test
@@ -1,16 +1,22 @@
# Test that clangd-indexer respects CompileFlags
+# We specify a custom path in XDG_CONFIG_HOME, which only works on some
+# systems, so that the developer's own user config can't affect the result.
+# UNSUPPORTED: system-windows
+# UNSUPPORTED: system-darwin
+
# RUN: rm -rf %t.dir && mkdir -p %t.dir
# RUN: split-file %s %t.dir
# By default config is disabled, so the .clangd file's CompileFlags.Add is not
# applied and the #error below must fire.
-# RUN: clangd-indexer %t.dir/test.cpp 2>&1 | FileCheck --check-prefix=DISABLED %s
+# RUN: env XDG_CONFIG_HOME=%t clangd-indexer %t.dir/test.cpp 2>&1 \
+# RUN: | FileCheck --check-prefix=DISABLED %s
# DISABLED: error: "FROM_CLANGD_FILE not defined - .clangd file was not applied"
# With config enabled, the .clangd file's CompileFlags.Add should define
# FROM_CLANGD_FILE, so the #error below must not fire.
-# RUN: clangd-indexer --enable-config %t.dir/test.cpp 2>&1 \
+# RUN: env XDG_CONFIG_HOME=%t clangd-indexer --enable-config %t.dir/test.cpp 2>&1 \
# RUN: | FileCheck --check-prefix=ENABLED %s
# ENABLED-NOT: error: "FROM_CLANGD_FILE not defined
>From 4e5106893fa02b30f4692f4eb5060f47c97aa30b Mon Sep 17 00:00:00 2001
From: Fil-Den <ffil.denn at gmail.com>
Date: Tue, 15 Sep 2026 23:42:45 +0300
Subject: [PATCH 09/18] separate stderr and stdout in test
---
.../clangd/test/indexer-clangd-config.test | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/clang-tools-extra/clangd/test/indexer-clangd-config.test b/clang-tools-extra/clangd/test/indexer-clangd-config.test
index 07ff2b6a9511e..440070d375821 100644
--- a/clang-tools-extra/clangd/test/indexer-clangd-config.test
+++ b/clang-tools-extra/clangd/test/indexer-clangd-config.test
@@ -10,14 +10,16 @@
# By default config is disabled, so the .clangd file's CompileFlags.Add is not
# applied and the #error below must fire.
-# RUN: env XDG_CONFIG_HOME=%t clangd-indexer %t.dir/test.cpp 2>&1 \
-# RUN: | FileCheck --check-prefix=DISABLED %s
+# RUN: env XDG_CONFIG_HOME=%t clangd-indexer %t.dir/test.cpp \
+# RUN: > %t.dir/disabled.idx 2> %t.dir/disabled.log
+# RUN: FileCheck --check-prefix=DISABLED --input-file=%t.dir/disabled.log %s
# DISABLED: error: "FROM_CLANGD_FILE not defined - .clangd file was not applied"
# With config enabled, the .clangd file's CompileFlags.Add should define
# FROM_CLANGD_FILE, so the #error below must not fire.
-# RUN: env XDG_CONFIG_HOME=%t clangd-indexer --enable-config %t.dir/test.cpp 2>&1 \
-# RUN: | FileCheck --check-prefix=ENABLED %s
+# RUN: env XDG_CONFIG_HOME=%t clangd-indexer --enable-config %t.dir/test.cpp \
+# RUN: > %t.dir/enabled.idx 2> %t.dir/enabled.log
+# RUN: FileCheck --check-prefix=ENABLED --input-file=%t.dir/enabled.log %s
# ENABLED-NOT: error: "FROM_CLANGD_FILE not defined
#--- .clangd
>From b7aefa3d595a1f21f472bfcbdf54de9bd28547e2 Mon Sep 17 00:00:00 2001
From: Fil-Den <ffil.denn at gmail.com>
Date: Tue, 15 Sep 2026 23:50:29 +0300
Subject: [PATCH 10/18] add positive check to test
---
.../clangd/test/indexer-clangd-config.test | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/clang-tools-extra/clangd/test/indexer-clangd-config.test b/clang-tools-extra/clangd/test/indexer-clangd-config.test
index 440070d375821..ce67f9d1eb183 100644
--- a/clang-tools-extra/clangd/test/indexer-clangd-config.test
+++ b/clang-tools-extra/clangd/test/indexer-clangd-config.test
@@ -7,26 +7,28 @@
# RUN: rm -rf %t.dir && mkdir -p %t.dir
# RUN: split-file %s %t.dir
+# RUN: echo '[{"directory":"%/t.dir/proj","command":"clang++ -c test.cpp","file":"%/t.dir/proj/test.cpp"}]' > %t.dir/compile_commands.json
# By default config is disabled, so the .clangd file's CompileFlags.Add is not
# applied and the #error below must fire.
-# RUN: env XDG_CONFIG_HOME=%t clangd-indexer %t.dir/test.cpp \
+# RUN: env XDG_CONFIG_HOME=%t clangd-indexer %t.dir/proj/test.cpp \
# RUN: > %t.dir/disabled.idx 2> %t.dir/disabled.log
# RUN: FileCheck --check-prefix=DISABLED --input-file=%t.dir/disabled.log %s
# DISABLED: error: "FROM_CLANGD_FILE not defined - .clangd file was not applied"
# With config enabled, the .clangd file's CompileFlags.Add should define
# FROM_CLANGD_FILE, so the #error below must not fire.
-# RUN: env XDG_CONFIG_HOME=%t clangd-indexer --enable-config %t.dir/test.cpp \
-# RUN: > %t.dir/enabled.idx 2> %t.dir/enabled.log
+# RUN: env XDG_CONFIG_HOME=%t clangd-indexer --enable-config --executor=all-TUs \
+# RUN: %t.dir/compile_commands.json > %t.dir/enabled.idx 2> %t.dir/enabled.log
# RUN: FileCheck --check-prefix=ENABLED --input-file=%t.dir/enabled.log %s
+# ENABLED: Processing file {{.*}}test.cpp
# ENABLED-NOT: error: "FROM_CLANGD_FILE not defined
-#--- .clangd
+#--- proj/.clangd
CompileFlags:
Add: [-DFROM_CLANGD_FILE=1]
-#--- test.cpp
+#--- proj/test.cpp
#ifndef FROM_CLANGD_FILE
#error "FROM_CLANGD_FILE not defined - .clangd file was not applied"
#endif
>From 3ec35e7d4e3e945996182682c5e3f20ac1e2802e Mon Sep 17 00:00:00 2001
From: Fil-Den <ffil.denn at gmail.com>
Date: Wed, 16 Sep 2026 23:37:50 +0300
Subject: [PATCH 11/18] add comments about current limitations
---
clang-tools-extra/clangd/indexer/IndexerMain.cpp | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/clang-tools-extra/clangd/indexer/IndexerMain.cpp b/clang-tools-extra/clangd/indexer/IndexerMain.cpp
index 47444af3450c1..4fd86f871c482 100644
--- a/clang-tools-extra/clangd/indexer/IndexerMain.cpp
+++ b/clang-tools-extra/clangd/indexer/IndexerMain.cpp
@@ -224,8 +224,17 @@ int main(int argc, const char **argv) {
[Mangler = std::move(Mangler),
ContextProvider = std::move(ContextProvider)](
const std::vector<std::string> &Args, llvm::StringRef File) {
+ // Issue: If File is relative, it's relative to the compile command's
+ // "directory", not our CWD, but ToolExecutor doesn't expose
+ // "directory" here, so make_absolute can resolve it wrong and
+ // miss the .clangd file. See indexer-clangd-config-relative-path.test.
llvm::SmallString<256> AbsFile(File);
llvm::sys::fs::make_absolute(AbsFile);
+ // Issue: WithCfg only lives for this ArgumentsAdjuster call, so it's
+ // visible to Mangler below but not to the parse that follows.
+ // That's harmless today since clangd-indexer doesn't consult
+ // config during the parse, but a real fix would need libTooling
+ // changes to keep the context alive for the whole invocation.
clang::clangd::WithContext WithCfg(ContextProvider(AbsFile));
clang::tooling::CompileCommand Cmd;
>From 812a068213fe523685a1553777479c79b67b7f70 Mon Sep 17 00:00:00 2001
From: Fil-Den <ffil.denn at gmail.com>
Date: Thu, 24 Sep 2026 13:39:03 +0300
Subject: [PATCH 12/18] format code
---
clang-tools-extra/clangd/indexer/IndexerMain.cpp | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/clang-tools-extra/clangd/indexer/IndexerMain.cpp b/clang-tools-extra/clangd/indexer/IndexerMain.cpp
index 4fd86f871c482..2bc4bfaf860ba 100644
--- a/clang-tools-extra/clangd/indexer/IndexerMain.cpp
+++ b/clang-tools-extra/clangd/indexer/IndexerMain.cpp
@@ -224,14 +224,15 @@ int main(int argc, const char **argv) {
[Mangler = std::move(Mangler),
ContextProvider = std::move(ContextProvider)](
const std::vector<std::string> &Args, llvm::StringRef File) {
- // Issue: If File is relative, it's relative to the compile command's
- // "directory", not our CWD, but ToolExecutor doesn't expose
- // "directory" here, so make_absolute can resolve it wrong and
- // miss the .clangd file. See indexer-clangd-config-relative-path.test.
+ // Issue: If File is relative, it's relative to the compile
+ // command's "directory", not our CWD, but ToolExecutor doesn't
+ // expose "directory" here, so make_absolute can resolve it wrong
+ // and miss the .clangd file. See
+ // indexer-clangd-config-relative-path.test.
llvm::SmallString<256> AbsFile(File);
llvm::sys::fs::make_absolute(AbsFile);
- // Issue: WithCfg only lives for this ArgumentsAdjuster call, so it's
- // visible to Mangler below but not to the parse that follows.
+ // Issue: WithCfg only lives for this ArgumentsAdjuster call, so
+ // it's visible to Mangler below but not to the parse that follows.
// That's harmless today since clangd-indexer doesn't consult
// config during the parse, but a real fix would need libTooling
// changes to keep the context alive for the whole invocation.
>From 62ce4202c4f38706bace01ac12d959b2e94aa53a Mon Sep 17 00:00:00 2001
From: Fil-Den <ffil.denn at gmail.com>
Date: Fri, 25 Sep 2026 12:09:45 +0300
Subject: [PATCH 13/18] format code
---
clang-tools-extra/clangd/ConfigProvider.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang-tools-extra/clangd/ConfigProvider.h b/clang-tools-extra/clangd/ConfigProvider.h
index ccd2ed6ecc2cd..2a965cd8ffe2d 100644
--- a/clang-tools-extra/clangd/ConfigProvider.h
+++ b/clang-tools-extra/clangd/ConfigProvider.h
@@ -83,7 +83,7 @@ class Provider {
/// A provider that includes fragments from all the supplied providers.
/// Order is preserved; later providers take precedence over earlier ones.
static std::unique_ptr<Provider>
- combine(std::vector<std::unique_ptr<Provider>>);
+ combine(std::vector<std::unique_ptr<Provider>>);
/// Returns providers for the configuration files that clangd tools read by
/// default: project config (ancestor `.clangd` files) and the user's
>From 054bb7577a585c78d1ee6a693d9db554f8a6ebd4 Mon Sep 17 00:00:00 2001
From: Fil-Den <ffil.denn at gmail.com>
Date: Fri, 25 Sep 2026 12:16:34 +0300
Subject: [PATCH 14/18] Revert "add info about parsing config files"
This reverts commit 0b4eb731afb9a319296f3df99f4d3629e85c985a.
---
.../clangd/indexer/IndexerMain.cpp | 40 ++-----------------
1 file changed, 3 insertions(+), 37 deletions(-)
diff --git a/clang-tools-extra/clangd/indexer/IndexerMain.cpp b/clang-tools-extra/clangd/indexer/IndexerMain.cpp
index 2bc4bfaf860ba..0af8624080c31 100644
--- a/clang-tools-extra/clangd/indexer/IndexerMain.cpp
+++ b/clang-tools-extra/clangd/indexer/IndexerMain.cpp
@@ -10,9 +10,9 @@
//
//===----------------------------------------------------------------------===//
+#include "ClangdServer.h"
#include "CompileCommands.h"
#include "Compiler.h"
-#include "Config.h"
#include "ConfigProvider.h"
#include "index/IndexAction.h"
#include "index/Merge.h"
@@ -62,41 +62,6 @@ static llvm::cl::opt<bool> EnableConfig{
llvm::cl::init(false),
};
-std::function<Context(llvm::StringRef)>
-createConfiguredContextProvider(const config::Provider *Provider) {
- if (!Provider)
- return [](llvm::StringRef) { return Context::current().clone(); };
-
- return [Provider](llvm::StringRef File) {
- config::Params Params;
- llvm::SmallString<256> PosixPath;
- if (!File.empty()) {
- assert(llvm::sys::path::is_absolute(File));
- llvm::sys::path::native(File, PosixPath, llvm::sys::path::Style::posix);
- Params.Path = PosixPath.str();
- }
-
- Config C = Provider->getConfig(Params, [](const llvm::SMDiagnostic &D) {
- switch (D.getKind()) {
- case llvm::SourceMgr::DK_Error:
- elog("config error at {0}:{1}:{2}: {3}", D.getFilename(), D.getLineNo(),
- D.getColumnNo(), D.getMessage());
- break;
- case llvm::SourceMgr::DK_Warning:
- log("config warning at {0}:{1}:{2}: {3}", D.getFilename(),
- D.getLineNo(), D.getColumnNo(), D.getMessage());
- break;
- case llvm::SourceMgr::DK_Note:
- case llvm::SourceMgr::DK_Remark:
- vlog("config note at {0}:{1}:{2}: {3}", D.getFilename(), D.getLineNo(),
- D.getColumnNo(), D.getMessage());
- break;
- }
- });
- return Context::current().derive(Config::Key, std::move(C));
- };
-}
-
class IndexActionFactory : public tooling::FrontendActionFactory {
public:
IndexActionFactory(IndexFileIn &Result) : Result(Result) {}
@@ -209,7 +174,8 @@ int main(int argc, const char **argv) {
auto ConfigProvider =
clang::clangd::config::Provider::combine(std::move(ProviderStack));
auto ContextProvider =
- clang::clangd::createConfiguredContextProvider(ConfigProvider.get());
+ clang::clangd::ClangdServer::createConfiguredContextProvider(
+ ConfigProvider.get(), /*Callbacks=*/nullptr);
// Collect symbols found in each translation unit, merging as we go.
clang::clangd::IndexFileIn Data;
>From 698bf2305fef6290b2b19e63625fba166ac8099f Mon Sep 17 00:00:00 2001
From: Fil-Den <ffil.denn at gmail.com>
Date: Fri, 25 Sep 2026 12:17:43 +0300
Subject: [PATCH 15/18] replace issue by FIXME in comments
---
clang-tools-extra/clangd/indexer/IndexerMain.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang-tools-extra/clangd/indexer/IndexerMain.cpp b/clang-tools-extra/clangd/indexer/IndexerMain.cpp
index 0af8624080c31..a18ce9ef81921 100644
--- a/clang-tools-extra/clangd/indexer/IndexerMain.cpp
+++ b/clang-tools-extra/clangd/indexer/IndexerMain.cpp
@@ -190,14 +190,14 @@ int main(int argc, const char **argv) {
[Mangler = std::move(Mangler),
ContextProvider = std::move(ContextProvider)](
const std::vector<std::string> &Args, llvm::StringRef File) {
- // Issue: If File is relative, it's relative to the compile
+ // FIXME: If File is relative, it's relative to the compile
// command's "directory", not our CWD, but ToolExecutor doesn't
// expose "directory" here, so make_absolute can resolve it wrong
// and miss the .clangd file. See
// indexer-clangd-config-relative-path.test.
llvm::SmallString<256> AbsFile(File);
llvm::sys::fs::make_absolute(AbsFile);
- // Issue: WithCfg only lives for this ArgumentsAdjuster call, so
+ // FIXME: WithCfg only lives for this ArgumentsAdjuster call, so
// it's visible to Mangler below but not to the parse that follows.
// That's harmless today since clangd-indexer doesn't consult
// config during the parse, but a real fix would need libTooling
>From 70496230786403a59b82ee5e9c05acb32e01fad0 Mon Sep 17 00:00:00 2001
From: Fil-Den <ffil.denn at gmail.com>
Date: Fri, 25 Sep 2026 12:22:29 +0300
Subject: [PATCH 16/18] move TFS declaration before Opts declaration
---
clang-tools-extra/clangd/tool/ClangdMain.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang-tools-extra/clangd/tool/ClangdMain.cpp b/clang-tools-extra/clangd/tool/ClangdMain.cpp
index cb8bcef990cac..d7cdcd55292ef 100644
--- a/clang-tools-extra/clangd/tool/ClangdMain.cpp
+++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp
@@ -926,6 +926,7 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
log("Env {0}: {1}", EnvVar, *Val);
}
+ RealThreadsafeFS TFS;
ClangdLSPServer::Options Opts;
Opts.UseDirBasedCDB = (CompileArgsFrom == FilesystemCompileArgs);
Opts.EnableExperimentalModulesSupport = ExperimentalModulesSupport;
@@ -988,7 +989,6 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
// external decls, since currently the index doesn't support C++20 modules.
Opts.CodeComplete.ForceLoadPreamble = ExperimentalModulesSupport;
- RealThreadsafeFS TFS;
std::vector<std::unique_ptr<config::Provider>> ProviderStack;
if (EnableConfig)
ProviderStack = config::Provider::createDefaultProviders(TFS);
>From f3d6c5a44cd0ee275a1a048eb448701bd09403fb Mon Sep 17 00:00:00 2001
From: Fil-Den <ffil.denn at gmail.com>
Date: Fri, 25 Sep 2026 12:26:18 +0300
Subject: [PATCH 17/18] change context only for absolute paths
---
clang-tools-extra/clangd/indexer/IndexerMain.cpp | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/clang-tools-extra/clangd/indexer/IndexerMain.cpp b/clang-tools-extra/clangd/indexer/IndexerMain.cpp
index a18ce9ef81921..94e6b59aa9db3 100644
--- a/clang-tools-extra/clangd/indexer/IndexerMain.cpp
+++ b/clang-tools-extra/clangd/indexer/IndexerMain.cpp
@@ -202,7 +202,10 @@ int main(int argc, const char **argv) {
// That's harmless today since clangd-indexer doesn't consult
// config during the parse, but a real fix would need libTooling
// changes to keep the context alive for the whole invocation.
- clang::clangd::WithContext WithCfg(ContextProvider(AbsFile));
+ std::optional<clang::clangd::WithContext> WithCfg;
+ if (llvm::sys::path::is_absolute(File)) {
+ WithCfg.emplace(ContextProvider(File));
+ }
clang::tooling::CompileCommand Cmd;
Cmd.CommandLine = Args;
>From 323584a433b78451943bab42c560110d1163b3c3 Mon Sep 17 00:00:00 2001
From: Fil-Den <ffil.denn at gmail.com>
Date: Fri, 25 Sep 2026 12:29:41 +0300
Subject: [PATCH 18/18] mark test as expectedly failed
---
.../clangd/test/indexer-clangd-config-relative-path.test | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/clang-tools-extra/clangd/test/indexer-clangd-config-relative-path.test b/clang-tools-extra/clangd/test/indexer-clangd-config-relative-path.test
index e2b08fc25b323..8f4f22c50a132 100644
--- a/clang-tools-extra/clangd/test/indexer-clangd-config-relative-path.test
+++ b/clang-tools-extra/clangd/test/indexer-clangd-config-relative-path.test
@@ -5,6 +5,10 @@
# systems, so that the developer's own user config can't affect the result.
# UNSUPPORTED: system-windows
# UNSUPPORTED: system-darwin
+#
+# FIXME: clangd-indexer does not yet find the .clangd file for relative "file"
+# entries, so the REL check below fails. Remove this XFAIL once that is fixed.
+# XFAIL: *
# RUN: rm -rf %t.dir && mkdir -p %t.dir
# RUN: split-file %s %t.dir
@@ -20,13 +24,13 @@
# that a process-CWD-relative lookup is guaranteed to look in the wrong place.
# Relative "file" entry: the .clangd file's CompileFlags.Add should still be
-# applied, so the #error must not fire, but indexer can't handle it yet.
+# applied, so the #error must not fire.
# RUN: cd %t.dir/elsewhere && env XDG_CONFIG_HOME=%t.dir/xdg \
# RUN: clangd-indexer --enable-config --executor=all-TUs \
# RUN: %t.dir/rel/compile_commands.json > %t.dir/rel.idx 2> %t.dir/rel.log
# RUN: FileCheck --check-prefix=REL --input-file=%t.dir/rel.log %s
# REL: Processing file {{.*}}test.cpp
-# REL: error: "FROM_CLANGD_FILE not defined
+# REL-NOT: error: "FROM_CLANGD_FILE not defined
# Absolute "file" entry: same project, same config, and this one works today.
# RUN: cd %t.dir/elsewhere && env XDG_CONFIG_HOME=%t.dir/xdg \
More information about the cfe-commits
mailing list