[Dependency Scanning] Query package-only dependencies from adjacent binary modules when necessary

When '.package.swiftinterface' loading ('-experimental-package-interface-load') is disabled and when '-scanner-module-validation' is disabled, the scanner defaults to locating the non-package textual interface and may specify its adjacent binary module as a valid candidate binary module to use. If said candidate is up-to-date and ends up getting used, and belongs to the same package as the loading Swift source, then the source compilation may attempt to load its package-only dependencies. Since the scanner only parsed the non-package textual interface, those dependencies are not located and specified as inputs to compilation. This change causes the scanner, in such cases, to also lookup package-only dependencies in adjacent binary Swift modules of textual Swift module dependencies, if such dependency belongs to the same package as the source target being scanned.

Resolves rdar://135215789
This commit is contained in:
Artem Chikin
2024-09-06 15:17:56 -07:00
parent 7a54784916
commit 12e2fb63a9
4 changed files with 120 additions and 1 deletions

View File

@@ -179,7 +179,18 @@ protected:
/// Load the module file into a buffer and also collect its module name.
static std::unique_ptr<llvm::MemoryBuffer>
getModuleName(ASTContext &Ctx, StringRef modulePath, std::string &Name);
/// If the module has a package name matching the one
/// specified, return a set of package-only imports for this module.
static llvm::ErrorOr<llvm::StringSet<>>
getMatchingPackageOnlyImportsOfModule(Twine modulePath,
bool isFramework,
bool isRequiredOSSAModules,
StringRef SDKName,
StringRef packageName,
llvm::vfs::FileSystem *fileSystem,
PathObfuscator &recoverer);
public:
virtual ~SerializedModuleLoaderBase();
SerializedModuleLoaderBase(const SerializedModuleLoaderBase &) = delete;

View File

@@ -149,6 +149,7 @@ SwiftModuleScanner::scanInterfaceFile(Twine moduleInterfacePath,
StringRef sdkPath = Ctx.SearchPathOpts.getSDKPath();
llvm::SmallString<32> modulePath = realModuleName.str();
llvm::sys::path::replace_extension(modulePath, newExt);
auto ScannerPackageName = Ctx.LangOpts.PackageName;
std::optional<ModuleDependencyInfo> Result;
std::error_code code = astDelegate.runInSubContext(
realModuleName.str(), moduleInterfacePath.str(), sdkPath,
@@ -272,6 +273,34 @@ SwiftModuleScanner::scanInterfaceFile(Twine moduleInterfacePath,
&alreadyAddedModules, &Ctx.SourceMgr);
}
// If this is a dependency that belongs to the same package, and we have not yet enabled Package Textual interfaces,
// scan the adjacent binary module for package dependencies.
if (!ScannerPackageName.empty() &&
!Ctx.LangOpts.EnablePackageInterfaceLoad) {
auto adjacentBinaryModule = std::find_if(
compiledCandidates.begin(), compiledCandidates.end(),
[moduleInterfacePath](const std::string &candidate) {
return llvm::sys::path::parent_path(candidate) ==
llvm::sys::path::parent_path(moduleInterfacePath.str());
});
if (adjacentBinaryModule != compiledCandidates.end()) {
auto adjacentBinaryModulePackageOnlyImports = getMatchingPackageOnlyImportsOfModule(
*adjacentBinaryModule, isFramework,
isRequiredOSSAModules(), Ctx.LangOpts.SDKName,
ScannerPackageName, Ctx.SourceMgr.getFileSystem().get(),
Ctx.SearchPathOpts.DeserializedPathRecoverer);
if (!adjacentBinaryModulePackageOnlyImports)
return adjacentBinaryModulePackageOnlyImports.getError();
for (const auto &requiredImport : *adjacentBinaryModulePackageOnlyImports)
if (!alreadyAddedModules.contains(requiredImport.getKey()))
Result->addModuleImport(requiredImport.getKey(),
&alreadyAddedModules);
}
}
return std::error_code();
});

View File

@@ -301,6 +301,45 @@ SerializedModuleLoaderBase::getModuleName(ASTContext &Ctx, StringRef modulePath,
return ModuleFile::getModuleName(Ctx, modulePath, Name);
}
llvm::ErrorOr<llvm::StringSet<>>
SerializedModuleLoaderBase::getMatchingPackageOnlyImportsOfModule(
Twine modulePath, bool isFramework, bool isRequiredOSSAModules,
StringRef SDKName, StringRef packageName, llvm::vfs::FileSystem *fileSystem,
PathObfuscator &recoverer) {
auto moduleBuf = fileSystem->getBufferForFile(modulePath);
if (!moduleBuf)
return moduleBuf.getError();
llvm::StringSet<> importedModuleNames;
// Load the module file without validation.
std::shared_ptr<const ModuleFileSharedCore> loadedModuleFile;
serialization::ValidationInfo loadInfo = ModuleFileSharedCore::load(
"", "", std::move(moduleBuf.get()), nullptr, nullptr, isFramework,
isRequiredOSSAModules, SDKName, recoverer, loadedModuleFile);
if (loadedModuleFile->getModulePackageName() != packageName)
return importedModuleNames;
for (const auto &dependency : loadedModuleFile->getDependencies()) {
if (dependency.isHeader())
continue;
if (!dependency.isPackageOnly())
continue;
// Find the top-level module name.
auto modulePathStr = dependency.getPrettyPrintedPath();
StringRef moduleName = modulePathStr;
auto dotPos = moduleName.find('.');
if (dotPos != std::string::npos)
moduleName = moduleName.slice(0, dotPos);
importedModuleNames.insert(moduleName);
}
return importedModuleNames;
}
std::error_code
SerializedModuleLoaderBase::openModuleSourceInfoFileIfPresent(
ImportPath::Element ModuleID,

View File

@@ -0,0 +1,40 @@
// REQUIRES: executable_test
// REQUIRES: objc_interop
// RUN: %empty-directory(%t)
// RUN: %empty-directory(%t/clang-module-cache)
// RUN: %empty-directory(%t/Foo.swiftmodule)
// RUN: %empty-directory(%t/Bar.swiftmodule)
// RUN: split-file %s %t
// Step 1: build Bar swift interface and swift module side by side
// RUN: %target-swift-frontend -emit-module %t/Bar.swift -emit-module-path %t/Bar.swiftmodule/%target-swiftmodule-name -module-name Bar -emit-module-interface-path %t/Bar.swiftmodule/%target-swiftinterface-name -I %S/Inputs/CHeaders -I %S/Inputs/Swift
// Step 2: build Foo swift interface and swift module side by side belonging to package 'Test'
// RUN: %target-swift-frontend -emit-module %t/Foo.swift -emit-module-path %t/Foo.swiftmodule/%target-swiftmodule-name -module-name Foo -emit-module-interface-path %t/Foo.swiftmodule/%target-swiftinterface-name -I %S/Inputs/CHeaders -I %S/Inputs/Swift -I %t -package-name Test
// Step 3: scan dependencies with '-no-scanner-module-validation' and '-package-name Test'
// RUN: %target-swift-frontend -scan-dependencies %t/Client.swift -o %t/deps.json -no-scanner-module-validation -I %t -sdk %t -prebuilt-module-cache-path %t/clang-module-cache -I %S/Inputs/CHeaders -I %S/Inputs/Swift -package-name Test
// Step 4: Ensure that same-package scan can see package-only dependencies
// RUN: %FileCheck %s --input-file=%t/deps.json --check-prefix CHECK-SAME-PACKAGE
// Step 5: scan dependencies with '-no-scanner-module-validation' and no package name
// RUN: %target-swift-frontend -scan-dependencies %t/Client.swift -o %t/deps_no_package.json -no-scanner-module-validation -I %t -sdk %t -prebuilt-module-cache-path %t/clang-module-cache -I %S/Inputs/CHeaders -I %S/Inputs/Swift
// Step 6: Ensure that non-same-package scan can not see package-only dependencies
// RUN: %FileCheck %s --input-file=%t/deps_no_package.json --check-prefix CHECK-NO-PACKAGE
// CHECK-SAME-PACKAGE: "swift": "Bar"
// CHECK-NO-PACKAGE-NOT: "swift": "Bar"
//--- Bar.swift
enum PubEnum {
case red, green
}
//--- Foo.swift
package import Bar
//--- Client.swift
import Foo