• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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