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