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(std::string(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 std::string(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 = std::string(Dir); 121 if (Entry.CDB) 122 log("Loaded compilation database from {0}", Dir); 123 } 124 return R.first->second; 125 } 126 127 llvm::Optional<DirectoryBasedGlobalCompilationDatabase::CDBLookupResult> 128 DirectoryBasedGlobalCompilationDatabase::lookupCDB( 129 CDBLookupRequest Request) const { 130 assert(llvm::sys::path::is_absolute(Request.FileName) && 131 "path must be absolute"); 132 133 bool ShouldBroadcast = false; 134 CDBLookupResult Result; 135 136 { 137 std::lock_guard<std::mutex> Lock(Mutex); 138 CachedCDB *Entry = nullptr; 139 if (CompileCommandsDir) { 140 Entry = &getCDBInDirLocked(*CompileCommandsDir); 141 } else { 142 // 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. 144 // FIXME(sammccall): this loop is hot, use a union-find-like structure. 145 actOnAllParentDirectories(removeDots(Request.FileName), 146 [&](PathRef Path) { 147 Entry = &getCDBInDirLocked(Path); 148 return Entry->CDB != nullptr; 149 }); 150 } 151 152 if (!Entry || !Entry->CDB) 153 return llvm::None; 154 155 // Mark CDB as broadcasted to make sure discovery is performed once. 156 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; 163 } 164 165 // FIXME: Maybe make the following part async, since this can block retrieval 166 // of compile commands. 167 if (ShouldBroadcast) 168 broadcastCDB(Result); 169 return Result; 170 } 171 172 void 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. 179 if (CompileCommandsDir) { 180 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 197 CachedCDB &Entry = getCDBInDirLocked(Path); 198 It.first->second = Entry.CDB != nullptr; 199 return pathEqual(Path, Result.PI.SourceRoot); 200 }); 201 } 202 } 203 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)) { 210 if (pathEqual(Path, Result.PI.SourceRoot)) 211 // Make sure listeners always get a canonical path for the file. 212 GovernedFiles.push_back(removeDots(File)); 213 // 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 223 llvm::Optional<ProjectInfo> 224 DirectoryBasedGlobalCompilationDatabase::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; 232 } 233 234 OverlayCDB::OverlayCDB(const GlobalCompilationDatabase *Base, 235 std::vector<std::string> FallbackFlags, 236 tooling::ArgumentsAdjuster Adjuster) 237 : Base(Base), ArgsAdjuster(std::move(Adjuster)), 238 FallbackFlags(std::move(FallbackFlags)) { 239 if (Base) 240 BaseChanged = Base->watch([this](const std::vector<std::string> Changes) { 241 OnCommandChanged.broadcast(Changes); 242 }); 243 } 244 245 llvm::Optional<tooling::CompileCommand> 246 OverlayCDB::getCompileCommand(PathRef File) const { 247 llvm::Optional<tooling::CompileCommand> Cmd; 248 { 249 std::lock_guard<std::mutex> Lock(Mutex); 250 auto It = Commands.find(removeDots(File)); 251 if (It != Commands.end()) 252 Cmd = It->second; 253 } 254 if (!Cmd && Base) 255 Cmd = Base->getCompileCommand(File); 256 if (!Cmd) 257 return llvm::None; 258 if (ArgsAdjuster) 259 Cmd->CommandLine = ArgsAdjuster(Cmd->CommandLine, Cmd->Filename); 260 return Cmd; 261 } 262 263 tooling::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()); 269 if (ArgsAdjuster) 270 Cmd.CommandLine = ArgsAdjuster(Cmd.CommandLine, Cmd.Filename); 271 return Cmd; 272 } 273 274 void OverlayCDB::setCompileCommand( 275 PathRef File, llvm::Optional<tooling::CompileCommand> Cmd) { 276 // 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); 280 { 281 std::unique_lock<std::mutex> Lock(Mutex); 282 if (Cmd) 283 Commands[CanonPath] = std::move(*Cmd); 284 else 285 Commands.erase(CanonPath); 286 } 287 OnCommandChanged.broadcast({CanonPath}); 288 } 289 290 llvm::Optional<ProjectInfo> OverlayCDB::getProjectInfo(PathRef File) const { 291 { 292 std::lock_guard<std::mutex> Lock(Mutex); 293 auto It = Commands.find(removeDots(File)); 294 if (It != Commands.end()) 295 return ProjectInfo{}; 296 } 297 if (Base) 298 return Base->getProjectInfo(File); 299 300 return llvm::None; 301 } 302 } // namespace clangd 303 } // namespace clang 304