Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion clang/lib/Tooling/DependencyScanning/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,5 @@ add_clang_library(clangDependencyScanning
clangFrontend
clangLex
clangSerialization
clangTooling
${LLVM_PTHREAD_LIB}
)
282 changes: 231 additions & 51 deletions clang/lib/Tooling/DependencyScanning/DependencyScannerImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
#include "DependencyScannerImpl.h"
#include "clang/Basic/DiagnosticFrontend.h"
#include "clang/Basic/DiagnosticSerialization.h"
#include "clang/Driver/Driver.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Tooling/DependencyScanning/DependencyScanningWorker.h"
#include "llvm/TargetParser/Host.h"

using namespace clang;
using namespace tooling;
Expand Down Expand Up @@ -332,11 +334,9 @@ class ScanningDependencyDirectivesGetter : public DependencyDirectivesGetter {
return DepFS->getDirectiveTokens(File.getName());
}
};
} // namespace

/// Sanitize diagnostic options for dependency scan.
void clang::tooling::dependencies::sanitizeDiagOpts(
DiagnosticOptions &DiagOpts) {
void sanitizeDiagOpts(DiagnosticOptions &DiagOpts) {
// Don't print 'X warnings and Y errors generated'.
DiagOpts.ShowCarets = false;
// Don't write out diagnostic file.
Expand All @@ -355,44 +355,146 @@ void clang::tooling::dependencies::sanitizeDiagOpts(
.Default(true);
});
}
} // namespace

bool DependencyScanningAction::runInvocation(
std::shared_ptr<CompilerInvocation> Invocation,
IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,
std::shared_ptr<PCHContainerOperations> PCHContainerOps,
DiagnosticConsumer *DiagConsumer) {
// Making sure that we canonicalize the defines before we create the deep
// copy to avoid unnecessary variants in the scanner and in the resulting
// explicit command lines.
if (any(Service.getOptimizeArgs() & ScanningOptimizations::Macros))
canonicalizeDefines(Invocation->getPreprocessorOpts());
namespace clang::tooling::dependencies {
std::unique_ptr<DiagnosticOptions>
createDiagOptions(ArrayRef<std::string> CommandLine) {
std::vector<const char *> CLI;
for (const std::string &Arg : CommandLine)
CLI.push_back(Arg.c_str());
auto DiagOpts = CreateAndPopulateDiagOpts(CLI);
sanitizeDiagOpts(*DiagOpts);
return DiagOpts;
}

// Make a deep copy of the original Clang invocation.
CompilerInvocation OriginalInvocation(*Invocation);
DignosticsEngineWithDiagOpts::DignosticsEngineWithDiagOpts(
ArrayRef<std::string> CommandLine,
IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS, DiagnosticConsumer &DC) {
std::vector<const char *> CCommandLine(CommandLine.size(), nullptr);
llvm::transform(CommandLine, CCommandLine.begin(),
[](const std::string &Str) { return Str.c_str(); });
DiagOpts = CreateAndPopulateDiagOpts(CCommandLine);
sanitizeDiagOpts(*DiagOpts);
DiagEngine = CompilerInstance::createDiagnostics(*FS, *DiagOpts, &DC,
/*ShouldOwnClient=*/false);
}

if (Scanned) {
// Scanning runs once for the first -cc1 invocation in a chain of driver
// jobs. For any dependent jobs, reuse the scanning result and just
// update the LastCC1Arguments to correspond to the new invocation.
// FIXME: to support multi-arch builds, each arch requires a separate scan
setLastCC1Arguments(std::move(OriginalInvocation));
return true;
std::pair<std::unique_ptr<driver::Driver>, std::unique_ptr<driver::Compilation>>
buildCompilation(ArrayRef<std::string> ArgStrs, DiagnosticsEngine &Diags,
IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS) {
SmallVector<const char *, 256> Argv;
Argv.reserve(ArgStrs.size());
for (const std::string &Arg : ArgStrs)
Argv.push_back(Arg.c_str());

std::unique_ptr<driver::Driver> Driver = std::make_unique<driver::Driver>(
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 std::make_pair(nullptr, nullptr);
}

Scanned = true;
std::unique_ptr<driver::Compilation> Compilation(
Driver->BuildCompilation(Argv));
if (!Compilation)
return std::make_pair(nullptr, nullptr);

// Create a compiler instance to handle the actual work.
auto ModCache = makeInProcessModuleCache(Service.getModuleCacheEntries());
ScanInstanceStorage.emplace(std::move(Invocation), std::move(PCHContainerOps),
ModCache.get());
CompilerInstance &ScanInstance = *ScanInstanceStorage;
if (Compilation->containsError())
return std::make_pair(nullptr, nullptr);

return std::make_pair(std::move(Driver), std::move(Compilation));
}

std::unique_ptr<CompilerInvocation>
createCompilerInvocation(ArrayRef<std::string> CommandLine,
DiagnosticsEngine &Diags) {
llvm::opt::ArgStringList Argv;
for (const std::string &Str : ArrayRef(CommandLine).drop_front())
Argv.push_back(Str.c_str());

auto Invocation = std::make_unique<CompilerInvocation>();
if (!CompilerInvocation::CreateFromArgs(*Invocation, Argv, Diags)) {
// FIXME: Should we just go on like cc1_main does?
return nullptr;
}
return Invocation;
}

std::pair<IntrusiveRefCntPtr<llvm::vfs::FileSystem>, std::vector<std::string>>
initVFSForTUBuferScanning(IntrusiveRefCntPtr<llvm::vfs::FileSystem> BaseFS,
ArrayRef<std::string> CommandLine,
StringRef WorkingDirectory,
llvm::MemoryBufferRef TUBuffer) {
// Reset what might have been modified in the previous worker invocation.
BaseFS->setCurrentWorkingDirectory(WorkingDirectory);

IntrusiveRefCntPtr<llvm::vfs::FileSystem> ModifiedFS;
auto OverlayFS =
llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(BaseFS);
auto InMemoryFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
InMemoryFS->setCurrentWorkingDirectory(WorkingDirectory);
auto InputPath = TUBuffer.getBufferIdentifier();
InMemoryFS->addFile(
InputPath, 0, llvm::MemoryBuffer::getMemBufferCopy(TUBuffer.getBuffer()));
IntrusiveRefCntPtr<llvm::vfs::FileSystem> InMemoryOverlay = InMemoryFS;

OverlayFS->pushOverlay(InMemoryOverlay);
ModifiedFS = OverlayFS;
std::vector<std::string> ModifiedCommandLine(CommandLine);
ModifiedCommandLine.emplace_back(InputPath);

return std::make_pair(ModifiedFS, ModifiedCommandLine);
}

std::pair<IntrusiveRefCntPtr<llvm::vfs::FileSystem>, std::vector<std::string>>
initVFSForByNameScanning(IntrusiveRefCntPtr<llvm::vfs::FileSystem> BaseFS,
ArrayRef<std::string> 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<llvm::vfs::OverlayFileSystem>(BaseFS);
auto InMemoryFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
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<llvm::vfs::FileSystem> InMemoryOverlay = InMemoryFS;
OverlayFS->pushOverlay(InMemoryOverlay);

std::vector<std::string> ModifiedCommandLine(CommandLine);
ModifiedCommandLine.emplace_back(FakeInputPath);

return std::make_pair(OverlayFS, ModifiedCommandLine);
}

bool initializeScanCompilerInstance(
CompilerInstance &ScanInstance,
IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,
DiagnosticConsumer *DiagConsumer, DependencyScanningService &Service,
IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS) {
ScanInstance.setBuildingModule(false);

ScanInstance.createVirtualFileSystem(FS, DiagConsumer);

// Create the compiler's actual diagnostics engine.
sanitizeDiagOpts(ScanInstance.getDiagnosticOpts());
assert(!DiagConsumerFinished && "attempt to reuse finished consumer");
ScanInstance.createDiagnostics(DiagConsumer, /*ShouldOwnClient=*/false);
if (!ScanInstance.hasDiagnostics())
return false;
Expand Down Expand Up @@ -434,14 +536,39 @@ bool DependencyScanningAction::runInvocation(

ScanInstance.createSourceManager(*FileMgr);

// Consider different header search and diagnostic options to create
// different modules. This avoids the unsound aliasing of module PCMs.
//
// TODO: Implement diagnostic bucketing to reduce the impact of strict
// context hashing.
ScanInstance.getHeaderSearchOpts().ModulesStrictContextHash = true;
ScanInstance.getHeaderSearchOpts().ModulesSerializeOnlyPreprocessor = true;
ScanInstance.getHeaderSearchOpts().ModulesSkipDiagnosticOptions = true;
ScanInstance.getHeaderSearchOpts().ModulesSkipHeaderSearchPaths = true;
ScanInstance.getHeaderSearchOpts().ModulesSkipPragmaDiagnosticMappings = true;
ScanInstance.getHeaderSearchOpts().ModulesForceValidateUserHeaders = false;

// Avoid some checks and module map parsing when loading PCM files.
ScanInstance.getPreprocessorOpts().ModulesCheckRelocated = false;

return true;
}

llvm::SmallVector<StringRef>
getInitialStableDirs(const CompilerInstance &ScanInstance) {
// Create a collection of stable directories derived from the ScanInstance
// for determining whether module dependencies would fully resolve from
// those directories.
llvm::SmallVector<StringRef> StableDirs;
const StringRef Sysroot = ScanInstance.getHeaderSearchOpts().Sysroot;
if (!Sysroot.empty() && (llvm::sys::path::root_directory(Sysroot) != Sysroot))
StableDirs = {Sysroot, ScanInstance.getHeaderSearchOpts().ResourceDir};
return StableDirs;
}

std::optional<PrebuiltModulesAttrsMap>
computePrebuiltModulesASTMap(CompilerInstance &ScanInstance,
llvm::SmallVector<StringRef> &StableDirs) {
// Store a mapping of prebuilt module files and their properties like header
// search options. This will prevent the implicit build to create duplicate
// modules and will force reuse of the existing prebuilt module files
Expand All @@ -453,53 +580,106 @@ bool DependencyScanningAction::runInvocation(
ScanInstance.getPreprocessorOpts().ImplicitPCHInclude, ScanInstance,
ScanInstance.getHeaderSearchOpts().PrebuiltModuleFiles,
PrebuiltModulesASTMap, ScanInstance.getDiagnostics(), StableDirs))
return false;
return {};

// Create the dependency collector that will collect the produced
// dependencies.
//
// This also moves the existing dependency output options from the
return PrebuiltModulesASTMap;
}

std::unique_ptr<DependencyOutputOptions>
takeDependencyOutputOptionsFrom(CompilerInstance &ScanInstance) {
// This function 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::swap(*Opts, ScanInstance.getInvocation().getDependencyOutputOpts());

// We need at least one -MT equivalent for the generator of make dependency
// files to work.
std::swap(*Opts, ScanInstance.getInvocation().getDependencyOutputOpts());
if (Opts->Targets.empty())
Opts->Targets = {deduceDepTarget(ScanInstance.getFrontendOpts().OutputFile,
ScanInstance.getFrontendOpts().Inputs)};
Opts->IncludeSystemHeaders = true;

return Opts;
}

std::shared_ptr<ModuleDepCollector> initializeScanInstanceDependencyCollector(
CompilerInstance &ScanInstance,
std::unique_ptr<DependencyOutputOptions> DepOutputOpts,
StringRef WorkingDirectory, DependencyConsumer &Consumer,
DependencyScanningService &Service, CompilerInvocation &Inv,
DependencyActionController &Controller,
PrebuiltModulesAttrsMap PrebuiltModulesASTMap,
llvm::SmallVector<StringRef> &StableDirs) {
std::shared_ptr<ModuleDepCollector> MDC;
switch (Service.getFormat()) {
case ScanningOutputFormat::Make:
ScanInstance.addDependencyCollector(
std::make_shared<DependencyConsumerForwarder>(
std::move(Opts), WorkingDirectory, Consumer));
std::move(DepOutputOpts), WorkingDirectory, Consumer));
break;
case ScanningOutputFormat::P1689:
case ScanningOutputFormat::Full:
MDC = std::make_shared<ModuleDepCollector>(
Service, std::move(Opts), ScanInstance, Consumer, Controller,
OriginalInvocation, std::move(PrebuiltModulesASTMap), StableDirs);
Service, std::move(DepOutputOpts), ScanInstance, Consumer, Controller,
Inv, std::move(PrebuiltModulesASTMap), StableDirs);
ScanInstance.addDependencyCollector(MDC);
break;
}

// Consider different header search and diagnostic options to create
// different modules. This avoids the unsound aliasing of module PCMs.
//
// TODO: Implement diagnostic bucketing to reduce the impact of strict
// context hashing.
ScanInstance.getHeaderSearchOpts().ModulesStrictContextHash = true;
ScanInstance.getHeaderSearchOpts().ModulesSerializeOnlyPreprocessor = true;
ScanInstance.getHeaderSearchOpts().ModulesSkipDiagnosticOptions = true;
ScanInstance.getHeaderSearchOpts().ModulesSkipHeaderSearchPaths = true;
ScanInstance.getHeaderSearchOpts().ModulesSkipPragmaDiagnosticMappings = true;
ScanInstance.getHeaderSearchOpts().ModulesForceValidateUserHeaders = false;
return MDC;
}
} // namespace clang::tooling::dependencies

// Avoid some checks and module map parsing when loading PCM files.
ScanInstance.getPreprocessorOpts().ModulesCheckRelocated = false;
bool DependencyScanningAction::runInvocation(
std::unique_ptr<CompilerInvocation> Invocation,
IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,
std::shared_ptr<PCHContainerOperations> PCHContainerOps,
DiagnosticConsumer *DiagConsumer) {
// Making sure that we canonicalize the defines before we create the deep
// copy to avoid unnecessary variants in the scanner and in the resulting
// explicit command lines.
if (any(Service.getOptimizeArgs() & ScanningOptimizations::Macros))
canonicalizeDefines(Invocation->getPreprocessorOpts());

// Make a deep copy of the original Clang invocation.
CompilerInvocation OriginalInvocation(*Invocation);

if (Scanned) {
// Scanning runs once for the first -cc1 invocation in a chain of driver
// jobs. For any dependent jobs, reuse the scanning result and just
// update the LastCC1Arguments to correspond to the new invocation.
// FIXME: to support multi-arch builds, each arch requires a separate scan
setLastCC1Arguments(std::move(OriginalInvocation));
return true;
}

Scanned = true;

// Create a compiler instance to handle the actual work.
auto ModCache = makeInProcessModuleCache(Service.getModuleCacheEntries());
ScanInstanceStorage.emplace(std::move(Invocation), std::move(PCHContainerOps),
ModCache.get());
CompilerInstance &ScanInstance = *ScanInstanceStorage;

assert(!DiagConsumerFinished && "attempt to reuse finished consumer");
if (!initializeScanCompilerInstance(ScanInstance, FS, DiagConsumer, Service,
DepFS))
return false;

llvm::SmallVector<StringRef> StableDirs = getInitialStableDirs(ScanInstance);
auto MaybePrebuiltModulesASTMap =
computePrebuiltModulesASTMap(ScanInstance, StableDirs);
if (!MaybePrebuiltModulesASTMap)
return false;

auto DepOutputOpts = takeDependencyOutputOptionsFrom(ScanInstance);

MDC = initializeScanInstanceDependencyCollector(
ScanInstance, std::move(DepOutputOpts), WorkingDirectory, Consumer,
Service, OriginalInvocation, Controller, *MaybePrebuiltModulesASTMap,
StableDirs);

std::unique_ptr<FrontendAction> Action;

Expand Down
Loading