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