//===- 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 "DependencyScannerImpl.h" #include "clang/Basic/DiagnosticDriver.h" #include "clang/Basic/DiagnosticFrontend.h" #include "clang/Basic/DiagnosticSerialization.h" #include "clang/Driver/Compilation.h" #include "clang/Driver/Driver.h" #include "clang/Driver/Job.h" #include "clang/Driver/Tool.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/Serialization/ObjectFilePCHContainerReader.h" #include "clang/Tooling/DependencyScanning/DependencyScanningService.h" #include "clang/Tooling/DependencyScanning/InProcessModuleCache.h" #include "clang/Tooling/DependencyScanning/ModuleDepCollector.h" #include "llvm/ADT/IntrusiveRefCntPtr.h" #include "llvm/Support/Allocator.h" #include "llvm/Support/Error.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/TargetParser/Host.h" #include using namespace clang; using namespace tooling; using namespace dependencies; DependencyScanningWorker::DependencyScanningWorker( DependencyScanningService &Service, llvm::IntrusiveRefCntPtr FS) : 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()) FS = llvm::makeIntrusiveRefCnt(std::move(FS)); switch (Service.getMode()) { case ScanningMode::DependencyDirectivesScan: DepFS = llvm::makeIntrusiveRefCnt( Service.getSharedCache(), FS); BaseFS = DepFS; break; case ScanningMode::CanonicalPreprocessing: DepFS = nullptr; BaseFS = FS; break; } } static std::unique_ptr createDiagOptions(const std::vector &CommandLine) { std::vector CLI; for (const std::string &Arg : CommandLine) CLI.push_back(Arg.c_str()); auto DiagOpts = CreateAndPopulateDiagOpts(CLI); sanitizeDiagOpts(*DiagOpts); return DiagOpts; } llvm::Error DependencyScanningWorker::computeDependencies( StringRef WorkingDirectory, const std::vector &CommandLine, DependencyConsumer &Consumer, DependencyActionController &Controller, std::optional TUBuffer) { // 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); auto DiagOpts = createDiagOptions(CommandLine); TextDiagnosticPrinter DiagPrinter(DiagnosticsOS, *DiagOpts); if (computeDependencies(WorkingDirectory, CommandLine, Consumer, Controller, DiagPrinter, TUBuffer)) return llvm::Error::success(); return llvm::make_error(DiagnosticsOS.str(), llvm::inconvertibleErrorCode()); } llvm::Error DependencyScanningWorker::computeDependencies( StringRef WorkingDirectory, const std::vector &CommandLine, DependencyConsumer &Consumer, DependencyActionController &Controller, StringRef ModuleName) { // 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); auto DiagOpts = createDiagOptions(CommandLine); TextDiagnosticPrinter DiagPrinter(DiagnosticsOS, *DiagOpts); if (computeDependencies(WorkingDirectory, CommandLine, Consumer, Controller, DiagPrinter, ModuleName)) return llvm::Error::success(); return llvm::make_error(DiagnosticsOS.str(), llvm::inconvertibleErrorCode()); } static bool forEachDriverJob( ArrayRef ArgStrs, DiagnosticsEngine &Diags, IntrusiveRefCntPtr FS, llvm::function_ref Callback) { SmallVector Argv; Argv.reserve(ArgStrs.size()); for (const std::string &Arg : ArgStrs) Argv.push_back(Arg.c_str()); std::unique_ptr Driver = std::make_unique( Argv[0], llvm::sys::getDefaultTargetTriple(), Diags, "clang LLVM compiler", FS); Driver->setTitle("clang_based_tool"); llvm::BumpPtrAllocator Alloc; bool CLMode = driver::IsClangCL( driver::getDriverMode(Argv[0], ArrayRef(Argv).slice(1))); if (llvm::Error E = driver::expandResponseFiles(Argv, CLMode, Alloc, FS.get())) { Diags.Report(diag::err_drv_expand_response_file) << llvm::toString(std::move(E)); return false; } const std::unique_ptr Compilation( Driver->BuildCompilation(llvm::ArrayRef(Argv))); if (!Compilation) return false; if (Compilation->containsError()) return false; for (const driver::Command &Job : Compilation->getJobs()) { if (!Callback(Job)) return false; } return true; } static bool createAndRunToolInvocation( std::vector CommandLine, DependencyScanningAction &Action, IntrusiveRefCntPtr FS, std::shared_ptr &PCHContainerOps, DiagnosticsEngine &Diags, DependencyConsumer &Consumer) { // Save executable path before providing CommandLine to ToolInvocation std::string Executable = CommandLine[0]; llvm::opt::ArgStringList Argv; for (const std::string &Str : ArrayRef(CommandLine).drop_front()) Argv.push_back(Str.c_str()); auto Invocation = std::make_shared(); if (!CompilerInvocation::CreateFromArgs(*Invocation, Argv, Diags)) { // FIXME: Should we just go on like cc1_main does? return false; } if (!Action.runInvocation(std::move(Invocation), std::move(FS), PCHContainerOps, Diags.getClient())) return false; std::vector Args = Action.takeLastCC1Arguments(); Consumer.handleBuildCommand({std::move(Executable), std::move(Args)}); return true; } bool DependencyScanningWorker::scanDependencies( StringRef WorkingDirectory, const std::vector &CommandLine, DependencyConsumer &Consumer, DependencyActionController &Controller, DiagnosticConsumer &DC, llvm::IntrusiveRefCntPtr FS, std::optional ModuleName) { std::vector CCommandLine(CommandLine.size(), nullptr); llvm::transform(CommandLine, CCommandLine.begin(), [](const std::string &Str) { return Str.c_str(); }); auto DiagOpts = CreateAndPopulateDiagOpts(CCommandLine); sanitizeDiagOpts(*DiagOpts); auto Diags = CompilerInstance::createDiagnostics(*FS, *DiagOpts, &DC, /*ShouldOwnClient=*/false); DependencyScanningAction Action(Service, WorkingDirectory, Consumer, Controller, DepFS, ModuleName); bool Success = false; if (CommandLine[1] == "-cc1") { Success = createAndRunToolInvocation(CommandLine, Action, FS, PCHContainerOps, *Diags, Consumer); } else { Success = forEachDriverJob( CommandLine, *Diags, FS, [&](const driver::Command &Cmd) { if (StringRef(Cmd.getCreator().getName()) != "clang") { // Non-clang command. Just pass through to the dependency // consumer. Consumer.handleBuildCommand( {Cmd.getExecutable(), {Cmd.getArguments().begin(), Cmd.getArguments().end()}}); return true; } // Insert -cc1 comand line options into Argv std::vector Argv; Argv.push_back(Cmd.getExecutable()); llvm::append_range(Argv, Cmd.getArguments()); // 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(std::move(Argv), Action, FS, PCHContainerOps, *Diags, Consumer); }); } if (Success && !Action.hasScanned()) Diags->Report(diag::err_fe_expected_compiler_job) << llvm::join(CommandLine, " "); // Ensure finish() is called even if we never reached ExecuteAction(). if (!Action.hasDiagConsumerFinished()) DC.finish(); return Success && Action.hasScanned(); } bool DependencyScanningWorker::computeDependencies( StringRef WorkingDirectory, const std::vector &CommandLine, DependencyConsumer &Consumer, DependencyActionController &Controller, DiagnosticConsumer &DC, std::optional TUBuffer) { // Reset what might have been modified in the previous worker invocation. BaseFS->setCurrentWorkingDirectory(WorkingDirectory); std::optional> ModifiedCommandLine; llvm::IntrusiveRefCntPtr ModifiedFS; // 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. if (TUBuffer) { 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())); llvm::IntrusiveRefCntPtr InMemoryOverlay = InMemoryFS; OverlayFS->pushOverlay(InMemoryOverlay); ModifiedFS = OverlayFS; ModifiedCommandLine = CommandLine; ModifiedCommandLine->emplace_back(InputPath); } const std::vector &FinalCommandLine = ModifiedCommandLine ? *ModifiedCommandLine : CommandLine; auto &FinalFS = ModifiedFS ? ModifiedFS : BaseFS; return scanDependencies(WorkingDirectory, FinalCommandLine, Consumer, Controller, DC, FinalFS, /*ModuleName=*/std::nullopt); } bool DependencyScanningWorker::computeDependencies( StringRef WorkingDirectory, const std::vector &CommandLine, DependencyConsumer &Consumer, DependencyActionController &Controller, DiagnosticConsumer &DC, 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("")); llvm::IntrusiveRefCntPtr InMemoryOverlay = InMemoryFS; OverlayFS->pushOverlay(InMemoryOverlay); auto ModifiedCommandLine = CommandLine; ModifiedCommandLine.emplace_back(FakeInputPath); return scanDependencies(WorkingDirectory, ModifiedCommandLine, Consumer, Controller, DC, OverlayFS, ModuleName); } DependencyActionController::~DependencyActionController() {}