ClangScanDeps.cpp 9.3 KB

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