xref: /netbsd-src/external/apache2/llvm/dist/clang/tools/clang-scan-deps/ClangScanDeps.cpp (revision 404ee5b9334f618040b6cdef96a0ff35a6fc4636)
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