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 20 using namespace clang; 21 using namespace tooling; 22 using namespace dependencies; 23 24 namespace { 25 26 /// Forwards the gatherered dependencies to the consumer. 27 class DependencyConsumerForwarder : public DependencyFileGenerator { 28 public: 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 42 private: 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. 49 class ProxyFileSystemWithoutChdir : public llvm::vfs::ProxyFileSystem { 50 public: 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 65 private: 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. 71 class DependencyScanningAction : public tooling::ToolAction { 72 public: 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 = std::string(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(std::make_shared<ModuleDepCollector>( 146 std::move(Opts), Compiler, Consumer)); 147 break; 148 } 149 150 // Consider different header search and diagnostic options to create 151 // different modules. This avoids the unsound aliasing of module PCMs. 152 // 153 // TODO: Implement diagnostic bucketing and header search pruning to reduce 154 // the impact of strict context hashing. 155 Compiler.getHeaderSearchOpts().ModulesStrictContextHash = true; 156 157 auto Action = std::make_unique<PreprocessOnlyAction>(); 158 const bool Result = Compiler.ExecuteAction(*Action); 159 if (!DepFS) 160 FileMgr->clearStatCache(); 161 return Result; 162 } 163 164 private: 165 StringRef WorkingDirectory; 166 DependencyConsumer &Consumer; 167 llvm::IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS; 168 ExcludedPreprocessorDirectiveSkipMapping *PPSkipMappings; 169 ScanningOutputFormat Format; 170 }; 171 172 } // end anonymous namespace 173 174 DependencyScanningWorker::DependencyScanningWorker( 175 DependencyScanningService &Service) 176 : Format(Service.getFormat()) { 177 DiagOpts = new DiagnosticOptions(); 178 PCHContainerOps = std::make_shared<PCHContainerOperations>(); 179 RealFS = new ProxyFileSystemWithoutChdir(llvm::vfs::getRealFileSystem()); 180 if (Service.canSkipExcludedPPRanges()) 181 PPSkipMappings = 182 std::make_unique<ExcludedPreprocessorDirectiveSkipMapping>(); 183 if (Service.getMode() == ScanningMode::MinimizedSourcePreprocessing) 184 DepFS = new DependencyScanningWorkerFilesystem( 185 Service.getSharedCache(), RealFS, PPSkipMappings.get()); 186 if (Service.canReuseFileManager()) 187 Files = new FileManager(FileSystemOptions(), RealFS); 188 } 189 190 static llvm::Error runWithDiags( 191 DiagnosticOptions *DiagOpts, 192 llvm::function_ref<bool(DiagnosticConsumer &DC)> BodyShouldSucceed) { 193 // Capture the emitted diagnostics and report them to the client 194 // in the case of a failure. 195 std::string DiagnosticOutput; 196 llvm::raw_string_ostream DiagnosticsOS(DiagnosticOutput); 197 TextDiagnosticPrinter DiagPrinter(DiagnosticsOS, DiagOpts); 198 199 if (BodyShouldSucceed(DiagPrinter)) 200 return llvm::Error::success(); 201 return llvm::make_error<llvm::StringError>(DiagnosticsOS.str(), 202 llvm::inconvertibleErrorCode()); 203 } 204 205 llvm::Error DependencyScanningWorker::computeDependencies( 206 const std::string &Input, StringRef WorkingDirectory, 207 const CompilationDatabase &CDB, DependencyConsumer &Consumer) { 208 RealFS->setCurrentWorkingDirectory(WorkingDirectory); 209 return runWithDiags(DiagOpts.get(), [&](DiagnosticConsumer &DC) { 210 /// Create the tool that uses the underlying file system to ensure that any 211 /// file system requests that are made by the driver do not go through the 212 /// dependency scanning filesystem. 213 tooling::ClangTool Tool(CDB, Input, PCHContainerOps, RealFS, Files); 214 Tool.clearArgumentsAdjusters(); 215 Tool.setRestoreWorkingDir(false); 216 Tool.setPrintErrorMessage(false); 217 Tool.setDiagnosticConsumer(&DC); 218 DependencyScanningAction Action(WorkingDirectory, Consumer, DepFS, 219 PPSkipMappings.get(), Format); 220 return !Tool.run(&Action); 221 }); 222 } 223