blob: fc40508394c348548c3ab2a12f6115df4b60721f [file] [log] [blame]
//===--- ClangModuleDependencyScanner.cpp - Dependency Scanning -----------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2019 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file implements dependency scanning for Clang modules.
//
//===----------------------------------------------------------------------===//
#include "ImporterImpl.h"
#include "swift/AST/ModuleDependencies.h"
#include "swift/ClangImporter/ClangImporter.h"
#include "clang/Tooling/DependencyScanning/DependencyScanningService.h"
#include "clang/Tooling/DependencyScanning/DependencyScanningTool.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Signals.h"
using namespace swift;
using namespace clang::tooling;
using namespace clang::tooling::dependencies;
class swift::ClangModuleDependenciesCacheImpl {
/// Cache the names of the files used for the "import hack" to compute module
/// dependencies.
/// FIXME: This should go away once Clang's dependency scanning library
/// can scan by module name.
llvm::StringMap<std::string> importHackFileCache;
public:
/// Set containing all of the Clang modules that have already been seen.
llvm::StringSet<> alreadySeen;
DependencyScanningService service;
DependencyScanningTool tool;
ClangModuleDependenciesCacheImpl()
: importHackFileCache(),
service(ScanningMode::MinimizedSourcePreprocessing, ScanningOutputFormat::Full),
tool(service) { }
~ClangModuleDependenciesCacheImpl();
/// Retrieve the name of the file used for the "import hack" that is
/// used to scan the dependencies of a Clang module.
llvm::ErrorOr<StringRef> getImportHackFile(StringRef moduleName);
};
ClangModuleDependenciesCacheImpl::~ClangModuleDependenciesCacheImpl() {
if (!importHackFileCache.empty()) {
for (auto& it: importHackFileCache) {
llvm::sys::fs::remove(it.second);
}
}
}
llvm::ErrorOr<StringRef> ClangModuleDependenciesCacheImpl::getImportHackFile(StringRef moduleName) {
auto cacheIt = importHackFileCache.find(moduleName.str());
if (cacheIt != importHackFileCache.end())
return cacheIt->second;
// Create a temporary file.
int resultFD;
SmallString<128> resultPath;
if (auto error = llvm::sys::fs::createTemporaryFile(
"import-hack-" + moduleName.str(), "c", resultFD, resultPath))
return error;
llvm::raw_fd_ostream out(resultFD, /*shouldClose=*/true);
out << "#pragma clang module import " << moduleName.str() << ";\n";
llvm::sys::RemoveFileOnSignal(resultPath);
importHackFileCache.insert(std::make_pair(moduleName, resultPath.str().str()));
return importHackFileCache[moduleName];
}
namespace {
class SingleCommandCompilationDatabase : public CompilationDatabase {
public:
SingleCommandCompilationDatabase(CompileCommand Cmd)
: Command(std::move(Cmd)) {}
virtual std::vector<CompileCommand>
getCompileCommands(StringRef FilePath) const override {
return {Command};
}
virtual std::vector<CompileCommand> getAllCompileCommands() const override {
return {Command};
}
private:
CompileCommand Command;
};
}
// Add search paths.
// Note: This is handled differently for the Clang importer itself, which
// adds search paths to Clang's data structures rather than to its
// command line.
static void addSearchPathInvocationArguments(
std::vector<std::string> &invocationArgStrs, ASTContext &ctx) {
SearchPathOptions &searchPathOpts = ctx.SearchPathOpts;
for (const auto &framepath : searchPathOpts.FrameworkSearchPaths) {
invocationArgStrs.push_back(framepath.IsSystem ? "-iframework" : "-F");
invocationArgStrs.push_back(framepath.Path);
}
for (auto path : searchPathOpts.ImportSearchPaths) {
invocationArgStrs.push_back("-I");
invocationArgStrs.push_back(path);
}
}
/// Create the command line for Clang dependency scanning.
static std::vector<std::string> getClangDepScanningInvocationArguments(
ASTContext &ctx,
StringRef sourceFileName) {
std::vector<std::string> commandLineArgs;
// Form the basic command line.
commandLineArgs.push_back("clang");
importer::getNormalInvocationArguments(commandLineArgs, ctx);
importer::addCommonInvocationArguments(commandLineArgs, ctx);
addSearchPathInvocationArguments(commandLineArgs, ctx);
auto sourceFilePos = std::find(
commandLineArgs.begin(), commandLineArgs.end(),
"<swift-imported-modules>");
assert(sourceFilePos != commandLineArgs.end());
*sourceFilePos = sourceFileName.str();
// HACK! Drop the -fmodule-format= argument and the one that
// precedes it.
{
auto moduleFormatPos = std::find_if(commandLineArgs.begin(),
commandLineArgs.end(),
[](StringRef arg) {
return arg.startswith("-fmodule-format=");
});
assert(moduleFormatPos != commandLineArgs.end());
assert(moduleFormatPos != commandLineArgs.begin());
commandLineArgs.erase(moduleFormatPos-1, moduleFormatPos+1);
}
// HACK: No -fsyntax-only here?
{
auto syntaxOnlyPos = std::find(commandLineArgs.begin(),
commandLineArgs.end(),
"-fsyntax-only");
assert(syntaxOnlyPos != commandLineArgs.end());
*syntaxOnlyPos = "-c";
}
// HACK: Stolen from ClangScanDeps.cpp
commandLineArgs.push_back("-o");
commandLineArgs.push_back("/dev/null");
commandLineArgs.push_back("-M");
commandLineArgs.push_back("-MT");
commandLineArgs.push_back("import-hack.o");
commandLineArgs.push_back("-Xclang");
commandLineArgs.push_back("-Eonly");
commandLineArgs.push_back("-Xclang");
commandLineArgs.push_back("-sys-header-deps");
commandLineArgs.push_back("-Wno-error");
return commandLineArgs;
}
/// Get or create the Clang-specific
static ClangModuleDependenciesCacheImpl *getOrCreateClangImpl(
ModuleDependenciesCache &cache) {
auto clangImpl = cache.getClangImpl();
if (!clangImpl) {
clangImpl = new ClangModuleDependenciesCacheImpl();
cache.setClangImpl(clangImpl,
[](ClangModuleDependenciesCacheImpl *ptr) {
delete ptr;
});
}
return clangImpl;
}
/// Record the module dependencies we found by scanning Clang modules into
/// the module dependencies cache.
void ClangImporter::recordModuleDependencies(
ModuleDependenciesCache &cache,
const FullDependenciesResult &clangDependencies) {
struct ModuleInfo {
std::string PCMPath;
std::string ModuleMapPath;
};
for (const auto &clangModuleDep : clangDependencies.DiscoveredModules) {
// If we've already cached this information, we're done.
if (cache.hasDependencies(clangModuleDep.ModuleName,
ModuleDependenciesKind::Clang))
continue;
// File dependencies for this module.
std::vector<std::string> fileDeps;
for (const auto &fileDep : clangModuleDep.FileDeps) {
fileDeps.push_back(fileDep.getKey().str());
}
// Inherit all Clang driver args when creating the clang importer.
ArrayRef<std::string> allArgs = Impl.ClangArgs;
ClangImporterOptions Opts;
// Ensure the arguments we collected is sufficient to create a Clang
// invocation.
assert(createClangInvocation(this, Opts, allArgs));
std::vector<std::string> swiftArgs;
// We are using Swift frontend mode.
swiftArgs.push_back("-frontend");
// We pass the entire argument list via -Xcc, so the invocation should
// use extra clang options alone.
swiftArgs.push_back("-only-use-extra-clang-opts");
auto addClangArg = [&](StringRef arg) {
swiftArgs.push_back("-Xcc");
swiftArgs.push_back(arg.str());
};
// Add all args inheritted from creating the importer.
auto It = allArgs.begin();
while(It != allArgs.end()) {
StringRef arg = *It;
// Remove the -target arguments because we should use the target triple
// from the depending Swift modules.
if (arg == "-target") {
It += 2;
} else if (arg.startswith("-fapinotes-swift-version=")) {
// Remove the apinotes version because we should use the language version
// specified in the interface file.
It += 1;
} else {
addClangArg(*It);
++ It;
}
}
// Add all args the non-path arguments required to be passed in, according
// to the Clang scanner
for (const auto &clangArg : clangModuleDep.NonPathCommandLine) {
swiftArgs.push_back("-Xcc");
swiftArgs.push_back("-Xclang");
swiftArgs.push_back("-Xcc");
swiftArgs.push_back(clangArg);
}
// Swift frontend action: -emit-pcm
swiftArgs.push_back("-emit-pcm");
swiftArgs.push_back("-module-name");
swiftArgs.push_back(clangModuleDep.ModuleName);
// Pass down search paths to the -emit-module action.
// Unlike building Swift modules, we need to include all search paths to
// the clang invocation to build PCMs because transitive headers can only
// be found via search paths. Passing these headers as explicit inputs can
// be quite challenging.
for (auto &path: Impl.SwiftContext.SearchPathOpts.ImportSearchPaths) {
addClangArg("-I" + path);
}
for (auto &path: Impl.SwiftContext.SearchPathOpts.FrameworkSearchPaths) {
addClangArg((path.IsSystem ? "-Fsystem": "-F") + path.Path);
}
// Swift frontend option for input file path (Foo.modulemap).
swiftArgs.push_back(clangModuleDep.ClangModuleMapFile);
// Module-level dependencies.
llvm::StringSet<> alreadyAddedModules;
auto dependencies = ModuleDependencies::forClangModule(
clangModuleDep.ClangModuleMapFile,
clangModuleDep.ContextHash,
swiftArgs,
fileDeps);
for (const auto &moduleName : clangModuleDep.ClangModuleDeps) {
dependencies.addModuleDependency(moduleName.ModuleName, &alreadyAddedModules);
}
cache.recordDependencies(clangModuleDep.ModuleName,
std::move(dependencies));
}
}
Optional<ModuleDependencies> ClangImporter::getModuleDependencies(
StringRef moduleName, ModuleDependenciesCache &cache,
InterfaceSubContextDelegate &delegate) {
// Check whether there is already a cached result.
if (auto found = cache.findDependencies(
moduleName, ModuleDependenciesKind::Clang))
return found;
// Retrieve or create the shared state.
auto clangImpl = getOrCreateClangImpl(cache);
// HACK! Replace the module import buffer name with the source file hack.
auto importHackFile = clangImpl->getImportHackFile(moduleName);
if (!importHackFile) {
// FIXME: Emit a diagnostic here.
return None;
}
// Determine the command-line arguments for dependency scanning.
auto &ctx = Impl.SwiftContext;
std::vector<std::string> commandLineArgs =
getClangDepScanningInvocationArguments(ctx, *importHackFile);
std::string workingDir =
ctx.SourceMgr.getFileSystem()->getCurrentWorkingDirectory().get();
CompileCommand command(workingDir, *importHackFile, commandLineArgs, "-");
SingleCommandCompilationDatabase database(command);
auto clangDependencies = clangImpl->tool.getFullDependencies(
database, workingDir, clangImpl->alreadySeen);
if (!clangDependencies) {
// FIXME: Route this to a normal diagnostic.
llvm::logAllUnhandledErrors(clangDependencies.takeError(), llvm::errs());
return None;
}
// Record module dependencies for each module we found.
recordModuleDependencies(cache, *clangDependencies);
return cache.findDependencies(moduleName, ModuleDependenciesKind::Clang);
}
bool ClangImporter::addBridgingHeaderDependencies(
StringRef moduleName,
ModuleDependenciesCache &cache) {
auto targetModule = *cache.findDependencies(
moduleName, ModuleDependenciesKind::SwiftTextual);
// If we've already recorded bridging header dependencies, we're done.
auto swiftDeps = targetModule.getAsSwiftTextualModule();
if (!swiftDeps->bridgingSourceFiles.empty() ||
!swiftDeps->bridgingModuleDependencies.empty())
return false;
// Retrieve or create the shared state.
auto clangImpl = getOrCreateClangImpl(cache);
// Retrieve the bridging header.
std::string bridgingHeader = *targetModule.getBridgingHeader();
// Determine the command-line arguments for dependency scanning.
auto &ctx = Impl.SwiftContext;
std::vector<std::string> commandLineArgs =
getClangDepScanningInvocationArguments(ctx, bridgingHeader);
std::string workingDir =
ctx.SourceMgr.getFileSystem()->getCurrentWorkingDirectory().get();
CompileCommand command(workingDir, bridgingHeader, commandLineArgs, "-");
SingleCommandCompilationDatabase database(command);
auto clangDependencies = clangImpl->tool.getFullDependencies(
database, workingDir, clangImpl->alreadySeen);
if (!clangDependencies) {
// FIXME: Route this to a normal diagnostic.
llvm::logAllUnhandledErrors(clangDependencies.takeError(), llvm::errs());
return true;
}
// Record module dependencies for each module we found.
recordModuleDependencies(cache, *clangDependencies);
// Record dependencies for the source files the bridging header includes.
for (const auto &fileDep : clangDependencies->FullDeps.FileDeps)
targetModule.addBridgingSourceFile(fileDep);
// ... and all module dependencies.
llvm::StringSet<> alreadyAddedModules;
for (const auto &moduleDep : clangDependencies->FullDeps.ClangModuleDeps) {
targetModule.addBridgingModuleDependency(
moduleDep.ModuleName, alreadyAddedModules);
}
// Update the cache with the new information for the module.
cache.updateDependencies(
{moduleName.str(), ModuleDependenciesKind::SwiftTextual},
std::move(targetModule));
return false;
}