blob: 885bf15c616cb6893421616786fa4ce3ec5b4980 [file] [log] [blame]
Kirill Bobyrev8e35f1e2018-08-14 16:03:32 +00001//===--- GlobalCompilationDatabase.cpp ---------------------------*- C++-*-===//
Ilya Biryukov38d79772017-05-16 09:38:59 +00002//
Chandler Carruth2946cd72019-01-19 08:50:56 +00003// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
Ilya Biryukov38d79772017-05-16 09:38:59 +00006//
Kirill Bobyrev8e35f1e2018-08-14 16:03:32 +00007//===----------------------------------------------------------------------===//
Ilya Biryukov38d79772017-05-16 09:38:59 +00008
9#include "GlobalCompilationDatabase.h"
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +000010#include "FS.h"
Ilya Biryukov0c1ca6b2017-10-02 15:13:20 +000011#include "Logger.h"
Kadir Cetinkayaad549352019-07-11 09:54:31 +000012#include "Path.h"
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +000013#include "clang/Frontend/CompilerInvocation.h"
14#include "clang/Tooling/ArgumentsAdjusters.h"
Ilya Biryukov38d79772017-05-16 09:38:59 +000015#include "clang/Tooling/CompilationDatabase.h"
Kadir Cetinkayaad549352019-07-11 09:54:31 +000016#include "llvm/ADT/None.h"
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +000017#include "llvm/ADT/Optional.h"
Kadir Cetinkayaad549352019-07-11 09:54:31 +000018#include "llvm/ADT/STLExtras.h"
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +000019#include "llvm/ADT/SmallString.h"
Ilya Biryukov38d79772017-05-16 09:38:59 +000020#include "llvm/Support/FileSystem.h"
21#include "llvm/Support/Path.h"
Kadir Cetinkayaad549352019-07-11 09:54:31 +000022#include <string>
23#include <tuple>
24#include <vector>
Ilya Biryukov38d79772017-05-16 09:38:59 +000025
Krasimir Georgievc2a16a32017-07-06 08:44:54 +000026namespace clang {
27namespace clangd {
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +000028namespace {
29
30void adjustArguments(tooling::CompileCommand &Cmd,
31 llvm::StringRef ResourceDir) {
Kadir Cetinkaya65944ab2019-03-08 08:38:25 +000032 tooling::ArgumentsAdjuster ArgsAdjuster = tooling::combineAdjusters(
33 // clangd should not write files to disk, including dependency files
34 // requested on the command line.
35 tooling::getClangStripDependencyFileAdjuster(),
36 // Strip plugin related command line arguments. Clangd does
37 // not support plugins currently. Therefore it breaks if
38 // compiler tries to load plugins.
39 tooling::combineAdjusters(tooling::getStripPluginsAdjuster(),
40 tooling::getClangSyntaxOnlyAdjuster()));
41
42 Cmd.CommandLine = ArgsAdjuster(Cmd.CommandLine, Cmd.Filename);
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +000043 // Inject the resource dir.
44 // FIXME: Don't overwrite it if it's already there.
45 if (!ResourceDir.empty())
46 Cmd.CommandLine.push_back(("-resource-dir=" + ResourceDir).str());
47}
48
49std::string getStandardResourceDir() {
50 static int Dummy; // Just an address in this process.
51 return CompilerInvocation::GetResourcesPath("clangd", (void *)&Dummy);
52}
53
Kadir Cetinkayaad549352019-07-11 09:54:31 +000054// Runs the given action on all parent directories of filename, starting from
55// deepest directory and going up to root. Stops whenever action succeeds.
56void actOnAllParentDirectories(PathRef FileName,
57 llvm::function_ref<bool(PathRef)> Action) {
58 for (auto Path = llvm::sys::path::parent_path(FileName);
59 !Path.empty() && !Action(Path);
60 Path = llvm::sys::path::parent_path(Path))
61 ;
62}
63
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +000064} // namespace
Krasimir Georgievc2a16a32017-07-06 08:44:54 +000065
Haojian Wue1ced5c2019-01-07 12:35:02 +000066static std::string getFallbackClangPath() {
67 static int Dummy;
68 std::string ClangdExecutable =
69 llvm::sys::fs::getMainExecutable("clangd", (void *)&Dummy);
70 SmallString<128> ClangPath;
71 ClangPath = llvm::sys::path::parent_path(ClangdExecutable);
72 llvm::sys::path::append(ClangPath, "clang");
73 return ClangPath.str();
74}
75
Sam McCallecbeab02017-12-04 10:08:45 +000076tooling::CompileCommand
77GlobalCompilationDatabase::getFallbackCommand(PathRef File) const {
Haojian Wue1ced5c2019-01-07 12:35:02 +000078 std::vector<std::string> Argv = {getFallbackClangPath()};
Haojian Wu8ddf31b2019-06-18 11:54:17 +000079 // Clang treats .h files as C by default and files without extension as linker
80 // input, resulting in unhelpful diagnostics.
Sam McCall690dcf12018-04-20 11:35:17 +000081 // Parsing as Objective C++ is friendly to more cases.
Haojian Wu8ddf31b2019-06-18 11:54:17 +000082 auto FileExtension = llvm::sys::path::extension(File);
83 if (FileExtension.empty() || FileExtension == ".h")
Sam McCall690dcf12018-04-20 11:35:17 +000084 Argv.push_back("-xobjective-c++-header");
85 Argv.push_back(File);
Eric Liu9ef03dd2019-04-15 12:32:28 +000086 tooling::CompileCommand Cmd(llvm::sys::path::parent_path(File),
87 llvm::sys::path::filename(File), std::move(Argv),
88 /*Output=*/"");
89 Cmd.Heuristic = "clangd fallback";
90 return Cmd;
Krasimir Georgievc2a16a32017-07-06 08:44:54 +000091}
Ilya Biryukov38d79772017-05-16 09:38:59 +000092
Ilya Biryukove5128f72017-09-20 07:24:15 +000093DirectoryBasedGlobalCompilationDatabase::
Ilya Biryukovf2001aa2019-01-07 15:45:19 +000094 DirectoryBasedGlobalCompilationDatabase(
95 llvm::Optional<Path> CompileCommandsDir)
Ilya Biryukov940901e2017-12-13 12:51:22 +000096 : CompileCommandsDir(std::move(CompileCommandsDir)) {}
Ilya Biryukove5128f72017-09-20 07:24:15 +000097
Sam McCall690dcf12018-04-20 11:35:17 +000098DirectoryBasedGlobalCompilationDatabase::
99 ~DirectoryBasedGlobalCompilationDatabase() = default;
100
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000101llvm::Optional<tooling::CompileCommand>
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000102DirectoryBasedGlobalCompilationDatabase::getCompileCommand(PathRef File) const {
103 CDBLookupRequest Req;
104 Req.FileName = File;
105 Req.ShouldBroadcast = true;
106
107 auto Res = lookupCDB(Req);
108 if (!Res) {
Sam McCallbed58852018-07-11 10:35:11 +0000109 log("Failed to find compilation database for {0}", File);
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000110 return llvm::None;
Krasimir Georgievc2a16a32017-07-06 08:44:54 +0000111 }
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000112
113 auto Candidates = Res->CDB->getCompileCommands(File);
114 if (!Candidates.empty())
115 return std::move(Candidates.front());
116
Sam McCallc008af62018-10-20 15:30:37 +0000117 return None;
Sam McCallecbeab02017-12-04 10:08:45 +0000118}
Krasimir Georgievc2a16a32017-07-06 08:44:54 +0000119
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000120std::pair<tooling::CompilationDatabase *, /*SentBroadcast*/ bool>
Sam McCallc02ba722017-12-22 09:47:34 +0000121DirectoryBasedGlobalCompilationDatabase::getCDBInDirLocked(PathRef Dir) const {
122 // FIXME(ibiryukov): Invalidate cached compilation databases on changes
123 auto CachedIt = CompilationDatabases.find(Dir);
124 if (CachedIt != CompilationDatabases.end())
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000125 return {CachedIt->second.CDB.get(), CachedIt->second.SentBroadcast};
Sam McCallc02ba722017-12-22 09:47:34 +0000126 std::string Error = "";
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000127
128 CachedCDB Entry;
129 Entry.CDB = tooling::CompilationDatabase::loadFromDirectory(Dir, Error);
130 auto Result = Entry.CDB.get();
131 CompilationDatabases[Dir] = std::move(Entry);
132
Sam McCall2bebc3d2018-11-20 10:56:03 +0000133 return {Result, false};
Sam McCallc02ba722017-12-22 09:47:34 +0000134}
Ilya Biryukov38d79772017-05-16 09:38:59 +0000135
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000136llvm::Optional<DirectoryBasedGlobalCompilationDatabase::CDBLookupResult>
137DirectoryBasedGlobalCompilationDatabase::lookupCDB(
138 CDBLookupRequest Request) const {
139 assert(llvm::sys::path::is_absolute(Request.FileName) &&
Ilya Biryukov38d79772017-05-16 09:38:59 +0000140 "path must be absolute");
141
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000142 CDBLookupResult Result;
143 bool SentBroadcast = false;
144
145 {
146 std::lock_guard<std::mutex> Lock(Mutex);
147 if (CompileCommandsDir) {
148 std::tie(Result.CDB, SentBroadcast) =
149 getCDBInDirLocked(*CompileCommandsDir);
150 Result.PI.SourceRoot = *CompileCommandsDir;
151 } else {
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000152 // Traverse the canonical version to prevent false positives. i.e.:
153 // src/build/../a.cc can detect a CDB in /src/build if not canonicalized.
154 actOnAllParentDirectories(removeDots(Request.FileName),
155 [this, &SentBroadcast, &Result](PathRef Path) {
156 std::tie(Result.CDB, SentBroadcast) =
157 getCDBInDirLocked(Path);
158 Result.PI.SourceRoot = Path;
159 return Result.CDB != nullptr;
160 });
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000161 }
162
163 if (!Result.CDB)
164 return llvm::None;
165
166 // Mark CDB as broadcasted to make sure discovery is performed once.
167 if (Request.ShouldBroadcast && !SentBroadcast)
168 CompilationDatabases[Result.PI.SourceRoot].SentBroadcast = true;
169 }
170
171 // FIXME: Maybe make the following part async, since this can block retrieval
172 // of compile commands.
173 if (Request.ShouldBroadcast && !SentBroadcast)
174 broadcastCDB(Result);
175 return Result;
176}
177
178void DirectoryBasedGlobalCompilationDatabase::broadcastCDB(
179 CDBLookupResult Result) const {
180 assert(Result.CDB && "Trying to broadcast an invalid CDB!");
181
182 std::vector<std::string> AllFiles = Result.CDB->getAllFiles();
183 // We assume CDB in CompileCommandsDir owns all of its entries, since we don't
184 // perform any search in parent paths whenever it is set.
Sam McCall2bebc3d2018-11-20 10:56:03 +0000185 if (CompileCommandsDir) {
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000186 assert(*CompileCommandsDir == Result.PI.SourceRoot &&
187 "Trying to broadcast a CDB outside of CompileCommandsDir!");
188 OnCommandChanged.broadcast(std::move(AllFiles));
189 return;
190 }
191
192 llvm::StringMap<bool> DirectoryHasCDB;
193 {
194 std::lock_guard<std::mutex> Lock(Mutex);
195 // Uniquify all parent directories of all files.
196 for (llvm::StringRef File : AllFiles) {
197 actOnAllParentDirectories(File, [&](PathRef Path) {
198 auto It = DirectoryHasCDB.try_emplace(Path);
199 // Already seen this path, and all of its parents.
200 if (!It.second)
201 return true;
202
203 auto Res = getCDBInDirLocked(Path);
204 It.first->second = Res.first != nullptr;
205 return Path == Result.PI.SourceRoot;
206 });
Sam McCall2bebc3d2018-11-20 10:56:03 +0000207 }
208 }
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000209
210 std::vector<std::string> GovernedFiles;
211 for (llvm::StringRef File : AllFiles) {
212 // A file is governed by this CDB if lookup for the file would find it.
213 // Independent of whether it has an entry for that file or not.
214 actOnAllParentDirectories(File, [&](PathRef Path) {
215 if (DirectoryHasCDB.lookup(Path)) {
216 if (Path == Result.PI.SourceRoot)
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000217 // Make sure listeners always get a canonical path for the file.
218 GovernedFiles.push_back(removeDots(File));
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000219 // Stop as soon as we hit a CDB.
220 return true;
221 }
222 return false;
223 });
224 }
225
226 OnCommandChanged.broadcast(std::move(GovernedFiles));
227}
228
229llvm::Optional<ProjectInfo>
230DirectoryBasedGlobalCompilationDatabase::getProjectInfo(PathRef File) const {
231 CDBLookupRequest Req;
232 Req.FileName = File;
233 Req.ShouldBroadcast = false;
234 auto Res = lookupCDB(Req);
235 if (!Res)
236 return llvm::None;
237 return Res->PI;
Sam McCall2bebc3d2018-11-20 10:56:03 +0000238}
239
240OverlayCDB::OverlayCDB(const GlobalCompilationDatabase *Base,
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +0000241 std::vector<std::string> FallbackFlags,
242 llvm::Optional<std::string> ResourceDir)
243 : Base(Base), ResourceDir(ResourceDir ? std::move(*ResourceDir)
244 : getStandardResourceDir()),
245 FallbackFlags(std::move(FallbackFlags)) {
Sam McCall2bebc3d2018-11-20 10:56:03 +0000246 if (Base)
247 BaseChanged = Base->watch([this](const std::vector<std::string> Changes) {
248 OnCommandChanged.broadcast(Changes);
249 });
Ilya Biryukov38d79772017-05-16 09:38:59 +0000250}
Krasimir Georgievc2a16a32017-07-06 08:44:54 +0000251
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000252llvm::Optional<tooling::CompileCommand>
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000253OverlayCDB::getCompileCommand(PathRef File) const {
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +0000254 llvm::Optional<tooling::CompileCommand> Cmd;
Sam McCallc55d09a2018-11-02 13:09:36 +0000255 {
256 std::lock_guard<std::mutex> Lock(Mutex);
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000257 auto It = Commands.find(removeDots(File));
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000258 if (It != Commands.end())
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +0000259 Cmd = It->second;
Sam McCallc55d09a2018-11-02 13:09:36 +0000260 }
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +0000261 if (!Cmd && Base)
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000262 Cmd = Base->getCompileCommand(File);
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +0000263 if (!Cmd)
264 return llvm::None;
265 adjustArguments(*Cmd, ResourceDir);
266 return Cmd;
Alex Lorenzf8087862018-08-01 17:39:29 +0000267}
268
Sam McCallc55d09a2018-11-02 13:09:36 +0000269tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File) const {
270 auto Cmd = Base ? Base->getFallbackCommand(File)
271 : GlobalCompilationDatabase::getFallbackCommand(File);
272 std::lock_guard<std::mutex> Lock(Mutex);
273 Cmd.CommandLine.insert(Cmd.CommandLine.end(), FallbackFlags.begin(),
274 FallbackFlags.end());
Kadir Cetinkayaa7f9f422019-06-04 13:38:36 +0000275 adjustArguments(Cmd, ResourceDir);
Sam McCallc55d09a2018-11-02 13:09:36 +0000276 return Cmd;
277}
278
279void OverlayCDB::setCompileCommand(
280 PathRef File, llvm::Optional<tooling::CompileCommand> Cmd) {
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000281 // We store a canonical version internally to prevent mismatches between set
282 // and get compile commands. Also it assures clients listening to broadcasts
283 // doesn't receive different names for the same file.
284 std::string CanonPath = removeDots(File);
Sam McCall2bebc3d2018-11-20 10:56:03 +0000285 {
286 std::unique_lock<std::mutex> Lock(Mutex);
287 if (Cmd)
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000288 Commands[CanonPath] = std::move(*Cmd);
Sam McCall2bebc3d2018-11-20 10:56:03 +0000289 else
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000290 Commands.erase(CanonPath);
Sam McCall2bebc3d2018-11-20 10:56:03 +0000291 }
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000292 OnCommandChanged.broadcast({CanonPath});
Alex Lorenzf8087862018-08-01 17:39:29 +0000293}
294
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000295llvm::Optional<ProjectInfo> OverlayCDB::getProjectInfo(PathRef File) const {
296 {
297 std::lock_guard<std::mutex> Lock(Mutex);
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000298 auto It = Commands.find(removeDots(File));
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000299 if (It != Commands.end())
300 return ProjectInfo{};
301 }
302 if (Base)
303 return Base->getProjectInfo(File);
304
305 return llvm::None;
306}
Krasimir Georgievc2a16a32017-07-06 08:44:54 +0000307} // namespace clangd
308} // namespace clang