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