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