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