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 "support/Logger.h" 12 #include "support/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 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; 117 std::string Error; 118 Entry.Path = std::string(Dir); 119 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 } 131 if (Entry.CDB) 132 log("Loaded compilation database from {0}", Dir); 133 } 134 return R.first->second; 135 } 136 137 llvm::Optional<DirectoryBasedGlobalCompilationDatabase::CDBLookupResult> 138 DirectoryBasedGlobalCompilationDatabase::lookupCDB( 139 CDBLookupRequest Request) const { 140 assert(llvm::sys::path::is_absolute(Request.FileName) && 141 "path must be absolute"); 142 143 bool ShouldBroadcast = false; 144 CDBLookupResult Result; 145 146 { 147 std::lock_guard<std::mutex> Lock(Mutex); 148 CachedCDB *Entry = nullptr; 149 if (CompileCommandsDir) { 150 Entry = &getCDBInDirLocked(*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 // FIXME(sammccall): this loop is hot, use a union-find-like structure. 155 actOnAllParentDirectories(removeDots(Request.FileName), 156 [&](PathRef Path) { 157 Entry = &getCDBInDirLocked(Path); 158 return Entry->CDB != nullptr; 159 }); 160 } 161 162 if (!Entry || !Entry->CDB) 163 return llvm::None; 164 165 // Mark CDB as broadcasted to make sure discovery is performed once. 166 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; 173 } 174 175 // FIXME: Maybe make the following part async, since this can block retrieval 176 // of compile commands. 177 if (ShouldBroadcast) 178 broadcastCDB(Result); 179 return Result; 180 } 181 182 void 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. 189 if (CompileCommandsDir) { 190 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 207 CachedCDB &Entry = getCDBInDirLocked(Path); 208 It.first->second = Entry.CDB != nullptr; 209 return pathEqual(Path, Result.PI.SourceRoot); 210 }); 211 } 212 } 213 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)) { 220 if (pathEqual(Path, Result.PI.SourceRoot)) 221 // Make sure listeners always get a canonical path for the file. 222 GovernedFiles.push_back(removeDots(File)); 223 // 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 233 llvm::Optional<ProjectInfo> 234 DirectoryBasedGlobalCompilationDatabase::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; 242 } 243 244 OverlayCDB::OverlayCDB(const GlobalCompilationDatabase *Base, 245 std::vector<std::string> FallbackFlags, 246 tooling::ArgumentsAdjuster Adjuster) 247 : Base(Base), ArgsAdjuster(std::move(Adjuster)), 248 FallbackFlags(std::move(FallbackFlags)) { 249 if (Base) 250 BaseChanged = Base->watch([this](const std::vector<std::string> Changes) { 251 OnCommandChanged.broadcast(Changes); 252 }); 253 } 254 255 llvm::Optional<tooling::CompileCommand> 256 OverlayCDB::getCompileCommand(PathRef File) const { 257 llvm::Optional<tooling::CompileCommand> Cmd; 258 { 259 std::lock_guard<std::mutex> Lock(Mutex); 260 auto It = Commands.find(removeDots(File)); 261 if (It != Commands.end()) 262 Cmd = It->second; 263 } 264 if (!Cmd && Base) 265 Cmd = Base->getCompileCommand(File); 266 if (!Cmd) 267 return llvm::None; 268 if (ArgsAdjuster) 269 Cmd->CommandLine = ArgsAdjuster(Cmd->CommandLine, Cmd->Filename); 270 return Cmd; 271 } 272 273 tooling::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()); 279 if (ArgsAdjuster) 280 Cmd.CommandLine = ArgsAdjuster(Cmd.CommandLine, Cmd.Filename); 281 return Cmd; 282 } 283 284 void OverlayCDB::setCompileCommand( 285 PathRef File, llvm::Optional<tooling::CompileCommand> Cmd) { 286 // 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); 290 { 291 std::unique_lock<std::mutex> Lock(Mutex); 292 if (Cmd) 293 Commands[CanonPath] = std::move(*Cmd); 294 else 295 Commands.erase(CanonPath); 296 } 297 OnCommandChanged.broadcast({CanonPath}); 298 } 299 300 llvm::Optional<ProjectInfo> OverlayCDB::getProjectInfo(PathRef File) const { 301 // It wouldn't make much sense to treat files with overridden commands 302 // specially when we can't do the same for the (unknown) local headers they 303 // include or changing behavior mid-air after receiving an override. 304 if (Base) 305 return Base->getProjectInfo(File); 306 return llvm::None; 307 } 308 } // namespace clangd 309 } // namespace clang 310