1 //===- ClangScanDeps.cpp - Implementation of clang-scan-deps --------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8
9 #include "clang/Driver/Compilation.h"
10 #include "clang/Driver/Driver.h"
11 #include "clang/Frontend/CompilerInstance.h"
12 #include "clang/Tooling/CommonOptionsParser.h"
13 #include "clang/Tooling/DependencyScanning/DependencyScanningService.h"
14 #include "clang/Tooling/DependencyScanning/DependencyScanningTool.h"
15 #include "clang/Tooling/DependencyScanning/DependencyScanningWorker.h"
16 #include "clang/Tooling/JSONCompilationDatabase.h"
17 #include "llvm/ADT/STLExtras.h"
18 #include "llvm/ADT/Twine.h"
19 #include "llvm/Support/CommandLine.h"
20 #include "llvm/Support/FileUtilities.h"
21 #include "llvm/Support/Host.h"
22 #include "llvm/Support/InitLLVM.h"
23 #include "llvm/Support/JSON.h"
24 #include "llvm/Support/Program.h"
25 #include "llvm/Support/Signals.h"
26 #include "llvm/Support/ThreadPool.h"
27 #include "llvm/Support/Threading.h"
28 #include <mutex>
29 #include <optional>
30 #include <thread>
31
32 using namespace clang;
33 using namespace tooling::dependencies;
34
35 namespace {
36
37 class SharedStream {
38 public:
SharedStream(raw_ostream & OS)39 SharedStream(raw_ostream &OS) : OS(OS) {}
applyLocked(llvm::function_ref<void (raw_ostream & OS)> Fn)40 void applyLocked(llvm::function_ref<void(raw_ostream &OS)> Fn) {
41 std::unique_lock<std::mutex> LockGuard(Lock);
42 Fn(OS);
43 OS.flush();
44 }
45
46 private:
47 std::mutex Lock;
48 raw_ostream &OS;
49 };
50
51 class ResourceDirectoryCache {
52 public:
53 /// findResourceDir finds the resource directory relative to the clang
54 /// compiler being used in Args, by running it with "-print-resource-dir"
55 /// option and cache the results for reuse. \returns resource directory path
56 /// associated with the given invocation command or empty string if the
57 /// compiler path is NOT an absolute path.
findResourceDir(const tooling::CommandLineArguments & Args,bool ClangCLMode)58 StringRef findResourceDir(const tooling::CommandLineArguments &Args,
59 bool ClangCLMode) {
60 if (Args.size() < 1)
61 return "";
62
63 const std::string &ClangBinaryPath = Args[0];
64 if (!llvm::sys::path::is_absolute(ClangBinaryPath))
65 return "";
66
67 const std::string &ClangBinaryName =
68 std::string(llvm::sys::path::filename(ClangBinaryPath));
69
70 std::unique_lock<std::mutex> LockGuard(CacheLock);
71 const auto &CachedResourceDir = Cache.find(ClangBinaryPath);
72 if (CachedResourceDir != Cache.end())
73 return CachedResourceDir->second;
74
75 std::vector<StringRef> PrintResourceDirArgs{ClangBinaryName};
76 if (ClangCLMode)
77 PrintResourceDirArgs.push_back("/clang:-print-resource-dir");
78 else
79 PrintResourceDirArgs.push_back("-print-resource-dir");
80
81 llvm::SmallString<64> OutputFile, ErrorFile;
82 llvm::sys::fs::createTemporaryFile("print-resource-dir-output",
83 "" /*no-suffix*/, OutputFile);
84 llvm::sys::fs::createTemporaryFile("print-resource-dir-error",
85 "" /*no-suffix*/, ErrorFile);
86 llvm::FileRemover OutputRemover(OutputFile.c_str());
87 llvm::FileRemover ErrorRemover(ErrorFile.c_str());
88 std::optional<StringRef> Redirects[] = {
89 {""}, // Stdin
90 OutputFile.str(),
91 ErrorFile.str(),
92 };
93 if (const int RC = llvm::sys::ExecuteAndWait(
94 ClangBinaryPath, PrintResourceDirArgs, {}, Redirects)) {
95 auto ErrorBuf = llvm::MemoryBuffer::getFile(ErrorFile.c_str());
96 llvm::errs() << ErrorBuf.get()->getBuffer();
97 return "";
98 }
99
100 auto OutputBuf = llvm::MemoryBuffer::getFile(OutputFile.c_str());
101 if (!OutputBuf)
102 return "";
103 StringRef Output = OutputBuf.get()->getBuffer().rtrim('\n');
104
105 Cache[ClangBinaryPath] = Output.str();
106 return Cache[ClangBinaryPath];
107 }
108
109 private:
110 std::map<std::string, std::string> Cache;
111 std::mutex CacheLock;
112 };
113
114 llvm::cl::opt<bool> Help("h", llvm::cl::desc("Alias for -help"),
115 llvm::cl::Hidden);
116
117 llvm::cl::OptionCategory DependencyScannerCategory("Tool options");
118
119 static llvm::cl::opt<ScanningMode> ScanMode(
120 "mode",
121 llvm::cl::desc("The preprocessing mode used to compute the dependencies"),
122 llvm::cl::values(
123 clEnumValN(ScanningMode::DependencyDirectivesScan,
124 "preprocess-dependency-directives",
125 "The set of dependencies is computed by preprocessing with "
126 "special lexing after scanning the source files to get the "
127 "directives that might affect the dependencies"),
128 clEnumValN(ScanningMode::CanonicalPreprocessing, "preprocess",
129 "The set of dependencies is computed by preprocessing the "
130 "source files")),
131 llvm::cl::init(ScanningMode::DependencyDirectivesScan),
132 llvm::cl::cat(DependencyScannerCategory));
133
134 static llvm::cl::opt<ScanningOutputFormat> Format(
135 "format", llvm::cl::desc("The output format for the dependencies"),
136 llvm::cl::values(
137 clEnumValN(ScanningOutputFormat::Make, "make",
138 "Makefile compatible dep file"),
139 clEnumValN(ScanningOutputFormat::P1689, "p1689",
140 "Generate standard c++ modules dependency P1689 format"),
141 clEnumValN(ScanningOutputFormat::Full, "experimental-full",
142 "Full dependency graph suitable"
143 " for explicitly building modules. This format "
144 "is experimental and will change.")),
145 llvm::cl::init(ScanningOutputFormat::Make),
146 llvm::cl::cat(DependencyScannerCategory));
147
148 static llvm::cl::opt<std::string> ModuleFilesDir(
149 "module-files-dir",
150 llvm::cl::desc(
151 "The build directory for modules. Defaults to the value of "
152 "'-fmodules-cache-path=' from command lines for implicit modules."),
153 llvm::cl::cat(DependencyScannerCategory));
154
155 static llvm::cl::opt<bool> OptimizeArgs(
156 "optimize-args",
157 llvm::cl::desc("Whether to optimize command-line arguments of modules."),
158 llvm::cl::init(false), llvm::cl::cat(DependencyScannerCategory));
159
160 static llvm::cl::opt<bool> EagerLoadModules(
161 "eager-load-pcm",
162 llvm::cl::desc("Load PCM files eagerly (instead of lazily on import)."),
163 llvm::cl::init(false), llvm::cl::cat(DependencyScannerCategory));
164
165 llvm::cl::opt<unsigned>
166 NumThreads("j", llvm::cl::Optional,
167 llvm::cl::desc("Number of worker threads to use (default: use "
168 "all concurrent threads)"),
169 llvm::cl::init(0), llvm::cl::cat(DependencyScannerCategory));
170
171 llvm::cl::opt<std::string>
172 CompilationDB("compilation-database",
173 llvm::cl::desc("Compilation database"), llvm::cl::Optional,
174 llvm::cl::cat(DependencyScannerCategory));
175
176 llvm::cl::opt<std::string> P1689TargettedCommand(
177 llvm::cl::Positional, llvm::cl::ZeroOrMore,
178 llvm::cl::desc("The command line flags for the target of which "
179 "the dependencies are to be computed."));
180
181 llvm::cl::opt<std::string> ModuleName(
182 "module-name", llvm::cl::Optional,
183 llvm::cl::desc("the module of which the dependencies are to be computed"),
184 llvm::cl::cat(DependencyScannerCategory));
185
186 llvm::cl::list<std::string> ModuleDepTargets(
187 "dependency-target",
188 llvm::cl::desc("The names of dependency targets for the dependency file"),
189 llvm::cl::cat(DependencyScannerCategory));
190
191 llvm::cl::opt<bool> DeprecatedDriverCommand(
192 "deprecated-driver-command", llvm::cl::Optional,
193 llvm::cl::desc("use a single driver command to build the tu (deprecated)"),
194 llvm::cl::cat(DependencyScannerCategory));
195
196 enum ResourceDirRecipeKind {
197 RDRK_ModifyCompilerPath,
198 RDRK_InvokeCompiler,
199 };
200
201 static llvm::cl::opt<ResourceDirRecipeKind> ResourceDirRecipe(
202 "resource-dir-recipe",
203 llvm::cl::desc("How to produce missing '-resource-dir' argument"),
204 llvm::cl::values(
205 clEnumValN(RDRK_ModifyCompilerPath, "modify-compiler-path",
206 "Construct the resource directory from the compiler path in "
207 "the compilation database. This assumes it's part of the "
208 "same toolchain as this clang-scan-deps. (default)"),
209 clEnumValN(RDRK_InvokeCompiler, "invoke-compiler",
210 "Invoke the compiler with '-print-resource-dir' and use the "
211 "reported path as the resource directory. (deprecated)")),
212 llvm::cl::init(RDRK_ModifyCompilerPath),
213 llvm::cl::cat(DependencyScannerCategory));
214
215 llvm::cl::opt<bool> Verbose("v", llvm::cl::Optional,
216 llvm::cl::desc("Use verbose output."),
217 llvm::cl::init(false),
218 llvm::cl::cat(DependencyScannerCategory));
219
220 } // end anonymous namespace
221
222 /// Takes the result of a dependency scan and prints error / dependency files
223 /// based on the result.
224 ///
225 /// \returns True on error.
226 static bool
handleMakeDependencyToolResult(const std::string & Input,llvm::Expected<std::string> & MaybeFile,SharedStream & OS,SharedStream & Errs)227 handleMakeDependencyToolResult(const std::string &Input,
228 llvm::Expected<std::string> &MaybeFile,
229 SharedStream &OS, SharedStream &Errs) {
230 if (!MaybeFile) {
231 llvm::handleAllErrors(
232 MaybeFile.takeError(), [&Input, &Errs](llvm::StringError &Err) {
233 Errs.applyLocked([&](raw_ostream &OS) {
234 OS << "Error while scanning dependencies for " << Input << ":\n";
235 OS << Err.getMessage();
236 });
237 });
238 return true;
239 }
240 OS.applyLocked([&](raw_ostream &OS) { OS << *MaybeFile; });
241 return false;
242 }
243
toJSONSorted(const llvm::StringSet<> & Set)244 static llvm::json::Array toJSONSorted(const llvm::StringSet<> &Set) {
245 std::vector<llvm::StringRef> Strings;
246 for (auto &&I : Set)
247 Strings.push_back(I.getKey());
248 llvm::sort(Strings);
249 return llvm::json::Array(Strings);
250 }
251
toJSONSorted(std::vector<ModuleID> V)252 static llvm::json::Array toJSONSorted(std::vector<ModuleID> V) {
253 llvm::sort(V, [](const ModuleID &A, const ModuleID &B) {
254 return std::tie(A.ModuleName, A.ContextHash) <
255 std::tie(B.ModuleName, B.ContextHash);
256 });
257
258 llvm::json::Array Ret;
259 for (const ModuleID &MID : V)
260 Ret.push_back(llvm::json::Object(
261 {{"module-name", MID.ModuleName}, {"context-hash", MID.ContextHash}}));
262 return Ret;
263 }
264
265 // Thread safe.
266 class FullDeps {
267 public:
mergeDeps(StringRef Input,FullDependenciesResult FDR,size_t InputIndex)268 void mergeDeps(StringRef Input, FullDependenciesResult FDR,
269 size_t InputIndex) {
270 FullDependencies &FD = FDR.FullDeps;
271
272 InputDeps ID;
273 ID.FileName = std::string(Input);
274 ID.ContextHash = std::move(FD.ID.ContextHash);
275 ID.FileDeps = std::move(FD.FileDeps);
276 ID.ModuleDeps = std::move(FD.ClangModuleDeps);
277
278 std::unique_lock<std::mutex> ul(Lock);
279 for (const ModuleDeps &MD : FDR.DiscoveredModules) {
280 auto I = Modules.find({MD.ID, 0});
281 if (I != Modules.end()) {
282 I->first.InputIndex = std::min(I->first.InputIndex, InputIndex);
283 continue;
284 }
285 Modules.insert(I, {{MD.ID, InputIndex}, std::move(MD)});
286 }
287
288 ID.DriverCommandLine = std::move(FD.DriverCommandLine);
289 ID.Commands = std::move(FD.Commands);
290 Inputs.push_back(std::move(ID));
291 }
292
printFullOutput(raw_ostream & OS)293 void printFullOutput(raw_ostream &OS) {
294 // Sort the modules by name to get a deterministic order.
295 std::vector<IndexedModuleID> ModuleIDs;
296 for (auto &&M : Modules)
297 ModuleIDs.push_back(M.first);
298 llvm::sort(ModuleIDs,
299 [](const IndexedModuleID &A, const IndexedModuleID &B) {
300 return std::tie(A.ID.ModuleName, A.InputIndex) <
301 std::tie(B.ID.ModuleName, B.InputIndex);
302 });
303
304 llvm::sort(Inputs, [](const InputDeps &A, const InputDeps &B) {
305 return A.FileName < B.FileName;
306 });
307
308 using namespace llvm::json;
309
310 Array OutModules;
311 for (auto &&ModID : ModuleIDs) {
312 auto &MD = Modules[ModID];
313 Object O{
314 {"name", MD.ID.ModuleName},
315 {"context-hash", MD.ID.ContextHash},
316 {"file-deps", toJSONSorted(MD.FileDeps)},
317 {"clang-module-deps", toJSONSorted(MD.ClangModuleDeps)},
318 {"clang-modulemap-file", MD.ClangModuleMapFile},
319 {"command-line", MD.BuildArguments},
320 };
321 OutModules.push_back(std::move(O));
322 }
323
324 Array TUs;
325 for (auto &&I : Inputs) {
326 Array Commands;
327 if (I.DriverCommandLine.empty()) {
328 for (const auto &Cmd : I.Commands) {
329 Object O{
330 {"input-file", I.FileName},
331 {"clang-context-hash", I.ContextHash},
332 {"file-deps", I.FileDeps},
333 {"clang-module-deps", toJSONSorted(I.ModuleDeps)},
334 {"executable", Cmd.Executable},
335 {"command-line", Cmd.Arguments},
336 };
337 Commands.push_back(std::move(O));
338 }
339 } else {
340 Object O{
341 {"input-file", I.FileName},
342 {"clang-context-hash", I.ContextHash},
343 {"file-deps", I.FileDeps},
344 {"clang-module-deps", toJSONSorted(I.ModuleDeps)},
345 {"executable", "clang"},
346 {"command-line", I.DriverCommandLine},
347 };
348 Commands.push_back(std::move(O));
349 }
350 TUs.push_back(Object{
351 {"commands", std::move(Commands)},
352 });
353 }
354
355 Object Output{
356 {"modules", std::move(OutModules)},
357 {"translation-units", std::move(TUs)},
358 };
359
360 OS << llvm::formatv("{0:2}\n", Value(std::move(Output)));
361 }
362
363 private:
364 struct IndexedModuleID {
365 ModuleID ID;
366 mutable size_t InputIndex;
367
operator ==FullDeps::IndexedModuleID368 bool operator==(const IndexedModuleID &Other) const {
369 return ID.ModuleName == Other.ID.ModuleName &&
370 ID.ContextHash == Other.ID.ContextHash;
371 }
372 };
373
374 struct IndexedModuleIDHasher {
operator ()FullDeps::IndexedModuleIDHasher375 std::size_t operator()(const IndexedModuleID &IMID) const {
376 using llvm::hash_combine;
377
378 return hash_combine(IMID.ID.ModuleName, IMID.ID.ContextHash);
379 }
380 };
381
382 struct InputDeps {
383 std::string FileName;
384 std::string ContextHash;
385 std::vector<std::string> FileDeps;
386 std::vector<ModuleID> ModuleDeps;
387 std::vector<std::string> DriverCommandLine;
388 std::vector<Command> Commands;
389 };
390
391 std::mutex Lock;
392 std::unordered_map<IndexedModuleID, ModuleDeps, IndexedModuleIDHasher>
393 Modules;
394 std::vector<InputDeps> Inputs;
395 };
396
handleFullDependencyToolResult(const std::string & Input,llvm::Expected<FullDependenciesResult> & MaybeFullDeps,FullDeps & FD,size_t InputIndex,SharedStream & OS,SharedStream & Errs)397 static bool handleFullDependencyToolResult(
398 const std::string &Input,
399 llvm::Expected<FullDependenciesResult> &MaybeFullDeps, FullDeps &FD,
400 size_t InputIndex, SharedStream &OS, SharedStream &Errs) {
401 if (!MaybeFullDeps) {
402 llvm::handleAllErrors(
403 MaybeFullDeps.takeError(), [&Input, &Errs](llvm::StringError &Err) {
404 Errs.applyLocked([&](raw_ostream &OS) {
405 OS << "Error while scanning dependencies for " << Input << ":\n";
406 OS << Err.getMessage();
407 });
408 });
409 return true;
410 }
411 FD.mergeDeps(Input, std::move(*MaybeFullDeps), InputIndex);
412 return false;
413 }
414
415 class P1689Deps {
416 public:
printDependencies(raw_ostream & OS)417 void printDependencies(raw_ostream &OS) {
418 addSourcePathsToRequires();
419 // Sort the modules by name to get a deterministic order.
420 llvm::sort(Rules, [](const P1689Rule &A, const P1689Rule &B) {
421 return A.PrimaryOutput < B.PrimaryOutput;
422 });
423
424 using namespace llvm::json;
425 Array OutputRules;
426 for (const P1689Rule &R : Rules) {
427 Object O{{"primary-output", R.PrimaryOutput}};
428
429 if (R.Provides) {
430 Array Provides;
431 Object Provided{{"logical-name", R.Provides->ModuleName},
432 {"source-path", R.Provides->SourcePath},
433 {"is-interface", R.Provides->IsStdCXXModuleInterface}};
434 Provides.push_back(std::move(Provided));
435 O.insert({"provides", std::move(Provides)});
436 }
437
438 Array Requires;
439 for (const P1689ModuleInfo &Info : R.Requires) {
440 Object RequiredInfo{{"logical-name", Info.ModuleName}};
441 if (!Info.SourcePath.empty())
442 RequiredInfo.insert({"source-path", Info.SourcePath});
443 Requires.push_back(std::move(RequiredInfo));
444 }
445
446 if (!Requires.empty())
447 O.insert({"requires", std::move(Requires)});
448
449 OutputRules.push_back(std::move(O));
450 }
451
452 Object Output{
453 {"version", 1}, {"revision", 0}, {"rules", std::move(OutputRules)}};
454
455 OS << llvm::formatv("{0:2}\n", Value(std::move(Output)));
456 }
457
addRules(P1689Rule & Rule)458 void addRules(P1689Rule &Rule) {
459 std::unique_lock<std::mutex> LockGuard(Lock);
460 Rules.push_back(Rule);
461 }
462
463 private:
addSourcePathsToRequires()464 void addSourcePathsToRequires() {
465 llvm::DenseMap<StringRef, StringRef> ModuleSourceMapper;
466 for (const P1689Rule &R : Rules)
467 if (R.Provides && !R.Provides->SourcePath.empty())
468 ModuleSourceMapper[R.Provides->ModuleName] = R.Provides->SourcePath;
469
470 for (P1689Rule &R : Rules) {
471 for (P1689ModuleInfo &Info : R.Requires) {
472 auto Iter = ModuleSourceMapper.find(Info.ModuleName);
473 if (Iter != ModuleSourceMapper.end())
474 Info.SourcePath = Iter->second;
475 }
476 }
477 }
478
479 std::mutex Lock;
480 std::vector<P1689Rule> Rules;
481 };
482
483 static bool
handleP1689DependencyToolResult(const std::string & Input,llvm::Expected<P1689Rule> & MaybeRule,P1689Deps & PD,SharedStream & Errs)484 handleP1689DependencyToolResult(const std::string &Input,
485 llvm::Expected<P1689Rule> &MaybeRule,
486 P1689Deps &PD, SharedStream &Errs) {
487 if (!MaybeRule) {
488 llvm::handleAllErrors(
489 MaybeRule.takeError(), [&Input, &Errs](llvm::StringError &Err) {
490 Errs.applyLocked([&](raw_ostream &OS) {
491 OS << "Error while scanning dependencies for " << Input << ":\n";
492 OS << Err.getMessage();
493 });
494 });
495 return true;
496 }
497 PD.addRules(*MaybeRule);
498 return false;
499 }
500
501 /// Construct a path for the explicitly built PCM.
constructPCMPath(ModuleID MID,StringRef OutputDir)502 static std::string constructPCMPath(ModuleID MID, StringRef OutputDir) {
503 SmallString<256> ExplicitPCMPath(OutputDir);
504 llvm::sys::path::append(ExplicitPCMPath, MID.ContextHash,
505 MID.ModuleName + "-" + MID.ContextHash + ".pcm");
506 return std::string(ExplicitPCMPath);
507 }
508
lookupModuleOutput(const ModuleID & MID,ModuleOutputKind MOK,StringRef OutputDir)509 static std::string lookupModuleOutput(const ModuleID &MID, ModuleOutputKind MOK,
510 StringRef OutputDir) {
511 std::string PCMPath = constructPCMPath(MID, OutputDir);
512 switch (MOK) {
513 case ModuleOutputKind::ModuleFile:
514 return PCMPath;
515 case ModuleOutputKind::DependencyFile:
516 return PCMPath + ".d";
517 case ModuleOutputKind::DependencyTargets:
518 // Null-separate the list of targets.
519 return join(ModuleDepTargets, StringRef("\0", 1));
520 case ModuleOutputKind::DiagnosticSerializationFile:
521 return PCMPath + ".diag";
522 }
523 llvm_unreachable("Fully covered switch above!");
524 }
525
getModuleCachePath(ArrayRef<std::string> Args)526 static std::string getModuleCachePath(ArrayRef<std::string> Args) {
527 for (StringRef Arg : llvm::reverse(Args)) {
528 Arg.consume_front("/clang:");
529 if (Arg.consume_front("-fmodules-cache-path="))
530 return std::string(Arg);
531 }
532 SmallString<128> Path;
533 driver::Driver::getDefaultModuleCachePath(Path);
534 return std::string(Path);
535 }
536
537 // getCompilationDataBase - If -compilation-database is set, load the
538 // compilation database from the specified file. Otherwise if the we're
539 // generating P1689 format, trying to generate the compilation database
540 // form specified command line after the positional parameter "--".
541 static std::unique_ptr<tooling::CompilationDatabase>
getCompilationDataBase(int argc,const char ** argv,std::string & ErrorMessage)542 getCompilationDataBase(int argc, const char **argv, std::string &ErrorMessage) {
543 llvm::InitLLVM X(argc, argv);
544 llvm::cl::HideUnrelatedOptions(DependencyScannerCategory);
545 if (!llvm::cl::ParseCommandLineOptions(argc, argv))
546 return nullptr;
547
548 if (!CompilationDB.empty())
549 return tooling::JSONCompilationDatabase::loadFromFile(
550 CompilationDB, ErrorMessage,
551 tooling::JSONCommandLineSyntax::AutoDetect);
552
553 if (Format != ScanningOutputFormat::P1689) {
554 llvm::errs() << "the --compilation-database option: must be specified at "
555 "least once!";
556 return nullptr;
557 }
558
559 // Trying to get the input file, the output file and the command line options
560 // from the positional parameter "--".
561 const char **DoubleDash = std::find(argv, argv + argc, StringRef("--"));
562 if (DoubleDash == argv + argc) {
563 llvm::errs() << "The command line arguments is required after '--' in "
564 "P1689 per file mode.";
565 return nullptr;
566 }
567 std::vector<const char *> CommandLine(DoubleDash + 1, argv + argc);
568
569 llvm::IntrusiveRefCntPtr<DiagnosticsEngine> Diags =
570 CompilerInstance::createDiagnostics(new DiagnosticOptions);
571 driver::Driver TheDriver(CommandLine[0], llvm::sys::getDefaultTargetTriple(),
572 *Diags);
573 std::unique_ptr<driver::Compilation> C(
574 TheDriver.BuildCompilation(CommandLine));
575 if (!C)
576 return nullptr;
577
578 auto Cmd = C->getJobs().begin();
579 auto CI = std::make_unique<CompilerInvocation>();
580 CompilerInvocation::CreateFromArgs(*CI, Cmd->getArguments(), *Diags,
581 CommandLine[0]);
582 if (!CI)
583 return nullptr;
584
585 FrontendOptions &FEOpts = CI->getFrontendOpts();
586 if (FEOpts.Inputs.size() != 1) {
587 llvm::errs() << "Only one input file is allowed in P1689 per file mode.";
588 return nullptr;
589 }
590
591 // There might be multiple jobs for a compilation. Extract the specified
592 // output filename from the last job.
593 auto LastCmd = C->getJobs().end();
594 LastCmd--;
595 if (LastCmd->getOutputFilenames().size() != 1) {
596 llvm::errs() << "The command line should provide exactly one output file "
597 "in P1689 per file mode.\n";
598 }
599 StringRef OutputFile = LastCmd->getOutputFilenames().front();
600
601 class InplaceCompilationDatabase : public tooling::CompilationDatabase {
602 public:
603 InplaceCompilationDatabase(StringRef InputFile, StringRef OutputFile,
604 ArrayRef<const char *> CommandLine)
605 : Command(".", InputFile, {}, OutputFile) {
606 for (auto *C : CommandLine)
607 Command.CommandLine.push_back(C);
608 }
609
610 std::vector<tooling::CompileCommand>
611 getCompileCommands(StringRef FilePath) const override {
612 if (FilePath != Command.Filename)
613 return {};
614 return {Command};
615 }
616
617 std::vector<std::string> getAllFiles() const override {
618 return {Command.Filename};
619 }
620
621 std::vector<tooling::CompileCommand>
622 getAllCompileCommands() const override {
623 return {Command};
624 }
625
626 private:
627 tooling::CompileCommand Command;
628 };
629
630 return std::make_unique<InplaceCompilationDatabase>(
631 FEOpts.Inputs[0].getFile(), OutputFile, CommandLine);
632 }
633
main(int argc,const char ** argv)634 int main(int argc, const char **argv) {
635 std::string ErrorMessage;
636 std::unique_ptr<tooling::CompilationDatabase> Compilations =
637 getCompilationDataBase(argc, argv, ErrorMessage);
638 if (!Compilations) {
639 llvm::errs() << ErrorMessage << "\n";
640 return 1;
641 }
642
643 llvm::cl::PrintOptionValues();
644
645 // The command options are rewritten to run Clang in preprocessor only mode.
646 auto AdjustingCompilations =
647 std::make_unique<tooling::ArgumentsAdjustingCompilations>(
648 std::move(Compilations));
649 ResourceDirectoryCache ResourceDirCache;
650
651 AdjustingCompilations->appendArgumentsAdjuster(
652 [&ResourceDirCache](const tooling::CommandLineArguments &Args,
653 StringRef FileName) {
654 std::string LastO;
655 bool HasResourceDir = false;
656 bool ClangCLMode = false;
657 auto FlagsEnd = llvm::find(Args, "--");
658 if (FlagsEnd != Args.begin()) {
659 ClangCLMode =
660 llvm::sys::path::stem(Args[0]).contains_insensitive("clang-cl") ||
661 llvm::is_contained(Args, "--driver-mode=cl");
662
663 // Reverse scan, starting at the end or at the element before "--".
664 auto R = std::make_reverse_iterator(FlagsEnd);
665 for (auto I = R, E = Args.rend(); I != E; ++I) {
666 StringRef Arg = *I;
667 if (ClangCLMode) {
668 // Ignore arguments that are preceded by "-Xclang".
669 if ((I + 1) != E && I[1] == "-Xclang")
670 continue;
671 if (LastO.empty()) {
672 // With clang-cl, the output obj file can be specified with
673 // "/opath", "/o path", "/Fopath", and the dash counterparts.
674 // Also, clang-cl adds ".obj" extension if none is found.
675 if ((Arg == "-o" || Arg == "/o") && I != R)
676 LastO = I[-1]; // Next argument (reverse iterator)
677 else if (Arg.startswith("/Fo") || Arg.startswith("-Fo"))
678 LastO = Arg.drop_front(3).str();
679 else if (Arg.startswith("/o") || Arg.startswith("-o"))
680 LastO = Arg.drop_front(2).str();
681
682 if (!LastO.empty() && !llvm::sys::path::has_extension(LastO))
683 LastO.append(".obj");
684 }
685 }
686 if (Arg == "-resource-dir")
687 HasResourceDir = true;
688 }
689 }
690 tooling::CommandLineArguments AdjustedArgs(Args.begin(), FlagsEnd);
691 // The clang-cl driver passes "-o -" to the frontend. Inject the real
692 // file here to ensure "-MT" can be deduced if need be.
693 if (ClangCLMode && !LastO.empty()) {
694 AdjustedArgs.push_back("/clang:-o");
695 AdjustedArgs.push_back("/clang:" + LastO);
696 }
697
698 if (!HasResourceDir && ResourceDirRecipe == RDRK_InvokeCompiler) {
699 StringRef ResourceDir =
700 ResourceDirCache.findResourceDir(Args, ClangCLMode);
701 if (!ResourceDir.empty()) {
702 AdjustedArgs.push_back("-resource-dir");
703 AdjustedArgs.push_back(std::string(ResourceDir));
704 }
705 }
706 AdjustedArgs.insert(AdjustedArgs.end(), FlagsEnd, Args.end());
707 return AdjustedArgs;
708 });
709
710 SharedStream Errs(llvm::errs());
711 // Print out the dependency results to STDOUT by default.
712 SharedStream DependencyOS(llvm::outs());
713
714 DependencyScanningService Service(ScanMode, Format, OptimizeArgs,
715 EagerLoadModules);
716 llvm::ThreadPool Pool(llvm::hardware_concurrency(NumThreads));
717 std::vector<std::unique_ptr<DependencyScanningTool>> WorkerTools;
718 for (unsigned I = 0; I < Pool.getThreadCount(); ++I)
719 WorkerTools.push_back(std::make_unique<DependencyScanningTool>(Service));
720
721 std::vector<tooling::CompileCommand> Inputs =
722 AdjustingCompilations->getAllCompileCommands();
723
724 std::atomic<bool> HadErrors(false);
725 FullDeps FD;
726 P1689Deps PD;
727 std::mutex Lock;
728 size_t Index = 0;
729
730 if (Verbose) {
731 llvm::outs() << "Running clang-scan-deps on " << Inputs.size()
732 << " files using " << Pool.getThreadCount() << " workers\n";
733 }
734 for (unsigned I = 0; I < Pool.getThreadCount(); ++I) {
735 Pool.async([I, &Lock, &Index, &Inputs, &HadErrors, &FD, &PD, &WorkerTools,
736 &DependencyOS, &Errs]() {
737 llvm::StringSet<> AlreadySeenModules;
738 while (true) {
739 const tooling::CompileCommand *Input;
740 std::string Filename;
741 std::string CWD;
742 size_t LocalIndex;
743 // Take the next input.
744 {
745 std::unique_lock<std::mutex> LockGuard(Lock);
746 if (Index >= Inputs.size())
747 return;
748 LocalIndex = Index;
749 Input = &Inputs[Index++];
750 Filename = std::move(Input->Filename);
751 CWD = std::move(Input->Directory);
752 }
753 std::optional<StringRef> MaybeModuleName;
754 if (!ModuleName.empty())
755 MaybeModuleName = ModuleName;
756
757 std::string OutputDir(ModuleFilesDir);
758 if (OutputDir.empty())
759 OutputDir = getModuleCachePath(Input->CommandLine);
760 auto LookupOutput = [&](const ModuleID &MID, ModuleOutputKind MOK) {
761 return ::lookupModuleOutput(MID, MOK, OutputDir);
762 };
763
764 // Run the tool on it.
765 if (Format == ScanningOutputFormat::Make) {
766 auto MaybeFile = WorkerTools[I]->getDependencyFile(
767 Input->CommandLine, CWD, MaybeModuleName);
768 if (handleMakeDependencyToolResult(Filename, MaybeFile, DependencyOS,
769 Errs))
770 HadErrors = true;
771 } else if (Format == ScanningOutputFormat::P1689) {
772 // It is useful to generate the make-format dependency output during
773 // the scanning for P1689. Otherwise the users need to scan again for
774 // it. We will generate the make-format dependency output if we find
775 // `-MF` in the command lines.
776 std::string MakeformatOutputPath;
777 std::string MakeformatOutput;
778
779 auto MaybeRule = WorkerTools[I]->getP1689ModuleDependencyFile(
780 *Input, CWD, MakeformatOutput, MakeformatOutputPath);
781 HadErrors =
782 handleP1689DependencyToolResult(Filename, MaybeRule, PD, Errs);
783
784 if (!MakeformatOutputPath.empty() && !MakeformatOutput.empty() &&
785 !HadErrors) {
786 static std::mutex Lock;
787 // With compilation database, we may open different files
788 // concurrently or we may write the same file concurrently. So we
789 // use a map here to allow multiple compile commands to write to the
790 // same file. Also we need a lock here to avoid data race.
791 static llvm::StringMap<llvm::raw_fd_ostream> OSs;
792 std::unique_lock<std::mutex> LockGuard(Lock);
793
794 auto OSIter = OSs.find(MakeformatOutputPath);
795 if (OSIter == OSs.end()) {
796 std::error_code EC;
797 OSIter = OSs.try_emplace(MakeformatOutputPath,
798 MakeformatOutputPath, EC)
799 .first;
800 if (EC)
801 llvm::errs()
802 << "Failed to open P1689 make format output file \""
803 << MakeformatOutputPath << "\" for " << EC.message()
804 << "\n";
805 }
806
807 SharedStream MakeformatOS(OSIter->second);
808 llvm::Expected<std::string> MaybeOutput(MakeformatOutput);
809 HadErrors = handleMakeDependencyToolResult(Filename, MaybeOutput,
810 MakeformatOS, Errs);
811 }
812 } else if (DeprecatedDriverCommand) {
813 auto MaybeFullDeps =
814 WorkerTools[I]->getFullDependenciesLegacyDriverCommand(
815 Input->CommandLine, CWD, AlreadySeenModules, LookupOutput,
816 MaybeModuleName);
817 if (handleFullDependencyToolResult(Filename, MaybeFullDeps, FD,
818 LocalIndex, DependencyOS, Errs))
819 HadErrors = true;
820 } else {
821 auto MaybeFullDeps = WorkerTools[I]->getFullDependencies(
822 Input->CommandLine, CWD, AlreadySeenModules, LookupOutput,
823 MaybeModuleName);
824 if (handleFullDependencyToolResult(Filename, MaybeFullDeps, FD,
825 LocalIndex, DependencyOS, Errs))
826 HadErrors = true;
827 }
828 }
829 });
830 }
831 Pool.wait();
832
833 if (Format == ScanningOutputFormat::Full)
834 FD.printFullOutput(llvm::outs());
835 else if (Format == ScanningOutputFormat::P1689)
836 PD.printDependencies(llvm::outs());
837
838 return HadErrors;
839 }
840