1 | //===- DependencyScanningWorker.cpp - clang-scan-deps worker --------------===// |
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/Tooling/DependencyScanning/DependencyScanningWorker.h" |
10 | #include "clang/Basic/DiagnosticDriver.h" |
11 | #include "clang/Basic/DiagnosticFrontend.h" |
12 | #include "clang/Basic/DiagnosticSerialization.h" |
13 | #include "clang/CodeGen/ObjectFilePCHContainerOperations.h" |
14 | #include "clang/Driver/Compilation.h" |
15 | #include "clang/Driver/Driver.h" |
16 | #include "clang/Driver/Job.h" |
17 | #include "clang/Driver/Tool.h" |
18 | #include "clang/Frontend/CompilerInstance.h" |
19 | #include "clang/Frontend/CompilerInvocation.h" |
20 | #include "clang/Frontend/FrontendActions.h" |
21 | #include "clang/Frontend/TextDiagnosticPrinter.h" |
22 | #include "clang/Frontend/Utils.h" |
23 | #include "clang/Lex/PreprocessorOptions.h" |
24 | #include "clang/Tooling/DependencyScanning/DependencyScanningService.h" |
25 | #include "clang/Tooling/DependencyScanning/ModuleDepCollector.h" |
26 | #include "clang/Tooling/Tooling.h" |
27 | #include "llvm/ADT/ScopeExit.h" |
28 | #include "llvm/Support/Allocator.h" |
29 | #include "llvm/Support/Error.h" |
30 | #include "llvm/TargetParser/Host.h" |
31 | #include <optional> |
32 | |
33 | using namespace clang; |
34 | using namespace tooling; |
35 | using namespace dependencies; |
36 | |
37 | namespace { |
38 | |
39 | /// Forwards the gatherered dependencies to the consumer. |
40 | class DependencyConsumerForwarder : public DependencyFileGenerator { |
41 | public: |
42 | DependencyConsumerForwarder(std::unique_ptr<DependencyOutputOptions> Opts, |
43 | StringRef WorkingDirectory, DependencyConsumer &C) |
44 | : DependencyFileGenerator(*Opts), WorkingDirectory(WorkingDirectory), |
45 | Opts(std::move(Opts)), C(C) {} |
46 | |
47 | void finishedMainFile(DiagnosticsEngine &Diags) override { |
48 | C.handleDependencyOutputOpts(Opts: *Opts); |
49 | llvm::SmallString<256> CanonPath; |
50 | for (const auto &File : getDependencies()) { |
51 | CanonPath = File; |
52 | llvm::sys::path::remove_dots(path&: CanonPath, /*remove_dot_dot=*/true); |
53 | llvm::sys::fs::make_absolute(current_directory: WorkingDirectory, path&: CanonPath); |
54 | C.handleFileDependency(Filename: CanonPath); |
55 | } |
56 | } |
57 | |
58 | private: |
59 | StringRef WorkingDirectory; |
60 | std::unique_ptr<DependencyOutputOptions> Opts; |
61 | DependencyConsumer &C; |
62 | }; |
63 | |
64 | static bool (const HeaderSearchOptions &HSOpts, |
65 | const HeaderSearchOptions &ExistingHSOpts, |
66 | DiagnosticsEngine *Diags, |
67 | const LangOptions &LangOpts) { |
68 | if (LangOpts.Modules) { |
69 | if (HSOpts.VFSOverlayFiles != ExistingHSOpts.VFSOverlayFiles) { |
70 | if (Diags) { |
71 | Diags->Report(DiagID: diag::warn_pch_vfsoverlay_mismatch); |
72 | auto VFSNote = [&](int Type, ArrayRef<std::string> VFSOverlays) { |
73 | if (VFSOverlays.empty()) { |
74 | Diags->Report(DiagID: diag::note_pch_vfsoverlay_empty) << Type; |
75 | } else { |
76 | std::string Files = llvm::join(R&: VFSOverlays, Separator: "\n" ); |
77 | Diags->Report(DiagID: diag::note_pch_vfsoverlay_files) << Type << Files; |
78 | } |
79 | }; |
80 | VFSNote(0, HSOpts.VFSOverlayFiles); |
81 | VFSNote(1, ExistingHSOpts.VFSOverlayFiles); |
82 | } |
83 | } |
84 | } |
85 | return false; |
86 | } |
87 | |
88 | using PrebuiltModuleFilesT = decltype(HeaderSearchOptions::PrebuiltModuleFiles); |
89 | |
90 | /// A listener that collects the imported modules and optionally the input |
91 | /// files. |
92 | class PrebuiltModuleListener : public ASTReaderListener { |
93 | public: |
94 | PrebuiltModuleListener(PrebuiltModuleFilesT &PrebuiltModuleFiles, |
95 | llvm::SmallVector<std::string> &NewModuleFiles, |
96 | PrebuiltModuleVFSMapT &PrebuiltModuleVFSMap, |
97 | const HeaderSearchOptions &HSOpts, |
98 | const LangOptions &LangOpts, DiagnosticsEngine &Diags) |
99 | : PrebuiltModuleFiles(PrebuiltModuleFiles), |
100 | NewModuleFiles(NewModuleFiles), |
101 | PrebuiltModuleVFSMap(PrebuiltModuleVFSMap), ExistingHSOpts(HSOpts), |
102 | ExistingLangOpts(LangOpts), Diags(Diags) {} |
103 | |
104 | bool needsImportVisitation() const override { return true; } |
105 | |
106 | void visitImport(StringRef ModuleName, StringRef Filename) override { |
107 | if (PrebuiltModuleFiles.insert(x: {ModuleName.str(), Filename.str()}).second) |
108 | NewModuleFiles.push_back(Elt: Filename.str()); |
109 | } |
110 | |
111 | void visitModuleFile(StringRef Filename, |
112 | serialization::ModuleKind Kind) override { |
113 | CurrentFile = Filename; |
114 | } |
115 | |
116 | bool (const HeaderSearchOptions &HSOpts, |
117 | bool Complain) override { |
118 | std::vector<std::string> VFSOverlayFiles = HSOpts.VFSOverlayFiles; |
119 | PrebuiltModuleVFSMap.insert( |
120 | KV: {CurrentFile, llvm::StringSet<>(VFSOverlayFiles)}); |
121 | return checkHeaderSearchPaths( |
122 | HSOpts, ExistingHSOpts, Diags: Complain ? &Diags : nullptr, LangOpts: ExistingLangOpts); |
123 | } |
124 | |
125 | private: |
126 | PrebuiltModuleFilesT &PrebuiltModuleFiles; |
127 | llvm::SmallVector<std::string> &NewModuleFiles; |
128 | PrebuiltModuleVFSMapT &PrebuiltModuleVFSMap; |
129 | const HeaderSearchOptions &ExistingHSOpts; |
130 | const LangOptions &ExistingLangOpts; |
131 | DiagnosticsEngine &Diags; |
132 | std::string CurrentFile; |
133 | }; |
134 | |
135 | /// Visit the given prebuilt module and collect all of the modules it |
136 | /// transitively imports and contributing input files. |
137 | static bool visitPrebuiltModule(StringRef PrebuiltModuleFilename, |
138 | CompilerInstance &CI, |
139 | PrebuiltModuleFilesT &ModuleFiles, |
140 | PrebuiltModuleVFSMapT &PrebuiltModuleVFSMap, |
141 | DiagnosticsEngine &Diags) { |
142 | // List of module files to be processed. |
143 | llvm::SmallVector<std::string> Worklist; |
144 | PrebuiltModuleListener Listener(ModuleFiles, Worklist, PrebuiltModuleVFSMap, |
145 | CI.getHeaderSearchOpts(), CI.getLangOpts(), |
146 | Diags); |
147 | |
148 | Listener.visitModuleFile(Filename: PrebuiltModuleFilename, |
149 | Kind: serialization::MK_ExplicitModule); |
150 | if (ASTReader::readASTFileControlBlock( |
151 | Filename: PrebuiltModuleFilename, FileMgr&: CI.getFileManager(), ModuleCache: CI.getModuleCache(), |
152 | PCHContainerRdr: CI.getPCHContainerReader(), |
153 | /*FindModuleFileExtensions=*/false, Listener, |
154 | /*ValidateDiagnosticOptions=*/false, ClientLoadCapabilities: ASTReader::ARR_OutOfDate)) |
155 | return true; |
156 | |
157 | while (!Worklist.empty()) { |
158 | Listener.visitModuleFile(Filename: Worklist.back(), Kind: serialization::MK_ExplicitModule); |
159 | if (ASTReader::readASTFileControlBlock( |
160 | Filename: Worklist.pop_back_val(), FileMgr&: CI.getFileManager(), ModuleCache: CI.getModuleCache(), |
161 | PCHContainerRdr: CI.getPCHContainerReader(), |
162 | /*FindModuleFileExtensions=*/false, Listener, |
163 | /*ValidateDiagnosticOptions=*/false)) |
164 | return true; |
165 | } |
166 | return false; |
167 | } |
168 | |
169 | /// Transform arbitrary file name into an object-like file name. |
170 | static std::string makeObjFileName(StringRef FileName) { |
171 | SmallString<128> ObjFileName(FileName); |
172 | llvm::sys::path::replace_extension(path&: ObjFileName, extension: "o" ); |
173 | return std::string(ObjFileName); |
174 | } |
175 | |
176 | /// Deduce the dependency target based on the output file and input files. |
177 | static std::string |
178 | deduceDepTarget(const std::string &OutputFile, |
179 | const SmallVectorImpl<FrontendInputFile> &InputFiles) { |
180 | if (OutputFile != "-" ) |
181 | return OutputFile; |
182 | |
183 | if (InputFiles.empty() || !InputFiles.front().isFile()) |
184 | return "clang-scan-deps\\ dependency" ; |
185 | |
186 | return makeObjFileName(FileName: InputFiles.front().getFile()); |
187 | } |
188 | |
189 | /// Sanitize diagnostic options for dependency scan. |
190 | static void sanitizeDiagOpts(DiagnosticOptions &DiagOpts) { |
191 | // Don't print 'X warnings and Y errors generated'. |
192 | DiagOpts.ShowCarets = false; |
193 | // Don't write out diagnostic file. |
194 | DiagOpts.DiagnosticSerializationFile.clear(); |
195 | // Don't emit warnings except for scanning specific warnings. |
196 | // TODO: It would be useful to add a more principled way to ignore all |
197 | // warnings that come from source code. The issue is that we need to |
198 | // ignore warnings that could be surpressed by |
199 | // `#pragma clang diagnostic`, while still allowing some scanning |
200 | // warnings for things we're not ready to turn into errors yet. |
201 | // See `test/ClangScanDeps/diagnostic-pragmas.c` for an example. |
202 | llvm::erase_if(C&: DiagOpts.Warnings, P: [](StringRef Warning) { |
203 | return llvm::StringSwitch<bool>(Warning) |
204 | .Cases(S0: "pch-vfs-diff" , S1: "error=pch-vfs-diff" , Value: false) |
205 | .StartsWith(S: "no-error=" , Value: false) |
206 | .Default(Value: true); |
207 | }); |
208 | } |
209 | |
210 | // Clang implements -D and -U by splatting text into a predefines buffer. This |
211 | // allows constructs such as `-DFඞ=3 "-D F\u{0D9E} 4 3 2”` to be accepted and |
212 | // define the same macro, or adding C++ style comments before the macro name. |
213 | // |
214 | // This function checks that the first non-space characters in the macro |
215 | // obviously form an identifier that can be uniqued on without lexing. Failing |
216 | // to do this could lead to changing the final definition of a macro. |
217 | // |
218 | // We could set up a preprocessor and actually lex the name, but that's very |
219 | // heavyweight for a situation that will almost never happen in practice. |
220 | static std::optional<StringRef> getSimpleMacroName(StringRef Macro) { |
221 | StringRef Name = Macro.split(Separator: "=" ).first.ltrim(Chars: " \t" ); |
222 | std::size_t I = 0; |
223 | |
224 | auto FinishName = [&]() -> std::optional<StringRef> { |
225 | StringRef SimpleName = Name.slice(Start: 0, End: I); |
226 | if (SimpleName.empty()) |
227 | return std::nullopt; |
228 | return SimpleName; |
229 | }; |
230 | |
231 | for (; I != Name.size(); ++I) { |
232 | switch (Name[I]) { |
233 | case '(': // Start of macro parameter list |
234 | case ' ': // End of macro name |
235 | case '\t': |
236 | return FinishName(); |
237 | case '_': |
238 | continue; |
239 | default: |
240 | if (llvm::isAlnum(C: Name[I])) |
241 | continue; |
242 | return std::nullopt; |
243 | } |
244 | } |
245 | return FinishName(); |
246 | } |
247 | |
248 | static void canonicalizeDefines(PreprocessorOptions &PPOpts) { |
249 | using MacroOpt = std::pair<StringRef, std::size_t>; |
250 | std::vector<MacroOpt> SimpleNames; |
251 | SimpleNames.reserve(n: PPOpts.Macros.size()); |
252 | std::size_t Index = 0; |
253 | for (const auto &M : PPOpts.Macros) { |
254 | auto SName = getSimpleMacroName(Macro: M.first); |
255 | // Skip optimizing if we can't guarantee we can preserve relative order. |
256 | if (!SName) |
257 | return; |
258 | SimpleNames.emplace_back(args&: *SName, args&: Index); |
259 | ++Index; |
260 | } |
261 | |
262 | llvm::stable_sort(Range&: SimpleNames, C: llvm::less_first()); |
263 | // Keep the last instance of each macro name by going in reverse |
264 | auto NewEnd = std::unique( |
265 | first: SimpleNames.rbegin(), last: SimpleNames.rend(), |
266 | binary_pred: [](const MacroOpt &A, const MacroOpt &B) { return A.first == B.first; }); |
267 | SimpleNames.erase(first: SimpleNames.begin(), last: NewEnd.base()); |
268 | |
269 | // Apply permutation. |
270 | decltype(PPOpts.Macros) NewMacros; |
271 | NewMacros.reserve(n: SimpleNames.size()); |
272 | for (std::size_t I = 0, E = SimpleNames.size(); I != E; ++I) { |
273 | std::size_t OriginalIndex = SimpleNames[I].second; |
274 | // We still emit undefines here as they may be undefining a predefined macro |
275 | NewMacros.push_back(x: std::move(PPOpts.Macros[OriginalIndex])); |
276 | } |
277 | std::swap(x&: PPOpts.Macros, y&: NewMacros); |
278 | } |
279 | |
280 | /// A clang tool that runs the preprocessor in a mode that's optimized for |
281 | /// dependency scanning for the given compiler invocation. |
282 | class DependencyScanningAction : public tooling::ToolAction { |
283 | public: |
284 | DependencyScanningAction( |
285 | StringRef WorkingDirectory, DependencyConsumer &Consumer, |
286 | DependencyActionController &Controller, |
287 | llvm::IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS, |
288 | ScanningOutputFormat Format, ScanningOptimizations OptimizeArgs, |
289 | bool EagerLoadModules, bool DisableFree, |
290 | std::optional<StringRef> ModuleName = std::nullopt) |
291 | : WorkingDirectory(WorkingDirectory), Consumer(Consumer), |
292 | Controller(Controller), DepFS(std::move(DepFS)), Format(Format), |
293 | OptimizeArgs(OptimizeArgs), EagerLoadModules(EagerLoadModules), |
294 | DisableFree(DisableFree), ModuleName(ModuleName) {} |
295 | |
296 | bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation, |
297 | FileManager *DriverFileMgr, |
298 | std::shared_ptr<PCHContainerOperations> PCHContainerOps, |
299 | DiagnosticConsumer *DiagConsumer) override { |
300 | // Make a deep copy of the original Clang invocation. |
301 | CompilerInvocation OriginalInvocation(*Invocation); |
302 | // Restore the value of DisableFree, which may be modified by Tooling. |
303 | OriginalInvocation.getFrontendOpts().DisableFree = DisableFree; |
304 | if (any(Val: OptimizeArgs & ScanningOptimizations::Macros)) |
305 | canonicalizeDefines(PPOpts&: OriginalInvocation.getPreprocessorOpts()); |
306 | |
307 | if (Scanned) { |
308 | // Scanning runs once for the first -cc1 invocation in a chain of driver |
309 | // jobs. For any dependent jobs, reuse the scanning result and just |
310 | // update the LastCC1Arguments to correspond to the new invocation. |
311 | // FIXME: to support multi-arch builds, each arch requires a separate scan |
312 | setLastCC1Arguments(std::move(OriginalInvocation)); |
313 | return true; |
314 | } |
315 | |
316 | Scanned = true; |
317 | |
318 | // Create a compiler instance to handle the actual work. |
319 | ScanInstanceStorage.emplace(args: std::move(PCHContainerOps)); |
320 | CompilerInstance &ScanInstance = *ScanInstanceStorage; |
321 | ScanInstance.setInvocation(std::move(Invocation)); |
322 | |
323 | // Create the compiler's actual diagnostics engine. |
324 | sanitizeDiagOpts(DiagOpts&: ScanInstance.getDiagnosticOpts()); |
325 | ScanInstance.createDiagnostics(Client: DiagConsumer, /*ShouldOwnClient=*/false); |
326 | if (!ScanInstance.hasDiagnostics()) |
327 | return false; |
328 | |
329 | // Some DiagnosticConsumers require that finish() is called. |
330 | auto DiagConsumerFinisher = |
331 | llvm::make_scope_exit(F: [DiagConsumer]() { DiagConsumer->finish(); }); |
332 | |
333 | ScanInstance.getPreprocessorOpts().AllowPCHWithDifferentModulesCachePath = |
334 | true; |
335 | |
336 | ScanInstance.getFrontendOpts().GenerateGlobalModuleIndex = false; |
337 | ScanInstance.getFrontendOpts().UseGlobalModuleIndex = false; |
338 | ScanInstance.getFrontendOpts().ModulesShareFileManager = false; |
339 | ScanInstance.getHeaderSearchOpts().ModuleFormat = "raw" ; |
340 | ScanInstance.getHeaderSearchOpts().ModulesIncludeVFSUsage = |
341 | any(Val: OptimizeArgs & ScanningOptimizations::VFS); |
342 | |
343 | // Support for virtual file system overlays. |
344 | auto FS = createVFSFromCompilerInvocation( |
345 | CI: ScanInstance.getInvocation(), Diags&: ScanInstance.getDiagnostics(), |
346 | BaseFS: DriverFileMgr->getVirtualFileSystemPtr()); |
347 | |
348 | // Create a new FileManager to match the invocation's FileSystemOptions. |
349 | auto *FileMgr = ScanInstance.createFileManager(VFS: FS); |
350 | ScanInstance.createSourceManager(FileMgr&: *FileMgr); |
351 | |
352 | // Store the list of prebuilt module files into header search options. This |
353 | // will prevent the implicit build to create duplicate modules and will |
354 | // force reuse of the existing prebuilt module files instead. |
355 | PrebuiltModuleVFSMapT PrebuiltModuleVFSMap; |
356 | if (!ScanInstance.getPreprocessorOpts().ImplicitPCHInclude.empty()) |
357 | if (visitPrebuiltModule( |
358 | PrebuiltModuleFilename: ScanInstance.getPreprocessorOpts().ImplicitPCHInclude, |
359 | CI&: ScanInstance, |
360 | ModuleFiles&: ScanInstance.getHeaderSearchOpts().PrebuiltModuleFiles, |
361 | PrebuiltModuleVFSMap, Diags&: ScanInstance.getDiagnostics())) |
362 | return false; |
363 | |
364 | // Use the dependency scanning optimized file system if requested to do so. |
365 | if (DepFS) |
366 | ScanInstance.getPreprocessorOpts().DependencyDirectivesForFile = |
367 | [LocalDepFS = DepFS](FileEntryRef File) |
368 | -> std::optional<ArrayRef<dependency_directives_scan::Directive>> { |
369 | if (llvm::ErrorOr<EntryRef> Entry = |
370 | LocalDepFS->getOrCreateFileSystemEntry(Filename: File.getName())) |
371 | if (LocalDepFS->ensureDirectiveTokensArePopulated(Entry: *Entry)) |
372 | return Entry->getDirectiveTokens(); |
373 | return std::nullopt; |
374 | }; |
375 | |
376 | // Create the dependency collector that will collect the produced |
377 | // dependencies. |
378 | // |
379 | // This also moves the existing dependency output options from the |
380 | // invocation to the collector. The options in the invocation are reset, |
381 | // which ensures that the compiler won't create new dependency collectors, |
382 | // and thus won't write out the extra '.d' files to disk. |
383 | auto Opts = std::make_unique<DependencyOutputOptions>(); |
384 | std::swap(a&: *Opts, b&: ScanInstance.getInvocation().getDependencyOutputOpts()); |
385 | // We need at least one -MT equivalent for the generator of make dependency |
386 | // files to work. |
387 | if (Opts->Targets.empty()) |
388 | Opts->Targets = { |
389 | deduceDepTarget(OutputFile: ScanInstance.getFrontendOpts().OutputFile, |
390 | InputFiles: ScanInstance.getFrontendOpts().Inputs)}; |
391 | Opts->IncludeSystemHeaders = true; |
392 | |
393 | switch (Format) { |
394 | case ScanningOutputFormat::Make: |
395 | ScanInstance.addDependencyCollector( |
396 | Listener: std::make_shared<DependencyConsumerForwarder>( |
397 | args: std::move(Opts), args&: WorkingDirectory, args&: Consumer)); |
398 | break; |
399 | case ScanningOutputFormat::P1689: |
400 | case ScanningOutputFormat::Full: |
401 | MDC = std::make_shared<ModuleDepCollector>( |
402 | args: std::move(Opts), args&: ScanInstance, args&: Consumer, args&: Controller, |
403 | args&: OriginalInvocation, args: std::move(PrebuiltModuleVFSMap), args&: OptimizeArgs, |
404 | args&: EagerLoadModules, args: Format == ScanningOutputFormat::P1689); |
405 | ScanInstance.addDependencyCollector(Listener: MDC); |
406 | break; |
407 | } |
408 | |
409 | // Consider different header search and diagnostic options to create |
410 | // different modules. This avoids the unsound aliasing of module PCMs. |
411 | // |
412 | // TODO: Implement diagnostic bucketing to reduce the impact of strict |
413 | // context hashing. |
414 | ScanInstance.getHeaderSearchOpts().ModulesStrictContextHash = true; |
415 | ScanInstance.getHeaderSearchOpts().ModulesSkipDiagnosticOptions = true; |
416 | ScanInstance.getHeaderSearchOpts().ModulesSkipHeaderSearchPaths = true; |
417 | ScanInstance.getHeaderSearchOpts().ModulesSkipPragmaDiagnosticMappings = |
418 | true; |
419 | |
420 | // Avoid some checks and module map parsing when loading PCM files. |
421 | ScanInstance.getPreprocessorOpts().ModulesCheckRelocated = false; |
422 | |
423 | std::unique_ptr<FrontendAction> Action; |
424 | |
425 | if (ModuleName) |
426 | Action = std::make_unique<GetDependenciesByModuleNameAction>(args&: *ModuleName); |
427 | else |
428 | Action = std::make_unique<ReadPCHAndPreprocessAction>(); |
429 | |
430 | if (ScanInstance.getDiagnostics().hasErrorOccurred()) |
431 | return false; |
432 | |
433 | // Each action is responsible for calling finish. |
434 | DiagConsumerFinisher.release(); |
435 | const bool Result = ScanInstance.ExecuteAction(Act&: *Action); |
436 | |
437 | if (Result) |
438 | setLastCC1Arguments(std::move(OriginalInvocation)); |
439 | |
440 | // Propagate the statistics to the parent FileManager. |
441 | DriverFileMgr->AddStats(Other: ScanInstance.getFileManager()); |
442 | |
443 | return Result; |
444 | } |
445 | |
446 | bool hasScanned() const { return Scanned; } |
447 | |
448 | /// Take the cc1 arguments corresponding to the most recent invocation used |
449 | /// with this action. Any modifications implied by the discovered dependencies |
450 | /// will have already been applied. |
451 | std::vector<std::string> takeLastCC1Arguments() { |
452 | std::vector<std::string> Result; |
453 | std::swap(x&: Result, y&: LastCC1Arguments); // Reset LastCC1Arguments to empty. |
454 | return Result; |
455 | } |
456 | |
457 | private: |
458 | void setLastCC1Arguments(CompilerInvocation &&CI) { |
459 | if (MDC) |
460 | MDC->applyDiscoveredDependencies(CI); |
461 | LastCC1Arguments = CI.getCC1CommandLine(); |
462 | } |
463 | |
464 | private: |
465 | StringRef WorkingDirectory; |
466 | DependencyConsumer &Consumer; |
467 | DependencyActionController &Controller; |
468 | llvm::IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS; |
469 | ScanningOutputFormat Format; |
470 | ScanningOptimizations OptimizeArgs; |
471 | bool EagerLoadModules; |
472 | bool DisableFree; |
473 | std::optional<StringRef> ModuleName; |
474 | std::optional<CompilerInstance> ScanInstanceStorage; |
475 | std::shared_ptr<ModuleDepCollector> MDC; |
476 | std::vector<std::string> LastCC1Arguments; |
477 | bool Scanned = false; |
478 | }; |
479 | |
480 | } // end anonymous namespace |
481 | |
482 | DependencyScanningWorker::DependencyScanningWorker( |
483 | DependencyScanningService &Service, |
484 | llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS) |
485 | : Format(Service.getFormat()), OptimizeArgs(Service.getOptimizeArgs()), |
486 | EagerLoadModules(Service.shouldEagerLoadModules()) { |
487 | PCHContainerOps = std::make_shared<PCHContainerOperations>(); |
488 | // We need to read object files from PCH built outside the scanner. |
489 | PCHContainerOps->registerReader( |
490 | Reader: std::make_unique<ObjectFilePCHContainerReader>()); |
491 | // The scanner itself writes only raw ast files. |
492 | PCHContainerOps->registerWriter(Writer: std::make_unique<RawPCHContainerWriter>()); |
493 | |
494 | switch (Service.getMode()) { |
495 | case ScanningMode::DependencyDirectivesScan: |
496 | DepFS = |
497 | new DependencyScanningWorkerFilesystem(Service.getSharedCache(), FS); |
498 | BaseFS = DepFS; |
499 | break; |
500 | case ScanningMode::CanonicalPreprocessing: |
501 | DepFS = nullptr; |
502 | BaseFS = FS; |
503 | break; |
504 | } |
505 | } |
506 | |
507 | llvm::Error DependencyScanningWorker::computeDependencies( |
508 | StringRef WorkingDirectory, const std::vector<std::string> &CommandLine, |
509 | DependencyConsumer &Consumer, DependencyActionController &Controller, |
510 | std::optional<StringRef> ModuleName) { |
511 | std::vector<const char *> CLI; |
512 | for (const std::string &Arg : CommandLine) |
513 | CLI.push_back(x: Arg.c_str()); |
514 | auto DiagOpts = CreateAndPopulateDiagOpts(Argv: CLI); |
515 | sanitizeDiagOpts(DiagOpts&: *DiagOpts); |
516 | |
517 | // Capture the emitted diagnostics and report them to the client |
518 | // in the case of a failure. |
519 | std::string DiagnosticOutput; |
520 | llvm::raw_string_ostream DiagnosticsOS(DiagnosticOutput); |
521 | TextDiagnosticPrinter DiagPrinter(DiagnosticsOS, DiagOpts.release()); |
522 | |
523 | if (computeDependencies(WorkingDirectory, CommandLine, DepConsumer&: Consumer, Controller, |
524 | DiagConsumer&: DiagPrinter, ModuleName)) |
525 | return llvm::Error::success(); |
526 | return llvm::make_error<llvm::StringError>(Args&: DiagnosticsOS.str(), |
527 | Args: llvm::inconvertibleErrorCode()); |
528 | } |
529 | |
530 | static bool forEachDriverJob( |
531 | ArrayRef<std::string> ArgStrs, DiagnosticsEngine &Diags, FileManager &FM, |
532 | llvm::function_ref<bool(const driver::Command &Cmd)> Callback) { |
533 | SmallVector<const char *, 256> Argv; |
534 | Argv.reserve(N: ArgStrs.size()); |
535 | for (const std::string &Arg : ArgStrs) |
536 | Argv.push_back(Elt: Arg.c_str()); |
537 | |
538 | llvm::vfs::FileSystem *FS = &FM.getVirtualFileSystem(); |
539 | |
540 | std::unique_ptr<driver::Driver> Driver = std::make_unique<driver::Driver>( |
541 | args&: Argv[0], args: llvm::sys::getDefaultTargetTriple(), args&: Diags, |
542 | args: "clang LLVM compiler" , args&: FS); |
543 | Driver->setTitle("clang_based_tool" ); |
544 | |
545 | llvm::BumpPtrAllocator Alloc; |
546 | bool CLMode = driver::IsClangCL( |
547 | DriverMode: driver::getDriverMode(ProgName: Argv[0], Args: ArrayRef(Argv).slice(N: 1))); |
548 | |
549 | if (llvm::Error E = driver::expandResponseFiles(Args&: Argv, ClangCLMode: CLMode, Alloc, FS)) { |
550 | Diags.Report(DiagID: diag::err_drv_expand_response_file) |
551 | << llvm::toString(E: std::move(E)); |
552 | return false; |
553 | } |
554 | |
555 | const std::unique_ptr<driver::Compilation> Compilation( |
556 | Driver->BuildCompilation(Args: llvm::ArrayRef(Argv))); |
557 | if (!Compilation) |
558 | return false; |
559 | |
560 | if (Compilation->containsError()) |
561 | return false; |
562 | |
563 | for (const driver::Command &Job : Compilation->getJobs()) { |
564 | if (!Callback(Job)) |
565 | return false; |
566 | } |
567 | return true; |
568 | } |
569 | |
570 | static bool createAndRunToolInvocation( |
571 | std::vector<std::string> CommandLine, DependencyScanningAction &Action, |
572 | FileManager &FM, |
573 | std::shared_ptr<clang::PCHContainerOperations> &PCHContainerOps, |
574 | DiagnosticsEngine &Diags, DependencyConsumer &Consumer) { |
575 | |
576 | // Save executable path before providing CommandLine to ToolInvocation |
577 | std::string Executable = CommandLine[0]; |
578 | ToolInvocation Invocation(std::move(CommandLine), &Action, &FM, |
579 | PCHContainerOps); |
580 | Invocation.setDiagnosticConsumer(Diags.getClient()); |
581 | Invocation.setDiagnosticOptions(&Diags.getDiagnosticOptions()); |
582 | if (!Invocation.run()) |
583 | return false; |
584 | |
585 | std::vector<std::string> Args = Action.takeLastCC1Arguments(); |
586 | Consumer.handleBuildCommand(Cmd: {.Executable: std::move(Executable), .Arguments: std::move(Args)}); |
587 | return true; |
588 | } |
589 | |
590 | bool DependencyScanningWorker::computeDependencies( |
591 | StringRef WorkingDirectory, const std::vector<std::string> &CommandLine, |
592 | DependencyConsumer &Consumer, DependencyActionController &Controller, |
593 | DiagnosticConsumer &DC, std::optional<StringRef> ModuleName) { |
594 | // Reset what might have been modified in the previous worker invocation. |
595 | BaseFS->setCurrentWorkingDirectory(WorkingDirectory); |
596 | |
597 | std::optional<std::vector<std::string>> ModifiedCommandLine; |
598 | llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> ModifiedFS; |
599 | |
600 | // If we're scanning based on a module name alone, we don't expect the client |
601 | // to provide us with an input file. However, the driver really wants to have |
602 | // one. Let's just make it up to make the driver happy. |
603 | if (ModuleName) { |
604 | auto OverlayFS = |
605 | llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(A&: BaseFS); |
606 | auto InMemoryFS = |
607 | llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>(); |
608 | InMemoryFS->setCurrentWorkingDirectory(WorkingDirectory); |
609 | OverlayFS->pushOverlay(FS: InMemoryFS); |
610 | ModifiedFS = OverlayFS; |
611 | |
612 | SmallString<128> FakeInputPath; |
613 | // TODO: We should retry the creation if the path already exists. |
614 | llvm::sys::fs::createUniquePath(Model: *ModuleName + "-%%%%%%%%.input" , |
615 | ResultPath&: FakeInputPath, |
616 | /*MakeAbsolute=*/false); |
617 | InMemoryFS->addFile(Path: FakeInputPath, ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "" )); |
618 | |
619 | ModifiedCommandLine = CommandLine; |
620 | ModifiedCommandLine->emplace_back(args&: FakeInputPath); |
621 | } |
622 | |
623 | const std::vector<std::string> &FinalCommandLine = |
624 | ModifiedCommandLine ? *ModifiedCommandLine : CommandLine; |
625 | auto &FinalFS = ModifiedFS ? ModifiedFS : BaseFS; |
626 | |
627 | auto FileMgr = |
628 | llvm::makeIntrusiveRefCnt<FileManager>(A: FileSystemOptions{}, A&: FinalFS); |
629 | |
630 | std::vector<const char *> FinalCCommandLine(FinalCommandLine.size(), nullptr); |
631 | llvm::transform(Range: FinalCommandLine, d_first: FinalCCommandLine.begin(), |
632 | F: [](const std::string &Str) { return Str.c_str(); }); |
633 | |
634 | auto DiagOpts = CreateAndPopulateDiagOpts(Argv: FinalCCommandLine); |
635 | sanitizeDiagOpts(DiagOpts&: *DiagOpts); |
636 | IntrusiveRefCntPtr<DiagnosticsEngine> Diags = |
637 | CompilerInstance::createDiagnostics(Opts: DiagOpts.release(), Client: &DC, |
638 | /*ShouldOwnClient=*/false); |
639 | |
640 | // Although `Diagnostics` are used only for command-line parsing, the |
641 | // custom `DiagConsumer` might expect a `SourceManager` to be present. |
642 | SourceManager SrcMgr(*Diags, *FileMgr); |
643 | Diags->setSourceManager(&SrcMgr); |
644 | // DisableFree is modified by Tooling for running |
645 | // in-process; preserve the original value, which is |
646 | // always true for a driver invocation. |
647 | bool DisableFree = true; |
648 | DependencyScanningAction Action(WorkingDirectory, Consumer, Controller, DepFS, |
649 | Format, OptimizeArgs, EagerLoadModules, |
650 | DisableFree, ModuleName); |
651 | |
652 | bool Success = false; |
653 | if (FinalCommandLine[1] == "-cc1" ) { |
654 | Success = createAndRunToolInvocation(CommandLine: FinalCommandLine, Action, FM&: *FileMgr, |
655 | PCHContainerOps, Diags&: *Diags, Consumer); |
656 | } else { |
657 | Success = forEachDriverJob( |
658 | ArgStrs: FinalCommandLine, Diags&: *Diags, FM&: *FileMgr, Callback: [&](const driver::Command &Cmd) { |
659 | if (StringRef(Cmd.getCreator().getName()) != "clang" ) { |
660 | // Non-clang command. Just pass through to the dependency |
661 | // consumer. |
662 | Consumer.handleBuildCommand( |
663 | Cmd: {.Executable: Cmd.getExecutable(), |
664 | .Arguments: {Cmd.getArguments().begin(), Cmd.getArguments().end()}}); |
665 | return true; |
666 | } |
667 | |
668 | // Insert -cc1 comand line options into Argv |
669 | std::vector<std::string> Argv; |
670 | Argv.push_back(x: Cmd.getExecutable()); |
671 | Argv.insert(position: Argv.end(), first: Cmd.getArguments().begin(), |
672 | last: Cmd.getArguments().end()); |
673 | |
674 | // Create an invocation that uses the underlying file |
675 | // system to ensure that any file system requests that |
676 | // are made by the driver do not go through the |
677 | // dependency scanning filesystem. |
678 | return createAndRunToolInvocation(CommandLine: std::move(Argv), Action, FM&: *FileMgr, |
679 | PCHContainerOps, Diags&: *Diags, Consumer); |
680 | }); |
681 | } |
682 | |
683 | if (Success && !Action.hasScanned()) |
684 | Diags->Report(DiagID: diag::err_fe_expected_compiler_job) |
685 | << llvm::join(R: FinalCommandLine, Separator: " " ); |
686 | return Success && Action.hasScanned(); |
687 | } |
688 | |
689 | DependencyActionController::~DependencyActionController() {} |
690 | |