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