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/DependencyScanningTool.h"
10#include "clang/Basic/Diagnostic.h"
11#include "clang/Basic/DiagnosticFrontend.h"
12#include "clang/DependencyScanning/DependencyScannerImpl.h"
13#include "clang/Driver/Tool.h"
14#include "clang/Frontend/Utils.h"
15#include "llvm/ADT/SmallVectorExtras.h"
16#include "llvm/ADT/iterator.h"
17#include "llvm/TargetParser/Host.h"
18#include <optional>
19
20using namespace clang;
21using namespace tooling;
22using namespace dependencies;
23
24namespace {
25/// Prints out all of the gathered dependencies into a string.
26class MakeDependencyPrinterConsumer : public DependencyConsumer {
27public:
28 void handleBuildCommand(Command) override {}
29
30 void
31 handleDependencyOutputOpts(const DependencyOutputOptions &Opts) override {
32 this->Opts = std::make_unique<DependencyOutputOptions>(args: Opts);
33 }
34
35 void handleFileDependency(StringRef File) override {
36 Dependencies.push_back(x: std::string(File));
37 }
38
39 // These are ignored for the make format as it can't support the full
40 // set of deps, and handleFileDependency handles enough for implicitly
41 // built modules to work.
42 void handlePrebuiltModuleDependency(PrebuiltModuleDep PMD) override {}
43 void handleModuleDependency(ModuleDeps MD) override {}
44 void handleDirectModuleDependency(ModuleID ID) override {}
45 void handleVisibleModule(std::string ModuleName) override {}
46 void handleContextHash(std::string Hash) override {}
47
48 void printDependencies(std::string &S) {
49 assert(Opts && "Handled dependency output options.");
50
51 class DependencyPrinter : public DependencyFileGenerator {
52 public:
53 DependencyPrinter(DependencyOutputOptions &Opts,
54 ArrayRef<std::string> Dependencies)
55 : DependencyFileGenerator(Opts) {
56 for (const auto &Dep : Dependencies)
57 addDependency(Filename: Dep);
58 }
59
60 void printDependencies(std::string &S) {
61 llvm::raw_string_ostream OS(S);
62 outputDependencyFile(OS);
63 }
64 };
65
66 DependencyPrinter Generator(*Opts, Dependencies);
67 Generator.printDependencies(S);
68 }
69
70protected:
71 std::unique_ptr<DependencyOutputOptions> Opts;
72 std::vector<std::string> Dependencies;
73};
74} // anonymous namespace
75
76static std::pair<std::unique_ptr<driver::Driver>,
77 std::unique_ptr<driver::Compilation>>
78buildCompilation(ArrayRef<std::string> ArgStrs, DiagnosticsEngine &Diags,
79 IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,
80 llvm::BumpPtrAllocator &Alloc) {
81 SmallVector<const char *, 256> Argv;
82 Argv.reserve(N: ArgStrs.size());
83 for (const std::string &Arg : ArgStrs)
84 Argv.push_back(Elt: Arg.c_str());
85
86 std::unique_ptr<driver::Driver> Driver = std::make_unique<driver::Driver>(
87 args&: Argv[0], args: llvm::sys::getDefaultTargetTriple(), args&: Diags,
88 args: "clang LLVM compiler", args&: FS);
89 Driver->setTitle("clang_based_tool");
90
91 bool CLMode = driver::IsClangCL(
92 DriverMode: driver::getDriverMode(ProgName: Argv[0], Args: ArrayRef(Argv).slice(N: 1)));
93
94 if (llvm::Error E =
95 driver::expandResponseFiles(Args&: Argv, ClangCLMode: CLMode, Alloc, FS: FS.get())) {
96 Diags.Report(DiagID: diag::err_drv_expand_response_file)
97 << llvm::toString(E: std::move(E));
98 return std::make_pair(x: nullptr, y: nullptr);
99 }
100
101 std::unique_ptr<driver::Compilation> Compilation(
102 Driver->BuildCompilation(Args: Argv));
103 if (!Compilation)
104 return std::make_pair(x: nullptr, y: nullptr);
105
106 if (Compilation->containsError())
107 return std::make_pair(x: nullptr, y: nullptr);
108
109 if (Compilation->getJobs().empty()) {
110 Diags.Report(DiagID: diag::err_fe_expected_compiler_job)
111 << llvm::join(R&: ArgStrs, Separator: " ");
112 return std::make_pair(x: nullptr, y: nullptr);
113 }
114
115 return std::make_pair(x: std::move(Driver), y: std::move(Compilation));
116}
117
118/// Constructs the full frontend command line, including executable, for the
119/// given driver \c Cmd.
120static SmallVector<std::string, 0>
121buildCC1CommandLine(const driver::Command &Cmd) {
122 const auto &Args = Cmd.getArguments();
123 SmallVector<std::string, 0> Out;
124 Out.reserve(N: Args.size() + 1);
125 Out.emplace_back(Args: Cmd.getExecutable());
126 llvm::append_range(C&: Out, R: Args);
127 return Out;
128}
129
130static bool computeDependenciesForDriverCommandLine(
131 DependencyScanningWorker &Worker, StringRef WorkingDirectory,
132 ArrayRef<std::string> CommandLine, DependencyConsumer &Consumer,
133 DependencyActionController &Controller, DiagnosticConsumer &DiagConsumer,
134 IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFS) {
135 IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS = nullptr;
136 if (OverlayFS) {
137 FS = OverlayFS;
138 } else {
139 FS = &Worker.getVFS();
140 FS->setCurrentWorkingDirectory(WorkingDirectory);
141 }
142
143 // Compilation holds a non-owning a reference to the Driver, hence we need to
144 // keep the Driver alive when we use Compilation. Arguments to commands may be
145 // owned by Alloc when expanded from response files.
146 llvm::BumpPtrAllocator Alloc;
147 auto DiagEngineWithDiagOpts =
148 DiagnosticsEngineWithDiagOpts(CommandLine, FS, DiagConsumer);
149 const auto [Driver, Compilation] = buildCompilation(
150 ArgStrs: CommandLine, Diags&: *DiagEngineWithDiagOpts.DiagEngine, FS, Alloc);
151 if (!Compilation)
152 return false;
153
154 SmallVector<SmallVector<std::string, 0>> FrontendCommandLines;
155 for (const auto &Cmd : Compilation->getJobs())
156 FrontendCommandLines.push_back(Elt: buildCC1CommandLine(Cmd));
157 SmallVector<ArrayRef<std::string>> FrontendCommandLinesView(
158 FrontendCommandLines.begin(), FrontendCommandLines.end());
159
160 return Worker.computeDependencies(WorkingDirectory, CommandLines: FrontendCommandLinesView,
161 DepConsumer&: Consumer, Controller, DiagConsumer,
162 OverlayFS);
163}
164
165static llvm::Error makeErrorFromDiagnosticsOS(
166 TextDiagnosticsPrinterWithOutput &DiagPrinterWithOS) {
167 return llvm::make_error<llvm::StringError>(
168 Args&: DiagPrinterWithOS.DiagnosticsOS.str(), Args: llvm::inconvertibleErrorCode());
169}
170
171bool tooling::computeDependencies(
172 DependencyScanningWorker &Worker, StringRef WorkingDirectory,
173 ArrayRef<std::string> CommandLine, DependencyConsumer &Consumer,
174 DependencyActionController &Controller, DiagnosticConsumer &DiagConsumer,
175 llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFS) {
176 const auto IsCC1Input = (CommandLine.size() >= 2 && CommandLine[1] == "-cc1");
177 return IsCC1Input ? Worker.computeDependencies(WorkingDirectory, CommandLine,
178 DepConsumer&: Consumer, Controller,
179 DiagConsumer, OverlayFS)
180 : computeDependenciesForDriverCommandLine(
181 Worker, WorkingDirectory, CommandLine, Consumer,
182 Controller, DiagConsumer, OverlayFS);
183}
184
185std::optional<std::string>
186DependencyScanningTool::getDependencyFile(ArrayRef<std::string> CommandLine,
187 StringRef CWD,
188 DiagnosticConsumer &DiagConsumer) {
189 MakeDependencyPrinterConsumer DepConsumer;
190 CallbackActionController Controller(nullptr);
191 if (!computeDependencies(Worker, WorkingDirectory: CWD, CommandLine, Consumer&: DepConsumer, Controller,
192 DiagConsumer))
193 return std::nullopt;
194 std::string Output;
195 DepConsumer.printDependencies(S&: Output);
196 return Output;
197}
198
199std::optional<P1689Rule> DependencyScanningTool::getP1689ModuleDependencyFile(
200 const CompileCommand &Command, StringRef CWD, std::string &MakeformatOutput,
201 std::string &MakeformatOutputPath, DiagnosticConsumer &DiagConsumer) {
202 class P1689ModuleDependencyPrinterConsumer
203 : public MakeDependencyPrinterConsumer {
204 public:
205 P1689ModuleDependencyPrinterConsumer(P1689Rule &Rule,
206 const CompileCommand &Command)
207 : Filename(Command.Filename), Rule(Rule) {
208 Rule.PrimaryOutput = Command.Output;
209 }
210
211 void handleProvidedAndRequiredStdCXXModules(
212 std::optional<P1689ModuleInfo> Provided,
213 std::vector<P1689ModuleInfo> Requires) override {
214 Rule.Provides = Provided;
215 if (Rule.Provides)
216 Rule.Provides->SourcePath = Filename.str();
217 Rule.Requires = std::move(Requires);
218 }
219
220 StringRef getMakeFormatDependencyOutputPath() {
221 if (Opts->OutputFormat != DependencyOutputFormat::Make)
222 return {};
223 return Opts->OutputFile;
224 }
225
226 private:
227 StringRef Filename;
228 P1689Rule &Rule;
229 };
230
231 class P1689ActionController : public DependencyActionController {
232 public:
233 // The lookupModuleOutput is for clang modules. P1689 format don't need it.
234 std::string lookupModuleOutput(const ModuleDeps &,
235 ModuleOutputKind Kind) override {
236 return "";
237 }
238 };
239
240 P1689Rule Rule;
241 P1689ModuleDependencyPrinterConsumer Consumer(Rule, Command);
242 P1689ActionController Controller;
243 if (!computeDependencies(Worker, WorkingDirectory: CWD, CommandLine: Command.CommandLine, Consumer,
244 Controller, DiagConsumer))
245 return std::nullopt;
246
247 MakeformatOutputPath = Consumer.getMakeFormatDependencyOutputPath();
248 if (!MakeformatOutputPath.empty())
249 Consumer.printDependencies(S&: MakeformatOutput);
250 return Rule;
251}
252
253std::optional<TranslationUnitDeps>
254DependencyScanningTool::getTranslationUnitDependencies(
255 ArrayRef<std::string> CommandLine, StringRef CWD,
256 DiagnosticConsumer &DiagConsumer,
257 const llvm::DenseSet<ModuleID> &AlreadySeen,
258 LookupModuleOutputCallback LookupModuleOutput,
259 std::optional<llvm::MemoryBufferRef> TUBuffer) {
260 FullDependencyConsumer Consumer(AlreadySeen);
261 CallbackActionController Controller(LookupModuleOutput);
262
263 // If we are scanning from a TUBuffer, create an overlay filesystem with the
264 // input as an in-memory file and add it to the command line.
265 IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFS = nullptr;
266 std::vector<std::string> CommandLineWithTUBufferInput;
267 if (TUBuffer) {
268 std::tie(args&: OverlayFS, args&: CommandLineWithTUBufferInput) =
269 initVFSForTUBufferScanning(BaseFS: &Worker.getVFS(), CommandLine, WorkingDirectory: CWD,
270 TUBuffer: *TUBuffer);
271 CommandLine = CommandLineWithTUBufferInput;
272 }
273
274 if (!computeDependencies(Worker, WorkingDirectory: CWD, CommandLine, Consumer, Controller,
275 DiagConsumer, OverlayFS))
276 return std::nullopt;
277 return Consumer.takeTranslationUnitDeps();
278}
279
280llvm::Expected<TranslationUnitDeps>
281DependencyScanningTool::getModuleDependencies(
282 StringRef ModuleName, ArrayRef<std::string> CommandLine, StringRef CWD,
283 const llvm::DenseSet<ModuleID> &AlreadySeen,
284 LookupModuleOutputCallback LookupModuleOutput) {
285 if (auto Error =
286 initializeCompilerInstanceWithContextOrError(CWD, CommandLine))
287 return Error;
288
289 auto Result = computeDependenciesByNameWithContextOrError(
290 ModuleName, AlreadySeen, LookupModuleOutput);
291
292 if (auto Error = finalizeCompilerInstanceWithContextOrError())
293 return Error;
294
295 return Result;
296}
297
298static std::optional<SmallVector<std::string, 0>> getFirstCC1CommandLine(
299 ArrayRef<std::string> CommandLine, DiagnosticsEngine &Diags,
300 llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFS) {
301 // Compilation holds a non-owning a reference to the Driver, hence we need to
302 // keep the Driver alive when we use Compilation. Arguments to commands may be
303 // owned by Alloc when expanded from response files.
304 llvm::BumpPtrAllocator Alloc;
305 const auto [Driver, Compilation] =
306 buildCompilation(ArgStrs: CommandLine, Diags, FS: OverlayFS, Alloc);
307 if (!Compilation)
308 return std::nullopt;
309
310 const auto IsClangCmd = [](const driver::Command &Cmd) {
311 return StringRef(Cmd.getCreator().getName()) == "clang";
312 };
313
314 const auto &Jobs = Compilation->getJobs();
315 if (const auto It = llvm::find_if(Range: Jobs, P: IsClangCmd); It != Jobs.end())
316 return buildCC1CommandLine(Cmd: *It);
317 return std::nullopt;
318}
319
320bool DependencyScanningTool::initializeWorkerCIWithContextFromCommandline(
321 DependencyScanningWorker &Worker, StringRef CWD,
322 ArrayRef<std::string> CommandLine, DiagnosticConsumer &DC) {
323 if (CommandLine.size() >= 2 && CommandLine[1] == "-cc1") {
324 // The input command line is already a -cc1 invocation; initialize the
325 // compiler instance directly from it.
326 return Worker.initializeCompilerInstanceWithContext(CWD, CommandLine, DC);
327 }
328
329 // The input command line is either a driver-style command line, or
330 // ill-formed. In this case, we will first call the Driver to build a -cc1
331 // command line for this compilation or diagnose any ill-formed input.
332 auto [OverlayFS, ModifiedCommandLine] = initVFSForByNameScanning(
333 BaseFS: &Worker.getVFS(), CommandLine, WorkingDirectory: CWD, ModuleName: "ScanningByName");
334 auto DiagEngineWithCmdAndOpts =
335 std::make_unique<DiagnosticsEngineWithDiagOpts>(args&: ModifiedCommandLine,
336 args&: OverlayFS, args&: DC);
337
338 const auto MaybeFirstCC1 = getFirstCC1CommandLine(
339 CommandLine: ModifiedCommandLine, Diags&: *DiagEngineWithCmdAndOpts->DiagEngine, OverlayFS);
340 if (!MaybeFirstCC1)
341 return false;
342
343 return Worker.initializeCompilerInstanceWithContext(
344 CWD, CommandLine: *MaybeFirstCC1, DiagEngineWithCmdAndOpts: std::move(DiagEngineWithCmdAndOpts), OverlayFS);
345}
346
347llvm::Error
348DependencyScanningTool::initializeCompilerInstanceWithContextOrError(
349 StringRef CWD, ArrayRef<std::string> CommandLine) {
350 DiagPrinterWithOS =
351 std::make_unique<TextDiagnosticsPrinterWithOutput>(args&: CommandLine);
352
353 bool Result = initializeWorkerCIWithContextFromCommandline(
354 Worker, CWD, CommandLine, DC&: DiagPrinterWithOS->DiagPrinter);
355
356 if (Result)
357 return llvm::Error::success();
358 return makeErrorFromDiagnosticsOS(DiagPrinterWithOS&: *DiagPrinterWithOS);
359}
360
361llvm::Expected<TranslationUnitDeps>
362DependencyScanningTool::computeDependenciesByNameWithContextOrError(
363 StringRef ModuleName, const llvm::DenseSet<ModuleID> &AlreadySeen,
364 LookupModuleOutputCallback LookupModuleOutput) {
365 FullDependencyConsumer Consumer(AlreadySeen);
366 CallbackActionController Controller(LookupModuleOutput);
367 // We need to clear the DiagnosticOutput so that each by-name lookup
368 // has a clean diagnostics buffer.
369 DiagPrinterWithOS->DiagnosticOutput.clear();
370 if (Worker.computeDependenciesByNameWithContext(ModuleName, Consumer,
371 Controller))
372 return Consumer.takeTranslationUnitDeps();
373 return makeErrorFromDiagnosticsOS(DiagPrinterWithOS&: *DiagPrinterWithOS);
374}
375
376llvm::Error
377DependencyScanningTool::finalizeCompilerInstanceWithContextOrError() {
378 if (Worker.finalizeCompilerInstanceWithContext())
379 return llvm::Error::success();
380 return makeErrorFromDiagnosticsOS(DiagPrinterWithOS&: *DiagPrinterWithOS);
381}
382