blob: ed3b86f0f55b9c17920b12469c3862f1cadc4080 [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.
Sam McCall93f77612019-12-02 22:12:23 +010044 // FIXME: Don't overwrite it if it's already there.
45 if (!ResourceDir.empty())
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +000046 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
Sam McCall93f77612019-12-02 22:12:23 +010066static 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 {
Sam McCall93f77612019-12-02 22:12:23 +010078 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
Sam McCall7ee08672019-07-26 14:07:11 +0000120// For platforms where paths are case-insensitive (but case-preserving),
121// we need to do case-insensitive comparisons and use lowercase keys.
122// FIXME: Make Path a real class with desired semantics instead.
123// This class is not the only place this problem exists.
124// FIXME: Mac filesystems default to case-insensitive, but may be sensitive.
125
126static std::string maybeCaseFoldPath(PathRef Path) {
127#if defined(_WIN32) || defined(__APPLE__)
128 return Path.lower();
129#else
130 return Path;
131#endif
132}
133
134static bool pathEqual(PathRef A, PathRef B) {
135#if defined(_WIN32) || defined(__APPLE__)
136 return A.equals_lower(B);
137#else
138 return A == B;
139#endif
140}
141
142DirectoryBasedGlobalCompilationDatabase::CachedCDB &
Sam McCallc02ba722017-12-22 09:47:34 +0000143DirectoryBasedGlobalCompilationDatabase::getCDBInDirLocked(PathRef Dir) const {
144 // FIXME(ibiryukov): Invalidate cached compilation databases on changes
Sam McCall7ee08672019-07-26 14:07:11 +0000145 // FIXME(sammccall): this function hot, avoid copying key when hitting cache.
146 auto Key = maybeCaseFoldPath(Dir);
147 auto R = CompilationDatabases.try_emplace(Key);
148 if (R.second) { // Cache miss, try to load CDB.
149 CachedCDB &Entry = R.first->second;
150 std::string Error = "";
151 Entry.CDB = tooling::CompilationDatabase::loadFromDirectory(Dir, Error);
152 Entry.Path = Dir;
153 }
154 return R.first->second;
Sam McCallc02ba722017-12-22 09:47:34 +0000155}
Ilya Biryukov38d79772017-05-16 09:38:59 +0000156
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000157llvm::Optional<DirectoryBasedGlobalCompilationDatabase::CDBLookupResult>
158DirectoryBasedGlobalCompilationDatabase::lookupCDB(
159 CDBLookupRequest Request) const {
160 assert(llvm::sys::path::is_absolute(Request.FileName) &&
Ilya Biryukov38d79772017-05-16 09:38:59 +0000161 "path must be absolute");
162
Sam McCall7ee08672019-07-26 14:07:11 +0000163 bool ShouldBroadcast = false;
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000164 CDBLookupResult Result;
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000165
166 {
167 std::lock_guard<std::mutex> Lock(Mutex);
Sam McCall7ee08672019-07-26 14:07:11 +0000168 CachedCDB *Entry = nullptr;
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000169 if (CompileCommandsDir) {
Sam McCall7ee08672019-07-26 14:07:11 +0000170 Entry = &getCDBInDirLocked(*CompileCommandsDir);
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000171 } else {
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000172 // Traverse the canonical version to prevent false positives. i.e.:
173 // src/build/../a.cc can detect a CDB in /src/build if not canonicalized.
Sam McCall7ee08672019-07-26 14:07:11 +0000174 // FIXME(sammccall): this loop is hot, use a union-find-like structure.
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000175 actOnAllParentDirectories(removeDots(Request.FileName),
Sam McCall7ee08672019-07-26 14:07:11 +0000176 [&](PathRef Path) {
177 Entry = &getCDBInDirLocked(Path);
178 return Entry->CDB != nullptr;
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000179 });
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000180 }
181
Sam McCall7ee08672019-07-26 14:07:11 +0000182 if (!Entry || !Entry->CDB)
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000183 return llvm::None;
184
185 // Mark CDB as broadcasted to make sure discovery is performed once.
Sam McCall7ee08672019-07-26 14:07:11 +0000186 if (Request.ShouldBroadcast && !Entry->SentBroadcast) {
187 Entry->SentBroadcast = true;
188 ShouldBroadcast = true;
189 }
190
191 Result.CDB = Entry->CDB.get();
192 Result.PI.SourceRoot = Entry->Path;
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000193 }
194
195 // FIXME: Maybe make the following part async, since this can block retrieval
196 // of compile commands.
Sam McCall7ee08672019-07-26 14:07:11 +0000197 if (ShouldBroadcast)
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000198 broadcastCDB(Result);
199 return Result;
200}
201
202void DirectoryBasedGlobalCompilationDatabase::broadcastCDB(
203 CDBLookupResult Result) const {
204 assert(Result.CDB && "Trying to broadcast an invalid CDB!");
205
206 std::vector<std::string> AllFiles = Result.CDB->getAllFiles();
207 // We assume CDB in CompileCommandsDir owns all of its entries, since we don't
208 // perform any search in parent paths whenever it is set.
Sam McCall2bebc3d2018-11-20 10:56:03 +0000209 if (CompileCommandsDir) {
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000210 assert(*CompileCommandsDir == Result.PI.SourceRoot &&
211 "Trying to broadcast a CDB outside of CompileCommandsDir!");
212 OnCommandChanged.broadcast(std::move(AllFiles));
213 return;
214 }
215
216 llvm::StringMap<bool> DirectoryHasCDB;
217 {
218 std::lock_guard<std::mutex> Lock(Mutex);
219 // Uniquify all parent directories of all files.
220 for (llvm::StringRef File : AllFiles) {
221 actOnAllParentDirectories(File, [&](PathRef Path) {
222 auto It = DirectoryHasCDB.try_emplace(Path);
223 // Already seen this path, and all of its parents.
224 if (!It.second)
225 return true;
226
Sam McCall7ee08672019-07-26 14:07:11 +0000227 CachedCDB &Entry = getCDBInDirLocked(Path);
228 It.first->second = Entry.CDB != nullptr;
229 return pathEqual(Path, Result.PI.SourceRoot);
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000230 });
Sam McCall2bebc3d2018-11-20 10:56:03 +0000231 }
232 }
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000233
234 std::vector<std::string> GovernedFiles;
235 for (llvm::StringRef File : AllFiles) {
236 // A file is governed by this CDB if lookup for the file would find it.
237 // Independent of whether it has an entry for that file or not.
238 actOnAllParentDirectories(File, [&](PathRef Path) {
239 if (DirectoryHasCDB.lookup(Path)) {
Sam McCall7ee08672019-07-26 14:07:11 +0000240 if (pathEqual(Path, Result.PI.SourceRoot))
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000241 // Make sure listeners always get a canonical path for the file.
242 GovernedFiles.push_back(removeDots(File));
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000243 // Stop as soon as we hit a CDB.
244 return true;
245 }
246 return false;
247 });
248 }
249
250 OnCommandChanged.broadcast(std::move(GovernedFiles));
251}
252
253llvm::Optional<ProjectInfo>
254DirectoryBasedGlobalCompilationDatabase::getProjectInfo(PathRef File) const {
255 CDBLookupRequest Req;
256 Req.FileName = File;
257 Req.ShouldBroadcast = false;
258 auto Res = lookupCDB(Req);
259 if (!Res)
260 return llvm::None;
261 return Res->PI;
Sam McCall2bebc3d2018-11-20 10:56:03 +0000262}
263
264OverlayCDB::OverlayCDB(const GlobalCompilationDatabase *Base,
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +0000265 std::vector<std::string> FallbackFlags,
266 llvm::Optional<std::string> ResourceDir)
267 : Base(Base), ResourceDir(ResourceDir ? std::move(*ResourceDir)
268 : getStandardResourceDir()),
269 FallbackFlags(std::move(FallbackFlags)) {
Sam McCall2bebc3d2018-11-20 10:56:03 +0000270 if (Base)
271 BaseChanged = Base->watch([this](const std::vector<std::string> Changes) {
272 OnCommandChanged.broadcast(Changes);
273 });
Ilya Biryukov38d79772017-05-16 09:38:59 +0000274}
Krasimir Georgievc2a16a32017-07-06 08:44:54 +0000275
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000276llvm::Optional<tooling::CompileCommand>
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000277OverlayCDB::getCompileCommand(PathRef File) const {
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +0000278 llvm::Optional<tooling::CompileCommand> Cmd;
Sam McCallc55d09a2018-11-02 13:09:36 +0000279 {
280 std::lock_guard<std::mutex> Lock(Mutex);
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000281 auto It = Commands.find(removeDots(File));
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000282 if (It != Commands.end())
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +0000283 Cmd = It->second;
Sam McCallc55d09a2018-11-02 13:09:36 +0000284 }
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +0000285 if (!Cmd && Base)
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000286 Cmd = Base->getCompileCommand(File);
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +0000287 if (!Cmd)
288 return llvm::None;
289 adjustArguments(*Cmd, ResourceDir);
290 return Cmd;
Alex Lorenzf8087862018-08-01 17:39:29 +0000291}
292
Sam McCallc55d09a2018-11-02 13:09:36 +0000293tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File) const {
294 auto Cmd = Base ? Base->getFallbackCommand(File)
295 : GlobalCompilationDatabase::getFallbackCommand(File);
296 std::lock_guard<std::mutex> Lock(Mutex);
297 Cmd.CommandLine.insert(Cmd.CommandLine.end(), FallbackFlags.begin(),
298 FallbackFlags.end());
Kadir Cetinkayaa7f9f422019-06-04 13:38:36 +0000299 adjustArguments(Cmd, ResourceDir);
Sam McCallc55d09a2018-11-02 13:09:36 +0000300 return Cmd;
301}
302
303void OverlayCDB::setCompileCommand(
304 PathRef File, llvm::Optional<tooling::CompileCommand> Cmd) {
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000305 // We store a canonical version internally to prevent mismatches between set
306 // and get compile commands. Also it assures clients listening to broadcasts
307 // doesn't receive different names for the same file.
308 std::string CanonPath = removeDots(File);
Sam McCall2bebc3d2018-11-20 10:56:03 +0000309 {
310 std::unique_lock<std::mutex> Lock(Mutex);
311 if (Cmd)
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000312 Commands[CanonPath] = std::move(*Cmd);
Sam McCall2bebc3d2018-11-20 10:56:03 +0000313 else
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000314 Commands.erase(CanonPath);
Sam McCall2bebc3d2018-11-20 10:56:03 +0000315 }
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000316 OnCommandChanged.broadcast({CanonPath});
Alex Lorenzf8087862018-08-01 17:39:29 +0000317}
318
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000319llvm::Optional<ProjectInfo> OverlayCDB::getProjectInfo(PathRef File) const {
320 {
321 std::lock_guard<std::mutex> Lock(Mutex);
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000322 auto It = Commands.find(removeDots(File));
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000323 if (It != Commands.end())
324 return ProjectInfo{};
325 }
326 if (Base)
327 return Base->getProjectInfo(File);
328
329 return llvm::None;
330}
Krasimir Georgievc2a16a32017-07-06 08:44:54 +0000331} // namespace clangd
332} // namespace clang