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