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, resulting in unhelpful diagnostics. 62 // Parsing as Objective C++ is friendly to more cases. 63 if (llvm::sys::path::extension(File) == ".h") 64 Argv.push_back("-xobjective-c++-header"); 65 Argv.push_back(File); 66 tooling::CompileCommand Cmd(llvm::sys::path::parent_path(File), 67 llvm::sys::path::filename(File), std::move(Argv), 68 /*Output=*/""); 69 Cmd.Heuristic = "clangd fallback"; 70 return Cmd; 71 } 72 73 DirectoryBasedGlobalCompilationDatabase:: 74 DirectoryBasedGlobalCompilationDatabase( 75 llvm::Optional<Path> CompileCommandsDir) 76 : CompileCommandsDir(std::move(CompileCommandsDir)) {} 77 78 DirectoryBasedGlobalCompilationDatabase:: 79 ~DirectoryBasedGlobalCompilationDatabase() = default; 80 81 llvm::Optional<tooling::CompileCommand> 82 DirectoryBasedGlobalCompilationDatabase::getCompileCommand( 83 PathRef File, ProjectInfo *Project) const { 84 if (auto CDB = getCDBForFile(File, Project)) { 85 auto Candidates = CDB->getCompileCommands(File); 86 if (!Candidates.empty()) { 87 return std::move(Candidates.front()); 88 } 89 } else { 90 log("Failed to find compilation database for {0}", File); 91 } 92 return None; 93 } 94 95 std::pair<tooling::CompilationDatabase *, /*Cached*/ bool> 96 DirectoryBasedGlobalCompilationDatabase::getCDBInDirLocked(PathRef Dir) const { 97 // FIXME(ibiryukov): Invalidate cached compilation databases on changes 98 auto CachedIt = CompilationDatabases.find(Dir); 99 if (CachedIt != CompilationDatabases.end()) 100 return {CachedIt->second.get(), true}; 101 std::string Error = ""; 102 auto CDB = tooling::CompilationDatabase::loadFromDirectory(Dir, Error); 103 auto Result = CDB.get(); 104 CompilationDatabases.insert(std::make_pair(Dir, std::move(CDB))); 105 return {Result, false}; 106 } 107 108 tooling::CompilationDatabase * 109 DirectoryBasedGlobalCompilationDatabase::getCDBForFile( 110 PathRef File, ProjectInfo *Project) const { 111 namespace path = llvm::sys::path; 112 assert((path::is_absolute(File, path::Style::posix) || 113 path::is_absolute(File, path::Style::windows)) && 114 "path must be absolute"); 115 116 tooling::CompilationDatabase *CDB = nullptr; 117 bool Cached = false; 118 std::lock_guard<std::mutex> Lock(Mutex); 119 if (CompileCommandsDir) { 120 std::tie(CDB, Cached) = getCDBInDirLocked(*CompileCommandsDir); 121 if (Project && CDB) 122 Project->SourceRoot = *CompileCommandsDir; 123 } else { 124 for (auto Path = path::parent_path(File); !CDB && !Path.empty(); 125 Path = path::parent_path(Path)) { 126 std::tie(CDB, Cached) = getCDBInDirLocked(Path); 127 if (Project && CDB) 128 Project->SourceRoot = Path; 129 } 130 } 131 // FIXME: getAllFiles() may return relative paths, we need absolute paths. 132 // Hopefully the fix is to change JSONCompilationDatabase and the interface. 133 if (CDB && !Cached) 134 OnCommandChanged.broadcast(CDB->getAllFiles()); 135 return CDB; 136 } 137 138 OverlayCDB::OverlayCDB(const GlobalCompilationDatabase *Base, 139 std::vector<std::string> FallbackFlags, 140 llvm::Optional<std::string> ResourceDir) 141 : Base(Base), ResourceDir(ResourceDir ? std::move(*ResourceDir) 142 : getStandardResourceDir()), 143 FallbackFlags(std::move(FallbackFlags)) { 144 if (Base) 145 BaseChanged = Base->watch([this](const std::vector<std::string> Changes) { 146 OnCommandChanged.broadcast(Changes); 147 }); 148 } 149 150 llvm::Optional<tooling::CompileCommand> 151 OverlayCDB::getCompileCommand(PathRef File, ProjectInfo *Project) const { 152 llvm::Optional<tooling::CompileCommand> Cmd; 153 { 154 std::lock_guard<std::mutex> Lock(Mutex); 155 auto It = Commands.find(File); 156 if (It != Commands.end()) { 157 if (Project) 158 Project->SourceRoot = ""; 159 Cmd = It->second; 160 } 161 } 162 if (!Cmd && Base) 163 Cmd = Base->getCompileCommand(File, Project); 164 if (!Cmd) 165 return llvm::None; 166 adjustArguments(*Cmd, ResourceDir); 167 return Cmd; 168 } 169 170 tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File) const { 171 auto Cmd = Base ? Base->getFallbackCommand(File) 172 : GlobalCompilationDatabase::getFallbackCommand(File); 173 std::lock_guard<std::mutex> Lock(Mutex); 174 Cmd.CommandLine.insert(Cmd.CommandLine.end(), FallbackFlags.begin(), 175 FallbackFlags.end()); 176 return Cmd; 177 } 178 179 void OverlayCDB::setCompileCommand( 180 PathRef File, llvm::Optional<tooling::CompileCommand> Cmd) { 181 { 182 std::unique_lock<std::mutex> Lock(Mutex); 183 if (Cmd) 184 Commands[File] = std::move(*Cmd); 185 else 186 Commands.erase(File); 187 } 188 OnCommandChanged.broadcast({File}); 189 } 190 191 } // namespace clangd 192 } // namespace clang 193