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