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