blob: 0921f8a96eaac656d0bffafc327f19d12b424ef1 [file] [edit]
//===- 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/DependencyActionController.h"
#include "clang/DependencyScanning/DependencyConsumer.h"
#include "clang/DependencyScanning/DependencyScannerImpl.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Serialization/ObjectFilePCHContainerReader.h"
#include "llvm/ADT/IntrusiveRefCntPtr.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/Support/VirtualFileSystem.h"
using namespace clang;
using namespace dependencies;
namespace clang {
namespace dependencies {
class CompilerInstanceWithContext {
// Context
DependencyScanningWorker &Worker;
llvm::StringRef CWD;
std::vector<std::string> CommandLine;
// Context - Diagnostics engine.
DiagnosticConsumer *DiagConsumer = nullptr;
std::unique_ptr<DiagnosticsEngineWithDiagOpts> DiagEngineWithCmdAndOpts;
// Context - compiler invocation
std::unique_ptr<CompilerInvocation> OriginalInvocation;
// Context - output options
std::unique_ptr<DependencyOutputOptions> OutputOpts;
// Context - stable directory handling
llvm::SmallVector<StringRef> StableDirs;
PrebuiltModulesAttrsMap PrebuiltModuleASTMap;
// Compiler Instance
std::unique_ptr<CompilerInstance> CIPtr;
// Source location offset.
int32_t SrcLocOffset = 0;
CompilerInstanceWithContext(DependencyScanningWorker &Worker, StringRef CWD,
ArrayRef<std::string> CMD)
: Worker(Worker), CWD(CWD), CommandLine(CMD.begin(), CMD.end()) {}
bool initialize(
DependencyActionController &Controller,
std::unique_ptr<DiagnosticsEngineWithDiagOpts> DiagEngineWithDiagOpts,
IntrusiveRefCntPtr<llvm::vfs::FileSystem> OverlayFS) {
{
auto LogLine = Worker.Service.getLogger().log();
LogLine.logArray("init_compiler_instance_with_context:", " ",
CommandLine);
}
assert(DiagEngineWithDiagOpts && "Valid diagnostics engine required!");
DiagEngineWithCmdAndOpts = std::move(DiagEngineWithDiagOpts);
DiagConsumer = DiagEngineWithCmdAndOpts->DiagEngine->getClient();
assert(OverlayFS && "OverlayFS required!");
auto FS = Worker.makeEffectiveVFS(CWD, std::move(OverlayFS));
OriginalInvocation = createCompilerInvocation(
CommandLine, *DiagEngineWithCmdAndOpts->DiagEngine);
if (!OriginalInvocation) {
DiagEngineWithCmdAndOpts->DiagEngine->Report(
diag::err_fe_expected_compiler_job)
<< llvm::join(CommandLine, " ");
return false;
}
if (any(Worker.Service.getOpts().OptimizeArgs &
ScanningOptimizations::Macros))
canonicalizeDefines(OriginalInvocation->getPreprocessorOpts());
// Create the CompilerInstance.
std::shared_ptr<ModuleCache> ModCache = makeInProcessModuleCache(
Worker.Service.getModuleCacheEntries(), Worker.Service.getLogger());
CIPtr = std::make_unique<CompilerInstance>(
createScanCompilerInvocation(*OriginalInvocation, Worker.Service,
Controller),
Worker.PCHContainerOps, std::move(ModCache));
auto &CI = *CIPtr;
initializeScanCompilerInstance(
CI, std::move(FS), DiagEngineWithCmdAndOpts->DiagEngine->getClient(),
Worker.Service, Worker.DepFS);
StableDirs = getInitialStableDirs(CI);
auto MaybePrebuiltModulesASTMap =
computePrebuiltModulesASTMap(CI, StableDirs);
if (!MaybePrebuiltModulesASTMap)
return false;
PrebuiltModuleASTMap = std::move(*MaybePrebuiltModulesASTMap);
OutputOpts = createDependencyOutputOptions(*OriginalInvocation);
// We do not create the target in initializeScanCompilerInstance because
// setting it here is unique for by-name lookups. We create the target only
// once here, and the information is reused for all computeDependencies
// calls. We do not need to call createTarget explicitly if we go through
// CompilerInstance::ExecuteAction to perform scanning.
return CI.createTarget();
}
public:
static std::optional<CompilerInstanceWithContext>
initializeFromCC1Commandline(
DependencyScanningWorker &Worker, StringRef CWD,
ArrayRef<std::string> CC1CommandLine,
std::unique_ptr<DiagnosticsEngineWithDiagOpts> DiagEngineWithDiagOpts,
IntrusiveRefCntPtr<llvm::vfs::FileSystem> OverlayFS,
DependencyActionController &Controller) {
CompilerInstanceWithContext CIWC(Worker, CWD, CC1CommandLine);
if (!CIWC.initialize(Controller, std::move(DiagEngineWithDiagOpts),
std::move(OverlayFS)))
return std::nullopt;
return std::move(CIWC);
}
bool computeDependencies(StringRef ModuleName, DependencyConsumer &Consumer,
DependencyActionController &Controller) {
Worker.Service.getLogger().log() << "start scan_by_name: " << ModuleName;
llvm::scope_exit ExitLogging([&] {
Worker.Service.getLogger().log() << "finish scan_by_name: " << ModuleName;
});
if (SrcLocOffset >= DependencyScanningWorker::MaxNumOfByNameQueries)
llvm::report_fatal_error("exceeded maximum by-name scans for worker");
assert(CIPtr && "CIPtr must be initialized before calling this method");
auto &CI = *CIPtr;
// We need to reset the diagnostics, so that the diagnostics issued
// during a previous computeDependencies call do not affect the current
// call. If we do not reset, we may inherit fatal errors from a previous
// call.
CI.getDiagnostics().Reset();
// We create this cleanup object because computeDependencies may exit
// early with errors.
llvm::scope_exit CleanUp([&]() {
CI.clearDependencyCollectors();
// The preprocessor may not be created at the entry of this method,
// but it must have been created when this method returns, whether
// there are errors during scanning or not.
CI.getPreprocessor().removePPCallbacks();
});
auto MDC = initializeScanInstanceDependencyCollector(
CI, std::make_unique<DependencyOutputOptions>(*OutputOpts),
Worker.Service,
/* The MDC's constructor makes a copy of the OriginalInvocation, so
we can pass it in without worrying that it might be changed across
invocations of computeDependencies. */
*OriginalInvocation, Controller, PrebuiltModuleASTMap, StableDirs);
CompilerInvocation ModuleInvocation(*OriginalInvocation);
if (!Controller.initialize(CI, ModuleInvocation))
return false;
if (!SrcLocOffset) {
// When SrcLocOffset is zero, we are at the beginning of the fake source
// file. In this case, we call BeginSourceFile to initialize.
std::unique_ptr<FrontendAction> Action =
std::make_unique<PreprocessOnlyAction>();
auto *InputFile = CI.getFrontendOpts().Inputs.begin();
bool ActionBeginSucceeded = Action->BeginSourceFile(CI, *InputFile);
assert(ActionBeginSucceeded && "Action BeginSourceFile must succeed");
(void)ActionBeginSucceeded;
}
Preprocessor &PP = CI.getPreprocessor();
SourceManager &SM = PP.getSourceManager();
FileID MainFileID = SM.getMainFileID();
SourceLocation FileStart = SM.getLocForStartOfFile(MainFileID);
SourceLocation IDLocation = FileStart.getLocWithOffset(SrcLocOffset);
PPCallbacks *CB = nullptr;
if (!SrcLocOffset) {
// We need to call EnterSourceFile when SrcLocOffset is zero to initialize
// the preprocessor.
bool PPFailed = PP.EnterSourceFile(MainFileID, nullptr, SourceLocation());
assert(!PPFailed && "Preprocess must be able to enter the main file.");
(void)PPFailed;
CB = MDC->getPPCallbacks();
} else {
// When SrcLocOffset is non-zero, the preprocessor has already been
// initialized through a previous call of computeDependencies. We want to
// preserve the PP's state, hence we do not call EnterSourceFile again.
MDC->attachToPreprocessor(PP);
CB = MDC->getPPCallbacks();
FileID PrevFID;
SrcMgr::CharacteristicKind FileType =
SM.getFileCharacteristic(IDLocation);
CB->LexedFileChanged(MainFileID,
PPChainedCallbacks::LexedFileChangeReason::EnterFile,
FileType, PrevFID, IDLocation);
}
// FIXME: Scan modules asynchronously here as well.
SrcLocOffset++;
SmallVector<IdentifierLoc, 2> Path;
IdentifierInfo *ModuleID = PP.getIdentifierInfo(ModuleName);
Path.emplace_back(IDLocation, ModuleID);
auto ModResult = CI.loadModule(IDLocation, Path, Module::Hidden, false);
assert(CB && "Must have PPCallbacks after module loading");
CB->moduleImport(SourceLocation(), Path, ModResult);
if (!ModResult)
return false;
if (CI.getDiagnostics().hasErrorOccurred())
return false;
MDC->run(Consumer);
MDC->applyDiscoveredDependencies(ModuleInvocation);
bool Success = ModuleInvocation.withCowRef<bool>(
[&](CowCompilerInvocation &CowModuleInvocation) {
return Controller.finalize(CI, CowModuleInvocation);
});
if (!Success)
return false;
Consumer.handleBuildCommand(
{CommandLine[0], ModuleInvocation.getCC1CommandLine()});
return true;
}
};
} // namespace dependencies
} // namespace clang
DependencyScanningWorker::DependencyScanningWorker(
DependencyScanningService &Service)
: Service(Service) {
PCHContainerOps = std::make_shared<PCHContainerOperations>();
// We need to read object files from PCH built outside the scanner.
PCHContainerOps->registerReader(
std::make_unique<ObjectFilePCHContainerReader>());
// The scanner itself writes only raw ast files.
PCHContainerOps->registerWriter(std::make_unique<RawPCHContainerWriter>());
auto BaseFS = Service.getOpts().MakeVFS();
if (Service.getOpts().TraceVFS) {
TracingFS = llvm::makeIntrusiveRefCnt<llvm::vfs::TracingFileSystem>(
std::move(BaseFS));
BaseFS = TracingFS;
}
DepFS = llvm::makeIntrusiveRefCnt<DependencyScanningWorkerFilesystem>(
Service, std::move(BaseFS));
}
DependencyScanningWorker::~DependencyScanningWorker() = default;
static bool createAndRunToolInvocation(
ArrayRef<std::string> CommandLine, DependencyScanningAction &Action,
IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,
std::shared_ptr<clang::PCHContainerOperations> &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());
}
IntrusiveRefCntPtr<llvm::vfs::FileSystem>
DependencyScanningWorker::makeEffectiveVFS(
StringRef WorkingDirectory,
IntrusiveRefCntPtr<llvm::vfs::FileSystem> OverlayFS) const {
IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS = DepFS;
if (OverlayFS) {
auto NewFS =
llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(std::move(FS));
NewFS->pushOverlay(std::move(OverlayFS));
FS = std::move(NewFS);
}
FS->setCurrentWorkingDirectory(WorkingDirectory);
return FS;
}
bool DependencyScanningWorker::computeDependencies(
StringRef WorkingDirectory, ArrayRef<ArrayRef<std::string>> CommandLines,
DependencyConsumer &DepConsumer, DependencyActionController &Controller,
DiagnosticConsumer &DiagConsumer,
IntrusiveRefCntPtr<llvm::vfs::FileSystem> OverlayFS) {
auto FS = makeEffectiveVFS(WorkingDirectory, std::move(OverlayFS));
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;
}
Service.getLogger().log().logArray("starting scanning command:", " ", Cmd);
llvm::scope_exit ExitLogging([&] {
Service.getLogger().log().logArray("finished scanning command:", " ",
Cmd);
});
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);
});
return Success && Action.hasScanned();
}
bool DependencyScanningWorker::computeDependenciesByName(
StringRef CWD, ArrayRef<std::string> CC1CommandLine,
IntrusiveRefCntPtr<llvm::vfs::FileSystem> OverlayFS,
DiagnosticConsumer &DiagConsumer, DependencyActionController &Controller,
llvm::function_ref<std::optional<std::string>()> getNextName,
DependencyConsumer &DepConsumer) {
auto FS = makeEffectiveVFS(CWD, OverlayFS);
auto DiagEngine = std::make_unique<DiagnosticsEngineWithDiagOpts>(
CC1CommandLine, FS, DiagConsumer);
std::optional<CompilerInstanceWithContext> CIWC =
CompilerInstanceWithContext::initializeFromCC1Commandline(
*this, CWD, CC1CommandLine, std::move(DiagEngine),
std::move(OverlayFS), Controller);
if (!CIWC)
return false;
bool AllScansSucceeded = true;
while (std::optional<std::string> NextName = getNextName()) {
bool Success =
CIWC->computeDependencies(*NextName, DepConsumer, Controller);
DepConsumer.finishQuery(*NextName, Success);
AllScansSucceeded = AllScansSucceeded && Success;
}
return AllScansSucceeded;
}