annotate clang-tools-extra/clangd/GlobalCompilationDatabase.cpp @ 160:dbfec6499728

...
author anatofuz
date Wed, 18 Mar 2020 19:11:03 +0900
parents 1d019706d866
children 0572611fdcc8
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
150
anatofuz
parents:
diff changeset
1 //===--- GlobalCompilationDatabase.cpp ---------------------------*- C++-*-===//
anatofuz
parents:
diff changeset
2 //
anatofuz
parents:
diff changeset
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
anatofuz
parents:
diff changeset
4 // See https://llvm.org/LICENSE.txt for license information.
anatofuz
parents:
diff changeset
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
anatofuz
parents:
diff changeset
6 //
anatofuz
parents:
diff changeset
7 //===----------------------------------------------------------------------===//
anatofuz
parents:
diff changeset
8
anatofuz
parents:
diff changeset
9 #include "GlobalCompilationDatabase.h"
anatofuz
parents:
diff changeset
10 #include "FS.h"
anatofuz
parents:
diff changeset
11 #include "Logger.h"
anatofuz
parents:
diff changeset
12 #include "Path.h"
anatofuz
parents:
diff changeset
13 #include "clang/Frontend/CompilerInvocation.h"
anatofuz
parents:
diff changeset
14 #include "clang/Tooling/ArgumentsAdjusters.h"
anatofuz
parents:
diff changeset
15 #include "clang/Tooling/CompilationDatabase.h"
anatofuz
parents:
diff changeset
16 #include "llvm/ADT/None.h"
anatofuz
parents:
diff changeset
17 #include "llvm/ADT/Optional.h"
anatofuz
parents:
diff changeset
18 #include "llvm/ADT/STLExtras.h"
anatofuz
parents:
diff changeset
19 #include "llvm/ADT/SmallString.h"
anatofuz
parents:
diff changeset
20 #include "llvm/Support/FileSystem.h"
anatofuz
parents:
diff changeset
21 #include "llvm/Support/FileUtilities.h"
anatofuz
parents:
diff changeset
22 #include "llvm/Support/Path.h"
anatofuz
parents:
diff changeset
23 #include "llvm/Support/Program.h"
anatofuz
parents:
diff changeset
24 #include <string>
anatofuz
parents:
diff changeset
25 #include <tuple>
anatofuz
parents:
diff changeset
26 #include <vector>
anatofuz
parents:
diff changeset
27
anatofuz
parents:
diff changeset
28 namespace clang {
anatofuz
parents:
diff changeset
29 namespace clangd {
anatofuz
parents:
diff changeset
30 namespace {
anatofuz
parents:
diff changeset
31
anatofuz
parents:
diff changeset
32 // Runs the given action on all parent directories of filename, starting from
anatofuz
parents:
diff changeset
33 // deepest directory and going up to root. Stops whenever action succeeds.
anatofuz
parents:
diff changeset
34 void actOnAllParentDirectories(PathRef FileName,
anatofuz
parents:
diff changeset
35 llvm::function_ref<bool(PathRef)> Action) {
anatofuz
parents:
diff changeset
36 for (auto Path = llvm::sys::path::parent_path(FileName);
anatofuz
parents:
diff changeset
37 !Path.empty() && !Action(Path);
anatofuz
parents:
diff changeset
38 Path = llvm::sys::path::parent_path(Path))
anatofuz
parents:
diff changeset
39 ;
anatofuz
parents:
diff changeset
40 }
anatofuz
parents:
diff changeset
41
anatofuz
parents:
diff changeset
42 } // namespace
anatofuz
parents:
diff changeset
43
anatofuz
parents:
diff changeset
44 tooling::CompileCommand
anatofuz
parents:
diff changeset
45 GlobalCompilationDatabase::getFallbackCommand(PathRef File) const {
anatofuz
parents:
diff changeset
46 std::vector<std::string> Argv = {"clang"};
anatofuz
parents:
diff changeset
47 // Clang treats .h files as C by default and files without extension as linker
anatofuz
parents:
diff changeset
48 // input, resulting in unhelpful diagnostics.
anatofuz
parents:
diff changeset
49 // Parsing as Objective C++ is friendly to more cases.
anatofuz
parents:
diff changeset
50 auto FileExtension = llvm::sys::path::extension(File);
anatofuz
parents:
diff changeset
51 if (FileExtension.empty() || FileExtension == ".h")
anatofuz
parents:
diff changeset
52 Argv.push_back("-xobjective-c++-header");
anatofuz
parents:
diff changeset
53 Argv.push_back(std::string(File));
anatofuz
parents:
diff changeset
54 tooling::CompileCommand Cmd(llvm::sys::path::parent_path(File),
anatofuz
parents:
diff changeset
55 llvm::sys::path::filename(File), std::move(Argv),
anatofuz
parents:
diff changeset
56 /*Output=*/"");
anatofuz
parents:
diff changeset
57 Cmd.Heuristic = "clangd fallback";
anatofuz
parents:
diff changeset
58 return Cmd;
anatofuz
parents:
diff changeset
59 }
anatofuz
parents:
diff changeset
60
anatofuz
parents:
diff changeset
61 DirectoryBasedGlobalCompilationDatabase::
anatofuz
parents:
diff changeset
62 DirectoryBasedGlobalCompilationDatabase(
anatofuz
parents:
diff changeset
63 llvm::Optional<Path> CompileCommandsDir)
anatofuz
parents:
diff changeset
64 : CompileCommandsDir(std::move(CompileCommandsDir)) {}
anatofuz
parents:
diff changeset
65
anatofuz
parents:
diff changeset
66 DirectoryBasedGlobalCompilationDatabase::
anatofuz
parents:
diff changeset
67 ~DirectoryBasedGlobalCompilationDatabase() = default;
anatofuz
parents:
diff changeset
68
anatofuz
parents:
diff changeset
69 llvm::Optional<tooling::CompileCommand>
anatofuz
parents:
diff changeset
70 DirectoryBasedGlobalCompilationDatabase::getCompileCommand(PathRef File) const {
anatofuz
parents:
diff changeset
71 CDBLookupRequest Req;
anatofuz
parents:
diff changeset
72 Req.FileName = File;
anatofuz
parents:
diff changeset
73 Req.ShouldBroadcast = true;
anatofuz
parents:
diff changeset
74
anatofuz
parents:
diff changeset
75 auto Res = lookupCDB(Req);
anatofuz
parents:
diff changeset
76 if (!Res) {
anatofuz
parents:
diff changeset
77 log("Failed to find compilation database for {0}", File);
anatofuz
parents:
diff changeset
78 return llvm::None;
anatofuz
parents:
diff changeset
79 }
anatofuz
parents:
diff changeset
80
anatofuz
parents:
diff changeset
81 auto Candidates = Res->CDB->getCompileCommands(File);
anatofuz
parents:
diff changeset
82 if (!Candidates.empty())
anatofuz
parents:
diff changeset
83 return std::move(Candidates.front());
anatofuz
parents:
diff changeset
84
anatofuz
parents:
diff changeset
85 return None;
anatofuz
parents:
diff changeset
86 }
anatofuz
parents:
diff changeset
87
anatofuz
parents:
diff changeset
88 // For platforms where paths are case-insensitive (but case-preserving),
anatofuz
parents:
diff changeset
89 // we need to do case-insensitive comparisons and use lowercase keys.
anatofuz
parents:
diff changeset
90 // FIXME: Make Path a real class with desired semantics instead.
anatofuz
parents:
diff changeset
91 // This class is not the only place this problem exists.
anatofuz
parents:
diff changeset
92 // FIXME: Mac filesystems default to case-insensitive, but may be sensitive.
anatofuz
parents:
diff changeset
93
anatofuz
parents:
diff changeset
94 static std::string maybeCaseFoldPath(PathRef Path) {
anatofuz
parents:
diff changeset
95 #if defined(_WIN32) || defined(__APPLE__)
anatofuz
parents:
diff changeset
96 return Path.lower();
anatofuz
parents:
diff changeset
97 #else
anatofuz
parents:
diff changeset
98 return std::string(Path);
anatofuz
parents:
diff changeset
99 #endif
anatofuz
parents:
diff changeset
100 }
anatofuz
parents:
diff changeset
101
anatofuz
parents:
diff changeset
102 static bool pathEqual(PathRef A, PathRef B) {
anatofuz
parents:
diff changeset
103 #if defined(_WIN32) || defined(__APPLE__)
anatofuz
parents:
diff changeset
104 return A.equals_lower(B);
anatofuz
parents:
diff changeset
105 #else
anatofuz
parents:
diff changeset
106 return A == B;
anatofuz
parents:
diff changeset
107 #endif
anatofuz
parents:
diff changeset
108 }
anatofuz
parents:
diff changeset
109
anatofuz
parents:
diff changeset
110 DirectoryBasedGlobalCompilationDatabase::CachedCDB &
anatofuz
parents:
diff changeset
111 DirectoryBasedGlobalCompilationDatabase::getCDBInDirLocked(PathRef Dir) const {
anatofuz
parents:
diff changeset
112 // FIXME(ibiryukov): Invalidate cached compilation databases on changes
anatofuz
parents:
diff changeset
113 // FIXME(sammccall): this function hot, avoid copying key when hitting cache.
anatofuz
parents:
diff changeset
114 auto Key = maybeCaseFoldPath(Dir);
anatofuz
parents:
diff changeset
115 auto R = CompilationDatabases.try_emplace(Key);
anatofuz
parents:
diff changeset
116 if (R.second) { // Cache miss, try to load CDB.
anatofuz
parents:
diff changeset
117 CachedCDB &Entry = R.first->second;
anatofuz
parents:
diff changeset
118 std::string Error;
anatofuz
parents:
diff changeset
119 Entry.CDB = tooling::CompilationDatabase::loadFromDirectory(Dir, Error);
anatofuz
parents:
diff changeset
120 Entry.Path = std::string(Dir);
anatofuz
parents:
diff changeset
121 if (Entry.CDB)
anatofuz
parents:
diff changeset
122 log("Loaded compilation database from {0}", Dir);
anatofuz
parents:
diff changeset
123 }
anatofuz
parents:
diff changeset
124 return R.first->second;
anatofuz
parents:
diff changeset
125 }
anatofuz
parents:
diff changeset
126
anatofuz
parents:
diff changeset
127 llvm::Optional<DirectoryBasedGlobalCompilationDatabase::CDBLookupResult>
anatofuz
parents:
diff changeset
128 DirectoryBasedGlobalCompilationDatabase::lookupCDB(
anatofuz
parents:
diff changeset
129 CDBLookupRequest Request) const {
anatofuz
parents:
diff changeset
130 assert(llvm::sys::path::is_absolute(Request.FileName) &&
anatofuz
parents:
diff changeset
131 "path must be absolute");
anatofuz
parents:
diff changeset
132
anatofuz
parents:
diff changeset
133 bool ShouldBroadcast = false;
anatofuz
parents:
diff changeset
134 CDBLookupResult Result;
anatofuz
parents:
diff changeset
135
anatofuz
parents:
diff changeset
136 {
anatofuz
parents:
diff changeset
137 std::lock_guard<std::mutex> Lock(Mutex);
anatofuz
parents:
diff changeset
138 CachedCDB *Entry = nullptr;
anatofuz
parents:
diff changeset
139 if (CompileCommandsDir) {
anatofuz
parents:
diff changeset
140 Entry = &getCDBInDirLocked(*CompileCommandsDir);
anatofuz
parents:
diff changeset
141 } else {
anatofuz
parents:
diff changeset
142 // Traverse the canonical version to prevent false positives. i.e.:
anatofuz
parents:
diff changeset
143 // src/build/../a.cc can detect a CDB in /src/build if not canonicalized.
anatofuz
parents:
diff changeset
144 // FIXME(sammccall): this loop is hot, use a union-find-like structure.
anatofuz
parents:
diff changeset
145 actOnAllParentDirectories(removeDots(Request.FileName),
anatofuz
parents:
diff changeset
146 [&](PathRef Path) {
anatofuz
parents:
diff changeset
147 Entry = &getCDBInDirLocked(Path);
anatofuz
parents:
diff changeset
148 return Entry->CDB != nullptr;
anatofuz
parents:
diff changeset
149 });
anatofuz
parents:
diff changeset
150 }
anatofuz
parents:
diff changeset
151
anatofuz
parents:
diff changeset
152 if (!Entry || !Entry->CDB)
anatofuz
parents:
diff changeset
153 return llvm::None;
anatofuz
parents:
diff changeset
154
anatofuz
parents:
diff changeset
155 // Mark CDB as broadcasted to make sure discovery is performed once.
anatofuz
parents:
diff changeset
156 if (Request.ShouldBroadcast && !Entry->SentBroadcast) {
anatofuz
parents:
diff changeset
157 Entry->SentBroadcast = true;
anatofuz
parents:
diff changeset
158 ShouldBroadcast = true;
anatofuz
parents:
diff changeset
159 }
anatofuz
parents:
diff changeset
160
anatofuz
parents:
diff changeset
161 Result.CDB = Entry->CDB.get();
anatofuz
parents:
diff changeset
162 Result.PI.SourceRoot = Entry->Path;
anatofuz
parents:
diff changeset
163 }
anatofuz
parents:
diff changeset
164
anatofuz
parents:
diff changeset
165 // FIXME: Maybe make the following part async, since this can block retrieval
anatofuz
parents:
diff changeset
166 // of compile commands.
anatofuz
parents:
diff changeset
167 if (ShouldBroadcast)
anatofuz
parents:
diff changeset
168 broadcastCDB(Result);
anatofuz
parents:
diff changeset
169 return Result;
anatofuz
parents:
diff changeset
170 }
anatofuz
parents:
diff changeset
171
anatofuz
parents:
diff changeset
172 void DirectoryBasedGlobalCompilationDatabase::broadcastCDB(
anatofuz
parents:
diff changeset
173 CDBLookupResult Result) const {
anatofuz
parents:
diff changeset
174 assert(Result.CDB && "Trying to broadcast an invalid CDB!");
anatofuz
parents:
diff changeset
175
anatofuz
parents:
diff changeset
176 std::vector<std::string> AllFiles = Result.CDB->getAllFiles();
anatofuz
parents:
diff changeset
177 // We assume CDB in CompileCommandsDir owns all of its entries, since we don't
anatofuz
parents:
diff changeset
178 // perform any search in parent paths whenever it is set.
anatofuz
parents:
diff changeset
179 if (CompileCommandsDir) {
anatofuz
parents:
diff changeset
180 assert(*CompileCommandsDir == Result.PI.SourceRoot &&
anatofuz
parents:
diff changeset
181 "Trying to broadcast a CDB outside of CompileCommandsDir!");
anatofuz
parents:
diff changeset
182 OnCommandChanged.broadcast(std::move(AllFiles));
anatofuz
parents:
diff changeset
183 return;
anatofuz
parents:
diff changeset
184 }
anatofuz
parents:
diff changeset
185
anatofuz
parents:
diff changeset
186 llvm::StringMap<bool> DirectoryHasCDB;
anatofuz
parents:
diff changeset
187 {
anatofuz
parents:
diff changeset
188 std::lock_guard<std::mutex> Lock(Mutex);
anatofuz
parents:
diff changeset
189 // Uniquify all parent directories of all files.
anatofuz
parents:
diff changeset
190 for (llvm::StringRef File : AllFiles) {
anatofuz
parents:
diff changeset
191 actOnAllParentDirectories(File, [&](PathRef Path) {
anatofuz
parents:
diff changeset
192 auto It = DirectoryHasCDB.try_emplace(Path);
anatofuz
parents:
diff changeset
193 // Already seen this path, and all of its parents.
anatofuz
parents:
diff changeset
194 if (!It.second)
anatofuz
parents:
diff changeset
195 return true;
anatofuz
parents:
diff changeset
196
anatofuz
parents:
diff changeset
197 CachedCDB &Entry = getCDBInDirLocked(Path);
anatofuz
parents:
diff changeset
198 It.first->second = Entry.CDB != nullptr;
anatofuz
parents:
diff changeset
199 return pathEqual(Path, Result.PI.SourceRoot);
anatofuz
parents:
diff changeset
200 });
anatofuz
parents:
diff changeset
201 }
anatofuz
parents:
diff changeset
202 }
anatofuz
parents:
diff changeset
203
anatofuz
parents:
diff changeset
204 std::vector<std::string> GovernedFiles;
anatofuz
parents:
diff changeset
205 for (llvm::StringRef File : AllFiles) {
anatofuz
parents:
diff changeset
206 // A file is governed by this CDB if lookup for the file would find it.
anatofuz
parents:
diff changeset
207 // Independent of whether it has an entry for that file or not.
anatofuz
parents:
diff changeset
208 actOnAllParentDirectories(File, [&](PathRef Path) {
anatofuz
parents:
diff changeset
209 if (DirectoryHasCDB.lookup(Path)) {
anatofuz
parents:
diff changeset
210 if (pathEqual(Path, Result.PI.SourceRoot))
anatofuz
parents:
diff changeset
211 // Make sure listeners always get a canonical path for the file.
anatofuz
parents:
diff changeset
212 GovernedFiles.push_back(removeDots(File));
anatofuz
parents:
diff changeset
213 // Stop as soon as we hit a CDB.
anatofuz
parents:
diff changeset
214 return true;
anatofuz
parents:
diff changeset
215 }
anatofuz
parents:
diff changeset
216 return false;
anatofuz
parents:
diff changeset
217 });
anatofuz
parents:
diff changeset
218 }
anatofuz
parents:
diff changeset
219
anatofuz
parents:
diff changeset
220 OnCommandChanged.broadcast(std::move(GovernedFiles));
anatofuz
parents:
diff changeset
221 }
anatofuz
parents:
diff changeset
222
anatofuz
parents:
diff changeset
223 llvm::Optional<ProjectInfo>
anatofuz
parents:
diff changeset
224 DirectoryBasedGlobalCompilationDatabase::getProjectInfo(PathRef File) const {
anatofuz
parents:
diff changeset
225 CDBLookupRequest Req;
anatofuz
parents:
diff changeset
226 Req.FileName = File;
anatofuz
parents:
diff changeset
227 Req.ShouldBroadcast = false;
anatofuz
parents:
diff changeset
228 auto Res = lookupCDB(Req);
anatofuz
parents:
diff changeset
229 if (!Res)
anatofuz
parents:
diff changeset
230 return llvm::None;
anatofuz
parents:
diff changeset
231 return Res->PI;
anatofuz
parents:
diff changeset
232 }
anatofuz
parents:
diff changeset
233
anatofuz
parents:
diff changeset
234 OverlayCDB::OverlayCDB(const GlobalCompilationDatabase *Base,
anatofuz
parents:
diff changeset
235 std::vector<std::string> FallbackFlags,
anatofuz
parents:
diff changeset
236 tooling::ArgumentsAdjuster Adjuster)
anatofuz
parents:
diff changeset
237 : Base(Base), ArgsAdjuster(std::move(Adjuster)),
anatofuz
parents:
diff changeset
238 FallbackFlags(std::move(FallbackFlags)) {
anatofuz
parents:
diff changeset
239 if (Base)
anatofuz
parents:
diff changeset
240 BaseChanged = Base->watch([this](const std::vector<std::string> Changes) {
anatofuz
parents:
diff changeset
241 OnCommandChanged.broadcast(Changes);
anatofuz
parents:
diff changeset
242 });
anatofuz
parents:
diff changeset
243 }
anatofuz
parents:
diff changeset
244
anatofuz
parents:
diff changeset
245 llvm::Optional<tooling::CompileCommand>
anatofuz
parents:
diff changeset
246 OverlayCDB::getCompileCommand(PathRef File) const {
anatofuz
parents:
diff changeset
247 llvm::Optional<tooling::CompileCommand> Cmd;
anatofuz
parents:
diff changeset
248 {
anatofuz
parents:
diff changeset
249 std::lock_guard<std::mutex> Lock(Mutex);
anatofuz
parents:
diff changeset
250 auto It = Commands.find(removeDots(File));
anatofuz
parents:
diff changeset
251 if (It != Commands.end())
anatofuz
parents:
diff changeset
252 Cmd = It->second;
anatofuz
parents:
diff changeset
253 }
anatofuz
parents:
diff changeset
254 if (!Cmd && Base)
anatofuz
parents:
diff changeset
255 Cmd = Base->getCompileCommand(File);
anatofuz
parents:
diff changeset
256 if (!Cmd)
anatofuz
parents:
diff changeset
257 return llvm::None;
anatofuz
parents:
diff changeset
258 if (ArgsAdjuster)
anatofuz
parents:
diff changeset
259 Cmd->CommandLine = ArgsAdjuster(Cmd->CommandLine, Cmd->Filename);
anatofuz
parents:
diff changeset
260 return Cmd;
anatofuz
parents:
diff changeset
261 }
anatofuz
parents:
diff changeset
262
anatofuz
parents:
diff changeset
263 tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File) const {
anatofuz
parents:
diff changeset
264 auto Cmd = Base ? Base->getFallbackCommand(File)
anatofuz
parents:
diff changeset
265 : GlobalCompilationDatabase::getFallbackCommand(File);
anatofuz
parents:
diff changeset
266 std::lock_guard<std::mutex> Lock(Mutex);
anatofuz
parents:
diff changeset
267 Cmd.CommandLine.insert(Cmd.CommandLine.end(), FallbackFlags.begin(),
anatofuz
parents:
diff changeset
268 FallbackFlags.end());
anatofuz
parents:
diff changeset
269 if (ArgsAdjuster)
anatofuz
parents:
diff changeset
270 Cmd.CommandLine = ArgsAdjuster(Cmd.CommandLine, Cmd.Filename);
anatofuz
parents:
diff changeset
271 return Cmd;
anatofuz
parents:
diff changeset
272 }
anatofuz
parents:
diff changeset
273
anatofuz
parents:
diff changeset
274 void OverlayCDB::setCompileCommand(
anatofuz
parents:
diff changeset
275 PathRef File, llvm::Optional<tooling::CompileCommand> Cmd) {
anatofuz
parents:
diff changeset
276 // We store a canonical version internally to prevent mismatches between set
anatofuz
parents:
diff changeset
277 // and get compile commands. Also it assures clients listening to broadcasts
anatofuz
parents:
diff changeset
278 // doesn't receive different names for the same file.
anatofuz
parents:
diff changeset
279 std::string CanonPath = removeDots(File);
anatofuz
parents:
diff changeset
280 {
anatofuz
parents:
diff changeset
281 std::unique_lock<std::mutex> Lock(Mutex);
anatofuz
parents:
diff changeset
282 if (Cmd)
anatofuz
parents:
diff changeset
283 Commands[CanonPath] = std::move(*Cmd);
anatofuz
parents:
diff changeset
284 else
anatofuz
parents:
diff changeset
285 Commands.erase(CanonPath);
anatofuz
parents:
diff changeset
286 }
anatofuz
parents:
diff changeset
287 OnCommandChanged.broadcast({CanonPath});
anatofuz
parents:
diff changeset
288 }
anatofuz
parents:
diff changeset
289
anatofuz
parents:
diff changeset
290 llvm::Optional<ProjectInfo> OverlayCDB::getProjectInfo(PathRef File) const {
anatofuz
parents:
diff changeset
291 {
anatofuz
parents:
diff changeset
292 std::lock_guard<std::mutex> Lock(Mutex);
anatofuz
parents:
diff changeset
293 auto It = Commands.find(removeDots(File));
anatofuz
parents:
diff changeset
294 if (It != Commands.end())
anatofuz
parents:
diff changeset
295 return ProjectInfo{};
anatofuz
parents:
diff changeset
296 }
anatofuz
parents:
diff changeset
297 if (Base)
anatofuz
parents:
diff changeset
298 return Base->getProjectInfo(File);
anatofuz
parents:
diff changeset
299
anatofuz
parents:
diff changeset
300 return llvm::None;
anatofuz
parents:
diff changeset
301 }
anatofuz
parents:
diff changeset
302 } // namespace clangd
anatofuz
parents:
diff changeset
303 } // namespace clang