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(diag::warn_pch_vfsoverlay_mismatch); |
72 | auto VFSNote = [&](int Type, ArrayRef<std::string> VFSOverlays) { |
73 | if (VFSOverlays.empty()) { |
74 | Diags->Report(diag::note_pch_vfsoverlay_empty) << Type; |
75 | } else { |
76 | std::string Files = llvm::join(R&: VFSOverlays, Separator: "\n" ); |
77 | Diags->Report(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: [](const MacroOpt &A, const MacroOpt &B) { |
263 | return A.first < B.first; |
264 | }); |
265 | // Keep the last instance of each macro name by going in reverse |
266 | auto NewEnd = std::unique( |
267 | first: SimpleNames.rbegin(), last: SimpleNames.rend(), |
268 | binary_pred: [](const MacroOpt &A, const MacroOpt &B) { return A.first == B.first; }); |
269 | SimpleNames.erase(first: SimpleNames.begin(), last: NewEnd.base()); |
270 | |
271 | // Apply permutation. |
272 | decltype(PPOpts.Macros) NewMacros; |
273 | NewMacros.reserve(n: SimpleNames.size()); |
274 | for (std::size_t I = 0, E = SimpleNames.size(); I != E; ++I) { |
275 | std::size_t OriginalIndex = SimpleNames[I].second; |
276 | // We still emit undefines here as they may be undefining a predefined macro |
277 | NewMacros.push_back(x: std::move(PPOpts.Macros[OriginalIndex])); |
278 | } |
279 | std::swap(x&: PPOpts.Macros, y&: NewMacros); |
280 | } |
281 | |
282 | /// A clang tool that runs the preprocessor in a mode that's optimized for |
283 | /// dependency scanning for the given compiler invocation. |
284 | class DependencyScanningAction : public tooling::ToolAction { |
285 | public: |
286 | DependencyScanningAction( |
287 | StringRef WorkingDirectory, DependencyConsumer &Consumer, |
288 | DependencyActionController &Controller, |
289 | llvm::IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS, |
290 | ScanningOutputFormat Format, ScanningOptimizations OptimizeArgs, |
291 | bool EagerLoadModules, bool DisableFree, |
292 | std::optional<StringRef> ModuleName = std::nullopt) |
293 | : WorkingDirectory(WorkingDirectory), Consumer(Consumer), |
294 | Controller(Controller), DepFS(std::move(DepFS)), Format(Format), |
295 | OptimizeArgs(OptimizeArgs), EagerLoadModules(EagerLoadModules), |
296 | DisableFree(DisableFree), ModuleName(ModuleName) {} |
297 | |
298 | bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation, |
299 | FileManager *DriverFileMgr, |
300 | std::shared_ptr<PCHContainerOperations> PCHContainerOps, |
301 | DiagnosticConsumer *DiagConsumer) override { |
302 | // Make a deep copy of the original Clang invocation. |
303 | CompilerInvocation OriginalInvocation(*Invocation); |
304 | // Restore the value of DisableFree, which may be modified by Tooling. |
305 | OriginalInvocation.getFrontendOpts().DisableFree = DisableFree; |
306 | if (any(Val: OptimizeArgs & ScanningOptimizations::Macros)) |
307 | canonicalizeDefines(PPOpts&: OriginalInvocation.getPreprocessorOpts()); |
308 | |
309 | if (Scanned) { |
310 | // Scanning runs once for the first -cc1 invocation in a chain of driver |
311 | // jobs. For any dependent jobs, reuse the scanning result and just |
312 | // update the LastCC1Arguments to correspond to the new invocation. |
313 | // FIXME: to support multi-arch builds, each arch requires a separate scan |
314 | setLastCC1Arguments(std::move(OriginalInvocation)); |
315 | return true; |
316 | } |
317 | |
318 | Scanned = true; |
319 | |
320 | // Create a compiler instance to handle the actual work. |
321 | ScanInstanceStorage.emplace(args: std::move(PCHContainerOps)); |
322 | CompilerInstance &ScanInstance = *ScanInstanceStorage; |
323 | ScanInstance.setInvocation(std::move(Invocation)); |
324 | |
325 | // Create the compiler's actual diagnostics engine. |
326 | sanitizeDiagOpts(DiagOpts&: ScanInstance.getDiagnosticOpts()); |
327 | ScanInstance.createDiagnostics(Client: DiagConsumer, /*ShouldOwnClient=*/false); |
328 | if (!ScanInstance.hasDiagnostics()) |
329 | return false; |
330 | |
331 | // Some DiagnosticConsumers require that finish() is called. |
332 | auto DiagConsumerFinisher = |
333 | llvm::make_scope_exit(F: [DiagConsumer]() { DiagConsumer->finish(); }); |
334 | |
335 | ScanInstance.getPreprocessorOpts().AllowPCHWithDifferentModulesCachePath = |
336 | true; |
337 | |
338 | ScanInstance.getFrontendOpts().GenerateGlobalModuleIndex = false; |
339 | ScanInstance.getFrontendOpts().UseGlobalModuleIndex = false; |
340 | ScanInstance.getFrontendOpts().ModulesShareFileManager = false; |
341 | ScanInstance.getHeaderSearchOpts().ModuleFormat = "raw" ; |
342 | ScanInstance.getHeaderSearchOpts().ModulesIncludeVFSUsage = |
343 | any(Val: OptimizeArgs & ScanningOptimizations::VFS); |
344 | |
345 | // Support for virtual file system overlays. |
346 | auto FS = createVFSFromCompilerInvocation( |
347 | CI: ScanInstance.getInvocation(), Diags&: ScanInstance.getDiagnostics(), |
348 | BaseFS: DriverFileMgr->getVirtualFileSystemPtr()); |
349 | |
350 | // Create a new FileManager to match the invocation's FileSystemOptions. |
351 | auto *FileMgr = ScanInstance.createFileManager(VFS: FS); |
352 | ScanInstance.createSourceManager(FileMgr&: *FileMgr); |
353 | |
354 | // Store the list of prebuilt module files into header search options. This |
355 | // will prevent the implicit build to create duplicate modules and will |
356 | // force reuse of the existing prebuilt module files instead. |
357 | PrebuiltModuleVFSMapT PrebuiltModuleVFSMap; |
358 | if (!ScanInstance.getPreprocessorOpts().ImplicitPCHInclude.empty()) |
359 | if (visitPrebuiltModule( |
360 | PrebuiltModuleFilename: ScanInstance.getPreprocessorOpts().ImplicitPCHInclude, |
361 | CI&: ScanInstance, |
362 | ModuleFiles&: ScanInstance.getHeaderSearchOpts().PrebuiltModuleFiles, |
363 | PrebuiltModuleVFSMap, Diags&: ScanInstance.getDiagnostics())) |
364 | return false; |
365 | |
366 | // Use the dependency scanning optimized file system if requested to do so. |
367 | if (DepFS) |
368 | ScanInstance.getPreprocessorOpts().DependencyDirectivesForFile = |
369 | [LocalDepFS = DepFS](FileEntryRef File) |
370 | -> std::optional<ArrayRef<dependency_directives_scan::Directive>> { |
371 | if (llvm::ErrorOr<EntryRef> Entry = |
372 | LocalDepFS->getOrCreateFileSystemEntry(Filename: File.getName())) |
373 | if (LocalDepFS->ensureDirectiveTokensArePopulated(Entry: *Entry)) |
374 | return Entry->getDirectiveTokens(); |
375 | return std::nullopt; |
376 | }; |
377 | |
378 | // Create the dependency collector that will collect the produced |
379 | // dependencies. |
380 | // |
381 | // This also moves the existing dependency output options from the |
382 | // invocation to the collector. The options in the invocation are reset, |
383 | // which ensures that the compiler won't create new dependency collectors, |
384 | // and thus won't write out the extra '.d' files to disk. |
385 | auto Opts = std::make_unique<DependencyOutputOptions>(); |
386 | std::swap(a&: *Opts, b&: ScanInstance.getInvocation().getDependencyOutputOpts()); |
387 | // We need at least one -MT equivalent for the generator of make dependency |
388 | // files to work. |
389 | if (Opts->Targets.empty()) |
390 | Opts->Targets = { |
391 | deduceDepTarget(OutputFile: ScanInstance.getFrontendOpts().OutputFile, |
392 | InputFiles: ScanInstance.getFrontendOpts().Inputs)}; |
393 | Opts->IncludeSystemHeaders = true; |
394 | |
395 | switch (Format) { |
396 | case ScanningOutputFormat::Make: |
397 | ScanInstance.addDependencyCollector( |
398 | Listener: std::make_shared<DependencyConsumerForwarder>( |
399 | args: std::move(Opts), args&: WorkingDirectory, args&: Consumer)); |
400 | break; |
401 | case ScanningOutputFormat::P1689: |
402 | case ScanningOutputFormat::Full: |
403 | MDC = std::make_shared<ModuleDepCollector>( |
404 | args: std::move(Opts), args&: ScanInstance, args&: Consumer, args&: Controller, |
405 | args&: OriginalInvocation, args: std::move(PrebuiltModuleVFSMap), args&: OptimizeArgs, |
406 | args&: EagerLoadModules, args: Format == ScanningOutputFormat::P1689); |
407 | ScanInstance.addDependencyCollector(Listener: MDC); |
408 | break; |
409 | } |
410 | |
411 | // Consider different header search and diagnostic options to create |
412 | // different modules. This avoids the unsound aliasing of module PCMs. |
413 | // |
414 | // TODO: Implement diagnostic bucketing to reduce the impact of strict |
415 | // context hashing. |
416 | ScanInstance.getHeaderSearchOpts().ModulesStrictContextHash = true; |
417 | ScanInstance.getHeaderSearchOpts().ModulesSkipDiagnosticOptions = true; |
418 | ScanInstance.getHeaderSearchOpts().ModulesSkipHeaderSearchPaths = true; |
419 | ScanInstance.getHeaderSearchOpts().ModulesSkipPragmaDiagnosticMappings = |
420 | true; |
421 | |
422 | // Avoid some checks and module map parsing when loading PCM files. |
423 | ScanInstance.getPreprocessorOpts().ModulesCheckRelocated = false; |
424 | |
425 | std::unique_ptr<FrontendAction> Action; |
426 | |
427 | if (ModuleName) |
428 | Action = std::make_unique<GetDependenciesByModuleNameAction>(args&: *ModuleName); |
429 | else |
430 | Action = std::make_unique<ReadPCHAndPreprocessAction>(); |
431 | |
432 | if (ScanInstance.getDiagnostics().hasErrorOccurred()) |
433 | return false; |
434 | |
435 | // Each action is responsible for calling finish. |
436 | DiagConsumerFinisher.release(); |
437 | const bool Result = ScanInstance.ExecuteAction(Act&: *Action); |
438 | |
439 | if (Result) |
440 | setLastCC1Arguments(std::move(OriginalInvocation)); |
441 | |
442 | return Result; |
443 | } |
444 | |
445 | bool hasScanned() const { return Scanned; } |
446 | |
447 | /// Take the cc1 arguments corresponding to the most recent invocation used |
448 | /// with this action. Any modifications implied by the discovered dependencies |
449 | /// will have already been applied. |
450 | std::vector<std::string> takeLastCC1Arguments() { |
451 | std::vector<std::string> Result; |
452 | std::swap(x&: Result, y&: LastCC1Arguments); // Reset LastCC1Arguments to empty. |
453 | return Result; |
454 | } |
455 | |
456 | private: |
457 | void setLastCC1Arguments(CompilerInvocation &&CI) { |
458 | if (MDC) |
459 | MDC->applyDiscoveredDependencies(CI); |
460 | LastCC1Arguments = CI.getCC1CommandLine(); |
461 | } |
462 | |
463 | private: |
464 | StringRef WorkingDirectory; |
465 | DependencyConsumer &Consumer; |
466 | DependencyActionController &Controller; |
467 | llvm::IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS; |
468 | ScanningOutputFormat Format; |
469 | ScanningOptimizations OptimizeArgs; |
470 | bool EagerLoadModules; |
471 | bool DisableFree; |
472 | std::optional<StringRef> ModuleName; |
473 | std::optional<CompilerInstance> ScanInstanceStorage; |
474 | std::shared_ptr<ModuleDepCollector> MDC; |
475 | std::vector<std::string> LastCC1Arguments; |
476 | bool Scanned = false; |
477 | }; |
478 | |
479 | } // end anonymous namespace |
480 | |
481 | DependencyScanningWorker::DependencyScanningWorker( |
482 | DependencyScanningService &Service, |
483 | llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS) |
484 | : Format(Service.getFormat()), OptimizeArgs(Service.getOptimizeArgs()), |
485 | EagerLoadModules(Service.shouldEagerLoadModules()) { |
486 | PCHContainerOps = std::make_shared<PCHContainerOperations>(); |
487 | // We need to read object files from PCH built outside the scanner. |
488 | PCHContainerOps->registerReader( |
489 | Reader: std::make_unique<ObjectFilePCHContainerReader>()); |
490 | // The scanner itself writes only raw ast files. |
491 | PCHContainerOps->registerWriter(Writer: std::make_unique<RawPCHContainerWriter>()); |
492 | |
493 | switch (Service.getMode()) { |
494 | case ScanningMode::DependencyDirectivesScan: |
495 | DepFS = |
496 | new DependencyScanningWorkerFilesystem(Service.getSharedCache(), FS); |
497 | BaseFS = DepFS; |
498 | break; |
499 | case ScanningMode::CanonicalPreprocessing: |
500 | DepFS = nullptr; |
501 | BaseFS = FS; |
502 | break; |
503 | } |
504 | } |
505 | |
506 | llvm::Error DependencyScanningWorker::computeDependencies( |
507 | StringRef WorkingDirectory, const std::vector<std::string> &CommandLine, |
508 | DependencyConsumer &Consumer, DependencyActionController &Controller, |
509 | std::optional<StringRef> ModuleName) { |
510 | std::vector<const char *> CLI; |
511 | for (const std::string &Arg : CommandLine) |
512 | CLI.push_back(x: Arg.c_str()); |
513 | auto DiagOpts = CreateAndPopulateDiagOpts(Argv: CLI); |
514 | sanitizeDiagOpts(DiagOpts&: *DiagOpts); |
515 | |
516 | // Capture the emitted diagnostics and report them to the client |
517 | // in the case of a failure. |
518 | std::string DiagnosticOutput; |
519 | llvm::raw_string_ostream DiagnosticsOS(DiagnosticOutput); |
520 | TextDiagnosticPrinter DiagPrinter(DiagnosticsOS, DiagOpts.release()); |
521 | |
522 | if (computeDependencies(WorkingDirectory, CommandLine, DepConsumer&: Consumer, Controller, |
523 | DiagConsumer&: DiagPrinter, ModuleName)) |
524 | return llvm::Error::success(); |
525 | return llvm::make_error<llvm::StringError>(Args&: DiagnosticsOS.str(), |
526 | Args: llvm::inconvertibleErrorCode()); |
527 | } |
528 | |
529 | static bool forEachDriverJob( |
530 | ArrayRef<std::string> ArgStrs, DiagnosticsEngine &Diags, FileManager &FM, |
531 | llvm::function_ref<bool(const driver::Command &Cmd)> Callback) { |
532 | SmallVector<const char *, 256> Argv; |
533 | Argv.reserve(N: ArgStrs.size()); |
534 | for (const std::string &Arg : ArgStrs) |
535 | Argv.push_back(Elt: Arg.c_str()); |
536 | |
537 | llvm::vfs::FileSystem *FS = &FM.getVirtualFileSystem(); |
538 | |
539 | std::unique_ptr<driver::Driver> Driver = std::make_unique<driver::Driver>( |
540 | args&: Argv[0], args: llvm::sys::getDefaultTargetTriple(), args&: Diags, |
541 | args: "clang LLVM compiler" , args&: FS); |
542 | Driver->setTitle("clang_based_tool" ); |
543 | |
544 | llvm::BumpPtrAllocator Alloc; |
545 | bool CLMode = driver::IsClangCL( |
546 | DriverMode: driver::getDriverMode(ProgName: Argv[0], Args: ArrayRef(Argv).slice(N: 1))); |
547 | |
548 | if (llvm::Error E = driver::expandResponseFiles(Args&: Argv, ClangCLMode: CLMode, Alloc, FS)) { |
549 | Diags.Report(diag::err_drv_expand_response_file) |
550 | << llvm::toString(std::move(E)); |
551 | return false; |
552 | } |
553 | |
554 | const std::unique_ptr<driver::Compilation> Compilation( |
555 | Driver->BuildCompilation(Args: llvm::ArrayRef(Argv))); |
556 | if (!Compilation) |
557 | return false; |
558 | |
559 | if (Compilation->containsError()) |
560 | return false; |
561 | |
562 | for (const driver::Command &Job : Compilation->getJobs()) { |
563 | if (!Callback(Job)) |
564 | return false; |
565 | } |
566 | return true; |
567 | } |
568 | |
569 | static bool createAndRunToolInvocation( |
570 | std::vector<std::string> CommandLine, DependencyScanningAction &Action, |
571 | FileManager &FM, |
572 | std::shared_ptr<clang::PCHContainerOperations> &PCHContainerOps, |
573 | DiagnosticsEngine &Diags, DependencyConsumer &Consumer) { |
574 | |
575 | // Save executable path before providing CommandLine to ToolInvocation |
576 | std::string Executable = CommandLine[0]; |
577 | ToolInvocation Invocation(std::move(CommandLine), &Action, &FM, |
578 | PCHContainerOps); |
579 | Invocation.setDiagnosticConsumer(Diags.getClient()); |
580 | Invocation.setDiagnosticOptions(&Diags.getDiagnosticOptions()); |
581 | if (!Invocation.run()) |
582 | return false; |
583 | |
584 | std::vector<std::string> Args = Action.takeLastCC1Arguments(); |
585 | Consumer.handleBuildCommand(Cmd: {.Executable: std::move(Executable), .Arguments: std::move(Args)}); |
586 | return true; |
587 | } |
588 | |
589 | bool DependencyScanningWorker::computeDependencies( |
590 | StringRef WorkingDirectory, const std::vector<std::string> &CommandLine, |
591 | DependencyConsumer &Consumer, DependencyActionController &Controller, |
592 | DiagnosticConsumer &DC, std::optional<StringRef> ModuleName) { |
593 | // Reset what might have been modified in the previous worker invocation. |
594 | BaseFS->setCurrentWorkingDirectory(WorkingDirectory); |
595 | |
596 | std::optional<std::vector<std::string>> ModifiedCommandLine; |
597 | llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> ModifiedFS; |
598 | |
599 | // If we're scanning based on a module name alone, we don't expect the client |
600 | // to provide us with an input file. However, the driver really wants to have |
601 | // one. Let's just make it up to make the driver happy. |
602 | if (ModuleName) { |
603 | auto OverlayFS = |
604 | llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(A&: BaseFS); |
605 | auto InMemoryFS = |
606 | llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>(); |
607 | InMemoryFS->setCurrentWorkingDirectory(WorkingDirectory); |
608 | OverlayFS->pushOverlay(FS: InMemoryFS); |
609 | ModifiedFS = OverlayFS; |
610 | |
611 | SmallString<128> FakeInputPath; |
612 | // TODO: We should retry the creation if the path already exists. |
613 | llvm::sys::fs::createUniquePath(Model: *ModuleName + "-%%%%%%%%.input" , |
614 | ResultPath&: FakeInputPath, |
615 | /*MakeAbsolute=*/false); |
616 | InMemoryFS->addFile(Path: FakeInputPath, ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "" )); |
617 | |
618 | ModifiedCommandLine = CommandLine; |
619 | ModifiedCommandLine->emplace_back(args&: FakeInputPath); |
620 | } |
621 | |
622 | const std::vector<std::string> &FinalCommandLine = |
623 | ModifiedCommandLine ? *ModifiedCommandLine : CommandLine; |
624 | auto &FinalFS = ModifiedFS ? ModifiedFS : BaseFS; |
625 | |
626 | auto FileMgr = |
627 | llvm::makeIntrusiveRefCnt<FileManager>(A: FileSystemOptions{}, A&: FinalFS); |
628 | |
629 | std::vector<const char *> FinalCCommandLine(FinalCommandLine.size(), nullptr); |
630 | llvm::transform(Range: FinalCommandLine, d_first: FinalCCommandLine.begin(), |
631 | F: [](const std::string &Str) { return Str.c_str(); }); |
632 | |
633 | auto DiagOpts = CreateAndPopulateDiagOpts(Argv: FinalCCommandLine); |
634 | sanitizeDiagOpts(DiagOpts&: *DiagOpts); |
635 | IntrusiveRefCntPtr<DiagnosticsEngine> Diags = |
636 | CompilerInstance::createDiagnostics(Opts: DiagOpts.release(), Client: &DC, |
637 | /*ShouldOwnClient=*/false); |
638 | |
639 | // Although `Diagnostics` are used only for command-line parsing, the |
640 | // custom `DiagConsumer` might expect a `SourceManager` to be present. |
641 | SourceManager SrcMgr(*Diags, *FileMgr); |
642 | Diags->setSourceManager(&SrcMgr); |
643 | // DisableFree is modified by Tooling for running |
644 | // in-process; preserve the original value, which is |
645 | // always true for a driver invocation. |
646 | bool DisableFree = true; |
647 | DependencyScanningAction Action(WorkingDirectory, Consumer, Controller, DepFS, |
648 | Format, OptimizeArgs, EagerLoadModules, |
649 | DisableFree, ModuleName); |
650 | |
651 | bool Success = false; |
652 | if (FinalCommandLine[1] == "-cc1" ) { |
653 | Success = createAndRunToolInvocation(CommandLine: FinalCommandLine, Action, FM&: *FileMgr, |
654 | PCHContainerOps, Diags&: *Diags, Consumer); |
655 | } else { |
656 | Success = forEachDriverJob( |
657 | ArgStrs: FinalCommandLine, Diags&: *Diags, FM&: *FileMgr, Callback: [&](const driver::Command &Cmd) { |
658 | if (StringRef(Cmd.getCreator().getName()) != "clang" ) { |
659 | // Non-clang command. Just pass through to the dependency |
660 | // consumer. |
661 | Consumer.handleBuildCommand( |
662 | Cmd: {.Executable: Cmd.getExecutable(), |
663 | .Arguments: {Cmd.getArguments().begin(), Cmd.getArguments().end()}}); |
664 | return true; |
665 | } |
666 | |
667 | // Insert -cc1 comand line options into Argv |
668 | std::vector<std::string> Argv; |
669 | Argv.push_back(x: Cmd.getExecutable()); |
670 | Argv.insert(position: Argv.end(), first: Cmd.getArguments().begin(), |
671 | last: Cmd.getArguments().end()); |
672 | |
673 | // Create an invocation that uses the underlying file |
674 | // system to ensure that any file system requests that |
675 | // are made by the driver do not go through the |
676 | // dependency scanning filesystem. |
677 | return createAndRunToolInvocation(CommandLine: std::move(Argv), Action, FM&: *FileMgr, |
678 | PCHContainerOps, Diags&: *Diags, Consumer); |
679 | }); |
680 | } |
681 | |
682 | if (Success && !Action.hasScanned()) |
683 | Diags->Report(diag::err_fe_expected_compiler_job) |
684 | << llvm::join(FinalCommandLine, " " ); |
685 | return Success && Action.hasScanned(); |
686 | } |
687 | |
688 | DependencyActionController::~DependencyActionController() {} |
689 | |