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/CodeGen/ObjectFilePCHContainerOperations.h"
11 #include "clang/Frontend/CompilerInstance.h"
12 #include "clang/Frontend/CompilerInvocation.h"
13 #include "clang/Frontend/FrontendActions.h"
14 #include "clang/Frontend/TextDiagnosticPrinter.h"
15 #include "clang/Frontend/Utils.h"
16 #include "clang/Lex/PreprocessorOptions.h"
17 #include "clang/Tooling/DependencyScanning/DependencyScanningService.h"
18 #include "clang/Tooling/DependencyScanning/ModuleDepCollector.h"
19 #include "clang/Tooling/Tooling.h"
20
21 using namespace clang;
22 using namespace tooling;
23 using namespace dependencies;
24
25 namespace {
26
27 /// Forwards the gatherered dependencies to the consumer.
28 class DependencyConsumerForwarder : public DependencyFileGenerator {
29 public:
DependencyConsumerForwarder(std::unique_ptr<DependencyOutputOptions> Opts,DependencyConsumer & C)30 DependencyConsumerForwarder(std::unique_ptr<DependencyOutputOptions> Opts,
31 DependencyConsumer &C)
32 : DependencyFileGenerator(*Opts), Opts(std::move(Opts)), C(C) {}
33
finishedMainFile(DiagnosticsEngine & Diags)34 void finishedMainFile(DiagnosticsEngine &Diags) override {
35 C.handleDependencyOutputOpts(*Opts);
36 llvm::SmallString<256> CanonPath;
37 for (const auto &File : getDependencies()) {
38 CanonPath = File;
39 llvm::sys::path::remove_dots(CanonPath, /*remove_dot_dot=*/true);
40 C.handleFileDependency(CanonPath);
41 }
42 }
43
44 private:
45 std::unique_ptr<DependencyOutputOptions> Opts;
46 DependencyConsumer &C;
47 };
48
49 /// A listener that collects the imported modules and optionally the input
50 /// files.
51 class PrebuiltModuleListener : public ASTReaderListener {
52 public:
PrebuiltModuleListener(llvm::StringMap<std::string> & PrebuiltModuleFiles,llvm::StringSet<> & InputFiles,bool VisitInputFiles)53 PrebuiltModuleListener(llvm::StringMap<std::string> &PrebuiltModuleFiles,
54 llvm::StringSet<> &InputFiles, bool VisitInputFiles)
55 : PrebuiltModuleFiles(PrebuiltModuleFiles), InputFiles(InputFiles),
56 VisitInputFiles(VisitInputFiles) {}
57
needsImportVisitation() const58 bool needsImportVisitation() const override { return true; }
needsInputFileVisitation()59 bool needsInputFileVisitation() override { return VisitInputFiles; }
needsSystemInputFileVisitation()60 bool needsSystemInputFileVisitation() override { return VisitInputFiles; }
61
visitImport(StringRef ModuleName,StringRef Filename)62 void visitImport(StringRef ModuleName, StringRef Filename) override {
63 PrebuiltModuleFiles.insert({ModuleName, Filename.str()});
64 }
65
visitInputFile(StringRef Filename,bool isSystem,bool isOverridden,bool isExplicitModule)66 bool visitInputFile(StringRef Filename, bool isSystem, bool isOverridden,
67 bool isExplicitModule) override {
68 InputFiles.insert(Filename);
69 return true;
70 }
71
72 private:
73 llvm::StringMap<std::string> &PrebuiltModuleFiles;
74 llvm::StringSet<> &InputFiles;
75 bool VisitInputFiles;
76 };
77
78 using PrebuiltModuleFilesT = decltype(HeaderSearchOptions::PrebuiltModuleFiles);
79
80 /// Visit the given prebuilt module and collect all of the modules it
81 /// transitively imports and contributing input files.
visitPrebuiltModule(StringRef PrebuiltModuleFilename,CompilerInstance & CI,PrebuiltModuleFilesT & ModuleFiles,llvm::StringSet<> & InputFiles,bool VisitInputFiles)82 static void visitPrebuiltModule(StringRef PrebuiltModuleFilename,
83 CompilerInstance &CI,
84 PrebuiltModuleFilesT &ModuleFiles,
85 llvm::StringSet<> &InputFiles,
86 bool VisitInputFiles) {
87 // Maps the names of modules that weren't yet visited to their PCM path.
88 llvm::StringMap<std::string> ModuleFilesWorklist;
89 // Contains PCM paths of all visited modules.
90 llvm::StringSet<> VisitedModuleFiles;
91
92 PrebuiltModuleListener Listener(ModuleFilesWorklist, InputFiles,
93 VisitInputFiles);
94
95 auto GatherModuleFileInfo = [&](StringRef ASTFile) {
96 ASTReader::readASTFileControlBlock(
97 ASTFile, CI.getFileManager(), CI.getPCHContainerReader(),
98 /*FindModuleFileExtensions=*/false, Listener,
99 /*ValidateDiagnosticOptions=*/false);
100 };
101
102 GatherModuleFileInfo(PrebuiltModuleFilename);
103 while (!ModuleFilesWorklist.empty()) {
104 auto WorklistItemIt = ModuleFilesWorklist.begin();
105
106 if (!VisitedModuleFiles.contains(WorklistItemIt->getValue())) {
107 VisitedModuleFiles.insert(WorklistItemIt->getValue());
108 GatherModuleFileInfo(WorklistItemIt->getValue());
109 ModuleFiles[WorklistItemIt->getKey().str()] = WorklistItemIt->getValue();
110 }
111
112 ModuleFilesWorklist.erase(WorklistItemIt);
113 }
114 }
115
116 /// Transform arbitrary file name into an object-like file name.
makeObjFileName(StringRef FileName)117 static std::string makeObjFileName(StringRef FileName) {
118 SmallString<128> ObjFileName(FileName);
119 llvm::sys::path::replace_extension(ObjFileName, "o");
120 return std::string(ObjFileName.str());
121 }
122
123 /// Deduce the dependency target based on the output file and input files.
124 static std::string
deduceDepTarget(const std::string & OutputFile,const SmallVectorImpl<FrontendInputFile> & InputFiles)125 deduceDepTarget(const std::string &OutputFile,
126 const SmallVectorImpl<FrontendInputFile> &InputFiles) {
127 if (OutputFile != "-")
128 return OutputFile;
129
130 if (InputFiles.empty() || !InputFiles.front().isFile())
131 return "clang-scan-deps\\ dependency";
132
133 return makeObjFileName(InputFiles.front().getFile());
134 }
135
136 /// A clang tool that runs the preprocessor in a mode that's optimized for
137 /// dependency scanning for the given compiler invocation.
138 class DependencyScanningAction : public tooling::ToolAction {
139 public:
DependencyScanningAction(StringRef WorkingDirectory,DependencyConsumer & Consumer,llvm::IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS,ExcludedPreprocessorDirectiveSkipMapping * PPSkipMappings,ScanningOutputFormat Format)140 DependencyScanningAction(
141 StringRef WorkingDirectory, DependencyConsumer &Consumer,
142 llvm::IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS,
143 ExcludedPreprocessorDirectiveSkipMapping *PPSkipMappings,
144 ScanningOutputFormat Format)
145 : WorkingDirectory(WorkingDirectory), Consumer(Consumer),
146 DepFS(std::move(DepFS)), PPSkipMappings(PPSkipMappings),
147 Format(Format) {}
148
runInvocation(std::shared_ptr<CompilerInvocation> Invocation,FileManager * FileMgr,std::shared_ptr<PCHContainerOperations> PCHContainerOps,DiagnosticConsumer * DiagConsumer)149 bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation,
150 FileManager *FileMgr,
151 std::shared_ptr<PCHContainerOperations> PCHContainerOps,
152 DiagnosticConsumer *DiagConsumer) override {
153 // Make a deep copy of the original Clang invocation.
154 CompilerInvocation OriginalInvocation(*Invocation);
155
156 // Create a compiler instance to handle the actual work.
157 CompilerInstance Compiler(std::move(PCHContainerOps));
158 Compiler.setInvocation(std::move(Invocation));
159
160 // Don't print 'X warnings and Y errors generated'.
161 Compiler.getDiagnosticOpts().ShowCarets = false;
162 // Don't write out diagnostic file.
163 Compiler.getDiagnosticOpts().DiagnosticSerializationFile.clear();
164 // Don't treat warnings as errors.
165 Compiler.getDiagnosticOpts().Warnings.push_back("no-error");
166 // Create the compiler's actual diagnostics engine.
167 Compiler.createDiagnostics(DiagConsumer, /*ShouldOwnClient=*/false);
168 if (!Compiler.hasDiagnostics())
169 return false;
170
171 Compiler.getPreprocessorOpts().AllowPCHWithDifferentModulesCachePath = true;
172
173 FileMgr->getFileSystemOpts().WorkingDir = std::string(WorkingDirectory);
174 Compiler.setFileManager(FileMgr);
175 Compiler.createSourceManager(*FileMgr);
176
177 llvm::StringSet<> PrebuiltModulesInputFiles;
178 // Store the list of prebuilt module files into header search options. This
179 // will prevent the implicit build to create duplicate modules and will
180 // force reuse of the existing prebuilt module files instead.
181 if (!Compiler.getPreprocessorOpts().ImplicitPCHInclude.empty())
182 visitPrebuiltModule(
183 Compiler.getPreprocessorOpts().ImplicitPCHInclude, Compiler,
184 Compiler.getHeaderSearchOpts().PrebuiltModuleFiles,
185 PrebuiltModulesInputFiles, /*VisitInputFiles=*/DepFS != nullptr);
186
187 // Use the dependency scanning optimized file system if requested to do so.
188 if (DepFS) {
189 const CompilerInvocation &CI = Compiler.getInvocation();
190 DepFS->clearIgnoredFiles();
191 // Ignore any files that contributed to prebuilt modules. The implicit
192 // build validates the modules by comparing the reported sizes of their
193 // inputs to the current state of the filesystem. Minimization would throw
194 // this mechanism off.
195 for (const auto &File : PrebuiltModulesInputFiles)
196 DepFS->ignoreFile(File.getKey());
197 // Add any filenames that were explicity passed in the build settings and
198 // that might be opened, as we want to ensure we don't run source
199 // minimization on them.
200 for (const auto &Entry : CI.getHeaderSearchOpts().UserEntries)
201 DepFS->ignoreFile(Entry.Path);
202 for (const auto &Entry : CI.getHeaderSearchOpts().VFSOverlayFiles)
203 DepFS->ignoreFile(Entry);
204
205 // Support for virtual file system overlays on top of the caching
206 // filesystem.
207 FileMgr->setVirtualFileSystem(createVFSFromCompilerInvocation(
208 CI, Compiler.getDiagnostics(), DepFS));
209
210 // Pass the skip mappings which should speed up excluded conditional block
211 // skipping in the preprocessor.
212 if (PPSkipMappings)
213 Compiler.getPreprocessorOpts()
214 .ExcludedConditionalDirectiveSkipMappings = PPSkipMappings;
215 }
216
217 // Create the dependency collector that will collect the produced
218 // dependencies.
219 //
220 // This also moves the existing dependency output options from the
221 // invocation to the collector. The options in the invocation are reset,
222 // which ensures that the compiler won't create new dependency collectors,
223 // and thus won't write out the extra '.d' files to disk.
224 auto Opts = std::make_unique<DependencyOutputOptions>();
225 std::swap(*Opts, Compiler.getInvocation().getDependencyOutputOpts());
226 // We need at least one -MT equivalent for the generator of make dependency
227 // files to work.
228 if (Opts->Targets.empty())
229 Opts->Targets = {deduceDepTarget(Compiler.getFrontendOpts().OutputFile,
230 Compiler.getFrontendOpts().Inputs)};
231 Opts->IncludeSystemHeaders = true;
232
233 switch (Format) {
234 case ScanningOutputFormat::Make:
235 Compiler.addDependencyCollector(
236 std::make_shared<DependencyConsumerForwarder>(std::move(Opts),
237 Consumer));
238 break;
239 case ScanningOutputFormat::Full:
240 Compiler.addDependencyCollector(std::make_shared<ModuleDepCollector>(
241 std::move(Opts), Compiler, Consumer, std::move(OriginalInvocation)));
242 break;
243 }
244
245 // Consider different header search and diagnostic options to create
246 // different modules. This avoids the unsound aliasing of module PCMs.
247 //
248 // TODO: Implement diagnostic bucketing and header search pruning to reduce
249 // the impact of strict context hashing.
250 Compiler.getHeaderSearchOpts().ModulesStrictContextHash = true;
251
252 auto Action = std::make_unique<ReadPCHAndPreprocessAction>();
253 const bool Result = Compiler.ExecuteAction(*Action);
254 if (!DepFS)
255 FileMgr->clearStatCache();
256 return Result;
257 }
258
259 private:
260 StringRef WorkingDirectory;
261 DependencyConsumer &Consumer;
262 llvm::IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS;
263 ExcludedPreprocessorDirectiveSkipMapping *PPSkipMappings;
264 ScanningOutputFormat Format;
265 };
266
267 } // end anonymous namespace
268
DependencyScanningWorker(DependencyScanningService & Service)269 DependencyScanningWorker::DependencyScanningWorker(
270 DependencyScanningService &Service)
271 : Format(Service.getFormat()) {
272 DiagOpts = new DiagnosticOptions();
273
274 PCHContainerOps = std::make_shared<PCHContainerOperations>();
275 PCHContainerOps->registerReader(
276 std::make_unique<ObjectFilePCHContainerReader>());
277 // We don't need to write object files, but the current PCH implementation
278 // requires the writer to be registered as well.
279 PCHContainerOps->registerWriter(
280 std::make_unique<ObjectFilePCHContainerWriter>());
281
282 RealFS = llvm::vfs::createPhysicalFileSystem();
283 if (Service.canSkipExcludedPPRanges())
284 PPSkipMappings =
285 std::make_unique<ExcludedPreprocessorDirectiveSkipMapping>();
286 if (Service.getMode() == ScanningMode::MinimizedSourcePreprocessing)
287 DepFS = new DependencyScanningWorkerFilesystem(
288 Service.getSharedCache(), RealFS, PPSkipMappings.get());
289 if (Service.canReuseFileManager())
290 Files = new FileManager(FileSystemOptions(), RealFS);
291 }
292
runWithDiags(DiagnosticOptions * DiagOpts,llvm::function_ref<bool (DiagnosticConsumer & DC)> BodyShouldSucceed)293 static llvm::Error runWithDiags(
294 DiagnosticOptions *DiagOpts,
295 llvm::function_ref<bool(DiagnosticConsumer &DC)> BodyShouldSucceed) {
296 // Capture the emitted diagnostics and report them to the client
297 // in the case of a failure.
298 std::string DiagnosticOutput;
299 llvm::raw_string_ostream DiagnosticsOS(DiagnosticOutput);
300 TextDiagnosticPrinter DiagPrinter(DiagnosticsOS, DiagOpts);
301
302 if (BodyShouldSucceed(DiagPrinter))
303 return llvm::Error::success();
304 return llvm::make_error<llvm::StringError>(DiagnosticsOS.str(),
305 llvm::inconvertibleErrorCode());
306 }
307
computeDependencies(const std::string & Input,StringRef WorkingDirectory,const CompilationDatabase & CDB,DependencyConsumer & Consumer)308 llvm::Error DependencyScanningWorker::computeDependencies(
309 const std::string &Input, StringRef WorkingDirectory,
310 const CompilationDatabase &CDB, DependencyConsumer &Consumer) {
311 RealFS->setCurrentWorkingDirectory(WorkingDirectory);
312 return runWithDiags(DiagOpts.get(), [&](DiagnosticConsumer &DC) {
313 /// Create the tool that uses the underlying file system to ensure that any
314 /// file system requests that are made by the driver do not go through the
315 /// dependency scanning filesystem.
316 tooling::ClangTool Tool(CDB, Input, PCHContainerOps, RealFS, Files);
317 Tool.clearArgumentsAdjusters();
318 Tool.setRestoreWorkingDir(false);
319 Tool.setPrintErrorMessage(false);
320 Tool.setDiagnosticConsumer(&DC);
321 DependencyScanningAction Action(WorkingDirectory, Consumer, DepFS,
322 PPSkipMappings.get(), Format);
323 return !Tool.run(&Action);
324 });
325 }
326