1 //===- DependencyScanningWorker.cpp - clang-scan-deps worker --------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "clang/Tooling/DependencyScanning/DependencyScanningWorker.h"
10 #include "clang/Frontend/CompilerInstance.h"
11 #include "clang/Frontend/FrontendActions.h"
12 #include "clang/Frontend/TextDiagnosticPrinter.h"
13 #include "clang/Frontend/Utils.h"
14 #include "clang/Tooling/Tooling.h"
16 using namespace clang;
17 using namespace tooling;
18 using namespace dependencies;
22 /// Prints out all of the gathered dependencies into a string.
23 class DependencyPrinter : public DependencyFileGenerator {
25 DependencyPrinter(std::unique_ptr<DependencyOutputOptions> Opts,
27 : DependencyFileGenerator(*Opts), Opts(std::move(Opts)), S(S) {}
29 void finishedMainFile(DiagnosticsEngine &Diags) override {
30 llvm::raw_string_ostream OS(S);
31 outputDependencyFile(OS);
35 std::unique_ptr<DependencyOutputOptions> Opts;
39 /// A proxy file system that doesn't call `chdir` when changing the working
40 /// directory of a clang tool.
41 class ProxyFileSystemWithoutChdir : public llvm::vfs::ProxyFileSystem {
43 ProxyFileSystemWithoutChdir(
44 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
45 : ProxyFileSystem(std::move(FS)) {}
47 llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const override {
48 assert(!CWD.empty() && "empty CWD");
52 std::error_code setCurrentWorkingDirectory(const Twine &Path) override {
61 /// A clang tool that runs the preprocessor in a mode that's optimized for
62 /// dependency scanning for the given compiler invocation.
63 class DependencyScanningAction : public tooling::ToolAction {
65 DependencyScanningAction(StringRef WorkingDirectory,
66 std::string &DependencyFileContents)
67 : WorkingDirectory(WorkingDirectory),
68 DependencyFileContents(DependencyFileContents) {}
70 bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation,
72 std::shared_ptr<PCHContainerOperations> PCHContainerOps,
73 DiagnosticConsumer *DiagConsumer) override {
74 // Create a compiler instance to handle the actual work.
75 CompilerInstance Compiler(std::move(PCHContainerOps));
76 Compiler.setInvocation(std::move(Invocation));
77 FileMgr->getFileSystemOpts().WorkingDir = WorkingDirectory;
78 Compiler.setFileManager(FileMgr);
80 // Don't print 'X warnings and Y errors generated'.
81 Compiler.getDiagnosticOpts().ShowCarets = false;
82 // Create the compiler's actual diagnostics engine.
83 Compiler.createDiagnostics(DiagConsumer, /*ShouldOwnClient=*/false);
84 if (!Compiler.hasDiagnostics())
87 Compiler.createSourceManager(*FileMgr);
89 // Create the dependency collector that will collect the produced
92 // This also moves the existing dependency output options from the
93 // invocation to the collector. The options in the invocation are reset,
94 // which ensures that the compiler won't create new dependency collectors,
95 // and thus won't write out the extra '.d' files to disk.
96 auto Opts = llvm::make_unique<DependencyOutputOptions>(
97 std::move(Compiler.getInvocation().getDependencyOutputOpts()));
98 // We need at least one -MT equivalent for the generator to work.
99 if (Opts->Targets.empty())
100 Opts->Targets = {"clang-scan-deps dependency"};
101 Compiler.addDependencyCollector(std::make_shared<DependencyPrinter>(
102 std::move(Opts), DependencyFileContents));
104 auto Action = llvm::make_unique<PreprocessOnlyAction>();
105 const bool Result = Compiler.ExecuteAction(*Action);
106 FileMgr->clearStatCache();
111 StringRef WorkingDirectory;
112 /// The dependency file will be written to this string.
113 std::string &DependencyFileContents;
116 } // end anonymous namespace
118 DependencyScanningWorker::DependencyScanningWorker() {
119 DiagOpts = new DiagnosticOptions();
120 PCHContainerOps = std::make_shared<PCHContainerOperations>();
121 /// FIXME: Use the shared file system from the service for fast scanning
123 WorkerFS = new ProxyFileSystemWithoutChdir(llvm::vfs::getRealFileSystem());
126 llvm::Expected<std::string>
127 DependencyScanningWorker::getDependencyFile(const std::string &Input,
128 StringRef WorkingDirectory,
129 const CompilationDatabase &CDB) {
130 // Capture the emitted diagnostics and report them to the client
131 // in the case of a failure.
132 std::string DiagnosticOutput;
133 llvm::raw_string_ostream DiagnosticsOS(DiagnosticOutput);
134 TextDiagnosticPrinter DiagPrinter(DiagnosticsOS, DiagOpts.get());
136 WorkerFS->setCurrentWorkingDirectory(WorkingDirectory);
137 tooling::ClangTool Tool(CDB, Input, PCHContainerOps, WorkerFS);
138 Tool.clearArgumentsAdjusters();
139 Tool.setRestoreWorkingDir(false);
140 Tool.setPrintErrorMessage(false);
141 Tool.setDiagnosticConsumer(&DiagPrinter);
143 DependencyScanningAction Action(WorkingDirectory, Output);
144 if (Tool.run(&Action)) {
145 return llvm::make_error<llvm::StringError>(DiagnosticsOS.str(),
146 llvm::inconvertibleErrorCode());