Skip to content

Commit e1cba23

Browse files
committed
[Explicit Module Builds] Add support for header dependencies of binary Swift module dependencies
When we encounter a pre-built Swift binary module dependency (without an interface file), such module may have been built with a bridging header, which must still be present and is referenced by the binary .swiftmodule as either a .h or, more-likely, a pre-built .pch in a fully-explicit build. Clients must be able to know about such header dependencies in order to be able to import this binary module, because this binary module may be referencing types brought in via its bridging header. The build-system client (swift-driver) will then ensure these header dependencies are fed as inputs to all requiring compilation tasks. This adds support to the driver to query such header dependencies and feed them as inputs to all requiring compilation tasks.
1 parent cdcd4de commit e1cba23

File tree

8 files changed

+140
-21
lines changed

8 files changed

+140
-21
lines changed

Sources/CSwiftScan/include/swiftscan_header.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ typedef struct {
132132
(*swiftscan_swift_binary_detail_get_module_doc_path)(swiftscan_module_details_t);
133133
swiftscan_string_ref_t
134134
(*swiftscan_swift_binary_detail_get_module_source_info_path)(swiftscan_module_details_t);
135+
swiftscan_string_set_t *
136+
(*swiftscan_swift_binary_detail_get_header_dependencies)(swiftscan_module_details_t);
135137
bool
136138
(*swiftscan_swift_binary_detail_get_is_framework)(swiftscan_module_details_t);
137139

Sources/SwiftDriver/ExplicitModuleBuilds/ExplicitDependencyBuildPlanner.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,12 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
234234
for dependencyModule in swiftDependencyArtifacts {
235235
inputs.append(TypedVirtualPath(file: dependencyModule.modulePath.path,
236236
type: .swiftModule))
237+
238+
for headerDep in dependencyModule.prebuiltHeaderDependencyPaths ?? [] {
239+
commandLine.appendFlags(["-Xcc", "-include-pch", "-Xcc"])
240+
commandLine.appendPath(VirtualPath.lookup(headerDep.path))
241+
inputs.append(TypedVirtualPath(file: headerDep.path, type: .pch))
242+
}
237243
}
238244

239245
// Clang module dependencies are specified on the command line explicitly
@@ -301,6 +307,7 @@ public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalT
301307
swiftDependencyArtifacts.append(
302308
SwiftModuleArtifactInfo(name: dependencyId.moduleName,
303309
modulePath: TextualVirtualPath(path: swiftModulePath.fileHandle),
310+
headerDependencies: prebuiltModuleDetails.headerDependencyPaths,
304311
isFramework: isFramework))
305312
case .swiftPlaceholder:
306313
fatalError("Unresolved placeholder dependencies at planning stage: \(dependencyId) of \(moduleId)")

Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/InterModuleDependencyGraph.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,16 +146,21 @@ public struct SwiftPrebuiltExternalModuleDetails: Codable {
146146
/// The path to the .swiftSourceInfo file.
147147
public var moduleSourceInfoPath: TextualVirtualPath?
148148

149+
/// The paths to the binary module's header dependencies
150+
public var headerDependencyPaths: [TextualVirtualPath]?
151+
149152
/// A flag to indicate whether or not this module is a framework.
150153
public var isFramework: Bool?
151154

152155
public init(compiledModulePath: TextualVirtualPath,
153156
moduleDocPath: TextualVirtualPath? = nil,
154157
moduleSourceInfoPath: TextualVirtualPath? = nil,
158+
headerDependencies: [TextualVirtualPath]? = nil,
155159
isFramework: Bool) throws {
156160
self.compiledModulePath = compiledModulePath
157161
self.moduleDocPath = moduleDocPath
158162
self.moduleSourceInfoPath = moduleSourceInfoPath
163+
self.headerDependencyPaths = headerDependencies
159164
self.isFramework = isFramework
160165
}
161166
}

Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/InterModuleDependencyOracle.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,16 +132,23 @@ public class InterModuleDependencyOracle {
132132

133133
@_spi(Testing) public func supportsScannerDiagnostics() throws -> Bool {
134134
guard let swiftScan = swiftScanLibInstance else {
135-
fatalError("Attempting to reset scanner cache with no scanner instance.")
135+
fatalError("Attempting to query supported scanner API with no scanner instance.")
136+
}
137+
return swiftScan.supportsScannerDiagnostics
138+
}
139+
140+
@_spi(Testing) public func supportsBinaryModuleHeaderDependencies() throws -> Bool {
141+
guard let swiftScan = swiftScanLibInstance else {
142+
fatalError("Attempting to query supported scanner API with no scanner instance.")
136143
}
137-
return swiftScan.supportsScannerDiagnostics()
144+
return swiftScan.supportsBinaryModuleHeaderDependencies
138145
}
139146

140147
@_spi(Testing) public func getScannerDiagnostics() throws -> [ScannerDiagnosticPayload]? {
141148
guard let swiftScan = swiftScanLibInstance else {
142149
fatalError("Attempting to reset scanner cache with no scanner instance.")
143150
}
144-
guard swiftScan.supportsScannerDiagnostics() else {
151+
guard swiftScan.supportsScannerDiagnostics else {
145152
return nil
146153
}
147154
let diags = try swiftScan.queryScannerDiagnostics()

Sources/SwiftDriver/ExplicitModuleBuilds/SerializableModuleArtifacts.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,19 @@
2424
public let docPath: TextualVirtualPath?
2525
/// The path for the module's .swiftsourceinfo file
2626
public let sourceInfoPath: TextualVirtualPath?
27+
/// Header dependencies of this module
28+
public let prebuiltHeaderDependencyPaths: [TextualVirtualPath]?
2729
/// A flag to indicate whether this module is a framework
2830
public let isFramework: Bool
2931

3032
init(name: String, modulePath: TextualVirtualPath, docPath: TextualVirtualPath? = nil,
31-
sourceInfoPath: TextualVirtualPath? = nil, isFramework: Bool = false) {
33+
sourceInfoPath: TextualVirtualPath? = nil, headerDependencies: [TextualVirtualPath]? = nil,
34+
isFramework: Bool = false) {
3235
self.moduleName = name
3336
self.modulePath = modulePath
3437
self.docPath = docPath
3538
self.sourceInfoPath = sourceInfoPath
39+
self.prebuiltHeaderDependencyPaths = headerDependencies
3640
self.isFramework = isFramework
3741
}
3842
}

Sources/SwiftDriver/SwiftScan/DependencyGraphBuilder.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ private extension SwiftScan {
195195

196196
// Decode all dependencies of this module
197197
let swiftOverlayDependencies: [ModuleDependencyId]?
198-
if supportsSeparateSwiftOverlayDependencies(),
198+
if supportsSeparateSwiftOverlayDependencies,
199199
let encodedOverlayDepsRef = api.swiftscan_swift_textual_detail_get_swift_overlay_dependencies(moduleDetailsRef) {
200200
let encodedOverlayDependencies = try toSwiftStringArray(encodedOverlayDepsRef.pointee)
201201
swiftOverlayDependencies =
@@ -228,6 +228,14 @@ private extension SwiftScan {
228228
try getOptionalPathDetail(from: moduleDetailsRef,
229229
using: api.swiftscan_swift_binary_detail_get_module_source_info_path)
230230

231+
let headerDependencies: [TextualVirtualPath]?
232+
if supportsBinaryModuleHeaderDependencies {
233+
headerDependencies = try getOptionalPathArrayDetail(from: moduleDetailsRef,
234+
using: api.swiftscan_swift_binary_detail_get_header_dependencies)
235+
} else {
236+
headerDependencies = nil
237+
}
238+
231239
let isFramework: Bool
232240
if hasBinarySwiftModuleIsFramework {
233241
isFramework = api.swiftscan_swift_binary_detail_get_is_framework(moduleDetailsRef)
@@ -238,6 +246,7 @@ private extension SwiftScan {
238246
return try SwiftPrebuiltExternalModuleDetails(compiledModulePath: compiledModulePath,
239247
moduleDocPath: moduleDocPath,
240248
moduleSourceInfoPath: moduleSourceInfoPath,
249+
headerDependencies: headerDependencies,
241250
isFramework: isFramework)
242251
}
243252

Sources/SwiftDriver/SwiftScan/SwiftScan.swift

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -250,36 +250,41 @@ internal extension swiftscan_diagnostic_severity_t {
250250
api.swiftscan_clang_detail_get_captured_pcm_args != nil
251251
}
252252

253-
func serializeScannerCache(to path: AbsolutePath) {
254-
api.swiftscan_scanner_cache_serialize(scanner,
255-
path.description.cString(using: String.Encoding.utf8))
253+
@_spi(Testing) public var supportsBinaryModuleHeaderDependencies : Bool {
254+
return api.swiftscan_swift_binary_detail_get_header_dependencies != nil
256255
}
257256

258-
func loadScannerCache(from path: AbsolutePath) -> Bool {
259-
return api.swiftscan_scanner_cache_load(scanner,
260-
path.description.cString(using: String.Encoding.utf8))
257+
@_spi(Testing) public var supportsStringDispose : Bool {
258+
return api.swiftscan_string_dispose != nil
261259
}
262260

263-
func resetScannerCache() {
264-
api.swiftscan_scanner_cache_reset(scanner)
265-
}
266261

267-
@_spi(Testing) public func supportsSeparateSwiftOverlayDependencies() -> Bool {
262+
@_spi(Testing) public var supportsSeparateSwiftOverlayDependencies : Bool {
268263
return api.swiftscan_swift_textual_detail_get_swift_overlay_dependencies != nil
269264
}
270-
271-
@_spi(Testing) public func supportsScannerDiagnostics() -> Bool {
265+
266+
@_spi(Testing) public var supportsScannerDiagnostics : Bool {
272267
return api.swiftscan_scanner_diagnostics_query != nil &&
273268
api.swiftscan_scanner_diagnostics_reset != nil &&
274269
api.swiftscan_diagnostic_get_message != nil &&
275270
api.swiftscan_diagnostic_get_severity != nil &&
276271
api.swiftscan_diagnostics_set_dispose != nil
277272
}
278273

279-
@_spi(Testing) public func supportsStringDispose() -> Bool {
280-
return api.swiftscan_string_dispose != nil
274+
func serializeScannerCache(to path: AbsolutePath) {
275+
api.swiftscan_scanner_cache_serialize(scanner,
276+
path.description.cString(using: String.Encoding.utf8))
281277
}
282-
278+
279+
func loadScannerCache(from path: AbsolutePath) -> Bool {
280+
return api.swiftscan_scanner_cache_load(scanner,
281+
path.description.cString(using: String.Encoding.utf8))
282+
}
283+
284+
func resetScannerCache() {
285+
api.swiftscan_scanner_cache_reset(scanner)
286+
}
287+
283288
@_spi(Testing) public func queryScannerDiagnostics() throws -> [ScannerDiagnosticPayload] {
284289
var result: [ScannerDiagnosticPayload] = []
285290
let diagnosticSetRefOrNull = api.swiftscan_scanner_diagnostics_query(scanner)
@@ -427,6 +432,10 @@ private extension swiftscan_functions_t {
427432
self.swiftscan_swift_textual_detail_get_swift_overlay_dependencies =
428433
try loadOptional("swiftscan_swift_textual_detail_get_swift_overlay_dependencies")
429434

435+
// Header dependencies of binary modules
436+
self.swiftscan_swift_binary_detail_get_header_dependencies =
437+
try loadOptional("swiftscan_swift_binary_detail_get_header_dependencies")
438+
430439
// MARK: Required Methods
431440
func loadRequired<T>(_ symbol: String) throws -> T {
432441
guard let sym: T = Loader.lookup(symbol: symbol, in: swiftscan) else {

Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -904,7 +904,83 @@ final class ExplicitModuleBuildTests: XCTestCase {
904904
XCTAssertTrue(FileManager.default.fileExists(atPath: moduleFooPath))
905905
}
906906
}
907-
907+
908+
func testExplicitModuleBuildEndToEndWithBinaryHeaderDeps() throws {
909+
try withTemporaryDirectory { path in
910+
try localFileSystem.changeCurrentWorkingDirectory(to: path)
911+
let moduleCachePath = path.appending(component: "ModuleCache")
912+
try localFileSystem.createDirectory(moduleCachePath)
913+
let PCHPath = path.appending(component: "PCH")
914+
try localFileSystem.createDirectory(PCHPath)
915+
let FooInstallPath = path.appending(component: "Foo")
916+
try localFileSystem.createDirectory(FooInstallPath)
917+
let foo = path.appending(component: "foo.swift")
918+
try localFileSystem.writeFileContents(foo) {
919+
$0 <<< "extension Profiler {"
920+
$0 <<< " public static let count: Int = 42"
921+
$0 <<< "}"
922+
}
923+
let fooHeader = path.appending(component: "foo.h")
924+
try localFileSystem.writeFileContents(fooHeader) {
925+
$0 <<< "struct Profiler { void* ptr; };"
926+
}
927+
let main = path.appending(component: "main.swift")
928+
try localFileSystem.writeFileContents(main) {
929+
$0 <<< "import Foo"
930+
}
931+
let sdkArgumentsForTesting = (try? Driver.sdkArgumentsForTesting()) ?? []
932+
933+
var fooBuildDriver = try Driver(args: ["swiftc",
934+
"-explicit-module-build",
935+
"-module-cache-path", moduleCachePath.nativePathString(escaped: true),
936+
"-working-directory", path.nativePathString(escaped: true),
937+
foo.nativePathString(escaped: true),
938+
"-emit-module", "-wmo", "-module-name", "Foo",
939+
"-emit-module-path", FooInstallPath.nativePathString(escaped: true),
940+
"-import-objc-header", fooHeader.nativePathString(escaped: true),
941+
"-pch-output-dir", PCHPath.nativePathString(escaped: true),
942+
FooInstallPath.appending(component: "Foo.swiftmodule").nativePathString(escaped: true)]
943+
+ sdkArgumentsForTesting,
944+
env: ProcessEnv.vars)
945+
946+
// Ensure this tooling supports this functionality
947+
let dependencyOracle = InterModuleDependencyOracle()
948+
let scanLibPath = try XCTUnwrap(fooBuildDriver.toolchain.lookupSwiftScanLib())
949+
guard try dependencyOracle
950+
.verifyOrCreateScannerInstance(fileSystem: localFileSystem,
951+
swiftScanLibPath: scanLibPath) else {
952+
XCTFail("Dependency scanner library not found")
953+
return
954+
}
955+
guard try dependencyOracle.supportsBinaryModuleHeaderDependencies() else {
956+
throw XCTSkip("libSwiftScan does not support binary module header dependencies.")
957+
}
958+
959+
let fooJobs = try fooBuildDriver.planBuild()
960+
try fooBuildDriver.run(jobs: fooJobs)
961+
XCTAssertFalse(fooBuildDriver.diagnosticEngine.hasErrors)
962+
963+
var driver = try Driver(args: ["swiftc",
964+
"-I", FooInstallPath.nativePathString(escaped: true),
965+
"-explicit-module-build", "-emit-module", "-emit-module-path",
966+
path.appending(component: "testEMBETEWBHD.swiftmodule").nativePathString(escaped: true),
967+
"-module-cache-path", moduleCachePath.nativePathString(escaped: true),
968+
"-working-directory", path.nativePathString(escaped: true),
969+
main.nativePathString(escaped: true)] + sdkArgumentsForTesting,
970+
env: ProcessEnv.vars)
971+
let jobs = try driver.planBuild()
972+
let compileJob = try XCTUnwrap(jobs.first(where: { $0.description == "Compiling main main.swift" }))
973+
974+
// Ensure the header dependency of Foo shows up on client compile commands
975+
XCTAssertTrue(compileJob.commandLine.contains(subsequence: [.flag("-Xcc"),
976+
.flag("-include-pch"),
977+
.flag("-Xcc"),
978+
.path(.absolute(PCHPath.appending(component: "foo.pch")))]))
979+
try driver.run(jobs: jobs)
980+
XCTAssertFalse(driver.diagnosticEngine.hasErrors)
981+
}
982+
}
983+
908984
func testExplicitModuleBuildEndToEnd() throws {
909985
try withTemporaryDirectory { path in
910986
try localFileSystem.changeCurrentWorkingDirectory(to: path)

0 commit comments

Comments
 (0)