//===- DependencyScanningWorker.cpp - Thread-Safe Scanning 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/DependencyScanning/DependencyScanningWorker.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/DiagnosticFrontend.h" #include "clang/DependencyScanning/DependencyScannerImpl.h" #include "clang/Driver/Driver.h" #include "clang/Driver/Tool.h" #include "clang/Serialization/ObjectFilePCHContainerReader.h" #include "llvm/ADT/IntrusiveRefCntPtr.h" #include "llvm/Support/VirtualFileSystem.h" using namespace clang; using namespace dependencies; DependencyScanningWorker::DependencyScanningWorker( DependencyScanningService &Service, llvm::IntrusiveRefCntPtr BaseFS) : Service(Service) { PCHContainerOps = std::make_shared(); // We need to read object files from PCH built outside the scanner. PCHContainerOps->registerReader( std::make_unique()); // The scanner itself writes only raw ast files. PCHContainerOps->registerWriter(std::make_unique()); if (Service.shouldTraceVFS()) BaseFS = llvm::makeIntrusiveRefCnt( std::move(BaseFS)); DepFS = llvm::makeIntrusiveRefCnt( Service.getSharedCache(), std::move(BaseFS)); } DependencyScanningWorker::~DependencyScanningWorker() = default; DependencyActionController::~DependencyActionController() = default; static bool createAndRunToolInvocation( ArrayRef CommandLine, DependencyScanningAction &Action, IntrusiveRefCntPtr FS, std::shared_ptr &PCHContainerOps, DiagnosticsEngine &Diags) { auto Invocation = createCompilerInvocation(CommandLine, Diags); if (!Invocation) return false; return Action.runInvocation(CommandLine[0], std::move(Invocation), std::move(FS), PCHContainerOps, Diags.getClient()); } bool DependencyScanningWorker::computeDependencies( StringRef WorkingDirectory, ArrayRef CommandLine, DependencyConsumer &DepConsumer, DependencyActionController &Controller, DiagnosticConsumer &DiagConsumer, llvm::IntrusiveRefCntPtr OverlayFS) { return computeDependencies(WorkingDirectory, ArrayRef>(CommandLine), DepConsumer, Controller, DiagConsumer, OverlayFS); } bool DependencyScanningWorker::computeDependencies( StringRef WorkingDirectory, ArrayRef> CommandLines, DependencyConsumer &DepConsumer, DependencyActionController &Controller, DiagnosticConsumer &DiagConsumer, llvm::IntrusiveRefCntPtr OverlayFS) { IntrusiveRefCntPtr FS = nullptr; if (OverlayFS) { #ifndef NDEBUG bool SawDepFS = false; OverlayFS->visit( [&](llvm::vfs::FileSystem &VFS) { SawDepFS |= &VFS == DepFS.get(); }); assert(SawDepFS && "OverlayFS not based on DepFS"); #endif FS = std::move(OverlayFS); } else { FS = DepFS; FS->setCurrentWorkingDirectory(WorkingDirectory); } DependencyScanningAction Action(Service, WorkingDirectory, DepConsumer, Controller, DepFS); const bool Success = llvm::all_of(CommandLines, [&](const auto &Cmd) { if (StringRef(Cmd[1]) != "-cc1") { // Non-clang command. Just pass through to the dependency consumer. DepConsumer.handleBuildCommand( {Cmd.front(), {Cmd.begin() + 1, Cmd.end()}}); return true; } auto DiagEngineWithDiagOpts = DiagnosticsEngineWithDiagOpts(Cmd, FS, DiagConsumer); auto &Diags = *DiagEngineWithDiagOpts.DiagEngine; // Create an invocation 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. return createAndRunToolInvocation(Cmd, Action, FS, PCHContainerOps, Diags); }); // Ensure finish() is called even if we never reached ExecuteAction(). if (!Action.hasDiagConsumerFinished()) DiagConsumer.finish(); return Success && Action.hasScanned(); } bool DependencyScanningWorker::initializeCompilerInstanceWithContext( StringRef CWD, ArrayRef CommandLine, DiagnosticConsumer &DC) { auto [OverlayFS, ModifiedCommandLine] = initVFSForByNameScanning(DepFS, CommandLine, CWD, "ScanningByName"); auto DiagEngineWithCmdAndOpts = std::make_unique(ModifiedCommandLine, OverlayFS, DC); return initializeCompilerInstanceWithContext( CWD, ModifiedCommandLine, std::move(DiagEngineWithCmdAndOpts), OverlayFS); } bool DependencyScanningWorker::initializeCompilerInstanceWithContext( StringRef CWD, ArrayRef CommandLine, std::unique_ptr DiagEngineWithDiagOpts, IntrusiveRefCntPtr OverlayFS) { CIWithContext = std::make_unique(*this, CWD, CommandLine); return CIWithContext->initialize(std::move(DiagEngineWithDiagOpts), OverlayFS); } bool DependencyScanningWorker::computeDependenciesByNameWithContext( StringRef ModuleName, DependencyConsumer &Consumer, DependencyActionController &Controller) { assert(CIWithContext && "CompilerInstance with context required!"); return CIWithContext->computeDependencies(ModuleName, Consumer, Controller); } bool DependencyScanningWorker::finalizeCompilerInstanceWithContext() { return CIWithContext->finalize(); } std::pair, std::vector> dependencies::initVFSForTUBufferScanning( IntrusiveRefCntPtr BaseFS, ArrayRef CommandLine, StringRef WorkingDirectory, llvm::MemoryBufferRef TUBuffer) { // Reset what might have been modified in the previous worker invocation. BaseFS->setCurrentWorkingDirectory(WorkingDirectory); auto OverlayFS = llvm::makeIntrusiveRefCnt(BaseFS); auto InMemoryFS = llvm::makeIntrusiveRefCnt(); InMemoryFS->setCurrentWorkingDirectory(WorkingDirectory); auto InputPath = TUBuffer.getBufferIdentifier(); InMemoryFS->addFile( InputPath, 0, llvm::MemoryBuffer::getMemBufferCopy(TUBuffer.getBuffer())); IntrusiveRefCntPtr InMemoryOverlay = InMemoryFS; OverlayFS->pushOverlay(InMemoryOverlay); std::vector ModifiedCommandLine(CommandLine); ModifiedCommandLine.emplace_back(InputPath); return std::make_pair(OverlayFS, ModifiedCommandLine); } std::pair, std::vector> dependencies::initVFSForByNameScanning( IntrusiveRefCntPtr BaseFS, ArrayRef CommandLine, StringRef WorkingDirectory, StringRef ModuleName) { // Reset what might have been modified in the previous worker invocation. BaseFS->setCurrentWorkingDirectory(WorkingDirectory); // If we're scanning based on a module name alone, we don't expect the client // to provide us with an input file. However, the driver really wants to have // one. Let's just make it up to make the driver happy. auto OverlayFS = llvm::makeIntrusiveRefCnt(BaseFS); auto InMemoryFS = llvm::makeIntrusiveRefCnt(); InMemoryFS->setCurrentWorkingDirectory(WorkingDirectory); SmallString<128> FakeInputPath; // TODO: We should retry the creation if the path already exists. llvm::sys::fs::createUniquePath(ModuleName + "-%%%%%%%%.input", FakeInputPath, /*MakeAbsolute=*/false); InMemoryFS->addFile(FakeInputPath, 0, llvm::MemoryBuffer::getMemBuffer("")); IntrusiveRefCntPtr InMemoryOverlay = InMemoryFS; OverlayFS->pushOverlay(InMemoryOverlay); std::vector ModifiedCommandLine(CommandLine); ModifiedCommandLine.emplace_back(FakeInputPath); return std::make_pair(OverlayFS, ModifiedCommandLine); }