https://github.com/Dominicentek updated 
https://github.com/llvm/llvm-project/pull/155905

>From 9a30c2b92be357deac5a65e2fa0952d91634de70 Mon Sep 17 00:00:00 2001
From: Dominicentek <[email protected]>
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 <[email protected]>
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 <[email protected]>
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)}, "");

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to