Skip to content

Commit 2bebc3d

Browse files
committed
[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
1 parent 17fa42a commit 2bebc3d

File tree

6 files changed

+195
-20
lines changed

6 files changed

+195
-20
lines changed

clang-tools-extra/clangd/Function.h

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include "llvm/ADT/FunctionExtras.h"
1818
#include "llvm/Support/Error.h"
19+
#include <mutex>
1920
#include <tuple>
2021
#include <utility>
2122

@@ -83,6 +84,82 @@ ForwardBinder<Func, Args...> Bind(Func F, Args &&... As) {
8384
std::make_tuple(std::forward<Func>(F), std::forward<Args>(As)...));
8485
}
8586

87+
/// An Event<T> allows events of type T to be broadcast to listeners.
88+
template <typename T> class Event {
89+
public:
90+
// A Listener is the callback through which events are delivered.
91+
using Listener = std::function<void(const T &)>;
92+
93+
// A subscription defines the scope of when a listener should receive events.
94+
// After destroying the subscription, no more events are received.
95+
class LLVM_NODISCARD Subscription {
96+
Event *Parent;
97+
unsigned ListenerID;
98+
99+
Subscription(Event *Parent, unsigned ListenerID)
100+
: Parent(Parent), ListenerID(ListenerID) {}
101+
friend Event;
102+
103+
public:
104+
Subscription() : Parent(nullptr) {}
105+
Subscription(Subscription &&Other) : Parent(nullptr) {
106+
*this = std::move(Other);
107+
}
108+
Subscription &operator=(Subscription &&Other) {
109+
// If *this is active, unsubscribe.
110+
if (Parent) {
111+
std::lock_guard<std::recursive_mutex>(Parent->ListenersMu);
112+
llvm::erase_if(Parent->Listeners,
113+
[&](const std::pair<Listener, unsigned> &P) {
114+
return P.second == ListenerID;
115+
});
116+
}
117+
// Take over the other subscription, and mark it inactive.
118+
std::tie(Parent, ListenerID) = std::tie(Other.Parent, Other.ListenerID);
119+
Other.Parent = nullptr;
120+
return *this;
121+
}
122+
// Destroying a subscription may block if an event is being broadcast.
123+
~Subscription() {
124+
if (Parent)
125+
*this = Subscription(); // Unsubscribe.
126+
}
127+
};
128+
129+
// Adds a listener that will observe all future events until the returned
130+
// subscription is destroyed.
131+
// May block if an event is currently being broadcast.
132+
Subscription observe(Listener L) {
133+
std::lock_guard<std::recursive_mutex> Lock(ListenersMu);
134+
Listeners.push_back({std::move(L), ++ListenerCount});
135+
return Subscription(this, ListenerCount);
136+
;
137+
}
138+
139+
// Synchronously sends an event to all registered listeners.
140+
// Must not be called from a listener to this event.
141+
void broadcast(const T &V) {
142+
// FIXME: it would be nice to dynamically check non-reentrancy here.
143+
std::lock_guard<std::recursive_mutex> Lock(ListenersMu);
144+
for (const auto &L : Listeners)
145+
L.first(V);
146+
}
147+
148+
~Event() {
149+
std::lock_guard<std::recursive_mutex> Lock(ListenersMu);
150+
assert(Listeners.empty());
151+
}
152+
153+
private:
154+
static_assert(std::is_same<typename std::decay<T>::type, T>::value,
155+
"use a plain type: event values are always passed by const&");
156+
157+
std::recursive_mutex ListenersMu;
158+
bool IsBroadcasting = false;
159+
std::vector<std::pair<Listener, unsigned>> Listeners;
160+
unsigned ListenerCount = 0;
161+
};
162+
86163
} // namespace clangd
87164
} // namespace clang
88165

clang-tools-extra/clangd/GlobalCompilationDatabase.cpp

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,17 @@ DirectoryBasedGlobalCompilationDatabase::getCompileCommand(PathRef File) const {
4949
return None;
5050
}
5151

52-
tooling::CompilationDatabase *
52+
std::pair<tooling::CompilationDatabase *, /*Cached*/ bool>
5353
DirectoryBasedGlobalCompilationDatabase::getCDBInDirLocked(PathRef Dir) const {
5454
// FIXME(ibiryukov): Invalidate cached compilation databases on changes
5555
auto CachedIt = CompilationDatabases.find(Dir);
5656
if (CachedIt != CompilationDatabases.end())
57-
return CachedIt->second.get();
57+
return {CachedIt->second.get(), true};
5858
std::string Error = "";
5959
auto CDB = tooling::CompilationDatabase::loadFromDirectory(Dir, Error);
6060
auto Result = CDB.get();
6161
CompilationDatabases.insert(std::make_pair(Dir, std::move(CDB)));
62-
return Result;
62+
return {Result, false};
6363
}
6464

6565
tooling::CompilationDatabase *
@@ -69,14 +69,29 @@ DirectoryBasedGlobalCompilationDatabase::getCDBForFile(PathRef File) const {
6969
path::is_absolute(File, path::Style::windows)) &&
7070
"path must be absolute");
7171

72+
tooling::CompilationDatabase *CDB = nullptr;
73+
bool Cached = false;
7274
std::lock_guard<std::mutex> Lock(Mutex);
73-
if (CompileCommandsDir)
74-
return getCDBInDirLocked(*CompileCommandsDir);
75-
for (auto Path = path::parent_path(File); !Path.empty();
76-
Path = path::parent_path(Path))
77-
if (auto CDB = getCDBInDirLocked(Path))
78-
return CDB;
79-
return nullptr;
75+
if (CompileCommandsDir) {
76+
std::tie(CDB, Cached) = getCDBInDirLocked(*CompileCommandsDir);
77+
} else {
78+
for (auto Path = path::parent_path(File); !CDB && !Path.empty();
79+
Path = path::parent_path(Path)) {
80+
std::tie(CDB, Cached) = getCDBInDirLocked(Path);
81+
}
82+
}
83+
if (CDB && !Cached)
84+
OnCommandChanged.broadcast(CDB->getAllFiles());
85+
return CDB;
86+
}
87+
88+
OverlayCDB::OverlayCDB(const GlobalCompilationDatabase *Base,
89+
std::vector<std::string> FallbackFlags)
90+
: Base(Base), FallbackFlags(std::move(FallbackFlags)) {
91+
if (Base)
92+
BaseChanged = Base->watch([this](const std::vector<std::string> Changes) {
93+
OnCommandChanged.broadcast(Changes);
94+
});
8095
}
8196

8297
Optional<tooling::CompileCommand>
@@ -101,11 +116,14 @@ tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File) const {
101116

102117
void OverlayCDB::setCompileCommand(
103118
PathRef File, llvm::Optional<tooling::CompileCommand> Cmd) {
104-
std::unique_lock<std::mutex> Lock(Mutex);
105-
if (Cmd)
106-
Commands[File] = std::move(*Cmd);
107-
else
108-
Commands.erase(File);
119+
{
120+
std::unique_lock<std::mutex> Lock(Mutex);
121+
if (Cmd)
122+
Commands[File] = std::move(*Cmd);
123+
else
124+
Commands.erase(File);
125+
}
126+
OnCommandChanged.broadcast({File});
109127
}
110128

111129
} // namespace clangd

clang-tools-extra/clangd/GlobalCompilationDatabase.h

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_GLOBALCOMPILATIONDATABASE_H
1111
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_GLOBALCOMPILATIONDATABASE_H
1212

13+
#include "Function.h"
1314
#include "Path.h"
1415
#include "llvm/ADT/StringMap.h"
1516
#include <memory>
@@ -41,8 +42,15 @@ class GlobalCompilationDatabase {
4142
/// Clangd should treat the results as unreliable.
4243
virtual tooling::CompileCommand getFallbackCommand(PathRef File) const;
4344

44-
/// FIXME(ibiryukov): add facilities to track changes to compilation flags of
45-
/// existing targets.
45+
using CommandChanged = Event<std::vector<std::string>>;
46+
/// The callback is notified when files may have new compile commands.
47+
/// The argument is a list of full file paths.
48+
CommandChanged::Subscription watch(CommandChanged::Listener L) const {
49+
return OnCommandChanged.observe(std::move(L));
50+
}
51+
52+
protected:
53+
mutable CommandChanged OnCommandChanged;
4654
};
4755

4856
/// Gets compile args from tooling::CompilationDatabases built for parent
@@ -61,7 +69,8 @@ class DirectoryBasedGlobalCompilationDatabase
6169

6270
private:
6371
tooling::CompilationDatabase *getCDBForFile(PathRef File) const;
64-
tooling::CompilationDatabase *getCDBInDirLocked(PathRef File) const;
72+
std::pair<tooling::CompilationDatabase *, /*Cached*/ bool>
73+
getCDBInDirLocked(PathRef File) const;
6574

6675
mutable std::mutex Mutex;
6776
/// Caches compilation databases loaded from directories(keys are
@@ -81,8 +90,7 @@ class OverlayCDB : public GlobalCompilationDatabase {
8190
// Base may be null, in which case no entries are inherited.
8291
// FallbackFlags are added to the fallback compile command.
8392
OverlayCDB(const GlobalCompilationDatabase *Base,
84-
std::vector<std::string> FallbackFlags = {})
85-
: Base(Base), FallbackFlags(std::move(FallbackFlags)) {}
93+
std::vector<std::string> FallbackFlags = {});
8694

8795
llvm::Optional<tooling::CompileCommand>
8896
getCompileCommand(PathRef File) const override;
@@ -98,6 +106,7 @@ class OverlayCDB : public GlobalCompilationDatabase {
98106
llvm::StringMap<tooling::CompileCommand> Commands; /* GUARDED_BY(Mut) */
99107
const GlobalCompilationDatabase *Base;
100108
std::vector<std::string> FallbackFlags;
109+
CommandChanged::Subscription BaseChanged;
101110
};
102111

103112
} // namespace clangd

clang-tools-extra/unittests/clangd/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ add_extra_unittest(ClangdTests
2323
FileIndexTests.cpp
2424
FindSymbolsTests.cpp
2525
FSTests.cpp
26+
FunctionTests.cpp
2627
FuzzyMatchTests.cpp
2728
GlobalCompilationDatabaseTests.cpp
2829
HeadersTests.cpp
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//===-- FunctionsTests.cpp ------------------------------------------------===//
2+
//
3+
// The LLVM Compiler Infrastructure
4+
//
5+
// This file is distributed under the University of Illinois Open Source
6+
// License. See LICENSE.TXT for details.
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
#include "Function.h"
11+
#include "gmock/gmock.h"
12+
#include "gtest/gtest.h"
13+
14+
using namespace llvm;
15+
namespace clang {
16+
namespace clangd {
17+
namespace {
18+
19+
TEST(EventTest, Subscriptions) {
20+
Event<int> E;
21+
int N = 0;
22+
{
23+
Event<int>::Subscription SubA;
24+
// No subscriptions are active.
25+
E.broadcast(42);
26+
EXPECT_EQ(0, N);
27+
28+
Event<int>::Subscription SubB = E.observe([&](int) { ++N; });
29+
// Now one is active.
30+
E.broadcast(42);
31+
EXPECT_EQ(1, N);
32+
33+
SubA = E.observe([&](int) { ++N; });
34+
// Both are active.
35+
EXPECT_EQ(1, N);
36+
E.broadcast(42);
37+
EXPECT_EQ(3, N);
38+
39+
SubA = std::move(SubB);
40+
// One is active.
41+
EXPECT_EQ(3, N);
42+
E.broadcast(42);
43+
EXPECT_EQ(4, N);
44+
}
45+
// None are active.
46+
EXPECT_EQ(4, N);
47+
E.broadcast(42);
48+
EXPECT_EQ(4, N);
49+
}
50+
51+
} // namespace
52+
} // namespace clangd
53+
} // namespace clang

clang-tools-extra/unittests/clangd/GlobalCompilationDatabaseTests.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,23 @@ TEST_F(OverlayCDBTest, NoBase) {
8888
ElementsAre("clang", testPath("foo.cc"), "-DA=6"));
8989
}
9090

91+
TEST_F(OverlayCDBTest, Watch) {
92+
OverlayCDB Inner(nullptr);
93+
OverlayCDB Outer(&Inner);
94+
95+
std::vector<std::vector<std::string>> Changes;
96+
auto Sub = Outer.watch([&](const std::vector<std::string> &ChangedFiles) {
97+
Changes.push_back(ChangedFiles);
98+
});
99+
100+
Inner.setCompileCommand("A.cpp", tooling::CompileCommand());
101+
Outer.setCompileCommand("B.cpp", tooling::CompileCommand());
102+
Inner.setCompileCommand("A.cpp", llvm::None);
103+
Outer.setCompileCommand("C.cpp", llvm::None);
104+
EXPECT_THAT(Changes, ElementsAre(ElementsAre("A.cpp"), ElementsAre("B.cpp"),
105+
ElementsAre("A.cpp"), ElementsAre("C.cpp")));
106+
}
107+
91108
} // namespace
92109
} // namespace clangd
93110
} // namespace clang

0 commit comments

Comments
 (0)