1 //===--- GlobalCompilationDatabase.cpp ---------------------------*- C++-*-===// 2 // 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 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "GlobalCompilationDatabase.h" 10 #include "FS.h" 11 #include "Logger.h" 12 #include "Path.h" 13 #include "clang/Frontend/CompilerInvocation.h" 14 #include "clang/Tooling/ArgumentsAdjusters.h" 15 #include "clang/Tooling/CompilationDatabase.h" 16 #include "llvm/ADT/None.h" 17 #include "llvm/ADT/Optional.h" 18 #include "llvm/ADT/STLExtras.h" 19 #include "llvm/ADT/SmallString.h" 20 #include "llvm/Support/FileSystem.h" 21 #include "llvm/Support/Path.h" 22 #include <string> 23 #include <tuple> 24 #include <vector> 25 26 namespace clang { 27 namespace clangd { 28 namespace { 29 30 void adjustArguments(tooling::CompileCommand &Cmd, 31 llvm::StringRef ResourceDir) { 32 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); 43 // 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 49 std::string getStandardResourceDir() { 50 static int Dummy; // Just an address in this process. 51 return CompilerInvocation::GetResourcesPath("clangd", (void *)&Dummy); 52 } 53 54 // Runs the given action on all parent directories of filename, starting from 55 // deepest directory and going up to root. Stops whenever action succeeds. 56 void 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 64 } // namespace 65 66 static 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 76 tooling::CompileCommand 77 GlobalCompilationDatabase::getFallbackCommand(PathRef File) const { 78 std::vector<std::string> Argv = {getFallbackClangPath()}; 79 // Clang treats .h files as C by default and files without extension as linker 80 // input, resulting in unhelpful diagnostics. 81 // Parsing as Objective C++ is friendly to more cases. 82 auto FileExtension = llvm::sys::path::extension(File); 83 if (FileExtension.empty() || FileExtension == ".h") 84 Argv.push_back("-xobjective-c++-header"); 85 Argv.push_back(File); 86 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; 91 } 92 93 DirectoryBasedGlobalCompilationDatabase:: 94 DirectoryBasedGlobalCompilationDatabase( 95 llvm::Optional<Path> CompileCommandsDir) 96 : CompileCommandsDir(std::move(CompileCommandsDir)) {} 97 98 DirectoryBasedGlobalCompilationDatabase:: 99 ~DirectoryBasedGlobalCompilationDatabase() = default; 100 101 llvm::Optional<tooling::CompileCommand> 102 DirectoryBasedGlobalCompilationDatabase::getCompileCommand(PathRef File) const { 103 CDBLookupRequest Req; 104 Req.FileName = File; 105 Req.ShouldBroadcast = true; 106 107 auto Res = lookupCDB(Req); 108 if (!Res) { 109 log("Failed to find compilation database for {0}", File); 110 return llvm::None; 111 } 112 113 auto Candidates = Res->CDB->getCompileCommands(File); 114 if (!Candidates.empty()) 115 return std::move(Candidates.front()); 116 117 return None; 118 } 119 120 std::pair<tooling::CompilationDatabase *, /*SentBroadcast*/ bool> 121 DirectoryBasedGlobalCompilationDatabase::getCDBInDirLocked(PathRef Dir) const { 122 // FIXME(ibiryukov): Invalidate cached compilation databases on changes 123 auto CachedIt = CompilationDatabases.find(Dir); 124 if (CachedIt != CompilationDatabases.end()) 125 return {CachedIt->second.CDB.get(), CachedIt->second.SentBroadcast}; 126 std::string Error = ""; 127 128 CachedCDB Entry; 129 Entry.CDB = tooling::CompilationDatabase::loadFromDirectory(Dir, Error); 130 auto Result = Entry.CDB.get(); 131 CompilationDatabases[Dir] = std::move(Entry); 132 133 return {Result, false}; 134 } 135 136 llvm::Optional<DirectoryBasedGlobalCompilationDatabase::CDBLookupResult> 137 DirectoryBasedGlobalCompilationDatabase::lookupCDB( 138 CDBLookupRequest Request) const { 139 assert(llvm::sys::path::is_absolute(Request.FileName) && 140 "path must be absolute"); 141 142 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 { 152 // 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 }); 161 } 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 178 void 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. 185 if (CompileCommandsDir) { 186 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 }); 207 } 208 } 209 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) 217 // Make sure listeners always get a canonical path for the file. 218 GovernedFiles.push_back(removeDots(File)); 219 // 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 229 llvm::Optional<ProjectInfo> 230 DirectoryBasedGlobalCompilationDatabase::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; 238 } 239 240 OverlayCDB::OverlayCDB(const GlobalCompilationDatabase *Base, 241 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)) { 246 if (Base) 247 BaseChanged = Base->watch([this](const std::vector<std::string> Changes) { 248 OnCommandChanged.broadcast(Changes); 249 }); 250 } 251 252 llvm::Optional<tooling::CompileCommand> 253 OverlayCDB::getCompileCommand(PathRef File) const { 254 llvm::Optional<tooling::CompileCommand> Cmd; 255 { 256 std::lock_guard<std::mutex> Lock(Mutex); 257 auto It = Commands.find(removeDots(File)); 258 if (It != Commands.end()) 259 Cmd = It->second; 260 } 261 if (!Cmd && Base) 262 Cmd = Base->getCompileCommand(File); 263 if (!Cmd) 264 return llvm::None; 265 adjustArguments(*Cmd, ResourceDir); 266 return Cmd; 267 } 268 269 tooling::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()); 275 adjustArguments(Cmd, ResourceDir); 276 return Cmd; 277 } 278 279 void OverlayCDB::setCompileCommand( 280 PathRef File, llvm::Optional<tooling::CompileCommand> Cmd) { 281 // 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); 285 { 286 std::unique_lock<std::mutex> Lock(Mutex); 287 if (Cmd) 288 Commands[CanonPath] = std::move(*Cmd); 289 else 290 Commands.erase(CanonPath); 291 } 292 OnCommandChanged.broadcast({CanonPath}); 293 } 294 295 llvm::Optional<ProjectInfo> OverlayCDB::getProjectInfo(PathRef File) const { 296 { 297 std::lock_guard<std::mutex> Lock(Mutex); 298 auto It = Commands.find(removeDots(File)); 299 if (It != Commands.end()) 300 return ProjectInfo{}; 301 } 302 if (Base) 303 return Base->getProjectInfo(File); 304 305 return llvm::None; 306 } 307 } // namespace clangd 308 } // namespace clang 309