[clang-tools-extra] [clangd] Add dynamic plugins support (PR #226995)
Aleksandr Platonov via cfe-commits
cfe-commits at lists.llvm.org
Tue Oct 6 04:36:58 PDT 2026
https://github.com/ArcsinX updated https://github.com/llvm/llvm-project/pull/226995
>From b8fbd5d0c189f9cfa1e7272407afbba87f82fb97 Mon Sep 17 00:00:00 2001
From: Aleksandr Platonov <arcsinx45 at gmail.com>
Date: Thu, 24 Sep 2026 23:33:25 +0300
Subject: [PATCH] [clangd][FeatureModule] Allow dynamic loading
---
clang-tools-extra/clangd/test/CMakeLists.txt | 30 +++++++++++++
.../clangd/test/FeatureModuleExample.cpp | 42 +++++++++++++++++++
.../clangd/test/StandaloneTweakExample.cpp | 32 ++++++++++++++
.../clangd/test/feature-module.test | 14 +++++++
clang-tools-extra/clangd/test/lit.cfg.py | 5 +++
.../clangd/test/lit.site.cfg.py.in | 2 +
.../clangd/test/standalone-tweak.test | 10 +++++
clang-tools-extra/clangd/tool/CMakeLists.txt | 10 +++++
clang-tools-extra/clangd/tool/ClangdMain.cpp | 16 +++++--
clang-tools-extra/docs/ReleaseNotes.md | 3 ++
10 files changed, 160 insertions(+), 4 deletions(-)
create mode 100644 clang-tools-extra/clangd/test/FeatureModuleExample.cpp
create mode 100644 clang-tools-extra/clangd/test/StandaloneTweakExample.cpp
create mode 100644 clang-tools-extra/clangd/test/feature-module.test
create mode 100644 clang-tools-extra/clangd/test/standalone-tweak.test
diff --git a/clang-tools-extra/clangd/test/CMakeLists.txt b/clang-tools-extra/clangd/test/CMakeLists.txt
index 608835cab2fc5e6..401d3a8b156db71 100644
--- a/clang-tools-extra/clangd/test/CMakeLists.txt
+++ b/clang-tools-extra/clangd/test/CMakeLists.txt
@@ -30,6 +30,36 @@ foreach(dep FileCheck count not llvm-config)
endif()
endforeach()
+if (NOT WIN32 OR NOT LLVM_LINK_LLVM_DYLIB)
+ llvm_add_library(
+ ClangdFeatureModuleExample
+ MODULE FeatureModuleExample.cpp
+ PLUGIN_TOOL clangd
+ PARTIAL_SOURCES_INTENDED)
+ llvm_add_library(
+ ClangdStandaloneTweakExample
+ MODULE StandaloneTweakExample.cpp
+ PLUGIN_TOOL clangd
+ PARTIAL_SOURCES_INTENDED)
+endif()
+
+foreach(target ClangdFeatureModuleExample ClangdStandaloneTweakExample)
+ if(TARGET ${target})
+ list(APPEND CLANGD_TEST_DEPS ${target})
+ target_include_directories(${target} PUBLIC BEFORE
+ "${CLANG_TOOLS_SOURCE_DIR}/clangd"
+ "${CMAKE_CURRENT_BINARY_DIR}/.."
+ )
+ if(CLANG_PLUGIN_SUPPORT AND (WIN32 OR CYGWIN))
+ set(LLVM_LINK_COMPONENTS
+ Support
+ )
+ endif()
+ endif()
+endforeach()
+
+llvm_canonicalize_cmake_booleans(CLANG_PLUGIN_SUPPORT)
+
configure_lit_site_cfg(
${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in
${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg.py
diff --git a/clang-tools-extra/clangd/test/FeatureModuleExample.cpp b/clang-tools-extra/clangd/test/FeatureModuleExample.cpp
new file mode 100644
index 000000000000000..e1e86678f30f538
--- /dev/null
+++ b/clang-tools-extra/clangd/test/FeatureModuleExample.cpp
@@ -0,0 +1,42 @@
+//===--- FeatureModuleExample.cpp - Example dynamic FeatureModule ---------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "FeatureModule.h"
+#include "refactor/Tweak.h"
+#include "support/Logger.h"
+
+namespace clang::clangd {
+namespace {
+
+/// An example tweak contributed by the dynamic feature module.
+class ExampleModuleTweak final : public Tweak {
+public:
+ const char *id() const override { return "ExampleModuleTweak"; }
+ bool prepare(const Selection &) override { return true; }
+ Expected<Effect> apply(const Selection &) override {
+ return Effect::showMessage("Example module tweak executed");
+ }
+ std::string title() const override { return "Execute example module action"; }
+ llvm::StringLiteral kind() const override {
+ return llvm::StringLiteral("quickfix");
+ }
+};
+
+/// An example FeatureModule loaded dynamically into clangd.
+class ExampleFeatureModule final : public FeatureModule {
+public:
+ void contributeTweaks(std::vector<std::unique_ptr<Tweak>> &Out) override {
+ Out.emplace_back(new ExampleModuleTweak);
+ }
+};
+
+static FeatureModuleRegistry::Add<ExampleFeatureModule>
+ X("example-feature-module", "Example clangd feature module");
+
+} // namespace
+} // namespace clang::clangd
diff --git a/clang-tools-extra/clangd/test/StandaloneTweakExample.cpp b/clang-tools-extra/clangd/test/StandaloneTweakExample.cpp
new file mode 100644
index 000000000000000..3afe027fd7ab647
--- /dev/null
+++ b/clang-tools-extra/clangd/test/StandaloneTweakExample.cpp
@@ -0,0 +1,32 @@
+//===--- StandaloneTweakExample.cpp - Example standalone dynamic Tweak ----===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "refactor/Tweak.h"
+
+namespace clang::clangd {
+namespace {
+
+/// A standalone tweak registered directly via REGISTER_TWEAK without a
+/// FeatureModule.
+class StandaloneTweak final : public Tweak {
+public:
+ const char *id() const override;
+ bool prepare(const Selection &) override { return true; }
+ Expected<Effect> apply(const Selection &) override {
+ return Effect::showMessage("Standalone tweak executed");
+ }
+ std::string title() const override { return "Execute standalone action"; }
+ llvm::StringLiteral kind() const override {
+ return llvm::StringLiteral("quickfix");
+ }
+};
+
+REGISTER_TWEAK(StandaloneTweak)
+
+} // namespace
+} // namespace clang::clangd
diff --git a/clang-tools-extra/clangd/test/feature-module.test b/clang-tools-extra/clangd/test/feature-module.test
new file mode 100644
index 000000000000000..e25cf8933f6ff82
--- /dev/null
+++ b/clang-tools-extra/clangd/test/feature-module.test
@@ -0,0 +1,14 @@
+// REQUIRES: plugins
+// RUN: clangd --help | FileCheck --check-prefix=CHECK-HELP %s
+// CHECK-HELP: --load=<pluginfilename> - Load the specified plugin
+
+// RUN: cp %s %t.cpp
+// RUN: clangd -enable-config=0 -log=verbose -check=%t.cpp -load=%llvmshlibdir/ClangdFeatureModuleExample%pluginext 2>&1 | FileCheck -strict-whitespace %s
+
+// CHECK: Loaded plugin: {{.*}}ClangdFeatureModuleExample{{.*}}
+// CHECK: Adding feature module 'example-feature-module' (Example clangd feature module)
+// CHECK: Testing features at each token
+// CHECK-DAG: tweak: ExampleModuleTweak
+// CHECK: All checks completed, 0 errors
+
+int x = 42;
diff --git a/clang-tools-extra/clangd/test/lit.cfg.py b/clang-tools-extra/clangd/test/lit.cfg.py
index aac3a938353e8c5..f56a73b56a9113a 100644
--- a/clang-tools-extra/clangd/test/lit.cfg.py
+++ b/clang-tools-extra/clangd/test/lit.cfg.py
@@ -43,6 +43,11 @@ def calculate_arch_features(arch_string):
if lit.util.pythonize_bool(config.have_benchmarks):
config.available_features.add("have-benchmarks")
+# Plugins (loadable modules)
+if config.has_plugins and config.llvm_plugin_ext:
+ config.available_features.add("plugins")
+ config.substitutions.append(("%pluginext", config.llvm_plugin_ext))
+
# It is not realistically possible to account for all options that could
# possibly be present in system and user configuration files, so disable
# default configs for the test runs.
diff --git a/clang-tools-extra/clangd/test/lit.site.cfg.py.in b/clang-tools-extra/clangd/test/lit.site.cfg.py.in
index f5ae3eb1f0743d5..650006b8b72f57f 100644
--- a/clang-tools-extra/clangd/test/lit.site.cfg.py.in
+++ b/clang-tools-extra/clangd/test/lit.site.cfg.py.in
@@ -20,6 +20,8 @@ config.clangd_enable_remote = @CLANGD_ENABLE_REMOTE@
config.clangd_tidy_checks = @CLANGD_TIDY_CHECKS@
config.have_zlib = @LLVM_ENABLE_ZLIB@
config.have_benchmarks = "@LLVM_INCLUDE_BENCHMARKS@"
+config.has_plugins = @CLANG_PLUGIN_SUPPORT@
+config.llvm_plugin_ext = "@LLVM_PLUGIN_EXT@"
# Delegate logic to lit.cfg.py.
lit_config.load_config(config, "@CMAKE_CURRENT_SOURCE_DIR@/lit.cfg.py")
diff --git a/clang-tools-extra/clangd/test/standalone-tweak.test b/clang-tools-extra/clangd/test/standalone-tweak.test
new file mode 100644
index 000000000000000..c74e81a4a68b3c8
--- /dev/null
+++ b/clang-tools-extra/clangd/test/standalone-tweak.test
@@ -0,0 +1,10 @@
+// REQUIRES: plugins
+// RUN: cp %s %t.cpp
+// RUN: clangd -enable-config=0 -log=verbose -check=%t.cpp -load=%llvmshlibdir/ClangdStandaloneTweakExample%pluginext 2>&1 | FileCheck -strict-whitespace %s
+
+// CHECK: Loaded plugin: {{.*}}ClangdStandaloneTweakExample{{.*}}
+// CHECK: Testing features at each token
+// CHECK-DAG: tweak: StandaloneTweak
+// CHECK: All checks completed, 0 errors
+
+int x = 42;
diff --git a/clang-tools-extra/clangd/tool/CMakeLists.txt b/clang-tools-extra/clangd/tool/CMakeLists.txt
index 1bd6a1f864dd3b0..bae511341219f07 100644
--- a/clang-tools-extra/clangd/tool/CMakeLists.txt
+++ b/clang-tools-extra/clangd/tool/CMakeLists.txt
@@ -9,9 +9,15 @@ add_clang_library(clangdMain STATIC
DISABLE_PCH_REUSE
)
+# Support plugins.
+if(CLANG_PLUGIN_SUPPORT)
+ set(support_plugins SUPPORT_PLUGINS)
+endif()
+
add_clang_tool(clangd
ClangdToolMain.cpp
$<TARGET_OBJECTS:obj.clangDaemonTweaks>
+ ${support_plugins}
)
set(LLVM_LINK_COMPONENTS
@@ -61,3 +67,7 @@ target_link_libraries(clangd
clangDaemon
clangdSupport
)
+
+if(CLANG_PLUGIN_SUPPORT)
+ export_executable_symbols_for_plugins(clangd)
+endif()
diff --git a/clang-tools-extra/clangd/tool/ClangdMain.cpp b/clang-tools-extra/clangd/tool/ClangdMain.cpp
index 9dffa3bef8caa2b..25c987d9c293418 100644
--- a/clang-tools-extra/clangd/tool/ClangdMain.cpp
+++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp
@@ -38,6 +38,7 @@
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/Path.h"
+#include "llvm/Support/PluginLoader.h"
#include "llvm/Support/Process.h"
#include "llvm/Support/Program.h"
#include "llvm/Support/Signals.h"
@@ -788,6 +789,11 @@ It should be used via an editor plugin rather than invoked directly. For more in
clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment variable.
)";
+ // Enable help for -load option, if plugins are enabled.
+ if (llvm::cl::Option *LoadOpt =
+ llvm::cl::getRegisteredOptions().lookup("load"))
+ LoadOpt->addCategory(Features);
+
llvm::cl::HideUnrelatedOptions(ClangdCategories);
llvm::cl::ParseCommandLineOptions(argc, argv, Overview, /*Errs=*/nullptr,
/*VFS=*/nullptr, FlagsEnvVar);
@@ -907,6 +913,8 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
}
for (int I = 0; I < argc; ++I)
log("argv[{0}]: {1}", I, argv[I]);
+ for (unsigned I = 0, E = llvm::PluginLoader::getNumPlugins(); I < E; ++I)
+ log("Loaded plugin: {0}", llvm::PluginLoader::getPlugin(I));
if (auto EnvFlags = llvm::sys::Process::GetEnv(FlagsEnvVar))
log("{0}: {1}", FlagsEnvVar, *EnvFlags);
// Log environment variables that influence how clangd finds system headers.
@@ -1028,6 +1036,10 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
if (ForceOffsetEncoding != OffsetEncoding::UnsupportedEncoding)
Opts.Encoding = ForceOffsetEncoding;
+ FeatureModuleSet ModuleSet = FeatureModuleSet::fromRegistry();
+ if (ModuleSet.begin() != ModuleSet.end())
+ Opts.FeatureModules = &ModuleSet;
+
if (CheckFile.getNumOccurrences()) {
llvm::SmallString<256> Path;
if (auto Error =
@@ -1041,10 +1053,6 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
: static_cast<int>(ErrorResultCode::CheckFailed);
}
- FeatureModuleSet ModuleSet = FeatureModuleSet::fromRegistry();
- if (ModuleSet.begin() != ModuleSet.end())
- Opts.FeatureModules = &ModuleSet;
-
// Initialize and run ClangdLSPServer.
// Change stdin to binary to not lose \r\n on windows.
llvm::sys::ChangeStdinToBinary();
diff --git a/clang-tools-extra/docs/ReleaseNotes.md b/clang-tools-extra/docs/ReleaseNotes.md
index 42451c459059284..a32d3fbee3f9603 100644
--- a/clang-tools-extra/docs/ReleaseNotes.md
+++ b/clang-tools-extra/docs/ReleaseNotes.md
@@ -116,6 +116,9 @@ infrastructure are described first, followed by tool-specific sections.
user config) by default. Pass `--enable-config=false` to restore the previous
behavior.
+- Added support for loading dynamic plugins via the `-load` command-line
+ option.
+
### Improvements to clang-doc
### Improvements to clang-query
More information about the cfe-commits
mailing list