Files
swift-mirror/lib/DependencyScan/ScanDependencies.cpp
Artem Chikin 8cec61e50d Merge pull request #82779 from artemcm/62_DepScanImportAccessControl
[6.2 🍒][Dependency Scanning] Keep track of each imported module's access control
2025-07-07 10:53:00 -07:00

1702 lines
68 KiB
C++

//===--- ScanDependencies.cpp -- Scans the dependencies of a module -------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 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
//
//===----------------------------------------------------------------------===//
#include "swift-c/DependencyScan/DependencyScan.h"
#include "swift/AST/DiagnosticsCommon.h"
#include "swift/Basic/PrettyStackTrace.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/Decl.h"
#include "swift/AST/DiagnosticEngine.h"
#include "swift/AST/DiagnosticsDriver.h"
#include "swift/AST/DiagnosticsFrontend.h"
#include "swift/AST/DiagnosticsSema.h"
#include "swift/AST/FileSystem.h"
#include "swift/AST/Module.h"
#include "swift/AST/ModuleDependencies.h"
#include "swift/AST/ModuleLoader.h"
#include "swift/AST/SourceFile.h"
#include "swift/Basic/Assertions.h"
#include "swift/Basic/Defer.h"
#include "swift/Basic/FileTypes.h"
#include "swift/Basic/LLVM.h"
#include "swift/Basic/STLExtras.h"
#include "swift/ClangImporter/ClangImporter.h"
#include "swift/DependencyScan/DependencyScanImpl.h"
#include "swift/DependencyScan/DependencyScanJSON.h"
#include "swift/DependencyScan/DependencyScanningTool.h"
#include "swift/DependencyScan/ModuleDependencyScanner.h"
#include "swift/DependencyScan/ScanDependencies.h"
#include "swift/DependencyScan/SerializedModuleDependencyCacheFormat.h"
#include "swift/DependencyScan/StringUtils.h"
#include "swift/Frontend/CachingUtils.h"
#include "swift/Frontend/CompileJobCacheKey.h"
#include "swift/Frontend/CompileJobCacheResult.h"
#include "swift/Frontend/Frontend.h"
#include "swift/Frontend/FrontendOptions.h"
#include "swift/Frontend/ModuleInterfaceLoader.h"
#include "swift/Strings.h"
#include "clang/CAS/IncludeTree.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SetOperations.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/CAS/ActionCache.h"
#include "llvm/CAS/CASReference.h"
#include "llvm/CAS/ObjectStore.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/StringSaver.h"
#include "llvm/Support/VirtualOutputBackend.h"
#include "llvm/Support/YAMLParser.h"
#include "llvm/Support/YAMLTraits.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <set>
#include <sstream>
#include <stack>
#include <string>
using namespace swift;
using namespace swift::dependencies;
using namespace swift::c_string_utils;
using namespace llvm::yaml;
namespace {
class ExplicitModuleDependencyResolver {
public:
ExplicitModuleDependencyResolver(
const ModuleDependencyID &moduleID, ModuleDependenciesCache &cache,
CompilerInstance &instance, std::optional<SwiftDependencyTracker> tracker)
: moduleID(moduleID), cache(cache), instance(instance),
resolvingDepInfo(cache.findKnownDependency(moduleID)),
tracker(std::move(tracker)) {
// Copy commandline.
commandline = resolvingDepInfo.getCommandline();
}
// Resolve the dependencies for the current moduleID. Return true on error.
bool resolve(const std::set<ModuleDependencyID> &dependencies,
std::optional<std::set<ModuleDependencyID>> bridgingHeaderDeps) {
// No need to resolve dependency for placeholder.
if (moduleID.Kind == ModuleDependencyKind::SwiftPlaceholder)
return false;
// If the dependency is already finalized, nothing needs to be done.
if (resolvingDepInfo.isFinalized())
return false;
for (const auto &depModuleID : dependencies) {
const auto &depInfo = cache.findKnownDependency(depModuleID);
switch (depModuleID.Kind) {
case swift::ModuleDependencyKind::SwiftInterface: {
auto interfaceDepDetails = depInfo.getAsSwiftInterfaceModule();
assert(interfaceDepDetails && "Expected Swift Interface dependency.");
if (handleSwiftInterfaceModuleDependency(depModuleID,
*interfaceDepDetails))
return true;
} break;
case swift::ModuleDependencyKind::SwiftBinary: {
auto binaryDepDetails = depInfo.getAsSwiftBinaryModule();
assert(binaryDepDetails && "Expected Swift Binary Module dependency.");
if (handleSwiftBinaryModuleDependency(depModuleID, *binaryDepDetails))
return true;
} break;
case swift::ModuleDependencyKind::SwiftPlaceholder: {
auto placeholderDetails = depInfo.getAsPlaceholderDependencyModule();
assert(placeholderDetails && "Expected Swift Placeholder dependency.");
if (handleSwiftPlaceholderModuleDependency(depModuleID,
*placeholderDetails))
return true;
} break;
case swift::ModuleDependencyKind::Clang: {
auto clangDepDetails = depInfo.getAsClangModule();
assert(clangDepDetails && "Expected Clang Module dependency.");
if (handleClangModuleDependency(depModuleID, *clangDepDetails))
return true;
} break;
case swift::ModuleDependencyKind::SwiftSource: {
auto sourceDepDetails = depInfo.getAsSwiftSourceModule();
assert(sourceDepDetails && "Expected Swift Source Module dependency.");
if (handleSwiftSourceModuleDependency(depModuleID, *sourceDepDetails))
return true;
} break;
default:
llvm_unreachable("Unhandled dependency kind.");
}
}
// Update bridging header build command if there is a bridging header
// dependency.
if (addBridgingHeaderDeps(resolvingDepInfo))
return true;
if (bridgingHeaderDeps) {
bridgingHeaderBuildCmd =
resolvingDepInfo.getBridgingHeaderCommandline();
for (auto bridgingDep : *bridgingHeaderDeps) {
auto &dep = cache.findKnownDependency(bridgingDep);
auto *clangDep = dep.getAsClangModule();
assert(clangDep && "wrong module dependency kind");
if (!clangDep->moduleCacheKey.empty()) {
bridgingHeaderBuildCmd.push_back("-Xcc");
bridgingHeaderBuildCmd.push_back("-fmodule-file-cache-key");
bridgingHeaderBuildCmd.push_back("-Xcc");
bridgingHeaderBuildCmd.push_back(clangDep->mappedPCMPath);
bridgingHeaderBuildCmd.push_back("-Xcc");
bridgingHeaderBuildCmd.push_back(clangDep->moduleCacheKey);
}
}
addDeterministicCheckFlags(bridgingHeaderBuildCmd);
}
SwiftInterfaceModuleOutputPathResolution::ResultTy swiftInterfaceOutputPath;
if (resolvingDepInfo.isSwiftInterfaceModule()) {
pruneUnusedVFSOverlay(swiftInterfaceOutputPath);
addSwiftInterfaceModuleOutputPathToCommandLine(swiftInterfaceOutputPath);
}
// Update the dependency in the cache with the modified command-line.
if (resolvingDepInfo.isSwiftInterfaceModule() ||
resolvingDepInfo.isClangModule()) {
if (cache.getScanService().hasPathMapping())
commandline =
remapPathsFromCommandLine(commandline, [&](StringRef path) {
return cache.getScanService().remapPath(path);
});
addDeterministicCheckFlags(commandline);
}
auto dependencyInfoCopy = resolvingDepInfo;
if (finalize(dependencyInfoCopy, swiftInterfaceOutputPath))
return true;
dependencyInfoCopy.setIsFinalized(true);
cache.updateDependency(moduleID, dependencyInfoCopy);
return false;
}
private:
// Finalize the resolving dependency info.
bool finalize(ModuleDependencyInfo &depInfo,
const SwiftInterfaceModuleOutputPathResolution::ResultTy
&swiftInterfaceModuleOutputPath) {
if (resolvingDepInfo.isSwiftPlaceholderModule())
return false;
if (resolvingDepInfo.isSwiftInterfaceModule())
depInfo.setOutputPathAndHash(
swiftInterfaceModuleOutputPath.outputPath.str().str(),
swiftInterfaceModuleOutputPath.hash.str());
// Add macros.
for (auto &macro : macros)
depInfo.addMacroDependency(macro.first(), macro.second.LibraryPath,
macro.second.ExecutablePath);
bool needPathRemapping = instance.getInvocation()
.getSearchPathOptions()
.ResolvedPluginVerification &&
cache.getScanService().hasPathMapping();
auto mapPath = [&](StringRef path) {
if (!needPathRemapping)
return path.str();
return cache.getScanService().remapPath(path);
};
if (needPathRemapping)
commandline.push_back("-resolved-plugin-verification");
for (auto &macro : depInfo.getMacroDependencies()) {
std::string arg = mapPath(macro.second.LibraryPath) + "#" +
mapPath(macro.second.ExecutablePath) + "#" +
macro.first;
commandline.push_back("-load-resolved-plugin");
commandline.push_back(arg);
}
// Update CAS dependencies.
if (auto err = collectCASDependencies(depInfo))
return err;
if (!bridgingHeaderBuildCmd.empty())
depInfo.updateBridgingHeaderCommandLine(bridgingHeaderBuildCmd);
if (!resolvingDepInfo.isSwiftBinaryModule()) {
depInfo.updateCommandLine(commandline);
if (updateModuleCacheKey(depInfo))
return true;
}
return false;
}
bool handleSwiftInterfaceModuleDependency(
ModuleDependencyID depModuleID,
const SwiftInterfaceModuleDependenciesStorage &interfaceDepDetails) {
if (!resolvingDepInfo.isSwiftSourceModule()) {
auto &path = interfaceDepDetails.moduleCacheKey.empty()
? interfaceDepDetails.moduleOutputPath
: interfaceDepDetails.moduleCacheKey;
commandline.push_back("-swift-module-file=" + depModuleID.ModuleName +
"=" + path);
}
addMacroDependencies(depModuleID, interfaceDepDetails);
return false;
}
bool handleSwiftBinaryModuleDependency(
ModuleDependencyID depModuleID,
const SwiftBinaryModuleDependencyStorage &binaryDepDetails) {
if (!resolvingDepInfo.isSwiftSourceModule()) {
auto &path = binaryDepDetails.moduleCacheKey.empty()
? binaryDepDetails.compiledModulePath
: binaryDepDetails.moduleCacheKey;
commandline.push_back("-swift-module-file=" + depModuleID.ModuleName +
"=" + path);
// If this binary module was built with a header, the header's module
// dependencies must also specify a .modulemap to the compilation, in
// order to resolve the header's own header include directives.
for (const auto &bridgingHeaderDepID :
binaryDepDetails.headerModuleDependencies) {
auto optionalBridgingHeaderDepModuleInfo =
cache.findKnownDependency(bridgingHeaderDepID);
const auto bridgingHeaderDepModuleDetails =
optionalBridgingHeaderDepModuleInfo.getAsClangModule();
commandline.push_back("-Xcc");
commandline.push_back(
"-fmodule-map-file=" +
cache.getScanService().remapPath(
bridgingHeaderDepModuleDetails->moduleMapFile));
}
}
addMacroDependencies(depModuleID, binaryDepDetails);
return false;
}
bool handleSwiftPlaceholderModuleDependency(
ModuleDependencyID depModuleID,
const SwiftPlaceholderModuleDependencyStorage &placeholderDetails) {
if (!resolvingDepInfo.isSwiftSourceModule())
commandline.push_back("-swift-module-file=" + depModuleID.ModuleName +
"=" + placeholderDetails.compiledModulePath);
return false;
}
bool handleClangModuleDependency(
ModuleDependencyID depModuleID,
const ClangModuleDependencyStorage &clangDepDetails) {
if (!resolvingDepInfo.isSwiftSourceModule()) {
if (!resolvingDepInfo.isClangModule()) {
commandline.push_back("-Xcc");
commandline.push_back("-fmodule-file=" + depModuleID.ModuleName + "=" +
clangDepDetails.mappedPCMPath);
}
if (!clangDepDetails.moduleCacheKey.empty()) {
commandline.push_back("-Xcc");
commandline.push_back("-fmodule-file-cache-key");
commandline.push_back("-Xcc");
commandline.push_back(clangDepDetails.mappedPCMPath);
commandline.push_back("-Xcc");
commandline.push_back(clangDepDetails.moduleCacheKey);
}
}
// Collect CAS deppendencies from clang modules.
if (!clangDepDetails.CASClangIncludeTreeRootID.empty()) {
if (addIncludeTree(clangDepDetails.CASClangIncludeTreeRootID))
return true;
}
collectUsedVFSOverlay(clangDepDetails);
return false;
}
bool handleSwiftSourceModuleDependency(
ModuleDependencyID depModuleID,
const SwiftSourceModuleDependenciesStorage &sourceDepDetails) {
addMacroDependencies(depModuleID, sourceDepDetails);
return addBridgingHeaderDeps(sourceDepDetails);
}
bool addBridgingHeaderDeps(const ModuleDependencyInfo &depInfo) {
auto sourceDepDetails = depInfo.getAsSwiftSourceModule();
if (!sourceDepDetails)
return false;
return addBridgingHeaderDeps(*sourceDepDetails);
}
bool addBridgingHeaderDeps(
const SwiftSourceModuleDependenciesStorage &sourceDepDetails) {
if (sourceDepDetails.textualModuleDetails.CASBridgingHeaderIncludeTreeRootID
.empty()) {
if (!sourceDepDetails.textualModuleDetails.bridgingSourceFiles.empty()) {
if (tracker) {
tracker->startTracking(/*includeCommonDeps*/ false);
for (auto &file :
sourceDepDetails.textualModuleDetails.bridgingSourceFiles)
tracker->trackFile(file);
auto bridgeRoot = tracker->createTreeFromDependencies();
if (!bridgeRoot)
return diagnoseCASFSCreationError(bridgeRoot.takeError());
fileListRefs.push_back(bridgeRoot->getRef());
}
}
} else if (addIncludeTree(sourceDepDetails.textualModuleDetails
.CASBridgingHeaderIncludeTreeRootID))
return true;
return false;
};
void addMacroDependencies(ModuleDependencyID moduleID,
const ModuleDependencyInfoStorageBase &dep) {
auto directDeps = cache.getAllDependencies(this->moduleID);
if (llvm::find(directDeps, moduleID) == directDeps.end())
return;
for (auto &entry : dep.macroDependencies)
macros.insert({entry.first,
{entry.second.LibraryPath, entry.second.ExecutablePath}});
}
static bool isVFSOverlayFlag(StringRef arg) {
return arg == "-ivfsoverlay" || arg == "-vfsoverlay";
};
static bool isXCCArg(StringRef arg) { return arg == "-Xcc"; };
void
collectUsedVFSOverlay(const ClangModuleDependencyStorage &clangDepDetails) {
// true if the previous argument was the dash-option of an option pair
bool getNext = false;
for (const auto &A : clangDepDetails.buildCommandLine) {
StringRef arg(A);
if (isXCCArg(arg))
continue;
if (getNext) {
getNext = false;
usedVFSOverlayPaths.insert(arg);
} else if (isVFSOverlayFlag(arg))
getNext = true;
}
}
void pruneUnusedVFSOverlay(
SwiftInterfaceModuleOutputPathResolution::ResultTy &outputPath) {
// Pruning of unused VFS overlay options for Clang dependencies is performed
// by the Clang dependency scanner.
if (moduleID.Kind == ModuleDependencyKind::Clang)
return;
// Prune the command line.
std::vector<std::string> resolvedCommandLine;
size_t skip = 0;
for (auto it = commandline.begin(), end = commandline.end(); it != end;
it++) {
if (skip) {
skip--;
continue;
}
// If this VFS overlay was not used across any of the dependencies, skip
// it.
if ((it + 1) != end && isXCCArg(*it) && isVFSOverlayFlag(*(it + 1))) {
assert(it + 2 != end); // Extra -Xcc
assert(it + 3 != end); // Actual VFS overlay path argument
if (!usedVFSOverlayPaths.contains(*(it + 3))) {
skip = 3;
continue;
}
}
resolvedCommandLine.push_back(*it);
}
commandline = std::move(resolvedCommandLine);
// Prune the clang impoter options. We do not need to deal with -Xcc because
// these are clang options.
const auto &CI = instance.getInvocation();
SwiftInterfaceModuleOutputPathResolution::ArgListTy extraArgsList;
const auto &clangImporterOptions =
CI.getClangImporterOptions()
.getReducedExtraArgsForSwiftModuleDependency();
skip = 0;
for (auto it = clangImporterOptions.begin(),
end = clangImporterOptions.end();
it != end; it++) {
if (skip) {
skip = 0;
continue;
}
if ((it + 1) != end && isVFSOverlayFlag(*it)) {
if (!usedVFSOverlayPaths.contains(*(it + 1))) {
skip = 1;
continue;
}
}
extraArgsList.push_back(*it);
}
auto swiftTextualDeps = resolvingDepInfo.getAsSwiftInterfaceModule();
auto &interfacePath = swiftTextualDeps->swiftInterfaceFile;
auto sdkPath = instance.getASTContext().SearchPathOpts.getSDKPath();
SwiftInterfaceModuleOutputPathResolution::setOutputPath(
outputPath, moduleID.ModuleName, interfacePath, sdkPath, CI,
extraArgsList);
return;
}
void addSwiftInterfaceModuleOutputPathToCommandLine(
const SwiftInterfaceModuleOutputPathResolution::ResultTy &outputPath) {
StringRef outputName = outputPath.outputPath.str();
commandline.push_back("-o");
commandline.push_back(outputName.str());
return;
}
bool collectCASDependencies(ModuleDependencyInfo &dependencyInfoCopy) {
if (!instance.getInvocation().getCASOptions().EnableCaching)
return false;
// Collect CAS info from current resolving module.
if (auto *sourceDep = resolvingDepInfo.getAsSwiftSourceModule()) {
tracker->startTracking();
llvm::for_each(
sourceDep->sourceFiles,
[this](const std::string &file) { tracker->trackFile(file); });
llvm::for_each(
sourceDep->auxiliaryFiles,
[this](const std::string &file) { tracker->trackFile(file); });
llvm::for_each(dependencyInfoCopy.getMacroDependencies(),
[this](const auto &entry) {
tracker->trackFile(entry.second.LibraryPath);
});
auto root = tracker->createTreeFromDependencies();
if (!root)
return diagnoseCASFSCreationError(root.takeError());
fileListRefs.push_back(root->getRef());
} else if (auto *textualDep =
resolvingDepInfo.getAsSwiftInterfaceModule()) {
tracker->startTracking();
tracker->trackFile(textualDep->swiftInterfaceFile);
llvm::for_each(
textualDep->auxiliaryFiles,
[this](const std::string &file) { tracker->trackFile(file); });
llvm::for_each(dependencyInfoCopy.getMacroDependencies(),
[this](const auto &entry) {
tracker->trackFile(entry.second.LibraryPath);
});
auto root = tracker->createTreeFromDependencies();
if (!root)
return diagnoseCASFSCreationError(root.takeError());
fileListRefs.push_back(root->getRef());
}
// Update build command line.
if (resolvingDepInfo.isSwiftInterfaceModule() ||
resolvingDepInfo.isSwiftSourceModule()) {
// Update with casfs option.
if (computeCASFileSystem(dependencyInfoCopy))
return true;
}
// Compute and update module cache key.
if (auto *binaryDep = dependencyInfoCopy.getAsSwiftBinaryModule()) {
if (setupBinaryCacheKey(binaryDep->compiledModulePath,
dependencyInfoCopy))
return true;
}
return false;
}
bool updateModuleCacheKey(ModuleDependencyInfo &depInfo) {
if (!instance.getInvocation().getCASOptions().EnableCaching)
return false;
auto &CAS = cache.getScanService().getCAS();
auto commandLine = depInfo.getCommandline();
std::vector<const char *> Args;
if (commandLine.size() > 1)
for (auto &c : ArrayRef<std::string>(commandLine).drop_front(1))
Args.push_back(c.c_str());
auto base = createCompileJobBaseCacheKey(CAS, Args);
if (!base) {
instance.getDiags().diagnose(SourceLoc(), diag::error_cache_key_creation,
moduleID.ModuleName,
toString(base.takeError()));
return true;
}
// Module compilation commands always have only one input and the input
// index is always 0.
auto key = createCompileJobCacheKeyForOutput(CAS, *base, /*InputIndex=*/0);
if (!key) {
instance.getDiags().diagnose(SourceLoc(), diag::error_cache_key_creation,
moduleID.ModuleName,
toString(key.takeError()));
return true;
}
depInfo.updateModuleCacheKey(CAS.getID(*key).toString());
return false;
}
bool setupBinaryCacheKey(StringRef path, ModuleDependencyInfo &depInfo) {
auto &CASFS = cache.getScanService().getSharedCachingFS();
auto &CAS = cache.getScanService().getCAS();
// For binary module, we need to make sure the lookup key is setup here in
// action cache. We just use the CASID of the binary module itself as key.
auto Ref = CASFS.getObjectRefForFileContent(path);
if (!Ref) {
instance.getDiags().diagnose(SourceLoc(), diag::error_cas_file_ref, path);
return true;
}
assert(*Ref && "Binary module should be loaded into CASFS already");
depInfo.updateModuleCacheKey(CAS.getID(**Ref).toString());
swift::cas::CompileJobCacheResult::Builder Builder;
Builder.addOutput(file_types::ID::TY_SwiftModuleFile, **Ref);
auto Result = Builder.build(CAS);
if (!Result) {
instance.getDiags().diagnose(SourceLoc(), diag::error_cas,
"adding binary module dependencies",
toString(Result.takeError()));
return true;
}
if (auto E = instance.getActionCache().put(CAS.getID(**Ref),
CAS.getID(*Result))) {
instance.getDiags().diagnose(
SourceLoc(), diag::error_cas,
"adding binary module dependencies cache entry",
toString(std::move(E)));
return true;
}
return false;
}
bool diagnoseCASFSCreationError(llvm::Error err) {
if (!err)
return false;
instance.getDiags().diagnose(SourceLoc(), diag::error_cas_fs_creation,
toString(std::move(err)));
return true;
}
void addDeterministicCheckFlags(std::vector<std::string> &cmd) {
// Propagate the deterministic check to explicit built module command.
if (!instance.getInvocation().getFrontendOptions().DeterministicCheck)
return;
cmd.push_back("-enable-deterministic-check");
cmd.push_back("-always-compile-output-files");
// disable cache replay because that defeat the purpose of the check.
if (instance.getInvocation().getCASOptions().EnableCaching)
cmd.push_back("-cache-disable-replay");
}
bool addIncludeTree(StringRef includeTree) {
auto &db = cache.getScanService().getCAS();
auto casID = db.parseID(includeTree);
if (!casID) {
instance.getDiags().diagnose(SourceLoc(), diag::error_invalid_cas_id,
includeTree, toString(casID.takeError()));
return true;
}
auto ref = db.getReference(*casID);
if (!ref) {
instance.getDiags().diagnose(SourceLoc(), diag::error_load_input_from_cas,
includeTree);
return true;
}
auto root = clang::cas::IncludeTreeRoot::get(db, *ref);
if (!root) {
instance.getDiags().diagnose(SourceLoc(), diag::error_cas_malformed_input,
includeTree, toString(root.takeError()));
return true;
}
fileListRefs.push_back(root->getFileListRef());
return false;
}
bool computeCASFileSystem(ModuleDependencyInfo &dependencyInfoCopy) {
if (fileListRefs.empty())
return false;
auto &db = cache.getScanService().getCAS();
auto casFS =
clang::cas::IncludeTree::FileList::create(db, {}, fileListRefs);
if (!casFS) {
instance.getDiags().diagnose(SourceLoc(), diag::error_cas,
"CAS IncludeTree FileList creation",
toString(casFS.takeError()));
return true;
}
auto casID = casFS->getID().toString();
dependencyInfoCopy.updateCASFileSystemRootID(casID);
commandline.push_back("-clang-include-tree-filelist");
commandline.push_back(casID);
return false;
}
private:
const ModuleDependencyID &moduleID;
ModuleDependenciesCache &cache;
CompilerInstance &instance;
const ModuleDependencyInfo &resolvingDepInfo;
std::optional<SwiftDependencyTracker> tracker;
std::vector<llvm::cas::ObjectRef> fileListRefs;
std::vector<std::string> commandline;
std::vector<std::string> bridgingHeaderBuildCmd;
llvm::StringMap<MacroPluginDependency> macros;
llvm::StringSet<> usedVFSOverlayPaths;
};
static bool resolveExplicitModuleInputs(
const ModuleDependencyID &moduleID,
const std::set<ModuleDependencyID> &dependencies,
ModuleDependenciesCache &cache, CompilerInstance &instance,
std::optional<std::set<ModuleDependencyID>> bridgingHeaderDeps,
std::optional<SwiftDependencyTracker> tracker) {
ExplicitModuleDependencyResolver resolver(moduleID, cache, instance,
std::move(tracker));
return resolver.resolve(dependencies, bridgingHeaderDeps);
}
static bool writePrescanJSONToOutput(DiagnosticEngine &diags,
llvm::vfs::OutputBackend &backend,
StringRef path,
swiftscan_import_set_t importSet) {
return withOutputPath(diags, backend, path, [&](llvm::raw_pwrite_stream &os) {
writePrescanJSON(os, importSet);
return false;
});
}
static bool writeJSONToOutput(DiagnosticEngine &diags,
llvm::vfs::OutputBackend &backend, StringRef path,
swiftscan_dependency_graph_t dependencies) {
return withOutputPath(diags, backend, path, [&](llvm::raw_pwrite_stream &os) {
writeJSON(os, dependencies);
return false;
});
}
static void bridgeDependencyIDs(const ArrayRef<ModuleDependencyID> dependencies,
std::vector<std::string> &bridgedDependencyNames) {
for (const auto &dep : dependencies) {
std::string dependencyKindAndName;
switch (dep.Kind) {
case ModuleDependencyKind::SwiftInterface:
case ModuleDependencyKind::SwiftSource:
dependencyKindAndName = "swiftTextual";
break;
case ModuleDependencyKind::SwiftBinary:
dependencyKindAndName = "swiftBinary";
break;
case ModuleDependencyKind::SwiftPlaceholder:
dependencyKindAndName = "swiftPlaceholder";
break;
case ModuleDependencyKind::Clang:
dependencyKindAndName = "clang";
break;
default:
llvm_unreachable("Unhandled dependency kind.");
}
dependencyKindAndName += ":";
dependencyKindAndName += dep.ModuleName;
bridgedDependencyNames.push_back(dependencyKindAndName);
}
}
static swiftscan_macro_dependency_set_t *createMacroDependencySet(
const std::map<std::string, MacroPluginDependency> &macroDeps) {
if (macroDeps.empty())
return nullptr;
swiftscan_macro_dependency_set_t *set = new swiftscan_macro_dependency_set_t;
set->count = macroDeps.size();
set->macro_dependencies = new swiftscan_macro_dependency_t[set->count];
unsigned SI = 0;
for (auto &entry : macroDeps) {
set->macro_dependencies[SI] = new swiftscan_macro_dependency_s;
set->macro_dependencies[SI]->module_name = create_clone(entry.first.c_str());
set->macro_dependencies[SI]->library_path =
create_clone(entry.second.LibraryPath.c_str());
set->macro_dependencies[SI]->executable_path =
create_clone(entry.second.ExecutablePath.c_str());
++ SI;
}
return set;
}
static swiftscan_dependency_graph_t
generateFullDependencyGraph(const CompilerInstance &instance,
const DependencyScanDiagnosticCollector *diagnosticCollector,
const ModuleDependenciesCache &cache,
const ArrayRef<ModuleDependencyID> allModules) {
if (allModules.empty()) {
return nullptr;
}
std::string mainModuleName = allModules.front().ModuleName;
swiftscan_dependency_set_t *dependencySet = new swiftscan_dependency_set_t;
dependencySet->count = allModules.size();
dependencySet->modules =
new swiftscan_dependency_info_t[dependencySet->count];
for (size_t i = 0; i < allModules.size(); ++i) {
const auto &moduleID = allModules[i];
auto &moduleDependencyInfo = cache.findKnownDependency(moduleID);
// Collect all the required pieces to build a ModuleInfo
auto swiftPlaceholderDeps = moduleDependencyInfo.getAsPlaceholderDependencyModule();
auto swiftTextualDeps = moduleDependencyInfo.getAsSwiftInterfaceModule();
auto swiftSourceDeps = moduleDependencyInfo.getAsSwiftSourceModule();
auto swiftBinaryDeps = moduleDependencyInfo.getAsSwiftBinaryModule();
auto clangDeps = moduleDependencyInfo.getAsClangModule();
// ModulePath
const char *modulePathSuffix =
moduleDependencyInfo.isSwiftModule() ? ".swiftmodule" : ".pcm";
std::string modulePath;
if (swiftTextualDeps)
modulePath = swiftTextualDeps->moduleOutputPath;
else if (swiftPlaceholderDeps)
modulePath = swiftPlaceholderDeps->compiledModulePath;
else if (swiftBinaryDeps)
modulePath = swiftBinaryDeps->compiledModulePath;
else if (clangDeps)
modulePath = clangDeps->pcmOutputPath;
else
modulePath = moduleID.ModuleName + modulePathSuffix;
// SourceFiles
std::vector<std::string> sourceFiles;
if (swiftSourceDeps) {
sourceFiles = swiftSourceDeps->sourceFiles;
} else if (clangDeps) {
sourceFiles = clangDeps->fileDependencies;
}
auto directDependencies = cache.getAllDependencies(moduleID);
std::vector<std::string> clangHeaderDependencyNames;
for (const auto &headerDepID : moduleDependencyInfo.getHeaderClangDependencies())
clangHeaderDependencyNames.push_back(headerDepID.ModuleName);
// Generate a swiftscan_clang_details_t object based on the dependency kind
auto getModuleDetails = [&]() -> swiftscan_module_details_t {
swiftscan_module_details_s *details = new swiftscan_module_details_s;
if (swiftTextualDeps) {
swiftscan_string_ref_t moduleInterfacePath =
create_clone(swiftTextualDeps->swiftInterfaceFile.c_str());
swiftscan_string_ref_t bridgingHeaderPath =
swiftTextualDeps->textualModuleDetails.bridgingHeaderFile
.has_value()
? create_clone(
swiftTextualDeps->textualModuleDetails.bridgingHeaderFile
.value()
.c_str())
: create_null();
details->kind = SWIFTSCAN_DEPENDENCY_INFO_SWIFT_TEXTUAL;
// Create an overlay dependencies set according to the output format
std::vector<std::string> bridgedOverlayDependencyNames;
bridgeDependencyIDs(swiftTextualDeps->swiftOverlayDependencies,
bridgedOverlayDependencyNames);
details->swift_textual_details = {
moduleInterfacePath,
create_set(swiftTextualDeps->compiledModuleCandidates),
bridgingHeaderPath,
create_set(
swiftTextualDeps->textualModuleDetails.bridgingSourceFiles),
create_set(clangHeaderDependencyNames),
create_set(bridgedOverlayDependencyNames),
/*sourceImportedDependencies*/ create_set({}),
create_set(swiftTextualDeps->textualModuleDetails.buildCommandLine),
/*bridgingHeaderBuildCommand*/ create_set({}),
create_clone(swiftTextualDeps->contextHash.c_str()),
swiftTextualDeps->isFramework,
swiftTextualDeps->isStatic,
create_clone(swiftTextualDeps->textualModuleDetails
.CASFileSystemRootID.c_str()),
create_clone(swiftTextualDeps->textualModuleDetails
.CASBridgingHeaderIncludeTreeRootID.c_str()),
create_clone(swiftTextualDeps->moduleCacheKey.c_str()),
createMacroDependencySet(swiftTextualDeps->macroDependencies),
create_clone(swiftTextualDeps->userModuleVersion.c_str()),
/*chained_bridging_header_path=*/create_clone(""),
/*chained_bridging_header_content=*/create_clone("")};
} else if (swiftSourceDeps) {
swiftscan_string_ref_t moduleInterfacePath = create_null();
swiftscan_string_ref_t bridgingHeaderPath =
swiftSourceDeps->textualModuleDetails.bridgingHeaderFile.has_value()
? create_clone(
swiftSourceDeps->textualModuleDetails.bridgingHeaderFile.value().c_str())
: create_null();
details->kind = SWIFTSCAN_DEPENDENCY_INFO_SWIFT_TEXTUAL;
// Create an overlay dependencies set according to the output format
std::vector<std::string> bridgedOverlayDependencyNames;
bridgeDependencyIDs(swiftSourceDeps->swiftOverlayDependencies,
bridgedOverlayDependencyNames);
// Create a set of directly-source-imported dependencies
std::vector<ModuleDependencyID> sourceImportDependencies;
std::copy(swiftSourceDeps->importedSwiftModules.begin(),
swiftSourceDeps->importedSwiftModules.end(),
std::back_inserter(sourceImportDependencies));
std::copy(swiftSourceDeps->importedClangModules.begin(),
swiftSourceDeps->importedClangModules.end(),
std::back_inserter(sourceImportDependencies));
std::vector<std::string> bridgedSourceImportedDependencyNames;
bridgeDependencyIDs(sourceImportDependencies,
bridgedSourceImportedDependencyNames);
details->swift_textual_details = {
moduleInterfacePath, create_empty_set(), bridgingHeaderPath,
create_set(
swiftSourceDeps->textualModuleDetails.bridgingSourceFiles),
create_set(clangHeaderDependencyNames),
create_set(bridgedOverlayDependencyNames),
create_set(bridgedSourceImportedDependencyNames),
create_set(swiftSourceDeps->textualModuleDetails.buildCommandLine),
create_set(swiftSourceDeps->bridgingHeaderBuildCommandLine),
/*contextHash*/
create_clone(
instance.getInvocation().getModuleScanningHash().c_str()),
/*isFramework*/ false,
/*isStatic*/ false,
/*CASFS*/
create_clone(swiftSourceDeps->textualModuleDetails
.CASFileSystemRootID.c_str()),
/*IncludeTree*/
create_clone(swiftSourceDeps->textualModuleDetails
.CASBridgingHeaderIncludeTreeRootID.c_str()),
/*CacheKey*/ create_clone(""),
createMacroDependencySet(swiftSourceDeps->macroDependencies),
/*userModuleVersion*/ create_clone(""),
create_clone(swiftSourceDeps->chainedBridgingHeaderPath.c_str()),
create_clone(
swiftSourceDeps->chainedBridgingHeaderContent.c_str())};
} else if (swiftPlaceholderDeps) {
details->kind = SWIFTSCAN_DEPENDENCY_INFO_SWIFT_PLACEHOLDER;
details->swift_placeholder_details = {
create_clone(swiftPlaceholderDeps->compiledModulePath.c_str()),
create_clone(swiftPlaceholderDeps->moduleDocPath.c_str()),
create_clone(swiftPlaceholderDeps->sourceInfoPath.c_str())};
} else if (swiftBinaryDeps) {
details->kind = SWIFTSCAN_DEPENDENCY_INFO_SWIFT_BINARY;
// Create an overlay dependencies set according to the output format
std::vector<std::string> bridgedOverlayDependencyNames;
bridgeDependencyIDs(swiftBinaryDeps->swiftOverlayDependencies,
bridgedOverlayDependencyNames);
details->swift_binary_details = {
create_clone(swiftBinaryDeps->compiledModulePath.c_str()),
create_clone(swiftBinaryDeps->moduleDocPath.c_str()),
create_clone(swiftBinaryDeps->sourceInfoPath.c_str()),
create_set(bridgedOverlayDependencyNames),
create_clone(swiftBinaryDeps->headerImport.c_str()),
create_set(clangHeaderDependencyNames),
create_set(swiftBinaryDeps->headerSourceFiles),
swiftBinaryDeps->isFramework,
swiftBinaryDeps->isStatic,
createMacroDependencySet(swiftBinaryDeps->macroDependencies),
create_clone(swiftBinaryDeps->moduleCacheKey.c_str()),
create_clone(swiftBinaryDeps->userModuleVersion.c_str())};
} else {
// Clang module details
details->kind = SWIFTSCAN_DEPENDENCY_INFO_CLANG;
details->clang_details = {
create_clone(clangDeps->moduleMapFile.c_str()),
create_clone(clangDeps->contextHash.c_str()),
create_set(clangDeps->buildCommandLine),
create_clone(clangDeps->CASFileSystemRootID.c_str()),
create_clone(clangDeps->CASClangIncludeTreeRootID.c_str()),
create_clone(clangDeps->moduleCacheKey.c_str())};
}
return details;
};
swiftscan_dependency_info_s *moduleInfo = new swiftscan_dependency_info_s;
dependencySet->modules[i] = moduleInfo;
std::string encodedModuleName = createEncodedModuleKindAndName(moduleID);
auto ttt = create_clone(encodedModuleName.c_str());
moduleInfo->module_name = ttt;
moduleInfo->module_path = create_clone(modulePath.c_str());
moduleInfo->source_files = create_set(sourceFiles);
// Create a direct dependencies set according to the output format
std::vector<std::string> bridgedDependencyNames;
bridgeDependencyIDs(directDependencies.getArrayRef(),
bridgedDependencyNames);
moduleInfo->direct_dependencies = create_set(bridgedDependencyNames);
moduleInfo->details = getModuleDetails();
// Create a link libraries set for this module
auto linkLibraries = moduleDependencyInfo.getLinkLibraries();
swiftscan_link_library_set_t *linkLibrarySet =
new swiftscan_link_library_set_t;
linkLibrarySet->count = linkLibraries.size();
linkLibrarySet->link_libraries =
new swiftscan_link_library_info_t[linkLibrarySet->count];
for (size_t i = 0; i < linkLibraries.size(); ++i) {
const auto &ll = linkLibraries[i];
swiftscan_link_library_info_s *llInfo = new swiftscan_link_library_info_s;
llInfo->name = create_clone(ll.getName().str().c_str());
llInfo->isStatic = ll.isStaticLibrary();
llInfo->isFramework = ll.getKind() == LibraryKind::Framework;
llInfo->forceLoad = ll.shouldForceLoad();
linkLibrarySet->link_libraries[i] = llInfo;
}
moduleInfo->link_libraries = linkLibrarySet;
// Create source import infos set for this module
auto imports = moduleDependencyInfo.getModuleImports();
swiftscan_import_info_set_t *importInfoSet =
new swiftscan_import_info_set_t;
importInfoSet->count = imports.size();
importInfoSet->imports = new swiftscan_import_info_t[importInfoSet->count];
for (size_t i = 0; i < imports.size(); ++i) {
const auto &ii = imports[i];
swiftscan_import_info_s *iInfo = new swiftscan_import_info_s;
iInfo->import_identifier = create_clone(ii.importIdentifier.c_str());
iInfo->access_level = static_cast<swiftscan_access_level_t>(ii.accessLevel);
const auto &sourceLocations = ii.importLocations;
swiftscan_source_location_set_t *sourceLocSet =
new swiftscan_source_location_set_t;
sourceLocSet->count = sourceLocations.size();
sourceLocSet->source_locations =
new swiftscan_source_location_t[sourceLocSet->count];
for (size_t j = 0; j < sourceLocations.size(); ++j) {
const auto &sl = sourceLocations[j];
swiftscan_source_location_s *slInfo = new swiftscan_source_location_s;
slInfo->buffer_identifier = create_clone(sl.bufferIdentifier.c_str());
slInfo->line_number = sl.lineNumber;
slInfo->column_number = sl.columnNumber;
sourceLocSet->source_locations[j] = slInfo;
}
iInfo->source_locations = sourceLocSet;
importInfoSet->imports[i] = iInfo;
}
moduleInfo->imports = importInfoSet;
}
swiftscan_dependency_graph_t result = new swiftscan_dependency_graph_s;
result->main_module_name = create_clone(mainModuleName.c_str());
result->dependencies = dependencySet;
result->diagnostics =
diagnosticCollector
? mapCollectedDiagnosticsForOutput(diagnosticCollector)
: nullptr;
return result;
}
/// Implements a topological sort via recursion and reverse postorder DFS.
/// Does not bother handling cycles, relying on a DAG guarantee by the client.
static std::vector<ModuleDependencyID>
computeTopologicalSortOfExplicitDependencies(
const std::vector<ModuleDependencyID> &allModules,
const ModuleDependenciesCache &cache) {
std::unordered_set<ModuleDependencyID> visited;
std::vector<ModuleDependencyID> result;
std::stack<ModuleDependencyID> stack;
// Must be explicitly-typed to allow recursion
std::function<void(const ModuleDependencyID &)> visit;
visit = [&visit, &cache, &visited, &result,
&stack](const ModuleDependencyID &moduleID) {
// Mark this node as visited -- we are done if it already was.
if (!visited.insert(moduleID).second)
return;
// Otherwise, visit each adjacent node.
for (const auto &succID : cache.getAllDependencies(moduleID)) {
// We don't worry if successor is already in this current stack,
// since that would mean we have found a cycle, which should not
// be possible because we checked for cycles earlier.
stack.push(succID);
visit(succID);
auto top = stack.top();
stack.pop();
assert(top == succID);
}
// Add to the result.
result.push_back(moduleID);
};
for (const auto &modID : allModules) {
assert(stack.empty());
stack.push(modID);
visit(modID);
auto top = stack.top();
stack.pop();
assert(top == modID);
}
std::reverse(result.begin(), result.end());
return result;
}
/// For each module in the graph, compute a set of all its dependencies,
/// direct *and* transitive.
static std::unordered_map<ModuleDependencyID, std::set<ModuleDependencyID>>
computeTransitiveClosureOfExplicitDependencies(
const std::vector<ModuleDependencyID> &topologicallySortedModuleList,
const ModuleDependenciesCache &cache) {
// The usage of an ordered ::set is important to ensure the
// dependencies are listed in a deterministic order.
std::unordered_map<ModuleDependencyID, std::set<ModuleDependencyID>> result;
for (const auto &modID : topologicallySortedModuleList)
result[modID] = {modID};
// Traverse the set of modules in reverse topological order, assimilating
// transitive closures
for (auto it = topologicallySortedModuleList.rbegin(),
end = topologicallySortedModuleList.rend();
it != end; ++it) {
const auto &modID = *it;
auto &modReachableSet = result[modID];
for (const auto &succID : cache.getAllDependencies(modID)) {
const auto &succReachableSet = result[succID];
llvm::set_union(modReachableSet, succReachableSet);
}
}
// For ease of use down-the-line, remove the node's self from its set of
// reachable nodes
for (const auto &modID : topologicallySortedModuleList)
result[modID].erase(modID);
return result;
}
static std::set<ModuleDependencyID> computeBridgingHeaderTransitiveDependencies(
const ModuleDependencyInfo &dep,
const std::unordered_map<ModuleDependencyID, std::set<ModuleDependencyID>>
&transitiveClosures,
const ModuleDependenciesCache &cache) {
std::set<ModuleDependencyID> result;
if (!dep.isSwiftSourceModule())
return result;
for (auto &depID : dep.getHeaderClangDependencies()) {
result.insert(depID);
auto succDeps = transitiveClosures.find(depID);
assert(succDeps != transitiveClosures.end() && "unknown dependency");
llvm::set_union(result, succDeps->second);
}
return result;
}
static std::vector<ModuleDependencyID>
findClangDepPath(const ModuleDependencyID &from, const ModuleDependencyID &to,
const ModuleDependenciesCache &cache) {
std::unordered_set<ModuleDependencyID> visited;
std::vector<ModuleDependencyID> result;
std::stack<ModuleDependencyID, std::vector<ModuleDependencyID>> stack;
// Must be explicitly-typed to allow recursion
std::function<void(const ModuleDependencyID &)> visit;
visit = [&visit, &cache, &visited, &result, &stack,
to](const ModuleDependencyID &moduleID) {
if (!visited.insert(moduleID).second)
return;
if (moduleID == to) {
// Copy stack contents to the result
auto end = &stack.top() + 1;
auto begin = end - stack.size();
result.assign(begin, end);
return;
}
// Otherwise, visit each child node.
for (const auto &succID : cache.getImportedClangDependencies(moduleID)) {
stack.push(succID);
visit(succID);
stack.pop();
}
};
stack.push(from);
visit(from);
return result;
}
static bool diagnoseCycle(const CompilerInstance &instance,
const ModuleDependenciesCache &cache,
ModuleDependencyID mainId) {
ModuleDependencyIDSetVector openSet;
ModuleDependencyIDSetVector closeSet;
auto kindIsSwiftDependency = [&](const ModuleDependencyID &ID) {
return ID.Kind == swift::ModuleDependencyKind::SwiftInterface ||
ID.Kind == swift::ModuleDependencyKind::SwiftBinary ||
ID.Kind == swift::ModuleDependencyKind::SwiftSource;
};
auto emitModulePath = [&](const std::vector<ModuleDependencyID> path,
llvm::SmallString<64> &buffer) {
llvm::interleave(
path,
[&buffer](const ModuleDependencyID &id) {
buffer.append(id.ModuleName);
switch (id.Kind) {
case swift::ModuleDependencyKind::SwiftSource:
buffer.append(" (Source Target)");
break;
case swift::ModuleDependencyKind::SwiftInterface:
buffer.append(".swiftinterface");
break;
case swift::ModuleDependencyKind::SwiftBinary:
buffer.append(".swiftmodule");
break;
case swift::ModuleDependencyKind::Clang:
buffer.append(".pcm");
break;
default:
llvm::report_fatal_error(
Twine("Invalid Module Dependency Kind in cycle: ") +
id.ModuleName);
break;
}
},
[&buffer] { buffer.append(" -> "); });
};
auto emitCycleDiagnostic = [&](const ModuleDependencyID &sourceId,
const ModuleDependencyID &sinkId) {
auto startIt = std::find(openSet.begin(), openSet.end(), sourceId);
assert(startIt != openSet.end());
std::vector<ModuleDependencyID> cycleNodes(startIt, openSet.end());
cycleNodes.push_back(sinkId);
llvm::SmallString<64> errorBuffer;
emitModulePath(cycleNodes, errorBuffer);
instance.getASTContext().Diags.diagnose(
SourceLoc(), diag::scanner_find_cycle, errorBuffer.str());
// TODO: for (std::tuple<const ModuleDependencyID&, const
// ModuleDependencyID&> v : cycleNodes | std::views::adjacent<2>)
for (auto it = cycleNodes.begin(), end = cycleNodes.end(); it != end;
it++) {
if (it + 1 == cycleNodes.end())
continue;
const auto &thisID = *it;
const auto &nextID = *(it + 1);
if (kindIsSwiftDependency(thisID) && kindIsSwiftDependency(nextID) &&
llvm::any_of(
cache.getSwiftOverlayDependencies(thisID),
[&](const ModuleDependencyID id) { return id == nextID; })) {
llvm::SmallString<64> noteBuffer;
auto clangDepPath = findClangDepPath(
thisID,
ModuleDependencyID{nextID.ModuleName, ModuleDependencyKind::Clang},
cache);
emitModulePath(clangDepPath, noteBuffer);
instance.getASTContext().Diags.diagnose(
SourceLoc(), diag::scanner_find_cycle_swift_overlay_path,
thisID.ModuleName, nextID.ModuleName, noteBuffer.str());
}
}
};
// Start from the main module and check direct and overlay dependencies
openSet.insert(mainId);
while (!openSet.empty()) {
auto lastOpen = openSet.back();
auto beforeSize = openSet.size();
assert(cache.findDependency(lastOpen).has_value() &&
"Missing dependency info during cycle diagnosis.");
for (const auto &depId : cache.getAllDependencies(lastOpen)) {
if (closeSet.count(depId))
continue;
// Ensure we detect dependency of the Source target
// on an existing Swift module with the same name
if (kindIsSwiftDependency(depId) &&
depId.ModuleName == mainId.ModuleName && openSet.contains(mainId)) {
emitCycleDiagnostic(mainId, depId);
return true;
}
if (openSet.insert(depId)) {
break;
} else {
emitCycleDiagnostic(depId, depId);
return true;
}
}
// No new node added. We can close this node
if (openSet.size() == beforeSize) {
closeSet.insert(openSet.back());
openSet.pop_back();
} else {
assert(openSet.size() == beforeSize + 1);
}
}
assert(openSet.empty());
closeSet.clear();
return false;
}
} // namespace
bool swift::dependencies::scanDependencies(CompilerInstance &CI) {
ASTContext &ctx = CI.getASTContext();
std::string depGraphOutputPath =
CI.getInvocation().getFrontendOptions().InputsAndOutputs.getSingleOutputFilename();
// `-scan-dependencies` invocations use a single new instance
// of a module cache
SwiftDependencyScanningService *service =
ctx.Allocate<SwiftDependencyScanningService>();
ModuleDependenciesCache cache(
*service, CI.getMainModule()->getNameStr().str(),
CI.getInvocation().getFrontendOptions().ExplicitModulesOutputPath,
CI.getInvocation().getFrontendOptions().ExplicitSDKModulesOutputPath,
CI.getInvocation().getModuleScanningHash());
if (service->setupCachingDependencyScanningService(CI))
return true;
// Execute scan
llvm::ErrorOr<swiftscan_dependency_graph_t> dependenciesOrErr =
performModuleScan(CI, nullptr, cache);
if (dependenciesOrErr.getError())
return true;
auto dependencies = std::move(*dependenciesOrErr);
if (writeJSONToOutput(ctx.Diags, CI.getOutputBackend(), depGraphOutputPath,
dependencies))
return true;
// This process succeeds regardless of whether any errors occurred.
// FIXME: We shouldn't need this, but it's masking bugs in our scanning
// logic where we don't create a fresh context when scanning Swift interfaces
// that includes their own command-line flags.
ctx.Diags.resetHadAnyError();
return false;
}
bool swift::dependencies::prescanDependencies(CompilerInstance &instance) {
ASTContext &Context = instance.getASTContext();
const FrontendOptions &opts = instance.getInvocation().getFrontendOptions();
std::string path = opts.InputsAndOutputs.getSingleOutputFilename();
// `-scan-dependencies` invocations use a single new instance
// of a module cache
SwiftDependencyScanningService *singleUseService = Context.Allocate<SwiftDependencyScanningService>();
ModuleDependenciesCache cache(
*singleUseService, instance.getMainModule()->getNameStr().str(),
instance.getInvocation().getFrontendOptions().ExplicitModulesOutputPath,
instance.getInvocation().getFrontendOptions().ExplicitSDKModulesOutputPath,
instance.getInvocation().getModuleScanningHash());
// Execute import prescan, and write JSON output to the output stream
auto importSetOrErr = performModulePrescan(instance, nullptr, cache);
if (importSetOrErr.getError())
return true;
auto importSet = std::move(*importSetOrErr);
// Serialize and output main module dependencies only and exit.
if (writePrescanJSONToOutput(Context.Diags, instance.getOutputBackend(), path,
importSet))
return true;
// This process succeeds regardless of whether any errors occurred.
// FIXME: We shouldn't need this, but it's masking bugs in our scanning
// logic where we don't create a fresh context when scanning Swift interfaces
// that includes their own command-line flags.
Context.Diags.resetHadAnyError();
return false;
}
std::string
swift::dependencies::createEncodedModuleKindAndName(ModuleDependencyID id) {
switch (id.Kind) {
case ModuleDependencyKind::SwiftInterface:
case ModuleDependencyKind::SwiftSource:
return "swiftTextual:" + id.ModuleName;
case ModuleDependencyKind::SwiftBinary:
return "swiftBinary:" + id.ModuleName;
case ModuleDependencyKind::SwiftPlaceholder:
return "swiftPlaceholder:" + id.ModuleName;
case ModuleDependencyKind::Clang:
return "clang:" + id.ModuleName;
default:
llvm_unreachable("Unhandled dependency kind.");
}
}
static bool resolveDependencyCommandLineArguments(
CompilerInstance &instance, ModuleDependenciesCache &cache,
const std::vector<ModuleDependencyID> &topoSortedModuleList) {
auto moduleTransitiveClosures =
computeTransitiveClosureOfExplicitDependencies(topoSortedModuleList,
cache);
auto tracker = cache.getScanService().createSwiftDependencyTracker(
instance.getInvocation());
for (const auto &modID : llvm::reverse(topoSortedModuleList)) {
auto dependencyClosure = moduleTransitiveClosures[modID];
// For main module or binary modules, no command-line to resolve.
// For Clang modules, their dependencies are resolved by the clang Scanner
// itself for us.
auto &deps = cache.findKnownDependency(modID);
std::optional<std::set<ModuleDependencyID>> bridgingHeaderDeps;
if (modID.Kind == ModuleDependencyKind::SwiftSource)
bridgingHeaderDeps = computeBridgingHeaderTransitiveDependencies(
deps, moduleTransitiveClosures, cache);
if (resolveExplicitModuleInputs(modID, dependencyClosure, cache, instance,
bridgingHeaderDeps, tracker))
return true;
}
return false;
}
static void
updateDependencyTracker(CompilerInstance &instance,
ModuleDependenciesCache &cache,
const std::vector<ModuleDependencyID> &allModules) {
if (auto depTracker = instance.getDependencyTracker()) {
for (auto module : allModules) {
auto optionalDeps = cache.findDependency(module);
if (!optionalDeps.has_value())
continue;
auto deps = optionalDeps.value();
if (auto swiftDeps = deps->getAsSwiftInterfaceModule()) {
depTracker->addDependency(swiftDeps->swiftInterfaceFile,
/*IsSystem=*/false);
for (const auto &bridgingSourceFile :
swiftDeps->textualModuleDetails.bridgingSourceFiles)
depTracker->addDependency(bridgingSourceFile, /*IsSystem=*/false);
} else if (auto swiftSourceDeps = deps->getAsSwiftSourceModule()) {
for (const auto &sourceFile : swiftSourceDeps->sourceFiles)
depTracker->addDependency(sourceFile, /*IsSystem=*/false);
for (const auto &bridgingSourceFile :
swiftSourceDeps->textualModuleDetails.bridgingSourceFiles)
depTracker->addDependency(bridgingSourceFile, /*IsSystem=*/false);
} else if (auto clangDeps = deps->getAsClangModule()) {
if (!clangDeps->moduleMapFile.empty())
depTracker->addDependency(clangDeps->moduleMapFile,
/*IsSystem=*/false);
for (const auto &sourceFile : clangDeps->fileDependencies)
depTracker->addDependency(sourceFile, /*IsSystem=*/false);
}
}
}
}
static void resolveImplicitLinkLibraries(const CompilerInstance &instance,
ModuleDependenciesCache &cache) {
auto langOpts = instance.getInvocation().getLangOptions();
auto irGenOpts = instance.getInvocation().getIRGenOptions();
auto mainModuleName = instance.getMainModule()->getNameStr();
auto mainModuleID = ModuleDependencyID{mainModuleName.str(),
ModuleDependencyKind::SwiftSource};
auto mainModuleDepInfo = cache.findKnownDependency(mainModuleID);
std::vector<LinkLibrary> linkLibraries;
auto addLinkLibrary = [&linkLibraries](const LinkLibrary &ll) {
linkLibraries.push_back(ll);
};
if (langOpts.EnableObjCInterop)
addLinkLibrary(LinkLibrary{"objc", LibraryKind::Library, /*static=*/false});
if (langOpts.EnableCXXInterop) {
auto OptionalCxxDep = cache.findDependency(CXX_MODULE_NAME);
auto OptionalCxxStdLibDep =
cache.findDependency(instance.getASTContext().Id_CxxStdlib.str());
bool hasStaticCxx =
OptionalCxxDep.has_value() && OptionalCxxDep.value()->isStaticLibrary();
bool hasStaticCxxStdlib = OptionalCxxStdLibDep.has_value() &&
OptionalCxxStdLibDep.value()->isStaticLibrary();
registerCxxInteropLibraries(langOpts.Target, mainModuleName, hasStaticCxx,
hasStaticCxxStdlib, langOpts.CXXStdlib,
addLinkLibrary);
}
if (!irGenOpts.UseJIT && !langOpts.hasFeature(Feature::Embedded))
registerBackDeployLibraries(irGenOpts, addLinkLibrary);
mainModuleDepInfo.setLinkLibraries(linkLibraries);
cache.updateDependency(mainModuleID, mainModuleDepInfo);
}
llvm::ErrorOr<swiftscan_dependency_graph_t>
swift::dependencies::performModuleScan(
CompilerInstance &instance,
DependencyScanDiagnosticCollector *diagnosticCollector,
ModuleDependenciesCache &cache) {
const ASTContext &ctx = instance.getASTContext();
const FrontendOptions &opts = instance.getInvocation().getFrontendOptions();
// Load the dependency cache if -reuse-dependency-scan-cache
// is specified
if (opts.ReuseDependencyScannerCache) {
auto cachePath = opts.SerializedDependencyScannerCachePath;
if (opts.EmitDependencyScannerCacheRemarks)
ctx.Diags.diagnose(SourceLoc(), diag::remark_reuse_cache, cachePath);
llvm::sys::TimePoint<> serializedCacheTimeStamp;
bool loadFailure =
module_dependency_cache_serialization::readInterModuleDependenciesCache(
cachePath, cache, serializedCacheTimeStamp);
if (opts.EmitDependencyScannerCacheRemarks && loadFailure)
ctx.Diags.diagnose(SourceLoc(), diag::warn_scanner_deserialize_failed,
cachePath);
if (!loadFailure && opts.ValidatePriorDependencyScannerCache) {
auto mainModuleID =
ModuleDependencyID{instance.getMainModule()->getNameStr().str(),
ModuleDependencyKind::SwiftSource};
incremental::validateInterModuleDependenciesCache(
mainModuleID, cache, serializedCacheTimeStamp,
*instance.getSourceMgr().getFileSystem(), ctx.Diags,
opts.EmitDependencyScannerCacheRemarks);
}
}
auto scanner = ModuleDependencyScanner(
cache.getScanService(), instance.getInvocation(),
instance.getSILOptions(), instance.getASTContext(),
*instance.getDependencyTracker(), instance.getDiags(),
instance.getInvocation().getFrontendOptions().ParallelDependencyScan);
// Identify imports of the main module and add an entry for it
// to the dependency graph.
auto mainModuleName = instance.getMainModule()->getNameStr();
auto mainModuleID = ModuleDependencyID{mainModuleName.str(),
ModuleDependencyKind::SwiftSource};
if (!cache.hasDependency(mainModuleID))
cache.recordDependency(mainModuleName,
*scanner.getMainModuleDependencyInfo(instance.getMainModule()));
// Perform the full module scan starting at the main module.
auto allModules = scanner.performDependencyScan(mainModuleID, cache);
if (diagnoseCycle(instance, cache, mainModuleID))
return std::make_error_code(std::errc::not_supported);
auto topologicallySortedModuleList =
computeTopologicalSortOfExplicitDependencies(allModules, cache);
resolveDependencyCommandLineArguments(instance, cache,
topologicallySortedModuleList);
resolveImplicitLinkLibraries(instance, cache);
updateDependencyTracker(instance, cache, allModules);
if (ctx.Stats)
ctx.Stats->getFrontendCounters().NumDepScanFilesystemLookups = scanner.getNumLookups();
// Serialize the dependency cache if -serialize-dependency-scan-cache
// is specified
if (opts.SerializeDependencyScannerCache) {
auto savePath = opts.SerializedDependencyScannerCachePath;
module_dependency_cache_serialization::writeInterModuleDependenciesCache(
ctx.Diags, instance.getOutputBackend(), savePath, cache);
if (opts.EmitDependencyScannerCacheRemarks)
ctx.Diags.diagnose(SourceLoc(), diag::remark_save_cache, savePath);
}
return generateFullDependencyGraph(instance, diagnosticCollector, cache,
topologicallySortedModuleList);
}
llvm::ErrorOr<swiftscan_import_set_t>
swift::dependencies::performModulePrescan(CompilerInstance &instance,
DependencyScanDiagnosticCollector *diagnosticCollector,
ModuleDependenciesCache &cache) {
// Setup the scanner
auto scanner = ModuleDependencyScanner(
cache.getScanService(), instance.getInvocation(),
instance.getSILOptions(), instance.getASTContext(),
*instance.getDependencyTracker(), instance.getDiags(),
instance.getInvocation().getFrontendOptions().ParallelDependencyScan);
// Execute import prescan, and write JSON output to the output stream
auto mainDependencies =
scanner.getMainModuleDependencyInfo(instance.getMainModule());
if (!mainDependencies)
return mainDependencies.getError();
auto *importSet = new swiftscan_import_set_s;
std::vector<std::string> importIdentifiers;
importIdentifiers.reserve(mainDependencies->getModuleImports().size());
llvm::transform(mainDependencies->getModuleImports(),
std::back_inserter(importIdentifiers),
[](const auto &importInfo) -> std::string {
return importInfo.importIdentifier;
});
importSet->imports = create_set(importIdentifiers);
importSet->diagnostics =
diagnosticCollector
? mapCollectedDiagnosticsForOutput(diagnosticCollector)
: nullptr;
importSet->diagnostics =
diagnosticCollector
? mapCollectedDiagnosticsForOutput(diagnosticCollector)
: nullptr;
return importSet;
}
void swift::dependencies::incremental::validateInterModuleDependenciesCache(
const ModuleDependencyID &rootModuleID, ModuleDependenciesCache &cache,
const llvm::sys::TimePoint<> &cacheTimeStamp, llvm::vfs::FileSystem &fs,
DiagnosticEngine &diags, bool emitRemarks) {
ModuleDependencyIDSet visited;
ModuleDependencyIDSet modulesRequiringRescan;
outOfDateModuleScan(rootModuleID, cache, cacheTimeStamp, fs, diags,
emitRemarks, visited, modulesRequiringRescan);
for (const auto &outOfDateModID : modulesRequiringRescan)
cache.removeDependency(outOfDateModID);
// Regardless of invalidation, always re-scan main module.
cache.removeDependency(rootModuleID);
}
void swift::dependencies::incremental::outOfDateModuleScan(
const ModuleDependencyID &moduleID, const ModuleDependenciesCache &cache,
const llvm::sys::TimePoint<> &cacheTimeStamp, llvm::vfs::FileSystem &fs,
DiagnosticEngine &diags, bool emitRemarks, ModuleDependencyIDSet &visited,
ModuleDependencyIDSet &modulesRequiringRescan) {
// Visit the module's dependencies
bool hasOutOfDateModuleDependency = false;
for (const auto &depID : cache.getAllDependencies(moduleID)) {
// If we have not already visited this module, recurse.
if (visited.find(depID) == visited.end())
outOfDateModuleScan(depID, cache, cacheTimeStamp, fs, diags, emitRemarks,
visited, modulesRequiringRescan);
// Even if we're not revisiting a dependency, we must check if it's
// already known to be out of date.
hasOutOfDateModuleDependency |=
(modulesRequiringRescan.find(depID) != modulesRequiringRescan.end());
}
if (hasOutOfDateModuleDependency) {
if (emitRemarks)
diags.diagnose(SourceLoc(), diag::remark_scanner_invalidate_upstream,
moduleID.ModuleName);
modulesRequiringRescan.insert(moduleID);
} else if (!verifyModuleDependencyUpToDate(moduleID, cache, cacheTimeStamp,
fs, diags, emitRemarks))
modulesRequiringRescan.insert(moduleID);
visited.insert(moduleID);
}
bool swift::dependencies::incremental::verifyModuleDependencyUpToDate(
const ModuleDependencyID &moduleID, const ModuleDependenciesCache &cache,
const llvm::sys::TimePoint<> &cacheTimeStamp, llvm::vfs::FileSystem &fs,
DiagnosticEngine &diags, bool emitRemarks) {
const auto &moduleInfo = cache.findKnownDependency(moduleID);
auto verifyInputOlderThanCacheTimeStamp = [&cacheTimeStamp, &fs, &diags,
emitRemarks](StringRef moduleName,
StringRef inputPath) {
llvm::sys::TimePoint<> inputModTime = llvm::sys::TimePoint<>::max();
if (auto Status = fs.status(inputPath))
inputModTime = Status->getLastModificationTime();
if (inputModTime > cacheTimeStamp) {
if (emitRemarks)
diags.diagnose(SourceLoc(),
diag::remark_scanner_stale_result_invalidate, moduleName,
inputPath);
return false;
}
return true;
};
auto verifyCASID = [&cache, &diags, emitRemarks](StringRef moduleName,
const std::string &casID) {
if (!cache.getScanService().hasCAS()) {
// If the wrong cache is passed.
if (emitRemarks)
diags.diagnose(SourceLoc(),
diag::remark_scanner_invalidate_configuration,
moduleName);
return false;
}
auto &CAS = cache.getScanService().getCAS();
auto ID = CAS.parseID(casID);
if (!ID) {
if (emitRemarks)
diags.diagnose(SourceLoc(), diag::remark_scanner_invalidate_cas_error,
moduleName, toString(ID.takeError()));
return false;
}
if (!CAS.getReference(*ID)) {
if (emitRemarks)
diags.diagnose(SourceLoc(), diag::remark_scanner_invalidate_missing_cas,
moduleName, casID);
return false;
}
return true;
};
// Check CAS inputs exist
if (const auto casID = moduleInfo.getClangIncludeTree())
if (!verifyCASID(moduleID.ModuleName, *casID))
return false;
if (const auto casID = moduleInfo.getCASFSRootID())
if (!verifyCASID(moduleID.ModuleName, *casID))
return false;
// Check interface file for Swift textual modules
if (const auto &textualModuleDetails = moduleInfo.getAsSwiftInterfaceModule())
if (!verifyInputOlderThanCacheTimeStamp(
moduleID.ModuleName, textualModuleDetails->swiftInterfaceFile))
return false;
// Check binary module file for Swift binary-only modules
if (const auto &binaryModuleDetails = moduleInfo.getAsSwiftBinaryModule())
if (!verifyInputOlderThanCacheTimeStamp(
moduleID.ModuleName, binaryModuleDetails->compiledModulePath))
return false;
// Check header input source files (bridging header etc.)
for (const auto &headerInput : moduleInfo.getHeaderInputSourceFiles())
if (!verifyInputOlderThanCacheTimeStamp(moduleID.ModuleName, headerInput))
return false;
// Auxiliary files
for (const auto &auxInput : moduleInfo.getAuxiliaryFiles())
if (!verifyInputOlderThanCacheTimeStamp(moduleID.ModuleName, auxInput))
return false;
// Check header/modulemap source files for a Clang dependency
if (const auto &clangModuleDetails = moduleInfo.getAsClangModule())
for (const auto &fileInput : clangModuleDetails->fileDependencies)
if (!verifyInputOlderThanCacheTimeStamp(moduleID.ModuleName, fileInput))
return false;
return true;
}