https://github.com/Dominicentek created https://github.com/llvm/llvm-project/pull/155905
Adds an ability to change the current working directory for fallback commands. >From 9a30c2b92be357deac5a65e2fa0952d91634de70 Mon Sep 17 00:00:00 2001 From: Dominicentek <dominicentekgam...@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()) { _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits