[clang-tools-extra] Add --project-root to clangd (PR #155905)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Nov 25 11:25:26 PST 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 1/3] 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()) {
>From 82f80c04f0a6cb8d1c759178a707057be9d5ad7d Mon Sep 17 00:00:00 2001
From: Dominicentek <dominicentekgaming at gmail.com>
Date: Mon, 24 Nov 2025 21:59:31 +0100
Subject: [PATCH 2/3] [clangd] Replace --project-root with
--strong-workspace-mode
---
clang-tools-extra/clangd/ClangdServer.cpp | 2 +-
clang-tools-extra/clangd/ClangdServer.h | 6 +++---
.../clangd/GlobalCompilationDatabase.cpp | 19 ++++++++++++-------
.../clangd/GlobalCompilationDatabase.h | 6 +++---
clang-tools-extra/clangd/TUScheduler.cpp | 8 ++++----
clang-tools-extra/clangd/TUScheduler.h | 5 ++---
clang-tools-extra/clangd/tool/Check.cpp | 2 +-
clang-tools-extra/clangd/tool/ClangdMain.cpp | 16 ++++++++--------
8 files changed, 34 insertions(+), 30 deletions(-)
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index 51230b4506b1a..882515e97eeb7 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -208,7 +208,7 @@ ClangdServer::Options::operator TUScheduler::Options() const {
Opts.UpdateDebounce = UpdateDebounce;
Opts.ContextProvider = ContextProvider;
Opts.PreambleThrottler = PreambleThrottler;
- Opts.FallbackProjectRoot = FallbackProjectRoot;
+ Opts.StrongWorkspaceMode = StrongWorkspaceMode;
return Opts;
}
diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index 2c56d6f7e6d6c..3f3eaf4115669 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -152,9 +152,9 @@ 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;
+ /// Sets an alterante mode of operation. Current effects are:
+ /// - Using the current working directory as the working directory for fallback commands
+ bool StrongWorkspaceMode;
/// The resource directory is used to find internal headers, overriding
/// defaults and -resource-dir compiler flag).
diff --git a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
index b73697d4ee7e5..4f161b38d96a2 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, std::optional<std::string> ProjectRoot) const {
+GlobalCompilationDatabase::getFallbackCommand(PathRef File, bool StrongWorkspaceMode) 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,12 @@ GlobalCompilationDatabase::getFallbackCommand(PathRef File, std::optional<std::s
if (FileExtension.empty() || FileExtension == ".h")
Argv.push_back("-xobjective-c++-header");
Argv.push_back(std::string(File));
- tooling::CompileCommand Cmd(ProjectRoot ? *ProjectRoot : llvm::sys::path::parent_path(File),
+ SmallString<256> WorkingDir;
+ if (StrongWorkspaceMode)
+ llvm::sys::fs::current_path(WorkingDir);
+ else
+ WorkingDir = llvm::sys::path::parent_path(File);
+ tooling::CompileCommand Cmd(WorkingDir,
llvm::sys::path::filename(File), std::move(Argv),
/*Output=*/"");
Cmd.Heuristic = "clangd fallback";
@@ -797,8 +802,8 @@ OverlayCDB::getCompileCommand(PathRef File) const {
return Cmd;
}
-tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File, std::optional<std::string> ProjectRoot) const {
- auto Cmd = DelegatingCDB::getFallbackCommand(File, ProjectRoot);
+tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File, bool StrongWorkspaceMode) const {
+ auto Cmd = DelegatingCDB::getFallbackCommand(File, StrongWorkspaceMode);
std::lock_guard<std::mutex> Lock(Mutex);
Cmd.CommandLine.insert(Cmd.CommandLine.end(), FallbackFlags.begin(),
FallbackFlags.end());
@@ -877,10 +882,10 @@ DelegatingCDB::getProjectModules(PathRef File) const {
return Base->getProjectModules(File);
}
-tooling::CompileCommand DelegatingCDB::getFallbackCommand(PathRef File, std::optional<std::string> ProjectRoot) const {
+tooling::CompileCommand DelegatingCDB::getFallbackCommand(PathRef File, bool StrongWorkspaceMode) const {
if (!Base)
- return GlobalCompilationDatabase::getFallbackCommand(File, ProjectRoot);
- return Base->getFallbackCommand(File, ProjectRoot);
+ return GlobalCompilationDatabase::getFallbackCommand(File, StrongWorkspaceMode);
+ return Base->getFallbackCommand(File, StrongWorkspaceMode);
}
bool DelegatingCDB::blockUntilIdle(Deadline D) const {
diff --git a/clang-tools-extra/clangd/GlobalCompilationDatabase.h b/clang-tools-extra/clangd/GlobalCompilationDatabase.h
index 5d1b5cb632154..a23e8cc162c4e 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, std::optional<std::string> ProjectRoot = std::nullopt) const;
+ virtual tooling::CompileCommand getFallbackCommand(PathRef File, bool StrongWorkspaceMode = false) 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, std::optional<std::string> ProjectRoot = std::nullopt) const override;
+ tooling::CompileCommand getFallbackCommand(PathRef File, bool StrongWorkspaceMode = false) 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, std::optional<std::string> ProjectRoot = std::nullopt) const override;
+ tooling::CompileCommand getFallbackCommand(PathRef File, bool StrongWorkspaceMode = false) 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 3dc53767e0ea4..09bb8d1927e90 100644
--- a/clang-tools-extra/clangd/TUScheduler.cpp
+++ b/clang-tools-extra/clangd/TUScheduler.cpp
@@ -723,7 +723,7 @@ class ASTWorker {
const GlobalCompilationDatabase &CDB;
/// Callback invoked when preamble or main file AST is built.
ParsingCallbacks &Callbacks;
- std::optional<std::string> FallbackProjectRoot;
+ bool StrongWorkspaceMode;
Semaphore &Barrier;
/// Whether the 'onMainAST' callback ran for the current FileInputs.
@@ -841,14 +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),
+ StrongWorkspaceMode(Opts.StrongWorkspaceMode),
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, FallbackProjectRoot);
+ FileInputs.CompileCommand = CDB.getFallbackCommand(FileName, StrongWorkspaceMode);
}
ASTWorker::~ASTWorker() {
@@ -890,7 +890,7 @@ void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags,
if (Cmd)
Inputs.CompileCommand = std::move(*Cmd);
else
- Inputs.CompileCommand = CDB.getFallbackCommand(FileName, FallbackProjectRoot);
+ Inputs.CompileCommand = CDB.getFallbackCommand(FileName, StrongWorkspaceMode);
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 581a639646527..d5dbd0e1830d1 100644
--- a/clang-tools-extra/clangd/TUScheduler.h
+++ b/clang-tools-extra/clangd/TUScheduler.h
@@ -237,9 +237,8 @@ class TUScheduler {
/// 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;
+ /// Sets an alterante mode of operation. See ClangdServer::Options::StrongWorkspaceMode.
+ bool StrongWorkspaceMode;
};
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 8d49b82d2ca53..451cdbce56fea 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, Opts.FallbackProjectRoot);
+ Cmd = CDB->getFallbackCommand(File, Opts.StrongWorkspaceMode);
log("Generic fallback command is: [{0}] {1}", Cmd.Directory,
printArgv(Cmd.CommandLine));
}
diff --git a/clang-tools-extra/clangd/tool/ClangdMain.cpp b/clang-tools-extra/clangd/tool/ClangdMain.cpp
index 7d2db715d37fd..f947171090a46 100644
--- a/clang-tools-extra/clangd/tool/ClangdMain.cpp
+++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp
@@ -500,12 +500,13 @@ 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> StrongWorkspaceMode{
+ "strong-workspace-mode",
+ cat(Features),
+ desc(
+ "An alternate mode of operation for clangd, operating more closely to the workspace.\n"
+ "When enabled, fallback commands use the workspace directory as their working directory instead of the parent folder."),
+ init(false),
};
opt<bool> UseDirtyHeaders{"use-dirty-headers", cat(Misc),
@@ -915,8 +916,7 @@ 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.StrongWorkspaceMode = StrongWorkspaceMode;
Opts.BuildDynamicSymbolIndex = true;
#if CLANGD_ENABLE_REMOTE
if (RemoteIndexAddress.empty() != ProjectRoot.empty()) {
>From 03b26a6f54e9e0dc3fe6e26e1e8060f3c6a6d112 Mon Sep 17 00:00:00 2001
From: Dominicentek <dominicentekgaming at gmail.com>
Date: Tue, 25 Nov 2025 20:25:08 +0100
Subject: [PATCH 3/3] [clangd] Fallback working directory code cleanup
---
clang-tools-extra/clangd/ClangdLSPServer.cpp | 2 +
clang-tools-extra/clangd/ClangdServer.cpp | 1 -
.../clangd/GlobalCompilationDatabase.cpp | 53 +++++++++++--------
.../clangd/GlobalCompilationDatabase.h | 24 ++++++---
clang-tools-extra/clangd/TUScheduler.cpp | 5 +-
clang-tools-extra/clangd/TUScheduler.h | 3 --
clang-tools-extra/clangd/tool/Check.cpp | 8 ++-
.../GlobalCompilationDatabaseTests.cpp | 14 +++++
8 files changed, 74 insertions(+), 36 deletions(-)
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index f8e6da73bbb1f..3e8bacc715494 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -554,6 +554,8 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
if (const auto &Dir = Params.initializationOptions.compilationDatabasePath)
CDBOpts.CompileCommandsDir = Dir;
CDBOpts.ContextProvider = Opts.ContextProvider;
+ if (Opts.StrongWorkspaceMode)
+ CDBOpts.applyWorkingDirectory(std::move(Opts.WorkspaceRoot));
BaseCDB =
std::make_unique<DirectoryBasedGlobalCompilationDatabase>(CDBOpts);
}
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index 882515e97eeb7..ac1e9aa5f0ff1 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -208,7 +208,6 @@ ClangdServer::Options::operator TUScheduler::Options() const {
Opts.UpdateDebounce = UpdateDebounce;
Opts.ContextProvider = ContextProvider;
Opts.PreambleThrottler = PreambleThrottler;
- Opts.StrongWorkspaceMode = StrongWorkspaceMode;
return Opts;
}
diff --git a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
index 4f161b38d96a2..e2ac2d17171be 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, bool StrongWorkspaceMode) const {
+GlobalCompilationDatabase::getFallbackCommand(PathRef File) 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,14 +64,10 @@ GlobalCompilationDatabase::getFallbackCommand(PathRef File, bool StrongWorkspace
if (FileExtension.empty() || FileExtension == ".h")
Argv.push_back("-xobjective-c++-header");
Argv.push_back(std::string(File));
- SmallString<256> WorkingDir;
- if (StrongWorkspaceMode)
- llvm::sys::fs::current_path(WorkingDir);
- else
- WorkingDir = llvm::sys::path::parent_path(File);
- tooling::CompileCommand Cmd(WorkingDir,
- llvm::sys::path::filename(File), std::move(Argv),
- /*Output=*/"");
+ tooling::CompileCommand Cmd(
+ WorkingDirectory ? *WorkingDirectory : llvm::sys::path::parent_path(File),
+ llvm::sys::path::filename(File), std::move(Argv),
+ /*Output=*/"");
Cmd.Heuristic = "clangd fallback";
return Cmd;
}
@@ -354,7 +350,8 @@ bool DirectoryBasedGlobalCompilationDatabase::DirectoryCache::load(
DirectoryBasedGlobalCompilationDatabase::
DirectoryBasedGlobalCompilationDatabase(const Options &Opts)
- : Opts(Opts), Broadcaster(std::make_unique<BroadcastThread>(*this)) {
+ : GlobalCompilationDatabase(Opts.WorkingDirectory), Opts(Opts),
+ Broadcaster(std::make_unique<BroadcastThread>(*this)) {
if (!this->Opts.ContextProvider)
this->Opts.ContextProvider = [](llvm::StringRef) {
return Context::current().clone();
@@ -465,6 +462,17 @@ DirectoryBasedGlobalCompilationDatabase::lookupCDB(
return Result;
}
+void DirectoryBasedGlobalCompilationDatabase::Options::applyWorkingDirectory(
+ const std::optional<std::string> &&WorkingDirectory) {
+ if (WorkingDirectory)
+ this->WorkingDirectory = *WorkingDirectory;
+ else {
+ SmallString<256> CWD;
+ llvm::sys::fs::current_path(CWD);
+ this->WorkingDirectory = std::string(CWD);
+ }
+}
+
// The broadcast thread announces files with new compile commands to the world.
// Primarily this is used to enqueue them for background indexing.
//
@@ -764,8 +772,9 @@ DirectoryBasedGlobalCompilationDatabase::getProjectModules(PathRef File) const {
OverlayCDB::OverlayCDB(const GlobalCompilationDatabase *Base,
std::vector<std::string> FallbackFlags,
- CommandMangler Mangler)
- : DelegatingCDB(Base), Mangler(std::move(Mangler)),
+ CommandMangler Mangler,
+ std::optional<std::string> WorkingDirectory)
+ : DelegatingCDB(Base, WorkingDirectory), Mangler(std::move(Mangler)),
FallbackFlags(std::move(FallbackFlags)) {}
std::optional<tooling::CompileCommand>
@@ -802,8 +811,8 @@ OverlayCDB::getCompileCommand(PathRef File) const {
return Cmd;
}
-tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File, bool StrongWorkspaceMode) const {
- auto Cmd = DelegatingCDB::getFallbackCommand(File, StrongWorkspaceMode);
+tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File) const {
+ auto Cmd = DelegatingCDB::getFallbackCommand(File);
std::lock_guard<std::mutex> Lock(Mutex);
Cmd.CommandLine.insert(Cmd.CommandLine.end(), FallbackFlags.begin(),
FallbackFlags.end());
@@ -849,16 +858,18 @@ OverlayCDB::getProjectModules(PathRef File) const {
return MDB;
}
-DelegatingCDB::DelegatingCDB(const GlobalCompilationDatabase *Base)
- : Base(Base) {
+DelegatingCDB::DelegatingCDB(const GlobalCompilationDatabase *Base,
+ std::optional<std::string> WorkingDirectory)
+ : GlobalCompilationDatabase(WorkingDirectory), Base(Base) {
if (Base)
BaseChanged = Base->watch([this](const std::vector<std::string> Changes) {
OnCommandChanged.broadcast(Changes);
});
}
-DelegatingCDB::DelegatingCDB(std::unique_ptr<GlobalCompilationDatabase> Base)
- : DelegatingCDB(Base.get()) {
+DelegatingCDB::DelegatingCDB(std::unique_ptr<GlobalCompilationDatabase> Base,
+ std::optional<std::string> WorkingDirectory)
+ : DelegatingCDB(Base.get(), WorkingDirectory) {
BaseOwner = std::move(Base);
}
@@ -882,10 +893,10 @@ DelegatingCDB::getProjectModules(PathRef File) const {
return Base->getProjectModules(File);
}
-tooling::CompileCommand DelegatingCDB::getFallbackCommand(PathRef File, bool StrongWorkspaceMode) const {
+tooling::CompileCommand DelegatingCDB::getFallbackCommand(PathRef File) const {
if (!Base)
- return GlobalCompilationDatabase::getFallbackCommand(File, StrongWorkspaceMode);
- return Base->getFallbackCommand(File, StrongWorkspaceMode);
+ return GlobalCompilationDatabase::getFallbackCommand(File);
+ return Base->getFallbackCommand(File);
}
bool DelegatingCDB::blockUntilIdle(Deadline D) const {
diff --git a/clang-tools-extra/clangd/GlobalCompilationDatabase.h b/clang-tools-extra/clangd/GlobalCompilationDatabase.h
index a23e8cc162c4e..da005f8e19c4c 100644
--- a/clang-tools-extra/clangd/GlobalCompilationDatabase.h
+++ b/clang-tools-extra/clangd/GlobalCompilationDatabase.h
@@ -35,6 +35,8 @@ struct ProjectInfo {
/// Provides compilation arguments used for parsing C and C++ files.
class GlobalCompilationDatabase {
public:
+ GlobalCompilationDatabase(std::optional<std::string> WorkingDirectory)
+ : WorkingDirectory(WorkingDirectory) {}
virtual ~GlobalCompilationDatabase() = default;
/// If there are any known-good commands for building this file, returns one.
@@ -55,7 +57,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, bool StrongWorkspaceMode = false) const;
+ virtual tooling::CompileCommand getFallbackCommand(PathRef File) const;
/// If the CDB does any asynchronous work, wait for it to complete.
/// For use in tests.
@@ -69,14 +71,17 @@ class GlobalCompilationDatabase {
}
protected:
+ std::optional<std::string> WorkingDirectory;
mutable CommandChanged OnCommandChanged;
};
// Helper class for implementing GlobalCompilationDatabases that wrap others.
class DelegatingCDB : public GlobalCompilationDatabase {
public:
- DelegatingCDB(const GlobalCompilationDatabase *Base);
- DelegatingCDB(std::unique_ptr<GlobalCompilationDatabase> Base);
+ DelegatingCDB(const GlobalCompilationDatabase *Base,
+ std::optional<std::string> WorkingDirectory);
+ DelegatingCDB(std::unique_ptr<GlobalCompilationDatabase> Base,
+ std::optional<std::string> WorkingDirectory);
std::optional<tooling::CompileCommand>
getCompileCommand(PathRef File) const override;
@@ -86,7 +91,7 @@ class DelegatingCDB : public GlobalCompilationDatabase {
std::unique_ptr<ProjectModules>
getProjectModules(PathRef File) const override;
- tooling::CompileCommand getFallbackCommand(PathRef File, bool StrongWorkspaceMode = false) const override;
+ tooling::CompileCommand getFallbackCommand(PathRef File) const override;
bool blockUntilIdle(Deadline D) const override;
@@ -117,6 +122,12 @@ class DirectoryBasedGlobalCompilationDatabase
// Only look for a compilation database in this one fixed directory.
// FIXME: fold this into config/context mechanism.
std::optional<Path> CompileCommandsDir;
+ // Working directory for fallback commands
+ // If unset, parent directory of file should be used
+ std::optional<std::string> WorkingDirectory;
+
+ void
+ applyWorkingDirectory(const std::optional<std::string> &&WorkingDirectory);
};
DirectoryBasedGlobalCompilationDatabase(const Options &Opts);
@@ -196,11 +207,12 @@ class OverlayCDB : public DelegatingCDB {
// Adjuster is applied to all commands, fallback or not.
OverlayCDB(const GlobalCompilationDatabase *Base,
std::vector<std::string> FallbackFlags = {},
- CommandMangler Mangler = nullptr);
+ CommandMangler Mangler = nullptr,
+ std::optional<std::string> WorkingDirectory = std::nullopt);
std::optional<tooling::CompileCommand>
getCompileCommand(PathRef File) const override;
- tooling::CompileCommand getFallbackCommand(PathRef File, bool StrongWorkspaceMode = false) const override;
+ tooling::CompileCommand getFallbackCommand(PathRef File) 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 09bb8d1927e90..0a8bca76879b7 100644
--- a/clang-tools-extra/clangd/TUScheduler.cpp
+++ b/clang-tools-extra/clangd/TUScheduler.cpp
@@ -841,14 +841,13 @@ 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),
- StrongWorkspaceMode(Opts.StrongWorkspaceMode),
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, StrongWorkspaceMode);
+ FileInputs.CompileCommand = CDB.getFallbackCommand(FileName);
}
ASTWorker::~ASTWorker() {
@@ -890,7 +889,7 @@ void ASTWorker::update(ParseInputs Inputs, WantDiagnostics WantDiags,
if (Cmd)
Inputs.CompileCommand = std::move(*Cmd);
else
- Inputs.CompileCommand = CDB.getFallbackCommand(FileName, StrongWorkspaceMode);
+ Inputs.CompileCommand = CDB.getFallbackCommand(FileName);
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 d5dbd0e1830d1..d0da20310a8b2 100644
--- a/clang-tools-extra/clangd/TUScheduler.h
+++ b/clang-tools-extra/clangd/TUScheduler.h
@@ -236,9 +236,6 @@ class TUScheduler {
/// Typically to inject per-file configuration.
/// If the path is empty, context sholud be "generic".
std::function<Context(PathRef)> ContextProvider;
-
- /// Sets an alterante mode of operation. See ClangdServer::Options::StrongWorkspaceMode.
- bool StrongWorkspaceMode;
};
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 451cdbce56fea..6fc2b00cbc063 100644
--- a/clang-tools-extra/clangd/tool/Check.cpp
+++ b/clang-tools-extra/clangd/tool/Check.cpp
@@ -169,6 +169,8 @@ class Checker {
bool buildCommand(const ThreadsafeFS &TFS) {
log("Loading compilation database...");
DirectoryBasedGlobalCompilationDatabase::Options CDBOpts(TFS);
+ if (Opts.StrongWorkspaceMode)
+ CDBOpts.applyWorkingDirectory(std::move(Opts.WorkspaceRoot));
CDBOpts.CompileCommandsDir =
Config::current().CompileFlags.CDBSearch.FixedCDBPath;
BaseCDB =
@@ -178,8 +180,10 @@ class Checker {
getSystemIncludeExtractor(llvm::ArrayRef(Opts.QueryDriverGlobs));
if (Opts.ResourceDir)
Mangler.ResourceDir = *Opts.ResourceDir;
+
CDB = std::make_unique<OverlayCDB>(
- BaseCDB.get(), std::vector<std::string>{}, std::move(Mangler));
+ BaseCDB.get(), std::vector<std::string>{}, std::move(Mangler),
+ CDBOpts.WorkingDirectory);
if (auto TrueCmd = CDB->getCompileCommand(File)) {
Cmd = std::move(*TrueCmd);
@@ -187,7 +191,7 @@ class Checker {
Cmd.Heuristic.empty() ? "from CDB" : Cmd.Heuristic, Cmd.Directory,
printArgv(Cmd.CommandLine));
} else {
- Cmd = CDB->getFallbackCommand(File, Opts.StrongWorkspaceMode);
+ Cmd = CDB->getFallbackCommand(File);
log("Generic fallback command is: [{0}] {1}", Cmd.Directory,
printArgv(Cmd.CommandLine));
}
diff --git a/clang-tools-extra/clangd/unittests/GlobalCompilationDatabaseTests.cpp b/clang-tools-extra/clangd/unittests/GlobalCompilationDatabaseTests.cpp
index c9e01e52dac1f..f4ff7d83d7047 100644
--- a/clang-tools-extra/clangd/unittests/GlobalCompilationDatabaseTests.cpp
+++ b/clang-tools-extra/clangd/unittests/GlobalCompilationDatabaseTests.cpp
@@ -55,6 +55,20 @@ TEST(GlobalCompilationDatabaseTest, FallbackCommand) {
testPath("foo/bar")));
}
+TEST(GlobalCompilationDatabaseTest, FallbackWorkingDirectory) {
+ MockFS TFS;
+ DirectoryBasedGlobalCompilationDatabase::Options CDBOpts(TFS);
+ CDBOpts.applyWorkingDirectory(testPath("foo"));
+ EXPECT_EQ(CDBOpts.WorkingDirectory, testPath("foo"));
+
+ DirectoryBasedGlobalCompilationDatabase DB(CDBOpts);
+ auto Cmd = DB.getFallbackCommand(testPath("foo/src/bar.cc"));
+ EXPECT_EQ(Cmd.Directory, testPath("foo"));
+ EXPECT_THAT(Cmd.CommandLine,
+ ElementsAre("clang", testPath("foo/src/bar.cc")));
+ EXPECT_EQ(Cmd.Output, "");
+}
+
static tooling::CompileCommand cmd(llvm::StringRef File, llvm::StringRef Arg) {
return tooling::CompileCommand(
testRoot(), File, {"clang", std::string(Arg), std::string(File)}, "");
More information about the cfe-commits
mailing list