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/Frontend/TextDiagnosticPrinter.h" 13 #include "clang/Tooling/CommonOptionsParser.h" 14 #include "clang/Tooling/DependencyScanning/DependencyScanningService.h" 15 #include "clang/Tooling/DependencyScanning/DependencyScanningTool.h" 16 #include "clang/Tooling/DependencyScanning/DependencyScanningWorker.h" 17 #include "clang/Tooling/JSONCompilationDatabase.h" 18 #include "clang/Tooling/Tooling.h" 19 #include "llvm/ADT/STLExtras.h" 20 #include "llvm/ADT/Twine.h" 21 #include "llvm/Support/CommandLine.h" 22 #include "llvm/Support/FileUtilities.h" 23 #include "llvm/Support/Format.h" 24 #include "llvm/Support/JSON.h" 25 #include "llvm/Support/LLVMDriver.h" 26 #include "llvm/Support/Program.h" 27 #include "llvm/Support/Signals.h" 28 #include "llvm/Support/TargetSelect.h" 29 #include "llvm/Support/ThreadPool.h" 30 #include "llvm/Support/Threading.h" 31 #include "llvm/Support/Timer.h" 32 #include "llvm/TargetParser/Host.h" 33 #include <mutex> 34 #include <optional> 35 #include <thread> 36 37 #include "Opts.inc" 38 39 using namespace clang; 40 using namespace tooling::dependencies; 41 42 namespace { 43 44 using namespace llvm::opt; 45 enum ID { 46 OPT_INVALID = 0, // This is not an option ID. 47 #define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__), 48 #include "Opts.inc" 49 #undef OPTION 50 }; 51 52 #define PREFIX(NAME, VALUE) \ 53 constexpr llvm::StringLiteral NAME##_init[] = VALUE; \ 54 constexpr llvm::ArrayRef<llvm::StringLiteral> NAME( \ 55 NAME##_init, std::size(NAME##_init) - 1); 56 #include "Opts.inc" 57 #undef PREFIX 58 59 const llvm::opt::OptTable::Info InfoTable[] = { 60 #define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__), 61 #include "Opts.inc" 62 #undef OPTION 63 }; 64 65 class ScanDepsOptTable : public llvm::opt::GenericOptTable { 66 public: 67 ScanDepsOptTable() : GenericOptTable(InfoTable) { 68 setGroupedShortOptions(true); 69 } 70 }; 71 72 enum ResourceDirRecipeKind { 73 RDRK_ModifyCompilerPath, 74 RDRK_InvokeCompiler, 75 }; 76 77 static std::string OutputFileName = "-"; 78 static ScanningMode ScanMode = ScanningMode::DependencyDirectivesScan; 79 static ScanningOutputFormat Format = ScanningOutputFormat::Make; 80 static ScanningOptimizations OptimizeArgs; 81 static std::string ModuleFilesDir; 82 static bool EagerLoadModules; 83 static unsigned NumThreads = 0; 84 static std::string CompilationDB; 85 static std::string ModuleName; 86 static std::vector<std::string> ModuleDepTargets; 87 static bool DeprecatedDriverCommand; 88 static ResourceDirRecipeKind ResourceDirRecipe; 89 static bool Verbose; 90 static bool PrintTiming; 91 static llvm::BumpPtrAllocator Alloc; 92 static llvm::StringSaver Saver{Alloc}; 93 static std::vector<const char *> CommandLine; 94 95 #ifndef NDEBUG 96 static constexpr bool DoRoundTripDefault = true; 97 #else 98 static constexpr bool DoRoundTripDefault = false; 99 #endif 100 101 static bool RoundTripArgs = DoRoundTripDefault; 102 103 static void ParseArgs(int argc, char **argv) { 104 ScanDepsOptTable Tbl; 105 llvm::StringRef ToolName = argv[0]; 106 llvm::opt::InputArgList Args = 107 Tbl.parseArgs(argc, argv, OPT_UNKNOWN, Saver, [&](StringRef Msg) { 108 llvm::errs() << Msg << '\n'; 109 std::exit(1); 110 }); 111 112 if (Args.hasArg(OPT_help)) { 113 Tbl.printHelp(llvm::outs(), "clang-scan-deps [options]", "clang-scan-deps"); 114 std::exit(0); 115 } 116 if (Args.hasArg(OPT_version)) { 117 llvm::outs() << ToolName << '\n'; 118 llvm::cl::PrintVersionMessage(); 119 std::exit(0); 120 } 121 if (const llvm::opt::Arg *A = Args.getLastArg(OPT_mode_EQ)) { 122 auto ModeType = 123 llvm::StringSwitch<std::optional<ScanningMode>>(A->getValue()) 124 .Case("preprocess-dependency-directives", 125 ScanningMode::DependencyDirectivesScan) 126 .Case("preprocess", ScanningMode::CanonicalPreprocessing) 127 .Default(std::nullopt); 128 if (!ModeType) { 129 llvm::errs() << ToolName 130 << ": for the --mode option: Cannot find option named '" 131 << A->getValue() << "'\n"; 132 std::exit(1); 133 } 134 ScanMode = *ModeType; 135 } 136 137 if (const llvm::opt::Arg *A = Args.getLastArg(OPT_format_EQ)) { 138 auto FormatType = 139 llvm::StringSwitch<std::optional<ScanningOutputFormat>>(A->getValue()) 140 .Case("make", ScanningOutputFormat::Make) 141 .Case("p1689", ScanningOutputFormat::P1689) 142 .Case("experimental-full", ScanningOutputFormat::Full) 143 .Default(std::nullopt); 144 if (!FormatType) { 145 llvm::errs() << ToolName 146 << ": for the --format option: Cannot find option named '" 147 << A->getValue() << "'\n"; 148 std::exit(1); 149 } 150 Format = *FormatType; 151 } 152 153 std::vector<std::string> OptimizationFlags = 154 Args.getAllArgValues(OPT_optimize_args_EQ); 155 OptimizeArgs = ScanningOptimizations::None; 156 for (const auto &Arg : OptimizationFlags) { 157 auto Optimization = 158 llvm::StringSwitch<std::optional<ScanningOptimizations>>(Arg) 159 .Case("none", ScanningOptimizations::None) 160 .Case("header-search", ScanningOptimizations::HeaderSearch) 161 .Case("system-warnings", ScanningOptimizations::SystemWarnings) 162 .Case("vfs", ScanningOptimizations::VFS) 163 .Case("canonicalize-macros", ScanningOptimizations::Macros) 164 .Case("all", ScanningOptimizations::All) 165 .Default(std::nullopt); 166 if (!Optimization) { 167 llvm::errs() 168 << ToolName 169 << ": for the --optimize-args option: Cannot find option named '" 170 << Arg << "'\n"; 171 std::exit(1); 172 } 173 OptimizeArgs |= *Optimization; 174 } 175 if (OptimizationFlags.empty()) 176 OptimizeArgs = ScanningOptimizations::Default; 177 178 if (const llvm::opt::Arg *A = Args.getLastArg(OPT_module_files_dir_EQ)) 179 ModuleFilesDir = A->getValue(); 180 181 if (const llvm::opt::Arg *A = Args.getLastArg(OPT_o)) 182 OutputFileName = A->getValue(); 183 184 EagerLoadModules = Args.hasArg(OPT_eager_load_pcm); 185 186 if (const llvm::opt::Arg *A = Args.getLastArg(OPT_j)) { 187 StringRef S{A->getValue()}; 188 if (!llvm::to_integer(S, NumThreads, 0)) { 189 llvm::errs() << ToolName << ": for the -j option: '" << S 190 << "' value invalid for uint argument!\n"; 191 std::exit(1); 192 } 193 } 194 195 if (const llvm::opt::Arg *A = Args.getLastArg(OPT_compilation_database_EQ)) 196 CompilationDB = A->getValue(); 197 198 if (const llvm::opt::Arg *A = Args.getLastArg(OPT_module_name_EQ)) 199 ModuleName = A->getValue(); 200 201 for (const llvm::opt::Arg *A : Args.filtered(OPT_dependency_target_EQ)) 202 ModuleDepTargets.emplace_back(A->getValue()); 203 204 DeprecatedDriverCommand = Args.hasArg(OPT_deprecated_driver_command); 205 206 if (const llvm::opt::Arg *A = Args.getLastArg(OPT_resource_dir_recipe_EQ)) { 207 auto Kind = 208 llvm::StringSwitch<std::optional<ResourceDirRecipeKind>>(A->getValue()) 209 .Case("modify-compiler-path", RDRK_ModifyCompilerPath) 210 .Case("invoke-compiler", RDRK_InvokeCompiler) 211 .Default(std::nullopt); 212 if (!Kind) { 213 llvm::errs() << ToolName 214 << ": for the --resource-dir-recipe option: Cannot find " 215 "option named '" 216 << A->getValue() << "'\n"; 217 std::exit(1); 218 } 219 ResourceDirRecipe = *Kind; 220 } 221 222 PrintTiming = Args.hasArg(OPT_print_timing); 223 224 Verbose = Args.hasArg(OPT_verbose); 225 226 RoundTripArgs = Args.hasArg(OPT_round_trip_args); 227 228 if (const llvm::opt::Arg *A = Args.getLastArgNoClaim(OPT_DASH_DASH)) 229 CommandLine.assign(A->getValues().begin(), A->getValues().end()); 230 } 231 232 class SharedStream { 233 public: 234 SharedStream(raw_ostream &OS) : OS(OS) {} 235 void applyLocked(llvm::function_ref<void(raw_ostream &OS)> Fn) { 236 std::unique_lock<std::mutex> LockGuard(Lock); 237 Fn(OS); 238 OS.flush(); 239 } 240 241 private: 242 std::mutex Lock; 243 raw_ostream &OS; 244 }; 245 246 class ResourceDirectoryCache { 247 public: 248 /// findResourceDir finds the resource directory relative to the clang 249 /// compiler being used in Args, by running it with "-print-resource-dir" 250 /// option and cache the results for reuse. \returns resource directory path 251 /// associated with the given invocation command or empty string if the 252 /// compiler path is NOT an absolute path. 253 StringRef findResourceDir(const tooling::CommandLineArguments &Args, 254 bool ClangCLMode) { 255 if (Args.size() < 1) 256 return ""; 257 258 const std::string &ClangBinaryPath = Args[0]; 259 if (!llvm::sys::path::is_absolute(ClangBinaryPath)) 260 return ""; 261 262 const std::string &ClangBinaryName = 263 std::string(llvm::sys::path::filename(ClangBinaryPath)); 264 265 std::unique_lock<std::mutex> LockGuard(CacheLock); 266 const auto &CachedResourceDir = Cache.find(ClangBinaryPath); 267 if (CachedResourceDir != Cache.end()) 268 return CachedResourceDir->second; 269 270 std::vector<StringRef> PrintResourceDirArgs{ClangBinaryName}; 271 if (ClangCLMode) 272 PrintResourceDirArgs.push_back("/clang:-print-resource-dir"); 273 else 274 PrintResourceDirArgs.push_back("-print-resource-dir"); 275 276 llvm::SmallString<64> OutputFile, ErrorFile; 277 llvm::sys::fs::createTemporaryFile("print-resource-dir-output", 278 "" /*no-suffix*/, OutputFile); 279 llvm::sys::fs::createTemporaryFile("print-resource-dir-error", 280 "" /*no-suffix*/, ErrorFile); 281 llvm::FileRemover OutputRemover(OutputFile.c_str()); 282 llvm::FileRemover ErrorRemover(ErrorFile.c_str()); 283 std::optional<StringRef> Redirects[] = { 284 {""}, // Stdin 285 OutputFile.str(), 286 ErrorFile.str(), 287 }; 288 if (llvm::sys::ExecuteAndWait(ClangBinaryPath, PrintResourceDirArgs, {}, 289 Redirects)) { 290 auto ErrorBuf = llvm::MemoryBuffer::getFile(ErrorFile.c_str()); 291 llvm::errs() << ErrorBuf.get()->getBuffer(); 292 return ""; 293 } 294 295 auto OutputBuf = llvm::MemoryBuffer::getFile(OutputFile.c_str()); 296 if (!OutputBuf) 297 return ""; 298 StringRef Output = OutputBuf.get()->getBuffer().rtrim('\n'); 299 300 Cache[ClangBinaryPath] = Output.str(); 301 return Cache[ClangBinaryPath]; 302 } 303 304 private: 305 std::map<std::string, std::string> Cache; 306 std::mutex CacheLock; 307 }; 308 309 } // end anonymous namespace 310 311 /// Takes the result of a dependency scan and prints error / dependency files 312 /// based on the result. 313 /// 314 /// \returns True on error. 315 static bool 316 handleMakeDependencyToolResult(const std::string &Input, 317 llvm::Expected<std::string> &MaybeFile, 318 SharedStream &OS, SharedStream &Errs) { 319 if (!MaybeFile) { 320 llvm::handleAllErrors( 321 MaybeFile.takeError(), [&Input, &Errs](llvm::StringError &Err) { 322 Errs.applyLocked([&](raw_ostream &OS) { 323 OS << "Error while scanning dependencies for " << Input << ":\n"; 324 OS << Err.getMessage(); 325 }); 326 }); 327 return true; 328 } 329 OS.applyLocked([&](raw_ostream &OS) { OS << *MaybeFile; }); 330 return false; 331 } 332 333 static llvm::json::Array toJSONSorted(const llvm::StringSet<> &Set) { 334 std::vector<llvm::StringRef> Strings; 335 for (auto &&I : Set) 336 Strings.push_back(I.getKey()); 337 llvm::sort(Strings); 338 return llvm::json::Array(Strings); 339 } 340 341 // Technically, we don't need to sort the dependency list to get determinism. 342 // Leaving these be will simply preserve the import order. 343 static llvm::json::Array toJSONSorted(std::vector<ModuleID> V) { 344 llvm::sort(V); 345 346 llvm::json::Array Ret; 347 for (const ModuleID &MID : V) 348 Ret.push_back(llvm::json::Object( 349 {{"module-name", MID.ModuleName}, {"context-hash", MID.ContextHash}})); 350 return Ret; 351 } 352 353 static llvm::json::Array 354 toJSONSorted(llvm::SmallVector<Module::LinkLibrary, 2> &LinkLibs) { 355 llvm::sort(LinkLibs, [](const Module::LinkLibrary &lhs, 356 const Module::LinkLibrary &rhs) { 357 return lhs.Library < rhs.Library; 358 }); 359 360 llvm::json::Array Ret; 361 for (const Module::LinkLibrary &LL : LinkLibs) 362 Ret.push_back(llvm::json::Object( 363 {{"link-name", LL.Library}, {"isFramework", LL.IsFramework}})); 364 return Ret; 365 } 366 367 // Thread safe. 368 class FullDeps { 369 public: 370 FullDeps(size_t NumInputs) : Inputs(NumInputs) {} 371 372 void mergeDeps(StringRef Input, TranslationUnitDeps TUDeps, 373 size_t InputIndex) { 374 mergeDeps(std::move(TUDeps.ModuleGraph), InputIndex); 375 376 InputDeps ID; 377 ID.FileName = std::string(Input); 378 ID.ContextHash = std::move(TUDeps.ID.ContextHash); 379 ID.FileDeps = std::move(TUDeps.FileDeps); 380 ID.ModuleDeps = std::move(TUDeps.ClangModuleDeps); 381 ID.DriverCommandLine = std::move(TUDeps.DriverCommandLine); 382 ID.Commands = std::move(TUDeps.Commands); 383 384 assert(InputIndex < Inputs.size() && "Input index out of bounds"); 385 assert(Inputs[InputIndex].FileName.empty() && "Result already populated"); 386 Inputs[InputIndex] = std::move(ID); 387 } 388 389 void mergeDeps(ModuleDepsGraph Graph, size_t InputIndex) { 390 std::vector<ModuleDeps *> NewMDs; 391 { 392 std::unique_lock<std::mutex> ul(Lock); 393 for (const ModuleDeps &MD : Graph) { 394 auto I = Modules.find({MD.ID, 0}); 395 if (I != Modules.end()) { 396 I->first.InputIndex = std::min(I->first.InputIndex, InputIndex); 397 continue; 398 } 399 auto Res = Modules.insert(I, {{MD.ID, InputIndex}, std::move(MD)}); 400 NewMDs.push_back(&Res->second); 401 } 402 // First call to \c getBuildArguments is somewhat expensive. Let's call it 403 // on the current thread (instead of the main one), and outside the 404 // critical section. 405 for (ModuleDeps *MD : NewMDs) 406 (void)MD->getBuildArguments(); 407 } 408 } 409 410 bool roundTripCommand(ArrayRef<std::string> ArgStrs, 411 DiagnosticsEngine &Diags) { 412 if (ArgStrs.empty() || ArgStrs[0] != "-cc1") 413 return false; 414 SmallVector<const char *> Args; 415 for (const std::string &Arg : ArgStrs) 416 Args.push_back(Arg.c_str()); 417 return !CompilerInvocation::checkCC1RoundTrip(Args, Diags); 418 } 419 420 // Returns \c true if any command lines fail to round-trip. We expect 421 // commands already be canonical when output by the scanner. 422 bool roundTripCommands(raw_ostream &ErrOS) { 423 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions{}; 424 TextDiagnosticPrinter DiagConsumer(ErrOS, &*DiagOpts); 425 IntrusiveRefCntPtr<DiagnosticsEngine> Diags = 426 CompilerInstance::createDiagnostics(&*DiagOpts, &DiagConsumer, 427 /*ShouldOwnClient=*/false); 428 429 for (auto &&M : Modules) 430 if (roundTripCommand(M.second.getBuildArguments(), *Diags)) 431 return true; 432 433 for (auto &&I : Inputs) 434 for (const auto &Cmd : I.Commands) 435 if (roundTripCommand(Cmd.Arguments, *Diags)) 436 return true; 437 438 return false; 439 } 440 441 void printFullOutput(raw_ostream &OS) { 442 // Skip sorting modules and constructing the JSON object if the output 443 // cannot be observed anyway. This makes timings less noisy. 444 if (&OS == &llvm::nulls()) 445 return; 446 447 // Sort the modules by name to get a deterministic order. 448 std::vector<IndexedModuleID> ModuleIDs; 449 for (auto &&M : Modules) 450 ModuleIDs.push_back(M.first); 451 llvm::sort(ModuleIDs); 452 453 using namespace llvm::json; 454 455 Array OutModules; 456 for (auto &&ModID : ModuleIDs) { 457 auto &MD = Modules[ModID]; 458 Object O{{"name", MD.ID.ModuleName}, 459 {"context-hash", MD.ID.ContextHash}, 460 {"file-deps", toJSONSorted(MD.FileDeps)}, 461 {"clang-module-deps", toJSONSorted(MD.ClangModuleDeps)}, 462 {"clang-modulemap-file", MD.ClangModuleMapFile}, 463 {"command-line", MD.getBuildArguments()}, 464 {"link-libraries", toJSONSorted(MD.LinkLibraries)}}; 465 OutModules.push_back(std::move(O)); 466 } 467 468 Array TUs; 469 for (auto &&I : Inputs) { 470 Array Commands; 471 if (I.DriverCommandLine.empty()) { 472 for (const auto &Cmd : I.Commands) { 473 Object O{ 474 {"input-file", I.FileName}, 475 {"clang-context-hash", I.ContextHash}, 476 {"file-deps", I.FileDeps}, 477 {"clang-module-deps", toJSONSorted(I.ModuleDeps)}, 478 {"executable", Cmd.Executable}, 479 {"command-line", Cmd.Arguments}, 480 }; 481 Commands.push_back(std::move(O)); 482 } 483 } else { 484 Object O{ 485 {"input-file", I.FileName}, 486 {"clang-context-hash", I.ContextHash}, 487 {"file-deps", I.FileDeps}, 488 {"clang-module-deps", toJSONSorted(I.ModuleDeps)}, 489 {"executable", "clang"}, 490 {"command-line", I.DriverCommandLine}, 491 }; 492 Commands.push_back(std::move(O)); 493 } 494 TUs.push_back(Object{ 495 {"commands", std::move(Commands)}, 496 }); 497 } 498 499 Object Output{ 500 {"modules", std::move(OutModules)}, 501 {"translation-units", std::move(TUs)}, 502 }; 503 504 OS << llvm::formatv("{0:2}\n", Value(std::move(Output))); 505 } 506 507 private: 508 struct IndexedModuleID { 509 ModuleID ID; 510 511 // FIXME: This is mutable so that it can still be updated after insertion 512 // into an unordered associative container. This is "fine", since this 513 // field doesn't contribute to the hash, but it's a brittle hack. 514 mutable size_t InputIndex; 515 516 bool operator==(const IndexedModuleID &Other) const { 517 return ID == Other.ID; 518 } 519 520 bool operator<(const IndexedModuleID &Other) const { 521 /// We need the output of clang-scan-deps to be deterministic. However, 522 /// the dependency graph may contain two modules with the same name. How 523 /// do we decide which one to print first? If we made that decision based 524 /// on the context hash, the ordering would be deterministic, but 525 /// different across machines. This can happen for example when the inputs 526 /// or the SDKs (which both contribute to the "context" hash) live in 527 /// different absolute locations. We solve that by tracking the index of 528 /// the first input TU that (transitively) imports the dependency, which 529 /// is always the same for the same input, resulting in deterministic 530 /// sorting that's also reproducible across machines. 531 return std::tie(ID.ModuleName, InputIndex) < 532 std::tie(Other.ID.ModuleName, Other.InputIndex); 533 } 534 535 struct Hasher { 536 std::size_t operator()(const IndexedModuleID &IMID) const { 537 return llvm::hash_value(IMID.ID); 538 } 539 }; 540 }; 541 542 struct InputDeps { 543 std::string FileName; 544 std::string ContextHash; 545 std::vector<std::string> FileDeps; 546 std::vector<ModuleID> ModuleDeps; 547 std::vector<std::string> DriverCommandLine; 548 std::vector<Command> Commands; 549 }; 550 551 std::mutex Lock; 552 std::unordered_map<IndexedModuleID, ModuleDeps, IndexedModuleID::Hasher> 553 Modules; 554 std::vector<InputDeps> Inputs; 555 }; 556 557 static bool handleTranslationUnitResult( 558 StringRef Input, llvm::Expected<TranslationUnitDeps> &MaybeTUDeps, 559 FullDeps &FD, size_t InputIndex, SharedStream &OS, SharedStream &Errs) { 560 if (!MaybeTUDeps) { 561 llvm::handleAllErrors( 562 MaybeTUDeps.takeError(), [&Input, &Errs](llvm::StringError &Err) { 563 Errs.applyLocked([&](raw_ostream &OS) { 564 OS << "Error while scanning dependencies for " << Input << ":\n"; 565 OS << Err.getMessage(); 566 }); 567 }); 568 return true; 569 } 570 FD.mergeDeps(Input, std::move(*MaybeTUDeps), InputIndex); 571 return false; 572 } 573 574 static bool handleModuleResult( 575 StringRef ModuleName, llvm::Expected<ModuleDepsGraph> &MaybeModuleGraph, 576 FullDeps &FD, size_t InputIndex, SharedStream &OS, SharedStream &Errs) { 577 if (!MaybeModuleGraph) { 578 llvm::handleAllErrors(MaybeModuleGraph.takeError(), 579 [&ModuleName, &Errs](llvm::StringError &Err) { 580 Errs.applyLocked([&](raw_ostream &OS) { 581 OS << "Error while scanning dependencies for " 582 << ModuleName << ":\n"; 583 OS << Err.getMessage(); 584 }); 585 }); 586 return true; 587 } 588 FD.mergeDeps(std::move(*MaybeModuleGraph), InputIndex); 589 return false; 590 } 591 592 class P1689Deps { 593 public: 594 void printDependencies(raw_ostream &OS) { 595 addSourcePathsToRequires(); 596 // Sort the modules by name to get a deterministic order. 597 llvm::sort(Rules, [](const P1689Rule &A, const P1689Rule &B) { 598 return A.PrimaryOutput < B.PrimaryOutput; 599 }); 600 601 using namespace llvm::json; 602 Array OutputRules; 603 for (const P1689Rule &R : Rules) { 604 Object O{{"primary-output", R.PrimaryOutput}}; 605 606 if (R.Provides) { 607 Array Provides; 608 Object Provided{{"logical-name", R.Provides->ModuleName}, 609 {"source-path", R.Provides->SourcePath}, 610 {"is-interface", R.Provides->IsStdCXXModuleInterface}}; 611 Provides.push_back(std::move(Provided)); 612 O.insert({"provides", std::move(Provides)}); 613 } 614 615 Array Requires; 616 for (const P1689ModuleInfo &Info : R.Requires) { 617 Object RequiredInfo{{"logical-name", Info.ModuleName}}; 618 if (!Info.SourcePath.empty()) 619 RequiredInfo.insert({"source-path", Info.SourcePath}); 620 Requires.push_back(std::move(RequiredInfo)); 621 } 622 623 if (!Requires.empty()) 624 O.insert({"requires", std::move(Requires)}); 625 626 OutputRules.push_back(std::move(O)); 627 } 628 629 Object Output{ 630 {"version", 1}, {"revision", 0}, {"rules", std::move(OutputRules)}}; 631 632 OS << llvm::formatv("{0:2}\n", Value(std::move(Output))); 633 } 634 635 void addRules(P1689Rule &Rule) { 636 std::unique_lock<std::mutex> LockGuard(Lock); 637 Rules.push_back(Rule); 638 } 639 640 private: 641 void addSourcePathsToRequires() { 642 llvm::DenseMap<StringRef, StringRef> ModuleSourceMapper; 643 for (const P1689Rule &R : Rules) 644 if (R.Provides && !R.Provides->SourcePath.empty()) 645 ModuleSourceMapper[R.Provides->ModuleName] = R.Provides->SourcePath; 646 647 for (P1689Rule &R : Rules) { 648 for (P1689ModuleInfo &Info : R.Requires) { 649 auto Iter = ModuleSourceMapper.find(Info.ModuleName); 650 if (Iter != ModuleSourceMapper.end()) 651 Info.SourcePath = Iter->second; 652 } 653 } 654 } 655 656 std::mutex Lock; 657 std::vector<P1689Rule> Rules; 658 }; 659 660 static bool 661 handleP1689DependencyToolResult(const std::string &Input, 662 llvm::Expected<P1689Rule> &MaybeRule, 663 P1689Deps &PD, SharedStream &Errs) { 664 if (!MaybeRule) { 665 llvm::handleAllErrors( 666 MaybeRule.takeError(), [&Input, &Errs](llvm::StringError &Err) { 667 Errs.applyLocked([&](raw_ostream &OS) { 668 OS << "Error while scanning dependencies for " << Input << ":\n"; 669 OS << Err.getMessage(); 670 }); 671 }); 672 return true; 673 } 674 PD.addRules(*MaybeRule); 675 return false; 676 } 677 678 /// Construct a path for the explicitly built PCM. 679 static std::string constructPCMPath(ModuleID MID, StringRef OutputDir) { 680 SmallString<256> ExplicitPCMPath(OutputDir); 681 llvm::sys::path::append(ExplicitPCMPath, MID.ContextHash, 682 MID.ModuleName + "-" + MID.ContextHash + ".pcm"); 683 return std::string(ExplicitPCMPath); 684 } 685 686 static std::string lookupModuleOutput(const ModuleID &MID, ModuleOutputKind MOK, 687 StringRef OutputDir) { 688 std::string PCMPath = constructPCMPath(MID, OutputDir); 689 switch (MOK) { 690 case ModuleOutputKind::ModuleFile: 691 return PCMPath; 692 case ModuleOutputKind::DependencyFile: 693 return PCMPath + ".d"; 694 case ModuleOutputKind::DependencyTargets: 695 // Null-separate the list of targets. 696 return join(ModuleDepTargets, StringRef("\0", 1)); 697 case ModuleOutputKind::DiagnosticSerializationFile: 698 return PCMPath + ".diag"; 699 } 700 llvm_unreachable("Fully covered switch above!"); 701 } 702 703 static std::string getModuleCachePath(ArrayRef<std::string> Args) { 704 for (StringRef Arg : llvm::reverse(Args)) { 705 Arg.consume_front("/clang:"); 706 if (Arg.consume_front("-fmodules-cache-path=")) 707 return std::string(Arg); 708 } 709 SmallString<128> Path; 710 driver::Driver::getDefaultModuleCachePath(Path); 711 return std::string(Path); 712 } 713 714 /// Attempts to construct the compilation database from '-compilation-database' 715 /// or from the arguments following the positional '--'. 716 static std::unique_ptr<tooling::CompilationDatabase> 717 getCompilationDatabase(int argc, char **argv, std::string &ErrorMessage) { 718 ParseArgs(argc, argv); 719 720 if (!(CommandLine.empty() ^ CompilationDB.empty())) { 721 llvm::errs() << "The compilation command line must be provided either via " 722 "'-compilation-database' or after '--'."; 723 return nullptr; 724 } 725 726 if (!CompilationDB.empty()) 727 return tooling::JSONCompilationDatabase::loadFromFile( 728 CompilationDB, ErrorMessage, 729 tooling::JSONCommandLineSyntax::AutoDetect); 730 731 llvm::IntrusiveRefCntPtr<DiagnosticsEngine> Diags = 732 CompilerInstance::createDiagnostics(new DiagnosticOptions); 733 driver::Driver TheDriver(CommandLine[0], llvm::sys::getDefaultTargetTriple(), 734 *Diags); 735 TheDriver.setCheckInputsExist(false); 736 std::unique_ptr<driver::Compilation> C( 737 TheDriver.BuildCompilation(CommandLine)); 738 if (!C || C->getJobs().empty()) 739 return nullptr; 740 741 auto Cmd = C->getJobs().begin(); 742 auto CI = std::make_unique<CompilerInvocation>(); 743 CompilerInvocation::CreateFromArgs(*CI, Cmd->getArguments(), *Diags, 744 CommandLine[0]); 745 if (!CI) 746 return nullptr; 747 748 FrontendOptions &FEOpts = CI->getFrontendOpts(); 749 if (FEOpts.Inputs.size() != 1) { 750 llvm::errs() 751 << "Exactly one input file is required in the per-file mode ('--').\n"; 752 return nullptr; 753 } 754 755 // There might be multiple jobs for a compilation. Extract the specified 756 // output filename from the last job. 757 auto LastCmd = C->getJobs().end(); 758 LastCmd--; 759 if (LastCmd->getOutputFilenames().size() != 1) { 760 llvm::errs() 761 << "Exactly one output file is required in the per-file mode ('--').\n"; 762 return nullptr; 763 } 764 StringRef OutputFile = LastCmd->getOutputFilenames().front(); 765 766 class InplaceCompilationDatabase : public tooling::CompilationDatabase { 767 public: 768 InplaceCompilationDatabase(StringRef InputFile, StringRef OutputFile, 769 ArrayRef<const char *> CommandLine) 770 : Command(".", InputFile, {}, OutputFile) { 771 for (auto *C : CommandLine) 772 Command.CommandLine.push_back(C); 773 } 774 775 std::vector<tooling::CompileCommand> 776 getCompileCommands(StringRef FilePath) const override { 777 if (FilePath != Command.Filename) 778 return {}; 779 return {Command}; 780 } 781 782 std::vector<std::string> getAllFiles() const override { 783 return {Command.Filename}; 784 } 785 786 std::vector<tooling::CompileCommand> 787 getAllCompileCommands() const override { 788 return {Command}; 789 } 790 791 private: 792 tooling::CompileCommand Command; 793 }; 794 795 return std::make_unique<InplaceCompilationDatabase>( 796 FEOpts.Inputs[0].getFile(), OutputFile, CommandLine); 797 } 798 799 int clang_scan_deps_main(int argc, char **argv, const llvm::ToolContext &) { 800 llvm::InitializeAllTargetInfos(); 801 std::string ErrorMessage; 802 std::unique_ptr<tooling::CompilationDatabase> Compilations = 803 getCompilationDatabase(argc, argv, ErrorMessage); 804 if (!Compilations) { 805 llvm::errs() << ErrorMessage << "\n"; 806 return 1; 807 } 808 809 llvm::cl::PrintOptionValues(); 810 811 // Expand response files in advance, so that we can "see" all the arguments 812 // when adjusting below. 813 Compilations = expandResponseFiles(std::move(Compilations), 814 llvm::vfs::getRealFileSystem()); 815 816 Compilations = inferTargetAndDriverMode(std::move(Compilations)); 817 818 Compilations = inferToolLocation(std::move(Compilations)); 819 820 // The command options are rewritten to run Clang in preprocessor only mode. 821 auto AdjustingCompilations = 822 std::make_unique<tooling::ArgumentsAdjustingCompilations>( 823 std::move(Compilations)); 824 ResourceDirectoryCache ResourceDirCache; 825 826 AdjustingCompilations->appendArgumentsAdjuster( 827 [&ResourceDirCache](const tooling::CommandLineArguments &Args, 828 StringRef FileName) { 829 std::string LastO; 830 bool HasResourceDir = false; 831 bool ClangCLMode = false; 832 auto FlagsEnd = llvm::find(Args, "--"); 833 if (FlagsEnd != Args.begin()) { 834 ClangCLMode = 835 llvm::sys::path::stem(Args[0]).contains_insensitive("clang-cl") || 836 llvm::is_contained(Args, "--driver-mode=cl"); 837 838 // Reverse scan, starting at the end or at the element before "--". 839 auto R = std::make_reverse_iterator(FlagsEnd); 840 auto E = Args.rend(); 841 // Don't include Args[0] in the iteration; that's the executable, not 842 // an option. 843 if (E != R) 844 E--; 845 for (auto I = R; I != E; ++I) { 846 StringRef Arg = *I; 847 if (ClangCLMode) { 848 // Ignore arguments that are preceded by "-Xclang". 849 if ((I + 1) != E && I[1] == "-Xclang") 850 continue; 851 if (LastO.empty()) { 852 // With clang-cl, the output obj file can be specified with 853 // "/opath", "/o path", "/Fopath", and the dash counterparts. 854 // Also, clang-cl adds ".obj" extension if none is found. 855 if ((Arg == "-o" || Arg == "/o") && I != R) 856 LastO = I[-1]; // Next argument (reverse iterator) 857 else if (Arg.starts_with("/Fo") || Arg.starts_with("-Fo")) 858 LastO = Arg.drop_front(3).str(); 859 else if (Arg.starts_with("/o") || Arg.starts_with("-o")) 860 LastO = Arg.drop_front(2).str(); 861 862 if (!LastO.empty() && !llvm::sys::path::has_extension(LastO)) 863 LastO.append(".obj"); 864 } 865 } 866 if (Arg == "-resource-dir") 867 HasResourceDir = true; 868 } 869 } 870 tooling::CommandLineArguments AdjustedArgs(Args.begin(), FlagsEnd); 871 // The clang-cl driver passes "-o -" to the frontend. Inject the real 872 // file here to ensure "-MT" can be deduced if need be. 873 if (ClangCLMode && !LastO.empty()) { 874 AdjustedArgs.push_back("/clang:-o"); 875 AdjustedArgs.push_back("/clang:" + LastO); 876 } 877 878 if (!HasResourceDir && ResourceDirRecipe == RDRK_InvokeCompiler) { 879 StringRef ResourceDir = 880 ResourceDirCache.findResourceDir(Args, ClangCLMode); 881 if (!ResourceDir.empty()) { 882 AdjustedArgs.push_back("-resource-dir"); 883 AdjustedArgs.push_back(std::string(ResourceDir)); 884 } 885 } 886 AdjustedArgs.insert(AdjustedArgs.end(), FlagsEnd, Args.end()); 887 return AdjustedArgs; 888 }); 889 890 SharedStream Errs(llvm::errs()); 891 892 std::optional<llvm::raw_fd_ostream> FileOS; 893 llvm::raw_ostream &ThreadUnsafeDependencyOS = [&]() -> llvm::raw_ostream & { 894 if (OutputFileName == "-") 895 return llvm::outs(); 896 897 if (OutputFileName == "/dev/null") 898 return llvm::nulls(); 899 900 std::error_code EC; 901 FileOS.emplace(OutputFileName, EC); 902 if (EC) { 903 llvm::errs() << "Failed to open output file '" << OutputFileName 904 << "': " << llvm::errorCodeToError(EC) << '\n'; 905 std::exit(1); 906 } 907 return *FileOS; 908 }(); 909 SharedStream DependencyOS(ThreadUnsafeDependencyOS); 910 911 std::vector<tooling::CompileCommand> Inputs = 912 AdjustingCompilations->getAllCompileCommands(); 913 914 std::atomic<bool> HadErrors(false); 915 std::optional<FullDeps> FD; 916 P1689Deps PD; 917 918 std::mutex Lock; 919 size_t Index = 0; 920 auto GetNextInputIndex = [&]() -> std::optional<size_t> { 921 std::unique_lock<std::mutex> LockGuard(Lock); 922 if (Index < Inputs.size()) 923 return Index++; 924 return {}; 925 }; 926 927 if (Format == ScanningOutputFormat::Full) 928 FD.emplace(ModuleName.empty() ? Inputs.size() : 0); 929 930 auto ScanningTask = [&](DependencyScanningService &Service) { 931 DependencyScanningTool WorkerTool(Service); 932 933 llvm::DenseSet<ModuleID> AlreadySeenModules; 934 while (auto MaybeInputIndex = GetNextInputIndex()) { 935 size_t LocalIndex = *MaybeInputIndex; 936 const tooling::CompileCommand *Input = &Inputs[LocalIndex]; 937 std::string Filename = std::move(Input->Filename); 938 std::string CWD = std::move(Input->Directory); 939 940 std::optional<StringRef> MaybeModuleName; 941 if (!ModuleName.empty()) 942 MaybeModuleName = ModuleName; 943 944 std::string OutputDir(ModuleFilesDir); 945 if (OutputDir.empty()) 946 OutputDir = getModuleCachePath(Input->CommandLine); 947 auto LookupOutput = [&](const ModuleID &MID, ModuleOutputKind MOK) { 948 return ::lookupModuleOutput(MID, MOK, OutputDir); 949 }; 950 951 // Run the tool on it. 952 if (Format == ScanningOutputFormat::Make) { 953 auto MaybeFile = WorkerTool.getDependencyFile(Input->CommandLine, CWD); 954 if (handleMakeDependencyToolResult(Filename, MaybeFile, DependencyOS, 955 Errs)) 956 HadErrors = true; 957 } else if (Format == ScanningOutputFormat::P1689) { 958 // It is useful to generate the make-format dependency output during 959 // the scanning for P1689. Otherwise the users need to scan again for 960 // it. We will generate the make-format dependency output if we find 961 // `-MF` in the command lines. 962 std::string MakeformatOutputPath; 963 std::string MakeformatOutput; 964 965 auto MaybeRule = WorkerTool.getP1689ModuleDependencyFile( 966 *Input, CWD, MakeformatOutput, MakeformatOutputPath); 967 968 if (handleP1689DependencyToolResult(Filename, MaybeRule, PD, Errs)) 969 HadErrors = true; 970 971 if (!MakeformatOutputPath.empty() && !MakeformatOutput.empty() && 972 !HadErrors) { 973 static std::mutex Lock; 974 // With compilation database, we may open different files 975 // concurrently or we may write the same file concurrently. So we 976 // use a map here to allow multiple compile commands to write to the 977 // same file. Also we need a lock here to avoid data race. 978 static llvm::StringMap<llvm::raw_fd_ostream> OSs; 979 std::unique_lock<std::mutex> LockGuard(Lock); 980 981 auto OSIter = OSs.find(MakeformatOutputPath); 982 if (OSIter == OSs.end()) { 983 std::error_code EC; 984 OSIter = 985 OSs.try_emplace(MakeformatOutputPath, MakeformatOutputPath, EC) 986 .first; 987 if (EC) 988 llvm::errs() << "Failed to open P1689 make format output file \"" 989 << MakeformatOutputPath << "\" for " << EC.message() 990 << "\n"; 991 } 992 993 SharedStream MakeformatOS(OSIter->second); 994 llvm::Expected<std::string> MaybeOutput(MakeformatOutput); 995 if (handleMakeDependencyToolResult(Filename, MaybeOutput, 996 MakeformatOS, Errs)) 997 HadErrors = true; 998 } 999 } else if (MaybeModuleName) { 1000 auto MaybeModuleDepsGraph = WorkerTool.getModuleDependencies( 1001 *MaybeModuleName, Input->CommandLine, CWD, AlreadySeenModules, 1002 LookupOutput); 1003 if (handleModuleResult(*MaybeModuleName, MaybeModuleDepsGraph, *FD, 1004 LocalIndex, DependencyOS, Errs)) 1005 HadErrors = true; 1006 } else { 1007 auto MaybeTUDeps = WorkerTool.getTranslationUnitDependencies( 1008 Input->CommandLine, CWD, AlreadySeenModules, LookupOutput); 1009 if (handleTranslationUnitResult(Filename, MaybeTUDeps, *FD, LocalIndex, 1010 DependencyOS, Errs)) 1011 HadErrors = true; 1012 } 1013 } 1014 }; 1015 1016 DependencyScanningService Service(ScanMode, Format, OptimizeArgs, 1017 EagerLoadModules); 1018 1019 llvm::Timer T; 1020 T.startTimer(); 1021 1022 if (Inputs.size() == 1) { 1023 ScanningTask(Service); 1024 } else { 1025 llvm::DefaultThreadPool Pool(llvm::hardware_concurrency(NumThreads)); 1026 1027 if (Verbose) { 1028 llvm::outs() << "Running clang-scan-deps on " << Inputs.size() 1029 << " files using " << Pool.getMaxConcurrency() 1030 << " workers\n"; 1031 } 1032 1033 for (unsigned I = 0; I < Pool.getMaxConcurrency(); ++I) 1034 Pool.async([ScanningTask, &Service]() { ScanningTask(Service); }); 1035 1036 Pool.wait(); 1037 } 1038 1039 T.stopTimer(); 1040 if (PrintTiming) 1041 llvm::errs() << llvm::format( 1042 "clang-scan-deps timing: %0.2fs wall, %0.2fs process\n", 1043 T.getTotalTime().getWallTime(), T.getTotalTime().getProcessTime()); 1044 1045 if (RoundTripArgs) 1046 if (FD && FD->roundTripCommands(llvm::errs())) 1047 HadErrors = true; 1048 1049 if (Format == ScanningOutputFormat::Full) 1050 FD->printFullOutput(ThreadUnsafeDependencyOS); 1051 else if (Format == ScanningOutputFormat::P1689) 1052 PD.printDependencies(ThreadUnsafeDependencyOS); 1053 1054 return HadErrors; 1055 } 1056