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