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