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