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