https://github.com/ArcsinX updated https://github.com/llvm/llvm-project/pull/226995
>From e535c83df43ab3da72246f96a3fc6c2a7ce23cfa Mon Sep 17 00:00:00 2001 From: Aleksandr Platonov <[email protected]> Date: Thu, 24 Sep 2026 23:33:25 +0300 Subject: [PATCH 1/7] [clangd][FeatureModule] Allow dynamic loading --- clang-tools-extra/clangd/CMakeLists.txt | 5 + clang-tools-extra/clangd/FeatureModule.cpp | 13 +++ clang-tools-extra/clangd/FeatureModule.h | 7 ++ .../clangd/examples/CMakeLists.txt | 1 + .../SampleFeatureModule/CMakeLists.txt | 34 ++++++ .../examples/SampleFeatureModule/README.md | 78 ++++++++++++++ .../SampleFeatureModule.cpp | 100 ++++++++++++++++++ clang-tools-extra/clangd/test/CMakeLists.txt | 22 ++++ .../clangd/test/FeatureModuleExample.cpp | 42 ++++++++ .../clangd/test/feature-module.test | 14 +++ clang-tools-extra/clangd/test/lit.cfg.py | 5 + .../clangd/test/lit.site.cfg.py.in | 2 + clang-tools-extra/clangd/tool/CMakeLists.txt | 10 ++ clang-tools-extra/clangd/tool/ClangdMain.cpp | 16 ++- .../unittests/FeatureModulesRegistryTests.cpp | 13 ++- 15 files changed, 354 insertions(+), 8 deletions(-) create mode 100644 clang-tools-extra/clangd/examples/CMakeLists.txt create mode 100644 clang-tools-extra/clangd/examples/SampleFeatureModule/CMakeLists.txt create mode 100644 clang-tools-extra/clangd/examples/SampleFeatureModule/README.md create mode 100644 clang-tools-extra/clangd/examples/SampleFeatureModule/SampleFeatureModule.cpp create mode 100644 clang-tools-extra/clangd/test/FeatureModuleExample.cpp create mode 100644 clang-tools-extra/clangd/test/feature-module.test diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt index 3aa1b7bddb994e..e2da91b8968e5c 100644 --- a/clang-tools-extra/clangd/CMakeLists.txt +++ b/clang-tools-extra/clangd/CMakeLists.txt @@ -233,3 +233,8 @@ add_subdirectory(index/remote) if(CLANGD_BUILD_DEXP) add_subdirectory(index/dex/dexp) endif() + +option(CLANGD_BUILD_EXAMPLES "Build clangd examples" OFF) +if(CLANGD_BUILD_EXAMPLES) + add_subdirectory(examples) +endif() diff --git a/clang-tools-extra/clangd/FeatureModule.cpp b/clang-tools-extra/clangd/FeatureModule.cpp index 38461e3db4e6bd..dd72a6dd6c6cbe 100644 --- a/clang-tools-extra/clangd/FeatureModule.cpp +++ b/clang-tools-extra/clangd/FeatureModule.cpp @@ -8,6 +8,8 @@ #include "FeatureModule.h" #include "support/Logger.h" +#include "llvm/Support/DynamicLibrary.h" +#include "llvm/Support/Error.h" namespace clang { namespace clangd { @@ -46,6 +48,17 @@ FeatureModuleSet FeatureModuleSet::fromRegistry() { return ModuleSet; } +llvm::Error loadFeatureModule(llvm::StringRef SharedLibraryPath) { + std::string Err; + if (llvm::sys::DynamicLibrary::LoadLibraryPermanently( + SharedLibraryPath.str().c_str(), &Err)) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Failed to load feature module '%s': %s", + SharedLibraryPath.str().c_str(), + Err.c_str()); + return llvm::Error::success(); +} + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/FeatureModule.h b/clang-tools-extra/clangd/FeatureModule.h index 90714fe9101a83..0b3ba894ae3871 100644 --- a/clang-tools-extra/clangd/FeatureModule.h +++ b/clang-tools-extra/clangd/FeatureModule.h @@ -14,6 +14,7 @@ #include "clang/Basic/Diagnostic.h" #include "llvm/ADT/FunctionExtras.h" #include "llvm/Support/Compiler.h" +#include "llvm/Support/Error.h" #include "llvm/Support/JSON.h" #include "llvm/Support/Registry.h" #include <memory> @@ -215,6 +216,12 @@ template <typename Mod> int FeatureModuleSet::ID<Mod>::Key; using FeatureModuleRegistry = llvm::Registry<FeatureModule>; +/// Dynamically load a feature module from a shared library. +/// The shared library is expected to register one or more FeatureModules +/// via FeatureModuleRegistry. +/// Returns an Error if the library could not be loaded. +llvm::Error loadFeatureModule(llvm::StringRef SharedLibraryPath); + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/examples/CMakeLists.txt b/clang-tools-extra/clangd/examples/CMakeLists.txt new file mode 100644 index 00000000000000..42c3c05d146087 --- /dev/null +++ b/clang-tools-extra/clangd/examples/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(SampleFeatureModule) diff --git a/clang-tools-extra/clangd/examples/SampleFeatureModule/CMakeLists.txt b/clang-tools-extra/clangd/examples/SampleFeatureModule/CMakeLists.txt new file mode 100644 index 00000000000000..eaee5d017d3400 --- /dev/null +++ b/clang-tools-extra/clangd/examples/SampleFeatureModule/CMakeLists.txt @@ -0,0 +1,34 @@ +if(NOT DEFINED LLVM_MAIN_SRC_DIR) + cmake_minimum_required(VERSION 3.20) + project(SampleFeatureModule) + + find_package(LLVM REQUIRED CONFIG) + find_package(Clang REQUIRED CONFIG) + + list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}") + include(AddLLVM) + + include_directories(${LLVM_INCLUDE_DIRS} ${CLANG_INCLUDE_DIRS}) + link_directories(${LLVM_LIBDIR_SUFFIX}) + + add_llvm_library(SampleFeatureModule MODULE + SampleFeatureModule.cpp + PLUGIN_TOOL clangd + ) +else() + add_llvm_library(SampleFeatureModule MODULE + SampleFeatureModule.cpp + PLUGIN_TOOL clangd + ) + + target_include_directories(SampleFeatureModule PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/../../" + "${CMAKE_CURRENT_BINARY_DIR}/../../" + ) + + if(WIN32 OR CYGWIN) + set(LLVM_LINK_COMPONENTS + Support + ) + endif() +endif() diff --git a/clang-tools-extra/clangd/examples/SampleFeatureModule/README.md b/clang-tools-extra/clangd/examples/SampleFeatureModule/README.md new file mode 100644 index 00000000000000..ac8c180d49afbf --- /dev/null +++ b/clang-tools-extra/clangd/examples/SampleFeatureModule/README.md @@ -0,0 +1,78 @@ +# Sample Dynamic FeatureModule for Clangd + +This example demonstrates how to implement, build, and load a **dynamic `FeatureModule`** plugin into `clangd`. + +## Background + +Clangd supports vertical extensions via `FeatureModule`s. Previously, `FeatureModule`s could only be linked statically into the `clangd` binary. With dynamic loading support, feature modules can be compiled into independent shared libraries (`.so` / `.dylib` / `.dll`) and loaded at runtime into `clangd` via the standard LLVM `-load` command-line option. + +When a plugin shared library is loaded: +1. Static initializers in the shared library run. +2. `FeatureModuleRegistry::Add<MyModule> X("name", "description")` registers the module with `FeatureModuleRegistry`. +3. Clangd queries `FeatureModuleSet::fromRegistry()`, which instantiates all registered feature modules and integrates them into the LSP server. + +## Features demonstrated in this example + +- **Tweaks (Code Actions)**: Contributes a custom code action tweak (`SampleModuleTweak`) that appears in editors. +- **AST Listeners**: Observes AST building lifecycle events (`beforeExecute`, `afterExecute`) and diagnostics (`sawDiagnostic`). +- **LSP Bindings**: Demonstrates how to register custom LSP methods, notifications, or commands via `LSPBinder` and modify server capabilities in `initializeLSP`. + +## How to Build + +### In-tree Build + +To build this example as part of LLVM / Clang: + +1. Configure LLVM with examples enabled: + ```bash + cmake -B build -S llvm -DCLANGD_BUILD_EXAMPLES=ON [other options...] + ``` +2. Build the plugin: + ```bash + ninja -C build SampleFeatureModule + ``` + The shared library will be built in `build/lib/SampleFeatureModule.so` (or `.dylib` on macOS). + +### Out-of-tree Build + +You can also build feature modules against an installed LLVM / Clang toolchain: + +```bash +mkdir build && cd build +cmake -DCMAKE_PREFIX_PATH=/path/to/llvm/installation .. +cmake --build . +``` + +## How to Use with Clangd + +### Check Mode + +You can test that your module is loaded and functioning using `clangd --check`: + +```bash +clangd -check=test.cpp -load=/path/to/SampleFeatureModule.so -log=verbose +``` + +Output will show: +``` +Loaded plugin: /path/to/SampleFeatureModule.so +Adding feature module 'sample-feature-module' (Example feature module demonstrating dynamic loading into clangd) +... + tweak: SampleModuleTweak +``` + +### Editor Integration + +Pass `-load=/path/to/SampleFeatureModule.so` to the `clangd` arguments in your editor configuration: + +- **VS Code** (`settings.json`): + ```json + "clangd.arguments": [ + "--load=/path/to/SampleFeatureModule.so" + ] + ``` +- **Environment Variable**: + You can also pass arguments via the `CLANGD_FLAGS` environment variable: + ```bash + export CLANGD_FLAGS="--load=/path/to/SampleFeatureModule.so" + ``` diff --git a/clang-tools-extra/clangd/examples/SampleFeatureModule/SampleFeatureModule.cpp b/clang-tools-extra/clangd/examples/SampleFeatureModule/SampleFeatureModule.cpp new file mode 100644 index 00000000000000..6ae690a14eb15b --- /dev/null +++ b/clang-tools-extra/clangd/examples/SampleFeatureModule/SampleFeatureModule.cpp @@ -0,0 +1,100 @@ +//===--- SampleFeatureModule.cpp - Example Clangd Feature Module ----------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This file implements an example FeatureModule for clangd that can be +// dynamically loaded into clangd at runtime via the `-load` option: +// +// clangd -load=/path/to/SampleFeatureModule.so +// +// When loaded, the module registers itself with clangd's FeatureModuleRegistry +// and demonstrates: +// 1. Contributing custom code action tweaks (Tweak). +// 2. Observing AST builds and diagnostics via ASTListener. +// 3. Registering custom LSP method/command handlers via LSPBinder. +// +//===----------------------------------------------------------------------===// + +#include "FeatureModule.h" +#include "LSPBinder.h" +#include "refactor/Tweak.h" +#include "support/Logger.h" + +namespace clang::clangd { +namespace { + +/// An example code action tweak contributed by the feature module. +class SampleTweak final : public Tweak { +public: + const char *id() const override { return "SampleModuleTweak"; } + + bool prepare(const Selection &Sel) override { + // This tweak is available anywhere in the file. + return true; + } + + Expected<Effect> apply(const Selection &Sel) override { + return Effect::showMessage("Sample FeatureModule tweak executed!"); + } + + std::string title() const override { + return "Execute Sample FeatureModule action"; + } + + llvm::StringLiteral kind() const override { + return llvm::StringLiteral("quickfix"); + } +}; + +/// An example AST listener that observes AST construction events. +class SampleASTListener final : public FeatureModule::ASTListener { +public: + void beforeExecute(CompilerInstance &CI) override { + vlog("SampleFeatureModule: before AST execution"); + } + + void afterExecute(CompilerInstance &CI) override { + vlog("SampleFeatureModule: after AST execution"); + } + + void sawDiagnostic(const clang::Diagnostic &Diag, clangd::Diag &D) override { + vlog("SampleFeatureModule: observed diagnostic '{0}'", D.Message); + } +}; + +/// The main feature module class. +class SampleFeatureModule final : public FeatureModule { +public: + /// Contribute custom tweaks to clangd's code action list. + void contributeTweaks(std::vector<std::unique_ptr<Tweak>> &Out) override { + Out.emplace_back(new SampleTweak); + } + + /// Hook into AST creation to observe AST events and diagnostics. + std::unique_ptr<ASTListener> astListeners() override { + return std::make_unique<SampleASTListener>(); + } + + /// Register custom LSP endpoints or update server capabilities. + void initializeLSP(LSPBinder &Bind, + const llvm::json::Object &ClientCaps, + llvm::json::Object &ServerCaps) override { + vlog("SampleFeatureModule: initializeLSP called"); + // Example: Bind a custom LSP method or notification if needed: + // Bind.method("sample/customMethod", this, &SampleFeatureModule::onCustomMethod); + } +}; + +// Register the module with clangd's FeatureModuleRegistry. +// When clangd loads this shared library via -load, this static initializer +// will run and add the module to clangd's registry. +static FeatureModuleRegistry::Add<SampleFeatureModule> + X("sample-feature-module", + "Example feature module demonstrating dynamic loading into clangd"); + +} // namespace +} // namespace clang::clangd diff --git a/clang-tools-extra/clangd/test/CMakeLists.txt b/clang-tools-extra/clangd/test/CMakeLists.txt index 608835cab2fc5e..17f95f8abc005e 100644 --- a/clang-tools-extra/clangd/test/CMakeLists.txt +++ b/clang-tools-extra/clangd/test/CMakeLists.txt @@ -30,6 +30,28 @@ 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) +endif() + +if(TARGET ClangdFeatureModuleExample) + list(APPEND CLANGD_TEST_DEPS ClangdFeatureModuleExample) + target_include_directories(ClangdFeatureModuleExample 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() + +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 00000000000000..e1e86678f30f53 --- /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/feature-module.test b/clang-tools-extra/clangd/test/feature-module.test new file mode 100644 index 00000000000000..e25cf8933f6ff8 --- /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 aac3a938353e8c..f56a73b56a9113 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 f5ae3eb1f0743d..650006b8b72f57 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/tool/CMakeLists.txt b/clang-tools-extra/clangd/tool/CMakeLists.txt index 1bd6a1f864dd3b..bae511341219f0 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 13fe4d3911731d..dc0e1be1e69461 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" // IWYU pragma: keep #include "llvm/Support/Process.h" #include "llvm/Support/Program.h" #include "llvm/Support/Signals.h" @@ -795,6 +796,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); @@ -910,6 +916,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. @@ -1047,6 +1055,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 = @@ -1060,10 +1072,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/clangd/unittests/FeatureModulesRegistryTests.cpp b/clang-tools-extra/clangd/unittests/FeatureModulesRegistryTests.cpp index b8941cdb4b1783..936070220c0aa5 100644 --- a/clang-tools-extra/clangd/unittests/FeatureModulesRegistryTests.cpp +++ b/clang-tools-extra/clangd/unittests/FeatureModulesRegistryTests.cpp @@ -74,10 +74,9 @@ static FeatureModuleRegistry::Add<Dummy> MATCHER_P(moduleName, Name, "") { return arg.getName() == Name; } MATCHER_P(tweakID, ID, "") { return arg->id() == llvm::StringRef(ID); } -// In this test, it is assumed that for unittests executable, all feature -// modules are added to the registry only here (in this file). To implement -// modules for clangd tool, one need to link them directly to the clangd -// executable in clangd/tool/CMakeLists.txt. +// Feature modules can be added statically via FeatureModuleRegistry::Add, +// or loaded dynamically at runtime via the -load command-line option or +// loadFeatureModule(). TEST(FeatureModulesRegistryTest, DummyModule) { EXPECT_THAT(FeatureModuleRegistry::entries(), ElementsAre(moduleName("dummy"))); @@ -88,5 +87,11 @@ TEST(FeatureModulesRegistryTest, DummyModule) { EXPECT_THAT(Tweaks, ElementsAre(tweakID("DummyTweak"))); } +TEST(FeatureModulesRegistryTest, LoadNonexistentModule) { + llvm::Error Err = loadFeatureModule("nonexistent_module.so"); + EXPECT_TRUE(bool(Err)); + llvm::consumeError(std::move(Err)); +} + } // namespace } // namespace clang::clangd >From eebf8d4c0dc6b65a180ebe7385e8417136780d88 Mon Sep 17 00:00:00 2001 From: Aleksandr Platonov <[email protected]> Date: Thu, 24 Sep 2026 23:48:25 +0300 Subject: [PATCH 2/7] Remove examples dir --- clang-tools-extra/clangd/CMakeLists.txt | 5 - .../clangd/examples/CMakeLists.txt | 1 - .../SampleFeatureModule/CMakeLists.txt | 34 ------ .../examples/SampleFeatureModule/README.md | 78 -------------- .../SampleFeatureModule.cpp | 100 ------------------ 5 files changed, 218 deletions(-) delete mode 100644 clang-tools-extra/clangd/examples/CMakeLists.txt delete mode 100644 clang-tools-extra/clangd/examples/SampleFeatureModule/CMakeLists.txt delete mode 100644 clang-tools-extra/clangd/examples/SampleFeatureModule/README.md delete mode 100644 clang-tools-extra/clangd/examples/SampleFeatureModule/SampleFeatureModule.cpp diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt index e2da91b8968e5c..3aa1b7bddb994e 100644 --- a/clang-tools-extra/clangd/CMakeLists.txt +++ b/clang-tools-extra/clangd/CMakeLists.txt @@ -233,8 +233,3 @@ add_subdirectory(index/remote) if(CLANGD_BUILD_DEXP) add_subdirectory(index/dex/dexp) endif() - -option(CLANGD_BUILD_EXAMPLES "Build clangd examples" OFF) -if(CLANGD_BUILD_EXAMPLES) - add_subdirectory(examples) -endif() diff --git a/clang-tools-extra/clangd/examples/CMakeLists.txt b/clang-tools-extra/clangd/examples/CMakeLists.txt deleted file mode 100644 index 42c3c05d146087..00000000000000 --- a/clang-tools-extra/clangd/examples/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_subdirectory(SampleFeatureModule) diff --git a/clang-tools-extra/clangd/examples/SampleFeatureModule/CMakeLists.txt b/clang-tools-extra/clangd/examples/SampleFeatureModule/CMakeLists.txt deleted file mode 100644 index eaee5d017d3400..00000000000000 --- a/clang-tools-extra/clangd/examples/SampleFeatureModule/CMakeLists.txt +++ /dev/null @@ -1,34 +0,0 @@ -if(NOT DEFINED LLVM_MAIN_SRC_DIR) - cmake_minimum_required(VERSION 3.20) - project(SampleFeatureModule) - - find_package(LLVM REQUIRED CONFIG) - find_package(Clang REQUIRED CONFIG) - - list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}") - include(AddLLVM) - - include_directories(${LLVM_INCLUDE_DIRS} ${CLANG_INCLUDE_DIRS}) - link_directories(${LLVM_LIBDIR_SUFFIX}) - - add_llvm_library(SampleFeatureModule MODULE - SampleFeatureModule.cpp - PLUGIN_TOOL clangd - ) -else() - add_llvm_library(SampleFeatureModule MODULE - SampleFeatureModule.cpp - PLUGIN_TOOL clangd - ) - - target_include_directories(SampleFeatureModule PRIVATE - "${CMAKE_CURRENT_SOURCE_DIR}/../../" - "${CMAKE_CURRENT_BINARY_DIR}/../../" - ) - - if(WIN32 OR CYGWIN) - set(LLVM_LINK_COMPONENTS - Support - ) - endif() -endif() diff --git a/clang-tools-extra/clangd/examples/SampleFeatureModule/README.md b/clang-tools-extra/clangd/examples/SampleFeatureModule/README.md deleted file mode 100644 index ac8c180d49afbf..00000000000000 --- a/clang-tools-extra/clangd/examples/SampleFeatureModule/README.md +++ /dev/null @@ -1,78 +0,0 @@ -# Sample Dynamic FeatureModule for Clangd - -This example demonstrates how to implement, build, and load a **dynamic `FeatureModule`** plugin into `clangd`. - -## Background - -Clangd supports vertical extensions via `FeatureModule`s. Previously, `FeatureModule`s could only be linked statically into the `clangd` binary. With dynamic loading support, feature modules can be compiled into independent shared libraries (`.so` / `.dylib` / `.dll`) and loaded at runtime into `clangd` via the standard LLVM `-load` command-line option. - -When a plugin shared library is loaded: -1. Static initializers in the shared library run. -2. `FeatureModuleRegistry::Add<MyModule> X("name", "description")` registers the module with `FeatureModuleRegistry`. -3. Clangd queries `FeatureModuleSet::fromRegistry()`, which instantiates all registered feature modules and integrates them into the LSP server. - -## Features demonstrated in this example - -- **Tweaks (Code Actions)**: Contributes a custom code action tweak (`SampleModuleTweak`) that appears in editors. -- **AST Listeners**: Observes AST building lifecycle events (`beforeExecute`, `afterExecute`) and diagnostics (`sawDiagnostic`). -- **LSP Bindings**: Demonstrates how to register custom LSP methods, notifications, or commands via `LSPBinder` and modify server capabilities in `initializeLSP`. - -## How to Build - -### In-tree Build - -To build this example as part of LLVM / Clang: - -1. Configure LLVM with examples enabled: - ```bash - cmake -B build -S llvm -DCLANGD_BUILD_EXAMPLES=ON [other options...] - ``` -2. Build the plugin: - ```bash - ninja -C build SampleFeatureModule - ``` - The shared library will be built in `build/lib/SampleFeatureModule.so` (or `.dylib` on macOS). - -### Out-of-tree Build - -You can also build feature modules against an installed LLVM / Clang toolchain: - -```bash -mkdir build && cd build -cmake -DCMAKE_PREFIX_PATH=/path/to/llvm/installation .. -cmake --build . -``` - -## How to Use with Clangd - -### Check Mode - -You can test that your module is loaded and functioning using `clangd --check`: - -```bash -clangd -check=test.cpp -load=/path/to/SampleFeatureModule.so -log=verbose -``` - -Output will show: -``` -Loaded plugin: /path/to/SampleFeatureModule.so -Adding feature module 'sample-feature-module' (Example feature module demonstrating dynamic loading into clangd) -... - tweak: SampleModuleTweak -``` - -### Editor Integration - -Pass `-load=/path/to/SampleFeatureModule.so` to the `clangd` arguments in your editor configuration: - -- **VS Code** (`settings.json`): - ```json - "clangd.arguments": [ - "--load=/path/to/SampleFeatureModule.so" - ] - ``` -- **Environment Variable**: - You can also pass arguments via the `CLANGD_FLAGS` environment variable: - ```bash - export CLANGD_FLAGS="--load=/path/to/SampleFeatureModule.so" - ``` diff --git a/clang-tools-extra/clangd/examples/SampleFeatureModule/SampleFeatureModule.cpp b/clang-tools-extra/clangd/examples/SampleFeatureModule/SampleFeatureModule.cpp deleted file mode 100644 index 6ae690a14eb15b..00000000000000 --- a/clang-tools-extra/clangd/examples/SampleFeatureModule/SampleFeatureModule.cpp +++ /dev/null @@ -1,100 +0,0 @@ -//===--- SampleFeatureModule.cpp - Example Clangd Feature Module ----------===// -// -// 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 -// -//===----------------------------------------------------------------------===// -// -// This file implements an example FeatureModule for clangd that can be -// dynamically loaded into clangd at runtime via the `-load` option: -// -// clangd -load=/path/to/SampleFeatureModule.so -// -// When loaded, the module registers itself with clangd's FeatureModuleRegistry -// and demonstrates: -// 1. Contributing custom code action tweaks (Tweak). -// 2. Observing AST builds and diagnostics via ASTListener. -// 3. Registering custom LSP method/command handlers via LSPBinder. -// -//===----------------------------------------------------------------------===// - -#include "FeatureModule.h" -#include "LSPBinder.h" -#include "refactor/Tweak.h" -#include "support/Logger.h" - -namespace clang::clangd { -namespace { - -/// An example code action tweak contributed by the feature module. -class SampleTweak final : public Tweak { -public: - const char *id() const override { return "SampleModuleTweak"; } - - bool prepare(const Selection &Sel) override { - // This tweak is available anywhere in the file. - return true; - } - - Expected<Effect> apply(const Selection &Sel) override { - return Effect::showMessage("Sample FeatureModule tweak executed!"); - } - - std::string title() const override { - return "Execute Sample FeatureModule action"; - } - - llvm::StringLiteral kind() const override { - return llvm::StringLiteral("quickfix"); - } -}; - -/// An example AST listener that observes AST construction events. -class SampleASTListener final : public FeatureModule::ASTListener { -public: - void beforeExecute(CompilerInstance &CI) override { - vlog("SampleFeatureModule: before AST execution"); - } - - void afterExecute(CompilerInstance &CI) override { - vlog("SampleFeatureModule: after AST execution"); - } - - void sawDiagnostic(const clang::Diagnostic &Diag, clangd::Diag &D) override { - vlog("SampleFeatureModule: observed diagnostic '{0}'", D.Message); - } -}; - -/// The main feature module class. -class SampleFeatureModule final : public FeatureModule { -public: - /// Contribute custom tweaks to clangd's code action list. - void contributeTweaks(std::vector<std::unique_ptr<Tweak>> &Out) override { - Out.emplace_back(new SampleTweak); - } - - /// Hook into AST creation to observe AST events and diagnostics. - std::unique_ptr<ASTListener> astListeners() override { - return std::make_unique<SampleASTListener>(); - } - - /// Register custom LSP endpoints or update server capabilities. - void initializeLSP(LSPBinder &Bind, - const llvm::json::Object &ClientCaps, - llvm::json::Object &ServerCaps) override { - vlog("SampleFeatureModule: initializeLSP called"); - // Example: Bind a custom LSP method or notification if needed: - // Bind.method("sample/customMethod", this, &SampleFeatureModule::onCustomMethod); - } -}; - -// Register the module with clangd's FeatureModuleRegistry. -// When clangd loads this shared library via -load, this static initializer -// will run and add the module to clangd's registry. -static FeatureModuleRegistry::Add<SampleFeatureModule> - X("sample-feature-module", - "Example feature module demonstrating dynamic loading into clangd"); - -} // namespace -} // namespace clang::clangd >From b605be6b81b287499dd4bb941e61910868deaf63 Mon Sep 17 00:00:00 2001 From: Aleksandr Platonov <[email protected]> Date: Fri, 25 Sep 2026 10:08:34 +0300 Subject: [PATCH 3/7] Added tweak example --- clang-tools-extra/clangd/test/CMakeLists.txt | 30 +++++++++++------- .../clangd/test/StandaloneTweakExample.cpp | 31 +++++++++++++++++++ .../clangd/test/standalone-tweak.test | 10 ++++++ 3 files changed, 60 insertions(+), 11 deletions(-) create mode 100644 clang-tools-extra/clangd/test/StandaloneTweakExample.cpp 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 17f95f8abc005e..401d3a8b156db7 100644 --- a/clang-tools-extra/clangd/test/CMakeLists.txt +++ b/clang-tools-extra/clangd/test/CMakeLists.txt @@ -34,21 +34,29 @@ if (NOT WIN32 OR NOT LLVM_LINK_LLVM_DYLIB) llvm_add_library( ClangdFeatureModuleExample MODULE FeatureModuleExample.cpp - PLUGIN_TOOL clangd) + PLUGIN_TOOL clangd + PARTIAL_SOURCES_INTENDED) + llvm_add_library( + ClangdStandaloneTweakExample + MODULE StandaloneTweakExample.cpp + PLUGIN_TOOL clangd + PARTIAL_SOURCES_INTENDED) endif() -if(TARGET ClangdFeatureModuleExample) - list(APPEND CLANGD_TEST_DEPS ClangdFeatureModuleExample) - target_include_directories(ClangdFeatureModuleExample PUBLIC BEFORE - "${CLANG_TOOLS_SOURCE_DIR}/clangd" - "${CMAKE_CURRENT_BINARY_DIR}/.." - ) - if(CLANG_PLUGIN_SUPPORT AND (WIN32 OR CYGWIN)) - set(LLVM_LINK_COMPONENTS - Support +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() -endif() +endforeach() llvm_canonicalize_cmake_booleans(CLANG_PLUGIN_SUPPORT) diff --git a/clang-tools-extra/clangd/test/StandaloneTweakExample.cpp b/clang-tools-extra/clangd/test/StandaloneTweakExample.cpp new file mode 100644 index 00000000000000..4a472b15ec74b1 --- /dev/null +++ b/clang-tools-extra/clangd/test/StandaloneTweakExample.cpp @@ -0,0 +1,31 @@ +//===--- 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/standalone-tweak.test b/clang-tools-extra/clangd/test/standalone-tweak.test new file mode 100644 index 00000000000000..c74e81a4a68b3c --- /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; >From a335b0634538ee7a66aca4f163f699bc4f202bad Mon Sep 17 00:00:00 2001 From: Aleksandr Platonov <[email protected]> Date: Mon, 28 Sep 2026 16:19:12 +0300 Subject: [PATCH 4/7] Remove loadFeatureModule() --- clang-tools-extra/clangd/FeatureModule.cpp | 13 ------------- clang-tools-extra/clangd/FeatureModule.h | 7 ------- .../unittests/FeatureModulesRegistryTests.cpp | 13 ++++--------- 3 files changed, 4 insertions(+), 29 deletions(-) diff --git a/clang-tools-extra/clangd/FeatureModule.cpp b/clang-tools-extra/clangd/FeatureModule.cpp index dd72a6dd6c6cbe..38461e3db4e6bd 100644 --- a/clang-tools-extra/clangd/FeatureModule.cpp +++ b/clang-tools-extra/clangd/FeatureModule.cpp @@ -8,8 +8,6 @@ #include "FeatureModule.h" #include "support/Logger.h" -#include "llvm/Support/DynamicLibrary.h" -#include "llvm/Support/Error.h" namespace clang { namespace clangd { @@ -48,17 +46,6 @@ FeatureModuleSet FeatureModuleSet::fromRegistry() { return ModuleSet; } -llvm::Error loadFeatureModule(llvm::StringRef SharedLibraryPath) { - std::string Err; - if (llvm::sys::DynamicLibrary::LoadLibraryPermanently( - SharedLibraryPath.str().c_str(), &Err)) - return llvm::createStringError(llvm::inconvertibleErrorCode(), - "Failed to load feature module '%s': %s", - SharedLibraryPath.str().c_str(), - Err.c_str()); - return llvm::Error::success(); -} - } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/FeatureModule.h b/clang-tools-extra/clangd/FeatureModule.h index 0b3ba894ae3871..90714fe9101a83 100644 --- a/clang-tools-extra/clangd/FeatureModule.h +++ b/clang-tools-extra/clangd/FeatureModule.h @@ -14,7 +14,6 @@ #include "clang/Basic/Diagnostic.h" #include "llvm/ADT/FunctionExtras.h" #include "llvm/Support/Compiler.h" -#include "llvm/Support/Error.h" #include "llvm/Support/JSON.h" #include "llvm/Support/Registry.h" #include <memory> @@ -216,12 +215,6 @@ template <typename Mod> int FeatureModuleSet::ID<Mod>::Key; using FeatureModuleRegistry = llvm::Registry<FeatureModule>; -/// Dynamically load a feature module from a shared library. -/// The shared library is expected to register one or more FeatureModules -/// via FeatureModuleRegistry. -/// Returns an Error if the library could not be loaded. -llvm::Error loadFeatureModule(llvm::StringRef SharedLibraryPath); - } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/unittests/FeatureModulesRegistryTests.cpp b/clang-tools-extra/clangd/unittests/FeatureModulesRegistryTests.cpp index 936070220c0aa5..b8941cdb4b1783 100644 --- a/clang-tools-extra/clangd/unittests/FeatureModulesRegistryTests.cpp +++ b/clang-tools-extra/clangd/unittests/FeatureModulesRegistryTests.cpp @@ -74,9 +74,10 @@ static FeatureModuleRegistry::Add<Dummy> MATCHER_P(moduleName, Name, "") { return arg.getName() == Name; } MATCHER_P(tweakID, ID, "") { return arg->id() == llvm::StringRef(ID); } -// Feature modules can be added statically via FeatureModuleRegistry::Add, -// or loaded dynamically at runtime via the -load command-line option or -// loadFeatureModule(). +// In this test, it is assumed that for unittests executable, all feature +// modules are added to the registry only here (in this file). To implement +// modules for clangd tool, one need to link them directly to the clangd +// executable in clangd/tool/CMakeLists.txt. TEST(FeatureModulesRegistryTest, DummyModule) { EXPECT_THAT(FeatureModuleRegistry::entries(), ElementsAre(moduleName("dummy"))); @@ -87,11 +88,5 @@ TEST(FeatureModulesRegistryTest, DummyModule) { EXPECT_THAT(Tweaks, ElementsAre(tweakID("DummyTweak"))); } -TEST(FeatureModulesRegistryTest, LoadNonexistentModule) { - llvm::Error Err = loadFeatureModule("nonexistent_module.so"); - EXPECT_TRUE(bool(Err)); - llvm::consumeError(std::move(Err)); -} - } // namespace } // namespace clang::clangd >From 036de1a5df808326ef664909c36a27b6281bf401 Mon Sep 17 00:00:00 2001 From: Aleksandr Platonov <[email protected]> Date: Mon, 28 Sep 2026 16:39:52 +0300 Subject: [PATCH 5/7] Removed IWYU comment --- 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 dc0e1be1e69461..6f3451232db04f 100644 --- a/clang-tools-extra/clangd/tool/ClangdMain.cpp +++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp @@ -38,7 +38,7 @@ #include "llvm/Support/FileSystem.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/Path.h" -#include "llvm/Support/PluginLoader.h" // IWYU pragma: keep +#include "llvm/Support/PluginLoader.h" #include "llvm/Support/Process.h" #include "llvm/Support/Program.h" #include "llvm/Support/Signals.h" >From 2805916ac0afe8c423edd5100417fb5114dd1afd Mon Sep 17 00:00:00 2001 From: Aleksandr Platonov <[email protected]> Date: Mon, 28 Sep 2026 17:52:26 +0300 Subject: [PATCH 6/7] fix format --- clang-tools-extra/clangd/test/StandaloneTweakExample.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/clangd/test/StandaloneTweakExample.cpp b/clang-tools-extra/clangd/test/StandaloneTweakExample.cpp index 4a472b15ec74b1..3afe027fd7ab64 100644 --- a/clang-tools-extra/clangd/test/StandaloneTweakExample.cpp +++ b/clang-tools-extra/clangd/test/StandaloneTweakExample.cpp @@ -11,7 +11,8 @@ namespace clang::clangd { namespace { -/// A standalone tweak registered directly via REGISTER_TWEAK without a FeatureModule. +/// A standalone tweak registered directly via REGISTER_TWEAK without a +/// FeatureModule. class StandaloneTweak final : public Tweak { public: const char *id() const override; >From 0523da70748b4994b8cf2e2cea70654720c81541 Mon Sep 17 00:00:00 2001 From: Aleksandr Platonov <[email protected]> Date: Tue, 29 Sep 2026 15:32:50 +0300 Subject: [PATCH 7/7] update release notes --- clang-tools-extra/docs/ReleaseNotes.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clang-tools-extra/docs/ReleaseNotes.md b/clang-tools-extra/docs/ReleaseNotes.md index 833638a47abc63..92342c4818a738 100644 --- a/clang-tools-extra/docs/ReleaseNotes.md +++ b/clang-tools-extra/docs/ReleaseNotes.md @@ -112,6 +112,9 @@ infrastructure are described first, followed by tool-specific sections. C++20 concepts. ([#206875](https://github.com/llvm/llvm-project/issues/206875)) +- Added support for loading dynamic plugins via the `-load` command-line + option. + ### Improvements to clang-doc ### Improvements to clang-query _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
