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