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