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: 39 SharedStream(raw_ostream &OS) : OS(OS) {} 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. 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 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 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 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: 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 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 368 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 { 375 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 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: 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 458 void addRules(P1689Rule &Rule) { 459 std::unique_lock<std::mutex> LockGuard(Lock); 460 Rules.push_back(Rule); 461 } 462 463 private: 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 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. 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 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 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> 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 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