blob: 5e75864ec8d443e8747fed6eb4786a9917a2a3f3 [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"
Sam McCallad97ccf2020-04-28 17:49:17 +020011#include "support/Logger.h"
12#include "support/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 auto Key = maybeCaseFoldPath(Dir);
114 auto R = CompilationDatabases.try_emplace(Key);
115 if (R.second) { // Cache miss, try to load CDB.
116 CachedCDB &Entry = R.first->second;
Kadir Cetinkaya4fb1adc2020-01-29 11:54:22 +0100117 std::string Error;
Benjamin Krameradcd0262020-01-28 20:23:46 +0100118 Entry.Path = std::string(Dir);
Sam McCall226b0452020-04-22 14:24:12 +0200119 Entry.CDB = tooling::CompilationDatabase::loadFromDirectory(Dir, Error);
120 // Check for $src/build, the conventional CMake build root.
121 // Probe existence first to avoid each plugin doing IO if it doesn't exist.
122 if (!CompileCommandsDir && !Entry.CDB) {
123 llvm::SmallString<256> BuildDir = Dir;
124 llvm::sys::path::append(BuildDir, "build");
125 if (llvm::sys::fs::is_directory(BuildDir)) {
126 vlog("Found candidate build directory {0}", BuildDir);
127 Entry.CDB =
128 tooling::CompilationDatabase::loadFromDirectory(BuildDir, Error);
129 }
130 }
Kadir Cetinkaya4fb1adc2020-01-29 11:54:22 +0100131 if (Entry.CDB)
132 log("Loaded compilation database from {0}", Dir);
Sam McCall7ee08672019-07-26 14:07:11 +0000133 }
134 return R.first->second;
Sam McCallc02ba722017-12-22 09:47:34 +0000135}
Ilya Biryukov38d79772017-05-16 09:38:59 +0000136
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000137llvm::Optional<DirectoryBasedGlobalCompilationDatabase::CDBLookupResult>
138DirectoryBasedGlobalCompilationDatabase::lookupCDB(
139 CDBLookupRequest Request) const {
140 assert(llvm::sys::path::is_absolute(Request.FileName) &&
Ilya Biryukov38d79772017-05-16 09:38:59 +0000141 "path must be absolute");
142
Sam McCall7ee08672019-07-26 14:07:11 +0000143 bool ShouldBroadcast = false;
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000144 CDBLookupResult Result;
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000145
146 {
147 std::lock_guard<std::mutex> Lock(Mutex);
Sam McCall7ee08672019-07-26 14:07:11 +0000148 CachedCDB *Entry = nullptr;
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000149 if (CompileCommandsDir) {
Sam McCall7ee08672019-07-26 14:07:11 +0000150 Entry = &getCDBInDirLocked(*CompileCommandsDir);
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000151 } 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.
Sam McCall7ee08672019-07-26 14:07:11 +0000154 // FIXME(sammccall): this loop is hot, use a union-find-like structure.
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000155 actOnAllParentDirectories(removeDots(Request.FileName),
Sam McCall7ee08672019-07-26 14:07:11 +0000156 [&](PathRef Path) {
157 Entry = &getCDBInDirLocked(Path);
158 return Entry->CDB != nullptr;
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000159 });
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000160 }
161
Sam McCall7ee08672019-07-26 14:07:11 +0000162 if (!Entry || !Entry->CDB)
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000163 return llvm::None;
164
165 // Mark CDB as broadcasted to make sure discovery is performed once.
Sam McCall7ee08672019-07-26 14:07:11 +0000166 if (Request.ShouldBroadcast && !Entry->SentBroadcast) {
167 Entry->SentBroadcast = true;
168 ShouldBroadcast = true;
169 }
170
171 Result.CDB = Entry->CDB.get();
172 Result.PI.SourceRoot = Entry->Path;
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000173 }
174
175 // FIXME: Maybe make the following part async, since this can block retrieval
176 // of compile commands.
Sam McCall7ee08672019-07-26 14:07:11 +0000177 if (ShouldBroadcast)
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000178 broadcastCDB(Result);
179 return Result;
180}
181
182void DirectoryBasedGlobalCompilationDatabase::broadcastCDB(
183 CDBLookupResult Result) const {
184 assert(Result.CDB && "Trying to broadcast an invalid CDB!");
185
186 std::vector<std::string> AllFiles = Result.CDB->getAllFiles();
187 // We assume CDB in CompileCommandsDir owns all of its entries, since we don't
188 // perform any search in parent paths whenever it is set.
Sam McCall2bebc3d2018-11-20 10:56:03 +0000189 if (CompileCommandsDir) {
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000190 assert(*CompileCommandsDir == Result.PI.SourceRoot &&
191 "Trying to broadcast a CDB outside of CompileCommandsDir!");
192 OnCommandChanged.broadcast(std::move(AllFiles));
193 return;
194 }
195
196 llvm::StringMap<bool> DirectoryHasCDB;
197 {
198 std::lock_guard<std::mutex> Lock(Mutex);
199 // Uniquify all parent directories of all files.
200 for (llvm::StringRef File : AllFiles) {
201 actOnAllParentDirectories(File, [&](PathRef Path) {
202 auto It = DirectoryHasCDB.try_emplace(Path);
203 // Already seen this path, and all of its parents.
204 if (!It.second)
205 return true;
206
Sam McCall7ee08672019-07-26 14:07:11 +0000207 CachedCDB &Entry = getCDBInDirLocked(Path);
208 It.first->second = Entry.CDB != nullptr;
209 return pathEqual(Path, Result.PI.SourceRoot);
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000210 });
Sam McCall2bebc3d2018-11-20 10:56:03 +0000211 }
212 }
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000213
214 std::vector<std::string> GovernedFiles;
215 for (llvm::StringRef File : AllFiles) {
216 // A file is governed by this CDB if lookup for the file would find it.
217 // Independent of whether it has an entry for that file or not.
218 actOnAllParentDirectories(File, [&](PathRef Path) {
219 if (DirectoryHasCDB.lookup(Path)) {
Sam McCall7ee08672019-07-26 14:07:11 +0000220 if (pathEqual(Path, Result.PI.SourceRoot))
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000221 // Make sure listeners always get a canonical path for the file.
222 GovernedFiles.push_back(removeDots(File));
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000223 // Stop as soon as we hit a CDB.
224 return true;
225 }
226 return false;
227 });
228 }
229
230 OnCommandChanged.broadcast(std::move(GovernedFiles));
231}
232
233llvm::Optional<ProjectInfo>
234DirectoryBasedGlobalCompilationDatabase::getProjectInfo(PathRef File) const {
235 CDBLookupRequest Req;
236 Req.FileName = File;
237 Req.ShouldBroadcast = false;
238 auto Res = lookupCDB(Req);
239 if (!Res)
240 return llvm::None;
241 return Res->PI;
Sam McCall2bebc3d2018-11-20 10:56:03 +0000242}
243
244OverlayCDB::OverlayCDB(const GlobalCompilationDatabase *Base,
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +0000245 std::vector<std::string> FallbackFlags,
Sam McCall99768b22019-11-29 19:37:48 +0100246 tooling::ArgumentsAdjuster Adjuster)
247 : Base(Base), ArgsAdjuster(std::move(Adjuster)),
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +0000248 FallbackFlags(std::move(FallbackFlags)) {
Sam McCall2bebc3d2018-11-20 10:56:03 +0000249 if (Base)
250 BaseChanged = Base->watch([this](const std::vector<std::string> Changes) {
251 OnCommandChanged.broadcast(Changes);
252 });
Ilya Biryukov38d79772017-05-16 09:38:59 +0000253}
Krasimir Georgievc2a16a32017-07-06 08:44:54 +0000254
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000255llvm::Optional<tooling::CompileCommand>
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000256OverlayCDB::getCompileCommand(PathRef File) const {
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +0000257 llvm::Optional<tooling::CompileCommand> Cmd;
Sam McCallc55d09a2018-11-02 13:09:36 +0000258 {
259 std::lock_guard<std::mutex> Lock(Mutex);
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000260 auto It = Commands.find(removeDots(File));
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000261 if (It != Commands.end())
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +0000262 Cmd = It->second;
Sam McCallc55d09a2018-11-02 13:09:36 +0000263 }
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +0000264 if (!Cmd && Base)
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000265 Cmd = Base->getCompileCommand(File);
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +0000266 if (!Cmd)
267 return llvm::None;
Sam McCall99768b22019-11-29 19:37:48 +0100268 if (ArgsAdjuster)
269 Cmd->CommandLine = ArgsAdjuster(Cmd->CommandLine, Cmd->Filename);
Kadir Cetinkayabe6b35d2019-01-22 09:10:20 +0000270 return Cmd;
Alex Lorenzf8087862018-08-01 17:39:29 +0000271}
272
Sam McCallc55d09a2018-11-02 13:09:36 +0000273tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File) const {
274 auto Cmd = Base ? Base->getFallbackCommand(File)
275 : GlobalCompilationDatabase::getFallbackCommand(File);
276 std::lock_guard<std::mutex> Lock(Mutex);
277 Cmd.CommandLine.insert(Cmd.CommandLine.end(), FallbackFlags.begin(),
278 FallbackFlags.end());
Sam McCall99768b22019-11-29 19:37:48 +0100279 if (ArgsAdjuster)
280 Cmd.CommandLine = ArgsAdjuster(Cmd.CommandLine, Cmd.Filename);
Sam McCallc55d09a2018-11-02 13:09:36 +0000281 return Cmd;
282}
283
284void OverlayCDB::setCompileCommand(
285 PathRef File, llvm::Optional<tooling::CompileCommand> Cmd) {
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000286 // We store a canonical version internally to prevent mismatches between set
287 // and get compile commands. Also it assures clients listening to broadcasts
288 // doesn't receive different names for the same file.
289 std::string CanonPath = removeDots(File);
Sam McCall2bebc3d2018-11-20 10:56:03 +0000290 {
291 std::unique_lock<std::mutex> Lock(Mutex);
292 if (Cmd)
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000293 Commands[CanonPath] = std::move(*Cmd);
Sam McCall2bebc3d2018-11-20 10:56:03 +0000294 else
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000295 Commands.erase(CanonPath);
Sam McCall2bebc3d2018-11-20 10:56:03 +0000296 }
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000297 OnCommandChanged.broadcast({CanonPath});
Alex Lorenzf8087862018-08-01 17:39:29 +0000298}
299
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000300llvm::Optional<ProjectInfo> OverlayCDB::getProjectInfo(PathRef File) const {
301 {
302 std::lock_guard<std::mutex> Lock(Mutex);
Kadir Cetinkaya6d53adf2019-07-18 16:13:23 +0000303 auto It = Commands.find(removeDots(File));
Kadir Cetinkayaad549352019-07-11 09:54:31 +0000304 if (It != Commands.end())
305 return ProjectInfo{};
306 }
307 if (Base)
308 return Base->getProjectInfo(File);
309
310 return llvm::None;
311}
Krasimir Georgievc2a16a32017-07-06 08:44:54 +0000312} // namespace clangd
313} // namespace clang