[clang-tools-extra] Add --project-root to clangd (PR #155905)
via cfe-commits
cfe-commits at lists.llvm.org
Sat Sep 13 04:30:49 PDT 2025
https://github.com/Dominicentek updated https://github.com/llvm/llvm-project/pull/155905
>From 9a30c2b92be357deac5a65e2fa0952d91634de70 Mon Sep 17 00:00:00 2001
From: Dominicentek <dominicentekgaming at gmail.com>
Date: Thu, 28 Aug 2025 20:46:35 +0200
Subject: [PATCH] Add --project-root to clangd
---
clang-tools-extra/clangd/ClangdServer.cpp | 1 +
clang-tools-extra/clangd/ClangdServer.h | 4 ++++
.../clangd/GlobalCompilationDatabase.cpp | 14 +++++++-------
.../clangd/GlobalCompilationDatabase.h | 6 +++---
clang-tools-extra/clangd/TUScheduler.cpp | 6 ++++--
clang-tools-extra/clangd/TUScheduler.h | 4 ++++
clang-tools-extra/clangd/tool/Check.cpp | 4 ++--
clang-tools-extra/clangd/tool/ClangdMain.cpp | 10 ++++++++++
8 files changed, 35 insertions(+), 14 deletions(-)
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index ac1e9aa5f0ff1..51230b4506b1a 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -208,6 +208,7 @@ ClangdServer::Options::operator TUScheduler::Options() const {
Opts.UpdateDebounce = UpdateDebounce;
Opts.ContextProvider = ContextProvider;
Opts.PreambleThrottler = PreambleThrottler;
+ Opts.FallbackProjectRoot = FallbackProjectRoot;
return Opts;
}
diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index 4a1eae188f7eb..2c56d6f7e6d6c 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -152,6 +152,10 @@ class ClangdServer {
/// FIXME: If not set, should use the current working directory.
std::optional<std::string> WorkspaceRoot;
+ /// If set, fallback command uses this path as its current working directory
+ /// instead of the file's parent path.
+ std::optional<std::string> FallbackProjectRoot;
+
/// The resource directory is used to find internal headers, overriding
/// defaults and -resource-dir compiler flag).
/// If std::nullopt, ClangdServer calls
diff --git a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
index c6afd0bc07cbd..b73697d4ee7e5 100644
--- a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
+++ b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
@@ -55,7 +55,7 @@ void actOnAllParentDirectories(PathRef FileName,
} // namespace
tooling::CompileCommand
-GlobalCompilationDatabase::getFallbackCommand(PathRef File) const {
+GlobalCompilationDatabase::getFallbackCommand(PathRef File, std::optional<std::string> ProjectRoot) const {
std::vector<std::string> Argv = {"clang"};
// Clang treats .h files as C by default and files without extension as linker
// input, resulting in unhelpful diagnostics.
@@ -64,7 +64,7 @@ GlobalCompilationDatabase::getFallbackCommand(PathRef File) const {
if (FileExtension.empty() || FileExtension == ".h")
Argv.push_back("-xobjective-c++-header");
Argv.push_back(std::string(File));
- tooling::CompileCommand Cmd(llvm::sys::path::parent_path(File),
+ tooling::CompileCommand Cmd(ProjectRoot ? *ProjectRoot : llvm::sys::path::parent_path(File),
llvm::sys::path::filename(File), std::move(Argv),
/*Output=*/"");
Cmd.Heuristic = "clangd fallback";
@@ -797,8 +797,8 @@ OverlayCDB::getCompileCommand(PathRef File) const {
return Cmd;
}
-tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File) const {
- auto Cmd = DelegatingCDB::getFallbackCommand(File);
+tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File, std::optional<std::string> ProjectRoot) const {
+ auto Cmd = DelegatingCDB::getFallbackCommand(File, ProjectRoot);
std::lock_guard<std::mutex> Lock(Mutex);
Cmd.CommandLine.insert(Cmd.CommandLine.end(), FallbackFlags.begin(),
FallbackFlags.end());
@@ -877,10 +877,10 @@ DelegatingCDB::getProjectModules(PathRef File) const {
return Base->getProjectModules(File);
}
-tooling::CompileCommand DelegatingCDB::getFallbackCommand(PathRef File) const {
+tooling::CompileCommand DelegatingCDB::getFallbackCommand(PathRef File, std::optional<std::string> ProjectRoot) const {
if (!Base)
- return GlobalCompilationDatabase::getFallbackCommand(File);
- return Base->getFallbackCommand(File);
+ return GlobalCompilationDatabase::getFallbackCommand(File, ProjectRoot);
+ return Base->getFallbackCommand(File, ProjectRoot);
}
bool DelegatingCDB::blockUntilIdle(Deadline D) const {
diff --git a/clang-tools-extra/clangd/GlobalCompilationDatabase.h b/clang-tools-extra/clangd/GlobalCompilationDatabase.h
index 1d636d73664be..5d1b5cb632154 100644
--- a/clang-tools-extra/clangd/GlobalCompilationDatabase.h
+++ b/clang-tools-extra/clangd/GlobalCompilationDatabase.h
@@ -55,7 +55,7 @@ class GlobalCompilationDatabase {
/// Makes a guess at how to build a file.
/// The default implementation just runs clang on the file.
/// Clangd should treat the results as unreliable.
- virtual tooling::CompileCommand getFallbackCommand(PathRef File) const;
+ virtual tooling::CompileCommand getFallbackCommand(PathRef File, std::optional<std::string> ProjectRoot = std::nullopt) const;
/// If the CDB does any asynchronous work, wait for it to complete.
/// For use in tests.
@@ -86,7 +86,7 @@ class DelegatingCDB : public GlobalCompilationDatabase {
std::unique_ptr<ProjectModules>
getProjectModules(PathRef File) const override;
- tooling::CompileCommand getFallbackCommand(PathRef File) const override;
+ tooling::CompileCommand getFallbackCommand(PathRef File, std::optional<std::string> ProjectRoot = std::nullopt) const override;
bool blockUntilIdle(Deadline D) const override;
@@ -200,7 +200,7 @@ class OverlayCDB : public DelegatingCDB {
std::optional<tooling::CompileCommand>
getCompileCommand(PathRef File) const override;
- tooling::CompileCommand getFallbackCommand(PathRef File) const override;
+ tooling::CompileCommand getFallbackCommand(PathRef File, std::optional<std::string> ProjectRoot = std::nullopt) const override;
/// Sets or clears the compilation command for a particular file.
/// Returns true if the command was changed (including insertion and removal),
diff --git a/clang-tools-extra/clangd/TUScheduler.cpp b/clang-tools-extra/clangd/TUScheduler.cpp
index 035e5e63d8fbb..3dc53767e0ea4 100644
--- a/clang-tools-extra/clangd/TUScheduler.cpp
+++ b/clang-tools-extra/clangd/TUScheduler.cpp
@@ -723,6 +723,7 @@ class ASTWorker {
const GlobalCompilationDatabase &CDB;
/// Callback invoked when preamble or main file AST is built.
ParsingCallbacks &Callbacks;
+ std::optional<std::string> FallbackProjectRoot;
Semaphore &Barrier;
/// Whether the 'onMainAST' callback ran for the current FileInputs.
@@ -840,13 +841,14 @@ ASTWorker::ASTWorker(PathRef FileName, const GlobalCompilationDatabase &CDB,
: IdleASTs(LRUCache), HeaderIncluders(HeaderIncluders), RunSync(RunSync),
UpdateDebounce(Opts.UpdateDebounce), FileName(FileName),
ContextProvider(Opts.ContextProvider), CDB(CDB), Callbacks(Callbacks),
+ FallbackProjectRoot(Opts.FallbackProjectRoot),
Barrier(Barrier), Done(false), Status(FileName, Callbacks),
PreamblePeer(FileName, Callbacks, Opts.StorePreamblesInMemory, RunSync,
Opts.PreambleThrottler, Status, HeaderIncluders, *this) {
// Set a fallback command because compile command can be accessed before
// `Inputs` is initialized. Other fields are only used after initialization
// from client inputs.
- FileInputs.CompileCommand = CDB.getFallbackCommand(FileName);
+ FileInputs.CompileCommand = CDB.getFallbackCommand(FileName, FallbackProjectRoot);
}
ASTWorker::~ASTWorker() {
@@ -888,7 +890,7 @@ void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags,
if (Cmd)
Inputs.CompileCommand = std::move(*Cmd);
else
- Inputs.CompileCommand = CDB.getFallbackCommand(FileName);
+ Inputs.CompileCommand = CDB.getFallbackCommand(FileName, FallbackProjectRoot);
bool InputsAreTheSame =
std::tie(FileInputs.CompileCommand, FileInputs.Contents) ==
diff --git a/clang-tools-extra/clangd/TUScheduler.h b/clang-tools-extra/clangd/TUScheduler.h
index d0da20310a8b2..581a639646527 100644
--- a/clang-tools-extra/clangd/TUScheduler.h
+++ b/clang-tools-extra/clangd/TUScheduler.h
@@ -236,6 +236,10 @@ class TUScheduler {
/// Typically to inject per-file configuration.
/// If the path is empty, context sholud be "generic".
std::function<Context(PathRef)> ContextProvider;
+
+ /// If set, fallback command uses this path as its current working directory
+ /// instead of the file's parent path.
+ std::optional<std::string> FallbackProjectRoot;
};
TUScheduler(const GlobalCompilationDatabase &CDB, const Options &Opts,
diff --git a/clang-tools-extra/clangd/tool/Check.cpp b/clang-tools-extra/clangd/tool/Check.cpp
index df8d075e80596..8d49b82d2ca53 100644
--- a/clang-tools-extra/clangd/tool/Check.cpp
+++ b/clang-tools-extra/clangd/tool/Check.cpp
@@ -187,7 +187,7 @@ class Checker {
Cmd.Heuristic.empty() ? "from CDB" : Cmd.Heuristic, Cmd.Directory,
printArgv(Cmd.CommandLine));
} else {
- Cmd = CDB->getFallbackCommand(File);
+ Cmd = CDB->getFallbackCommand(File, Opts.FallbackProjectRoot);
log("Generic fallback command is: [{0}] {1}", Cmd.Directory,
printArgv(Cmd.CommandLine));
}
@@ -502,7 +502,7 @@ bool check(llvm::StringRef File, const ThreadsafeFS &TFS,
config::DiagnosticCallback Diag) const override {
config::Fragment F;
// If we're timing clang-tidy checks, implicitly disabling the slow ones
- // is counterproductive!
+ // is counterproductive!
if (CheckTidyTime.getNumOccurrences())
F.Diagnostics.ClangTidy.FastCheckFilter.emplace("None");
return {std::move(F).compile(Diag)};
diff --git a/clang-tools-extra/clangd/tool/ClangdMain.cpp b/clang-tools-extra/clangd/tool/ClangdMain.cpp
index f287439f10cab..75d71c5a78f45 100644
--- a/clang-tools-extra/clangd/tool/ClangdMain.cpp
+++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp
@@ -499,6 +499,14 @@ opt<bool> EnableConfig{
init(true),
};
+opt<Path> ProjectRoot{
+ "project-root",
+ cat(Misc),
+ desc("Path to use as the current working directory for fallback commands."),
+ init(""),
+ ValueOptional,
+};
+
opt<bool> UseDirtyHeaders{"use-dirty-headers", cat(Misc),
desc("Use files open in the editor when parsing "
"headers instead of reading from the disk"),
@@ -906,6 +914,8 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
}
if (!ResourceDir.empty())
Opts.ResourceDir = ResourceDir;
+ if (!ProjectRoot.empty())
+ Opts.FallbackProjectRoot = ProjectRoot;
Opts.BuildDynamicSymbolIndex = true;
#if CLANGD_ENABLE_REMOTE
if (RemoteIndexAddress.empty() != ProjectRoot.empty()) {
More information about the cfe-commits
mailing list