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/FrontendActions.h" 12 #include "clang/Frontend/TextDiagnosticPrinter.h" 13 #include "clang/Frontend/Utils.h" 14 #include "clang/Tooling/Tooling.h" 15 16 using namespace clang; 17 using namespace tooling; 18 using namespace dependencies; 19 20 namespace { 21 22 /// Prints out all of the gathered dependencies into a string. 23 class DependencyPrinter : public DependencyFileGenerator { 24 public: 25 DependencyPrinter(std::unique_ptr<DependencyOutputOptions> Opts, 26 std::string &S) 27 : DependencyFileGenerator(*Opts), Opts(std::move(Opts)), S(S) {} 28 29 void finishedMainFile(DiagnosticsEngine &Diags) override { 30 llvm::raw_string_ostream OS(S); 31 outputDependencyFile(OS); 32 } 33 34 private: 35 std::unique_ptr<DependencyOutputOptions> Opts; 36 std::string &S; 37 }; 38 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 { 42 public: 43 ProxyFileSystemWithoutChdir( 44 llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS) 45 : ProxyFileSystem(std::move(FS)) {} 46 47 llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const override { 48 assert(!CWD.empty() && "empty CWD"); 49 return CWD; 50 } 51 52 std::error_code setCurrentWorkingDirectory(const Twine &Path) override { 53 CWD = Path.str(); 54 return {}; 55 } 56 57 private: 58 std::string CWD; 59 }; 60 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 { 64 public: 65 DependencyScanningAction(StringRef WorkingDirectory, 66 std::string &DependencyFileContents) 67 : WorkingDirectory(WorkingDirectory), 68 DependencyFileContents(DependencyFileContents) {} 69 70 bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation, 71 FileManager *FileMgr, 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); 79 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()) 85 return false; 86 87 Compiler.createSourceManager(*FileMgr); 88 89 // Create the dependency collector that will collect the produced 90 // dependencies. 91 // 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)); 103 104 auto Action = llvm::make_unique<PreprocessOnlyAction>(); 105 const bool Result = Compiler.ExecuteAction(*Action); 106 FileMgr->clearStatCache(); 107 return Result; 108 } 109 110 private: 111 StringRef WorkingDirectory; 112 /// The dependency file will be written to this string. 113 std::string &DependencyFileContents; 114 }; 115 116 } // end anonymous namespace 117 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 122 /// mode. 123 WorkerFS = new ProxyFileSystemWithoutChdir(llvm::vfs::getRealFileSystem()); 124 } 125 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()); 135 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); 142 std::string Output; 143 DependencyScanningAction Action(WorkingDirectory, Output); 144 if (Tool.run(&Action)) { 145 return llvm::make_error<llvm::StringError>(DiagnosticsOS.str(), 146 llvm::inconvertibleErrorCode()); 147 } 148 return Output; 149 } 150