/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/tools/clang-scan-deps/ClangScanDeps.cpp
Line | Count | Source (jump to first uncovered line) |
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/ADT/STLExtras.h" |
16 | | #include "llvm/ADT/Twine.h" |
17 | | #include "llvm/Support/CommandLine.h" |
18 | | #include "llvm/Support/FileUtilities.h" |
19 | | #include "llvm/Support/InitLLVM.h" |
20 | | #include "llvm/Support/JSON.h" |
21 | | #include "llvm/Support/Program.h" |
22 | | #include "llvm/Support/Signals.h" |
23 | | #include "llvm/Support/ThreadPool.h" |
24 | | #include "llvm/Support/Threading.h" |
25 | | #include <mutex> |
26 | | #include <thread> |
27 | | |
28 | | using namespace clang; |
29 | | using namespace tooling::dependencies; |
30 | | |
31 | | namespace { |
32 | | |
33 | | class SharedStream { |
34 | | public: |
35 | 176 | SharedStream(raw_ostream &OS) : OS(OS) {} |
36 | 137 | void applyLocked(llvm::function_ref<void(raw_ostream &OS)> Fn) { |
37 | 137 | std::unique_lock<std::mutex> LockGuard(Lock); |
38 | 137 | Fn(OS); |
39 | 137 | OS.flush(); |
40 | 137 | } |
41 | | |
42 | | private: |
43 | | std::mutex Lock; |
44 | | raw_ostream &OS; |
45 | | }; |
46 | | |
47 | | class ResourceDirectoryCache { |
48 | | public: |
49 | | /// findResourceDir finds the resource directory relative to the clang |
50 | | /// compiler being used in Args, by running it with "-print-resource-dir" |
51 | | /// option and cache the results for reuse. \returns resource directory path |
52 | | /// associated with the given invocation command or empty string if the |
53 | | /// compiler path is NOT an absolute path. |
54 | | StringRef findResourceDir(const tooling::CommandLineArguments &Args, |
55 | 1 | bool ClangCLMode) { |
56 | 1 | if (Args.size() < 1) |
57 | 0 | return ""; |
58 | | |
59 | 1 | const std::string &ClangBinaryPath = Args[0]; |
60 | 1 | if (!llvm::sys::path::is_absolute(ClangBinaryPath)) |
61 | 0 | return ""; |
62 | | |
63 | 1 | const std::string &ClangBinaryName = |
64 | 1 | std::string(llvm::sys::path::filename(ClangBinaryPath)); |
65 | | |
66 | 1 | std::unique_lock<std::mutex> LockGuard(CacheLock); |
67 | 1 | const auto &CachedResourceDir = Cache.find(ClangBinaryPath); |
68 | 1 | if (CachedResourceDir != Cache.end()) |
69 | 0 | return CachedResourceDir->second; |
70 | | |
71 | 1 | std::vector<StringRef> PrintResourceDirArgs{ClangBinaryName}; |
72 | 1 | if (ClangCLMode) |
73 | 0 | PrintResourceDirArgs.push_back("/clang:-print-resource-dir"); |
74 | 1 | else |
75 | 1 | PrintResourceDirArgs.push_back("-print-resource-dir"); |
76 | | |
77 | 1 | llvm::SmallString<64> OutputFile, ErrorFile; |
78 | 1 | llvm::sys::fs::createTemporaryFile("print-resource-dir-output", |
79 | 1 | "" /*no-suffix*/, OutputFile); |
80 | 1 | llvm::sys::fs::createTemporaryFile("print-resource-dir-error", |
81 | 1 | "" /*no-suffix*/, ErrorFile); |
82 | 1 | llvm::FileRemover OutputRemover(OutputFile.c_str()); |
83 | 1 | llvm::FileRemover ErrorRemover(ErrorFile.c_str()); |
84 | 1 | llvm::Optional<StringRef> Redirects[] = { |
85 | 1 | {""}, // Stdin |
86 | 1 | OutputFile.str(), |
87 | 1 | ErrorFile.str(), |
88 | 1 | }; |
89 | 1 | if (const int RC = llvm::sys::ExecuteAndWait( |
90 | 1 | ClangBinaryPath, PrintResourceDirArgs, {}, Redirects)) { |
91 | 0 | auto ErrorBuf = llvm::MemoryBuffer::getFile(ErrorFile.c_str()); |
92 | 0 | llvm::errs() << ErrorBuf.get()->getBuffer(); |
93 | 0 | return ""; |
94 | 0 | } |
95 | | |
96 | 1 | auto OutputBuf = llvm::MemoryBuffer::getFile(OutputFile.c_str()); |
97 | 1 | if (!OutputBuf) |
98 | 0 | return ""; |
99 | 1 | StringRef Output = OutputBuf.get()->getBuffer().rtrim('\n'); |
100 | | |
101 | 1 | Cache[ClangBinaryPath] = Output.str(); |
102 | 1 | return Cache[ClangBinaryPath]; |
103 | 1 | } |
104 | | |
105 | | private: |
106 | | std::map<std::string, std::string> Cache; |
107 | | std::mutex CacheLock; |
108 | | }; |
109 | | |
110 | | llvm::cl::opt<bool> Help("h", llvm::cl::desc("Alias for -help"), |
111 | | llvm::cl::Hidden); |
112 | | |
113 | | llvm::cl::OptionCategory DependencyScannerCategory("Tool options"); |
114 | | |
115 | | static llvm::cl::opt<ScanningMode> ScanMode( |
116 | | "mode", |
117 | | llvm::cl::desc("The preprocessing mode used to compute the dependencies"), |
118 | | llvm::cl::values( |
119 | | clEnumValN(ScanningMode::DependencyDirectivesScan, |
120 | | "preprocess-dependency-directives", |
121 | | "The set of dependencies is computed by preprocessing with " |
122 | | "special lexing after scanning the source files to get the " |
123 | | "directives that might affect the dependencies"), |
124 | | clEnumValN(ScanningMode::CanonicalPreprocessing, "preprocess", |
125 | | "The set of dependencies is computed by preprocessing the " |
126 | | "source files")), |
127 | | llvm::cl::init(ScanningMode::DependencyDirectivesScan), |
128 | | llvm::cl::cat(DependencyScannerCategory)); |
129 | | |
130 | | static llvm::cl::opt<ScanningOutputFormat> Format( |
131 | | "format", llvm::cl::desc("The output format for the dependencies"), |
132 | | llvm::cl::values(clEnumValN(ScanningOutputFormat::Make, "make", |
133 | | "Makefile compatible dep file"), |
134 | | clEnumValN(ScanningOutputFormat::Full, "experimental-full", |
135 | | "Full dependency graph suitable" |
136 | | " for explicitly building modules. This format " |
137 | | "is experimental and will change.")), |
138 | | llvm::cl::init(ScanningOutputFormat::Make), |
139 | | llvm::cl::cat(DependencyScannerCategory)); |
140 | | |
141 | | // This mode is mostly useful for development of explicitly built modules. |
142 | | // Command lines will contain arguments specifying modulemap file paths and |
143 | | // absolute paths to PCM files in the module cache directory. |
144 | | // |
145 | | // Build tools that want to put the PCM files in a different location should use |
146 | | // the C++ APIs instead, of which there are two flavors: |
147 | | // |
148 | | // 1. APIs that generate arguments with paths PCM files via a callback provided |
149 | | // by the client: |
150 | | // * ModuleDeps::getCanonicalCommandLine(LookupPCMPath) |
151 | | // * FullDependencies::getCommandLine(LookupPCMPath) |
152 | | // |
153 | | // 2. APIs that don't generate arguments with paths PCM files and instead expect |
154 | | // the client to append them manually after the fact: |
155 | | // * ModuleDeps::getCanonicalCommandLineWithoutModulePaths() |
156 | | // * FullDependencies::getCommandLineWithoutModulePaths() |
157 | | // |
158 | | static llvm::cl::opt<bool> GenerateModulesPathArgs( |
159 | | "generate-modules-path-args", |
160 | | llvm::cl::desc( |
161 | | "With '-format experimental-full', include arguments specifying " |
162 | | "modules-related paths in the generated command lines: " |
163 | | "'-fmodule-file=', '-o', '-fmodule-map-file='."), |
164 | | llvm::cl::init(false), llvm::cl::cat(DependencyScannerCategory)); |
165 | | |
166 | | static llvm::cl::opt<std::string> ModuleFilesDir( |
167 | | "module-files-dir", |
168 | | llvm::cl::desc("With '-generate-modules-path-args', paths to module files " |
169 | | "in the generated command lines will begin with the " |
170 | | "specified directory instead the module cache directory."), |
171 | | llvm::cl::cat(DependencyScannerCategory)); |
172 | | |
173 | | static llvm::cl::opt<bool> OptimizeArgs( |
174 | | "optimize-args", |
175 | | llvm::cl::desc("Whether to optimize command-line arguments of modules."), |
176 | | llvm::cl::init(false), llvm::cl::cat(DependencyScannerCategory)); |
177 | | |
178 | | llvm::cl::opt<unsigned> |
179 | | NumThreads("j", llvm::cl::Optional, |
180 | | llvm::cl::desc("Number of worker threads to use (default: use " |
181 | | "all concurrent threads)"), |
182 | | llvm::cl::init(0), llvm::cl::cat(DependencyScannerCategory)); |
183 | | |
184 | | llvm::cl::opt<std::string> |
185 | | CompilationDB("compilation-database", |
186 | | llvm::cl::desc("Compilation database"), llvm::cl::Required, |
187 | | llvm::cl::cat(DependencyScannerCategory)); |
188 | | |
189 | | llvm::cl::opt<bool> ReuseFileManager( |
190 | | "reuse-filemanager", |
191 | | llvm::cl::desc("Reuse the file manager and its cache between invocations."), |
192 | | llvm::cl::init(true), llvm::cl::cat(DependencyScannerCategory)); |
193 | | |
194 | | llvm::cl::opt<std::string> ModuleName( |
195 | | "module-name", llvm::cl::Optional, |
196 | | llvm::cl::desc("the module of which the dependencies are to be computed"), |
197 | | llvm::cl::cat(DependencyScannerCategory)); |
198 | | |
199 | | llvm::cl::list<std::string> ModuleDepTargets( |
200 | | "dependency-target", |
201 | | llvm::cl::desc("With '-generate-modules-path-args', the names of " |
202 | | "dependency targets for the dependency file"), |
203 | | llvm::cl::cat(DependencyScannerCategory)); |
204 | | |
205 | | enum ResourceDirRecipeKind { |
206 | | RDRK_ModifyCompilerPath, |
207 | | RDRK_InvokeCompiler, |
208 | | }; |
209 | | |
210 | | static llvm::cl::opt<ResourceDirRecipeKind> ResourceDirRecipe( |
211 | | "resource-dir-recipe", |
212 | | llvm::cl::desc("How to produce missing '-resource-dir' argument"), |
213 | | llvm::cl::values( |
214 | | clEnumValN(RDRK_ModifyCompilerPath, "modify-compiler-path", |
215 | | "Construct the resource directory from the compiler path in " |
216 | | "the compilation database. This assumes it's part of the " |
217 | | "same toolchain as this clang-scan-deps. (default)"), |
218 | | clEnumValN(RDRK_InvokeCompiler, "invoke-compiler", |
219 | | "Invoke the compiler with '-print-resource-dir' and use the " |
220 | | "reported path as the resource directory. (deprecated)")), |
221 | | llvm::cl::init(RDRK_ModifyCompilerPath), |
222 | | llvm::cl::cat(DependencyScannerCategory)); |
223 | | |
224 | | llvm::cl::opt<bool> Verbose("v", llvm::cl::Optional, |
225 | | llvm::cl::desc("Use verbose output."), |
226 | | llvm::cl::init(false), |
227 | | llvm::cl::cat(DependencyScannerCategory)); |
228 | | |
229 | | } // end anonymous namespace |
230 | | |
231 | | /// Takes the result of a dependency scan and prints error / dependency files |
232 | | /// based on the result. |
233 | | /// |
234 | | /// \returns True on error. |
235 | | static bool |
236 | | handleMakeDependencyToolResult(const std::string &Input, |
237 | | llvm::Expected<std::string> &MaybeFile, |
238 | 137 | SharedStream &OS, SharedStream &Errs) { |
239 | 137 | if (!MaybeFile) { |
240 | 7 | llvm::handleAllErrors( |
241 | 7 | MaybeFile.takeError(), [&Input, &Errs](llvm::StringError &Err) { |
242 | 7 | Errs.applyLocked([&](raw_ostream &OS) { |
243 | 7 | OS << "Error while scanning dependencies for " << Input << ":\n"; |
244 | 7 | OS << Err.getMessage(); |
245 | 7 | }); |
246 | 7 | }); |
247 | 7 | return true; |
248 | 7 | } |
249 | 130 | OS.applyLocked([&](raw_ostream &OS) { OS << *MaybeFile; }); |
250 | 130 | return false; |
251 | 137 | } |
252 | | |
253 | 77 | static llvm::json::Array toJSONSorted(const llvm::StringSet<> &Set) { |
254 | 77 | std::vector<llvm::StringRef> Strings; |
255 | 77 | for (auto &&I : Set) |
256 | 178 | Strings.push_back(I.getKey()); |
257 | 77 | llvm::sort(Strings); |
258 | 77 | return llvm::json::Array(Strings); |
259 | 77 | } |
260 | | |
261 | 131 | static llvm::json::Array toJSONSorted(std::vector<ModuleID> V) { |
262 | 131 | llvm::sort(V, [](const ModuleID &A, const ModuleID &B) { |
263 | 39 | return std::tie(A.ModuleName, A.ContextHash) < |
264 | 39 | std::tie(B.ModuleName, B.ContextHash); |
265 | 39 | }); |
266 | | |
267 | 131 | llvm::json::Array Ret; |
268 | 131 | for (const ModuleID &MID : V) |
269 | 85 | Ret.push_back(llvm::json::Object( |
270 | 85 | {{"module-name", MID.ModuleName}, {"context-hash", MID.ContextHash}})); |
271 | 131 | return Ret; |
272 | 131 | } |
273 | | |
274 | | // Thread safe. |
275 | | class FullDeps { |
276 | | public: |
277 | | void mergeDeps(StringRef Input, FullDependenciesResult FDR, |
278 | 54 | size_t InputIndex) { |
279 | 54 | const FullDependencies &FD = FDR.FullDeps; |
280 | | |
281 | 54 | InputDeps ID; |
282 | 54 | ID.FileName = std::string(Input); |
283 | 54 | ID.ContextHash = std::move(FD.ID.ContextHash); |
284 | 54 | ID.FileDeps = std::move(FD.FileDeps); |
285 | 54 | ID.ModuleDeps = std::move(FD.ClangModuleDeps); |
286 | | |
287 | 54 | std::unique_lock<std::mutex> ul(Lock); |
288 | 85 | for (const ModuleDeps &MD : FDR.DiscoveredModules) { |
289 | 85 | auto I = Modules.find({MD.ID, 0}); |
290 | 85 | if (I != Modules.end()) { |
291 | 8 | I->first.InputIndex = std::min(I->first.InputIndex, InputIndex); |
292 | 8 | continue; |
293 | 8 | } |
294 | 77 | Modules.insert(I, {{MD.ID, InputIndex}, std::move(MD)}); |
295 | 77 | } |
296 | | |
297 | 54 | ID.CommandLine = |
298 | 54 | GenerateModulesPathArgs |
299 | 54 | ? FD.getCommandLine([&](const ModuleID &MID, ModuleOutputKind MOK) 31 { |
300 | 31 | return lookupModuleOutput(MID, MOK); |
301 | 31 | }) |
302 | 54 | : FD.getCommandLineWithoutModulePaths()23 ; |
303 | 54 | Inputs.push_back(std::move(ID)); |
304 | 54 | } |
305 | | |
306 | 40 | void printFullOutput(raw_ostream &OS) { |
307 | | // Sort the modules by name to get a deterministic order. |
308 | 40 | std::vector<IndexedModuleID> ModuleIDs; |
309 | 40 | for (auto &&M : Modules) |
310 | 77 | ModuleIDs.push_back(M.first); |
311 | 40 | llvm::sort(ModuleIDs, |
312 | 98 | [](const IndexedModuleID &A, const IndexedModuleID &B) { |
313 | 98 | return std::tie(A.ID.ModuleName, A.InputIndex) < |
314 | 98 | std::tie(B.ID.ModuleName, B.InputIndex); |
315 | 98 | }); |
316 | | |
317 | 40 | llvm::sort(Inputs, [](const InputDeps &A, const InputDeps &B) { |
318 | 18 | return A.FileName < B.FileName; |
319 | 18 | }); |
320 | | |
321 | 40 | using namespace llvm::json; |
322 | | |
323 | 40 | Array OutModules; |
324 | 77 | for (auto &&ModID : ModuleIDs) { |
325 | 77 | auto &MD = Modules[ModID]; |
326 | 77 | Object O{ |
327 | 77 | {"name", MD.ID.ModuleName}, |
328 | 77 | {"context-hash", MD.ID.ContextHash}, |
329 | 77 | {"file-deps", toJSONSorted(MD.FileDeps)}, |
330 | 77 | {"clang-module-deps", toJSONSorted(MD.ClangModuleDeps)}, |
331 | 77 | {"clang-modulemap-file", MD.ClangModuleMapFile}, |
332 | 77 | {"command-line", |
333 | 77 | GenerateModulesPathArgs |
334 | 77 | ? MD.getCanonicalCommandLine( |
335 | 94 | [&](const ModuleID &MID, ModuleOutputKind MOK) { |
336 | 94 | return lookupModuleOutput(MID, MOK); |
337 | 94 | }) |
338 | 77 | : MD.getCanonicalCommandLineWithoutModulePaths()26 }, |
339 | 77 | }; |
340 | 77 | OutModules.push_back(std::move(O)); |
341 | 77 | } |
342 | | |
343 | 40 | Array TUs; |
344 | 54 | for (auto &&I : Inputs) { |
345 | 54 | Object O{ |
346 | 54 | {"input-file", I.FileName}, |
347 | 54 | {"clang-context-hash", I.ContextHash}, |
348 | 54 | {"file-deps", I.FileDeps}, |
349 | 54 | {"clang-module-deps", toJSONSorted(I.ModuleDeps)}, |
350 | 54 | {"command-line", I.CommandLine}, |
351 | 54 | }; |
352 | 54 | TUs.push_back(std::move(O)); |
353 | 54 | } |
354 | | |
355 | 40 | Object Output{ |
356 | 40 | {"modules", std::move(OutModules)}, |
357 | 40 | {"translation-units", std::move(TUs)}, |
358 | 40 | }; |
359 | | |
360 | 40 | OS << llvm::formatv("{0:2}\n", Value(std::move(Output))); |
361 | 40 | } |
362 | | |
363 | | private: |
364 | 125 | std::string lookupModuleOutput(const ModuleID &MID, ModuleOutputKind MOK) { |
365 | | // Cache the PCM path, since it will be queried repeatedly for each module. |
366 | | // The other outputs are only queried once during getCanonicalCommandLine. |
367 | 125 | auto PCMPath = PCMPaths.insert({MID, ""}); |
368 | 125 | if (PCMPath.second) |
369 | 51 | PCMPath.first->second = constructPCMPath(MID); |
370 | 125 | switch (MOK) { |
371 | 106 | case ModuleOutputKind::ModuleFile: |
372 | 106 | return PCMPath.first->second; |
373 | 8 | case ModuleOutputKind::DependencyFile: |
374 | 8 | return PCMPath.first->second + ".d"; |
375 | 8 | case ModuleOutputKind::DependencyTargets: |
376 | | // Null-separate the list of targets. |
377 | 8 | return join(ModuleDepTargets, StringRef("\0", 1)); |
378 | 3 | case ModuleOutputKind::DiagnosticSerializationFile: |
379 | 3 | return PCMPath.first->second + ".diag"; |
380 | 125 | } |
381 | 0 | llvm_unreachable("Fully covered switch above!"); |
382 | 0 | } |
383 | | |
384 | | /// Construct a path for the explicitly built PCM. |
385 | 51 | std::string constructPCMPath(ModuleID MID) const { |
386 | 51 | auto MDIt = Modules.find(IndexedModuleID{MID, 0}); |
387 | 51 | assert(MDIt != Modules.end()); |
388 | 0 | const ModuleDeps &MD = MDIt->second; |
389 | | |
390 | 51 | StringRef Filename = llvm::sys::path::filename(MD.ImplicitModulePCMPath); |
391 | 51 | StringRef ModuleCachePath = llvm::sys::path::parent_path( |
392 | 51 | llvm::sys::path::parent_path(MD.ImplicitModulePCMPath)); |
393 | | |
394 | 51 | SmallString<256> ExplicitPCMPath(!ModuleFilesDir.empty() ? ModuleFilesDir32 |
395 | 51 | : ModuleCachePath19 ); |
396 | 51 | llvm::sys::path::append(ExplicitPCMPath, MD.ID.ContextHash, Filename); |
397 | 51 | return std::string(ExplicitPCMPath); |
398 | 51 | } |
399 | | |
400 | | struct IndexedModuleID { |
401 | | ModuleID ID; |
402 | | mutable size_t InputIndex; |
403 | | |
404 | 175 | bool operator==(const IndexedModuleID &Other) const { |
405 | 175 | return ID.ModuleName == Other.ID.ModuleName && |
406 | 175 | ID.ContextHash == Other.ID.ContextHash141 ; |
407 | 175 | } |
408 | | }; |
409 | | |
410 | | struct IndexedModuleIDHasher { |
411 | 290 | std::size_t operator()(const IndexedModuleID &IMID) const { |
412 | 290 | using llvm::hash_combine; |
413 | | |
414 | 290 | return hash_combine(IMID.ID.ModuleName, IMID.ID.ContextHash); |
415 | 290 | } |
416 | | }; |
417 | | |
418 | | struct InputDeps { |
419 | | std::string FileName; |
420 | | std::string ContextHash; |
421 | | std::vector<std::string> FileDeps; |
422 | | std::vector<ModuleID> ModuleDeps; |
423 | | std::vector<std::string> CommandLine; |
424 | | }; |
425 | | |
426 | | std::mutex Lock; |
427 | | std::unordered_map<IndexedModuleID, ModuleDeps, IndexedModuleIDHasher> |
428 | | Modules; |
429 | | std::unordered_map<ModuleID, std::string, ModuleIDHasher> PCMPaths; |
430 | | std::vector<InputDeps> Inputs; |
431 | | }; |
432 | | |
433 | | static bool handleFullDependencyToolResult( |
434 | | const std::string &Input, |
435 | | llvm::Expected<FullDependenciesResult> &MaybeFullDeps, FullDeps &FD, |
436 | 54 | size_t InputIndex, SharedStream &OS, SharedStream &Errs) { |
437 | 54 | if (!MaybeFullDeps) { |
438 | 0 | llvm::handleAllErrors( |
439 | 0 | MaybeFullDeps.takeError(), [&Input, &Errs](llvm::StringError &Err) { |
440 | 0 | Errs.applyLocked([&](raw_ostream &OS) { |
441 | 0 | OS << "Error while scanning dependencies for " << Input << ":\n"; |
442 | 0 | OS << Err.getMessage(); |
443 | 0 | }); |
444 | 0 | }); |
445 | 0 | return true; |
446 | 0 | } |
447 | 54 | FD.mergeDeps(Input, std::move(*MaybeFullDeps), InputIndex); |
448 | 54 | return false; |
449 | 54 | } |
450 | | |
451 | 88 | int main(int argc, const char **argv) { |
452 | 88 | llvm::InitLLVM X(argc, argv); |
453 | 88 | llvm::cl::HideUnrelatedOptions(DependencyScannerCategory); |
454 | 88 | if (!llvm::cl::ParseCommandLineOptions(argc, argv)) |
455 | 0 | return 1; |
456 | | |
457 | 88 | std::string ErrorMessage; |
458 | 88 | std::unique_ptr<tooling::JSONCompilationDatabase> Compilations = |
459 | 88 | tooling::JSONCompilationDatabase::loadFromFile( |
460 | 88 | CompilationDB, ErrorMessage, |
461 | 88 | tooling::JSONCommandLineSyntax::AutoDetect); |
462 | 88 | if (!Compilations) { |
463 | 0 | llvm::errs() << "error: " << ErrorMessage << "\n"; |
464 | 0 | return 1; |
465 | 0 | } |
466 | | |
467 | 88 | llvm::cl::PrintOptionValues(); |
468 | | |
469 | | // The command options are rewritten to run Clang in preprocessor only mode. |
470 | 88 | auto AdjustingCompilations = |
471 | 88 | std::make_unique<tooling::ArgumentsAdjustingCompilations>( |
472 | 88 | std::move(Compilations)); |
473 | 88 | ResourceDirectoryCache ResourceDirCache; |
474 | | |
475 | 88 | AdjustingCompilations->appendArgumentsAdjuster( |
476 | 88 | [&ResourceDirCache](const tooling::CommandLineArguments &Args, |
477 | 191 | StringRef FileName) { |
478 | 191 | std::string LastO; |
479 | 191 | bool HasResourceDir = false; |
480 | 191 | bool ClangCLMode = false; |
481 | 191 | auto FlagsEnd = llvm::find(Args, "--"); |
482 | 191 | if (FlagsEnd != Args.begin()) { |
483 | 191 | ClangCLMode = |
484 | 191 | llvm::sys::path::stem(Args[0]).contains_insensitive("clang-cl") || |
485 | 191 | llvm::is_contained(Args, "--driver-mode=cl")134 ; |
486 | | |
487 | | // Reverse scan, starting at the end or at the element before "--". |
488 | 191 | auto R = std::make_reverse_iterator(FlagsEnd); |
489 | 1.75k | for (auto I = R, E = Args.rend(); I != E; ++I1.56k ) { |
490 | 1.56k | StringRef Arg = *I; |
491 | 1.56k | if (ClangCLMode) { |
492 | | // Ignore arguments that are preceded by "-Xclang". |
493 | 454 | if ((I + 1) != E && I[1] == "-Xclang"390 ) |
494 | 4 | continue; |
495 | 450 | if (LastO.empty()) { |
496 | | // With clang-cl, the output obj file can be specified with |
497 | | // "/opath", "/o path", "/Fopath", and the dash counterparts. |
498 | | // Also, clang-cl adds ".obj" extension if none is found. |
499 | 311 | if ((Arg == "-o" || Arg == "/o"297 ) && I != R15 ) |
500 | 15 | LastO = I[-1]; // Next argument (reverse iterator) |
501 | 296 | else if (Arg.startswith("/Fo") || Arg.startswith("-Fo")287 ) |
502 | 9 | LastO = Arg.drop_front(3).str(); |
503 | 287 | else if (Arg.startswith("/o") || Arg.startswith("-o")285 ) |
504 | 3 | LastO = Arg.drop_front(2).str(); |
505 | | |
506 | 311 | if (!LastO.empty() && !llvm::sys::path::has_extension(LastO)27 ) |
507 | 0 | LastO.append(".obj"); |
508 | 311 | } |
509 | 450 | } |
510 | 1.55k | if (Arg == "-resource-dir") |
511 | 0 | HasResourceDir = true; |
512 | 1.55k | } |
513 | 191 | } |
514 | 191 | tooling::CommandLineArguments AdjustedArgs(Args.begin(), FlagsEnd); |
515 | | // The clang-cl driver passes "-o -" to the frontend. Inject the real |
516 | | // file here to ensure "-MT" can be deduced if need be. |
517 | 191 | if (ClangCLMode && !LastO.empty()64 ) { |
518 | 27 | AdjustedArgs.push_back("/clang:-o"); |
519 | 27 | AdjustedArgs.push_back("/clang:" + LastO); |
520 | 27 | } |
521 | | |
522 | 191 | if (!HasResourceDir && ResourceDirRecipe == RDRK_InvokeCompiler) { |
523 | 1 | StringRef ResourceDir = |
524 | 1 | ResourceDirCache.findResourceDir(Args, ClangCLMode); |
525 | 1 | if (!ResourceDir.empty()) { |
526 | 1 | AdjustedArgs.push_back("-resource-dir"); |
527 | 1 | AdjustedArgs.push_back(std::string(ResourceDir)); |
528 | 1 | } |
529 | 1 | } |
530 | 191 | AdjustedArgs.insert(AdjustedArgs.end(), FlagsEnd, Args.end()); |
531 | 191 | return AdjustedArgs; |
532 | 191 | }); |
533 | | |
534 | 88 | SharedStream Errs(llvm::errs()); |
535 | | // Print out the dependency results to STDOUT by default. |
536 | 88 | SharedStream DependencyOS(llvm::outs()); |
537 | | |
538 | 88 | DependencyScanningService Service(ScanMode, Format, ReuseFileManager, |
539 | 88 | OptimizeArgs); |
540 | 88 | llvm::ThreadPool Pool(llvm::hardware_concurrency(NumThreads)); |
541 | 88 | std::vector<std::unique_ptr<DependencyScanningTool>> WorkerTools; |
542 | 662 | for (unsigned I = 0; I < Pool.getThreadCount(); ++I574 ) |
543 | 574 | WorkerTools.push_back(std::make_unique<DependencyScanningTool>(Service)); |
544 | | |
545 | 88 | std::vector<tooling::CompileCommand> Inputs = |
546 | 88 | AdjustingCompilations->getAllCompileCommands(); |
547 | | |
548 | 88 | std::atomic<bool> HadErrors(false); |
549 | 88 | FullDeps FD; |
550 | 88 | std::mutex Lock; |
551 | 88 | size_t Index = 0; |
552 | | |
553 | 88 | if (Verbose) { |
554 | 0 | llvm::outs() << "Running clang-scan-deps on " << Inputs.size() |
555 | 0 | << " files using " << Pool.getThreadCount() << " workers\n"; |
556 | 0 | } |
557 | 662 | for (unsigned I = 0; I < Pool.getThreadCount(); ++I574 ) { |
558 | 574 | Pool.async([I, &Lock, &Index, &Inputs, &HadErrors, &FD, &WorkerTools, |
559 | 574 | &DependencyOS, &Errs]() { |
560 | 568 | llvm::StringSet<> AlreadySeenModules; |
561 | 759 | while (true753 ) { |
562 | 759 | const tooling::CompileCommand *Input; |
563 | 759 | std::string Filename; |
564 | 759 | std::string CWD; |
565 | 759 | size_t LocalIndex; |
566 | | // Take the next input. |
567 | 759 | { |
568 | 759 | std::unique_lock<std::mutex> LockGuard(Lock); |
569 | 759 | if (Index >= Inputs.size()) |
570 | 574 | return; |
571 | 185 | LocalIndex = Index; |
572 | 185 | Input = &Inputs[Index++]; |
573 | 185 | Filename = std::move(Input->Filename); |
574 | 185 | CWD = std::move(Input->Directory); |
575 | 185 | } |
576 | 0 | Optional<StringRef> MaybeModuleName; |
577 | 185 | if (!ModuleName.empty()) |
578 | 4 | MaybeModuleName = ModuleName; |
579 | | // Run the tool on it. |
580 | 185 | if (Format == ScanningOutputFormat::Make) { |
581 | 137 | auto MaybeFile = WorkerTools[I]->getDependencyFile( |
582 | 137 | Input->CommandLine, CWD, MaybeModuleName); |
583 | 137 | if (handleMakeDependencyToolResult(Filename, MaybeFile, DependencyOS, |
584 | 137 | Errs)) |
585 | 7 | HadErrors = true; |
586 | 137 | } else { |
587 | 48 | auto MaybeFullDeps = WorkerTools[I]->getFullDependencies( |
588 | 48 | Input->CommandLine, CWD, AlreadySeenModules, MaybeModuleName); |
589 | 48 | if (handleFullDependencyToolResult(Filename, MaybeFullDeps, FD, |
590 | 48 | LocalIndex, DependencyOS, Errs)) |
591 | 0 | HadErrors = true; |
592 | 48 | } |
593 | 185 | } |
594 | 568 | }); |
595 | 574 | } |
596 | 88 | Pool.wait(); |
597 | | |
598 | 88 | if (Format == ScanningOutputFormat::Full) |
599 | 40 | FD.printFullOutput(llvm::outs()); |
600 | | |
601 | 88 | return HadErrors; |
602 | 88 | } |