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