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 /// Prints out all of the gathered dependencies into a string. 25 class DependencyPrinter : public DependencyFileGenerator { 26 public: 27 DependencyPrinter(std::unique_ptr<DependencyOutputOptions> Opts, 28 std::string &S) 29 : DependencyFileGenerator(*Opts), Opts(std::move(Opts)), S(S) {} 30 31 void finishedMainFile(DiagnosticsEngine &Diags) override { 32 llvm::raw_string_ostream OS(S); 33 outputDependencyFile(OS); 34 } 35 36 private: 37 std::unique_ptr<DependencyOutputOptions> Opts; 38 std::string &S; 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, std::string &DependencyFileContents, 69 llvm::IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS) 70 : WorkingDirectory(WorkingDirectory), 71 DependencyFileContents(DependencyFileContents), 72 DepFS(std::move(DepFS)) {} 73 74 bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation, 75 FileManager *FileMgr, 76 std::shared_ptr<PCHContainerOperations> PCHContainerOps, 77 DiagnosticConsumer *DiagConsumer) override { 78 // Create a compiler instance to handle the actual work. 79 CompilerInstance Compiler(std::move(PCHContainerOps)); 80 Compiler.setInvocation(std::move(Invocation)); 81 82 // Don't print 'X warnings and Y errors generated'. 83 Compiler.getDiagnosticOpts().ShowCarets = false; 84 // Create the compiler's actual diagnostics engine. 85 Compiler.createDiagnostics(DiagConsumer, /*ShouldOwnClient=*/false); 86 if (!Compiler.hasDiagnostics()) 87 return false; 88 89 // Use the dependency scanning optimized file system if we can. 90 if (DepFS) { 91 // FIXME: Purge the symlink entries from the stat cache in the FM. 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 108 FileMgr->getFileSystemOpts().WorkingDir = WorkingDirectory; 109 Compiler.setFileManager(FileMgr); 110 Compiler.createSourceManager(*FileMgr); 111 112 // Create the dependency collector that will collect the produced 113 // dependencies. 114 // 115 // This also moves the existing dependency output options from the 116 // invocation to the collector. The options in the invocation are reset, 117 // which ensures that the compiler won't create new dependency collectors, 118 // and thus won't write out the extra '.d' files to disk. 119 auto Opts = std::make_unique<DependencyOutputOptions>( 120 std::move(Compiler.getInvocation().getDependencyOutputOpts())); 121 // We need at least one -MT equivalent for the generator to work. 122 if (Opts->Targets.empty()) 123 Opts->Targets = {"clang-scan-deps dependency"}; 124 Compiler.addDependencyCollector(std::make_shared<DependencyPrinter>( 125 std::move(Opts), DependencyFileContents)); 126 127 auto Action = std::make_unique<PreprocessOnlyAction>(); 128 const bool Result = Compiler.ExecuteAction(*Action); 129 if (!DepFS) 130 FileMgr->clearStatCache(); 131 return Result; 132 } 133 134 private: 135 StringRef WorkingDirectory; 136 /// The dependency file will be written to this string. 137 std::string &DependencyFileContents; 138 llvm::IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS; 139 }; 140 141 } // end anonymous namespace 142 143 DependencyScanningWorker::DependencyScanningWorker( 144 DependencyScanningService &Service) { 145 DiagOpts = new DiagnosticOptions(); 146 PCHContainerOps = std::make_shared<PCHContainerOperations>(); 147 RealFS = new ProxyFileSystemWithoutChdir(llvm::vfs::getRealFileSystem()); 148 if (Service.getMode() == ScanningMode::MinimizedSourcePreprocessing) 149 DepFS = new DependencyScanningWorkerFilesystem(Service.getSharedCache(), 150 RealFS); 151 } 152 153 llvm::Expected<std::string> 154 DependencyScanningWorker::getDependencyFile(const std::string &Input, 155 StringRef WorkingDirectory, 156 const CompilationDatabase &CDB) { 157 // Capture the emitted diagnostics and report them to the client 158 // in the case of a failure. 159 std::string DiagnosticOutput; 160 llvm::raw_string_ostream DiagnosticsOS(DiagnosticOutput); 161 TextDiagnosticPrinter DiagPrinter(DiagnosticsOS, DiagOpts.get()); 162 163 RealFS->setCurrentWorkingDirectory(WorkingDirectory); 164 /// Create the tool that uses the underlying file system to ensure that any 165 /// file system requests that are made by the driver do not go through the 166 /// dependency scanning filesystem. 167 tooling::ClangTool Tool(CDB, Input, PCHContainerOps, RealFS); 168 Tool.clearArgumentsAdjusters(); 169 Tool.setRestoreWorkingDir(false); 170 Tool.setPrintErrorMessage(false); 171 Tool.setDiagnosticConsumer(&DiagPrinter); 172 std::string Output; 173 DependencyScanningAction Action(WorkingDirectory, Output, DepFS); 174 if (Tool.run(&Action)) { 175 return llvm::make_error<llvm::StringError>(DiagnosticsOS.str(), 176 llvm::inconvertibleErrorCode()); 177 } 178 return Output; 179 } 180