Skip to content

Commit aed3e3c

Browse files
committed
[Explicit Module Builds] Add API to specify richer external target module details
Specifically, whether an external target moduel is a framework. This is required to pass this information down to the compiler in order to generate correct auto-linking directives. Part of rdar://81177797
1 parent 827afa0 commit aed3e3c

File tree

8 files changed

+175
-16
lines changed

8 files changed

+175
-16
lines changed

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public struct Driver {
2323
case invalidArgumentValue(String, String)
2424
case relativeFrontendPath(String)
2525
case subcommandPassedToDriver
26+
case externalTargetDetailsAPIError
2627
case integratedReplRemoved
2728
case cannotSpecify_OForMultipleOutputs
2829
case conflictingOptions(Option, Option)
@@ -57,6 +58,8 @@ public struct Driver {
5758
return "relative frontend path: \(path)"
5859
case .subcommandPassedToDriver:
5960
return "subcommand passed to driver"
61+
case .externalTargetDetailsAPIError:
62+
return "Cannot specify both: externalTargetModulePathMap and externalTargetModuleDetailsMap"
6063
case .integratedReplRemoved:
6164
return "Compiler-internal integrated REPL has been removed; use the LLDB-enhanced REPL instead."
6265
case .cannotSpecify_OForMultipleOutputs:
@@ -307,7 +310,7 @@ public struct Driver {
307310

308311
/// A dictionary of external targets that are a part of the same build, mapping to filesystem paths
309312
/// of their module files
310-
@_spi(Testing) public var externalTargetModulePathMap: ExternalTargetModulePathMap? = nil
313+
@_spi(Testing) public var externalTargetModuleDetailsMap: ExternalTargetModuleDetailsMap? = nil
311314

312315
/// A collection of all the flags the selected toolchain's `swift-frontend` supports
313316
public let supportedFrontendFlags: Set<String>
@@ -388,8 +391,11 @@ public struct Driver {
388391
/// an executable or as a library.
389392
/// - Parameter compilerExecutableDir: Directory that contains the compiler executable to be used.
390393
/// Used when in `integratedDriver` mode as a substitute for the driver knowing its executable path.
391-
/// - Parameter externalTargetModulePathMap: A dictionary of external targets that are a part of
392-
/// the same build, mapping to filesystem paths of their module files.
394+
/// - Parameter externalTargetModulePathMap: DEPRECATED: A dictionary of external targets
395+
/// that are a part of the same build, mapping to filesystem paths of their module files.
396+
/// - Parameter externalTargetModuleDetailsMap: A dictionary of external targets that are a part of
397+
/// the same build, mapping to a details value which includes a filesystem path of their
398+
/// `.swiftmodule` and a flag indicating whether the external target is a framework.
393399
/// - Parameter interModuleDependencyOracle: An oracle for querying inter-module dependencies,
394400
/// shared across different module builds by a build system.
395401
public init(
@@ -400,7 +406,9 @@ public struct Driver {
400406
executor: DriverExecutor,
401407
integratedDriver: Bool = true,
402408
compilerExecutableDir: AbsolutePath? = nil,
409+
// Deprecated in favour of the below `externalTargetModuleDetailsMap`
403410
externalTargetModulePathMap: ExternalTargetModulePathMap? = nil,
411+
externalTargetModuleDetailsMap: ExternalTargetModuleDetailsMap? = nil,
404412
interModuleDependencyOracle: InterModuleDependencyOracle? = nil
405413
) throws {
406414
self.env = env
@@ -410,8 +418,15 @@ public struct Driver {
410418
self.diagnosticEngine = diagnosticsEngine
411419
self.executor = executor
412420

421+
if externalTargetModulePathMap != nil && externalTargetModuleDetailsMap != nil {
422+
throw Error.externalTargetDetailsAPIError
423+
}
413424
if let externalTargetPaths = externalTargetModulePathMap {
414-
self.externalTargetModulePathMap = externalTargetPaths
425+
self.externalTargetModuleDetailsMap = externalTargetPaths.mapValues {
426+
ExternalTargetModuleDetails(path: $0, isFramework: false)
427+
}
428+
} else if let externalTargetDetails = externalTargetModuleDetailsMap {
429+
self.externalTargetModuleDetailsMap = externalTargetDetails
415430
}
416431

417432
if case .subcommand = try Self.invocationRunMode(forArgs: args).mode {

Sources/SwiftDriver/ExplicitModuleBuilds/ExplicitDependencyBuildPlanner.swift

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,22 @@ import TSCUtility
1414
import Foundation
1515

1616
/// A map from a module identifier to a path to its .swiftmodule file.
17+
/// Deprecated in favour of the below `ExternalTargetModuleDetails`
1718
public typealias ExternalTargetModulePathMap = [ModuleDependencyId: AbsolutePath]
1819

20+
/// Details about an external target, including the path to its .swiftmodule file
21+
/// and whether it is a framework.
22+
public struct ExternalTargetModuleDetails {
23+
@_spi(Testing) public init(path: AbsolutePath, isFramework: Bool) {
24+
self.path = path
25+
self.isFramework = isFramework
26+
}
27+
let path: AbsolutePath
28+
let isFramework: Bool
29+
}
30+
31+
public typealias ExternalTargetModuleDetailsMap = [ModuleDependencyId: ExternalTargetModuleDetails]
32+
1933
/// In Explicit Module Build mode, this planner is responsible for generating and providing
2034
/// build jobs for all module dependencies and providing compile command options
2135
/// that specify said explicit module dependencies.
@@ -317,16 +331,17 @@ public typealias ExternalTargetModulePathMap = [ModuleDependencyId: AbsolutePath
317331
modulePath: TextualVirtualPath(path: clangModulePath),
318332
moduleMapPath: dependencyClangModuleDetails.moduleMapPath))
319333
case .swiftPrebuiltExternal:
320-
let compiledModulePath = try dependencyGraph
321-
.swiftPrebuiltDetails(of: dependencyId)
322-
.compiledModulePath
334+
let prebuiltModuleDetails = try dependencyGraph.swiftPrebuiltDetails(of: dependencyId)
335+
let compiledModulePath = prebuiltModuleDetails.compiledModulePath
336+
let isFramework = prebuiltModuleDetails.isFramework
323337
let swiftModulePath: TypedVirtualPath =
324338
.init(file: compiledModulePath.path, type: .swiftModule)
325339
// Accumulate the requried information about this dependency
326340
// TODO: add .swiftdoc and .swiftsourceinfo for this module.
327341
swiftDependencyArtifacts.append(
328342
SwiftModuleArtifactInfo(name: dependencyId.moduleName,
329-
modulePath: TextualVirtualPath(path: swiftModulePath.fileHandle)))
343+
modulePath: TextualVirtualPath(path: swiftModulePath.fileHandle),
344+
isFramework: isFramework))
330345
case .swiftPlaceholder:
331346
fatalError("Unresolved placeholder dependencies at planning stage: \(dependencyId) of \(moduleId)")
332347
}

Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/CommonDependencyOperations.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ import TSCBasic
1515
/// For targets that are built alongside the driver's current module, the scanning action will report them as
1616
/// textual targets to be built from source. Because we can rely on these targets to have been built prior
1717
/// to the driver's current target, we resolve such external targets as prebuilt binary modules, in the graph.
18-
mutating func resolveExternalDependencies(for externalTargetModulePathMap: ExternalTargetModulePathMap)
18+
mutating func resolveExternalDependencies(for externalTargetModuleDetailsMap: ExternalTargetModuleDetailsMap)
1919
throws {
20-
for (externalModuleId, externalModulePath) in externalTargetModulePathMap {
20+
for (externalModuleId, externalModuleDetails) in externalTargetModuleDetailsMap {
21+
let externalModulePath = externalModuleDetails.path
2122
// Replace the occurence of a Swift module to-be-built from source-file
2223
// to an info that describes a pre-built binary module.
2324
let swiftModuleId: ModuleDependencyId = .swift(externalModuleId.moduleName)
@@ -32,12 +33,12 @@ import TSCBasic
3233
let newModuleId: ModuleDependencyId = .swiftPrebuiltExternal(externalModuleId.moduleName)
3334
let newExternalModuleDetails =
3435
try SwiftPrebuiltExternalModuleDetails(compiledModulePath:
35-
TextualVirtualPath(path: VirtualPath.absolute(externalModulePath).intern()))
36+
TextualVirtualPath(path: VirtualPath.absolute(externalModulePath).intern()),
37+
isFramework: externalModuleDetails.isFramework)
3638
let newInfo = ModuleInfo(modulePath: TextualVirtualPath(path: VirtualPath.absolute(externalModulePath).intern()),
3739
sourceFiles: [],
3840
directDependencies: currentInfo.directDependencies,
3941
details: .swiftPrebuiltExternal(newExternalModuleDetails))
40-
4142
Self.replaceModule(originalId: swiftModuleId, replacementId: newModuleId,
4243
replacementInfo: newInfo, in: &modules)
4344
}

Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/InterModuleDependencyGraph.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,12 +132,17 @@ public struct SwiftPrebuiltExternalModuleDetails: Codable {
132132
/// The path to the .swiftSourceInfo file.
133133
public var moduleSourceInfoPath: TextualVirtualPath?
134134

135+
/// A flag to indicate whether or not this module is a framework.
136+
public var isFramework: Bool
137+
135138
public init(compiledModulePath: TextualVirtualPath,
136139
moduleDocPath: TextualVirtualPath? = nil,
137-
moduleSourceInfoPath: TextualVirtualPath? = nil) throws {
140+
moduleSourceInfoPath: TextualVirtualPath? = nil,
141+
isFramework: Bool = false) throws {
138142
self.compiledModulePath = compiledModulePath
139143
self.moduleDocPath = moduleDocPath
140144
self.moduleSourceInfoPath = moduleSourceInfoPath
145+
self.isFramework = isFramework
141146
}
142147
}
143148

Sources/SwiftDriver/ExplicitModuleBuilds/ModuleDependencyScanning.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ public extension Driver {
5353
// FIXME: MSVC runtime flags
5454

5555
// Pass in external target dependencies to be treated as placeholder dependencies by the scanner
56-
if let externalTargetPaths = externalTargetModulePathMap {
56+
if let externalTargetDetails = externalTargetModuleDetailsMap {
57+
let externalTargetPaths = externalTargetDetails.mapValues { $0.path }
5758
let dependencyPlaceholderMapFile =
5859
try serializeExternalDependencyArtifacts(externalTargetPaths: externalTargetPaths)
5960
commandLine.appendFlag("-placeholder-dependency-module-map-file")

Sources/SwiftDriver/Jobs/Planning.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -513,9 +513,9 @@ extension Driver {
513513
throws -> InterModuleDependencyGraph {
514514
var dependencyGraph = try performDependencyScan()
515515

516-
if let externalTargetPaths = externalTargetModulePathMap {
516+
if let externalTargetDetails = externalTargetModuleDetailsMap {
517517
// Resolve external dependencies in the dependency graph, if any.
518-
try dependencyGraph.resolveExternalDependencies(for: externalTargetPaths)
518+
try dependencyGraph.resolveExternalDependencies(for: externalTargetDetails)
519519
}
520520

521521
// Re-scan Clang modules at all the targets they will be built against.

Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,52 @@ final class ExplicitModuleBuildTests: XCTestCase {
215215
}
216216
}
217217

218+
func testModuleDependencyBuildCommandGenerationWithExternalFramework() throws {
219+
do {
220+
let externalDetails: ExternalTargetModuleDetailsMap =
221+
[.swiftPrebuiltExternal("A"): ExternalTargetModuleDetails(path: AbsolutePath("/tmp/A.swiftmodule"),
222+
isFramework: true)]
223+
var driver = try Driver(args: ["swiftc", "-experimental-explicit-module-build",
224+
"-module-name", "testModuleDependencyBuildCommandGenerationWithExternalFramework",
225+
"test.swift"])
226+
var moduleDependencyGraph =
227+
try JSONDecoder().decode(
228+
InterModuleDependencyGraph.self,
229+
from: ModuleDependenciesInputs.simpleDependencyGraphInput.data(using: .utf8)!)
230+
// Key part of this test, using the external info to generate dependency pre-build jobs
231+
try moduleDependencyGraph.resolveExternalDependencies(for: externalDetails)
232+
driver.explicitDependencyBuildPlanner =
233+
try ExplicitDependencyBuildPlanner(dependencyGraph: moduleDependencyGraph,
234+
toolchain: driver.toolchain)
235+
let modulePrebuildJobs =
236+
try driver.explicitDependencyBuildPlanner!.generateExplicitModuleDependenciesBuildJobs()
237+
238+
XCTAssertEqual(modulePrebuildJobs.count, 1)
239+
let job = modulePrebuildJobs.first!
240+
// Load the dependency JSON and verify this dependency was encoded correctly
241+
let explicitDepsFlag =
242+
SwiftDriver.Job.ArgTemplate.flag(String("-explicit-swift-module-map-file"))
243+
XCTAssert(job.commandLine.contains(explicitDepsFlag))
244+
let jsonDepsPathIndex = job.commandLine.firstIndex(of: explicitDepsFlag)
245+
let jsonDepsPathArg = job.commandLine[jsonDepsPathIndex! + 1]
246+
guard case .path(let jsonDepsPath) = jsonDepsPathArg else {
247+
XCTFail("No JSON dependency file path found.")
248+
return
249+
}
250+
guard case let .temporaryWithKnownContents(_, contents) = jsonDepsPath else {
251+
XCTFail("Unexpected path type")
252+
return
253+
}
254+
let dependencyInfoList = try JSONDecoder().decode(Array<SwiftModuleArtifactInfo>.self,
255+
from: contents)
256+
XCTAssertEqual(dependencyInfoList.count, 1)
257+
let dependencyArtifacts =
258+
dependencyInfoList.first(where:{ $0.moduleName == "A" })!
259+
// Ensure this is a framework, as specified by the externalDetails above.
260+
XCTAssertEqual(dependencyArtifacts.isFramework, true)
261+
}
262+
}
263+
218264
private func pathMatchesSwiftModule(path: VirtualPath, _ name: String) -> Bool {
219265
return path.basenameWithoutExt.starts(with: "\(name)-") &&
220266
path.extension! == FileType.swiftModule.rawValue

Tests/SwiftDriverTests/Inputs/ExplicitModuleDependencyBuildInputs.swift

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,82 @@ enum ModuleDependenciesInputs {
396396
"""
397397
}
398398

399+
static var simpleDependencyGraphInput: String {
400+
"""
401+
{
402+
"mainModuleName": "main",
403+
"modules": [
404+
{
405+
"swift": "main"
406+
},
407+
{
408+
"modulePath": "main.swiftmodule",
409+
"sourceFiles": [
410+
"/main/main.swift"
411+
],
412+
"directDependencies": [
413+
{
414+
"swift": "B"
415+
},
416+
],
417+
"details": {
418+
"swift": {
419+
"isFramework": false,
420+
"extraPcmArgs": [
421+
"-Xcc",
422+
"-fapinotes-swift-version=5"
423+
]
424+
}
425+
}
426+
},
427+
{
428+
"swift" : "B"
429+
},
430+
{
431+
"modulePath" : "B.swiftmodule",
432+
"sourceFiles": [
433+
],
434+
"directDependencies" : [
435+
{
436+
"swift": "A"
437+
},
438+
],
439+
"details" : {
440+
"swift" : {
441+
"moduleInterfacePath": "B.swiftmodule/B.swiftinterface",
442+
"isFramework": false,
443+
"extraPcmArgs": [
444+
"-Xcc",
445+
"-fapinotes-swift-version=5"
446+
],
447+
}
448+
}
449+
},
450+
{
451+
"swift": "A"
452+
},
453+
{
454+
"modulePath": "/tmp/A.swiftmodule",
455+
"sourceFiles": [
456+
"/A/A.swift"
457+
],
458+
"directDependencies" : [
459+
],
460+
"details": {
461+
"swift": {
462+
"isFramework": false,
463+
"extraPcmArgs": [
464+
"-Xcc",
465+
"-fapinotes-swift-version=5"
466+
]
467+
}
468+
}
469+
}
470+
]
471+
}
472+
"""
473+
}
474+
399475
static var mergeGraphInput2: String {
400476
"""
401477
{

0 commit comments

Comments
 (0)