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

source code of clang/tools/clang-scan-deps/ClangScanDeps.cpp