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/Frontend/CompilerInstance.h"
11 #include "clang/Frontend/CompilerInvocation.h"
12 #include "clang/Frontend/FrontendActions.h"
13 #include "clang/Frontend/TextDiagnosticPrinter.h"
14 #include "clang/Frontend/Utils.h"
15 #include "clang/Lex/PreprocessorOptions.h"
16 #include "clang/Tooling/DependencyScanning/DependencyScanningService.h"
17 #include "clang/Tooling/DependencyScanning/ModuleDepCollector.h"
18 #include "clang/Tooling/Tooling.h"
19 
20 using namespace clang;
21 using namespace tooling;
22 using namespace dependencies;
23 
24 namespace {
25 
26 /// Forwards the gatherered dependencies to the consumer.
27 class DependencyConsumerForwarder : public DependencyFileGenerator {
28 public:
29   DependencyConsumerForwarder(std::unique_ptr<DependencyOutputOptions> Opts,
30                               DependencyConsumer &C)
31       : DependencyFileGenerator(*Opts), Opts(std::move(Opts)), C(C) {}
32 
33   void finishedMainFile(DiagnosticsEngine &Diags) override {
34     llvm::SmallString<256> CanonPath;
35     for (const auto &File : getDependencies()) {
36       CanonPath = File;
37       llvm::sys::path::remove_dots(CanonPath, /*remove_dot_dot=*/true);
38       C.handleFileDependency(*Opts, CanonPath);
39     }
40   }
41 
42 private:
43   std::unique_ptr<DependencyOutputOptions> Opts;
44   DependencyConsumer &C;
45 };
46 
47 /// A proxy file system that doesn't call `chdir` when changing the working
48 /// directory of a clang tool.
49 class ProxyFileSystemWithoutChdir : public llvm::vfs::ProxyFileSystem {
50 public:
51   ProxyFileSystemWithoutChdir(
52       llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
53       : ProxyFileSystem(std::move(FS)) {}
54 
55   llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const override {
56     assert(!CWD.empty() && "empty CWD");
57     return CWD;
58   }
59 
60   std::error_code setCurrentWorkingDirectory(const Twine &Path) override {
61     CWD = Path.str();
62     return {};
63   }
64 
65 private:
66   std::string CWD;
67 };
68 
69 /// A clang tool that runs the preprocessor in a mode that's optimized for
70 /// dependency scanning for the given compiler invocation.
71 class DependencyScanningAction : public tooling::ToolAction {
72 public:
73   DependencyScanningAction(
74       StringRef WorkingDirectory, DependencyConsumer &Consumer,
75       llvm::IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS,
76       ExcludedPreprocessorDirectiveSkipMapping *PPSkipMappings,
77       ScanningOutputFormat Format)
78       : WorkingDirectory(WorkingDirectory), Consumer(Consumer),
79         DepFS(std::move(DepFS)), PPSkipMappings(PPSkipMappings),
80         Format(Format) {}
81 
82   bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation,
83                      FileManager *FileMgr,
84                      std::shared_ptr<PCHContainerOperations> PCHContainerOps,
85                      DiagnosticConsumer *DiagConsumer) override {
86     // Create a compiler instance to handle the actual work.
87     CompilerInstance Compiler(std::move(PCHContainerOps));
88     Compiler.setInvocation(std::move(Invocation));
89 
90     // Don't print 'X warnings and Y errors generated'.
91     Compiler.getDiagnosticOpts().ShowCarets = false;
92     // Create the compiler's actual diagnostics engine.
93     Compiler.createDiagnostics(DiagConsumer, /*ShouldOwnClient=*/false);
94     if (!Compiler.hasDiagnostics())
95       return false;
96 
97     // Use the dependency scanning optimized file system if we can.
98     if (DepFS) {
99       const CompilerInvocation &CI = Compiler.getInvocation();
100       // Add any filenames that were explicity passed in the build settings and
101       // that might be opened, as we want to ensure we don't run source
102       // minimization on them.
103       DepFS->IgnoredFiles.clear();
104       for (const auto &Entry : CI.getHeaderSearchOpts().UserEntries)
105         DepFS->IgnoredFiles.insert(Entry.Path);
106       for (const auto &Entry : CI.getHeaderSearchOpts().VFSOverlayFiles)
107         DepFS->IgnoredFiles.insert(Entry);
108 
109       // Support for virtual file system overlays on top of the caching
110       // filesystem.
111       FileMgr->setVirtualFileSystem(createVFSFromCompilerInvocation(
112           CI, Compiler.getDiagnostics(), DepFS));
113 
114       // Pass the skip mappings which should speed up excluded conditional block
115       // skipping in the preprocessor.
116       if (PPSkipMappings)
117         Compiler.getPreprocessorOpts()
118             .ExcludedConditionalDirectiveSkipMappings = PPSkipMappings;
119     }
120 
121     FileMgr->getFileSystemOpts().WorkingDir = std::string(WorkingDirectory);
122     Compiler.setFileManager(FileMgr);
123     Compiler.createSourceManager(*FileMgr);
124 
125     // Create the dependency collector that will collect the produced
126     // dependencies.
127     //
128     // This also moves the existing dependency output options from the
129     // invocation to the collector. The options in the invocation are reset,
130     // which ensures that the compiler won't create new dependency collectors,
131     // and thus won't write out the extra '.d' files to disk.
132     auto Opts = std::make_unique<DependencyOutputOptions>(
133         std::move(Compiler.getInvocation().getDependencyOutputOpts()));
134     // We need at least one -MT equivalent for the generator to work.
135     if (Opts->Targets.empty())
136       Opts->Targets = {"clang-scan-deps dependency"};
137 
138     switch (Format) {
139     case ScanningOutputFormat::Make:
140       Compiler.addDependencyCollector(
141           std::make_shared<DependencyConsumerForwarder>(std::move(Opts),
142                                                         Consumer));
143       break;
144     case ScanningOutputFormat::Full:
145       Compiler.addDependencyCollector(std::make_shared<ModuleDepCollector>(
146           std::move(Opts), Compiler, Consumer));
147       break;
148     }
149 
150     // Consider different header search and diagnostic options to create
151     // different modules. This avoids the unsound aliasing of module PCMs.
152     //
153     // TODO: Implement diagnostic bucketing and header search pruning to reduce
154     // the impact of strict context hashing.
155     Compiler.getHeaderSearchOpts().ModulesStrictContextHash = true;
156 
157     auto Action = std::make_unique<PreprocessOnlyAction>();
158     const bool Result = Compiler.ExecuteAction(*Action);
159     if (!DepFS)
160       FileMgr->clearStatCache();
161     return Result;
162   }
163 
164 private:
165   StringRef WorkingDirectory;
166   DependencyConsumer &Consumer;
167   llvm::IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS;
168   ExcludedPreprocessorDirectiveSkipMapping *PPSkipMappings;
169   ScanningOutputFormat Format;
170 };
171 
172 } // end anonymous namespace
173 
174 DependencyScanningWorker::DependencyScanningWorker(
175     DependencyScanningService &Service)
176     : Format(Service.getFormat()) {
177   DiagOpts = new DiagnosticOptions();
178   PCHContainerOps = std::make_shared<PCHContainerOperations>();
179   RealFS = new ProxyFileSystemWithoutChdir(llvm::vfs::getRealFileSystem());
180   if (Service.canSkipExcludedPPRanges())
181     PPSkipMappings =
182         std::make_unique<ExcludedPreprocessorDirectiveSkipMapping>();
183   if (Service.getMode() == ScanningMode::MinimizedSourcePreprocessing)
184     DepFS = new DependencyScanningWorkerFilesystem(
185         Service.getSharedCache(), RealFS, PPSkipMappings.get());
186   if (Service.canReuseFileManager())
187     Files = new FileManager(FileSystemOptions(), RealFS);
188 }
189 
190 static llvm::Error runWithDiags(
191     DiagnosticOptions *DiagOpts,
192     llvm::function_ref<bool(DiagnosticConsumer &DC)> BodyShouldSucceed) {
193   // Capture the emitted diagnostics and report them to the client
194   // in the case of a failure.
195   std::string DiagnosticOutput;
196   llvm::raw_string_ostream DiagnosticsOS(DiagnosticOutput);
197   TextDiagnosticPrinter DiagPrinter(DiagnosticsOS, DiagOpts);
198 
199   if (BodyShouldSucceed(DiagPrinter))
200     return llvm::Error::success();
201   return llvm::make_error<llvm::StringError>(DiagnosticsOS.str(),
202                                              llvm::inconvertibleErrorCode());
203 }
204 
205 llvm::Error DependencyScanningWorker::computeDependencies(
206     const std::string &Input, StringRef WorkingDirectory,
207     const CompilationDatabase &CDB, DependencyConsumer &Consumer) {
208   RealFS->setCurrentWorkingDirectory(WorkingDirectory);
209   return runWithDiags(DiagOpts.get(), [&](DiagnosticConsumer &DC) {
210     /// Create the tool that uses the underlying file system to ensure that any
211     /// file system requests that are made by the driver do not go through the
212     /// dependency scanning filesystem.
213     tooling::ClangTool Tool(CDB, Input, PCHContainerOps, RealFS, Files);
214     Tool.clearArgumentsAdjusters();
215     Tool.setRestoreWorkingDir(false);
216     Tool.setPrintErrorMessage(false);
217     Tool.setDiagnosticConsumer(&DC);
218     DependencyScanningAction Action(WorkingDirectory, Consumer, DepFS,
219                                     PPSkipMappings.get(), Format);
220     return !Tool.run(&Action);
221   });
222 }
223