summaryrefslogtreecommitdiffstats
path: root/clang-tools-extra
diff options
context:
space:
mode:
authorSam McCall <sam.mccall@gmail.com>2018-11-20 10:56:03 +0000
committerSam McCall <sam.mccall@gmail.com>2018-11-20 10:56:03 +0000
commit2bebc3d0602b407b3f351e782940959da5808f97 (patch)
tree29fb56fe1e05f96c44ec65e70022780c07e56af2 /clang-tools-extra
parent17fa42a69b0a48df64b4fb15f69a41cfb248559c (diff)
downloadbcm5719-llvm-2bebc3d0602b407b3f351e782940959da5808f97.tar.gz
bcm5719-llvm-2bebc3d0602b407b3f351e782940959da5808f97.zip
[clangd] Allow observation of changes to global CDBs.
Summary: Currently, changes *within* CDBs are not tracked (CDB has no facility to do so). However, discovery of new CDBs are tracked (all files are marked as modified). Also, files whose compilation commands are explicitly set are marked modified. The intent is to use this for auto-index. Newly discovered files will be indexed with low priority. Reviewers: ilya-biryukov Subscribers: ioeric, MaskRay, jkorous, arphaman, kadircet, cfe-commits Differential Revision: https://reviews.llvm.org/D54475 llvm-svn: 347297
Diffstat (limited to 'clang-tools-extra')
-rw-r--r--clang-tools-extra/clangd/Function.h77
-rw-r--r--clang-tools-extra/clangd/GlobalCompilationDatabase.cpp48
-rw-r--r--clang-tools-extra/clangd/GlobalCompilationDatabase.h19
-rw-r--r--clang-tools-extra/unittests/clangd/CMakeLists.txt1
-rw-r--r--clang-tools-extra/unittests/clangd/FunctionTests.cpp53
-rw-r--r--clang-tools-extra/unittests/clangd/GlobalCompilationDatabaseTests.cpp17
6 files changed, 195 insertions, 20 deletions
diff --git a/clang-tools-extra/clangd/Function.h b/clang-tools-extra/clangd/Function.h
index 88a5bc00025..52effe0d9cc 100644
--- a/clang-tools-extra/clangd/Function.h
+++ b/clang-tools-extra/clangd/Function.h
@@ -16,6 +16,7 @@
#include "llvm/ADT/FunctionExtras.h"
#include "llvm/Support/Error.h"
+#include <mutex>
#include <tuple>
#include <utility>
@@ -83,6 +84,82 @@ ForwardBinder<Func, Args...> Bind(Func F, Args &&... As) {
std::make_tuple(std::forward<Func>(F), std::forward<Args>(As)...));
}
+/// An Event<T> allows events of type T to be broadcast to listeners.
+template <typename T> class Event {
+public:
+ // A Listener is the callback through which events are delivered.
+ using Listener = std::function<void(const T &)>;
+
+ // A subscription defines the scope of when a listener should receive events.
+ // After destroying the subscription, no more events are received.
+ class LLVM_NODISCARD Subscription {
+ Event *Parent;
+ unsigned ListenerID;
+
+ Subscription(Event *Parent, unsigned ListenerID)
+ : Parent(Parent), ListenerID(ListenerID) {}
+ friend Event;
+
+ public:
+ Subscription() : Parent(nullptr) {}
+ Subscription(Subscription &&Other) : Parent(nullptr) {
+ *this = std::move(Other);
+ }
+ Subscription &operator=(Subscription &&Other) {
+ // If *this is active, unsubscribe.
+ if (Parent) {
+ std::lock_guard<std::recursive_mutex>(Parent->ListenersMu);
+ llvm::erase_if(Parent->Listeners,
+ [&](const std::pair<Listener, unsigned> &P) {
+ return P.second == ListenerID;
+ });
+ }
+ // Take over the other subscription, and mark it inactive.
+ std::tie(Parent, ListenerID) = std::tie(Other.Parent, Other.ListenerID);
+ Other.Parent = nullptr;
+ return *this;
+ }
+ // Destroying a subscription may block if an event is being broadcast.
+ ~Subscription() {
+ if (Parent)
+ *this = Subscription(); // Unsubscribe.
+ }
+ };
+
+ // Adds a listener that will observe all future events until the returned
+ // subscription is destroyed.
+ // May block if an event is currently being broadcast.
+ Subscription observe(Listener L) {
+ std::lock_guard<std::recursive_mutex> Lock(ListenersMu);
+ Listeners.push_back({std::move(L), ++ListenerCount});
+ return Subscription(this, ListenerCount);
+ ;
+ }
+
+ // Synchronously sends an event to all registered listeners.
+ // Must not be called from a listener to this event.
+ void broadcast(const T &V) {
+ // FIXME: it would be nice to dynamically check non-reentrancy here.
+ std::lock_guard<std::recursive_mutex> Lock(ListenersMu);
+ for (const auto &L : Listeners)
+ L.first(V);
+ }
+
+ ~Event() {
+ std::lock_guard<std::recursive_mutex> Lock(ListenersMu);
+ assert(Listeners.empty());
+ }
+
+private:
+ static_assert(std::is_same<typename std::decay<T>::type, T>::value,
+ "use a plain type: event values are always passed by const&");
+
+ std::recursive_mutex ListenersMu;
+ bool IsBroadcasting = false;
+ std::vector<std::pair<Listener, unsigned>> Listeners;
+ unsigned ListenerCount = 0;
+};
+
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
index d02e5e9ff61..a21ea5231dc 100644
--- a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
+++ b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
@@ -49,17 +49,17 @@ DirectoryBasedGlobalCompilationDatabase::getCompileCommand(PathRef File) const {
return None;
}
-tooling::CompilationDatabase *
+std::pair<tooling::CompilationDatabase *, /*Cached*/ bool>
DirectoryBasedGlobalCompilationDatabase::getCDBInDirLocked(PathRef Dir) const {
// FIXME(ibiryukov): Invalidate cached compilation databases on changes
auto CachedIt = CompilationDatabases.find(Dir);
if (CachedIt != CompilationDatabases.end())
- return CachedIt->second.get();
+ return {CachedIt->second.get(), true};
std::string Error = "";
auto CDB = tooling::CompilationDatabase::loadFromDirectory(Dir, Error);
auto Result = CDB.get();
CompilationDatabases.insert(std::make_pair(Dir, std::move(CDB)));
- return Result;
+ return {Result, false};
}
tooling::CompilationDatabase *
@@ -69,14 +69,29 @@ DirectoryBasedGlobalCompilationDatabase::getCDBForFile(PathRef File) const {
path::is_absolute(File, path::Style::windows)) &&
"path must be absolute");
+ tooling::CompilationDatabase *CDB = nullptr;
+ bool Cached = false;
std::lock_guard<std::mutex> Lock(Mutex);
- if (CompileCommandsDir)
- return getCDBInDirLocked(*CompileCommandsDir);
- for (auto Path = path::parent_path(File); !Path.empty();
- Path = path::parent_path(Path))
- if (auto CDB = getCDBInDirLocked(Path))
- return CDB;
- return nullptr;
+ if (CompileCommandsDir) {
+ std::tie(CDB, Cached) = getCDBInDirLocked(*CompileCommandsDir);
+ } else {
+ for (auto Path = path::parent_path(File); !CDB && !Path.empty();
+ Path = path::parent_path(Path)) {
+ std::tie(CDB, Cached) = getCDBInDirLocked(Path);
+ }
+ }
+ if (CDB && !Cached)
+ OnCommandChanged.broadcast(CDB->getAllFiles());
+ return CDB;
+}
+
+OverlayCDB::OverlayCDB(const GlobalCompilationDatabase *Base,
+ std::vector<std::string> FallbackFlags)
+ : Base(Base), FallbackFlags(std::move(FallbackFlags)) {
+ if (Base)
+ BaseChanged = Base->watch([this](const std::vector<std::string> Changes) {
+ OnCommandChanged.broadcast(Changes);
+ });
}
Optional<tooling::CompileCommand>
@@ -101,11 +116,14 @@ tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File) const {
void OverlayCDB::setCompileCommand(
PathRef File, llvm::Optional<tooling::CompileCommand> Cmd) {
- std::unique_lock<std::mutex> Lock(Mutex);
- if (Cmd)
- Commands[File] = std::move(*Cmd);
- else
- Commands.erase(File);
+ {
+ std::unique_lock<std::mutex> Lock(Mutex);
+ if (Cmd)
+ Commands[File] = std::move(*Cmd);
+ else
+ Commands.erase(File);
+ }
+ OnCommandChanged.broadcast({File});
}
} // namespace clangd
diff --git a/clang-tools-extra/clangd/GlobalCompilationDatabase.h b/clang-tools-extra/clangd/GlobalCompilationDatabase.h
index 8297db1eaf4..84b17422db5 100644
--- a/clang-tools-extra/clangd/GlobalCompilationDatabase.h
+++ b/clang-tools-extra/clangd/GlobalCompilationDatabase.h
@@ -10,6 +10,7 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_GLOBALCOMPILATIONDATABASE_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_GLOBALCOMPILATIONDATABASE_H
+#include "Function.h"
#include "Path.h"
#include "llvm/ADT/StringMap.h"
#include <memory>
@@ -41,8 +42,15 @@ public:
/// Clangd should treat the results as unreliable.
virtual tooling::CompileCommand getFallbackCommand(PathRef File) const;
- /// FIXME(ibiryukov): add facilities to track changes to compilation flags of
- /// existing targets.
+ using CommandChanged = Event<std::vector<std::string>>;
+ /// The callback is notified when files may have new compile commands.
+ /// The argument is a list of full file paths.
+ CommandChanged::Subscription watch(CommandChanged::Listener L) const {
+ return OnCommandChanged.observe(std::move(L));
+ }
+
+protected:
+ mutable CommandChanged OnCommandChanged;
};
/// Gets compile args from tooling::CompilationDatabases built for parent
@@ -61,7 +69,8 @@ public:
private:
tooling::CompilationDatabase *getCDBForFile(PathRef File) const;
- tooling::CompilationDatabase *getCDBInDirLocked(PathRef File) const;
+ std::pair<tooling::CompilationDatabase *, /*Cached*/ bool>
+ getCDBInDirLocked(PathRef File) const;
mutable std::mutex Mutex;
/// Caches compilation databases loaded from directories(keys are
@@ -81,8 +90,7 @@ public:
// 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)) {}
+ std::vector<std::string> FallbackFlags = {});
llvm::Optional<tooling::CompileCommand>
getCompileCommand(PathRef File) const override;
@@ -98,6 +106,7 @@ private:
llvm::StringMap<tooling::CompileCommand> Commands; /* GUARDED_BY(Mut) */
const GlobalCompilationDatabase *Base;
std::vector<std::string> FallbackFlags;
+ CommandChanged::Subscription BaseChanged;
};
} // namespace clangd
diff --git a/clang-tools-extra/unittests/clangd/CMakeLists.txt b/clang-tools-extra/unittests/clangd/CMakeLists.txt
index 07341c80aba..b6233b07f94 100644
--- a/clang-tools-extra/unittests/clangd/CMakeLists.txt
+++ b/clang-tools-extra/unittests/clangd/CMakeLists.txt
@@ -23,6 +23,7 @@ add_extra_unittest(ClangdTests
FileIndexTests.cpp
FindSymbolsTests.cpp
FSTests.cpp
+ FunctionTests.cpp
FuzzyMatchTests.cpp
GlobalCompilationDatabaseTests.cpp
HeadersTests.cpp
diff --git a/clang-tools-extra/unittests/clangd/FunctionTests.cpp b/clang-tools-extra/unittests/clangd/FunctionTests.cpp
new file mode 100644
index 00000000000..1311ee43b99
--- /dev/null
+++ b/clang-tools-extra/unittests/clangd/FunctionTests.cpp
@@ -0,0 +1,53 @@
+//===-- FunctionsTests.cpp ------------------------------------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "Function.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+namespace clang {
+namespace clangd {
+namespace {
+
+TEST(EventTest, Subscriptions) {
+ Event<int> E;
+ int N = 0;
+ {
+ Event<int>::Subscription SubA;
+ // No subscriptions are active.
+ E.broadcast(42);
+ EXPECT_EQ(0, N);
+
+ Event<int>::Subscription SubB = E.observe([&](int) { ++N; });
+ // Now one is active.
+ E.broadcast(42);
+ EXPECT_EQ(1, N);
+
+ SubA = E.observe([&](int) { ++N; });
+ // Both are active.
+ EXPECT_EQ(1, N);
+ E.broadcast(42);
+ EXPECT_EQ(3, N);
+
+ SubA = std::move(SubB);
+ // One is active.
+ EXPECT_EQ(3, N);
+ E.broadcast(42);
+ EXPECT_EQ(4, N);
+ }
+ // None are active.
+ EXPECT_EQ(4, N);
+ E.broadcast(42);
+ EXPECT_EQ(4, N);
+}
+
+} // namespace
+} // namespace clangd
+} // namespace clang
diff --git a/clang-tools-extra/unittests/clangd/GlobalCompilationDatabaseTests.cpp b/clang-tools-extra/unittests/clangd/GlobalCompilationDatabaseTests.cpp
index f938b302b66..f502f79b71f 100644
--- a/clang-tools-extra/unittests/clangd/GlobalCompilationDatabaseTests.cpp
+++ b/clang-tools-extra/unittests/clangd/GlobalCompilationDatabaseTests.cpp
@@ -88,6 +88,23 @@ TEST_F(OverlayCDBTest, NoBase) {
ElementsAre("clang", testPath("foo.cc"), "-DA=6"));
}
+TEST_F(OverlayCDBTest, Watch) {
+ OverlayCDB Inner(nullptr);
+ OverlayCDB Outer(&Inner);
+
+ std::vector<std::vector<std::string>> Changes;
+ auto Sub = Outer.watch([&](const std::vector<std::string> &ChangedFiles) {
+ Changes.push_back(ChangedFiles);
+ });
+
+ Inner.setCompileCommand("A.cpp", tooling::CompileCommand());
+ Outer.setCompileCommand("B.cpp", tooling::CompileCommand());
+ Inner.setCompileCommand("A.cpp", llvm::None);
+ Outer.setCompileCommand("C.cpp", llvm::None);
+ EXPECT_THAT(Changes, ElementsAre(ElementsAre("A.cpp"), ElementsAre("B.cpp"),
+ ElementsAre("A.cpp"), ElementsAre("C.cpp")));
+}
+
} // namespace
} // namespace clangd
} // namespace clang
OpenPOWER on IntegriCloud