Kirill Bobyrev | 8e35f1e | 2018-08-14 16:03:32 +0000 | [diff] [blame] | 1 | //===--- GlobalCompilationDatabase.cpp ---------------------------*- C++-*-===// |
Ilya Biryukov | 38d7977 | 2017-05-16 09:38:59 +0000 | [diff] [blame] | 2 | // |
Chandler Carruth | 2946cd7 | 2019-01-19 08:50:56 +0000 | [diff] [blame] | 3 | // 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 Biryukov | 38d7977 | 2017-05-16 09:38:59 +0000 | [diff] [blame] | 6 | // |
Kirill Bobyrev | 8e35f1e | 2018-08-14 16:03:32 +0000 | [diff] [blame] | 7 | //===----------------------------------------------------------------------===// |
Ilya Biryukov | 38d7977 | 2017-05-16 09:38:59 +0000 | [diff] [blame] | 8 | |
| 9 | #include "GlobalCompilationDatabase.h" |
Kadir Cetinkaya | 6d53adf | 2019-07-18 16:13:23 +0000 | [diff] [blame] | 10 | #include "FS.h" |
Ilya Biryukov | 0c1ca6b | 2017-10-02 15:13:20 +0000 | [diff] [blame] | 11 | #include "Logger.h" |
Kadir Cetinkaya | ad54935 | 2019-07-11 09:54:31 +0000 | [diff] [blame] | 12 | #include "Path.h" |
Kadir Cetinkaya | be6b35d | 2019-01-22 09:10:20 +0000 | [diff] [blame] | 13 | #include "clang/Frontend/CompilerInvocation.h" |
| 14 | #include "clang/Tooling/ArgumentsAdjusters.h" |
Ilya Biryukov | 38d7977 | 2017-05-16 09:38:59 +0000 | [diff] [blame] | 15 | #include "clang/Tooling/CompilationDatabase.h" |
Kadir Cetinkaya | ad54935 | 2019-07-11 09:54:31 +0000 | [diff] [blame] | 16 | #include "llvm/ADT/None.h" |
Kadir Cetinkaya | be6b35d | 2019-01-22 09:10:20 +0000 | [diff] [blame] | 17 | #include "llvm/ADT/Optional.h" |
Kadir Cetinkaya | ad54935 | 2019-07-11 09:54:31 +0000 | [diff] [blame] | 18 | #include "llvm/ADT/STLExtras.h" |
Kadir Cetinkaya | 6d53adf | 2019-07-18 16:13:23 +0000 | [diff] [blame] | 19 | #include "llvm/ADT/SmallString.h" |
Ilya Biryukov | 38d7977 | 2017-05-16 09:38:59 +0000 | [diff] [blame] | 20 | #include "llvm/Support/FileSystem.h" |
Sam McCall | 88bccde | 2019-11-29 19:37:48 +0100 | [diff] [blame] | 21 | #include "llvm/Support/FileUtilities.h" |
Ilya Biryukov | 38d7977 | 2017-05-16 09:38:59 +0000 | [diff] [blame] | 22 | #include "llvm/Support/Path.h" |
Sam McCall | 88bccde | 2019-11-29 19:37:48 +0100 | [diff] [blame] | 23 | #include "llvm/Support/Program.h" |
Kadir Cetinkaya | ad54935 | 2019-07-11 09:54:31 +0000 | [diff] [blame] | 24 | #include <string> |
| 25 | #include <tuple> |
| 26 | #include <vector> |
Ilya Biryukov | 38d7977 | 2017-05-16 09:38:59 +0000 | [diff] [blame] | 27 | |
Krasimir Georgiev | c2a16a3 | 2017-07-06 08:44:54 +0000 | [diff] [blame] | 28 | namespace clang { |
| 29 | namespace clangd { |
Kadir Cetinkaya | be6b35d | 2019-01-22 09:10:20 +0000 | [diff] [blame] | 30 | namespace { |
| 31 | |
Sam McCall | 88bccde | 2019-11-29 19:37:48 +0100 | [diff] [blame] | 32 | // Query apple's `xcrun` launcher, which is the source of truth for "how should" |
| 33 | // clang be invoked on this system. |
| 34 | llvm::Optional<std::string> queryXcrun(llvm::ArrayRef<llvm::StringRef> Argv) { |
| 35 | auto Xcrun = llvm::sys::findProgramByName("xcrun"); |
| 36 | if (!Xcrun) { |
| 37 | log("Couldn't find xcrun. Hopefully you have a non-apple toolchain..."); |
| 38 | return llvm::None; |
| 39 | } |
| 40 | llvm::SmallString<64> OutFile; |
| 41 | llvm::sys::fs::createTemporaryFile("clangd-xcrun", "", OutFile); |
| 42 | llvm::FileRemover OutRemover(OutFile); |
| 43 | llvm::Optional<llvm::StringRef> Redirects[3] = { |
| 44 | /*stdin=*/{""}, /*stdout=*/{OutFile}, /*stderr=*/{""}}; |
| 45 | vlog("Invoking {0} to find clang installation", *Xcrun); |
| 46 | int Ret = llvm::sys::ExecuteAndWait(*Xcrun, Argv, |
| 47 | /*Env=*/llvm::None, Redirects, |
| 48 | /*SecondsToWait=*/10); |
| 49 | if (Ret != 0) { |
| 50 | log("xcrun exists but failed with code {0}. " |
| 51 | "If you have a non-apple toolchain, this is OK. " |
| 52 | "Otherwise, try xcode-select --install.", |
| 53 | Ret); |
| 54 | return llvm::None; |
| 55 | } |
| 56 | |
| 57 | auto Buf = llvm::MemoryBuffer::getFile(OutFile); |
| 58 | if (!Buf) { |
| 59 | log("Can't read xcrun output: {0}", Buf.getError().message()); |
| 60 | return llvm::None; |
| 61 | } |
| 62 | StringRef Path = Buf->get()->getBuffer().trim(); |
| 63 | if (Path.empty()) { |
| 64 | log("xcrun produced no output"); |
| 65 | return llvm::None; |
| 66 | } |
| 67 | return Path.str(); |
| 68 | } |
| 69 | |
| 70 | // On Mac, `which clang` is /usr/bin/clang. It runs `xcrun clang`, which knows |
| 71 | // where the real clang is kept. We need to do the same thing, |
| 72 | // because cc1 (not the driver!) will find libc++ relative to argv[0]. |
| 73 | llvm::Optional<std::string> queryMacClangPath() { |
| 74 | #ifndef __APPLE__ |
| 75 | return llvm::None; |
| 76 | #endif |
| 77 | |
| 78 | return queryXcrun({"xcrun", "--find", "clang"}); |
| 79 | } |
| 80 | |
| 81 | // Resolve symlinks if possible. |
| 82 | std::string resolve(std::string Path) { |
| 83 | llvm::SmallString<128> Resolved; |
| 84 | if (llvm::sys::fs::real_path(Path, Resolved)) |
| 85 | return Path; // On error; |
| 86 | return Resolved.str(); |
| 87 | } |
| 88 | |
| 89 | // Get a plausible full `clang` path. |
| 90 | // This is used in the fallback compile command, or when the CDB returns a |
| 91 | // generic driver with no path. |
| 92 | llvm::StringRef getFallbackClangPath() { |
| 93 | static const std::string &MemoizedFallbackPath = [&]() -> std::string { |
| 94 | // The driver and/or cc1 sometimes depend on the binary name to compute |
| 95 | // useful things like the standard library location. |
| 96 | // We need to emulate what clang on this system is likely to see. |
| 97 | // cc1 in particular looks at the "real path" of the running process, and |
| 98 | // so if /usr/bin/clang is a symlink, it sees the resolved path. |
| 99 | // clangd doesn't have that luxury, so we resolve symlinks ourselves. |
| 100 | |
| 101 | // /usr/bin/clang on a mac is a program that redirects to the right clang. |
| 102 | // We resolve it as if it were a symlink. |
| 103 | if (auto MacClang = queryMacClangPath()) |
| 104 | return resolve(std::move(*MacClang)); |
| 105 | // On other platforms, just look for compilers on the PATH. |
| 106 | for (const char* Name : {"clang", "gcc", "cc"}) |
| 107 | if (auto PathCC = llvm::sys::findProgramByName(Name)) |
| 108 | return resolve(std::move(*PathCC)); |
| 109 | // Fallback: a nonexistent 'clang' binary next to clangd. |
| 110 | static int Dummy; |
| 111 | std::string ClangdExecutable = |
| 112 | llvm::sys::fs::getMainExecutable("clangd", (void *)&Dummy); |
| 113 | SmallString<128> ClangPath; |
| 114 | ClangPath = llvm::sys::path::parent_path(ClangdExecutable); |
| 115 | llvm::sys::path::append(ClangPath, "clang"); |
| 116 | return ClangPath.str(); |
| 117 | }(); |
| 118 | return MemoizedFallbackPath; |
| 119 | } |
| 120 | |
| 121 | // On mac, /usr/bin/clang sets SDKROOT and then invokes the real clang. |
| 122 | // The effect of this is to set -isysroot correctly. We do the same. |
| 123 | const std::string *getMacSysroot() { |
| 124 | #ifndef __APPLE__ |
| 125 | return nullptr; |
| 126 | #endif |
| 127 | |
| 128 | // SDKROOT overridden in environment, respect it. Driver will set isysroot. |
| 129 | if (::getenv("SDKROOT")) |
| 130 | return nullptr; |
| 131 | static const llvm::Optional<std::string> &Sysroot = |
| 132 | queryXcrun({"xcrun", "--show-sdk-path"}); |
| 133 | return Sysroot ? Sysroot.getPointer() : nullptr; |
| 134 | } |
| 135 | |
| 136 | // Transform a command into the form we want to send to the driver. |
| 137 | // The command was originally either from the CDB or is the fallback command, |
| 138 | // and may have been modified by OverlayCDB. |
Kadir Cetinkaya | be6b35d | 2019-01-22 09:10:20 +0000 | [diff] [blame] | 139 | void adjustArguments(tooling::CompileCommand &Cmd, |
| 140 | llvm::StringRef ResourceDir) { |
Kadir Cetinkaya | 65944ab | 2019-03-08 08:38:25 +0000 | [diff] [blame] | 141 | tooling::ArgumentsAdjuster ArgsAdjuster = tooling::combineAdjusters( |
| 142 | // clangd should not write files to disk, including dependency files |
| 143 | // requested on the command line. |
| 144 | tooling::getClangStripDependencyFileAdjuster(), |
| 145 | // Strip plugin related command line arguments. Clangd does |
| 146 | // not support plugins currently. Therefore it breaks if |
| 147 | // compiler tries to load plugins. |
| 148 | tooling::combineAdjusters(tooling::getStripPluginsAdjuster(), |
| 149 | tooling::getClangSyntaxOnlyAdjuster())); |
| 150 | |
| 151 | Cmd.CommandLine = ArgsAdjuster(Cmd.CommandLine, Cmd.Filename); |
Sam McCall | 88bccde | 2019-11-29 19:37:48 +0100 | [diff] [blame] | 152 | // Check whether the flag exists, either as -flag or -flag=* |
| 153 | auto Has = [&](llvm::StringRef Flag) { |
| 154 | for (llvm::StringRef Arg : Cmd.CommandLine) { |
| 155 | if (Arg.consume_front(Flag) && (Arg.empty() || Arg[0] == '=')) |
| 156 | return true; |
| 157 | } |
| 158 | return false; |
| 159 | }; |
Kadir Cetinkaya | be6b35d | 2019-01-22 09:10:20 +0000 | [diff] [blame] | 160 | // Inject the resource dir. |
Sam McCall | 88bccde | 2019-11-29 19:37:48 +0100 | [diff] [blame] | 161 | if (!ResourceDir.empty() && !Has("-resource-dir")) |
Kadir Cetinkaya | be6b35d | 2019-01-22 09:10:20 +0000 | [diff] [blame] | 162 | Cmd.CommandLine.push_back(("-resource-dir=" + ResourceDir).str()); |
Sam McCall | 88bccde | 2019-11-29 19:37:48 +0100 | [diff] [blame] | 163 | if (!Has("-isysroot")) |
| 164 | if (const std::string *MacSysroot = getMacSysroot()) { |
| 165 | Cmd.CommandLine.push_back("-isysroot"); |
| 166 | Cmd.CommandLine.push_back(*MacSysroot); |
| 167 | } |
| 168 | |
| 169 | // If the driver is a generic name like "g++" with no path, add a clang path. |
| 170 | // This makes it easier for us to find the standard libraries on mac. |
| 171 | if (!Cmd.CommandLine.empty()) { |
| 172 | std::string &Driver = Cmd.CommandLine.front(); |
| 173 | if (Driver == "clang" || Driver == "clang++" || Driver == "gcc" || |
| 174 | Driver == "g++" || Driver == "cc" || Driver == "c++") { |
| 175 | llvm::SmallString<128> QualifiedDriver = |
| 176 | llvm::sys::path::parent_path(getFallbackClangPath()); |
| 177 | llvm::sys::path::append(QualifiedDriver, Driver); |
| 178 | Driver = QualifiedDriver.str(); |
| 179 | } |
| 180 | } |
Kadir Cetinkaya | be6b35d | 2019-01-22 09:10:20 +0000 | [diff] [blame] | 181 | } |
| 182 | |
| 183 | std::string getStandardResourceDir() { |
| 184 | static int Dummy; // Just an address in this process. |
| 185 | return CompilerInvocation::GetResourcesPath("clangd", (void *)&Dummy); |
| 186 | } |
| 187 | |
Kadir Cetinkaya | ad54935 | 2019-07-11 09:54:31 +0000 | [diff] [blame] | 188 | // Runs the given action on all parent directories of filename, starting from |
| 189 | // deepest directory and going up to root. Stops whenever action succeeds. |
| 190 | void actOnAllParentDirectories(PathRef FileName, |
| 191 | llvm::function_ref<bool(PathRef)> Action) { |
| 192 | for (auto Path = llvm::sys::path::parent_path(FileName); |
| 193 | !Path.empty() && !Action(Path); |
| 194 | Path = llvm::sys::path::parent_path(Path)) |
| 195 | ; |
| 196 | } |
| 197 | |
Kadir Cetinkaya | be6b35d | 2019-01-22 09:10:20 +0000 | [diff] [blame] | 198 | } // namespace |
Krasimir Georgiev | c2a16a3 | 2017-07-06 08:44:54 +0000 | [diff] [blame] | 199 | |
Sam McCall | ecbeab0 | 2017-12-04 10:08:45 +0000 | [diff] [blame] | 200 | tooling::CompileCommand |
| 201 | GlobalCompilationDatabase::getFallbackCommand(PathRef File) const { |
Sam McCall | 88bccde | 2019-11-29 19:37:48 +0100 | [diff] [blame] | 202 | std::vector<std::string> Argv = {"clang"}; |
Haojian Wu | 8ddf31b | 2019-06-18 11:54:17 +0000 | [diff] [blame] | 203 | // Clang treats .h files as C by default and files without extension as linker |
| 204 | // input, resulting in unhelpful diagnostics. |
Sam McCall | 690dcf1 | 2018-04-20 11:35:17 +0000 | [diff] [blame] | 205 | // Parsing as Objective C++ is friendly to more cases. |
Haojian Wu | 8ddf31b | 2019-06-18 11:54:17 +0000 | [diff] [blame] | 206 | auto FileExtension = llvm::sys::path::extension(File); |
| 207 | if (FileExtension.empty() || FileExtension == ".h") |
Sam McCall | 690dcf1 | 2018-04-20 11:35:17 +0000 | [diff] [blame] | 208 | Argv.push_back("-xobjective-c++-header"); |
| 209 | Argv.push_back(File); |
Eric Liu | 9ef03dd | 2019-04-15 12:32:28 +0000 | [diff] [blame] | 210 | tooling::CompileCommand Cmd(llvm::sys::path::parent_path(File), |
| 211 | llvm::sys::path::filename(File), std::move(Argv), |
| 212 | /*Output=*/""); |
| 213 | Cmd.Heuristic = "clangd fallback"; |
| 214 | return Cmd; |
Krasimir Georgiev | c2a16a3 | 2017-07-06 08:44:54 +0000 | [diff] [blame] | 215 | } |
Ilya Biryukov | 38d7977 | 2017-05-16 09:38:59 +0000 | [diff] [blame] | 216 | |
Ilya Biryukov | e5128f7 | 2017-09-20 07:24:15 +0000 | [diff] [blame] | 217 | DirectoryBasedGlobalCompilationDatabase:: |
Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame] | 218 | DirectoryBasedGlobalCompilationDatabase( |
| 219 | llvm::Optional<Path> CompileCommandsDir) |
Ilya Biryukov | 940901e | 2017-12-13 12:51:22 +0000 | [diff] [blame] | 220 | : CompileCommandsDir(std::move(CompileCommandsDir)) {} |
Ilya Biryukov | e5128f7 | 2017-09-20 07:24:15 +0000 | [diff] [blame] | 221 | |
Sam McCall | 690dcf1 | 2018-04-20 11:35:17 +0000 | [diff] [blame] | 222 | DirectoryBasedGlobalCompilationDatabase:: |
| 223 | ~DirectoryBasedGlobalCompilationDatabase() = default; |
| 224 | |
Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame] | 225 | llvm::Optional<tooling::CompileCommand> |
Kadir Cetinkaya | ad54935 | 2019-07-11 09:54:31 +0000 | [diff] [blame] | 226 | DirectoryBasedGlobalCompilationDatabase::getCompileCommand(PathRef File) const { |
| 227 | CDBLookupRequest Req; |
| 228 | Req.FileName = File; |
| 229 | Req.ShouldBroadcast = true; |
| 230 | |
| 231 | auto Res = lookupCDB(Req); |
| 232 | if (!Res) { |
Sam McCall | bed5885 | 2018-07-11 10:35:11 +0000 | [diff] [blame] | 233 | log("Failed to find compilation database for {0}", File); |
Kadir Cetinkaya | ad54935 | 2019-07-11 09:54:31 +0000 | [diff] [blame] | 234 | return llvm::None; |
Krasimir Georgiev | c2a16a3 | 2017-07-06 08:44:54 +0000 | [diff] [blame] | 235 | } |
Kadir Cetinkaya | ad54935 | 2019-07-11 09:54:31 +0000 | [diff] [blame] | 236 | |
| 237 | auto Candidates = Res->CDB->getCompileCommands(File); |
| 238 | if (!Candidates.empty()) |
| 239 | return std::move(Candidates.front()); |
| 240 | |
Sam McCall | c008af6 | 2018-10-20 15:30:37 +0000 | [diff] [blame] | 241 | return None; |
Sam McCall | ecbeab0 | 2017-12-04 10:08:45 +0000 | [diff] [blame] | 242 | } |
Krasimir Georgiev | c2a16a3 | 2017-07-06 08:44:54 +0000 | [diff] [blame] | 243 | |
Sam McCall | 7ee0867 | 2019-07-26 14:07:11 +0000 | [diff] [blame] | 244 | // For platforms where paths are case-insensitive (but case-preserving), |
| 245 | // we need to do case-insensitive comparisons and use lowercase keys. |
| 246 | // FIXME: Make Path a real class with desired semantics instead. |
| 247 | // This class is not the only place this problem exists. |
| 248 | // FIXME: Mac filesystems default to case-insensitive, but may be sensitive. |
| 249 | |
| 250 | static std::string maybeCaseFoldPath(PathRef Path) { |
| 251 | #if defined(_WIN32) || defined(__APPLE__) |
| 252 | return Path.lower(); |
| 253 | #else |
| 254 | return Path; |
| 255 | #endif |
| 256 | } |
| 257 | |
| 258 | static bool pathEqual(PathRef A, PathRef B) { |
| 259 | #if defined(_WIN32) || defined(__APPLE__) |
| 260 | return A.equals_lower(B); |
| 261 | #else |
| 262 | return A == B; |
| 263 | #endif |
| 264 | } |
| 265 | |
| 266 | DirectoryBasedGlobalCompilationDatabase::CachedCDB & |
Sam McCall | c02ba72 | 2017-12-22 09:47:34 +0000 | [diff] [blame] | 267 | DirectoryBasedGlobalCompilationDatabase::getCDBInDirLocked(PathRef Dir) const { |
| 268 | // FIXME(ibiryukov): Invalidate cached compilation databases on changes |
Sam McCall | 7ee0867 | 2019-07-26 14:07:11 +0000 | [diff] [blame] | 269 | // FIXME(sammccall): this function hot, avoid copying key when hitting cache. |
| 270 | auto Key = maybeCaseFoldPath(Dir); |
| 271 | auto R = CompilationDatabases.try_emplace(Key); |
| 272 | if (R.second) { // Cache miss, try to load CDB. |
| 273 | CachedCDB &Entry = R.first->second; |
| 274 | std::string Error = ""; |
| 275 | Entry.CDB = tooling::CompilationDatabase::loadFromDirectory(Dir, Error); |
| 276 | Entry.Path = Dir; |
| 277 | } |
| 278 | return R.first->second; |
Sam McCall | c02ba72 | 2017-12-22 09:47:34 +0000 | [diff] [blame] | 279 | } |
Ilya Biryukov | 38d7977 | 2017-05-16 09:38:59 +0000 | [diff] [blame] | 280 | |
Kadir Cetinkaya | ad54935 | 2019-07-11 09:54:31 +0000 | [diff] [blame] | 281 | llvm::Optional<DirectoryBasedGlobalCompilationDatabase::CDBLookupResult> |
| 282 | DirectoryBasedGlobalCompilationDatabase::lookupCDB( |
| 283 | CDBLookupRequest Request) const { |
| 284 | assert(llvm::sys::path::is_absolute(Request.FileName) && |
Ilya Biryukov | 38d7977 | 2017-05-16 09:38:59 +0000 | [diff] [blame] | 285 | "path must be absolute"); |
| 286 | |
Sam McCall | 7ee0867 | 2019-07-26 14:07:11 +0000 | [diff] [blame] | 287 | bool ShouldBroadcast = false; |
Kadir Cetinkaya | ad54935 | 2019-07-11 09:54:31 +0000 | [diff] [blame] | 288 | CDBLookupResult Result; |
Kadir Cetinkaya | ad54935 | 2019-07-11 09:54:31 +0000 | [diff] [blame] | 289 | |
| 290 | { |
| 291 | std::lock_guard<std::mutex> Lock(Mutex); |
Sam McCall | 7ee0867 | 2019-07-26 14:07:11 +0000 | [diff] [blame] | 292 | CachedCDB *Entry = nullptr; |
Kadir Cetinkaya | ad54935 | 2019-07-11 09:54:31 +0000 | [diff] [blame] | 293 | if (CompileCommandsDir) { |
Sam McCall | 7ee0867 | 2019-07-26 14:07:11 +0000 | [diff] [blame] | 294 | Entry = &getCDBInDirLocked(*CompileCommandsDir); |
Kadir Cetinkaya | ad54935 | 2019-07-11 09:54:31 +0000 | [diff] [blame] | 295 | } else { |
Kadir Cetinkaya | 6d53adf | 2019-07-18 16:13:23 +0000 | [diff] [blame] | 296 | // Traverse the canonical version to prevent false positives. i.e.: |
| 297 | // src/build/../a.cc can detect a CDB in /src/build if not canonicalized. |
Sam McCall | 7ee0867 | 2019-07-26 14:07:11 +0000 | [diff] [blame] | 298 | // FIXME(sammccall): this loop is hot, use a union-find-like structure. |
Kadir Cetinkaya | 6d53adf | 2019-07-18 16:13:23 +0000 | [diff] [blame] | 299 | actOnAllParentDirectories(removeDots(Request.FileName), |
Sam McCall | 7ee0867 | 2019-07-26 14:07:11 +0000 | [diff] [blame] | 300 | [&](PathRef Path) { |
| 301 | Entry = &getCDBInDirLocked(Path); |
| 302 | return Entry->CDB != nullptr; |
Kadir Cetinkaya | 6d53adf | 2019-07-18 16:13:23 +0000 | [diff] [blame] | 303 | }); |
Kadir Cetinkaya | ad54935 | 2019-07-11 09:54:31 +0000 | [diff] [blame] | 304 | } |
| 305 | |
Sam McCall | 7ee0867 | 2019-07-26 14:07:11 +0000 | [diff] [blame] | 306 | if (!Entry || !Entry->CDB) |
Kadir Cetinkaya | ad54935 | 2019-07-11 09:54:31 +0000 | [diff] [blame] | 307 | return llvm::None; |
| 308 | |
| 309 | // Mark CDB as broadcasted to make sure discovery is performed once. |
Sam McCall | 7ee0867 | 2019-07-26 14:07:11 +0000 | [diff] [blame] | 310 | if (Request.ShouldBroadcast && !Entry->SentBroadcast) { |
| 311 | Entry->SentBroadcast = true; |
| 312 | ShouldBroadcast = true; |
| 313 | } |
| 314 | |
| 315 | Result.CDB = Entry->CDB.get(); |
| 316 | Result.PI.SourceRoot = Entry->Path; |
Kadir Cetinkaya | ad54935 | 2019-07-11 09:54:31 +0000 | [diff] [blame] | 317 | } |
| 318 | |
| 319 | // FIXME: Maybe make the following part async, since this can block retrieval |
| 320 | // of compile commands. |
Sam McCall | 7ee0867 | 2019-07-26 14:07:11 +0000 | [diff] [blame] | 321 | if (ShouldBroadcast) |
Kadir Cetinkaya | ad54935 | 2019-07-11 09:54:31 +0000 | [diff] [blame] | 322 | broadcastCDB(Result); |
| 323 | return Result; |
| 324 | } |
| 325 | |
| 326 | void DirectoryBasedGlobalCompilationDatabase::broadcastCDB( |
| 327 | CDBLookupResult Result) const { |
| 328 | assert(Result.CDB && "Trying to broadcast an invalid CDB!"); |
| 329 | |
| 330 | std::vector<std::string> AllFiles = Result.CDB->getAllFiles(); |
| 331 | // We assume CDB in CompileCommandsDir owns all of its entries, since we don't |
| 332 | // perform any search in parent paths whenever it is set. |
Sam McCall | 2bebc3d | 2018-11-20 10:56:03 +0000 | [diff] [blame] | 333 | if (CompileCommandsDir) { |
Kadir Cetinkaya | ad54935 | 2019-07-11 09:54:31 +0000 | [diff] [blame] | 334 | assert(*CompileCommandsDir == Result.PI.SourceRoot && |
| 335 | "Trying to broadcast a CDB outside of CompileCommandsDir!"); |
| 336 | OnCommandChanged.broadcast(std::move(AllFiles)); |
| 337 | return; |
| 338 | } |
| 339 | |
| 340 | llvm::StringMap<bool> DirectoryHasCDB; |
| 341 | { |
| 342 | std::lock_guard<std::mutex> Lock(Mutex); |
| 343 | // Uniquify all parent directories of all files. |
| 344 | for (llvm::StringRef File : AllFiles) { |
| 345 | actOnAllParentDirectories(File, [&](PathRef Path) { |
| 346 | auto It = DirectoryHasCDB.try_emplace(Path); |
| 347 | // Already seen this path, and all of its parents. |
| 348 | if (!It.second) |
| 349 | return true; |
| 350 | |
Sam McCall | 7ee0867 | 2019-07-26 14:07:11 +0000 | [diff] [blame] | 351 | CachedCDB &Entry = getCDBInDirLocked(Path); |
| 352 | It.first->second = Entry.CDB != nullptr; |
| 353 | return pathEqual(Path, Result.PI.SourceRoot); |
Kadir Cetinkaya | ad54935 | 2019-07-11 09:54:31 +0000 | [diff] [blame] | 354 | }); |
Sam McCall | 2bebc3d | 2018-11-20 10:56:03 +0000 | [diff] [blame] | 355 | } |
| 356 | } |
Kadir Cetinkaya | ad54935 | 2019-07-11 09:54:31 +0000 | [diff] [blame] | 357 | |
| 358 | std::vector<std::string> GovernedFiles; |
| 359 | for (llvm::StringRef File : AllFiles) { |
| 360 | // A file is governed by this CDB if lookup for the file would find it. |
| 361 | // Independent of whether it has an entry for that file or not. |
| 362 | actOnAllParentDirectories(File, [&](PathRef Path) { |
| 363 | if (DirectoryHasCDB.lookup(Path)) { |
Sam McCall | 7ee0867 | 2019-07-26 14:07:11 +0000 | [diff] [blame] | 364 | if (pathEqual(Path, Result.PI.SourceRoot)) |
Kadir Cetinkaya | 6d53adf | 2019-07-18 16:13:23 +0000 | [diff] [blame] | 365 | // Make sure listeners always get a canonical path for the file. |
| 366 | GovernedFiles.push_back(removeDots(File)); |
Kadir Cetinkaya | ad54935 | 2019-07-11 09:54:31 +0000 | [diff] [blame] | 367 | // Stop as soon as we hit a CDB. |
| 368 | return true; |
| 369 | } |
| 370 | return false; |
| 371 | }); |
| 372 | } |
| 373 | |
| 374 | OnCommandChanged.broadcast(std::move(GovernedFiles)); |
| 375 | } |
| 376 | |
| 377 | llvm::Optional<ProjectInfo> |
| 378 | DirectoryBasedGlobalCompilationDatabase::getProjectInfo(PathRef File) const { |
| 379 | CDBLookupRequest Req; |
| 380 | Req.FileName = File; |
| 381 | Req.ShouldBroadcast = false; |
| 382 | auto Res = lookupCDB(Req); |
| 383 | if (!Res) |
| 384 | return llvm::None; |
| 385 | return Res->PI; |
Sam McCall | 2bebc3d | 2018-11-20 10:56:03 +0000 | [diff] [blame] | 386 | } |
| 387 | |
| 388 | OverlayCDB::OverlayCDB(const GlobalCompilationDatabase *Base, |
Kadir Cetinkaya | be6b35d | 2019-01-22 09:10:20 +0000 | [diff] [blame] | 389 | std::vector<std::string> FallbackFlags, |
| 390 | llvm::Optional<std::string> ResourceDir) |
| 391 | : Base(Base), ResourceDir(ResourceDir ? std::move(*ResourceDir) |
| 392 | : getStandardResourceDir()), |
| 393 | FallbackFlags(std::move(FallbackFlags)) { |
Sam McCall | 2bebc3d | 2018-11-20 10:56:03 +0000 | [diff] [blame] | 394 | if (Base) |
| 395 | BaseChanged = Base->watch([this](const std::vector<std::string> Changes) { |
| 396 | OnCommandChanged.broadcast(Changes); |
| 397 | }); |
Ilya Biryukov | 38d7977 | 2017-05-16 09:38:59 +0000 | [diff] [blame] | 398 | } |
Krasimir Georgiev | c2a16a3 | 2017-07-06 08:44:54 +0000 | [diff] [blame] | 399 | |
Ilya Biryukov | f2001aa | 2019-01-07 15:45:19 +0000 | [diff] [blame] | 400 | llvm::Optional<tooling::CompileCommand> |
Kadir Cetinkaya | ad54935 | 2019-07-11 09:54:31 +0000 | [diff] [blame] | 401 | OverlayCDB::getCompileCommand(PathRef File) const { |
Kadir Cetinkaya | be6b35d | 2019-01-22 09:10:20 +0000 | [diff] [blame] | 402 | llvm::Optional<tooling::CompileCommand> Cmd; |
Sam McCall | c55d09a | 2018-11-02 13:09:36 +0000 | [diff] [blame] | 403 | { |
| 404 | std::lock_guard<std::mutex> Lock(Mutex); |
Kadir Cetinkaya | 6d53adf | 2019-07-18 16:13:23 +0000 | [diff] [blame] | 405 | auto It = Commands.find(removeDots(File)); |
Kadir Cetinkaya | ad54935 | 2019-07-11 09:54:31 +0000 | [diff] [blame] | 406 | if (It != Commands.end()) |
Kadir Cetinkaya | be6b35d | 2019-01-22 09:10:20 +0000 | [diff] [blame] | 407 | Cmd = It->second; |
Sam McCall | c55d09a | 2018-11-02 13:09:36 +0000 | [diff] [blame] | 408 | } |
Kadir Cetinkaya | be6b35d | 2019-01-22 09:10:20 +0000 | [diff] [blame] | 409 | if (!Cmd && Base) |
Kadir Cetinkaya | ad54935 | 2019-07-11 09:54:31 +0000 | [diff] [blame] | 410 | Cmd = Base->getCompileCommand(File); |
Kadir Cetinkaya | be6b35d | 2019-01-22 09:10:20 +0000 | [diff] [blame] | 411 | if (!Cmd) |
| 412 | return llvm::None; |
| 413 | adjustArguments(*Cmd, ResourceDir); |
| 414 | return Cmd; |
Alex Lorenz | f808786 | 2018-08-01 17:39:29 +0000 | [diff] [blame] | 415 | } |
| 416 | |
Sam McCall | c55d09a | 2018-11-02 13:09:36 +0000 | [diff] [blame] | 417 | tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File) const { |
| 418 | auto Cmd = Base ? Base->getFallbackCommand(File) |
| 419 | : GlobalCompilationDatabase::getFallbackCommand(File); |
| 420 | std::lock_guard<std::mutex> Lock(Mutex); |
| 421 | Cmd.CommandLine.insert(Cmd.CommandLine.end(), FallbackFlags.begin(), |
| 422 | FallbackFlags.end()); |
Kadir Cetinkaya | a7f9f42 | 2019-06-04 13:38:36 +0000 | [diff] [blame] | 423 | adjustArguments(Cmd, ResourceDir); |
Sam McCall | c55d09a | 2018-11-02 13:09:36 +0000 | [diff] [blame] | 424 | return Cmd; |
| 425 | } |
| 426 | |
| 427 | void OverlayCDB::setCompileCommand( |
| 428 | PathRef File, llvm::Optional<tooling::CompileCommand> Cmd) { |
Kadir Cetinkaya | 6d53adf | 2019-07-18 16:13:23 +0000 | [diff] [blame] | 429 | // We store a canonical version internally to prevent mismatches between set |
| 430 | // and get compile commands. Also it assures clients listening to broadcasts |
| 431 | // doesn't receive different names for the same file. |
| 432 | std::string CanonPath = removeDots(File); |
Sam McCall | 2bebc3d | 2018-11-20 10:56:03 +0000 | [diff] [blame] | 433 | { |
| 434 | std::unique_lock<std::mutex> Lock(Mutex); |
| 435 | if (Cmd) |
Kadir Cetinkaya | 6d53adf | 2019-07-18 16:13:23 +0000 | [diff] [blame] | 436 | Commands[CanonPath] = std::move(*Cmd); |
Sam McCall | 2bebc3d | 2018-11-20 10:56:03 +0000 | [diff] [blame] | 437 | else |
Kadir Cetinkaya | 6d53adf | 2019-07-18 16:13:23 +0000 | [diff] [blame] | 438 | Commands.erase(CanonPath); |
Sam McCall | 2bebc3d | 2018-11-20 10:56:03 +0000 | [diff] [blame] | 439 | } |
Kadir Cetinkaya | 6d53adf | 2019-07-18 16:13:23 +0000 | [diff] [blame] | 440 | OnCommandChanged.broadcast({CanonPath}); |
Alex Lorenz | f808786 | 2018-08-01 17:39:29 +0000 | [diff] [blame] | 441 | } |
| 442 | |
Kadir Cetinkaya | ad54935 | 2019-07-11 09:54:31 +0000 | [diff] [blame] | 443 | llvm::Optional<ProjectInfo> OverlayCDB::getProjectInfo(PathRef File) const { |
| 444 | { |
| 445 | std::lock_guard<std::mutex> Lock(Mutex); |
Kadir Cetinkaya | 6d53adf | 2019-07-18 16:13:23 +0000 | [diff] [blame] | 446 | auto It = Commands.find(removeDots(File)); |
Kadir Cetinkaya | ad54935 | 2019-07-11 09:54:31 +0000 | [diff] [blame] | 447 | if (It != Commands.end()) |
| 448 | return ProjectInfo{}; |
| 449 | } |
| 450 | if (Base) |
| 451 | return Base->getProjectInfo(File); |
| 452 | |
| 453 | return llvm::None; |
| 454 | } |
Krasimir Georgiev | c2a16a3 | 2017-07-06 08:44:54 +0000 | [diff] [blame] | 455 | } // namespace clangd |
| 456 | } // namespace clang |