//===- DependencyScanningWorker.cpp - clang-scan-deps worker --------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "clang/Tooling/DependencyScanning/DependencyScanningWorker.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Frontend/Utils.h" #include "clang/Tooling/Tooling.h" using namespace clang; using namespace tooling; using namespace dependencies; namespace { /// Prints out all of the gathered dependencies into a string. class DependencyPrinter : public DependencyFileGenerator { public: DependencyPrinter(std::unique_ptr Opts, std::string &S) : DependencyFileGenerator(*Opts), Opts(std::move(Opts)), S(S) {} void finishedMainFile(DiagnosticsEngine &Diags) override { llvm::raw_string_ostream OS(S); outputDependencyFile(OS); } private: std::unique_ptr Opts; std::string &S; }; /// A proxy file system that doesn't call `chdir` when changing the working /// directory of a clang tool. class ProxyFileSystemWithoutChdir : public llvm::vfs::ProxyFileSystem { public: ProxyFileSystemWithoutChdir( llvm::IntrusiveRefCntPtr FS) : ProxyFileSystem(std::move(FS)) {} llvm::ErrorOr getCurrentWorkingDirectory() const override { assert(!CWD.empty() && "empty CWD"); return CWD; } std::error_code setCurrentWorkingDirectory(const Twine &Path) override { CWD = Path.str(); return {}; } private: std::string CWD; }; /// A clang tool that runs the preprocessor in a mode that's optimized for /// dependency scanning for the given compiler invocation. class DependencyScanningAction : public tooling::ToolAction { public: DependencyScanningAction(StringRef WorkingDirectory, std::string &DependencyFileContents) : WorkingDirectory(WorkingDirectory), DependencyFileContents(DependencyFileContents) {} bool runInvocation(std::shared_ptr Invocation, FileManager *FileMgr, std::shared_ptr PCHContainerOps, DiagnosticConsumer *DiagConsumer) override { // Create a compiler instance to handle the actual work. CompilerInstance Compiler(std::move(PCHContainerOps)); Compiler.setInvocation(std::move(Invocation)); FileMgr->getFileSystemOpts().WorkingDir = WorkingDirectory; Compiler.setFileManager(FileMgr); // Don't print 'X warnings and Y errors generated'. Compiler.getDiagnosticOpts().ShowCarets = false; // Create the compiler's actual diagnostics engine. Compiler.createDiagnostics(DiagConsumer, /*ShouldOwnClient=*/false); if (!Compiler.hasDiagnostics()) return false; Compiler.createSourceManager(*FileMgr); // Create the dependency collector that will collect the produced // dependencies. // // This also moves the existing dependency output options from the // invocation to the collector. The options in the invocation are reset, // which ensures that the compiler won't create new dependency collectors, // and thus won't write out the extra '.d' files to disk. auto Opts = llvm::make_unique( std::move(Compiler.getInvocation().getDependencyOutputOpts())); // We need at least one -MT equivalent for the generator to work. if (Opts->Targets.empty()) Opts->Targets = {"clang-scan-deps dependency"}; Compiler.addDependencyCollector(std::make_shared( std::move(Opts), DependencyFileContents)); auto Action = llvm::make_unique(); const bool Result = Compiler.ExecuteAction(*Action); FileMgr->clearStatCache(); return Result; } private: StringRef WorkingDirectory; /// The dependency file will be written to this string. std::string &DependencyFileContents; }; } // end anonymous namespace DependencyScanningWorker::DependencyScanningWorker() { DiagOpts = new DiagnosticOptions(); PCHContainerOps = std::make_shared(); /// FIXME: Use the shared file system from the service for fast scanning /// mode. WorkerFS = new ProxyFileSystemWithoutChdir(llvm::vfs::getRealFileSystem()); } llvm::Expected DependencyScanningWorker::getDependencyFile(const std::string &Input, StringRef WorkingDirectory, const CompilationDatabase &CDB) { // Capture the emitted diagnostics and report them to the client // in the case of a failure. std::string DiagnosticOutput; llvm::raw_string_ostream DiagnosticsOS(DiagnosticOutput); TextDiagnosticPrinter DiagPrinter(DiagnosticsOS, DiagOpts.get()); WorkerFS->setCurrentWorkingDirectory(WorkingDirectory); tooling::ClangTool Tool(CDB, Input, PCHContainerOps, WorkerFS); Tool.clearArgumentsAdjusters(); Tool.setRestoreWorkingDir(false); Tool.setPrintErrorMessage(false); Tool.setDiagnosticConsumer(&DiagPrinter); std::string Output; DependencyScanningAction Action(WorkingDirectory, Output); if (Tool.run(&Action)) { return llvm::make_error(DiagnosticsOS.str(), llvm::inconvertibleErrorCode()); } return Output; }