//===- ClangScanDeps.cpp - Implementation of clang-scan-deps --------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "clang/Frontend/CompilerInstance.h" #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/DependencyScanning/DependencyScanningService.h" #include "clang/Tooling/DependencyScanning/DependencyScanningTool.h" #include "clang/Tooling/DependencyScanning/DependencyScanningWorker.h" #include "clang/Tooling/JSONCompilationDatabase.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/Options.h" #include "llvm/Support/Program.h" #include "llvm/Support/Signals.h" #include "llvm/Support/Threading.h" #include #include using namespace clang; using namespace tooling::dependencies; namespace { class SharedStream { public: SharedStream(raw_ostream &OS) : OS(OS) {} void applyLocked(llvm::function_ref Fn) { std::unique_lock LockGuard(Lock); Fn(OS); OS.flush(); } private: std::mutex Lock; raw_ostream &OS; }; llvm::cl::opt Help("h", llvm::cl::desc("Alias for -help"), llvm::cl::Hidden); llvm::cl::OptionCategory DependencyScannerCategory("Tool options"); static llvm::cl::opt ScanMode( "mode", llvm::cl::desc("The preprocessing mode used to compute the dependencies"), llvm::cl::values( clEnumValN(ScanningMode::MinimizedSourcePreprocessing, "preprocess-minimized-sources", "The set of dependencies is computed by preprocessing the " "source files that were minimized to only include the " "contents that might affect the dependencies"), clEnumValN(ScanningMode::CanonicalPreprocessing, "preprocess", "The set of dependencies is computed by preprocessing the " "unmodified source files")), llvm::cl::init(ScanningMode::MinimizedSourcePreprocessing), llvm::cl::cat(DependencyScannerCategory)); llvm::cl::opt NumThreads("j", llvm::cl::Optional, llvm::cl::desc("Number of worker threads to use (default: use " "all concurrent threads)"), llvm::cl::init(0), llvm::cl::cat(DependencyScannerCategory)); llvm::cl::opt CompilationDB("compilation-database", llvm::cl::desc("Compilation database"), llvm::cl::Required, llvm::cl::cat(DependencyScannerCategory)); llvm::cl::opt ReuseFileManager( "reuse-filemanager", llvm::cl::desc("Reuse the file manager and its cache between invocations."), llvm::cl::init(true), llvm::cl::cat(DependencyScannerCategory)); llvm::cl::opt SkipExcludedPPRanges( "skip-excluded-pp-ranges", llvm::cl::desc( "Use the preprocessor optimization that skips excluded conditionals by " "bumping the buffer pointer in the lexer instead of lexing the tokens " "until reaching the end directive."), llvm::cl::init(true), llvm::cl::cat(DependencyScannerCategory)); llvm::cl::opt Verbose("v", llvm::cl::Optional, llvm::cl::desc("Use verbose output."), llvm::cl::init(false), llvm::cl::cat(DependencyScannerCategory)); } // end anonymous namespace /// \returns object-file path derived from source-file path. static std::string getObjFilePath(StringRef SrcFile) { SmallString<128> ObjFileName(SrcFile); llvm::sys::path::replace_extension(ObjFileName, "o"); return ObjFileName.str(); } /// Takes the result of a dependency scan and prints error / dependency files /// based on the result. /// /// \returns True on error. static bool handleDependencyToolResult(const std::string &Input, llvm::Expected &MaybeFile, SharedStream &OS, SharedStream &Errs) { if (!MaybeFile) { llvm::handleAllErrors( MaybeFile.takeError(), [&Input, &Errs](llvm::StringError &Err) { Errs.applyLocked([&](raw_ostream &OS) { OS << "Error while scanning dependencies for " << Input << ":\n"; OS << Err.getMessage(); }); }); return true; } OS.applyLocked([&](raw_ostream &OS) { OS << *MaybeFile; }); return false; } int main(int argc, const char **argv) { llvm::InitLLVM X(argc, argv); llvm::cl::HideUnrelatedOptions(DependencyScannerCategory); if (!llvm::cl::ParseCommandLineOptions(argc, argv)) return 1; std::string ErrorMessage; std::unique_ptr Compilations = tooling::JSONCompilationDatabase::loadFromFile( CompilationDB, ErrorMessage, tooling::JSONCommandLineSyntax::AutoDetect); if (!Compilations) { llvm::errs() << "error: " << ErrorMessage << "\n"; return 1; } llvm::cl::PrintOptionValues(); // By default the tool runs on all inputs in the CDB. std::vector> Inputs; for (const auto &Command : Compilations->getAllCompileCommands()) Inputs.emplace_back(Command.Filename, Command.Directory); // The command options are rewritten to run Clang in preprocessor only mode. auto AdjustingCompilations = std::make_unique( std::move(Compilations)); AdjustingCompilations->appendArgumentsAdjuster( [](const tooling::CommandLineArguments &Args, StringRef FileName) { std::string LastO = ""; bool HasMT = false; bool HasMQ = false; bool HasMD = false; // We need to find the last -o value. if (!Args.empty()) { std::size_t Idx = Args.size() - 1; for (auto It = Args.rbegin(); It != Args.rend(); ++It) { if (It != Args.rbegin()) { if (Args[Idx] == "-o") LastO = Args[Idx + 1]; if (Args[Idx] == "-MT") HasMT = true; if (Args[Idx] == "-MQ") HasMQ = true; if (Args[Idx] == "-MD") HasMD = true; } --Idx; } } // If there's no -MT/-MQ Driver would add -MT with the value of the last // -o option. tooling::CommandLineArguments AdjustedArgs = Args; AdjustedArgs.push_back("-o"); AdjustedArgs.push_back("/dev/null"); if (!HasMT && !HasMQ) { AdjustedArgs.push_back("-M"); AdjustedArgs.push_back("-MT"); // We're interested in source dependencies of an object file. if (!HasMD) { // FIXME: We are missing the directory unless the -o value is an // absolute path. AdjustedArgs.push_back(!LastO.empty() ? LastO : getObjFilePath(FileName)); } else { AdjustedArgs.push_back(FileName); } } AdjustedArgs.push_back("-Xclang"); AdjustedArgs.push_back("-Eonly"); AdjustedArgs.push_back("-Xclang"); AdjustedArgs.push_back("-sys-header-deps"); AdjustedArgs.push_back("-Wno-error"); return AdjustedArgs; }); AdjustingCompilations->appendArgumentsAdjuster( tooling::getClangStripSerializeDiagnosticAdjuster()); SharedStream Errs(llvm::errs()); // Print out the dependency results to STDOUT by default. SharedStream DependencyOS(llvm::outs()); DependencyScanningService Service(ScanMode, ReuseFileManager, SkipExcludedPPRanges); #if LLVM_ENABLE_THREADS unsigned NumWorkers = NumThreads == 0 ? llvm::hardware_concurrency() : NumThreads; #else unsigned NumWorkers = 1; #endif std::vector> WorkerTools; for (unsigned I = 0; I < NumWorkers; ++I) WorkerTools.push_back(std::make_unique( Service, *AdjustingCompilations)); std::vector WorkerThreads; std::atomic HadErrors(false); std::mutex Lock; size_t Index = 0; if (Verbose) { llvm::outs() << "Running clang-scan-deps on " << Inputs.size() << " files using " << NumWorkers << " workers\n"; } for (unsigned I = 0; I < NumWorkers; ++I) { auto Worker = [I, &Lock, &Index, &Inputs, &HadErrors, &WorkerTools, &DependencyOS, &Errs]() { while (true) { std::string Input; StringRef CWD; // Take the next input. { std::unique_lock LockGuard(Lock); if (Index >= Inputs.size()) return; const auto &Compilation = Inputs[Index++]; Input = Compilation.first; CWD = Compilation.second; } // Run the tool on it. auto MaybeFile = WorkerTools[I]->getDependencyFile(Input, CWD); if (handleDependencyToolResult(Input, MaybeFile, DependencyOS, Errs)) HadErrors = true; } }; #if LLVM_ENABLE_THREADS WorkerThreads.emplace_back(std::move(Worker)); #else // Run the worker without spawning a thread when threads are disabled. Worker(); #endif } for (auto &W : WorkerThreads) W.join(); return HadErrors; }