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/FrontendActions.h"
15#include "clang/Frontend/Utils.h"
16#include "clang/Lex/Preprocessor.h"
17#include "llvm/ADT/ScopeExit.h"
18#include "llvm/ADT/SmallVectorExtras.h"
19#include "llvm/ADT/iterator.h"
20#include "llvm/TargetParser/Host.h"
21#include <optional>
22
23using namespace clang;
24using namespace tooling;
25using namespace dependencies;
26
27namespace {
28/// Prints out all of the gathered dependencies into a string.
29class MakeDependencyPrinterConsumer : public DependencyConsumer {
30public:
31 void handleBuildCommand(Command) override {}
32
33 void
34 handleDependencyOutputOpts(const DependencyOutputOptions &Opts) override {
35 this->Opts = std::make_unique<DependencyOutputOptions>(args: Opts);
36 }
37
38 void handleFileDependency(StringRef File) override {
39 Dependencies.push_back(x: std::string(File));
40 }
41
42 // These are ignored for the make format as it can't support the full
43 // set of deps, and handleFileDependency handles enough for implicitly
44 // built modules to work.
45 void handlePrebuiltModuleDependency(PrebuiltModuleDep PMD) override {}
46 void handleModuleDependency(ModuleDeps MD) override {}
47 void handleDirectModuleDependency(ModuleID ID) override {}
48 void handleVisibleModule(std::string ModuleName) override {}
49 void handleContextHash(std::string Hash) override {}
50
51 void printDependencies(std::string &S) {
52 assert(Opts && "Handled dependency output options.");
53
54 class DependencyPrinter : public DependencyFileGenerator {
55 public:
56 DependencyPrinter(DependencyOutputOptions &Opts,
57 ArrayRef<std::string> Dependencies)
58 : DependencyFileGenerator(Opts) {
59 for (const auto &Dep : Dependencies)
60 addDependency(Filename: Dep);
61 }
62
63 void printDependencies(std::string &S) {
64 llvm::raw_string_ostream OS(S);
65 outputDependencyFile(OS);
66 }
67 };
68
69 DependencyPrinter Generator(*Opts, Dependencies);
70 Generator.printDependencies(S);
71 }
72
73protected:
74 std::unique_ptr<DependencyOutputOptions> Opts;
75 std::vector<std::string> Dependencies;
76};
77} // anonymous namespace
78
79static std::pair<std::unique_ptr<driver::Driver>,
80 std::unique_ptr<driver::Compilation>>
81buildCompilation(ArrayRef<std::string> ArgStrs, DiagnosticsEngine &Diags,
82 IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,
83 llvm::BumpPtrAllocator &Alloc) {
84 SmallVector<const char *, 256> Argv;
85 Argv.reserve(N: ArgStrs.size());
86 for (const std::string &Arg : ArgStrs)
87 Argv.push_back(Elt: Arg.c_str());
88
89 std::unique_ptr<driver::Driver> Driver = std::make_unique<driver::Driver>(
90 args&: Argv[0], args: llvm::sys::getDefaultTargetTriple(), args&: Diags,
91 args: "clang LLVM compiler", args&: FS);
92 Driver->setTitle("clang_based_tool");
93
94 bool CLMode = driver::IsClangCL(
95 DriverMode: driver::getDriverMode(ProgName: Argv[0], Args: ArrayRef(Argv).slice(N: 1)));
96
97 if (llvm::Error E =
98 driver::expandResponseFiles(Args&: Argv, ClangCLMode: CLMode, Alloc, FS: FS.get())) {
99 Diags.Report(DiagID: diag::err_drv_expand_response_file)
100 << llvm::toString(E: std::move(E));
101 return std::make_pair(x: nullptr, y: nullptr);
102 }
103
104 std::unique_ptr<driver::Compilation> Compilation(
105 Driver->BuildCompilation(Args: Argv));
106 if (!Compilation)
107 return std::make_pair(x: nullptr, y: nullptr);
108
109 if (Compilation->containsError())
110 return std::make_pair(x: nullptr, y: nullptr);
111
112 if (Compilation->getJobs().empty()) {
113 Diags.Report(DiagID: diag::err_fe_expected_compiler_job)
114 << llvm::join(R&: ArgStrs, Separator: " ");
115 return std::make_pair(x: nullptr, y: nullptr);
116 }
117
118 return std::make_pair(x: std::move(Driver), y: std::move(Compilation));
119}
120
121/// Constructs the full frontend command line, including executable, for the
122/// given driver \c Cmd.
123static SmallVector<std::string, 0>
124buildCC1CommandLine(const driver::Command &Cmd) {
125 const auto &Args = Cmd.getArguments();
126 SmallVector<std::string, 0> Out;
127 Out.reserve(N: Args.size() + 1);
128 Out.emplace_back(Args: Cmd.getExecutable());
129 llvm::append_range(C&: Out, R: Args);
130 return Out;
131}
132
133static bool computeDependenciesForDriverCommandLine(
134 DependencyScanningWorker &Worker, StringRef WorkingDirectory,
135 ArrayRef<std::string> CommandLine, DependencyConsumer &Consumer,
136 DependencyActionController &Controller, DiagnosticConsumer &DiagConsumer,
137 IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFS) {
138 IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS = nullptr;
139 if (OverlayFS) {
140 FS = OverlayFS;
141 } else {
142 FS = &Worker.getVFS();
143 FS->setCurrentWorkingDirectory(WorkingDirectory);
144 }
145
146 // Compilation holds a non-owning a reference to the Driver, hence we need to
147 // keep the Driver alive when we use Compilation. Arguments to commands may be
148 // owned by Alloc when expanded from response files.
149 llvm::BumpPtrAllocator Alloc;
150 auto DiagEngineWithDiagOpts =
151 DiagnosticsEngineWithDiagOpts(CommandLine, FS, DiagConsumer);
152 const auto [Driver, Compilation] = buildCompilation(
153 ArgStrs: CommandLine, Diags&: *DiagEngineWithDiagOpts.DiagEngine, FS, Alloc);
154 if (!Compilation)
155 return false;
156
157 SmallVector<SmallVector<std::string, 0>> FrontendCommandLines;
158 for (const auto &Cmd : Compilation->getJobs())
159 FrontendCommandLines.push_back(Elt: buildCC1CommandLine(Cmd));
160 SmallVector<ArrayRef<std::string>> FrontendCommandLinesView(
161 FrontendCommandLines.begin(), FrontendCommandLines.end());
162
163 return Worker.computeDependencies(WorkingDirectory, CommandLines: FrontendCommandLinesView,
164 DepConsumer&: Consumer, Controller, DiagConsumer,
165 OverlayFS);
166}
167
168static llvm::Error makeErrorFromDiagnosticsOS(
169 TextDiagnosticsPrinterWithOutput &DiagPrinterWithOS) {
170 return llvm::make_error<llvm::StringError>(
171 Args&: DiagPrinterWithOS.DiagnosticsOS.str(), Args: llvm::inconvertibleErrorCode());
172}
173
174bool tooling::computeDependencies(
175 DependencyScanningWorker &Worker, StringRef WorkingDirectory,
176 ArrayRef<std::string> CommandLine, DependencyConsumer &Consumer,
177 DependencyActionController &Controller, DiagnosticConsumer &DiagConsumer,
178 llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFS) {
179 const auto IsCC1Input = (CommandLine.size() >= 2 && CommandLine[1] == "-cc1");
180 return IsCC1Input ? Worker.computeDependencies(WorkingDirectory, CommandLine,
181 DepConsumer&: Consumer, Controller,
182 DiagConsumer, OverlayFS)
183 : computeDependenciesForDriverCommandLine(
184 Worker, WorkingDirectory, CommandLine, Consumer,
185 Controller, DiagConsumer, OverlayFS);
186}
187
188std::optional<std::string>
189DependencyScanningTool::getDependencyFile(ArrayRef<std::string> CommandLine,
190 StringRef CWD,
191 DiagnosticConsumer &DiagConsumer) {
192 MakeDependencyPrinterConsumer DepConsumer;
193 CallbackActionController Controller(nullptr);
194 if (!computeDependencies(Worker, WorkingDirectory: CWD, CommandLine, Consumer&: DepConsumer, Controller,
195 DiagConsumer))
196 return std::nullopt;
197 std::string Output;
198 DepConsumer.printDependencies(S&: Output);
199 return Output;
200}
201
202std::optional<P1689Rule> DependencyScanningTool::getP1689ModuleDependencyFile(
203 const CompileCommand &Command, StringRef CWD, std::string &MakeformatOutput,
204 std::string &MakeformatOutputPath, DiagnosticConsumer &DiagConsumer) {
205 class P1689ModuleDependencyPrinterConsumer
206 : public MakeDependencyPrinterConsumer {
207 public:
208 P1689ModuleDependencyPrinterConsumer(P1689Rule &Rule,
209 const CompileCommand &Command)
210 : Filename(Command.Filename), Rule(Rule) {
211 Rule.PrimaryOutput = Command.Output;
212 }
213
214 void handleProvidedAndRequiredStdCXXModules(
215 std::optional<P1689ModuleInfo> Provided,
216 std::vector<P1689ModuleInfo> Requires) override {
217 Rule.Provides = std::move(Provided);
218 if (Rule.Provides)
219 Rule.Provides->SourcePath = Filename.str();
220 Rule.Requires = std::move(Requires);
221 }
222
223 StringRef getMakeFormatDependencyOutputPath() {
224 if (Opts->OutputFormat != DependencyOutputFormat::Make)
225 return {};
226 return Opts->OutputFile;
227 }
228
229 private:
230 StringRef Filename;
231 P1689Rule &Rule;
232 };
233
234 class P1689ActionController : public DependencyActionController {
235 public:
236 // The lookupModuleOutput is for clang modules. P1689 format don't need it.
237 std::string lookupModuleOutput(const ModuleDeps &,
238 ModuleOutputKind Kind) override {
239 return "";
240 }
241
242 std::unique_ptr<DependencyActionController> clone() const override {
243 return std::make_unique<P1689ActionController>();
244 }
245 };
246
247 P1689Rule Rule;
248 P1689ModuleDependencyPrinterConsumer Consumer(Rule, Command);
249 P1689ActionController Controller;
250 if (!computeDependencies(Worker, WorkingDirectory: CWD, CommandLine: Command.CommandLine, Consumer,
251 Controller, DiagConsumer))
252 return std::nullopt;
253
254 MakeformatOutputPath = Consumer.getMakeFormatDependencyOutputPath();
255 if (!MakeformatOutputPath.empty())
256 Consumer.printDependencies(S&: MakeformatOutput);
257 return Rule;
258}
259
260static std::pair<IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem>,
261 std::vector<std::string>>
262initVFSForTUBufferScanning(IntrusiveRefCntPtr<llvm::vfs::FileSystem> BaseFS,
263 ArrayRef<std::string> CommandLine,
264 StringRef WorkingDirectory,
265 llvm::MemoryBufferRef TUBuffer) {
266 // Reset what might have been modified in the previous worker invocation.
267 BaseFS->setCurrentWorkingDirectory(WorkingDirectory);
268
269 auto OverlayFS =
270 llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(A&: BaseFS);
271 auto InMemoryFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
272 InMemoryFS->setCurrentWorkingDirectory(WorkingDirectory);
273 auto InputPath = TUBuffer.getBufferIdentifier();
274 InMemoryFS->addFile(
275 Path: InputPath, ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: TUBuffer.getBuffer()));
276 IntrusiveRefCntPtr<llvm::vfs::FileSystem> InMemoryOverlay = InMemoryFS;
277
278 OverlayFS->pushOverlay(FS: InMemoryOverlay);
279 std::vector<std::string> ModifiedCommandLine(CommandLine);
280 ModifiedCommandLine.emplace_back(args&: InputPath);
281
282 return std::make_pair(x&: OverlayFS, y&: ModifiedCommandLine);
283}
284
285static std::pair<IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem>,
286 std::vector<std::string>>
287initVFSForByNameScanning(IntrusiveRefCntPtr<llvm::vfs::FileSystem> BaseFS,
288 ArrayRef<std::string> CommandLine,
289 StringRef WorkingDirectory) {
290 // Reset what might have been modified in the previous worker invocation.
291 BaseFS->setCurrentWorkingDirectory(WorkingDirectory);
292
293 // If we're scanning based on a module name alone, we don't expect the client
294 // to provide us with an input file. However, the driver really wants to have
295 // one. Let's just make it up to make the driver happy.
296 auto OverlayFS =
297 llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(A&: BaseFS);
298 auto InMemoryFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
299 InMemoryFS->setCurrentWorkingDirectory(WorkingDirectory);
300 StringRef FakeInputPath("module-include.input");
301 // The fake input buffer is read-only, and it is used to produce
302 // unique source locations for the diagnostics. Therefore sharing
303 // this global buffer across threads is ok.
304 static const std::string FakeInput(
305 clang::tooling::CompilerInstanceWithContext::MaxNumOfQueries, ' ');
306 InMemoryFS->addFile(Path: FakeInputPath, ModificationTime: 0,
307 Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: FakeInput));
308 IntrusiveRefCntPtr<llvm::vfs::FileSystem> InMemoryOverlay = InMemoryFS;
309 OverlayFS->pushOverlay(FS: InMemoryOverlay);
310
311 std::vector<std::string> ModifiedCommandLine(CommandLine);
312 ModifiedCommandLine.emplace_back(args&: FakeInputPath);
313
314 return std::make_pair(x&: OverlayFS, y&: ModifiedCommandLine);
315}
316
317std::optional<TranslationUnitDeps>
318DependencyScanningTool::getTranslationUnitDependencies(
319 ArrayRef<std::string> CommandLine, StringRef CWD,
320 DiagnosticConsumer &DiagConsumer,
321 const llvm::DenseSet<ModuleID> &AlreadySeen,
322 LookupModuleOutputCallback LookupModuleOutput,
323 std::optional<llvm::MemoryBufferRef> TUBuffer) {
324 FullDependencyConsumer Consumer(AlreadySeen);
325 CallbackActionController Controller(LookupModuleOutput);
326
327 // If we are scanning from a TUBuffer, create an overlay filesystem with the
328 // input as an in-memory file and add it to the command line.
329 IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFS = nullptr;
330 std::vector<std::string> CommandLineWithTUBufferInput;
331 if (TUBuffer) {
332 std::tie(args&: OverlayFS, args&: CommandLineWithTUBufferInput) =
333 initVFSForTUBufferScanning(BaseFS: &Worker.getVFS(), CommandLine, WorkingDirectory: CWD,
334 TUBuffer: *TUBuffer);
335 CommandLine = CommandLineWithTUBufferInput;
336 }
337
338 if (!computeDependencies(Worker, WorkingDirectory: CWD, CommandLine, Consumer, Controller,
339 DiagConsumer, OverlayFS))
340 return std::nullopt;
341 return Consumer.takeTranslationUnitDeps();
342}
343
344llvm::Expected<TranslationUnitDeps>
345DependencyScanningTool::getModuleDependencies(
346 StringRef ModuleName, ArrayRef<std::string> CommandLine, StringRef CWD,
347 const llvm::DenseSet<ModuleID> &AlreadySeen,
348 LookupModuleOutputCallback LookupModuleOutput) {
349 auto MaybeCIWithContext = CompilerInstanceWithContext::initializeOrError(
350 Tool&: *this, CWD, CommandLine, LookupModuleOutput);
351 if (auto Error = MaybeCIWithContext.takeError())
352 return Error;
353
354 return MaybeCIWithContext->computeDependenciesByNameOrError(
355 ModuleName, AlreadySeen, LookupModuleOutput);
356}
357
358static std::optional<SmallVector<std::string, 0>> getFirstCC1CommandLine(
359 ArrayRef<std::string> CommandLine, DiagnosticsEngine &Diags,
360 llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFS) {
361 // Compilation holds a non-owning a reference to the Driver, hence we need to
362 // keep the Driver alive when we use Compilation. Arguments to commands may be
363 // owned by Alloc when expanded from response files.
364 llvm::BumpPtrAllocator Alloc;
365 const auto [Driver, Compilation] =
366 buildCompilation(ArgStrs: CommandLine, Diags, FS: OverlayFS, Alloc);
367 if (!Compilation)
368 return std::nullopt;
369
370 const auto IsClangCmd = [](const driver::Command &Cmd) {
371 return StringRef(Cmd.getCreator().getName()) == "clang";
372 };
373
374 const auto &Jobs = Compilation->getJobs();
375 if (const auto It = llvm::find_if(Range: Jobs, P: IsClangCmd); It != Jobs.end())
376 return buildCC1CommandLine(Cmd: *It);
377 return std::nullopt;
378}
379
380std::optional<CompilerInstanceWithContext>
381CompilerInstanceWithContext::initializeFromCommandline(
382 DependencyScanningTool &Tool, StringRef CWD,
383 ArrayRef<std::string> CommandLine, DependencyActionController &Controller,
384 DiagnosticConsumer &DC) {
385 auto [OverlayFS, ModifiedCommandLine] =
386 initVFSForByNameScanning(BaseFS: &Tool.Worker.getVFS(), CommandLine, WorkingDirectory: CWD);
387 auto DiagEngineWithCmdAndOpts =
388 std::make_unique<DiagnosticsEngineWithDiagOpts>(args&: ModifiedCommandLine,
389 args&: OverlayFS, args&: DC);
390
391 if (CommandLine.size() >= 2 && CommandLine[1] == "-cc1") {
392 // The input command line is already a -cc1 invocation; initialize the
393 // compiler instance directly from it.
394 CompilerInstanceWithContext CIWithContext(Tool.Worker, CWD, CommandLine);
395 if (!CIWithContext.initialize(
396 Controller, DiagEngineWithDiagOpts: std::move(DiagEngineWithCmdAndOpts), OverlayFS))
397 return std::nullopt;
398 return std::move(CIWithContext);
399 }
400
401 // The input command line is either a driver-style command line, or
402 // ill-formed. In this case, we will first call the Driver to build a -cc1
403 // command line for this compilation or diagnose any ill-formed input.
404 const auto MaybeFirstCC1 = getFirstCC1CommandLine(
405 CommandLine: ModifiedCommandLine, Diags&: *DiagEngineWithCmdAndOpts->DiagEngine, OverlayFS);
406 if (!MaybeFirstCC1)
407 return std::nullopt;
408
409 std::vector<std::string> CC1CommandLine(MaybeFirstCC1->begin(),
410 MaybeFirstCC1->end());
411 CompilerInstanceWithContext CIWithContext(Tool.Worker, CWD,
412 std::move(CC1CommandLine));
413 if (!CIWithContext.initialize(Controller, DiagEngineWithDiagOpts: std::move(DiagEngineWithCmdAndOpts),
414 OverlayFS))
415 return std::nullopt;
416 return std::move(CIWithContext);
417}
418
419llvm::Expected<CompilerInstanceWithContext>
420CompilerInstanceWithContext::initializeOrError(
421 DependencyScanningTool &Tool, StringRef CWD,
422 ArrayRef<std::string> CommandLine,
423 LookupModuleOutputCallback LookupModuleOutput) {
424 // It might seem wasteful to create fresh controller just for initializing the
425 // compiler instance, but repeated calls to computeDependenciesByNameOrError()
426 // do that as well, so this gets amortized.
427 CallbackActionController Controller(LookupModuleOutput);
428 auto DiagPrinterWithOS =
429 std::make_unique<TextDiagnosticsPrinterWithOutput>(args&: CommandLine);
430
431 auto Result = initializeFromCommandline(Tool, CWD, CommandLine, Controller,
432 DC&: DiagPrinterWithOS->DiagPrinter);
433 if (Result) {
434 Result->DiagPrinterWithOS = std::move(DiagPrinterWithOS);
435 return std::move(*Result);
436 }
437 return makeErrorFromDiagnosticsOS(DiagPrinterWithOS&: *DiagPrinterWithOS);
438}
439
440llvm::Expected<TranslationUnitDeps>
441CompilerInstanceWithContext::computeDependenciesByNameOrError(
442 StringRef ModuleName, const llvm::DenseSet<ModuleID> &AlreadySeen,
443 LookupModuleOutputCallback LookupModuleOutput) {
444 FullDependencyConsumer Consumer(AlreadySeen);
445 CallbackActionController Controller(LookupModuleOutput);
446 // We need to clear the DiagnosticOutput so that each by-name lookup
447 // has a clean diagnostics buffer.
448 DiagPrinterWithOS->DiagnosticOutput.clear();
449 if (computeDependencies(ModuleName, Consumer, Controller))
450 return Consumer.takeTranslationUnitDeps();
451 return makeErrorFromDiagnosticsOS(DiagPrinterWithOS&: *DiagPrinterWithOS);
452}
453
454bool CompilerInstanceWithContext::initialize(
455 DependencyActionController &Controller,
456 std::unique_ptr<DiagnosticsEngineWithDiagOpts> DiagEngineWithDiagOpts,
457 IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFS) {
458 assert(DiagEngineWithDiagOpts && "Valid diagnostics engine required!");
459 DiagEngineWithCmdAndOpts = std::move(DiagEngineWithDiagOpts);
460 DiagConsumer = DiagEngineWithCmdAndOpts->DiagEngine->getClient();
461
462#ifndef NDEBUG
463 assert(OverlayFS && "OverlayFS required!");
464 bool SawDepFS = false;
465 OverlayFS->visit([&](llvm::vfs::FileSystem &VFS) {
466 SawDepFS |= &VFS == Worker.DepFS.get();
467 });
468 assert(SawDepFS && "OverlayFS not based on DepFS");
469#endif
470
471 OriginalInvocation = createCompilerInvocation(
472 CommandLine, Diags&: *DiagEngineWithCmdAndOpts->DiagEngine);
473 if (!OriginalInvocation) {
474 DiagEngineWithCmdAndOpts->DiagEngine->Report(
475 DiagID: diag::err_fe_expected_compiler_job)
476 << llvm::join(R&: CommandLine, Separator: " ");
477 return false;
478 }
479
480 if (any(Val: Worker.Service.getOpts().OptimizeArgs &
481 ScanningOptimizations::Macros))
482 canonicalizeDefines(PPOpts&: OriginalInvocation->getPreprocessorOpts());
483
484 // Create the CompilerInstance.
485 std::shared_ptr<ModuleCache> ModCache =
486 makeInProcessModuleCache(Entries&: Worker.Service.getModuleCacheEntries());
487 CIPtr = std::make_unique<CompilerInstance>(
488 args: createScanCompilerInvocation(Invocation: *OriginalInvocation, Service: Worker.Service,
489 Controller),
490 args&: Worker.PCHContainerOps, args: std::move(ModCache));
491 auto &CI = *CIPtr;
492
493 initializeScanCompilerInstance(
494 ScanInstance&: CI, FS: OverlayFS, DiagConsumer: DiagEngineWithCmdAndOpts->DiagEngine->getClient(),
495 Service&: Worker.Service, DepFS: Worker.DepFS);
496
497 StableDirs = getInitialStableDirs(ScanInstance: CI);
498 auto MaybePrebuiltModulesASTMap =
499 computePrebuiltModulesASTMap(ScanInstance&: CI, StableDirs);
500 if (!MaybePrebuiltModulesASTMap)
501 return false;
502
503 PrebuiltModuleASTMap = std::move(*MaybePrebuiltModulesASTMap);
504 OutputOpts = createDependencyOutputOptions(Invocation: *OriginalInvocation);
505
506 // We do not create the target in initializeScanCompilerInstance because
507 // setting it here is unique for by-name lookups. We create the target only
508 // once here, and the information is reused for all computeDependencies calls.
509 // We do not need to call createTarget explicitly if we go through
510 // CompilerInstance::ExecuteAction to perform scanning.
511 CI.createTarget();
512
513 return true;
514}
515
516bool CompilerInstanceWithContext::computeDependencies(
517 StringRef ModuleName, DependencyConsumer &Consumer,
518 DependencyActionController &Controller) {
519 if (SrcLocOffset >= MaxNumOfQueries)
520 llvm::report_fatal_error(reason: "exceeded maximum by-name scans for worker");
521
522 assert(CIPtr && "CIPtr must be initialized before calling this method");
523 auto &CI = *CIPtr;
524
525 // We need to reset the diagnostics, so that the diagnostics issued
526 // during a previous computeDependencies call do not affect the current call.
527 // If we do not reset, we may inherit fatal errors from a previous call.
528 CI.getDiagnostics().Reset();
529
530 // We create this cleanup object because computeDependencies may exit
531 // early with errors.
532 llvm::scope_exit CleanUp([&]() {
533 CI.clearDependencyCollectors();
534 // The preprocessor may not be created at the entry of this method,
535 // but it must have been created when this method returns, whether
536 // there are errors during scanning or not.
537 CI.getPreprocessor().removePPCallbacks();
538 });
539
540 auto MDC = initializeScanInstanceDependencyCollector(
541 ScanInstance&: CI, DepOutputOpts: std::make_unique<DependencyOutputOptions>(args&: *OutputOpts), WorkingDirectory: CWD, Consumer,
542 Service&: Worker.Service,
543 /* The MDC's constructor makes a copy of the OriginalInvocation, so
544 we can pass it in without worrying that it might be changed across
545 invocations of computeDependencies. */
546 Inv&: *OriginalInvocation, Controller, PrebuiltModulesASTMap: PrebuiltModuleASTMap, StableDirs);
547
548 CompilerInvocation ModuleInvocation(*OriginalInvocation);
549 if (!Controller.initialize(ScanInstance&: CI, NewInvocation&: ModuleInvocation))
550 return false;
551
552 if (!SrcLocOffset) {
553 // When SrcLocOffset is zero, we are at the beginning of the fake source
554 // file. In this case, we call BeginSourceFile to initialize.
555 std::unique_ptr<FrontendAction> Action =
556 std::make_unique<PreprocessOnlyAction>();
557 auto *InputFile = CI.getFrontendOpts().Inputs.begin();
558 bool ActionBeginSucceeded = Action->BeginSourceFile(CI, Input: *InputFile);
559 assert(ActionBeginSucceeded && "Action BeginSourceFile must succeed");
560 (void)ActionBeginSucceeded;
561 }
562
563 Preprocessor &PP = CI.getPreprocessor();
564 SourceManager &SM = PP.getSourceManager();
565 FileID MainFileID = SM.getMainFileID();
566 SourceLocation FileStart = SM.getLocForStartOfFile(FID: MainFileID);
567 SourceLocation IDLocation = FileStart.getLocWithOffset(Offset: SrcLocOffset);
568 PPCallbacks *CB = nullptr;
569 if (!SrcLocOffset) {
570 // We need to call EnterSourceFile when SrcLocOffset is zero to initialize
571 // the preprocessor.
572 bool PPFailed = PP.EnterSourceFile(FID: MainFileID, Dir: nullptr, Loc: SourceLocation());
573 assert(!PPFailed && "Preprocess must be able to enter the main file.");
574 (void)PPFailed;
575 CB = MDC->getPPCallbacks();
576 } else {
577 // When SrcLocOffset is non-zero, the preprocessor has already been
578 // initialized through a previous call of computeDependencies. We want to
579 // preserve the PP's state, hence we do not call EnterSourceFile again.
580 MDC->attachToPreprocessor(PP);
581 CB = MDC->getPPCallbacks();
582
583 FileID PrevFID;
584 SrcMgr::CharacteristicKind FileType = SM.getFileCharacteristic(Loc: IDLocation);
585 CB->LexedFileChanged(FID: MainFileID,
586 Reason: PPChainedCallbacks::LexedFileChangeReason::EnterFile,
587 FileType, PrevFID, Loc: IDLocation);
588 }
589
590 // FIXME: Scan modules asynchronously here as well.
591
592 SrcLocOffset++;
593 SmallVector<IdentifierLoc, 2> Path;
594 IdentifierInfo *ModuleID = PP.getIdentifierInfo(Name: ModuleName);
595 Path.emplace_back(Args&: IDLocation, Args&: ModuleID);
596 auto ModResult = CI.loadModule(ImportLoc: IDLocation, Path, Visibility: Module::Hidden, IsInclusionDirective: false);
597
598 assert(CB && "Must have PPCallbacks after module loading");
599 CB->moduleImport(ImportLoc: SourceLocation(), Path, Imported: ModResult);
600 // Note that we are calling the CB's EndOfMainFile function, which
601 // forwards the results to the dependency consumer.
602 // It does not indicate the end of processing the fake file.
603 CB->EndOfMainFile();
604
605 if (!ModResult)
606 return false;
607
608 MDC->applyDiscoveredDependencies(CI&: ModuleInvocation);
609
610 if (!Controller.finalize(ScanInstance&: CI, NewInvocation&: ModuleInvocation))
611 return false;
612
613 Consumer.handleBuildCommand(
614 Cmd: {.Executable: CommandLine[0], .Arguments: ModuleInvocation.getCC1CommandLine()});
615
616 return true;
617}
618