blob: aa6727fc20f575cd343aaa0e98cc2a96b4c965d9 [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"
Sam McCall99768b22019-11-29 19:37:48 +010021#include "llvm/Support/FileUtilities.h"
Ilya Biryukov38d79772017-05-16 09:38:59 +000022#include "llvm/Support/Path.h"
Sam McCall99768b22019-11-29 19:37:48 +010023#include "llvm/Support/Program.h"
Kadir Cetinkayaad549352019-07-11 09:54:31 +000024#include <string>
25#include <tuple>
26#include <vector>
Ilya Biryukov38d79772017-05-16 09:38:59 +000027
Krasimir Georgievc2a16a32017-07-06 08:44:54 +000028namespace clang {
29namespace clangd {
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +000030namespace {
31
Kadir Cetinkayaad549352019-07-11 09:54:31 +000032// Runs the given action on all parent directories of filename, starting from
33// deepest directory and going up to root. Stops whenever action succeeds.
34void actOnAllParentDirectories(PathRef FileName,
35 llvm::function_ref<bool(PathRef)> Action) {
36 for (auto Path = llvm::sys::path::parent_path(FileName);
37 !Path.empty() && !Action(Path);
38 Path = llvm::sys::path::parent_path(Path))
39 ;
40}
41
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +000042} // namespace
Krasimir Georgievc2a16a32017-07-06 08:44:54 +000043
Sam McCallecbeab02017-12-04 10:08:45 +000044tooling::CompileCommand
45GlobalCompilationDatabase::getFallbackCommand(PathRef File) const {
Sam McCall99768b22019-11-29 19:37:48 +010046 std::vector<std::string> Argv = {"clang"};
Haojian Wu8ddf31b2019-06-18 11:54:17 +000047 // Clang treats .h files as C by default and files without extension as linker
48 // input, resulting in unhelpful diagnostics.
Sam McCall690dcf12018-04-20 11:35:17 +000049 // Parsing as Objective C++ is friendly to more cases.
Haojian Wu8ddf31b2019-06-18 11:54:17 +000050 auto FileExtension = llvm::sys::path::extension(File);
51 if (FileExtension.empty() || FileExtension == ".h")
Sam McCall690dcf12018-04-20 11:35:17 +000052 Argv.push_back("-xobjective-c++-header");
Benjamin Krameradcd0262020-01-28 20:23:46 +010053 Argv.push_back(std::string(File));
Eric Liu9ef03dd2019-04-15 12:32:28 +000054 tooling::CompileCommand Cmd(llvm::sys::path::parent_path(File),
55 llvm::sys::path::filename(File), std::move(Argv),
56 /*Output=*/"");
57 Cmd.Heuristic = "clangd fallback";
58 return Cmd;
Krasimir Georgievc2a16a32017-07-06 08:44:54 +000059}
Ilya Biryukov38d79772017-05-16 09:38:59 +000060
Ilya Biryukove5128f72017-09-20 07:24:15 +000061DirectoryBasedGlobalCompilationDatabase::
Ilya Biryukovf2001aa2019-01-07 15:45:19 +000062 DirectoryBasedGlobalCompilationDatabase(
63 llvm::Optional<Path> CompileCommandsDir)
Ilya Biryukov940901e2017-12-13 12:51:22 +000064 : CompileCommandsDir(std::move(CompileCommandsDir)) {}
Ilya Biryukove5128f72017-09-20 07:24:15 +000065
Sam McCall690dcf12018-04-20 11:35:17 +000066DirectoryBasedGlobalCompilationDatabase::
67 ~DirectoryBasedGlobalCompilationDatabase() = default;
68
Ilya Biryukovf2001aa2019-01-07 15:45:19 +000069llvm::Optional<tooling::CompileCommand>
Kadir Cetinkayaad549352019-07-11 09:54:31 +000070DirectoryBasedGlobalCompilationDatabase::getCompileCommand(PathRef File) const {
71 CDBLookupRequest Req;
72 Req.FileName = File;
73 Req.ShouldBroadcast = true;
74
75 auto Res = lookupCDB(Req);
76 if (!Res) {
Sam McCallbed58852018-07-11 10:35:11 +000077 log("Failed to find compilation database for {0}", File);
Kadir Cetinkayaad549352019-07-11 09:54:31 +000078 return llvm::None;
Krasimir Georgievc2a16a32017-07-06 08:44:54 +000079 }
Kadir Cetinkayaad549352019-07-11 09:54:31 +000080
81 auto Candidates = Res->CDB->getCompileCommands(File);
82 if (!Candidates.empty())
83 return std::move(Candidates.front());
84
Sam McCallc008af62018-10-20 15:30:37 +000085 return None;
Sam McCallecbeab02017-12-04 10:08:45 +000086}
Krasimir Georgievc2a16a32017-07-06 08:44:54 +000087
Sam McCall7ee08672019-07-26 14:07:11 +000088// For platforms where paths are case-insensitive (but case-preserving),
89// we need to do case-insensitive comparisons and use lowercase keys.
90// FIXME: Make Path a real class with desired semantics instead.
91// This class is not the only place this problem exists.
92// FIXME: Mac filesystems default to case-insensitive, but may be sensitive.
93
94static std::string maybeCaseFoldPath(PathRef Path) {
95#if defined(_WIN32) || defined(__APPLE__)
96 return Path.lower();
97#else
Benjamin Krameradcd0262020-01-28 20:23:46 +010098 return std::string(Path);
Sam McCall7ee08672019-07-26 14:07:11 +000099#endif
100}
101
102static bool pathEqual(PathRef A, PathRef B) {
103#if defined(_WIN32) || defined(__APPLE__)
104 return A.equals_lower(B);
105#else
106 return A == B;
107#endif
108}
109
110DirectoryBasedGlobalCompilationDatabase::CachedCDB &
Sam McCallc02ba722017-12-22 09:47:34 +0000111DirectoryBasedGlobalCompilationDatabase::getCDBInDirLocked(PathRef Dir) const {
112 // FIXME(ibiryukov): Invalidate cached compilation databases on changes
Sam McCall7ee08672019-07-26 14:07:11 +0000113 // FIXME(sammccall): this function hot, avoid copying key when hitting cache.
114 auto Key = maybeCaseFoldPath(Dir);
115 auto R = CompilationDatabases.try_emplace(Key);
116 if (R.second) { // Cache miss, try to load CDB.
117 CachedCDB &Entry = R.first->second;
Kadir Cetinkaya4fb1adc2020-01-29 11:54:22 +0100118 std::string Error;
Sam McCall7ee08672019-07-26 14:07:11 +0000119 Entry.CDB = tooling::CompilationDatabase::loadFromDirectory(Dir, Error);
Benjamin Krameradcd0262020-01-28 20:23:46 +0100120 Entry.Path = std::string(Dir);
Kadir Cetinkaya4fb1adc2020-01-29 11:54:22 +0100121 if (Entry.CDB)
122 log("Loaded compilation database from {0}", Dir);
Sam McCall7ee08672019-07-26 14:07:11 +0000123 }
124 return R.first->second;
Sam McCallc02ba722017-12-22 09:47:34 +0000125}
Ilya Biryukov38d79772017-05-16 09:38:59 +0000126
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000127llvm::Optional<DirectoryBasedGlobalCompilationDatabase::CDBLookupResult>
128DirectoryBasedGlobalCompilationDatabase::lookupCDB(
129 CDBLookupRequest Request) const {
130 assert(llvm::sys::path::is_absolute(Request.FileName) &&
Ilya Biryukov38d79772017-05-16 09:38:59 +0000131 "path must be absolute");
132
Sam McCall7ee08672019-07-26 14:07:11 +0000133 bool ShouldBroadcast = false;
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000134 CDBLookupResult Result;
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000135
136 {
137 std::lock_guard<std::mutex> Lock(Mutex);
Sam McCall7ee08672019-07-26 14:07:11 +0000138 CachedCDB *Entry = nullptr;
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000139 if (CompileCommandsDir) {
Sam McCall7ee08672019-07-26 14:07:11 +0000140 Entry = &getCDBInDirLocked(*CompileCommandsDir);
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000141 } else {
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000142 // Traverse the canonical version to prevent false positives. i.e.:
143 // src/build/../a.cc can detect a CDB in /src/build if not canonicalized.
Sam McCall7ee08672019-07-26 14:07:11 +0000144 // FIXME(sammccall): this loop is hot, use a union-find-like structure.
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000145 actOnAllParentDirectories(removeDots(Request.FileName),
Sam McCall7ee08672019-07-26 14:07:11 +0000146 [&](PathRef Path) {
147 Entry = &getCDBInDirLocked(Path);
148 return Entry->CDB != nullptr;
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000149 });
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000150 }
151
Sam McCall7ee08672019-07-26 14:07:11 +0000152 if (!Entry || !Entry->CDB)
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000153 return llvm::None;
154
155 // Mark CDB as broadcasted to make sure discovery is performed once.
Sam McCall7ee08672019-07-26 14:07:11 +0000156 if (Request.ShouldBroadcast && !Entry->SentBroadcast) {
157 Entry->SentBroadcast = true;
158 ShouldBroadcast = true;
159 }
160
161 Result.CDB = Entry->CDB.get();
162 Result.PI.SourceRoot = Entry->Path;
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000163 }
164
165 // FIXME: Maybe make the following part async, since this can block retrieval
166 // of compile commands.
Sam McCall7ee08672019-07-26 14:07:11 +0000167 if (ShouldBroadcast)
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000168 broadcastCDB(Result);
169 return Result;
170}
171
172void DirectoryBasedGlobalCompilationDatabase::broadcastCDB(
173 CDBLookupResult Result) const {
174 assert(Result.CDB && "Trying to broadcast an invalid CDB!");
175
176 std::vector<std::string> AllFiles = Result.CDB->getAllFiles();
177 // We assume CDB in CompileCommandsDir owns all of its entries, since we don't
178 // perform any search in parent paths whenever it is set.
Sam McCall2bebc3d2018-11-20 10:56:03 +0000179 if (CompileCommandsDir) {
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000180 assert(*CompileCommandsDir == Result.PI.SourceRoot &&
181 "Trying to broadcast a CDB outside of CompileCommandsDir!");
182 OnCommandChanged.broadcast(std::move(AllFiles));
183 return;
184 }
185
186 llvm::StringMap<bool> DirectoryHasCDB;
187 {
188 std::lock_guard<std::mutex> Lock(Mutex);
189 // Uniquify all parent directories of all files.
190 for (llvm::StringRef File : AllFiles) {
191 actOnAllParentDirectories(File, [&](PathRef Path) {
192 auto It = DirectoryHasCDB.try_emplace(Path);
193 // Already seen this path, and all of its parents.
194 if (!It.second)
195 return true;
196
Sam McCall7ee08672019-07-26 14:07:11 +0000197 CachedCDB &Entry = getCDBInDirLocked(Path);
198 It.first->second = Entry.CDB != nullptr;
199 return pathEqual(Path, Result.PI.SourceRoot);
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000200 });
Sam McCall2bebc3d2018-11-20 10:56:03 +0000201 }
202 }
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000203
204 std::vector<std::string> GovernedFiles;
205 for (llvm::StringRef File : AllFiles) {
206 // A file is governed by this CDB if lookup for the file would find it.
207 // Independent of whether it has an entry for that file or not.
208 actOnAllParentDirectories(File, [&](PathRef Path) {
209 if (DirectoryHasCDB.lookup(Path)) {
Sam McCall7ee08672019-07-26 14:07:11 +0000210 if (pathEqual(Path, Result.PI.SourceRoot))
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000211 // Make sure listeners always get a canonical path for the file.
212 GovernedFiles.push_back(removeDots(File));
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000213 // Stop as soon as we hit a CDB.
214 return true;
215 }
216 return false;
217 });
218 }
219
220 OnCommandChanged.broadcast(std::move(GovernedFiles));
221}
222
223llvm::Optional<ProjectInfo>
224DirectoryBasedGlobalCompilationDatabase::getProjectInfo(PathRef File) const {
225 CDBLookupRequest Req;
226 Req.FileName = File;
227 Req.ShouldBroadcast = false;
228 auto Res = lookupCDB(Req);
229 if (!Res)
230 return llvm::None;
231 return Res->PI;
Sam McCall2bebc3d2018-11-20 10:56:03 +0000232}
233
234OverlayCDB::OverlayCDB(const GlobalCompilationDatabase *Base,
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +0000235 std::vector<std::string> FallbackFlags,
Sam McCall99768b22019-11-29 19:37:48 +0100236 tooling::ArgumentsAdjuster Adjuster)
237 : Base(Base), ArgsAdjuster(std::move(Adjuster)),
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +0000238 FallbackFlags(std::move(FallbackFlags)) {
Sam McCall2bebc3d2018-11-20 10:56:03 +0000239 if (Base)
240 BaseChanged = Base->watch([this](const std::vector<std::string> Changes) {
241 OnCommandChanged.broadcast(Changes);
242 });
Ilya Biryukov38d79772017-05-16 09:38:59 +0000243}
Krasimir Georgievc2a16a32017-07-06 08:44:54 +0000244
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000245llvm::Optional<tooling::CompileCommand>
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000246OverlayCDB::getCompileCommand(PathRef File) const {
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +0000247 llvm::Optional<tooling::CompileCommand> Cmd;
Sam McCallc55d09a2018-11-02 13:09:36 +0000248 {
249 std::lock_guard<std::mutex> Lock(Mutex);
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000250 auto It = Commands.find(removeDots(File));
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000251 if (It != Commands.end())
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +0000252 Cmd = It->second;
Sam McCallc55d09a2018-11-02 13:09:36 +0000253 }
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +0000254 if (!Cmd && Base)
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000255 Cmd = Base->getCompileCommand(File);
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +0000256 if (!Cmd)
257 return llvm::None;
Sam McCall99768b22019-11-29 19:37:48 +0100258 if (ArgsAdjuster)
259 Cmd->CommandLine = ArgsAdjuster(Cmd->CommandLine, Cmd->Filename);
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +0000260 return Cmd;
Alex Lorenzf8087862018-08-01 17:39:29 +0000261}
262
Sam McCallc55d09a2018-11-02 13:09:36 +0000263tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File) const {
264 auto Cmd = Base ? Base->getFallbackCommand(File)
265 : GlobalCompilationDatabase::getFallbackCommand(File);
266 std::lock_guard<std::mutex> Lock(Mutex);
267 Cmd.CommandLine.insert(Cmd.CommandLine.end(), FallbackFlags.begin(),
268 FallbackFlags.end());
Sam McCall99768b22019-11-29 19:37:48 +0100269 if (ArgsAdjuster)
270 Cmd.CommandLine = ArgsAdjuster(Cmd.CommandLine, Cmd.Filename);
Sam McCallc55d09a2018-11-02 13:09:36 +0000271 return Cmd;
272}
273
274void OverlayCDB::setCompileCommand(
275 PathRef File, llvm::Optional<tooling::CompileCommand> Cmd) {
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000276 // We store a canonical version internally to prevent mismatches between set
277 // and get compile commands. Also it assures clients listening to broadcasts
278 // doesn't receive different names for the same file.
279 std::string CanonPath = removeDots(File);
Sam McCall2bebc3d2018-11-20 10:56:03 +0000280 {
281 std::unique_lock<std::mutex> Lock(Mutex);
282 if (Cmd)
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000283 Commands[CanonPath] = std::move(*Cmd);
Sam McCall2bebc3d2018-11-20 10:56:03 +0000284 else
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000285 Commands.erase(CanonPath);
Sam McCall2bebc3d2018-11-20 10:56:03 +0000286 }
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000287 OnCommandChanged.broadcast({CanonPath});
Alex Lorenzf8087862018-08-01 17:39:29 +0000288}
289
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000290llvm::Optional<ProjectInfo> OverlayCDB::getProjectInfo(PathRef File) const {
291 {
292 std::lock_guard<std::mutex> Lock(Mutex);
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000293 auto It = Commands.find(removeDots(File));
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000294 if (It != Commands.end())
295 return ProjectInfo{};
296 }
297 if (Base)
298 return Base->getProjectInfo(File);
299
300 return llvm::None;
301}
Krasimir Georgievc2a16a32017-07-06 08:44:54 +0000302} // namespace clangd
303} // namespace clang