//===- 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/CodeGen/ObjectFilePCHContainerOperations.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 Opts, DependencyConsumer &C) : DependencyFileGenerator(*Opts), Opts(std::move(Opts)), C(C) {} void finishedMainFile(DiagnosticsEngine &Diags) override { C.handleDependencyOutputOpts(*Opts); llvm::SmallString<256> CanonPath; for (const auto &File : getDependencies()) { CanonPath = File; llvm::sys::path::remove_dots(CanonPath, /*remove_dot_dot=*/true); C.handleFileDependency(CanonPath); } } private: std::unique_ptr Opts; DependencyConsumer &C; }; /// A listener that collects the imported modules and optionally the input /// files. class PrebuiltModuleListener : public ASTReaderListener { public: PrebuiltModuleListener(llvm::StringMap &PrebuiltModuleFiles, llvm::StringSet<> &InputFiles, bool VisitInputFiles) : PrebuiltModuleFiles(PrebuiltModuleFiles), InputFiles(InputFiles), VisitInputFiles(VisitInputFiles) {} bool needsImportVisitation() const override { return true; } bool needsInputFileVisitation() override { return VisitInputFiles; } bool needsSystemInputFileVisitation() override { return VisitInputFiles; } void visitImport(StringRef ModuleName, StringRef Filename) override { PrebuiltModuleFiles.insert({ModuleName, Filename.str()}); } bool visitInputFile(StringRef Filename, bool isSystem, bool isOverridden, bool isExplicitModule) override { InputFiles.insert(Filename); return true; } private: llvm::StringMap &PrebuiltModuleFiles; llvm::StringSet<> &InputFiles; bool VisitInputFiles; }; using PrebuiltModuleFilesT = decltype(HeaderSearchOptions::PrebuiltModuleFiles); /// Visit the given prebuilt module and collect all of the modules it /// transitively imports and contributing input files. static void visitPrebuiltModule(StringRef PrebuiltModuleFilename, CompilerInstance &CI, PrebuiltModuleFilesT &ModuleFiles, llvm::StringSet<> &InputFiles, bool VisitInputFiles) { // Maps the names of modules that weren't yet visited to their PCM path. llvm::StringMap ModuleFilesWorklist; // Contains PCM paths of all visited modules. llvm::StringSet<> VisitedModuleFiles; PrebuiltModuleListener Listener(ModuleFilesWorklist, InputFiles, VisitInputFiles); auto GatherModuleFileInfo = [&](StringRef ASTFile) { ASTReader::readASTFileControlBlock( ASTFile, CI.getFileManager(), CI.getPCHContainerReader(), /*FindModuleFileExtensions=*/false, Listener, /*ValidateDiagnosticOptions=*/false); }; GatherModuleFileInfo(PrebuiltModuleFilename); while (!ModuleFilesWorklist.empty()) { auto WorklistItemIt = ModuleFilesWorklist.begin(); if (!VisitedModuleFiles.contains(WorklistItemIt->getValue())) { VisitedModuleFiles.insert(WorklistItemIt->getValue()); GatherModuleFileInfo(WorklistItemIt->getValue()); ModuleFiles[WorklistItemIt->getKey().str()] = WorklistItemIt->getValue(); } ModuleFilesWorklist.erase(WorklistItemIt); } } /// Transform arbitrary file name into an object-like file name. static std::string makeObjFileName(StringRef FileName) { SmallString<128> ObjFileName(FileName); llvm::sys::path::replace_extension(ObjFileName, "o"); return std::string(ObjFileName.str()); } /// Deduce the dependency target based on the output file and input files. static std::string deduceDepTarget(const std::string &OutputFile, const SmallVectorImpl &InputFiles) { if (OutputFile != "-") return OutputFile; if (InputFiles.empty() || !InputFiles.front().isFile()) return "clang-scan-deps\\ dependency"; return makeObjFileName(InputFiles.front().getFile()); } /// 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 DepFS, ExcludedPreprocessorDirectiveSkipMapping *PPSkipMappings, ScanningOutputFormat Format) : WorkingDirectory(WorkingDirectory), Consumer(Consumer), DepFS(std::move(DepFS)), PPSkipMappings(PPSkipMappings), Format(Format) {} bool runInvocation(std::shared_ptr Invocation, FileManager *FileMgr, std::shared_ptr PCHContainerOps, DiagnosticConsumer *DiagConsumer) override { // Make a deep copy of the original Clang invocation. CompilerInvocation OriginalInvocation(*Invocation); // 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; // Don't write out diagnostic file. Compiler.getDiagnosticOpts().DiagnosticSerializationFile.clear(); // Don't treat warnings as errors. Compiler.getDiagnosticOpts().Warnings.push_back("no-error"); // Create the compiler's actual diagnostics engine. Compiler.createDiagnostics(DiagConsumer, /*ShouldOwnClient=*/false); if (!Compiler.hasDiagnostics()) return false; Compiler.getPreprocessorOpts().AllowPCHWithDifferentModulesCachePath = true; FileMgr->getFileSystemOpts().WorkingDir = std::string(WorkingDirectory); Compiler.setFileManager(FileMgr); Compiler.createSourceManager(*FileMgr); llvm::StringSet<> PrebuiltModulesInputFiles; // Store the list of prebuilt module files into header search options. This // will prevent the implicit build to create duplicate modules and will // force reuse of the existing prebuilt module files instead. if (!Compiler.getPreprocessorOpts().ImplicitPCHInclude.empty()) visitPrebuiltModule( Compiler.getPreprocessorOpts().ImplicitPCHInclude, Compiler, Compiler.getHeaderSearchOpts().PrebuiltModuleFiles, PrebuiltModulesInputFiles, /*VisitInputFiles=*/DepFS != nullptr); // Use the dependency scanning optimized file system if requested to do so. if (DepFS) { const CompilerInvocation &CI = Compiler.getInvocation(); DepFS->clearIgnoredFiles(); // Ignore any files that contributed to prebuilt modules. The implicit // build validates the modules by comparing the reported sizes of their // inputs to the current state of the filesystem. Minimization would throw // this mechanism off. for (const auto &File : PrebuiltModulesInputFiles) DepFS->ignoreFile(File.getKey()); // 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. for (const auto &Entry : CI.getHeaderSearchOpts().UserEntries) DepFS->ignoreFile(Entry.Path); for (const auto &Entry : CI.getHeaderSearchOpts().VFSOverlayFiles) DepFS->ignoreFile(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; } // 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(); std::swap(*Opts, Compiler.getInvocation().getDependencyOutputOpts()); // We need at least one -MT equivalent for the generator of make dependency // files to work. if (Opts->Targets.empty()) Opts->Targets = {deduceDepTarget(Compiler.getFrontendOpts().OutputFile, Compiler.getFrontendOpts().Inputs)}; Opts->IncludeSystemHeaders = true; switch (Format) { case ScanningOutputFormat::Make: Compiler.addDependencyCollector( std::make_shared(std::move(Opts), Consumer)); break; case ScanningOutputFormat::Full: Compiler.addDependencyCollector(std::make_shared( std::move(Opts), Compiler, Consumer, std::move(OriginalInvocation))); 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(); const bool Result = Compiler.ExecuteAction(*Action); if (!DepFS) FileMgr->clearStatCache(); return Result; } private: StringRef WorkingDirectory; DependencyConsumer &Consumer; llvm::IntrusiveRefCntPtr DepFS; ExcludedPreprocessorDirectiveSkipMapping *PPSkipMappings; ScanningOutputFormat Format; }; } // end anonymous namespace DependencyScanningWorker::DependencyScanningWorker( DependencyScanningService &Service) : Format(Service.getFormat()) { DiagOpts = new DiagnosticOptions(); PCHContainerOps = std::make_shared(); PCHContainerOps->registerReader( std::make_unique()); // We don't need to write object files, but the current PCH implementation // requires the writer to be registered as well. PCHContainerOps->registerWriter( std::make_unique()); RealFS = llvm::vfs::createPhysicalFileSystem(); if (Service.canSkipExcludedPPRanges()) PPSkipMappings = std::make_unique(); 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 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(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); }); }