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