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 "Logger.h" 11 #include "clang/Frontend/CompilerInvocation.h" 12 #include "clang/Tooling/ArgumentsAdjusters.h" 13 #include "clang/Tooling/CompilationDatabase.h" 14 #include "llvm/ADT/Optional.h" 15 #include "llvm/Support/FileSystem.h" 16 #include "llvm/Support/Path.h" 17 18 namespace clang { 19 namespace clangd { 20 namespace { 21 22 void adjustArguments(tooling::CompileCommand &Cmd, 23 llvm::StringRef ResourceDir) { 24 // Strip plugin related command line arguments. Clangd does 25 // not support plugins currently. Therefore it breaks if 26 // compiler tries to load plugins. 27 Cmd.CommandLine = 28 tooling::getStripPluginsAdjuster()(Cmd.CommandLine, Cmd.Filename); 29 // Inject the resource dir. 30 // FIXME: Don't overwrite it if it's already there. 31 if (!ResourceDir.empty()) 32 Cmd.CommandLine.push_back(("-resource-dir=" + ResourceDir).str()); 33 } 34 35 std::string getStandardResourceDir() { 36 static int Dummy; // Just an address in this process. 37 return CompilerInvocation::GetResourcesPath("clangd", (void *)&Dummy); 38 } 39 40 } // namespace 41 42 static std::string getFallbackClangPath() { 43 static int Dummy; 44 std::string ClangdExecutable = 45 llvm::sys::fs::getMainExecutable("clangd", (void *)&Dummy); 46 SmallString<128> ClangPath; 47 ClangPath = llvm::sys::path::parent_path(ClangdExecutable); 48 llvm::sys::path::append(ClangPath, "clang"); 49 return ClangPath.str(); 50 } 51 52 tooling::CompileCommand 53 GlobalCompilationDatabase::getFallbackCommand(PathRef File) const { 54 std::vector<std::string> Argv = {getFallbackClangPath()}; 55 // Clang treats .h files as C by default, resulting in unhelpful diagnostics. 56 // Parsing as Objective C++ is friendly to more cases. 57 if (llvm::sys::path::extension(File) == ".h") 58 Argv.push_back("-xobjective-c++-header"); 59 Argv.push_back(File); 60 return tooling::CompileCommand(llvm::sys::path::parent_path(File), 61 llvm::sys::path::filename(File), 62 std::move(Argv), 63 /*Output=*/""); 64 } 65 66 DirectoryBasedGlobalCompilationDatabase:: 67 DirectoryBasedGlobalCompilationDatabase( 68 llvm::Optional<Path> CompileCommandsDir) 69 : CompileCommandsDir(std::move(CompileCommandsDir)) {} 70 71 DirectoryBasedGlobalCompilationDatabase:: 72 ~DirectoryBasedGlobalCompilationDatabase() = default; 73 74 llvm::Optional<tooling::CompileCommand> 75 DirectoryBasedGlobalCompilationDatabase::getCompileCommand( 76 PathRef File, ProjectInfo *Project) const { 77 if (auto CDB = getCDBForFile(File, Project)) { 78 auto Candidates = CDB->getCompileCommands(File); 79 if (!Candidates.empty()) { 80 return std::move(Candidates.front()); 81 } 82 } else { 83 log("Failed to find compilation database for {0}", File); 84 } 85 return None; 86 } 87 88 std::pair<tooling::CompilationDatabase *, /*Cached*/ bool> 89 DirectoryBasedGlobalCompilationDatabase::getCDBInDirLocked(PathRef Dir) const { 90 // FIXME(ibiryukov): Invalidate cached compilation databases on changes 91 auto CachedIt = CompilationDatabases.find(Dir); 92 if (CachedIt != CompilationDatabases.end()) 93 return {CachedIt->second.get(), true}; 94 std::string Error = ""; 95 auto CDB = tooling::CompilationDatabase::loadFromDirectory(Dir, Error); 96 auto Result = CDB.get(); 97 CompilationDatabases.insert(std::make_pair(Dir, std::move(CDB))); 98 return {Result, false}; 99 } 100 101 tooling::CompilationDatabase * 102 DirectoryBasedGlobalCompilationDatabase::getCDBForFile( 103 PathRef File, ProjectInfo *Project) const { 104 namespace path = llvm::sys::path; 105 assert((path::is_absolute(File, path::Style::posix) || 106 path::is_absolute(File, path::Style::windows)) && 107 "path must be absolute"); 108 109 tooling::CompilationDatabase *CDB = nullptr; 110 bool Cached = false; 111 std::lock_guard<std::mutex> Lock(Mutex); 112 if (CompileCommandsDir) { 113 std::tie(CDB, Cached) = getCDBInDirLocked(*CompileCommandsDir); 114 if (Project && CDB) 115 Project->SourceRoot = *CompileCommandsDir; 116 } else { 117 for (auto Path = path::parent_path(File); !CDB && !Path.empty(); 118 Path = path::parent_path(Path)) { 119 std::tie(CDB, Cached) = getCDBInDirLocked(Path); 120 if (Project && CDB) 121 Project->SourceRoot = Path; 122 } 123 } 124 // FIXME: getAllFiles() may return relative paths, we need absolute paths. 125 // Hopefully the fix is to change JSONCompilationDatabase and the interface. 126 if (CDB && !Cached) 127 OnCommandChanged.broadcast(CDB->getAllFiles()); 128 return CDB; 129 } 130 131 OverlayCDB::OverlayCDB(const GlobalCompilationDatabase *Base, 132 std::vector<std::string> FallbackFlags, 133 llvm::Optional<std::string> ResourceDir) 134 : Base(Base), ResourceDir(ResourceDir ? std::move(*ResourceDir) 135 : getStandardResourceDir()), 136 FallbackFlags(std::move(FallbackFlags)) { 137 if (Base) 138 BaseChanged = Base->watch([this](const std::vector<std::string> Changes) { 139 OnCommandChanged.broadcast(Changes); 140 }); 141 } 142 143 llvm::Optional<tooling::CompileCommand> 144 OverlayCDB::getCompileCommand(PathRef File, ProjectInfo *Project) const { 145 llvm::Optional<tooling::CompileCommand> Cmd; 146 { 147 std::lock_guard<std::mutex> Lock(Mutex); 148 auto It = Commands.find(File); 149 if (It != Commands.end()) { 150 if (Project) 151 Project->SourceRoot = ""; 152 Cmd = It->second; 153 } 154 } 155 if (!Cmd && Base) 156 Cmd = Base->getCompileCommand(File, Project); 157 if (!Cmd) 158 return llvm::None; 159 adjustArguments(*Cmd, ResourceDir); 160 return Cmd; 161 } 162 163 tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File) const { 164 auto Cmd = Base ? Base->getFallbackCommand(File) 165 : GlobalCompilationDatabase::getFallbackCommand(File); 166 std::lock_guard<std::mutex> Lock(Mutex); 167 Cmd.CommandLine.insert(Cmd.CommandLine.end(), FallbackFlags.begin(), 168 FallbackFlags.end()); 169 return Cmd; 170 } 171 172 void OverlayCDB::setCompileCommand( 173 PathRef File, llvm::Optional<tooling::CompileCommand> Cmd) { 174 { 175 std::unique_lock<std::mutex> Lock(Mutex); 176 if (Cmd) 177 Commands[File] = std::move(*Cmd); 178 else 179 Commands.erase(File); 180 } 181 OnCommandChanged.broadcast({File}); 182 } 183 184 } // namespace clangd 185 } // namespace clang 186