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