ErrorCode::InvalidRequest));
if (const auto &Dir = Params.initializationOptions.compilationDatabasePath)
CompileCommandsDir = Dir;
- CDB.emplace(UseInMemoryCDB
- ? CompilationDB::makeInMemory()
- : CompilationDB::makeDirectoryBased(CompileCommandsDir));
- Server.emplace(CDB->getCDB(), FSProvider,
- static_cast<DiagnosticsConsumer &>(*this), ClangdServerOpts);
+ if (UseDirBasedCDB)
+ BaseCDB = llvm::make_unique<DirectoryBasedGlobalCompilationDatabase>(
+ CompileCommandsDir);
+ CDB.emplace(BaseCDB.get());
+ Server.emplace(*CDB, FSProvider, static_cast<DiagnosticsConsumer &>(*this),
+ ClangdServerOpts);
applyConfiguration(Params.initializationOptions.ConfigSettings);
CCOpts.EnableSnippets = Params.capabilities.CompletionSnippets;
/// The opened files need to be reparsed only when some existing
/// entries are changed.
PathRef File = Entry.first;
- if (!CDB->setCompilationCommandForFile(
- File, tooling::CompileCommand(
- std::move(Entry.second.workingDirectory), File,
- std::move(Entry.second.compilationCommand),
- /*Output=*/"")))
- ShouldReparseOpenFiles = true;
+ auto Old = CDB->getCompileCommand(File);
+ auto New =
+ tooling::CompileCommand(std::move(Entry.second.workingDirectory), File,
+ std::move(Entry.second.compilationCommand),
+ /*Output=*/"");
+ if (Old != New)
+ CDB->setCompileCommand(File, std::move(New));
+ ShouldReparseOpenFiles = true;
}
if (ShouldReparseOpenFiles)
reparseOpenedFiles();
ClangdLSPServer::ClangdLSPServer(class Transport &Transp,
const clangd::CodeCompleteOptions &CCOpts,
Optional<Path> CompileCommandsDir,
- bool ShouldUseInMemoryCDB,
+ bool UseDirBasedCDB,
const ClangdServer::Options &Opts)
: Transp(Transp), MsgHandler(new MessageHandler(*this)), CCOpts(CCOpts),
SupportedSymbolKinds(defaultSymbolKinds()),
SupportedCompletionItemKinds(defaultCompletionItemKinds()),
- UseInMemoryCDB(ShouldUseInMemoryCDB),
+ UseDirBasedCDB(UseDirBasedCDB),
CompileCommandsDir(std::move(CompileCommandsDir)),
ClangdServerOpts(Opts) {
// clang-format off
WantDiagnostics::Auto);
}
-ClangdLSPServer::CompilationDB ClangdLSPServer::CompilationDB::makeInMemory() {
- return CompilationDB(llvm::make_unique<InMemoryCompilationDb>(),
- /*IsDirectoryBased=*/false);
-}
-
-ClangdLSPServer::CompilationDB
-ClangdLSPServer::CompilationDB::makeDirectoryBased(
- Optional<Path> CompileCommandsDir) {
- auto CDB = llvm::make_unique<DirectoryBasedGlobalCompilationDatabase>(
- std::move(CompileCommandsDir));
- return CompilationDB(std::move(CDB),
- /*IsDirectoryBased=*/true);
-}
-
-bool ClangdLSPServer::CompilationDB::setCompilationCommandForFile(
- PathRef File, tooling::CompileCommand CompilationCommand) {
- if (IsDirectoryBased) {
- elog("Trying to set compile command for {0} while using directory-based "
- "compilation database",
- File);
- return false;
- }
- return static_cast<InMemoryCompilationDb *>(CDB.get())
- ->setCompilationCommandForFile(File, std::move(CompilationCommand));
-}
-
} // namespace clangd
} // namespace clang
/// If \p CompileCommandsDir has a value, compile_commands.json will be
/// loaded only from \p CompileCommandsDir. Otherwise, clangd will look
/// for compile_commands.json in all parent directories of each file.
+ /// If UseDirBasedCDB is false, compile commands are not read from disk.
+ // FIXME: Clean up signature around CDBs.
ClangdLSPServer(Transport &Transp, const clangd::CodeCompleteOptions &CCOpts,
- llvm::Optional<Path> CompileCommandsDir,
- bool ShouldUseInMemoryCDB, const ClangdServer::Options &Opts);
+ llvm::Optional<Path> CompileCommandsDir, bool UseDirBasedCDB,
+ const ClangdServer::Options &Opts);
~ClangdLSPServer();
/// Run LSP server loop, communicating with the Transport provided in the
/// Caches FixIts per file and diagnostics
llvm::StringMap<DiagnosticToReplacementMap> FixItsMap;
- /// Encapsulates the directory-based or the in-memory compilation database
- /// that's used by the LSP server.
- class CompilationDB {
- public:
- static CompilationDB makeInMemory();
- static CompilationDB
- makeDirectoryBased(llvm::Optional<Path> CompileCommandsDir);
-
- /// Sets the compilation command for a particular file.
- /// Only valid for in-memory CDB, no-op and error log on DirectoryBasedCDB.
- ///
- /// \returns True if the File had no compilation command before.
- bool
- setCompilationCommandForFile(PathRef File,
- tooling::CompileCommand CompilationCommand);
-
- /// Returns a CDB that should be used to get compile commands for the
- /// current instance of ClangdLSPServer.
- GlobalCompilationDatabase &getCDB() { return *CDB; }
-
- private:
- CompilationDB(std::unique_ptr<GlobalCompilationDatabase> CDB,
- bool IsDirectoryBased)
- : CDB(std::move(CDB)), IsDirectoryBased(IsDirectoryBased) {}
-
- // if IsDirectoryBased is true, an instance of InMemoryCDB.
- // If IsDirectoryBased is false, an instance of DirectoryBasedCDB.
- std::unique_ptr<GlobalCompilationDatabase> CDB;
- bool IsDirectoryBased;
- };
-
// Most code should not deal with Transport directly.
// MessageHandler deals with incoming messages, use call() etc for outgoing.
clangd::Transport &Transp;
DraftStore DraftMgr;
// The CDB is created by the "initialize" LSP method.
- bool UseInMemoryCDB; // FIXME: make this a capability.
+ bool UseDirBasedCDB; // FIXME: make this a capability.
llvm::Optional<Path> CompileCommandsDir; // FIXME: merge with capability?
- llvm::Optional<CompilationDB> CDB;
+ std::unique_ptr<GlobalCompilationDatabase> BaseCDB;
+ // CDB is BaseCDB plus any comands overridden via LSP extensions.
+ llvm::Optional<OverlayCDB> CDB;
// The ClangdServer is created by the "initialize" LSP method.
// It is destroyed before run() returns, to ensure worker threads exit.
ClangdServer::Options ClangdServerOpts;
}
Optional<tooling::CompileCommand>
-InMemoryCompilationDb::getCompileCommand(PathRef File) const {
+OverlayCDB::getCompileCommand(PathRef File) const {
+ {
+ std::lock_guard<std::mutex> Lock(Mutex);
+ auto It = Commands.find(File);
+ if (It != Commands.end())
+ return It->second;
+ }
+ return Base ? Base->getCompileCommand(File) : None;
+}
+
+tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File) const {
+ auto Cmd = Base ? Base->getFallbackCommand(File)
+ : GlobalCompilationDatabase::getFallbackCommand(File);
std::lock_guard<std::mutex> Lock(Mutex);
- auto It = Commands.find(File);
- if (It == Commands.end())
- return None;
- return It->second;
+ Cmd.CommandLine.insert(Cmd.CommandLine.end(), FallbackFlags.begin(),
+ FallbackFlags.end());
+ return Cmd;
}
-bool InMemoryCompilationDb::setCompilationCommandForFile(
- PathRef File, tooling::CompileCommand CompilationCommand) {
+void OverlayCDB::setCompileCommand(
+ PathRef File, llvm::Optional<tooling::CompileCommand> Cmd) {
std::unique_lock<std::mutex> Lock(Mutex);
- auto ItInserted = Commands.insert(std::make_pair(File, CompilationCommand));
- if (ItInserted.second)
- return true;
- ItInserted.first->setValue(std::move(CompilationCommand));
- return false;
+ if (Cmd)
+ Commands[File] = std::move(*Cmd);
+ else
+ Commands.erase(File);
}
} // namespace clangd
llvm::Optional<Path> CompileCommandsDir;
};
-/// Gets compile args from an in-memory mapping based on a filepath. Typically
-/// used by clients who provide the compile commands themselves.
-class InMemoryCompilationDb : public GlobalCompilationDatabase {
+/// Wraps another compilation database, and supports overriding the commands
+/// using an in-memory mapping.
+class OverlayCDB : public GlobalCompilationDatabase {
public:
- /// Gets compile command for \p File from the stored mapping.
+ // Base may be null, in which case no entries are inherited.
+ // FallbackFlags are added to the fallback compile command.
+ OverlayCDB(const GlobalCompilationDatabase *Base,
+ std::vector<std::string> FallbackFlags = {})
+ : Base(Base), FallbackFlags(std::move(FallbackFlags)) {}
+
llvm::Optional<tooling::CompileCommand>
getCompileCommand(PathRef File) const override;
+ tooling::CompileCommand getFallbackCommand(PathRef File) const override;
- /// Sets the compilation command for a particular file.
- ///
- /// \returns True if the File had no compilation command before.
- bool setCompilationCommandForFile(PathRef File,
- tooling::CompileCommand CompilationCommand);
+ /// Sets or clears the compilation command for a particular file.
+ void
+ setCompileCommand(PathRef File,
+ llvm::Optional<tooling::CompileCommand> CompilationCommand);
private:
mutable std::mutex Mutex;
llvm::StringMap<tooling::CompileCommand> Commands; /* GUARDED_BY(Mut) */
+ const GlobalCompilationDatabase *Base;
+ std::vector<std::string> FallbackFlags;
};
} // namespace clangd
InputStyle);
ClangdLSPServer LSPServer(
*Transport, CCOpts, CompileCommandsDirPath,
- /*ShouldUseInMemoryCDB=*/CompileArgsFrom == LSPCompileArgs, Opts);
+ /*UseDirBasedCDB=*/CompileArgsFrom == FilesystemCompileArgs, Opts);
constexpr int NoShutdownRequestErrorCode = 1;
set_thread_name("clangd.main");
return LSPServer.run() ? 0 : NoShutdownRequestErrorCode;
testPath("foo/bar.h")));
}
+static tooling::CompileCommand cmd(StringRef File, StringRef Arg) {
+ return tooling::CompileCommand(testRoot(), File, {"clang", Arg, File}, "");
+}
+
+class OverlayCDBTest : public ::testing::Test {
+ class BaseCDB : public GlobalCompilationDatabase {
+ public:
+ Optional<tooling::CompileCommand>
+ getCompileCommand(StringRef File) const override {
+ if (File == testPath("foo.cc"))
+ return cmd(File, "-DA=1");
+ return None;
+ }
+
+ tooling::CompileCommand getFallbackCommand(StringRef File) const override {
+ return cmd(File, "-DA=2");
+ }
+ };
+
+protected:
+ OverlayCDBTest() : Base(llvm::make_unique<BaseCDB>()) {}
+ std::unique_ptr<GlobalCompilationDatabase> Base;
+};
+
+TEST_F(OverlayCDBTest, GetCompileCommand) {
+ OverlayCDB CDB(Base.get());
+ EXPECT_EQ(CDB.getCompileCommand(testPath("foo.cc")),
+ Base->getCompileCommand(testPath("foo.cc")));
+ EXPECT_EQ(CDB.getCompileCommand(testPath("missing.cc")), llvm::None);
+
+ auto Override = cmd(testPath("foo.cc"), "-DA=3");
+ CDB.setCompileCommand(testPath("foo.cc"), Override);
+ EXPECT_EQ(CDB.getCompileCommand(testPath("foo.cc")), Override);
+ EXPECT_EQ(CDB.getCompileCommand(testPath("missing.cc")), llvm::None);
+ CDB.setCompileCommand(testPath("missing.cc"), Override);
+ EXPECT_EQ(CDB.getCompileCommand(testPath("missing.cc")), Override);
+}
+
+TEST_F(OverlayCDBTest, GetFallbackCommand) {
+ OverlayCDB CDB(Base.get(), {"-DA=4"});
+ EXPECT_THAT(CDB.getFallbackCommand(testPath("bar.cc")).CommandLine,
+ ElementsAre("clang", "-DA=2", testPath("bar.cc"), "-DA=4"));
+}
+
+TEST_F(OverlayCDBTest, NoBase) {
+ OverlayCDB CDB(nullptr, {"-DA=6"});
+ EXPECT_EQ(CDB.getCompileCommand(testPath("bar.cc")), None);
+ auto Override = cmd(testPath("bar.cc"), "-DA=5");
+ CDB.setCompileCommand(testPath("bar.cc"), Override);
+ EXPECT_EQ(CDB.getCompileCommand(testPath("bar.cc")), Override);
+
+ EXPECT_THAT(CDB.getFallbackCommand(testPath("foo.cc")).CommandLine,
+ ElementsAre("clang", testPath("foo.cc"), "-DA=6"));
+}
+
} // namespace
} // namespace clangd
} // namespace clang