1//===- ClangScanDeps.cpp - Implementation of clang-scan-deps --------------===//
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/DependencyScanning/DependencyScanningService.h"
10#include "clang/DependencyScanning/DependencyScanningWorker.h"
11#include "clang/Driver/Compilation.h"
12#include "clang/Driver/Driver.h"
13#include "clang/Frontend/CompilerInstance.h"
14#include "clang/Frontend/TextDiagnosticPrinter.h"
15#include "clang/Tooling/CommonOptionsParser.h"
16#include "clang/Tooling/DependencyScanningTool.h"
17#include "clang/Tooling/JSONCompilationDatabase.h"
18#include "clang/Tooling/Tooling.h"
19#include "llvm/ADT/STLExtras.h"
20#include "llvm/ADT/Twine.h"
21#include "llvm/Support/CommandLine.h"
22#include "llvm/Support/FileSystem.h"
23#include "llvm/Support/FileUtilities.h"
24#include "llvm/Support/Format.h"
25#include "llvm/Support/JSON.h"
26#include "llvm/Support/LLVMDriver.h"
27#include "llvm/Support/MemoryBuffer.h"
28#include "llvm/Support/Program.h"
29#include "llvm/Support/Signals.h"
30#include "llvm/Support/TargetSelect.h"
31#include "llvm/Support/ThreadPool.h"
32#include "llvm/Support/Threading.h"
33#include "llvm/Support/Timer.h"
34#include "llvm/Support/VirtualFileSystem.h"
35#include "llvm/TargetParser/Host.h"
36#include <memory>
37#include <mutex>
38#include <optional>
39#include <thread>
40
41#include "Opts.inc"
42
43using namespace clang;
44using namespace tooling;
45using namespace dependencies;
46
47namespace {
48
49using namespace llvm::opt;
50enum ID {
51 OPT_INVALID = 0, // This is not an option ID.
52#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),
53#include "Opts.inc"
54#undef OPTION
55};
56
57#define OPTTABLE_STR_TABLE_CODE
58#include "Opts.inc"
59#undef OPTTABLE_STR_TABLE_CODE
60
61#define OPTTABLE_PREFIXES_TABLE_CODE
62#include "Opts.inc"
63#undef OPTTABLE_PREFIXES_TABLE_CODE
64
65const llvm::opt::OptTable::Info InfoTable[] = {
66#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),
67#include "Opts.inc"
68#undef OPTION
69};
70
71class ScanDepsOptTable : public llvm::opt::GenericOptTable {
72public:
73 ScanDepsOptTable()
74 : GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable) {
75 setGroupedShortOptions(true);
76 }
77};
78
79enum ResourceDirRecipeKind {
80 RDRK_ModifyCompilerPath,
81 RDRK_InvokeCompiler,
82};
83
84/// The format that is output by the dependency scanner.
85enum class ScanningOutputFormat {
86 /// This is the Makefile compatible dep format. This will include all of the
87 /// deps necessary for an implicit modules build, but won't include any
88 /// intermodule dependency information.
89 Make,
90
91 /// This outputs the full clang module dependency graph suitable for use for
92 /// explicitly building modules.
93 Full,
94
95 /// This outputs the dependency graph for standard c++ modules in P1689R5
96 /// format.
97 P1689,
98};
99
100static std::string OutputFileName = "-";
101static ScanningMode ScanMode = ScanningMode::DependencyDirectivesScan;
102static ScanningOutputFormat Format = ScanningOutputFormat::Make;
103static ScanningOptimizations OptimizeArgs;
104static std::string ModuleFilesDir;
105static bool EagerLoadModules;
106static bool CacheNegativeStats;
107static unsigned NumThreads = 0;
108static std::string CompilationDB;
109static std::optional<std::string> ModuleNames;
110static std::vector<std::string> ModuleDepTargets;
111static std::string TranslationUnitFile;
112static ResourceDirRecipeKind ResourceDirRecipe;
113static bool Verbose;
114static bool AsyncScanModules;
115static bool PrintTiming;
116static bool EmitVisibleModules;
117static llvm::BumpPtrAllocator Alloc;
118static llvm::StringSaver Saver{Alloc};
119static std::vector<const char *> CommandLine;
120
121#ifndef NDEBUG
122static constexpr bool DoRoundTripDefault = true;
123#else
124static constexpr bool DoRoundTripDefault = false;
125#endif
126
127static bool RoundTripArgs = DoRoundTripDefault;
128static bool NoFlushModuleCache = false;
129static bool VerbatimArgs = false;
130
131static void ParseArgs(int argc, char **argv) {
132 ScanDepsOptTable Tbl;
133 llvm::StringRef ToolName = argv[0];
134 llvm::opt::InputArgList Args =
135 Tbl.parseArgs(Argc: argc, Argv: argv, Unknown: OPT_UNKNOWN, Saver, ErrorFn: [&](StringRef Msg) {
136 llvm::errs() << Msg << '\n';
137 std::exit(status: 1);
138 });
139
140 if (Args.hasArg(Ids: OPT_help)) {
141 Tbl.printHelp(OS&: llvm::outs(), Usage: "clang-scan-deps [options]", Title: "clang-scan-deps");
142 std::exit(status: 0);
143 }
144 if (Args.hasArg(Ids: OPT_version)) {
145 llvm::outs() << ToolName << '\n';
146 llvm::cl::PrintVersionMessage();
147 std::exit(status: 0);
148 }
149 if (const llvm::opt::Arg *A = Args.getLastArg(Ids: OPT_mode_EQ)) {
150 auto ModeType =
151 llvm::StringSwitch<std::optional<ScanningMode>>(A->getValue())
152 .Case(S: "preprocess-dependency-directives",
153 Value: ScanningMode::DependencyDirectivesScan)
154 .Case(S: "preprocess", Value: ScanningMode::CanonicalPreprocessing)
155 .Default(Value: std::nullopt);
156 if (!ModeType) {
157 llvm::errs() << ToolName
158 << ": for the --mode option: Cannot find option named '"
159 << A->getValue() << "'\n";
160 std::exit(status: 1);
161 }
162 ScanMode = *ModeType;
163 }
164
165 if (const llvm::opt::Arg *A = Args.getLastArg(Ids: OPT_format_EQ)) {
166 auto FormatType =
167 llvm::StringSwitch<std::optional<ScanningOutputFormat>>(A->getValue())
168 .Case(S: "make", Value: ScanningOutputFormat::Make)
169 .Case(S: "p1689", Value: ScanningOutputFormat::P1689)
170 .Case(S: "experimental-full", Value: ScanningOutputFormat::Full)
171 .Default(Value: std::nullopt);
172 if (!FormatType) {
173 llvm::errs() << ToolName
174 << ": for the --format option: Cannot find option named '"
175 << A->getValue() << "'\n";
176 std::exit(status: 1);
177 }
178 Format = *FormatType;
179 }
180
181 std::vector<std::string> OptimizationFlags =
182 Args.getAllArgValues(Id: OPT_optimize_args_EQ);
183 OptimizeArgs = ScanningOptimizations::None;
184 for (const auto &Arg : OptimizationFlags) {
185 auto Optimization =
186 llvm::StringSwitch<std::optional<ScanningOptimizations>>(Arg)
187 .Case(S: "none", Value: ScanningOptimizations::None)
188 .Case(S: "header-search", Value: ScanningOptimizations::HeaderSearch)
189 .Case(S: "system-warnings", Value: ScanningOptimizations::SystemWarnings)
190 .Case(S: "vfs", Value: ScanningOptimizations::VFS)
191 .Case(S: "canonicalize-macros", Value: ScanningOptimizations::Macros)
192 .Case(S: "ignore-current-working-dir",
193 Value: ScanningOptimizations::IgnoreCWD)
194 .Case(S: "all", Value: ScanningOptimizations::All)
195 .Default(Value: std::nullopt);
196 if (!Optimization) {
197 llvm::errs()
198 << ToolName
199 << ": for the --optimize-args option: Cannot find option named '"
200 << Arg << "'\n";
201 std::exit(status: 1);
202 }
203 OptimizeArgs |= *Optimization;
204 }
205 if (OptimizationFlags.empty())
206 OptimizeArgs = ScanningOptimizations::Default;
207
208 if (const llvm::opt::Arg *A = Args.getLastArg(Ids: OPT_module_files_dir_EQ))
209 ModuleFilesDir = A->getValue();
210
211 if (const llvm::opt::Arg *A = Args.getLastArg(Ids: OPT_o))
212 OutputFileName = A->getValue();
213
214 EagerLoadModules = Args.hasArg(Ids: OPT_eager_load_pcm);
215
216 CacheNegativeStats = Args.hasArg(Ids: OPT_cache_negative_stats);
217
218 if (const llvm::opt::Arg *A = Args.getLastArg(Ids: OPT_j)) {
219 StringRef S{A->getValue()};
220 if (!llvm::to_integer(S, Num&: NumThreads, Base: 0)) {
221 llvm::errs() << ToolName << ": for the -j option: '" << S
222 << "' value invalid for uint argument!\n";
223 std::exit(status: 1);
224 }
225 }
226
227 if (const llvm::opt::Arg *A = Args.getLastArg(Ids: OPT_compilation_database_EQ))
228 CompilationDB = A->getValue();
229
230 if (const llvm::opt::Arg *A = Args.getLastArg(Ids: OPT_module_names_EQ))
231 ModuleNames = A->getValue();
232
233 for (const llvm::opt::Arg *A : Args.filtered(Ids: OPT_dependency_target_EQ))
234 ModuleDepTargets.emplace_back(args: A->getValue());
235
236 if (const llvm::opt::Arg *A = Args.getLastArg(Ids: OPT_tu_buffer_path_EQ))
237 TranslationUnitFile = A->getValue();
238
239 if (const llvm::opt::Arg *A = Args.getLastArg(Ids: OPT_resource_dir_recipe_EQ)) {
240 auto Kind =
241 llvm::StringSwitch<std::optional<ResourceDirRecipeKind>>(A->getValue())
242 .Case(S: "modify-compiler-path", Value: RDRK_ModifyCompilerPath)
243 .Case(S: "invoke-compiler", Value: RDRK_InvokeCompiler)
244 .Default(Value: std::nullopt);
245 if (!Kind) {
246 llvm::errs() << ToolName
247 << ": for the --resource-dir-recipe option: Cannot find "
248 "option named '"
249 << A->getValue() << "'\n";
250 std::exit(status: 1);
251 }
252 ResourceDirRecipe = *Kind;
253 }
254
255 PrintTiming = Args.hasArg(Ids: OPT_print_timing);
256
257 EmitVisibleModules = Args.hasArg(Ids: OPT_emit_visible_modules);
258
259 Verbose = Args.hasArg(Ids: OPT_verbose);
260
261 AsyncScanModules = Args.hasArg(Ids: OPT_async_scan_modules);
262
263 RoundTripArgs = Args.hasArg(Ids: OPT_round_trip_args);
264
265 NoFlushModuleCache = Args.hasArg(Ids: OPT_no_flush_module_cache);
266
267 VerbatimArgs = Args.hasArg(Ids: OPT_verbatim_args);
268
269 if (const llvm::opt::Arg *A = Args.getLastArgNoClaim(Ids: OPT_DASH_DASH))
270 CommandLine.assign(first: A->getValues().begin(), last: A->getValues().end());
271}
272
273class SharedStream {
274public:
275 SharedStream(raw_ostream &OS) : OS(OS) {}
276 void applyLocked(llvm::function_ref<void(raw_ostream &OS)> Fn) {
277 std::unique_lock<std::mutex> LockGuard(Lock);
278 Fn(OS);
279 OS.flush();
280 }
281
282private:
283 std::mutex Lock;
284 raw_ostream &OS;
285};
286
287class ResourceDirectoryCache {
288public:
289 /// findResourceDir finds the resource directory relative to the clang
290 /// compiler being used in Args, by running it with "-print-resource-dir"
291 /// option and cache the results for reuse. \returns resource directory path
292 /// associated with the given invocation command or empty string if the
293 /// compiler path is NOT an absolute path.
294 StringRef findResourceDir(const tooling::CommandLineArguments &Args,
295 bool ClangCLMode) {
296 if (Args.size() < 1)
297 return "";
298
299 const std::string &ClangBinaryPath = Args[0];
300 if (!llvm::sys::path::is_absolute(path: ClangBinaryPath))
301 return "";
302
303 const std::string &ClangBinaryName =
304 std::string(llvm::sys::path::filename(path: ClangBinaryPath));
305
306 std::unique_lock<std::mutex> LockGuard(CacheLock);
307 const auto &CachedResourceDir = Cache.find(x: ClangBinaryPath);
308 if (CachedResourceDir != Cache.end())
309 return CachedResourceDir->second;
310
311 const std::array<StringRef, 2> PrintResourceDirArgs{
312 ClangBinaryName,
313 ClangCLMode ? "/clang:-print-resource-dir" : "-print-resource-dir"};
314
315 llvm::SmallString<64> OutputFile, ErrorFile;
316 llvm::sys::fs::createTemporaryFile(Prefix: "print-resource-dir-output",
317 Suffix: "" /*no-suffix*/, ResultPath&: OutputFile);
318 llvm::sys::fs::createTemporaryFile(Prefix: "print-resource-dir-error",
319 Suffix: "" /*no-suffix*/, ResultPath&: ErrorFile);
320 llvm::FileRemover OutputRemover(OutputFile.c_str());
321 llvm::FileRemover ErrorRemover(ErrorFile.c_str());
322 std::optional<StringRef> Redirects[] = {
323 {""}, // Stdin
324 OutputFile.str(),
325 ErrorFile.str(),
326 };
327 if (llvm::sys::ExecuteAndWait(Program: ClangBinaryPath, Args: PrintResourceDirArgs, Env: {},
328 Redirects)) {
329 auto ErrorBuf =
330 llvm::MemoryBuffer::getFile(Filename: ErrorFile.c_str(), /*IsText=*/true);
331 llvm::errs() << ErrorBuf.get()->getBuffer();
332 return "";
333 }
334
335 auto OutputBuf =
336 llvm::MemoryBuffer::getFile(Filename: OutputFile.c_str(), /*IsText=*/true);
337 if (!OutputBuf)
338 return "";
339 StringRef Output = OutputBuf.get()->getBuffer().rtrim(Char: '\n');
340
341 return Cache[ClangBinaryPath] = Output.str();
342 }
343
344private:
345 std::map<std::string, std::string> Cache;
346 std::mutex CacheLock;
347};
348
349} // end anonymous namespace
350
351/// Prints any diagnostics produced during a dependency scan.
352static void handleDiagnostics(StringRef Input, StringRef Diagnostics,
353 SharedStream &Errs) {
354 if (Diagnostics.empty())
355 return;
356
357 Errs.applyLocked(Fn: [&](raw_ostream &OS) {
358 OS << "Diagnostics while scanning dependencies for '" << Input << "':\n";
359 OS << Diagnostics;
360 });
361}
362
363template <typename Container>
364static auto toJSONStrings(llvm::json::OStream &JOS, Container &&Strings) {
365 return [&JOS, Strings = std::forward<Container>(Strings)] {
366 for (StringRef Str : Strings)
367 // Not reporting SDKSettings.json so that test checks can remain (mostly)
368 // platform-agnostic.
369 if (!Str.ends_with(Suffix: "SDKSettings.json"))
370 JOS.value(V: Str);
371 };
372}
373
374// Technically, we don't need to sort the dependency list to get determinism.
375// Leaving these be will simply preserve the import order.
376static auto toJSONSorted(llvm::json::OStream &JOS, std::vector<ModuleID> V) {
377 llvm::sort(C&: V);
378 return [&JOS, V = std::move(V)] {
379 for (const ModuleID &MID : V)
380 JOS.object(Contents: [&] {
381 JOS.attribute(Key: "context-hash", Contents: StringRef(MID.ContextHash));
382 JOS.attribute(Key: "module-name", Contents: StringRef(MID.ModuleName));
383 });
384 };
385}
386
387static auto toJSONSorted(llvm::json::OStream &JOS,
388 SmallVector<Module::LinkLibrary, 2> LinkLibs) {
389 llvm::sort(C&: LinkLibs, Comp: [](const auto &LHS, const auto &RHS) {
390 return LHS.Library < RHS.Library;
391 });
392 return [&JOS, LinkLibs = std::move(LinkLibs)] {
393 for (const auto &LL : LinkLibs)
394 JOS.object(Contents: [&] {
395 JOS.attribute(Key: "isFramework", Contents: LL.IsFramework);
396 JOS.attribute(Key: "link-name", Contents: StringRef(LL.Library));
397 });
398 };
399}
400
401static auto toJSONSorted(llvm::json::OStream &JOS, std::vector<std::string> V) {
402 llvm::sort(C&: V);
403 return [&JOS, V = std::move(V)] {
404 for (const StringRef Entry : V)
405 JOS.value(V: Entry);
406 };
407}
408
409// Thread safe.
410class FullDeps {
411public:
412 FullDeps(size_t NumInputs) : Inputs(NumInputs) {}
413
414 void mergeDeps(StringRef Input, TranslationUnitDeps TUDeps,
415 size_t InputIndex) {
416 mergeDeps(Graph: std::move(TUDeps.ModuleGraph), InputIndex);
417
418 InputDeps ID;
419 ID.FileName = std::string(Input);
420 ID.ContextHash = std::move(TUDeps.ID.ContextHash);
421 ID.FileDeps = std::move(TUDeps.FileDeps);
422 ID.NamedModule = std::move(TUDeps.ID.ModuleName);
423 ID.NamedModuleDeps = std::move(TUDeps.NamedModuleDeps);
424 ID.ClangModuleDeps = std::move(TUDeps.ClangModuleDeps);
425 ID.VisibleModules = std::move(TUDeps.VisibleModules);
426 ID.DriverCommandLine = std::move(TUDeps.DriverCommandLine);
427 ID.Commands = std::move(TUDeps.Commands);
428
429 assert(InputIndex < Inputs.size() && "Input index out of bounds");
430 assert(Inputs[InputIndex].FileName.empty() && "Result already populated");
431 Inputs[InputIndex] = std::move(ID);
432 }
433
434 void mergeDeps(ModuleDepsGraph Graph, size_t InputIndex) {
435 std::vector<ModuleDeps *> NewMDs;
436 {
437 std::unique_lock<std::mutex> ul(Lock);
438 for (ModuleDeps &MD : Graph) {
439 auto I = Modules.find(x: {.ID: MD.ID, .InputIndex: 0});
440 if (I != Modules.end()) {
441 I->first.InputIndex = std::min(a: I->first.InputIndex, b: InputIndex);
442 continue;
443 }
444 auto Res = Modules.insert(hint: I, x: {{.ID: MD.ID, .InputIndex: InputIndex}, std::move(MD)});
445 NewMDs.push_back(x: &Res->second);
446 }
447 }
448 // First call to \c getBuildArguments is somewhat expensive. Let's call it
449 // on the current thread (instead of the main one), and outside the
450 // critical section.
451 for (ModuleDeps *MD : NewMDs)
452 (void)MD->getBuildArguments();
453 }
454
455 bool roundTripCommand(ArrayRef<std::string> ArgStrs,
456 DiagnosticsEngine &Diags) {
457 if (ArgStrs.empty() || ArgStrs[0] != "-cc1")
458 return false;
459 SmallVector<const char *> Args;
460 for (const std::string &Arg : ArgStrs)
461 Args.push_back(Elt: Arg.c_str());
462 return !CompilerInvocation::checkCC1RoundTrip(Args, Diags);
463 }
464
465 // Returns \c true if any command lines fail to round-trip. We expect
466 // commands already be canonical when output by the scanner.
467 bool roundTripCommands(raw_ostream &ErrOS) {
468 DiagnosticOptions DiagOpts;
469 TextDiagnosticPrinter DiagConsumer(ErrOS, DiagOpts);
470 IntrusiveRefCntPtr<DiagnosticsEngine> Diags =
471 CompilerInstance::createDiagnostics(VFS&: *llvm::vfs::getRealFileSystem(),
472 Opts&: DiagOpts, Client: &DiagConsumer,
473 /*ShouldOwnClient=*/false);
474
475 for (auto &&M : Modules)
476 if (roundTripCommand(ArgStrs: M.second.getBuildArguments(), Diags&: *Diags))
477 return true;
478
479 for (auto &&I : Inputs)
480 for (const auto &Cmd : I.Commands)
481 if (roundTripCommand(ArgStrs: Cmd.Arguments, Diags&: *Diags))
482 return true;
483
484 return false;
485 }
486
487 void printFullOutput(raw_ostream &OS) {
488 // Skip sorting modules and constructing the JSON object if the output
489 // cannot be observed anyway. This makes timings less noisy.
490 if (&OS == &llvm::nulls())
491 return;
492
493 // Sort the modules by name to get a deterministic order.
494 std::vector<IndexedModuleID> ModuleIDs;
495 for (auto &&M : Modules)
496 ModuleIDs.push_back(x: M.first);
497 llvm::sort(C&: ModuleIDs);
498
499 llvm::json::OStream JOS(OS, /*IndentSize=*/2);
500
501 JOS.object(Contents: [&] {
502 JOS.attributeArray(Key: "modules", Contents: [&] {
503 for (auto &&ModID : ModuleIDs) {
504 auto &MD = Modules[ModID];
505 JOS.object(Contents: [&] {
506 if (MD.IsInStableDirectories)
507 JOS.attribute(Key: "is-in-stable-directories",
508 Contents: MD.IsInStableDirectories);
509 JOS.attributeArray(Key: "clang-module-deps",
510 Contents: toJSONSorted(JOS, V: MD.ClangModuleDeps));
511 JOS.attribute(Key: "clang-modulemap-file",
512 Contents: StringRef(MD.ClangModuleMapFile));
513 JOS.attributeArray(Key: "command-line",
514 Contents: toJSONStrings(JOS, Strings: MD.getBuildArguments()));
515 JOS.attribute(Key: "context-hash", Contents: StringRef(MD.ID.ContextHash));
516 JOS.attributeArray(Key: "file-deps", Contents: [&] {
517 MD.forEachFileDep(Cb: [&](StringRef FileDep) {
518 // Not reporting SDKSettings.json so that test checks can remain
519 // (mostly) platform-agnostic.
520 if (!FileDep.ends_with(Suffix: "SDKSettings.json"))
521 JOS.value(V: FileDep);
522 });
523 });
524 JOS.attributeArray(Key: "link-libraries",
525 Contents: toJSONSorted(JOS, LinkLibs: MD.LinkLibraries));
526 JOS.attribute(Key: "name", Contents: StringRef(MD.ID.ModuleName));
527 });
528 }
529 });
530
531 JOS.attributeArray(Key: "translation-units", Contents: [&] {
532 for (auto &&I : Inputs) {
533 JOS.object(Contents: [&] {
534 JOS.attributeArray(Key: "commands", Contents: [&] {
535 if (I.DriverCommandLine.empty()) {
536 for (const auto &Cmd : I.Commands) {
537 JOS.object(Contents: [&] {
538 JOS.attribute(Key: "clang-context-hash",
539 Contents: StringRef(I.ContextHash));
540 if (!I.NamedModule.empty())
541 JOS.attribute(Key: "named-module", Contents: (I.NamedModule));
542 if (!I.NamedModuleDeps.empty())
543 JOS.attributeArray(Key: "named-module-deps", Contents: [&] {
544 for (const auto &Dep : I.NamedModuleDeps)
545 JOS.value(V: Dep);
546 });
547 JOS.attributeArray(Key: "clang-module-deps",
548 Contents: toJSONSorted(JOS, V: I.ClangModuleDeps));
549 JOS.attributeArray(Key: "command-line",
550 Contents: toJSONStrings(JOS, Strings: Cmd.Arguments));
551 JOS.attribute(Key: "executable", Contents: StringRef(Cmd.Executable));
552 JOS.attributeArray(Key: "file-deps",
553 Contents: toJSONStrings(JOS, Strings&: I.FileDeps));
554 JOS.attribute(Key: "input-file", Contents: StringRef(I.FileName));
555 if (EmitVisibleModules)
556 JOS.attributeArray(Key: "visible-clang-modules",
557 Contents: toJSONSorted(JOS, V: I.VisibleModules));
558 });
559 }
560 } else {
561 JOS.object(Contents: [&] {
562 JOS.attribute(Key: "clang-context-hash", Contents: StringRef(I.ContextHash));
563 if (!I.NamedModule.empty())
564 JOS.attribute(Key: "named-module", Contents: (I.NamedModule));
565 if (!I.NamedModuleDeps.empty())
566 JOS.attributeArray(Key: "named-module-deps", Contents: [&] {
567 for (const auto &Dep : I.NamedModuleDeps)
568 JOS.value(V: Dep);
569 });
570 JOS.attributeArray(Key: "clang-module-deps",
571 Contents: toJSONSorted(JOS, V: I.ClangModuleDeps));
572 JOS.attributeArray(Key: "command-line",
573 Contents: toJSONStrings(JOS, Strings&: I.DriverCommandLine));
574 JOS.attribute(Key: "executable", Contents: "clang");
575 JOS.attributeArray(Key: "file-deps",
576 Contents: toJSONStrings(JOS, Strings&: I.FileDeps));
577 JOS.attribute(Key: "input-file", Contents: StringRef(I.FileName));
578 if (EmitVisibleModules)
579 JOS.attributeArray(Key: "visible-clang-modules",
580 Contents: toJSONSorted(JOS, V: I.VisibleModules));
581 });
582 }
583 });
584 });
585 }
586 });
587 });
588 }
589
590private:
591 struct IndexedModuleID {
592 ModuleID ID;
593
594 // FIXME: This is mutable so that it can still be updated after insertion
595 // into an unordered associative container. This is "fine", since this
596 // field doesn't contribute to the hash, but it's a brittle hack.
597 mutable size_t InputIndex;
598
599 bool operator==(const IndexedModuleID &Other) const {
600 return ID == Other.ID;
601 }
602
603 bool operator<(const IndexedModuleID &Other) const {
604 /// We need the output of clang-scan-deps to be deterministic. However,
605 /// the dependency graph may contain two modules with the same name. How
606 /// do we decide which one to print first? If we made that decision based
607 /// on the context hash, the ordering would be deterministic, but
608 /// different across machines. This can happen for example when the inputs
609 /// or the SDKs (which both contribute to the "context" hash) live in
610 /// different absolute locations. We solve that by tracking the index of
611 /// the first input TU that (transitively) imports the dependency, which
612 /// is always the same for the same input, resulting in deterministic
613 /// sorting that's also reproducible across machines.
614 return std::tie(args: ID.ModuleName, args&: InputIndex) <
615 std::tie(args: Other.ID.ModuleName, args&: Other.InputIndex);
616 }
617
618 struct Hasher {
619 std::size_t operator()(const IndexedModuleID &IMID) const {
620 return llvm::hash_value(ID: IMID.ID);
621 }
622 };
623 };
624
625 struct InputDeps {
626 std::string FileName;
627 std::string ContextHash;
628 std::vector<std::string> FileDeps;
629 std::string NamedModule;
630 std::vector<std::string> NamedModuleDeps;
631 std::vector<ModuleID> ClangModuleDeps;
632 std::vector<std::string> VisibleModules;
633 std::vector<std::string> DriverCommandLine;
634 std::vector<Command> Commands;
635 };
636
637 std::mutex Lock;
638 std::unordered_map<IndexedModuleID, ModuleDeps, IndexedModuleID::Hasher>
639 Modules;
640 std::vector<InputDeps> Inputs;
641};
642
643static bool handleModuleResult(StringRef ModuleName,
644 llvm::Expected<TranslationUnitDeps> &MaybeTUDeps,
645 FullDeps &FD, size_t InputIndex,
646 SharedStream &OS, SharedStream &Errs) {
647 if (!MaybeTUDeps) {
648 llvm::handleAllErrors(E: MaybeTUDeps.takeError(),
649 Handlers: [&ModuleName, &Errs](llvm::StringError &Err) {
650 Errs.applyLocked(Fn: [&](raw_ostream &OS) {
651 OS << "Error while scanning dependencies for "
652 << ModuleName << ":\n";
653 OS << Err.getMessage();
654 });
655 });
656 return true;
657 }
658 FD.mergeDeps(Graph: std::move(MaybeTUDeps->ModuleGraph), InputIndex);
659 return false;
660}
661
662static void handleErrorWithInfoString(StringRef Info, llvm::Error E,
663 SharedStream &OS, SharedStream &Errs) {
664 llvm::handleAllErrors(E: std::move(E), Handlers: [&Info, &Errs](llvm::StringError &Err) {
665 Errs.applyLocked(Fn: [&](raw_ostream &OS) {
666 OS << "Error: " << Info << ":\n";
667 OS << Err.getMessage();
668 });
669 });
670}
671
672class P1689Deps {
673public:
674 void printDependencies(raw_ostream &OS) {
675 addSourcePathsToRequires();
676 // Sort the modules by name to get a deterministic order.
677 llvm::sort(C&: Rules, Comp: [](const P1689Rule &A, const P1689Rule &B) {
678 return A.PrimaryOutput < B.PrimaryOutput;
679 });
680
681 using namespace llvm::json;
682 Array OutputRules;
683 for (const P1689Rule &R : Rules) {
684 Object O{{.K: "primary-output", .V: R.PrimaryOutput}};
685
686 if (R.Provides) {
687 Array Provides;
688 Object Provided{{.K: "logical-name", .V: R.Provides->ModuleName},
689 {.K: "source-path", .V: R.Provides->SourcePath},
690 {.K: "is-interface", .V: R.Provides->IsStdCXXModuleInterface}};
691 Provides.push_back(E: std::move(Provided));
692 O.insert(E: {.K: "provides", .V: std::move(Provides)});
693 }
694
695 Array Requires;
696 for (const P1689ModuleInfo &Info : R.Requires) {
697 Object RequiredInfo{{.K: "logical-name", .V: Info.ModuleName}};
698 if (!Info.SourcePath.empty())
699 RequiredInfo.insert(E: {.K: "source-path", .V: Info.SourcePath});
700 Requires.push_back(E: std::move(RequiredInfo));
701 }
702
703 if (!Requires.empty())
704 O.insert(E: {.K: "requires", .V: std::move(Requires)});
705
706 OutputRules.push_back(E: std::move(O));
707 }
708
709 Object Output{
710 {.K: "version", .V: 1}, {.K: "revision", .V: 0}, {.K: "rules", .V: std::move(OutputRules)}};
711
712 OS << llvm::formatv(Fmt: "{0:2}\n", Vals: Value(std::move(Output)));
713 }
714
715 void addRules(P1689Rule &Rule) {
716 std::unique_lock<std::mutex> LockGuard(Lock);
717 Rules.push_back(x: Rule);
718 }
719
720private:
721 void addSourcePathsToRequires() {
722 llvm::DenseMap<StringRef, StringRef> ModuleSourceMapper;
723 for (const P1689Rule &R : Rules)
724 if (R.Provides && !R.Provides->SourcePath.empty())
725 ModuleSourceMapper[R.Provides->ModuleName] = R.Provides->SourcePath;
726
727 for (P1689Rule &R : Rules) {
728 for (P1689ModuleInfo &Info : R.Requires) {
729 auto Iter = ModuleSourceMapper.find(Val: Info.ModuleName);
730 if (Iter != ModuleSourceMapper.end())
731 Info.SourcePath = Iter->second;
732 }
733 }
734 }
735
736 std::mutex Lock;
737 std::vector<P1689Rule> Rules;
738};
739
740/// Construct a path for the explicitly built PCM.
741static std::string constructPCMPath(ModuleID MID, StringRef OutputDir) {
742 SmallString<256> ExplicitPCMPath(OutputDir);
743 llvm::sys::path::append(path&: ExplicitPCMPath, a: MID.ContextHash,
744 b: MID.ModuleName + "-" + MID.ContextHash + ".pcm");
745 return std::string(ExplicitPCMPath);
746}
747
748static std::string lookupModuleOutput(const ModuleDeps &MD,
749 ModuleOutputKind MOK,
750 StringRef OutputDir) {
751 std::string PCMPath = constructPCMPath(MID: MD.ID, OutputDir);
752 switch (MOK) {
753 case ModuleOutputKind::ModuleFile:
754 return PCMPath;
755 case ModuleOutputKind::DependencyFile:
756 return PCMPath + ".d";
757 case ModuleOutputKind::DependencyTargets:
758 // Null-separate the list of targets.
759 return join(R&: ModuleDepTargets, Separator: StringRef("\0", 1));
760 case ModuleOutputKind::DiagnosticSerializationFile:
761 return PCMPath + ".diag";
762 }
763 llvm_unreachable("Fully covered switch above!");
764}
765
766static std::string getModuleCachePath(ArrayRef<std::string> Args) {
767 for (StringRef Arg : llvm::reverse(C&: Args)) {
768 Arg.consume_front(Prefix: "/clang:");
769 if (Arg.consume_front(Prefix: "-fmodules-cache-path="))
770 return std::string(Arg);
771 }
772 SmallString<128> Path;
773 driver::Driver::getDefaultModuleCachePath(Result&: Path);
774 return std::string(Path);
775}
776
777/// Attempts to construct the compilation database from '-compilation-database'
778/// or from the arguments following the positional '--'.
779static std::unique_ptr<tooling::CompilationDatabase>
780getCompilationDatabase(int argc, char **argv, std::string &ErrorMessage) {
781 ParseArgs(argc, argv);
782
783 if (!(CommandLine.empty() ^ CompilationDB.empty())) {
784 llvm::errs() << "The compilation command line must be provided either via "
785 "'-compilation-database' or after '--'.";
786 return nullptr;
787 }
788
789 if (!CompilationDB.empty())
790 return tooling::JSONCompilationDatabase::loadFromFile(
791 FilePath: CompilationDB, ErrorMessage,
792 Syntax: tooling::JSONCommandLineSyntax::AutoDetect);
793
794 DiagnosticOptions DiagOpts;
795 llvm::IntrusiveRefCntPtr<DiagnosticsEngine> Diags =
796 CompilerInstance::createDiagnostics(VFS&: *llvm::vfs::getRealFileSystem(),
797 Opts&: DiagOpts);
798 driver::Driver TheDriver(CommandLine[0], llvm::sys::getDefaultTargetTriple(),
799 *Diags);
800 TheDriver.setCheckInputsExist(false);
801 std::unique_ptr<driver::Compilation> C(
802 TheDriver.BuildCompilation(Args: CommandLine));
803 if (!C || C->getJobs().empty())
804 return nullptr;
805
806 auto Cmd = C->getJobs().begin();
807 auto CI = std::make_unique<CompilerInvocation>();
808 CompilerInvocation::CreateFromArgs(Res&: *CI, CommandLineArgs: Cmd->getArguments(), Diags&: *Diags,
809 Argv0: CommandLine[0]);
810 if (!CI)
811 return nullptr;
812
813 FrontendOptions &FEOpts = CI->getFrontendOpts();
814 if (FEOpts.Inputs.size() != 1) {
815 llvm::errs()
816 << "Exactly one input file is required in the per-file mode ('--').\n";
817 return nullptr;
818 }
819
820 // There might be multiple jobs for a compilation. Extract the specified
821 // output filename from the last job.
822 auto LastCmd = C->getJobs().end();
823 LastCmd--;
824 if (LastCmd->getOutputFilenames().size() != 1) {
825 llvm::errs()
826 << "Exactly one output file is required in the per-file mode ('--').\n";
827 return nullptr;
828 }
829 StringRef OutputFile = LastCmd->getOutputFilenames().front();
830
831 class InplaceCompilationDatabase : public tooling::CompilationDatabase {
832 public:
833 InplaceCompilationDatabase(StringRef InputFile, StringRef OutputFile,
834 ArrayRef<const char *> CommandLine)
835 : Command(".", InputFile, {}, OutputFile) {
836 for (auto *C : CommandLine)
837 Command.CommandLine.push_back(x: C);
838 }
839
840 std::vector<tooling::CompileCommand>
841 getCompileCommands(StringRef FilePath) const override {
842 if (FilePath != Command.Filename)
843 return {};
844 return {Command};
845 }
846
847 std::vector<std::string> getAllFiles() const override {
848 return {Command.Filename};
849 }
850
851 std::vector<tooling::CompileCommand>
852 getAllCompileCommands() const override {
853 return {Command};
854 }
855
856 private:
857 tooling::CompileCommand Command;
858 };
859
860 return std::make_unique<InplaceCompilationDatabase>(
861 args: FEOpts.Inputs[0].getFile(), args&: OutputFile, args&: CommandLine);
862}
863
864int clang_scan_deps_main(int argc, char **argv, const llvm::ToolContext &) {
865 llvm::InitializeAllTargetInfos();
866 std::string ErrorMessage;
867 std::unique_ptr<tooling::CompilationDatabase> Compilations =
868 getCompilationDatabase(argc, argv, ErrorMessage);
869 if (!Compilations) {
870 llvm::errs() << ErrorMessage << "\n";
871 return 1;
872 }
873
874 llvm::cl::PrintOptionValues();
875
876 if (!VerbatimArgs) {
877 // Expand response files in advance, so that we can "see" all the arguments
878 // when adjusting below.
879 Compilations = expandResponseFiles(Base: std::move(Compilations),
880 FS: llvm::vfs::getRealFileSystem());
881
882 Compilations = inferTargetAndDriverMode(Base: std::move(Compilations));
883
884 Compilations = inferToolLocation(Base: std::move(Compilations));
885 }
886
887 // The command options are rewritten to run Clang in preprocessor only mode.
888 auto AdjustingCompilations =
889 std::make_unique<tooling::ArgumentsAdjustingCompilations>(
890 args: std::move(Compilations));
891 ResourceDirectoryCache ResourceDirCache;
892
893 auto ArgsAdjuster =
894 [&ResourceDirCache](const tooling::CommandLineArguments &Args,
895 StringRef FileName) {
896 std::string LastO;
897 bool HasResourceDir = false;
898 bool ClangCLMode = false;
899 auto FlagsEnd = llvm::find(Range: Args, Val: "--");
900 if (FlagsEnd != Args.begin()) {
901 ClangCLMode =
902 llvm::sys::path::stem(path: Args[0]).contains_insensitive(Other: "clang-cl") ||
903 llvm::is_contained(Range: Args, Element: "--driver-mode=cl");
904
905 // Reverse scan, starting at the end or at the element before "--".
906 auto R = std::make_reverse_iterator(i: FlagsEnd);
907 auto E = Args.rend();
908 // Don't include Args[0] in the iteration; that's the executable, not
909 // an option.
910 if (E != R)
911 E--;
912 for (auto I = R; I != E; ++I) {
913 StringRef Arg = *I;
914 if (ClangCLMode) {
915 // Ignore arguments that are preceded by "-Xclang".
916 if ((I + 1) != E && I[1] == "-Xclang")
917 continue;
918 if (LastO.empty()) {
919 // With clang-cl, the output obj file can be specified with
920 // "/opath", "/o path", "/Fopath", and the dash counterparts.
921 // Also, clang-cl adds ".obj" extension if none is found.
922 if ((Arg == "-o" || Arg == "/o") && I != R)
923 LastO = I[-1]; // Next argument (reverse iterator)
924 else if (Arg.starts_with(Prefix: "/Fo") || Arg.starts_with(Prefix: "-Fo"))
925 LastO = Arg.drop_front(N: 3).str();
926 else if (Arg.starts_with(Prefix: "/o") || Arg.starts_with(Prefix: "-o"))
927 LastO = Arg.drop_front(N: 2).str();
928
929 if (!LastO.empty() && !llvm::sys::path::has_extension(path: LastO))
930 LastO.append(s: ".obj");
931 }
932 }
933 if (Arg == "-resource-dir")
934 HasResourceDir = true;
935 }
936 }
937 tooling::CommandLineArguments AdjustedArgs(Args.begin(), FlagsEnd);
938 // The clang-cl driver passes "-o -" to the frontend. Inject the real
939 // file here to ensure "-MT" can be deduced if need be.
940 if (ClangCLMode && !LastO.empty()) {
941 AdjustedArgs.push_back(x: "/clang:-o");
942 AdjustedArgs.push_back(x: "/clang:" + LastO);
943 }
944
945 if (!HasResourceDir && ResourceDirRecipe == RDRK_InvokeCompiler) {
946 StringRef ResourceDir =
947 ResourceDirCache.findResourceDir(Args, ClangCLMode);
948 if (!ResourceDir.empty()) {
949 AdjustedArgs.push_back(x: "-resource-dir");
950 AdjustedArgs.push_back(x: std::string(ResourceDir));
951 }
952 }
953 AdjustedArgs.insert(position: AdjustedArgs.end(), first: FlagsEnd, last: Args.end());
954 return AdjustedArgs;
955 };
956
957 if (!VerbatimArgs)
958 AdjustingCompilations->appendArgumentsAdjuster(Adjuster: ArgsAdjuster);
959
960 SharedStream Errs(llvm::errs());
961
962 std::optional<llvm::raw_fd_ostream> FileOS;
963 llvm::raw_ostream &ThreadUnsafeDependencyOS = [&]() -> llvm::raw_ostream & {
964 if (OutputFileName == "-")
965 return llvm::outs();
966
967 if (OutputFileName == "/dev/null")
968 return llvm::nulls();
969
970 std::error_code EC;
971 FileOS.emplace(args&: OutputFileName, args&: EC, args: llvm::sys::fs::OF_Text);
972 if (EC) {
973 llvm::errs() << "Failed to open output file '" << OutputFileName
974 << "': " << EC.message() << '\n';
975 std::exit(status: 1);
976 }
977 return *FileOS;
978 }();
979 SharedStream DependencyOS(ThreadUnsafeDependencyOS);
980
981 std::vector<tooling::CompileCommand> Inputs =
982 AdjustingCompilations->getAllCompileCommands();
983
984 std::atomic<bool> HadErrors(false);
985 std::optional<FullDeps> FD;
986 P1689Deps PD;
987
988 std::mutex Lock;
989 size_t Index = 0;
990 auto GetNextInputIndex = [&]() -> std::optional<size_t> {
991 std::unique_lock<std::mutex> LockGuard(Lock);
992 if (Index < Inputs.size())
993 return Index++;
994 return {};
995 };
996
997 if (Format == ScanningOutputFormat::Full)
998 FD.emplace(args: !ModuleNames ? Inputs.size() : 0);
999
1000 std::atomic<size_t> NumStatusCalls = 0;
1001 std::atomic<size_t> NumOpenFileForReadCalls = 0;
1002 std::atomic<size_t> NumDirBeginCalls = 0;
1003 std::atomic<size_t> NumGetRealPathCalls = 0;
1004 std::atomic<size_t> NumExistsCalls = 0;
1005 std::atomic<size_t> NumIsLocalCalls = 0;
1006
1007 auto ScanningTask = [&](DependencyScanningService &Service) {
1008 DependencyScanningTool WorkerTool(Service);
1009
1010 llvm::DenseSet<ModuleID> AlreadySeenModules;
1011 while (auto MaybeInputIndex = GetNextInputIndex()) {
1012 size_t LocalIndex = *MaybeInputIndex;
1013 const tooling::CompileCommand *Input = &Inputs[LocalIndex];
1014 std::string Filename = std::move(Input->Filename);
1015 std::string CWD = std::move(Input->Directory);
1016
1017 std::string S;
1018 llvm::raw_string_ostream OS(S);
1019 DiagnosticOptions DiagOpts;
1020 DiagOpts.ShowCarets = false;
1021 TextDiagnosticPrinter DiagConsumer(OS, DiagOpts);
1022
1023 std::string OutputDir(ModuleFilesDir);
1024 if (OutputDir.empty())
1025 OutputDir = getModuleCachePath(Args: Input->CommandLine);
1026 auto LookupOutput = [&](const ModuleDeps &MD, ModuleOutputKind MOK) {
1027 return ::lookupModuleOutput(MD, MOK, OutputDir);
1028 };
1029
1030 // Run the tool on it.
1031 if (Format == ScanningOutputFormat::Make) {
1032 auto MaybeFile = WorkerTool.getDependencyFile(
1033 CommandLine: Input->CommandLine, CWD, LookupModuleOutput: LookupOutput, DiagConsumer);
1034 handleDiagnostics(Input: Filename, Diagnostics: S, Errs);
1035 if (MaybeFile)
1036 DependencyOS.applyLocked(Fn: [&](raw_ostream &OS) { OS << *MaybeFile; });
1037 else
1038 HadErrors = true;
1039 } else if (Format == ScanningOutputFormat::P1689) {
1040 // It is useful to generate the make-format dependency output during
1041 // the scanning for P1689. Otherwise the users need to scan again for
1042 // it. We will generate the make-format dependency output if we find
1043 // `-MF` in the command lines.
1044 std::string MakeformatOutputPath;
1045 std::string MakeformatOutput;
1046
1047 auto MaybeRule = WorkerTool.getP1689ModuleDependencyFile(
1048 Command: *Input, CWD, MakeformatOutput, MakeformatOutputPath, DiagConsumer);
1049 handleDiagnostics(Input: Filename, Diagnostics: S, Errs);
1050 if (MaybeRule)
1051 PD.addRules(Rule&: *MaybeRule);
1052 else
1053 HadErrors = true;
1054
1055 if (!MakeformatOutputPath.empty() && !MakeformatOutput.empty() &&
1056 !HadErrors) {
1057 llvm::SmallString<256> FullDepPath;
1058 static std::mutex Lock;
1059 // With compilation database, we may open different files
1060 // concurrently or we may write the same file concurrently. So we
1061 // use a map here to allow multiple compile commands to write to the
1062 // same file. Also we need a lock here to avoid data race.
1063 static llvm::StringMap<llvm::raw_fd_ostream> OSs;
1064 std::unique_lock<std::mutex> LockGuard(Lock);
1065
1066 if (llvm::sys::path::is_absolute(path: MakeformatOutputPath))
1067 FullDepPath = MakeformatOutputPath;
1068 else
1069 llvm::sys::path::append(path&: FullDepPath, a: CWD, b: MakeformatOutputPath);
1070
1071 if (llvm::StringRef Parent =
1072 llvm::sys::path::parent_path(path: FullDepPath);
1073 !Parent.empty()) {
1074 if (std::error_code DirEC =
1075 llvm::sys::fs::create_directories(path: Parent)) {
1076 llvm::errs() << "Failed to create directory \"" << Parent
1077 << "\" for P1689 make format output: "
1078 << DirEC.message() << "\n";
1079 HadErrors = true;
1080 continue;
1081 }
1082 }
1083
1084 auto OSIter = OSs.find(Key: FullDepPath);
1085 if (OSIter == OSs.end()) {
1086 std::error_code EC;
1087 auto Emplaced = OSs.try_emplace(Key: FullDepPath.str(), Args&: FullDepPath, Args&: EC,
1088 Args: llvm::sys::fs::OF_Text);
1089 OSIter = Emplaced.first;
1090 if (EC) {
1091 OSs.erase(I: OSIter);
1092 llvm::errs() << "Failed to open P1689 make format output file \""
1093 << FullDepPath << "\" for " << EC.message() << "\n";
1094 HadErrors = true;
1095 continue;
1096 }
1097 }
1098
1099 SharedStream MakeformatOS(OSIter->second);
1100 MakeformatOS.applyLocked(
1101 Fn: [&](raw_ostream &OS) { OS << MakeformatOutput; });
1102 }
1103 } else if (ModuleNames) {
1104 StringRef ModuleNameRef(*ModuleNames);
1105 SmallVector<StringRef> Names;
1106 ModuleNameRef.split(A&: Names, Separator: ',');
1107
1108 CallbackActionController Controller(LookupOutput);
1109
1110 if (Names.size() == 1) {
1111 auto MaybeModuleDepsGraph = WorkerTool.getModuleDependencies(
1112 ModuleName: Names[0], CommandLine: Input->CommandLine, CWD, AlreadySeen: AlreadySeenModules,
1113 Controller);
1114 if (handleModuleResult(ModuleName: Names[0], MaybeTUDeps&: MaybeModuleDepsGraph, FD&: *FD,
1115 InputIndex: LocalIndex, OS&: DependencyOS, Errs))
1116 HadErrors = true;
1117 } else {
1118 auto CIWithCtx = CompilerInstanceWithContext::initializeOrError(
1119 Tool&: WorkerTool, CWD, CommandLine: Input->CommandLine, Controller);
1120 if (llvm::Error Err = CIWithCtx.takeError()) {
1121 handleErrorWithInfoString(
1122 Info: "Compiler instance with context setup error", E: std::move(Err),
1123 OS&: DependencyOS, Errs);
1124 HadErrors = true;
1125 continue;
1126 }
1127
1128 for (auto N : Names) {
1129 auto MaybeModuleDepsGraph =
1130 CIWithCtx->computeDependenciesByNameOrError(
1131 ModuleName: N, AlreadySeen: AlreadySeenModules, Controller);
1132 if (handleModuleResult(ModuleName: N, MaybeTUDeps&: MaybeModuleDepsGraph, FD&: *FD, InputIndex: LocalIndex,
1133 OS&: DependencyOS, Errs)) {
1134 HadErrors = true;
1135 }
1136 }
1137 }
1138 } else {
1139 std::unique_ptr<llvm::MemoryBuffer> TU;
1140 std::optional<llvm::MemoryBufferRef> TUBuffer;
1141 if (!TranslationUnitFile.empty()) {
1142 auto MaybeTU =
1143 llvm::MemoryBuffer::getFile(Filename: TranslationUnitFile, /*IsText=*/true);
1144 if (!MaybeTU) {
1145 llvm::errs() << "cannot open input translation unit: "
1146 << MaybeTU.getError().message() << "\n";
1147 HadErrors = true;
1148 continue;
1149 }
1150 TU = std::move(*MaybeTU);
1151 TUBuffer = TU->getMemBufferRef();
1152 Filename = TU->getBufferIdentifier();
1153 }
1154 auto MaybeTUDeps = WorkerTool.getTranslationUnitDependencies(
1155 CommandLine: Input->CommandLine, CWD, DiagConsumer, AlreadySeen: AlreadySeenModules,
1156 LookupModuleOutput: LookupOutput, TUBuffer);
1157 handleDiagnostics(Input: Filename, Diagnostics: S, Errs);
1158 if (MaybeTUDeps)
1159 FD->mergeDeps(Input: Filename, TUDeps: *MaybeTUDeps, InputIndex: LocalIndex);
1160 else
1161 HadErrors = true;
1162 }
1163 }
1164
1165 if (auto *T = WorkerTool.getWorkerTracingVFS()) {
1166 NumStatusCalls += T->NumStatusCalls;
1167 NumOpenFileForReadCalls += T->NumOpenFileForReadCalls;
1168 NumDirBeginCalls += T->NumDirBeginCalls;
1169 NumGetRealPathCalls += T->NumGetRealPathCalls;
1170 NumExistsCalls += T->NumExistsCalls;
1171 NumIsLocalCalls += T->NumIsLocalCalls;
1172 }
1173 };
1174
1175 DependencyScanningServiceOptions Opts;
1176 Opts.Mode = ScanMode;
1177 Opts.OptimizeArgs = OptimizeArgs;
1178 // The scanner currently ignores `#pragma clang diagnostic ...` and emits
1179 // unexpected diagnostics. Work around this for now by disabling warnings
1180 // entirely, at least for P1689 where people hit this most often.
1181 Opts.EmitWarnings = Format != ScanningOutputFormat::P1689;
1182 // Within P1689 format, we don't want all the paths to be absolute path
1183 // since it may violate the traditional make style dependencies info.
1184 Opts.ReportAbsolutePaths = Format != ScanningOutputFormat::P1689;
1185 Opts.ReportVisibleModules = EmitVisibleModules;
1186 Opts.EagerLoadModules = EagerLoadModules;
1187 Opts.TraceVFS = Verbose;
1188 Opts.AsyncScanModules = AsyncScanModules;
1189 Opts.FlushModuleCache = !NoFlushModuleCache;
1190 Opts.CacheNegativeStats = CacheNegativeStats;
1191
1192 llvm::Timer T;
1193 T.startTimer();
1194
1195 {
1196 DependencyScanningService Service(std::move(Opts));
1197
1198 if (Inputs.size() == 1) {
1199 ScanningTask(Service);
1200 } else {
1201 llvm::DefaultThreadPool Pool(llvm::hardware_concurrency(ThreadCount: NumThreads));
1202
1203 if (Verbose) {
1204 llvm::outs() << "Running clang-scan-deps on " << Inputs.size()
1205 << " files using " << Pool.getMaxConcurrency()
1206 << " workers\n";
1207 }
1208
1209 for (unsigned I = 0; I < Pool.getMaxConcurrency(); ++I)
1210 Pool.async(F: [ScanningTask, &Service]() { ScanningTask(Service); });
1211
1212 Pool.wait();
1213 }
1214 }
1215
1216 T.stopTimer();
1217
1218 if (Verbose)
1219 llvm::errs() << "\n*** Virtual File System Stats:\n"
1220 << NumStatusCalls << " status() calls\n"
1221 << NumOpenFileForReadCalls << " openFileForRead() calls\n"
1222 << NumDirBeginCalls << " dir_begin() calls\n"
1223 << NumGetRealPathCalls << " getRealPath() calls\n"
1224 << NumExistsCalls << " exists() calls\n"
1225 << NumIsLocalCalls << " isLocal() calls\n";
1226
1227 if (PrintTiming) {
1228 llvm::errs() << "wall time [s]\t"
1229 << "process time [s]\t"
1230 << "instruction count\n";
1231 const llvm::TimeRecord &R = T.getTotalTime();
1232 llvm::errs() << llvm::format(Fmt: "%0.4f", Vals: R.getWallTime()) << "\t"
1233 << llvm::format(Fmt: "%0.4f", Vals: R.getProcessTime()) << "\t"
1234 << llvm::format(Fmt: "%llu", Vals: R.getInstructionsExecuted()) << "\n";
1235 }
1236
1237 if (RoundTripArgs)
1238 if (FD && FD->roundTripCommands(ErrOS&: llvm::errs()))
1239 HadErrors = true;
1240
1241 if (Format == ScanningOutputFormat::Full)
1242 FD->printFullOutput(OS&: ThreadUnsafeDependencyOS);
1243 else if (Format == ScanningOutputFormat::P1689)
1244 PD.printDependencies(OS&: ThreadUnsafeDependencyOS);
1245
1246 return HadErrors;
1247}
1248