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