Path: blob/main/contrib/llvm-project/clang/tools/clang-scan-deps/ClangScanDeps.cpp
35236 views
//===- ClangScanDeps.cpp - Implementation of clang-scan-deps --------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include "clang/Driver/Compilation.h"9#include "clang/Driver/Driver.h"10#include "clang/Frontend/CompilerInstance.h"11#include "clang/Frontend/TextDiagnosticPrinter.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 "clang/Tooling/Tooling.h"18#include "llvm/ADT/STLExtras.h"19#include "llvm/ADT/Twine.h"20#include "llvm/Support/CommandLine.h"21#include "llvm/Support/FileUtilities.h"22#include "llvm/Support/Format.h"23#include "llvm/Support/JSON.h"24#include "llvm/Support/LLVMDriver.h"25#include "llvm/Support/Program.h"26#include "llvm/Support/Signals.h"27#include "llvm/Support/TargetSelect.h"28#include "llvm/Support/ThreadPool.h"29#include "llvm/Support/Threading.h"30#include "llvm/Support/Timer.h"31#include "llvm/TargetParser/Host.h"32#include <mutex>33#include <optional>34#include <thread>3536#include "Opts.inc"3738using namespace clang;39using namespace tooling::dependencies;4041namespace {4243using namespace llvm::opt;44enum ID {45OPT_INVALID = 0, // This is not an option ID.46#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),47#include "Opts.inc"48#undef OPTION49};5051#define PREFIX(NAME, VALUE) \52constexpr llvm::StringLiteral NAME##_init[] = VALUE; \53constexpr llvm::ArrayRef<llvm::StringLiteral> NAME( \54NAME##_init, std::size(NAME##_init) - 1);55#include "Opts.inc"56#undef PREFIX5758const llvm::opt::OptTable::Info InfoTable[] = {59#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),60#include "Opts.inc"61#undef OPTION62};6364class ScanDepsOptTable : public llvm::opt::GenericOptTable {65public:66ScanDepsOptTable() : GenericOptTable(InfoTable) {67setGroupedShortOptions(true);68}69};7071enum ResourceDirRecipeKind {72RDRK_ModifyCompilerPath,73RDRK_InvokeCompiler,74};7576static std::string OutputFileName = "-";77static ScanningMode ScanMode = ScanningMode::DependencyDirectivesScan;78static ScanningOutputFormat Format = ScanningOutputFormat::Make;79static ScanningOptimizations OptimizeArgs;80static std::string ModuleFilesDir;81static bool EagerLoadModules;82static unsigned NumThreads = 0;83static std::string CompilationDB;84static std::string ModuleName;85static std::vector<std::string> ModuleDepTargets;86static bool DeprecatedDriverCommand;87static ResourceDirRecipeKind ResourceDirRecipe;88static bool Verbose;89static bool PrintTiming;90static llvm::BumpPtrAllocator Alloc;91static llvm::StringSaver Saver{Alloc};92static std::vector<const char *> CommandLine;9394#ifndef NDEBUG95static constexpr bool DoRoundTripDefault = true;96#else97static constexpr bool DoRoundTripDefault = false;98#endif99100static bool RoundTripArgs = DoRoundTripDefault;101102static void ParseArgs(int argc, char **argv) {103ScanDepsOptTable Tbl;104llvm::StringRef ToolName = argv[0];105llvm::opt::InputArgList Args =106Tbl.parseArgs(argc, argv, OPT_UNKNOWN, Saver, [&](StringRef Msg) {107llvm::errs() << Msg << '\n';108std::exit(1);109});110111if (Args.hasArg(OPT_help)) {112Tbl.printHelp(llvm::outs(), "clang-scan-deps [options]", "clang-scan-deps");113std::exit(0);114}115if (Args.hasArg(OPT_version)) {116llvm::outs() << ToolName << '\n';117llvm::cl::PrintVersionMessage();118std::exit(0);119}120if (const llvm::opt::Arg *A = Args.getLastArg(OPT_mode_EQ)) {121auto ModeType =122llvm::StringSwitch<std::optional<ScanningMode>>(A->getValue())123.Case("preprocess-dependency-directives",124ScanningMode::DependencyDirectivesScan)125.Case("preprocess", ScanningMode::CanonicalPreprocessing)126.Default(std::nullopt);127if (!ModeType) {128llvm::errs() << ToolName129<< ": for the --mode option: Cannot find option named '"130<< A->getValue() << "'\n";131std::exit(1);132}133ScanMode = *ModeType;134}135136if (const llvm::opt::Arg *A = Args.getLastArg(OPT_format_EQ)) {137auto FormatType =138llvm::StringSwitch<std::optional<ScanningOutputFormat>>(A->getValue())139.Case("make", ScanningOutputFormat::Make)140.Case("p1689", ScanningOutputFormat::P1689)141.Case("experimental-full", ScanningOutputFormat::Full)142.Default(std::nullopt);143if (!FormatType) {144llvm::errs() << ToolName145<< ": for the --format option: Cannot find option named '"146<< A->getValue() << "'\n";147std::exit(1);148}149Format = *FormatType;150}151152std::vector<std::string> OptimizationFlags =153Args.getAllArgValues(OPT_optimize_args_EQ);154OptimizeArgs = ScanningOptimizations::None;155for (const auto &Arg : OptimizationFlags) {156auto Optimization =157llvm::StringSwitch<std::optional<ScanningOptimizations>>(Arg)158.Case("none", ScanningOptimizations::None)159.Case("header-search", ScanningOptimizations::HeaderSearch)160.Case("system-warnings", ScanningOptimizations::SystemWarnings)161.Case("vfs", ScanningOptimizations::VFS)162.Case("canonicalize-macros", ScanningOptimizations::Macros)163.Case("all", ScanningOptimizations::All)164.Default(std::nullopt);165if (!Optimization) {166llvm::errs()167<< ToolName168<< ": for the --optimize-args option: Cannot find option named '"169<< Arg << "'\n";170std::exit(1);171}172OptimizeArgs |= *Optimization;173}174if (OptimizationFlags.empty())175OptimizeArgs = ScanningOptimizations::Default;176177if (const llvm::opt::Arg *A = Args.getLastArg(OPT_module_files_dir_EQ))178ModuleFilesDir = A->getValue();179180if (const llvm::opt::Arg *A = Args.getLastArg(OPT_o))181OutputFileName = A->getValue();182183EagerLoadModules = Args.hasArg(OPT_eager_load_pcm);184185if (const llvm::opt::Arg *A = Args.getLastArg(OPT_j)) {186StringRef S{A->getValue()};187if (!llvm::to_integer(S, NumThreads, 0)) {188llvm::errs() << ToolName << ": for the -j option: '" << S189<< "' value invalid for uint argument!\n";190std::exit(1);191}192}193194if (const llvm::opt::Arg *A = Args.getLastArg(OPT_compilation_database_EQ))195CompilationDB = A->getValue();196197if (const llvm::opt::Arg *A = Args.getLastArg(OPT_module_name_EQ))198ModuleName = A->getValue();199200for (const llvm::opt::Arg *A : Args.filtered(OPT_dependency_target_EQ))201ModuleDepTargets.emplace_back(A->getValue());202203DeprecatedDriverCommand = Args.hasArg(OPT_deprecated_driver_command);204205if (const llvm::opt::Arg *A = Args.getLastArg(OPT_resource_dir_recipe_EQ)) {206auto Kind =207llvm::StringSwitch<std::optional<ResourceDirRecipeKind>>(A->getValue())208.Case("modify-compiler-path", RDRK_ModifyCompilerPath)209.Case("invoke-compiler", RDRK_InvokeCompiler)210.Default(std::nullopt);211if (!Kind) {212llvm::errs() << ToolName213<< ": for the --resource-dir-recipe option: Cannot find "214"option named '"215<< A->getValue() << "'\n";216std::exit(1);217}218ResourceDirRecipe = *Kind;219}220221PrintTiming = Args.hasArg(OPT_print_timing);222223Verbose = Args.hasArg(OPT_verbose);224225RoundTripArgs = Args.hasArg(OPT_round_trip_args);226227if (const llvm::opt::Arg *A = Args.getLastArgNoClaim(OPT_DASH_DASH))228CommandLine.assign(A->getValues().begin(), A->getValues().end());229}230231class SharedStream {232public:233SharedStream(raw_ostream &OS) : OS(OS) {}234void applyLocked(llvm::function_ref<void(raw_ostream &OS)> Fn) {235std::unique_lock<std::mutex> LockGuard(Lock);236Fn(OS);237OS.flush();238}239240private:241std::mutex Lock;242raw_ostream &OS;243};244245class ResourceDirectoryCache {246public:247/// findResourceDir finds the resource directory relative to the clang248/// compiler being used in Args, by running it with "-print-resource-dir"249/// option and cache the results for reuse. \returns resource directory path250/// associated with the given invocation command or empty string if the251/// compiler path is NOT an absolute path.252StringRef findResourceDir(const tooling::CommandLineArguments &Args,253bool ClangCLMode) {254if (Args.size() < 1)255return "";256257const std::string &ClangBinaryPath = Args[0];258if (!llvm::sys::path::is_absolute(ClangBinaryPath))259return "";260261const std::string &ClangBinaryName =262std::string(llvm::sys::path::filename(ClangBinaryPath));263264std::unique_lock<std::mutex> LockGuard(CacheLock);265const auto &CachedResourceDir = Cache.find(ClangBinaryPath);266if (CachedResourceDir != Cache.end())267return CachedResourceDir->second;268269std::vector<StringRef> PrintResourceDirArgs{ClangBinaryName};270if (ClangCLMode)271PrintResourceDirArgs.push_back("/clang:-print-resource-dir");272else273PrintResourceDirArgs.push_back("-print-resource-dir");274275llvm::SmallString<64> OutputFile, ErrorFile;276llvm::sys::fs::createTemporaryFile("print-resource-dir-output",277"" /*no-suffix*/, OutputFile);278llvm::sys::fs::createTemporaryFile("print-resource-dir-error",279"" /*no-suffix*/, ErrorFile);280llvm::FileRemover OutputRemover(OutputFile.c_str());281llvm::FileRemover ErrorRemover(ErrorFile.c_str());282std::optional<StringRef> Redirects[] = {283{""}, // Stdin284OutputFile.str(),285ErrorFile.str(),286};287if (llvm::sys::ExecuteAndWait(ClangBinaryPath, PrintResourceDirArgs, {},288Redirects)) {289auto ErrorBuf = llvm::MemoryBuffer::getFile(ErrorFile.c_str());290llvm::errs() << ErrorBuf.get()->getBuffer();291return "";292}293294auto OutputBuf = llvm::MemoryBuffer::getFile(OutputFile.c_str());295if (!OutputBuf)296return "";297StringRef Output = OutputBuf.get()->getBuffer().rtrim('\n');298299Cache[ClangBinaryPath] = Output.str();300return Cache[ClangBinaryPath];301}302303private:304std::map<std::string, std::string> Cache;305std::mutex CacheLock;306};307308} // end anonymous namespace309310/// Takes the result of a dependency scan and prints error / dependency files311/// based on the result.312///313/// \returns True on error.314static bool315handleMakeDependencyToolResult(const std::string &Input,316llvm::Expected<std::string> &MaybeFile,317SharedStream &OS, SharedStream &Errs) {318if (!MaybeFile) {319llvm::handleAllErrors(320MaybeFile.takeError(), [&Input, &Errs](llvm::StringError &Err) {321Errs.applyLocked([&](raw_ostream &OS) {322OS << "Error while scanning dependencies for " << Input << ":\n";323OS << Err.getMessage();324});325});326return true;327}328OS.applyLocked([&](raw_ostream &OS) { OS << *MaybeFile; });329return false;330}331332static llvm::json::Array toJSONSorted(const llvm::StringSet<> &Set) {333std::vector<llvm::StringRef> Strings;334for (auto &&I : Set)335Strings.push_back(I.getKey());336llvm::sort(Strings);337return llvm::json::Array(Strings);338}339340// Technically, we don't need to sort the dependency list to get determinism.341// Leaving these be will simply preserve the import order.342static llvm::json::Array toJSONSorted(std::vector<ModuleID> V) {343llvm::sort(V);344345llvm::json::Array Ret;346for (const ModuleID &MID : V)347Ret.push_back(llvm::json::Object(348{{"module-name", MID.ModuleName}, {"context-hash", MID.ContextHash}}));349return Ret;350}351352static llvm::json::Array353toJSONSorted(llvm::SmallVector<Module::LinkLibrary, 2> &LinkLibs) {354llvm::sort(LinkLibs, [](const Module::LinkLibrary &lhs,355const Module::LinkLibrary &rhs) {356return lhs.Library < rhs.Library;357});358359llvm::json::Array Ret;360for (const Module::LinkLibrary &LL : LinkLibs)361Ret.push_back(llvm::json::Object(362{{"link-name", LL.Library}, {"isFramework", LL.IsFramework}}));363return Ret;364}365366// Thread safe.367class FullDeps {368public:369FullDeps(size_t NumInputs) : Inputs(NumInputs) {}370371void mergeDeps(StringRef Input, TranslationUnitDeps TUDeps,372size_t InputIndex) {373mergeDeps(std::move(TUDeps.ModuleGraph), InputIndex);374375InputDeps ID;376ID.FileName = std::string(Input);377ID.ContextHash = std::move(TUDeps.ID.ContextHash);378ID.FileDeps = std::move(TUDeps.FileDeps);379ID.ModuleDeps = std::move(TUDeps.ClangModuleDeps);380ID.DriverCommandLine = std::move(TUDeps.DriverCommandLine);381ID.Commands = std::move(TUDeps.Commands);382383assert(InputIndex < Inputs.size() && "Input index out of bounds");384assert(Inputs[InputIndex].FileName.empty() && "Result already populated");385Inputs[InputIndex] = std::move(ID);386}387388void mergeDeps(ModuleDepsGraph Graph, size_t InputIndex) {389std::vector<ModuleDeps *> NewMDs;390{391std::unique_lock<std::mutex> ul(Lock);392for (const ModuleDeps &MD : Graph) {393auto I = Modules.find({MD.ID, 0});394if (I != Modules.end()) {395I->first.InputIndex = std::min(I->first.InputIndex, InputIndex);396continue;397}398auto Res = Modules.insert(I, {{MD.ID, InputIndex}, std::move(MD)});399NewMDs.push_back(&Res->second);400}401// First call to \c getBuildArguments is somewhat expensive. Let's call it402// on the current thread (instead of the main one), and outside the403// critical section.404for (ModuleDeps *MD : NewMDs)405(void)MD->getBuildArguments();406}407}408409bool roundTripCommand(ArrayRef<std::string> ArgStrs,410DiagnosticsEngine &Diags) {411if (ArgStrs.empty() || ArgStrs[0] != "-cc1")412return false;413SmallVector<const char *> Args;414for (const std::string &Arg : ArgStrs)415Args.push_back(Arg.c_str());416return !CompilerInvocation::checkCC1RoundTrip(Args, Diags);417}418419// Returns \c true if any command lines fail to round-trip. We expect420// commands already be canonical when output by the scanner.421bool roundTripCommands(raw_ostream &ErrOS) {422IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions{};423TextDiagnosticPrinter DiagConsumer(ErrOS, &*DiagOpts);424IntrusiveRefCntPtr<DiagnosticsEngine> Diags =425CompilerInstance::createDiagnostics(&*DiagOpts, &DiagConsumer,426/*ShouldOwnClient=*/false);427428for (auto &&M : Modules)429if (roundTripCommand(M.second.getBuildArguments(), *Diags))430return true;431432for (auto &&I : Inputs)433for (const auto &Cmd : I.Commands)434if (roundTripCommand(Cmd.Arguments, *Diags))435return true;436437return false;438}439440void printFullOutput(raw_ostream &OS) {441// Skip sorting modules and constructing the JSON object if the output442// cannot be observed anyway. This makes timings less noisy.443if (&OS == &llvm::nulls())444return;445446// Sort the modules by name to get a deterministic order.447std::vector<IndexedModuleID> ModuleIDs;448for (auto &&M : Modules)449ModuleIDs.push_back(M.first);450llvm::sort(ModuleIDs);451452using namespace llvm::json;453454Array OutModules;455for (auto &&ModID : ModuleIDs) {456auto &MD = Modules[ModID];457Object O{{"name", MD.ID.ModuleName},458{"context-hash", MD.ID.ContextHash},459{"file-deps", toJSONSorted(MD.FileDeps)},460{"clang-module-deps", toJSONSorted(MD.ClangModuleDeps)},461{"clang-modulemap-file", MD.ClangModuleMapFile},462{"command-line", MD.getBuildArguments()},463{"link-libraries", toJSONSorted(MD.LinkLibraries)}};464OutModules.push_back(std::move(O));465}466467Array TUs;468for (auto &&I : Inputs) {469Array Commands;470if (I.DriverCommandLine.empty()) {471for (const auto &Cmd : I.Commands) {472Object O{473{"input-file", I.FileName},474{"clang-context-hash", I.ContextHash},475{"file-deps", I.FileDeps},476{"clang-module-deps", toJSONSorted(I.ModuleDeps)},477{"executable", Cmd.Executable},478{"command-line", Cmd.Arguments},479};480Commands.push_back(std::move(O));481}482} else {483Object O{484{"input-file", I.FileName},485{"clang-context-hash", I.ContextHash},486{"file-deps", I.FileDeps},487{"clang-module-deps", toJSONSorted(I.ModuleDeps)},488{"executable", "clang"},489{"command-line", I.DriverCommandLine},490};491Commands.push_back(std::move(O));492}493TUs.push_back(Object{494{"commands", std::move(Commands)},495});496}497498Object Output{499{"modules", std::move(OutModules)},500{"translation-units", std::move(TUs)},501};502503OS << llvm::formatv("{0:2}\n", Value(std::move(Output)));504}505506private:507struct IndexedModuleID {508ModuleID ID;509510// FIXME: This is mutable so that it can still be updated after insertion511// into an unordered associative container. This is "fine", since this512// field doesn't contribute to the hash, but it's a brittle hack.513mutable size_t InputIndex;514515bool operator==(const IndexedModuleID &Other) const {516return ID == Other.ID;517}518519bool operator<(const IndexedModuleID &Other) const {520/// We need the output of clang-scan-deps to be deterministic. However,521/// the dependency graph may contain two modules with the same name. How522/// do we decide which one to print first? If we made that decision based523/// on the context hash, the ordering would be deterministic, but524/// different across machines. This can happen for example when the inputs525/// or the SDKs (which both contribute to the "context" hash) live in526/// different absolute locations. We solve that by tracking the index of527/// the first input TU that (transitively) imports the dependency, which528/// is always the same for the same input, resulting in deterministic529/// sorting that's also reproducible across machines.530return std::tie(ID.ModuleName, InputIndex) <531std::tie(Other.ID.ModuleName, Other.InputIndex);532}533534struct Hasher {535std::size_t operator()(const IndexedModuleID &IMID) const {536return llvm::hash_value(IMID.ID);537}538};539};540541struct InputDeps {542std::string FileName;543std::string ContextHash;544std::vector<std::string> FileDeps;545std::vector<ModuleID> ModuleDeps;546std::vector<std::string> DriverCommandLine;547std::vector<Command> Commands;548};549550std::mutex Lock;551std::unordered_map<IndexedModuleID, ModuleDeps, IndexedModuleID::Hasher>552Modules;553std::vector<InputDeps> Inputs;554};555556static bool handleTranslationUnitResult(557StringRef Input, llvm::Expected<TranslationUnitDeps> &MaybeTUDeps,558FullDeps &FD, size_t InputIndex, SharedStream &OS, SharedStream &Errs) {559if (!MaybeTUDeps) {560llvm::handleAllErrors(561MaybeTUDeps.takeError(), [&Input, &Errs](llvm::StringError &Err) {562Errs.applyLocked([&](raw_ostream &OS) {563OS << "Error while scanning dependencies for " << Input << ":\n";564OS << Err.getMessage();565});566});567return true;568}569FD.mergeDeps(Input, std::move(*MaybeTUDeps), InputIndex);570return false;571}572573static bool handleModuleResult(574StringRef ModuleName, llvm::Expected<ModuleDepsGraph> &MaybeModuleGraph,575FullDeps &FD, size_t InputIndex, SharedStream &OS, SharedStream &Errs) {576if (!MaybeModuleGraph) {577llvm::handleAllErrors(MaybeModuleGraph.takeError(),578[&ModuleName, &Errs](llvm::StringError &Err) {579Errs.applyLocked([&](raw_ostream &OS) {580OS << "Error while scanning dependencies for "581<< ModuleName << ":\n";582OS << Err.getMessage();583});584});585return true;586}587FD.mergeDeps(std::move(*MaybeModuleGraph), InputIndex);588return false;589}590591class P1689Deps {592public:593void printDependencies(raw_ostream &OS) {594addSourcePathsToRequires();595// Sort the modules by name to get a deterministic order.596llvm::sort(Rules, [](const P1689Rule &A, const P1689Rule &B) {597return A.PrimaryOutput < B.PrimaryOutput;598});599600using namespace llvm::json;601Array OutputRules;602for (const P1689Rule &R : Rules) {603Object O{{"primary-output", R.PrimaryOutput}};604605if (R.Provides) {606Array Provides;607Object Provided{{"logical-name", R.Provides->ModuleName},608{"source-path", R.Provides->SourcePath},609{"is-interface", R.Provides->IsStdCXXModuleInterface}};610Provides.push_back(std::move(Provided));611O.insert({"provides", std::move(Provides)});612}613614Array Requires;615for (const P1689ModuleInfo &Info : R.Requires) {616Object RequiredInfo{{"logical-name", Info.ModuleName}};617if (!Info.SourcePath.empty())618RequiredInfo.insert({"source-path", Info.SourcePath});619Requires.push_back(std::move(RequiredInfo));620}621622if (!Requires.empty())623O.insert({"requires", std::move(Requires)});624625OutputRules.push_back(std::move(O));626}627628Object Output{629{"version", 1}, {"revision", 0}, {"rules", std::move(OutputRules)}};630631OS << llvm::formatv("{0:2}\n", Value(std::move(Output)));632}633634void addRules(P1689Rule &Rule) {635std::unique_lock<std::mutex> LockGuard(Lock);636Rules.push_back(Rule);637}638639private:640void addSourcePathsToRequires() {641llvm::DenseMap<StringRef, StringRef> ModuleSourceMapper;642for (const P1689Rule &R : Rules)643if (R.Provides && !R.Provides->SourcePath.empty())644ModuleSourceMapper[R.Provides->ModuleName] = R.Provides->SourcePath;645646for (P1689Rule &R : Rules) {647for (P1689ModuleInfo &Info : R.Requires) {648auto Iter = ModuleSourceMapper.find(Info.ModuleName);649if (Iter != ModuleSourceMapper.end())650Info.SourcePath = Iter->second;651}652}653}654655std::mutex Lock;656std::vector<P1689Rule> Rules;657};658659static bool660handleP1689DependencyToolResult(const std::string &Input,661llvm::Expected<P1689Rule> &MaybeRule,662P1689Deps &PD, SharedStream &Errs) {663if (!MaybeRule) {664llvm::handleAllErrors(665MaybeRule.takeError(), [&Input, &Errs](llvm::StringError &Err) {666Errs.applyLocked([&](raw_ostream &OS) {667OS << "Error while scanning dependencies for " << Input << ":\n";668OS << Err.getMessage();669});670});671return true;672}673PD.addRules(*MaybeRule);674return false;675}676677/// Construct a path for the explicitly built PCM.678static std::string constructPCMPath(ModuleID MID, StringRef OutputDir) {679SmallString<256> ExplicitPCMPath(OutputDir);680llvm::sys::path::append(ExplicitPCMPath, MID.ContextHash,681MID.ModuleName + "-" + MID.ContextHash + ".pcm");682return std::string(ExplicitPCMPath);683}684685static std::string lookupModuleOutput(const ModuleID &MID, ModuleOutputKind MOK,686StringRef OutputDir) {687std::string PCMPath = constructPCMPath(MID, OutputDir);688switch (MOK) {689case ModuleOutputKind::ModuleFile:690return PCMPath;691case ModuleOutputKind::DependencyFile:692return PCMPath + ".d";693case ModuleOutputKind::DependencyTargets:694// Null-separate the list of targets.695return join(ModuleDepTargets, StringRef("\0", 1));696case ModuleOutputKind::DiagnosticSerializationFile:697return PCMPath + ".diag";698}699llvm_unreachable("Fully covered switch above!");700}701702static std::string getModuleCachePath(ArrayRef<std::string> Args) {703for (StringRef Arg : llvm::reverse(Args)) {704Arg.consume_front("/clang:");705if (Arg.consume_front("-fmodules-cache-path="))706return std::string(Arg);707}708SmallString<128> Path;709driver::Driver::getDefaultModuleCachePath(Path);710return std::string(Path);711}712713/// Attempts to construct the compilation database from '-compilation-database'714/// or from the arguments following the positional '--'.715static std::unique_ptr<tooling::CompilationDatabase>716getCompilationDatabase(int argc, char **argv, std::string &ErrorMessage) {717ParseArgs(argc, argv);718719if (!(CommandLine.empty() ^ CompilationDB.empty())) {720llvm::errs() << "The compilation command line must be provided either via "721"'-compilation-database' or after '--'.";722return nullptr;723}724725if (!CompilationDB.empty())726return tooling::JSONCompilationDatabase::loadFromFile(727CompilationDB, ErrorMessage,728tooling::JSONCommandLineSyntax::AutoDetect);729730llvm::IntrusiveRefCntPtr<DiagnosticsEngine> Diags =731CompilerInstance::createDiagnostics(new DiagnosticOptions);732driver::Driver TheDriver(CommandLine[0], llvm::sys::getDefaultTargetTriple(),733*Diags);734TheDriver.setCheckInputsExist(false);735std::unique_ptr<driver::Compilation> C(736TheDriver.BuildCompilation(CommandLine));737if (!C || C->getJobs().empty())738return nullptr;739740auto Cmd = C->getJobs().begin();741auto CI = std::make_unique<CompilerInvocation>();742CompilerInvocation::CreateFromArgs(*CI, Cmd->getArguments(), *Diags,743CommandLine[0]);744if (!CI)745return nullptr;746747FrontendOptions &FEOpts = CI->getFrontendOpts();748if (FEOpts.Inputs.size() != 1) {749llvm::errs()750<< "Exactly one input file is required in the per-file mode ('--').\n";751return nullptr;752}753754// There might be multiple jobs for a compilation. Extract the specified755// output filename from the last job.756auto LastCmd = C->getJobs().end();757LastCmd--;758if (LastCmd->getOutputFilenames().size() != 1) {759llvm::errs()760<< "Exactly one output file is required in the per-file mode ('--').\n";761return nullptr;762}763StringRef OutputFile = LastCmd->getOutputFilenames().front();764765class InplaceCompilationDatabase : public tooling::CompilationDatabase {766public:767InplaceCompilationDatabase(StringRef InputFile, StringRef OutputFile,768ArrayRef<const char *> CommandLine)769: Command(".", InputFile, {}, OutputFile) {770for (auto *C : CommandLine)771Command.CommandLine.push_back(C);772}773774std::vector<tooling::CompileCommand>775getCompileCommands(StringRef FilePath) const override {776if (FilePath != Command.Filename)777return {};778return {Command};779}780781std::vector<std::string> getAllFiles() const override {782return {Command.Filename};783}784785std::vector<tooling::CompileCommand>786getAllCompileCommands() const override {787return {Command};788}789790private:791tooling::CompileCommand Command;792};793794return std::make_unique<InplaceCompilationDatabase>(795FEOpts.Inputs[0].getFile(), OutputFile, CommandLine);796}797798int clang_scan_deps_main(int argc, char **argv, const llvm::ToolContext &) {799llvm::InitializeAllTargetInfos();800std::string ErrorMessage;801std::unique_ptr<tooling::CompilationDatabase> Compilations =802getCompilationDatabase(argc, argv, ErrorMessage);803if (!Compilations) {804llvm::errs() << ErrorMessage << "\n";805return 1;806}807808llvm::cl::PrintOptionValues();809810// Expand response files in advance, so that we can "see" all the arguments811// when adjusting below.812Compilations = expandResponseFiles(std::move(Compilations),813llvm::vfs::getRealFileSystem());814815Compilations = inferTargetAndDriverMode(std::move(Compilations));816817Compilations = inferToolLocation(std::move(Compilations));818819// The command options are rewritten to run Clang in preprocessor only mode.820auto AdjustingCompilations =821std::make_unique<tooling::ArgumentsAdjustingCompilations>(822std::move(Compilations));823ResourceDirectoryCache ResourceDirCache;824825AdjustingCompilations->appendArgumentsAdjuster(826[&ResourceDirCache](const tooling::CommandLineArguments &Args,827StringRef FileName) {828std::string LastO;829bool HasResourceDir = false;830bool ClangCLMode = false;831auto FlagsEnd = llvm::find(Args, "--");832if (FlagsEnd != Args.begin()) {833ClangCLMode =834llvm::sys::path::stem(Args[0]).contains_insensitive("clang-cl") ||835llvm::is_contained(Args, "--driver-mode=cl");836837// Reverse scan, starting at the end or at the element before "--".838auto R = std::make_reverse_iterator(FlagsEnd);839auto E = Args.rend();840// Don't include Args[0] in the iteration; that's the executable, not841// an option.842if (E != R)843E--;844for (auto I = R; I != E; ++I) {845StringRef Arg = *I;846if (ClangCLMode) {847// Ignore arguments that are preceded by "-Xclang".848if ((I + 1) != E && I[1] == "-Xclang")849continue;850if (LastO.empty()) {851// With clang-cl, the output obj file can be specified with852// "/opath", "/o path", "/Fopath", and the dash counterparts.853// Also, clang-cl adds ".obj" extension if none is found.854if ((Arg == "-o" || Arg == "/o") && I != R)855LastO = I[-1]; // Next argument (reverse iterator)856else if (Arg.starts_with("/Fo") || Arg.starts_with("-Fo"))857LastO = Arg.drop_front(3).str();858else if (Arg.starts_with("/o") || Arg.starts_with("-o"))859LastO = Arg.drop_front(2).str();860861if (!LastO.empty() && !llvm::sys::path::has_extension(LastO))862LastO.append(".obj");863}864}865if (Arg == "-resource-dir")866HasResourceDir = true;867}868}869tooling::CommandLineArguments AdjustedArgs(Args.begin(), FlagsEnd);870// The clang-cl driver passes "-o -" to the frontend. Inject the real871// file here to ensure "-MT" can be deduced if need be.872if (ClangCLMode && !LastO.empty()) {873AdjustedArgs.push_back("/clang:-o");874AdjustedArgs.push_back("/clang:" + LastO);875}876877if (!HasResourceDir && ResourceDirRecipe == RDRK_InvokeCompiler) {878StringRef ResourceDir =879ResourceDirCache.findResourceDir(Args, ClangCLMode);880if (!ResourceDir.empty()) {881AdjustedArgs.push_back("-resource-dir");882AdjustedArgs.push_back(std::string(ResourceDir));883}884}885AdjustedArgs.insert(AdjustedArgs.end(), FlagsEnd, Args.end());886return AdjustedArgs;887});888889SharedStream Errs(llvm::errs());890891std::optional<llvm::raw_fd_ostream> FileOS;892llvm::raw_ostream &ThreadUnsafeDependencyOS = [&]() -> llvm::raw_ostream & {893if (OutputFileName == "-")894return llvm::outs();895896if (OutputFileName == "/dev/null")897return llvm::nulls();898899std::error_code EC;900FileOS.emplace(OutputFileName, EC);901if (EC) {902llvm::errs() << "Failed to open output file '" << OutputFileName903<< "': " << llvm::errorCodeToError(EC) << '\n';904std::exit(1);905}906return *FileOS;907}();908SharedStream DependencyOS(ThreadUnsafeDependencyOS);909910std::vector<tooling::CompileCommand> Inputs =911AdjustingCompilations->getAllCompileCommands();912913std::atomic<bool> HadErrors(false);914std::optional<FullDeps> FD;915P1689Deps PD;916917std::mutex Lock;918size_t Index = 0;919auto GetNextInputIndex = [&]() -> std::optional<size_t> {920std::unique_lock<std::mutex> LockGuard(Lock);921if (Index < Inputs.size())922return Index++;923return {};924};925926if (Format == ScanningOutputFormat::Full)927FD.emplace(ModuleName.empty() ? Inputs.size() : 0);928929auto ScanningTask = [&](DependencyScanningService &Service) {930DependencyScanningTool WorkerTool(Service);931932llvm::DenseSet<ModuleID> AlreadySeenModules;933while (auto MaybeInputIndex = GetNextInputIndex()) {934size_t LocalIndex = *MaybeInputIndex;935const tooling::CompileCommand *Input = &Inputs[LocalIndex];936std::string Filename = std::move(Input->Filename);937std::string CWD = std::move(Input->Directory);938939std::optional<StringRef> MaybeModuleName;940if (!ModuleName.empty())941MaybeModuleName = ModuleName;942943std::string OutputDir(ModuleFilesDir);944if (OutputDir.empty())945OutputDir = getModuleCachePath(Input->CommandLine);946auto LookupOutput = [&](const ModuleID &MID, ModuleOutputKind MOK) {947return ::lookupModuleOutput(MID, MOK, OutputDir);948};949950// Run the tool on it.951if (Format == ScanningOutputFormat::Make) {952auto MaybeFile = WorkerTool.getDependencyFile(Input->CommandLine, CWD);953if (handleMakeDependencyToolResult(Filename, MaybeFile, DependencyOS,954Errs))955HadErrors = true;956} else if (Format == ScanningOutputFormat::P1689) {957// It is useful to generate the make-format dependency output during958// the scanning for P1689. Otherwise the users need to scan again for959// it. We will generate the make-format dependency output if we find960// `-MF` in the command lines.961std::string MakeformatOutputPath;962std::string MakeformatOutput;963964auto MaybeRule = WorkerTool.getP1689ModuleDependencyFile(965*Input, CWD, MakeformatOutput, MakeformatOutputPath);966967if (handleP1689DependencyToolResult(Filename, MaybeRule, PD, Errs))968HadErrors = true;969970if (!MakeformatOutputPath.empty() && !MakeformatOutput.empty() &&971!HadErrors) {972static std::mutex Lock;973// With compilation database, we may open different files974// concurrently or we may write the same file concurrently. So we975// use a map here to allow multiple compile commands to write to the976// same file. Also we need a lock here to avoid data race.977static llvm::StringMap<llvm::raw_fd_ostream> OSs;978std::unique_lock<std::mutex> LockGuard(Lock);979980auto OSIter = OSs.find(MakeformatOutputPath);981if (OSIter == OSs.end()) {982std::error_code EC;983OSIter =984OSs.try_emplace(MakeformatOutputPath, MakeformatOutputPath, EC)985.first;986if (EC)987llvm::errs() << "Failed to open P1689 make format output file \""988<< MakeformatOutputPath << "\" for " << EC.message()989<< "\n";990}991992SharedStream MakeformatOS(OSIter->second);993llvm::Expected<std::string> MaybeOutput(MakeformatOutput);994if (handleMakeDependencyToolResult(Filename, MaybeOutput,995MakeformatOS, Errs))996HadErrors = true;997}998} else if (MaybeModuleName) {999auto MaybeModuleDepsGraph = WorkerTool.getModuleDependencies(1000*MaybeModuleName, Input->CommandLine, CWD, AlreadySeenModules,1001LookupOutput);1002if (handleModuleResult(*MaybeModuleName, MaybeModuleDepsGraph, *FD,1003LocalIndex, DependencyOS, Errs))1004HadErrors = true;1005} else {1006auto MaybeTUDeps = WorkerTool.getTranslationUnitDependencies(1007Input->CommandLine, CWD, AlreadySeenModules, LookupOutput);1008if (handleTranslationUnitResult(Filename, MaybeTUDeps, *FD, LocalIndex,1009DependencyOS, Errs))1010HadErrors = true;1011}1012}1013};10141015DependencyScanningService Service(ScanMode, Format, OptimizeArgs,1016EagerLoadModules);10171018llvm::Timer T;1019T.startTimer();10201021if (Inputs.size() == 1) {1022ScanningTask(Service);1023} else {1024llvm::DefaultThreadPool Pool(llvm::hardware_concurrency(NumThreads));10251026if (Verbose) {1027llvm::outs() << "Running clang-scan-deps on " << Inputs.size()1028<< " files using " << Pool.getMaxConcurrency()1029<< " workers\n";1030}10311032for (unsigned I = 0; I < Pool.getMaxConcurrency(); ++I)1033Pool.async([ScanningTask, &Service]() { ScanningTask(Service); });10341035Pool.wait();1036}10371038T.stopTimer();1039if (PrintTiming)1040llvm::errs() << llvm::format(1041"clang-scan-deps timing: %0.2fs wall, %0.2fs process\n",1042T.getTotalTime().getWallTime(), T.getTotalTime().getProcessTime());10431044if (RoundTripArgs)1045if (FD && FD->roundTripCommands(llvm::errs()))1046HadErrors = true;10471048if (Format == ScanningOutputFormat::Full)1049FD->printFullOutput(ThreadUnsafeDependencyOS);1050else if (Format == ScanningOutputFormat::P1689)1051PD.printDependencies(ThreadUnsafeDependencyOS);10521053return HadErrors;1054}105510561057