Skip to content

Commit db6883e

Browse files
committed
[Dependency Scanning] Pull required dependencies from the adjacent binary module for direct '@testable' interface dependencies
They may be a super-set of the ones that appear in the textual interface - e.g. 'internal' imports will be contained in the adjacent binary module, but not the textual interface
1 parent affbe34 commit db6883e

File tree

3 files changed

+58
-12
lines changed

3 files changed

+58
-12
lines changed

lib/Serialization/ModuleDependencyScanner.cpp

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -186,25 +186,51 @@ ErrorOr<ModuleDependencyInfo> ModuleDependencyScanner::scanInterfaceFile(
186186
Result->addModuleImport(import.module.getModulePath(), &alreadyAddedModules);
187187
}
188188

189-
// Check the adjacent binary module, if one is found,
190-
// for additional "optional" dependencies.
189+
// For a `@testable` direct dependency, read in the dependencies
190+
// from an adjacent binary module, for completeness.
191191
if (isTestableImport) {
192-
auto adjacentBinaryCandidate = std::find_if(
192+
auto adjacentBinaryModule = std::find_if(
193193
compiledCandidates.begin(), compiledCandidates.end(),
194194
[moduleInterfacePath](const std::string &candidate) {
195195
return llvm::sys::path::parent_path(candidate) ==
196196
llvm::sys::path::parent_path(moduleInterfacePath.str());
197197
});
198-
if (adjacentBinaryCandidate != compiledCandidates.end()) {
199-
auto adjacentBinaryCandidateModules = getModuleImportsOfModule(
200-
*adjacentBinaryCandidate, ModuleLoadingBehavior::Optional,
201-
isFramework, isRequiredOSSAModules(), Ctx.LangOpts.SDKName,
198+
if (adjacentBinaryModule != compiledCandidates.end()) {
199+
// Required modules.
200+
auto adjacentBinaryModuleRequiredImports = getModuleImportsOfModule(
201+
*adjacentBinaryModule, ModuleLoadingBehavior::Required, isFramework,
202+
isRequiredOSSAModules(), Ctx.LangOpts.SDKName,
202203
Ctx.LangOpts.PackageName, Ctx.SourceMgr.getFileSystem().get(),
203204
Ctx.SearchPathOpts.DeserializedPathRecoverer);
204-
if (!adjacentBinaryCandidateModules)
205-
return adjacentBinaryCandidateModules.getError();
206-
for (const auto &t : *adjacentBinaryCandidateModules)
207-
Result->addOptionalModuleImport(t.getKey(), &alreadyAddedModules);
205+
if (!adjacentBinaryModuleRequiredImports)
206+
return adjacentBinaryModuleRequiredImports.getError();
207+
208+
#ifndef NDEBUG
209+
// Verify that the set of required modules read out from the binary
210+
// module is a super-set of module imports identified in the
211+
// textual interface.
212+
for (const auto &requiredImport : Result->getModuleImports()) {
213+
assert(adjacentBinaryModuleRequiredImports->contains(requiredImport) &&
214+
"Expected adjacent binary module's import set to contain all "
215+
"textual interface imports.");
216+
}
217+
#endif
218+
219+
for (const auto &requiredImport : *adjacentBinaryModuleRequiredImports)
220+
Result->addModuleImport(requiredImport.getKey(),
221+
&alreadyAddedModules);
222+
223+
// Optional modules. Will be looked-up on a best-effort basis
224+
auto adjacentBinaryModuleOptionalImports = getModuleImportsOfModule(
225+
*adjacentBinaryModule, ModuleLoadingBehavior::Optional, isFramework,
226+
isRequiredOSSAModules(), Ctx.LangOpts.SDKName,
227+
Ctx.LangOpts.PackageName, Ctx.SourceMgr.getFileSystem().get(),
228+
Ctx.SearchPathOpts.DeserializedPathRecoverer);
229+
if (!adjacentBinaryModuleOptionalImports)
230+
return adjacentBinaryModuleOptionalImports.getError();
231+
for (const auto &optionalImport : *adjacentBinaryModuleOptionalImports)
232+
Result->addOptionalModuleImport(optionalImport.getKey(),
233+
&alreadyAddedModules);
208234
}
209235
}
210236

lib/Serialization/SerializedModuleLoader.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ SerializedModuleLoaderBase::getModuleImportsOfModule(
420420
/*debuggerMode*/ false,
421421
/*isPartialModule*/ false, packageName,
422422
loadedModuleFile->isTestable());
423-
if (dependencyTransitiveBehavior != transitiveBehavior)
423+
if (dependencyTransitiveBehavior > transitiveBehavior)
424424
continue;
425425

426426
// Find the top-level module name.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %empty-directory(%t/clang-module-cache)
3+
// RUN: %empty-directory(%t/Foo.swiftmodule)
4+
// RUN: echo "internal import A; public func foo() {}" > %t/Foo.swift
5+
// REQUIRES: executable_test
6+
// REQUIRES: objc_interop
7+
8+
@testable import Foo
9+
10+
// Step 1: build swift interface and swift module side by side, make them testable
11+
// 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 -enable-testing -enable-experimental-feature AccessLevelOnImport
12+
13+
// Step 3: scan dependencies
14+
// RUN: %target-swift-frontend -scan-dependencies %s -o %t/deps.json -I %t -sdk %t -prebuilt-module-cache-path %t/clang-module-cache -I %S/Inputs/CHeaders -I %S/Inputs/Swift
15+
16+
// The dependency of `Foo` on `A` will not be visible if the scanner simply scans the textual interface
17+
// of `Foo`. So we verify that for a `@testable` import, the scanner also opens up the adjacent binary module and
18+
// resolves the required dependencies contained within.
19+
//
20+
// CHECK: "swift": "A"

0 commit comments

Comments
 (0)