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