forked from OSchip/llvm-project
				
			
		
			
				
	
	
		
			223 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			223 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			C++
		
	
	
	
| //===- 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/CompilerInvocation.h"
 | |
| #include "clang/Frontend/FrontendActions.h"
 | |
| #include "clang/Frontend/TextDiagnosticPrinter.h"
 | |
| #include "clang/Frontend/Utils.h"
 | |
| #include "clang/Lex/PreprocessorOptions.h"
 | |
| #include "clang/Tooling/DependencyScanning/DependencyScanningService.h"
 | |
| #include "clang/Tooling/DependencyScanning/ModuleDepCollector.h"
 | |
| #include "clang/Tooling/Tooling.h"
 | |
| 
 | |
| using namespace clang;
 | |
| using namespace tooling;
 | |
| using namespace dependencies;
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| /// Forwards the gatherered dependencies to the consumer.
 | |
| class DependencyConsumerForwarder : public DependencyFileGenerator {
 | |
| public:
 | |
|   DependencyConsumerForwarder(std::unique_ptr<DependencyOutputOptions> Opts,
 | |
|                               DependencyConsumer &C)
 | |
|       : DependencyFileGenerator(*Opts), Opts(std::move(Opts)), C(C) {}
 | |
| 
 | |
|   void finishedMainFile(DiagnosticsEngine &Diags) override {
 | |
|     llvm::SmallString<256> CanonPath;
 | |
|     for (const auto &File : getDependencies()) {
 | |
|       CanonPath = File;
 | |
|       llvm::sys::path::remove_dots(CanonPath, /*remove_dot_dot=*/true);
 | |
|       C.handleFileDependency(*Opts, CanonPath);
 | |
|     }
 | |
|   }
 | |
| 
 | |
| private:
 | |
|   std::unique_ptr<DependencyOutputOptions> Opts;
 | |
|   DependencyConsumer &C;
 | |
| };
 | |
| 
 | |
| /// 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<llvm::vfs::FileSystem> FS)
 | |
|       : ProxyFileSystem(std::move(FS)) {}
 | |
| 
 | |
|   llvm::ErrorOr<std::string> 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, DependencyConsumer &Consumer,
 | |
|       llvm::IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS,
 | |
|       ExcludedPreprocessorDirectiveSkipMapping *PPSkipMappings,
 | |
|       ScanningOutputFormat Format)
 | |
|       : WorkingDirectory(WorkingDirectory), Consumer(Consumer),
 | |
|         DepFS(std::move(DepFS)), PPSkipMappings(PPSkipMappings),
 | |
|         Format(Format) {}
 | |
| 
 | |
|   bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation,
 | |
|                      FileManager *FileMgr,
 | |
|                      std::shared_ptr<PCHContainerOperations> PCHContainerOps,
 | |
|                      DiagnosticConsumer *DiagConsumer) override {
 | |
|     // Create a compiler instance to handle the actual work.
 | |
|     CompilerInstance Compiler(std::move(PCHContainerOps));
 | |
|     Compiler.setInvocation(std::move(Invocation));
 | |
| 
 | |
|     // 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;
 | |
| 
 | |
|     // Use the dependency scanning optimized file system if we can.
 | |
|     if (DepFS) {
 | |
|       const CompilerInvocation &CI = Compiler.getInvocation();
 | |
|       // Add any filenames that were explicity passed in the build settings and
 | |
|       // that might be opened, as we want to ensure we don't run source
 | |
|       // minimization on them.
 | |
|       DepFS->IgnoredFiles.clear();
 | |
|       for (const auto &Entry : CI.getHeaderSearchOpts().UserEntries)
 | |
|         DepFS->IgnoredFiles.insert(Entry.Path);
 | |
|       for (const auto &Entry : CI.getHeaderSearchOpts().VFSOverlayFiles)
 | |
|         DepFS->IgnoredFiles.insert(Entry);
 | |
| 
 | |
|       // Support for virtual file system overlays on top of the caching
 | |
|       // filesystem.
 | |
|       FileMgr->setVirtualFileSystem(createVFSFromCompilerInvocation(
 | |
|           CI, Compiler.getDiagnostics(), DepFS));
 | |
| 
 | |
|       // Pass the skip mappings which should speed up excluded conditional block
 | |
|       // skipping in the preprocessor.
 | |
|       if (PPSkipMappings)
 | |
|         Compiler.getPreprocessorOpts()
 | |
|             .ExcludedConditionalDirectiveSkipMappings = PPSkipMappings;
 | |
|     }
 | |
| 
 | |
|     FileMgr->getFileSystemOpts().WorkingDir = std::string(WorkingDirectory);
 | |
|     Compiler.setFileManager(FileMgr);
 | |
|     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 = std::make_unique<DependencyOutputOptions>(
 | |
|         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"};
 | |
| 
 | |
|     switch (Format) {
 | |
|     case ScanningOutputFormat::Make:
 | |
|       Compiler.addDependencyCollector(
 | |
|           std::make_shared<DependencyConsumerForwarder>(std::move(Opts),
 | |
|                                                         Consumer));
 | |
|       break;
 | |
|     case ScanningOutputFormat::Full:
 | |
|       Compiler.addDependencyCollector(std::make_shared<ModuleDepCollector>(
 | |
|           std::move(Opts), Compiler, Consumer));
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     // Consider different header search and diagnostic options to create
 | |
|     // different modules. This avoids the unsound aliasing of module PCMs.
 | |
|     //
 | |
|     // TODO: Implement diagnostic bucketing and header search pruning to reduce
 | |
|     // the impact of strict context hashing.
 | |
|     Compiler.getHeaderSearchOpts().ModulesStrictContextHash = true;
 | |
| 
 | |
|     auto Action = std::make_unique<PreprocessOnlyAction>();
 | |
|     const bool Result = Compiler.ExecuteAction(*Action);
 | |
|     if (!DepFS)
 | |
|       FileMgr->clearStatCache();
 | |
|     return Result;
 | |
|   }
 | |
| 
 | |
| private:
 | |
|   StringRef WorkingDirectory;
 | |
|   DependencyConsumer &Consumer;
 | |
|   llvm::IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS;
 | |
|   ExcludedPreprocessorDirectiveSkipMapping *PPSkipMappings;
 | |
|   ScanningOutputFormat Format;
 | |
| };
 | |
| 
 | |
| } // end anonymous namespace
 | |
| 
 | |
| DependencyScanningWorker::DependencyScanningWorker(
 | |
|     DependencyScanningService &Service)
 | |
|     : Format(Service.getFormat()) {
 | |
|   DiagOpts = new DiagnosticOptions();
 | |
|   PCHContainerOps = std::make_shared<PCHContainerOperations>();
 | |
|   RealFS = new ProxyFileSystemWithoutChdir(llvm::vfs::getRealFileSystem());
 | |
|   if (Service.canSkipExcludedPPRanges())
 | |
|     PPSkipMappings =
 | |
|         std::make_unique<ExcludedPreprocessorDirectiveSkipMapping>();
 | |
|   if (Service.getMode() == ScanningMode::MinimizedSourcePreprocessing)
 | |
|     DepFS = new DependencyScanningWorkerFilesystem(
 | |
|         Service.getSharedCache(), RealFS, PPSkipMappings.get());
 | |
|   if (Service.canReuseFileManager())
 | |
|     Files = new FileManager(FileSystemOptions(), RealFS);
 | |
| }
 | |
| 
 | |
| static llvm::Error runWithDiags(
 | |
|     DiagnosticOptions *DiagOpts,
 | |
|     llvm::function_ref<bool(DiagnosticConsumer &DC)> BodyShouldSucceed) {
 | |
|   // 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);
 | |
| 
 | |
|   if (BodyShouldSucceed(DiagPrinter))
 | |
|     return llvm::Error::success();
 | |
|   return llvm::make_error<llvm::StringError>(DiagnosticsOS.str(),
 | |
|                                              llvm::inconvertibleErrorCode());
 | |
| }
 | |
| 
 | |
| llvm::Error DependencyScanningWorker::computeDependencies(
 | |
|     const std::string &Input, StringRef WorkingDirectory,
 | |
|     const CompilationDatabase &CDB, DependencyConsumer &Consumer) {
 | |
|   RealFS->setCurrentWorkingDirectory(WorkingDirectory);
 | |
|   return runWithDiags(DiagOpts.get(), [&](DiagnosticConsumer &DC) {
 | |
|     /// Create the tool that uses the underlying file system to ensure that any
 | |
|     /// file system requests that are made by the driver do not go through the
 | |
|     /// dependency scanning filesystem.
 | |
|     tooling::ClangTool Tool(CDB, Input, PCHContainerOps, RealFS, Files);
 | |
|     Tool.clearArgumentsAdjusters();
 | |
|     Tool.setRestoreWorkingDir(false);
 | |
|     Tool.setPrintErrorMessage(false);
 | |
|     Tool.setDiagnosticConsumer(&DC);
 | |
|     DependencyScanningAction Action(WorkingDirectory, Consumer, DepFS,
 | |
|                                     PPSkipMappings.get(), Format);
 | |
|     return !Tool.run(&Action);
 | |
|   });
 | |
| }
 |