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