1 //===- DependencyScanningTool.cpp - clang-scan-deps service ---------------===//
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 "clang/Tooling/DependencyScanning/DependencyScanningTool.h"
10 #include "clang/Frontend/Utils.h"
11 #include <optional>
12
13 using namespace clang;
14 using namespace tooling;
15 using namespace dependencies;
16
17 static std::vector<std::string>
makeTUCommandLineWithoutPaths(ArrayRef<std::string> OriginalCommandLine)18 makeTUCommandLineWithoutPaths(ArrayRef<std::string> OriginalCommandLine) {
19 std::vector<std::string> Args = OriginalCommandLine;
20
21 Args.push_back("-fno-implicit-modules");
22 Args.push_back("-fno-implicit-module-maps");
23
24 // These arguments are unused in explicit compiles.
25 llvm::erase_if(Args, [](StringRef Arg) {
26 if (Arg.consume_front("-fmodules-")) {
27 return Arg.startswith("cache-path=") ||
28 Arg.startswith("prune-interval=") ||
29 Arg.startswith("prune-after=") ||
30 Arg == "validate-once-per-build-session";
31 }
32 return Arg.startswith("-fbuild-session-file=");
33 });
34
35 return Args;
36 }
37
DependencyScanningTool(DependencyScanningService & Service,llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)38 DependencyScanningTool::DependencyScanningTool(
39 DependencyScanningService &Service,
40 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
41 : Worker(Service, std::move(FS)) {}
42
43 namespace {
44 /// Prints out all of the gathered dependencies into a string.
45 class MakeDependencyPrinterConsumer : public DependencyConsumer {
46 public:
handleBuildCommand(Command)47 void handleBuildCommand(Command) override {}
48
49 void
handleDependencyOutputOpts(const DependencyOutputOptions & Opts)50 handleDependencyOutputOpts(const DependencyOutputOptions &Opts) override {
51 this->Opts = std::make_unique<DependencyOutputOptions>(Opts);
52 }
53
handleFileDependency(StringRef File)54 void handleFileDependency(StringRef File) override {
55 Dependencies.push_back(std::string(File));
56 }
57
handlePrebuiltModuleDependency(PrebuiltModuleDep PMD)58 void handlePrebuiltModuleDependency(PrebuiltModuleDep PMD) override {
59 // Same as `handleModuleDependency`.
60 }
61
handleModuleDependency(ModuleDeps MD)62 void handleModuleDependency(ModuleDeps MD) override {
63 // These are ignored for the make format as it can't support the full
64 // set of deps, and handleFileDependency handles enough for implicitly
65 // built modules to work.
66 }
67
handleContextHash(std::string Hash)68 void handleContextHash(std::string Hash) override {}
69
lookupModuleOutput(const ModuleID & ID,ModuleOutputKind Kind)70 std::string lookupModuleOutput(const ModuleID &ID,
71 ModuleOutputKind Kind) override {
72 llvm::report_fatal_error("unexpected call to lookupModuleOutput");
73 }
74
printDependencies(std::string & S)75 void printDependencies(std::string &S) {
76 assert(Opts && "Handled dependency output options.");
77
78 class DependencyPrinter : public DependencyFileGenerator {
79 public:
80 DependencyPrinter(DependencyOutputOptions &Opts,
81 ArrayRef<std::string> Dependencies)
82 : DependencyFileGenerator(Opts) {
83 for (const auto &Dep : Dependencies)
84 addDependency(Dep);
85 }
86
87 void printDependencies(std::string &S) {
88 llvm::raw_string_ostream OS(S);
89 outputDependencyFile(OS);
90 }
91 };
92
93 DependencyPrinter Generator(*Opts, Dependencies);
94 Generator.printDependencies(S);
95 }
96
97 protected:
98 std::unique_ptr<DependencyOutputOptions> Opts;
99 std::vector<std::string> Dependencies;
100 };
101 } // anonymous namespace
102
getDependencyFile(const std::vector<std::string> & CommandLine,StringRef CWD,std::optional<StringRef> ModuleName)103 llvm::Expected<std::string> DependencyScanningTool::getDependencyFile(
104 const std::vector<std::string> &CommandLine, StringRef CWD,
105 std::optional<StringRef> ModuleName) {
106 MakeDependencyPrinterConsumer Consumer;
107 auto Result =
108 Worker.computeDependencies(CWD, CommandLine, Consumer, ModuleName);
109 if (Result)
110 return std::move(Result);
111 std::string Output;
112 Consumer.printDependencies(Output);
113 return Output;
114 }
115
getP1689ModuleDependencyFile(const CompileCommand & Command,StringRef CWD,std::string & MakeformatOutput,std::string & MakeformatOutputPath)116 llvm::Expected<P1689Rule> DependencyScanningTool::getP1689ModuleDependencyFile(
117 const CompileCommand &Command, StringRef CWD,
118 std::string &MakeformatOutput, std::string &MakeformatOutputPath) {
119 class P1689ModuleDependencyPrinterConsumer
120 : public MakeDependencyPrinterConsumer {
121 public:
122 P1689ModuleDependencyPrinterConsumer(P1689Rule &Rule,
123 const CompileCommand &Command)
124 : Filename(Command.Filename), Rule(Rule) {
125 Rule.PrimaryOutput = Command.Output;
126 }
127
128 void handleProvidedAndRequiredStdCXXModules(
129 std::optional<P1689ModuleInfo> Provided,
130 std::vector<P1689ModuleInfo> Requires) override {
131 Rule.Provides = Provided;
132 if (Rule.Provides)
133 Rule.Provides->SourcePath = Filename.str();
134 Rule.Requires = Requires;
135 }
136
137 StringRef getMakeFormatDependencyOutputPath() {
138 if (Opts->OutputFormat != DependencyOutputFormat::Make)
139 return {};
140 return Opts->OutputFile;
141 }
142
143 private:
144 StringRef Filename;
145 P1689Rule &Rule;
146 };
147
148 P1689Rule Rule;
149 P1689ModuleDependencyPrinterConsumer Consumer(Rule, Command);
150 auto Result = Worker.computeDependencies(CWD, Command.CommandLine, Consumer);
151 if (Result)
152 return std::move(Result);
153
154 MakeformatOutputPath = Consumer.getMakeFormatDependencyOutputPath();
155 if (!MakeformatOutputPath.empty())
156 Consumer.printDependencies(MakeformatOutput);
157 return Rule;
158 }
159
160 llvm::Expected<FullDependenciesResult>
getFullDependencies(const std::vector<std::string> & CommandLine,StringRef CWD,const llvm::StringSet<> & AlreadySeen,LookupModuleOutputCallback LookupModuleOutput,std::optional<StringRef> ModuleName)161 DependencyScanningTool::getFullDependencies(
162 const std::vector<std::string> &CommandLine, StringRef CWD,
163 const llvm::StringSet<> &AlreadySeen,
164 LookupModuleOutputCallback LookupModuleOutput,
165 std::optional<StringRef> ModuleName) {
166 FullDependencyConsumer Consumer(AlreadySeen, LookupModuleOutput,
167 Worker.shouldEagerLoadModules());
168 llvm::Error Result =
169 Worker.computeDependencies(CWD, CommandLine, Consumer, ModuleName);
170 if (Result)
171 return std::move(Result);
172 return Consumer.takeFullDependencies();
173 }
174
175 llvm::Expected<FullDependenciesResult>
getFullDependenciesLegacyDriverCommand(const std::vector<std::string> & CommandLine,StringRef CWD,const llvm::StringSet<> & AlreadySeen,LookupModuleOutputCallback LookupModuleOutput,std::optional<StringRef> ModuleName)176 DependencyScanningTool::getFullDependenciesLegacyDriverCommand(
177 const std::vector<std::string> &CommandLine, StringRef CWD,
178 const llvm::StringSet<> &AlreadySeen,
179 LookupModuleOutputCallback LookupModuleOutput,
180 std::optional<StringRef> ModuleName) {
181 FullDependencyConsumer Consumer(AlreadySeen, LookupModuleOutput,
182 Worker.shouldEagerLoadModules());
183 llvm::Error Result =
184 Worker.computeDependencies(CWD, CommandLine, Consumer, ModuleName);
185 if (Result)
186 return std::move(Result);
187 return Consumer.getFullDependenciesLegacyDriverCommand(CommandLine);
188 }
189
takeFullDependencies()190 FullDependenciesResult FullDependencyConsumer::takeFullDependencies() {
191 FullDependenciesResult FDR;
192 FullDependencies &FD = FDR.FullDeps;
193
194 FD.ID.ContextHash = std::move(ContextHash);
195 FD.FileDeps = std::move(Dependencies);
196 FD.PrebuiltModuleDeps = std::move(PrebuiltModuleDeps);
197 FD.Commands = std::move(Commands);
198
199 for (auto &&M : ClangModuleDeps) {
200 auto &MD = M.second;
201 if (MD.ImportedByMainFile)
202 FD.ClangModuleDeps.push_back(MD.ID);
203 // TODO: Avoid handleModuleDependency even being called for modules
204 // we've already seen.
205 if (AlreadySeen.count(M.first))
206 continue;
207 FDR.DiscoveredModules.push_back(std::move(MD));
208 }
209
210 return FDR;
211 }
212
213 FullDependenciesResult
getFullDependenciesLegacyDriverCommand(const std::vector<std::string> & OriginalCommandLine) const214 FullDependencyConsumer::getFullDependenciesLegacyDriverCommand(
215 const std::vector<std::string> &OriginalCommandLine) const {
216 FullDependencies FD;
217
218 FD.DriverCommandLine = makeTUCommandLineWithoutPaths(
219 ArrayRef<std::string>(OriginalCommandLine).slice(1));
220
221 FD.ID.ContextHash = std::move(ContextHash);
222
223 FD.FileDeps.assign(Dependencies.begin(), Dependencies.end());
224
225 for (const PrebuiltModuleDep &PMD : PrebuiltModuleDeps)
226 FD.DriverCommandLine.push_back("-fmodule-file=" + PMD.PCMFile);
227
228 for (auto &&M : ClangModuleDeps) {
229 auto &MD = M.second;
230 if (MD.ImportedByMainFile) {
231 FD.ClangModuleDeps.push_back(MD.ID);
232 auto PCMPath = LookupModuleOutput(MD.ID, ModuleOutputKind::ModuleFile);
233 if (EagerLoadModules) {
234 FD.DriverCommandLine.push_back("-fmodule-file=" + PCMPath);
235 } else {
236 FD.DriverCommandLine.push_back("-fmodule-map-file=" +
237 MD.ClangModuleMapFile);
238 FD.DriverCommandLine.push_back("-fmodule-file=" + MD.ID.ModuleName +
239 "=" + PCMPath);
240 }
241 }
242 }
243
244 FD.PrebuiltModuleDeps = std::move(PrebuiltModuleDeps);
245
246 FullDependenciesResult FDR;
247
248 for (auto &&M : ClangModuleDeps) {
249 // TODO: Avoid handleModuleDependency even being called for modules
250 // we've already seen.
251 if (AlreadySeen.count(M.first))
252 continue;
253 FDR.DiscoveredModules.push_back(std::move(M.second));
254 }
255
256 FDR.FullDeps = std::move(FD);
257 return FDR;
258 }
259