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/FileUtilities.h" 22 #include "llvm/Support/Path.h" 23 #include "llvm/Support/Program.h" 24 #include <string> 25 #include <tuple> 26 #include <vector> 27 28 namespace clang { 29 namespace clangd { 30 namespace { 31 32 // Runs the given action on all parent directories of filename, starting from 33 // deepest directory and going up to root. Stops whenever action succeeds. 34 void 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 42 } // namespace 43 44 tooling::CompileCommand 45 GlobalCompilationDatabase::getFallbackCommand(PathRef File) const { 46 std::vector<std::string> Argv = {"clang"}; 47 // Clang treats .h files as C by default and files without extension as linker 48 // input, resulting in unhelpful diagnostics. 49 // Parsing as Objective C++ is friendly to more cases. 50 auto FileExtension = llvm::sys::path::extension(File); 51 if (FileExtension.empty() || FileExtension == ".h") 52 Argv.push_back("-xobjective-c++-header"); 53 Argv.push_back(File); 54 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; 59 } 60 61 DirectoryBasedGlobalCompilationDatabase:: 62 DirectoryBasedGlobalCompilationDatabase( 63 llvm::Optional<Path> CompileCommandsDir) 64 : CompileCommandsDir(std::move(CompileCommandsDir)) {} 65 66 DirectoryBasedGlobalCompilationDatabase:: 67 ~DirectoryBasedGlobalCompilationDatabase() = default; 68 69 llvm::Optional<tooling::CompileCommand> 70 DirectoryBasedGlobalCompilationDatabase::getCompileCommand(PathRef File) const { 71 CDBLookupRequest Req; 72 Req.FileName = File; 73 Req.ShouldBroadcast = true; 74 75 auto Res = lookupCDB(Req); 76 if (!Res) { 77 log("Failed to find compilation database for {0}", File); 78 return llvm::None; 79 } 80 81 auto Candidates = Res->CDB->getCompileCommands(File); 82 if (!Candidates.empty()) 83 return std::move(Candidates.front()); 84 85 return None; 86 } 87 88 // 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 94 static std::string maybeCaseFoldPath(PathRef Path) { 95 #if defined(_WIN32) || defined(__APPLE__) 96 return Path.lower(); 97 #else 98 return Path; 99 #endif 100 } 101 102 static 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 110 DirectoryBasedGlobalCompilationDatabase::CachedCDB & 111 DirectoryBasedGlobalCompilationDatabase::getCDBInDirLocked(PathRef Dir) const { 112 // FIXME(ibiryukov): Invalidate cached compilation databases on changes 113 // 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; 118 std::string Error = ""; 119 Entry.CDB = tooling::CompilationDatabase::loadFromDirectory(Dir, Error); 120 Entry.Path = Dir; 121 } 122 return R.first->second; 123 } 124 125 llvm::Optional<DirectoryBasedGlobalCompilationDatabase::CDBLookupResult> 126 DirectoryBasedGlobalCompilationDatabase::lookupCDB( 127 CDBLookupRequest Request) const { 128 assert(llvm::sys::path::is_absolute(Request.FileName) && 129 "path must be absolute"); 130 131 bool ShouldBroadcast = false; 132 CDBLookupResult Result; 133 134 { 135 std::lock_guard<std::mutex> Lock(Mutex); 136 CachedCDB *Entry = nullptr; 137 if (CompileCommandsDir) { 138 Entry = &getCDBInDirLocked(*CompileCommandsDir); 139 } else { 140 // Traverse the canonical version to prevent false positives. i.e.: 141 // src/build/../a.cc can detect a CDB in /src/build if not canonicalized. 142 // FIXME(sammccall): this loop is hot, use a union-find-like structure. 143 actOnAllParentDirectories(removeDots(Request.FileName), 144 [&](PathRef Path) { 145 Entry = &getCDBInDirLocked(Path); 146 return Entry->CDB != nullptr; 147 }); 148 } 149 150 if (!Entry || !Entry->CDB) 151 return llvm::None; 152 153 // Mark CDB as broadcasted to make sure discovery is performed once. 154 if (Request.ShouldBroadcast && !Entry->SentBroadcast) { 155 Entry->SentBroadcast = true; 156 ShouldBroadcast = true; 157 } 158 159 Result.CDB = Entry->CDB.get(); 160 Result.PI.SourceRoot = Entry->Path; 161 } 162 163 // FIXME: Maybe make the following part async, since this can block retrieval 164 // of compile commands. 165 if (ShouldBroadcast) 166 broadcastCDB(Result); 167 return Result; 168 } 169 170 void DirectoryBasedGlobalCompilationDatabase::broadcastCDB( 171 CDBLookupResult Result) const { 172 assert(Result.CDB && "Trying to broadcast an invalid CDB!"); 173 174 std::vector<std::string> AllFiles = Result.CDB->getAllFiles(); 175 // We assume CDB in CompileCommandsDir owns all of its entries, since we don't 176 // perform any search in parent paths whenever it is set. 177 if (CompileCommandsDir) { 178 assert(*CompileCommandsDir == Result.PI.SourceRoot && 179 "Trying to broadcast a CDB outside of CompileCommandsDir!"); 180 OnCommandChanged.broadcast(std::move(AllFiles)); 181 return; 182 } 183 184 llvm::StringMap<bool> DirectoryHasCDB; 185 { 186 std::lock_guard<std::mutex> Lock(Mutex); 187 // Uniquify all parent directories of all files. 188 for (llvm::StringRef File : AllFiles) { 189 actOnAllParentDirectories(File, [&](PathRef Path) { 190 auto It = DirectoryHasCDB.try_emplace(Path); 191 // Already seen this path, and all of its parents. 192 if (!It.second) 193 return true; 194 195 CachedCDB &Entry = getCDBInDirLocked(Path); 196 It.first->second = Entry.CDB != nullptr; 197 return pathEqual(Path, Result.PI.SourceRoot); 198 }); 199 } 200 } 201 202 std::vector<std::string> GovernedFiles; 203 for (llvm::StringRef File : AllFiles) { 204 // A file is governed by this CDB if lookup for the file would find it. 205 // Independent of whether it has an entry for that file or not. 206 actOnAllParentDirectories(File, [&](PathRef Path) { 207 if (DirectoryHasCDB.lookup(Path)) { 208 if (pathEqual(Path, Result.PI.SourceRoot)) 209 // Make sure listeners always get a canonical path for the file. 210 GovernedFiles.push_back(removeDots(File)); 211 // Stop as soon as we hit a CDB. 212 return true; 213 } 214 return false; 215 }); 216 } 217 218 OnCommandChanged.broadcast(std::move(GovernedFiles)); 219 } 220 221 llvm::Optional<ProjectInfo> 222 DirectoryBasedGlobalCompilationDatabase::getProjectInfo(PathRef File) const { 223 CDBLookupRequest Req; 224 Req.FileName = File; 225 Req.ShouldBroadcast = false; 226 auto Res = lookupCDB(Req); 227 if (!Res) 228 return llvm::None; 229 return Res->PI; 230 } 231 232 OverlayCDB::OverlayCDB(const GlobalCompilationDatabase *Base, 233 std::vector<std::string> FallbackFlags, 234 tooling::ArgumentsAdjuster Adjuster) 235 : Base(Base), ArgsAdjuster(std::move(Adjuster)), 236 FallbackFlags(std::move(FallbackFlags)) { 237 if (Base) 238 BaseChanged = Base->watch([this](const std::vector<std::string> Changes) { 239 OnCommandChanged.broadcast(Changes); 240 }); 241 } 242 243 llvm::Optional<tooling::CompileCommand> 244 OverlayCDB::getCompileCommand(PathRef File) const { 245 llvm::Optional<tooling::CompileCommand> Cmd; 246 { 247 std::lock_guard<std::mutex> Lock(Mutex); 248 auto It = Commands.find(removeDots(File)); 249 if (It != Commands.end()) 250 Cmd = It->second; 251 } 252 if (!Cmd && Base) 253 Cmd = Base->getCompileCommand(File); 254 if (!Cmd) 255 return llvm::None; 256 if (ArgsAdjuster) 257 Cmd->CommandLine = ArgsAdjuster(Cmd->CommandLine, Cmd->Filename); 258 return Cmd; 259 } 260 261 tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File) const { 262 auto Cmd = Base ? Base->getFallbackCommand(File) 263 : GlobalCompilationDatabase::getFallbackCommand(File); 264 std::lock_guard<std::mutex> Lock(Mutex); 265 Cmd.CommandLine.insert(Cmd.CommandLine.end(), FallbackFlags.begin(), 266 FallbackFlags.end()); 267 if (ArgsAdjuster) 268 Cmd.CommandLine = ArgsAdjuster(Cmd.CommandLine, Cmd.Filename); 269 return Cmd; 270 } 271 272 void OverlayCDB::setCompileCommand( 273 PathRef File, llvm::Optional<tooling::CompileCommand> Cmd) { 274 // We store a canonical version internally to prevent mismatches between set 275 // and get compile commands. Also it assures clients listening to broadcasts 276 // doesn't receive different names for the same file. 277 std::string CanonPath = removeDots(File); 278 { 279 std::unique_lock<std::mutex> Lock(Mutex); 280 if (Cmd) 281 Commands[CanonPath] = std::move(*Cmd); 282 else 283 Commands.erase(CanonPath); 284 } 285 OnCommandChanged.broadcast({CanonPath}); 286 } 287 288 llvm::Optional<ProjectInfo> OverlayCDB::getProjectInfo(PathRef File) const { 289 { 290 std::lock_guard<std::mutex> Lock(Mutex); 291 auto It = Commands.find(removeDots(File)); 292 if (It != Commands.end()) 293 return ProjectInfo{}; 294 } 295 if (Base) 296 return Base->getProjectInfo(File); 297 298 return llvm::None; 299 } 300 } // namespace clangd 301 } // namespace clang 302