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/Frontend/CompilerInstance.h" 10 #include "clang/Tooling/CommonOptionsParser.h" 11 #include "clang/Tooling/DependencyScanning/DependencyScanningService.h" 12 #include "clang/Tooling/DependencyScanning/DependencyScanningTool.h" 13 #include "clang/Tooling/DependencyScanning/DependencyScanningWorker.h" 14 #include "clang/Tooling/JSONCompilationDatabase.h" 15 #include "llvm/Support/InitLLVM.h" 16 #include "llvm/Support/Options.h" 17 #include "llvm/Support/Program.h" 18 #include "llvm/Support/Signals.h" 19 #include "llvm/Support/Threading.h" 20 #include <mutex> 21 #include <thread> 22 23 using namespace clang; 24 using namespace tooling::dependencies; 25 26 namespace { 27 28 class SharedStream { 29 public: 30 SharedStream(raw_ostream &OS) : OS(OS) {} 31 void applyLocked(llvm::function_ref<void(raw_ostream &OS)> Fn) { 32 std::unique_lock<std::mutex> LockGuard(Lock); 33 Fn(OS); 34 OS.flush(); 35 } 36 37 private: 38 std::mutex Lock; 39 raw_ostream &OS; 40 }; 41 42 llvm::cl::opt<bool> Help("h", llvm::cl::desc("Alias for -help"), 43 llvm::cl::Hidden); 44 45 llvm::cl::OptionCategory DependencyScannerCategory("Tool options"); 46 47 static llvm::cl::opt<ScanningMode> ScanMode( 48 "mode", 49 llvm::cl::desc("The preprocessing mode used to compute the dependencies"), 50 llvm::cl::values( 51 clEnumValN(ScanningMode::MinimizedSourcePreprocessing, 52 "preprocess-minimized-sources", 53 "The set of dependencies is computed by preprocessing the " 54 "source files that were minimized to only include the " 55 "contents that might affect the dependencies"), 56 clEnumValN(ScanningMode::CanonicalPreprocessing, "preprocess", 57 "The set of dependencies is computed by preprocessing the " 58 "unmodified source files")), 59 llvm::cl::init(ScanningMode::MinimizedSourcePreprocessing), 60 llvm::cl::cat(DependencyScannerCategory)); 61 62 llvm::cl::opt<unsigned> 63 NumThreads("j", llvm::cl::Optional, 64 llvm::cl::desc("Number of worker threads to use (default: use " 65 "all concurrent threads)"), 66 llvm::cl::init(0), llvm::cl::cat(DependencyScannerCategory)); 67 68 llvm::cl::opt<std::string> 69 CompilationDB("compilation-database", 70 llvm::cl::desc("Compilation database"), llvm::cl::Required, 71 llvm::cl::cat(DependencyScannerCategory)); 72 73 llvm::cl::opt<bool> ReuseFileManager( 74 "reuse-filemanager", 75 llvm::cl::desc("Reuse the file manager and its cache between invocations."), 76 llvm::cl::init(true), llvm::cl::cat(DependencyScannerCategory)); 77 78 llvm::cl::opt<bool> SkipExcludedPPRanges( 79 "skip-excluded-pp-ranges", 80 llvm::cl::desc( 81 "Use the preprocessor optimization that skips excluded conditionals by " 82 "bumping the buffer pointer in the lexer instead of lexing the tokens " 83 "until reaching the end directive."), 84 llvm::cl::init(true), llvm::cl::cat(DependencyScannerCategory)); 85 86 llvm::cl::opt<bool> Verbose("v", llvm::cl::Optional, 87 llvm::cl::desc("Use verbose output."), 88 llvm::cl::init(false), 89 llvm::cl::cat(DependencyScannerCategory)); 90 91 } // end anonymous namespace 92 93 /// \returns object-file path derived from source-file path. 94 static std::string getObjFilePath(StringRef SrcFile) { 95 SmallString<128> ObjFileName(SrcFile); 96 llvm::sys::path::replace_extension(ObjFileName, "o"); 97 return ObjFileName.str(); 98 } 99 100 /// Takes the result of a dependency scan and prints error / dependency files 101 /// based on the result. 102 /// 103 /// \returns True on error. 104 static bool handleDependencyToolResult(const std::string &Input, 105 llvm::Expected<std::string> &MaybeFile, 106 SharedStream &OS, SharedStream &Errs) { 107 if (!MaybeFile) { 108 llvm::handleAllErrors( 109 MaybeFile.takeError(), [&Input, &Errs](llvm::StringError &Err) { 110 Errs.applyLocked([&](raw_ostream &OS) { 111 OS << "Error while scanning dependencies for " << Input << ":\n"; 112 OS << Err.getMessage(); 113 }); 114 }); 115 return true; 116 } 117 OS.applyLocked([&](raw_ostream &OS) { OS << *MaybeFile; }); 118 return false; 119 } 120 121 int main(int argc, const char **argv) { 122 llvm::InitLLVM X(argc, argv); 123 llvm::cl::HideUnrelatedOptions(DependencyScannerCategory); 124 if (!llvm::cl::ParseCommandLineOptions(argc, argv)) 125 return 1; 126 127 std::string ErrorMessage; 128 std::unique_ptr<tooling::JSONCompilationDatabase> Compilations = 129 tooling::JSONCompilationDatabase::loadFromFile( 130 CompilationDB, ErrorMessage, 131 tooling::JSONCommandLineSyntax::AutoDetect); 132 if (!Compilations) { 133 llvm::errs() << "error: " << ErrorMessage << "\n"; 134 return 1; 135 } 136 137 llvm::cl::PrintOptionValues(); 138 139 // By default the tool runs on all inputs in the CDB. 140 std::vector<std::pair<std::string, std::string>> Inputs; 141 for (const auto &Command : Compilations->getAllCompileCommands()) 142 Inputs.emplace_back(Command.Filename, Command.Directory); 143 144 // The command options are rewritten to run Clang in preprocessor only mode. 145 auto AdjustingCompilations = 146 std::make_unique<tooling::ArgumentsAdjustingCompilations>( 147 std::move(Compilations)); 148 AdjustingCompilations->appendArgumentsAdjuster( 149 [](const tooling::CommandLineArguments &Args, StringRef FileName) { 150 std::string LastO = ""; 151 bool HasMT = false; 152 bool HasMQ = false; 153 bool HasMD = false; 154 // We need to find the last -o value. 155 if (!Args.empty()) { 156 std::size_t Idx = Args.size() - 1; 157 for (auto It = Args.rbegin(); It != Args.rend(); ++It) { 158 if (It != Args.rbegin()) { 159 if (Args[Idx] == "-o") 160 LastO = Args[Idx + 1]; 161 if (Args[Idx] == "-MT") 162 HasMT = true; 163 if (Args[Idx] == "-MQ") 164 HasMQ = true; 165 if (Args[Idx] == "-MD") 166 HasMD = true; 167 } 168 --Idx; 169 } 170 } 171 // If there's no -MT/-MQ Driver would add -MT with the value of the last 172 // -o option. 173 tooling::CommandLineArguments AdjustedArgs = Args; 174 AdjustedArgs.push_back("-o"); 175 AdjustedArgs.push_back("/dev/null"); 176 if (!HasMT && !HasMQ) { 177 AdjustedArgs.push_back("-M"); 178 AdjustedArgs.push_back("-MT"); 179 // We're interested in source dependencies of an object file. 180 if (!HasMD) { 181 // FIXME: We are missing the directory unless the -o value is an 182 // absolute path. 183 AdjustedArgs.push_back(!LastO.empty() ? LastO 184 : getObjFilePath(FileName)); 185 } else { 186 AdjustedArgs.push_back(FileName); 187 } 188 } 189 AdjustedArgs.push_back("-Xclang"); 190 AdjustedArgs.push_back("-Eonly"); 191 AdjustedArgs.push_back("-Xclang"); 192 AdjustedArgs.push_back("-sys-header-deps"); 193 AdjustedArgs.push_back("-Wno-error"); 194 return AdjustedArgs; 195 }); 196 AdjustingCompilations->appendArgumentsAdjuster( 197 tooling::getClangStripSerializeDiagnosticAdjuster()); 198 199 SharedStream Errs(llvm::errs()); 200 // Print out the dependency results to STDOUT by default. 201 SharedStream DependencyOS(llvm::outs()); 202 203 DependencyScanningService Service(ScanMode, ReuseFileManager, 204 SkipExcludedPPRanges); 205 #if LLVM_ENABLE_THREADS 206 unsigned NumWorkers = 207 NumThreads == 0 ? llvm::hardware_concurrency() : NumThreads; 208 #else 209 unsigned NumWorkers = 1; 210 #endif 211 std::vector<std::unique_ptr<DependencyScanningTool>> WorkerTools; 212 for (unsigned I = 0; I < NumWorkers; ++I) 213 WorkerTools.push_back(std::make_unique<DependencyScanningTool>( 214 Service, *AdjustingCompilations)); 215 216 std::vector<std::thread> WorkerThreads; 217 std::atomic<bool> HadErrors(false); 218 std::mutex Lock; 219 size_t Index = 0; 220 221 if (Verbose) { 222 llvm::outs() << "Running clang-scan-deps on " << Inputs.size() 223 << " files using " << NumWorkers << " workers\n"; 224 } 225 for (unsigned I = 0; I < NumWorkers; ++I) { 226 auto Worker = [I, &Lock, &Index, &Inputs, &HadErrors, &WorkerTools, 227 &DependencyOS, &Errs]() { 228 while (true) { 229 std::string Input; 230 StringRef CWD; 231 // Take the next input. 232 { 233 std::unique_lock<std::mutex> LockGuard(Lock); 234 if (Index >= Inputs.size()) 235 return; 236 const auto &Compilation = Inputs[Index++]; 237 Input = Compilation.first; 238 CWD = Compilation.second; 239 } 240 // Run the tool on it. 241 auto MaybeFile = WorkerTools[I]->getDependencyFile(Input, CWD); 242 if (handleDependencyToolResult(Input, MaybeFile, DependencyOS, Errs)) 243 HadErrors = true; 244 } 245 }; 246 #if LLVM_ENABLE_THREADS 247 WorkerThreads.emplace_back(std::move(Worker)); 248 #else 249 // Run the worker without spawning a thread when threads are disabled. 250 Worker(); 251 #endif 252 } 253 for (auto &W : WorkerThreads) 254 W.join(); 255 256 return HadErrors; 257 } 258