#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 {
class DependencyConsumerForwarder : public DependencyFileGenerator {
public:
DependencyConsumerForwarder(std::unique_ptr<DependencyOutputOptions> 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, true);
C.handleFileDependency(CanonPath);
}
}
private:
std::unique_ptr<DependencyOutputOptions> Opts;
DependencyConsumer &C;
};
using PrebuiltModuleFilesT = decltype(HeaderSearchOptions::PrebuiltModuleFiles);
class PrebuiltModuleListener : public ASTReaderListener {
public:
PrebuiltModuleListener(PrebuiltModuleFilesT &PrebuiltModuleFiles,
llvm::StringSet<> &InputFiles, bool VisitInputFiles,
llvm::SmallVector<std::string> &NewModuleFiles)
: PrebuiltModuleFiles(PrebuiltModuleFiles), InputFiles(InputFiles),
VisitInputFiles(VisitInputFiles), NewModuleFiles(NewModuleFiles) {}
bool needsImportVisitation() const override { return true; }
bool needsInputFileVisitation() override { return VisitInputFiles; }
bool needsSystemInputFileVisitation() override { return VisitInputFiles; }
void visitImport(StringRef ModuleName, StringRef Filename) override {
if (PrebuiltModuleFiles.insert({ModuleName.str(), Filename.str()}).second)
NewModuleFiles.push_back(Filename.str());
}
bool visitInputFile(StringRef Filename, bool isSystem, bool isOverridden,
bool isExplicitModule) override {
InputFiles.insert(Filename);
return true;
}
private:
PrebuiltModuleFilesT &PrebuiltModuleFiles;
llvm::StringSet<> &InputFiles;
bool VisitInputFiles;
llvm::SmallVector<std::string> &NewModuleFiles;
};
static void visitPrebuiltModule(StringRef PrebuiltModuleFilename,
CompilerInstance &CI,
PrebuiltModuleFilesT &ModuleFiles,
llvm::StringSet<> &InputFiles,
bool VisitInputFiles) {
llvm::SmallVector<std::string> Worklist{PrebuiltModuleFilename.str()};
PrebuiltModuleListener Listener(ModuleFiles, InputFiles, VisitInputFiles,
Worklist);
while (!Worklist.empty())
ASTReader::readASTFileControlBlock(
Worklist.pop_back_val(), CI.getFileManager(),
CI.getPCHContainerReader(),
false, Listener,
false);
}
static std::string makeObjFileName(StringRef FileName) {
SmallString<128> ObjFileName(FileName);
llvm::sys::path::replace_extension(ObjFileName, "o");
return std::string(ObjFileName.str());
}
static std::string
deduceDepTarget(const std::string &OutputFile,
const SmallVectorImpl<FrontendInputFile> &InputFiles) {
if (OutputFile != "-")
return OutputFile;
if (InputFiles.empty() || !InputFiles.front().isFile())
return "clang-scan-deps\\ dependency";
return makeObjFileName(InputFiles.front().getFile());
}
static void sanitizeDiagOpts(DiagnosticOptions &DiagOpts) {
DiagOpts.ShowCarets = false;
DiagOpts.DiagnosticSerializationFile.clear();
DiagOpts.Warnings.push_back("no-error");
}
class DependencyScanningAction : public tooling::ToolAction {
public:
DependencyScanningAction(
StringRef WorkingDirectory, DependencyConsumer &Consumer,
llvm::IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS,
ScanningOutputFormat Format, bool OptimizeArgs, bool DisableFree,
llvm::Optional<StringRef> ModuleName = None)
: WorkingDirectory(WorkingDirectory), Consumer(Consumer),
DepFS(std::move(DepFS)), Format(Format), OptimizeArgs(OptimizeArgs),
DisableFree(DisableFree), ModuleName(ModuleName) {}
bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation,
FileManager *FileMgr,
std::shared_ptr<PCHContainerOperations> PCHContainerOps,
DiagnosticConsumer *DiagConsumer) override {
CompilerInvocation OriginalInvocation(*Invocation);
OriginalInvocation.getFrontendOpts().DisableFree = DisableFree;
CompilerInstance ScanInstance(std::move(PCHContainerOps));
ScanInstance.setInvocation(std::move(Invocation));
sanitizeDiagOpts(ScanInstance.getDiagnosticOpts());
ScanInstance.createDiagnostics(DiagConsumer, false);
if (!ScanInstance.hasDiagnostics())
return false;
ScanInstance.getPreprocessorOpts().AllowPCHWithDifferentModulesCachePath =
true;
ScanInstance.getFrontendOpts().GenerateGlobalModuleIndex = false;
ScanInstance.getFrontendOpts().UseGlobalModuleIndex = false;
FileMgr->getFileSystemOpts().WorkingDir = std::string(WorkingDirectory);
ScanInstance.setFileManager(FileMgr);
ScanInstance.createSourceManager(*FileMgr);
llvm::StringSet<> PrebuiltModulesInputFiles;
if (!ScanInstance.getPreprocessorOpts().ImplicitPCHInclude.empty())
visitPrebuiltModule(
ScanInstance.getPreprocessorOpts().ImplicitPCHInclude, ScanInstance,
ScanInstance.getHeaderSearchOpts().PrebuiltModuleFiles,
PrebuiltModulesInputFiles, DepFS != nullptr);
if (DepFS) {
FileMgr->setVirtualFileSystem(createVFSFromCompilerInvocation(
ScanInstance.getInvocation(), ScanInstance.getDiagnostics(), DepFS));
llvm::IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> LocalDepFS =
DepFS;
ScanInstance.getPreprocessorOpts().DependencyDirectivesForFile =
[LocalDepFS = std::move(LocalDepFS)](FileEntryRef File)
-> Optional<ArrayRef<dependency_directives_scan::Directive>> {
if (llvm::ErrorOr<EntryRef> Entry =
LocalDepFS->getOrCreateFileSystemEntry(File.getName()))
return Entry->getDirectiveTokens();
return None;
};
}
auto Opts = std::make_unique<DependencyOutputOptions>();
std::swap(*Opts, ScanInstance.getInvocation().getDependencyOutputOpts());
if (Opts->Targets.empty())
Opts->Targets = {
deduceDepTarget(ScanInstance.getFrontendOpts().OutputFile,
ScanInstance.getFrontendOpts().Inputs)};
Opts->IncludeSystemHeaders = true;
switch (Format) {
case ScanningOutputFormat::Make:
ScanInstance.addDependencyCollector(
std::make_shared<DependencyConsumerForwarder>(std::move(Opts),
Consumer));
break;
case ScanningOutputFormat::Full:
ScanInstance.addDependencyCollector(std::make_shared<ModuleDepCollector>(
std::move(Opts), ScanInstance, Consumer,
std::move(OriginalInvocation), OptimizeArgs));
break;
}
ScanInstance.getHeaderSearchOpts().ModulesStrictContextHash = true;
std::unique_ptr<FrontendAction> Action;
if (ModuleName)
Action = std::make_unique<GetDependenciesByModuleNameAction>(*ModuleName);
else
Action = std::make_unique<ReadPCHAndPreprocessAction>();
const bool Result = ScanInstance.ExecuteAction(*Action);
if (!DepFS)
FileMgr->clearStatCache();
return Result;
}
private:
StringRef WorkingDirectory;
DependencyConsumer &Consumer;
llvm::IntrusiveRefCntPtr<DependencyScanningWorkerFilesystem> DepFS;
ScanningOutputFormat Format;
bool OptimizeArgs;
bool DisableFree;
llvm::Optional<StringRef> ModuleName;
};
}
DependencyScanningWorker::DependencyScanningWorker(
DependencyScanningService &Service,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
: Format(Service.getFormat()), OptimizeArgs(Service.canOptimizeArgs()) {
PCHContainerOps = std::make_shared<PCHContainerOperations>();
PCHContainerOps->registerReader(
std::make_unique<ObjectFilePCHContainerReader>());
PCHContainerOps->registerWriter(
std::make_unique<ObjectFilePCHContainerWriter>());
auto OverlayFS =
llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(std::move(FS));
InMemoryFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
OverlayFS->pushOverlay(InMemoryFS);
RealFS = OverlayFS;
if (Service.getMode() == ScanningMode::DependencyDirectivesScan)
DepFS = new DependencyScanningWorkerFilesystem(Service.getSharedCache(),
RealFS);
if (Service.canReuseFileManager())
Files = new FileManager(FileSystemOptions(), RealFS);
}
static llvm::Error
runWithDiags(DiagnosticOptions *DiagOpts,
llvm::function_ref<bool(DiagnosticConsumer &, DiagnosticOptions &)>
BodyShouldSucceed) {
sanitizeDiagOpts(*DiagOpts);
std::string DiagnosticOutput;
llvm::raw_string_ostream DiagnosticsOS(DiagnosticOutput);
TextDiagnosticPrinter DiagPrinter(DiagnosticsOS, DiagOpts);
if (BodyShouldSucceed(DiagPrinter, *DiagOpts))
return llvm::Error::success();
return llvm::make_error<llvm::StringError>(DiagnosticsOS.str(),
llvm::inconvertibleErrorCode());
}
llvm::Error DependencyScanningWorker::computeDependencies(
StringRef WorkingDirectory, const std::vector<std::string> &CommandLine,
DependencyConsumer &Consumer, llvm::Optional<StringRef> ModuleName) {
RealFS->setCurrentWorkingDirectory(WorkingDirectory);
if (Files)
Files->setVirtualFileSystem(RealFS);
llvm::IntrusiveRefCntPtr<FileManager> CurrentFiles =
Files ? Files : new FileManager(FileSystemOptions(), RealFS);
Optional<std::vector<std::string>> ModifiedCommandLine;
if (ModuleName) {
ModifiedCommandLine = CommandLine;
InMemoryFS->addFile(*ModuleName, 0, llvm::MemoryBuffer::getMemBuffer(""));
ModifiedCommandLine->emplace_back(*ModuleName);
}
const std::vector<std::string> &FinalCommandLine =
ModifiedCommandLine ? *ModifiedCommandLine : CommandLine;
std::vector<const char *> FinalCCommandLine(CommandLine.size(), nullptr);
llvm::transform(CommandLine, FinalCCommandLine.begin(),
[](const std::string &Str) { return Str.c_str(); });
return runWithDiags(CreateAndPopulateDiagOpts(FinalCCommandLine).release(),
[&](DiagnosticConsumer &DC, DiagnosticOptions &DiagOpts) {
bool DisableFree = true;
DependencyScanningAction Action(
WorkingDirectory, Consumer, DepFS, Format,
OptimizeArgs, DisableFree, ModuleName);
ToolInvocation Invocation(FinalCommandLine, &Action,
CurrentFiles.get(),
PCHContainerOps);
Invocation.setDiagnosticConsumer(&DC);
Invocation.setDiagnosticOptions(&DiagOpts);
return Invocation.run();
});
}