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(CDBOpts); } diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h index 4a1eae188f7eb..6ed5db517eb5e 100644 --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -152,6 +152,11 @@ class ClangdServer { /// FIXME: If not set, should use the current working directory. std::optional WorkspaceRoot; + /// 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). /// If std::nullopt, ClangdServer calls diff --git a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp index c6afd0bc07cbd..e2ac2d17171be 100644 --- a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp +++ b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp @@ -64,9 +64,10 @@ 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), - 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; } @@ -349,7 +350,8 @@ bool DirectoryBasedGlobalCompilationDatabase::DirectoryCache::load( DirectoryBasedGlobalCompilationDatabase:: DirectoryBasedGlobalCompilationDatabase(const Options &Opts) - : Opts(Opts), Broadcaster(std::make_unique(*this)) { + : GlobalCompilationDatabase(Opts.WorkingDirectory), Opts(Opts), + Broadcaster(std::make_unique(*this)) { if (!this->Opts.ContextProvider) this->Opts.ContextProvider = [](llvm::StringRef) { return Context::current().clone(); @@ -460,6 +462,17 @@ DirectoryBasedGlobalCompilationDatabase::lookupCDB( return Result; } +void DirectoryBasedGlobalCompilationDatabase::Options::applyWorkingDirectory( + const std::optional &&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. // @@ -759,8 +772,9 @@ DirectoryBasedGlobalCompilationDatabase::getProjectModules(PathRef File) const { OverlayCDB::OverlayCDB(const GlobalCompilationDatabase *Base, std::vector FallbackFlags, - CommandMangler Mangler) - : DelegatingCDB(Base), Mangler(std::move(Mangler)), + CommandMangler Mangler, + std::optional WorkingDirectory) + : DelegatingCDB(Base, WorkingDirectory), Mangler(std::move(Mangler)), FallbackFlags(std::move(FallbackFlags)) {} std::optional @@ -844,16 +858,18 @@ OverlayCDB::getProjectModules(PathRef File) const { return MDB; } -DelegatingCDB::DelegatingCDB(const GlobalCompilationDatabase *Base) - : Base(Base) { +DelegatingCDB::DelegatingCDB(const GlobalCompilationDatabase *Base, + std::optional WorkingDirectory) + : GlobalCompilationDatabase(WorkingDirectory), Base(Base) { if (Base) BaseChanged = Base->watch([this](const std::vector Changes) { OnCommandChanged.broadcast(Changes); }); } -DelegatingCDB::DelegatingCDB(std::unique_ptr Base) - : DelegatingCDB(Base.get()) { +DelegatingCDB::DelegatingCDB(std::unique_ptr Base, + std::optional WorkingDirectory) + : DelegatingCDB(Base.get(), WorkingDirectory) { BaseOwner = std::move(Base); } diff --git a/clang-tools-extra/clangd/GlobalCompilationDatabase.h b/clang-tools-extra/clangd/GlobalCompilationDatabase.h index 1d636d73664be..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 WorkingDirectory) + : WorkingDirectory(WorkingDirectory) {} virtual ~GlobalCompilationDatabase() = default; /// If there are any known-good commands for building this file, returns one. @@ -69,14 +71,17 @@ class GlobalCompilationDatabase { } protected: + std::optional 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 Base); + DelegatingCDB(const GlobalCompilationDatabase *Base, + std::optional WorkingDirectory); + DelegatingCDB(std::unique_ptr Base, + std::optional WorkingDirectory); std::optional getCompileCommand(PathRef File) 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 CompileCommandsDir; + // Working directory for fallback commands + // If unset, parent directory of file should be used + std::optional WorkingDirectory; + + void + applyWorkingDirectory(const std::optional &&WorkingDirectory); }; DirectoryBasedGlobalCompilationDatabase(const Options &Opts); @@ -196,7 +207,8 @@ class OverlayCDB : public DelegatingCDB { // Adjuster is applied to all commands, fallback or not. OverlayCDB(const GlobalCompilationDatabase *Base, std::vector FallbackFlags = {}, - CommandMangler Mangler = nullptr); + CommandMangler Mangler = nullptr, + std::optional WorkingDirectory = std::nullopt); std::optional getCompileCommand(PathRef File) const override; diff --git a/clang-tools-extra/clangd/TUScheduler.h b/clang-tools-extra/clangd/TUScheduler.h index d0da20310a8b2..e23a4d63bf297 100644 --- a/clang-tools-extra/clangd/TUScheduler.h +++ b/clang-tools-extra/clangd/TUScheduler.h @@ -236,6 +236,8 @@ class TUScheduler { /// Typically to inject per-file configuration. /// If the path is empty, context sholud be "generic". std::function ContextProvider; + + bool test; }; 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..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( - BaseCDB.get(), std::vector{}, std::move(Mangler)); + BaseCDB.get(), std::vector{}, std::move(Mangler), + CDBOpts.WorkingDirectory); if (auto TrueCmd = CDB->getCompileCommand(File)) { Cmd = std::move(*TrueCmd); @@ -502,7 +506,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 4a990f8f716ca..39f2a8c8346c6 100644 --- a/clang-tools-extra/clangd/tool/ClangdMain.cpp +++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp @@ -500,6 +500,16 @@ opt EnableConfig{ init(true), }; +opt 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 UseDirtyHeaders{"use-dirty-headers", cat(Misc), desc("Use files open in the editor when parsing " "headers instead of reading from the disk"), @@ -907,6 +917,7 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var } if (!ResourceDir.empty()) Opts.ResourceDir = ResourceDir; + Opts.StrongWorkspaceMode = StrongWorkspaceMode; Opts.BuildDynamicSymbolIndex = true; #if CLANGD_ENABLE_REMOTE if (RemoteIndexAddress.empty() != ProjectRoot.empty()) { diff --git a/clang-tools-extra/clangd/unittests/ClangdTests.cpp b/clang-tools-extra/clangd/unittests/ClangdTests.cpp index 9ea7c3e02411d..24ca8c93fb2d3 100644 --- a/clang-tools-extra/clangd/unittests/ClangdTests.cpp +++ b/clang-tools-extra/clangd/unittests/ClangdTests.cpp @@ -1110,7 +1110,8 @@ TEST(ClangdServerTest, FallbackWhenWaitingForCompileCommand) { class DelayedCompilationDatabase : public GlobalCompilationDatabase { public: DelayedCompilationDatabase(Notification &CanReturnCommand) - : CanReturnCommand(CanReturnCommand) {} + : GlobalCompilationDatabase(std::nullopt), + CanReturnCommand(CanReturnCommand) {} std::optional getCompileCommand(PathRef File) const override { diff --git a/clang-tools-extra/clangd/unittests/GlobalCompilationDatabaseTests.cpp b/clang-tools-extra/clangd/unittests/GlobalCompilationDatabaseTests.cpp index c9e01e52dac1f..631cbc1be12e8 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)}, ""); @@ -63,6 +77,8 @@ static tooling::CompileCommand cmd(llvm::StringRef File, llvm::StringRef Arg) { class OverlayCDBTest : public ::testing::Test { class BaseCDB : public GlobalCompilationDatabase { public: + BaseCDB() : GlobalCompilationDatabase(std::nullopt) {} + std::optional getCompileCommand(llvm::StringRef File) const override { if (File == testPath("foo.cc")) diff --git a/clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp b/clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp index 43f38e39c8952..531067095464c 100644 --- a/clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp +++ b/clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp @@ -1329,6 +1329,8 @@ TEST_F(TUSchedulerTests, IncluderCache) { OK = testPath("ok.h"), NotIncluded = testPath("not_included.h"); struct NoHeadersCDB : public GlobalCompilationDatabase { + NoHeadersCDB() : GlobalCompilationDatabase(std::nullopt) {} + std::optional getCompileCommand(PathRef File) const override { if (File == NoCmd || File == NotIncluded || FailAll) diff --git a/clang-tools-extra/clangd/unittests/TestFS.cpp b/clang-tools-extra/clangd/unittests/TestFS.cpp index bb309609eda20..33efc3651d359 100644 --- a/clang-tools-extra/clangd/unittests/TestFS.cpp +++ b/clang-tools-extra/clangd/unittests/TestFS.cpp @@ -46,7 +46,8 @@ buildTestFS(llvm::StringMap const &Files, MockCompilationDatabase::MockCompilationDatabase(llvm::StringRef Directory, llvm::StringRef RelPathPrefix) - : ExtraClangFlags({"-ffreestanding"}), Directory(Directory), + : GlobalCompilationDatabase(std::nullopt), + ExtraClangFlags({"-ffreestanding"}), Directory(Directory), RelPathPrefix(RelPathPrefix) { // -ffreestanding avoids implicit stdc-predef.h. }