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