Skip to content

Commit 187d0ae

Browse files
DougGregorslavapestovowenv
authored
Add support for the libswiftCompatibilitySpan.dylib backward deployment library (#642)
* Add support for the libswiftCompatibilitySpan.dylib backward deployment library * Improve handling of Span back-deployment library bundling A couple of related fixes: * Use 26.0 as the fallback version number when SDKSettings doesn't include SwiftSpanMinimumDeploymentTarget * Update tests to expect `--back-deploy-swift-span` and check when it shouldn't be there * Further handling of Swift back deployment + e2e tests Co-authored-by: Owen Voorhees <[email protected]> * Include rpath for the back-deployed Span dylib Co-authored-by: Owen Voorhees <[email protected]> * Drop unnecessary default arguments * Simplify requireXcode26 * Introduce a hack to allow testing Span back-deployment on older Xcode versions Xcode versions that predate Xcode 26 don't include the Span back-deployment library. Introduce a hack to make it easier to still test with those Xcode versions. * copySwiftLibs tests require Xcode 26 for libSwiftCompatibilitySpan.dylib --------- Co-authored-by: Slava Pestov <[email protected]> Co-authored-by: Owen Voorhees <[email protected]>
1 parent 8049798 commit 187d0ae

File tree

13 files changed

+195
-82
lines changed

13 files changed

+195
-82
lines changed

Sources/SWBCore/PlatformRegistry.swift

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,12 @@ public final class Platform: Sendable {
7373
/// Minimum OS version for Swift-in-the-OS support. If this is `nil`, the platform does not support Swift-in-the-OS at all.
7474
fileprivate(set) var minimumOSForSwiftInTheOS: Version? = nil
7575

76-
/// Minimum OS version for built-in Swift concurrency support. If this is `nil`, the platform does not support Swift concurrency at all.
76+
/// Minimum OS version for Swift concurrency (Swift 5.5). If this is `nil`, the platform does not support Swift concurrency at all.
7777
fileprivate(set) var minimumOSForSwiftConcurrency: Version? = nil
7878

79+
/// Minimum OS version for Span in the standard library (Swift 6.2). If this is `nil`, Span, MutableSpan, and related types are not available.
80+
fileprivate(set) var minimumOSForSwiftSpan: Version? = nil
81+
7982
/// The canonical name of the public SDK for this platform.
8083
/// - remark: This does not mean that this SDK exists, just that this is its canonical name if it does exist.
8184
@_spi(Testing) public let sdkCanonicalName: String?
@@ -244,6 +247,11 @@ extension Platform {
244247
return self.minimumOSForSwiftConcurrency ?? self.correspondingDevicePlatform?.minimumOSForSwiftConcurrency ?? nil
245248
}
246249

250+
/// Determines the deployment version to use for Swift Span support.
251+
fileprivate func swiftOSSpanVersion(_ deploymentTarget: StringMacroDeclaration) -> Version? {
252+
return self.minimumOSForSwiftSpan ?? self.correspondingDevicePlatform?.minimumOSForSwiftSpan ?? nil
253+
}
254+
247255
/// Determines if the platform supports Swift in the OS.
248256
public func supportsSwiftInTheOS(_ scope: MacroEvaluationScope, forceNextMajorVersion: Bool = false, considerTargetDeviceOSVersion: Bool = true) -> Bool {
249257
guard let deploymentTarget = self.deploymentTargetMacro else { return false }
@@ -265,7 +273,7 @@ extension Platform {
265273
return version >= minimumSwiftInTheOSVersion
266274
}
267275

268-
/// Determines if the platform natively supports Swift concurrency. If `false`, then the Swift back-compat concurrency libs needs to be copied into the app/framework's bundle.
276+
/// Determines if the platform natively supports Swift concurrency. If `false`, then the Swift concurrency back-compat concurrency libs needs to be copied into the app/framework's bundle.
269277
public func supportsSwiftConcurrencyNatively(_ scope: MacroEvaluationScope, forceNextMajorVersion: Bool = false, considerTargetDeviceOSVersion: Bool = true) -> Bool? {
270278
guard let deploymentTarget = self.deploymentTargetMacro else { return false }
271279

@@ -287,6 +295,29 @@ extension Platform {
287295

288296
return version >= minimumSwiftConcurrencyVersion
289297
}
298+
299+
/// Determines if the platform natively supports Swift 6.2's Span type. If `false`, then the Swift Span back-compat lib needs to be copied into the app/framework's bundle.
300+
public func supportsSwiftSpanNatively(_ scope: MacroEvaluationScope, forceNextMajorVersion: Bool, considerTargetDeviceOSVersion: Bool) -> Bool? {
301+
guard let deploymentTarget = self.deploymentTargetMacro else { return false }
302+
303+
// If we have target device info and its platform matches the build platform, compare the device OS version
304+
let targetDeviceVersion: Version?
305+
if considerTargetDeviceOSVersion && scope.evaluate(BuiltinMacros.TARGET_DEVICE_PLATFORM_NAME) == self.name {
306+
targetDeviceVersion = try? Version(scope.evaluate(BuiltinMacros.TARGET_DEVICE_OS_VERSION))
307+
} else {
308+
targetDeviceVersion = nil
309+
}
310+
311+
// Otherwise fall back to comparing the minimum deployment target
312+
let deploymentTargetVersion = try? Version(scope.evaluate(deploymentTarget))
313+
314+
guard let version = targetDeviceVersion ?? deploymentTargetVersion else { return false }
315+
316+
// Return `nil` here as there is no metadata for the platform to allow downstream clients to be aware of this.
317+
guard let minimumSwiftSpanVersion = swiftOSSpanVersion(deploymentTarget) else { return nil }
318+
319+
return version >= minimumSwiftSpanVersion
320+
}
290321
}
291322

292323
extension Platform: CustomStringConvertible {
@@ -633,6 +664,7 @@ public final class PlatformRegistry {
633664
if let variant = platform.defaultSDKVariant {
634665
platform.minimumOSForSwiftInTheOS = variant.minimumOSForSwiftInTheOS
635666
platform.minimumOSForSwiftConcurrency = variant.minimumOSForSwiftConcurrency
667+
platform.minimumOSForSwiftSpan = variant.minimumOSForSwiftSpan
636668
}
637669
}
638670

Sources/SWBCore/SDKRegistry.swift

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -313,9 +313,12 @@ public final class SDKVariant: PlatformInfoProvider, Sendable {
313313
/// Minimum OS version for Swift-in-the-OS support. If this is `nil`, the platform does not support Swift-in-the-OS at all.
314314
public let minimumOSForSwiftInTheOS: Version?
315315

316-
/// Minimum OS version for built-in Swift concurrency support. If this is `nil`, the platform does not support Swift concurrency at all.
316+
/// Minimum OS version for built-in Swift concurrency support (Swift 5.5). If this is `nil`, the platform does not support Swift concurrency at all.
317317
public let minimumOSForSwiftConcurrency: Version?
318318

319+
/// Minimum OS version for built-in Swift Span support (Swift 6.2). If this is `nil`, the platform does not support Swift Span at all.
320+
public let minimumOSForSwiftSpan: Version?
321+
319322
/// The path prefix under which all built content produced by this SDK variant should be installed, relative to the system root.
320323
///
321324
/// Empty string if content should be installed directly into the system root (default).
@@ -392,9 +395,10 @@ public final class SDKVariant: PlatformInfoProvider, Sendable {
392395

393396
self.clangRuntimeLibraryPlatformName = supportedTargetDict["ClangRuntimeLibraryPlatformName"]?.stringValue ?? Self.fallbackClangRuntimeLibraryPlatformName(variantName: name)
394397

395-
let (os, concurrency) = Self.fallbackSwiftVersions(variantName: name)
398+
let (os, concurrency, span) = Self.fallbackSwiftVersions(variantName: name)
396399
self.minimumOSForSwiftInTheOS = try (supportedTargetDict["SwiftOSRuntimeMinimumDeploymentTarget"]?.stringValue ?? os).map { try Version($0) }
397400
self.minimumOSForSwiftConcurrency = try (supportedTargetDict["SwiftConcurrencyMinimumDeploymentTarget"]?.stringValue ?? concurrency).map { try Version($0) }
401+
self.minimumOSForSwiftSpan = try (supportedTargetDict["SwiftSpanMinimumDeploymentTarget"]?.stringValue ?? span).map { try Version($0) }
398402

399403
self.systemPrefix = supportedTargetDict["SystemPrefix"]?.stringValue ?? {
400404
switch name {
@@ -445,12 +449,12 @@ public final class SDKVariant: PlatformInfoProvider, Sendable {
445449
}
446450
}
447451

448-
private static func fallbackSwiftVersions(variantName name: String) -> (String?, String?) {
452+
private static func fallbackSwiftVersions(variantName name: String) -> (os: String?, concurrency: String?, span: String?) {
449453
switch name {
450454
case "macos", "macosx":
451-
return ("10.14.4", "12.0")
455+
return ("10.14.4", "12.0", "26.0")
452456
default:
453-
return (nil, nil)
457+
return (nil, nil, "26.0")
454458
}
455459
}
456460

Sources/SWBCore/Settings/BuiltinMacros.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,7 @@ public final class BuiltinMacros {
602602
public static let DISABLE_INFOPLIST_PLATFORM_PROCESSING = BuiltinMacros.declareBooleanMacro("DISABLE_INFOPLIST_PLATFORM_PROCESSING")
603603
public static let DISABLE_MANUAL_TARGET_ORDER_BUILD_WARNING = BuiltinMacros.declareBooleanMacro("DISABLE_MANUAL_TARGET_ORDER_BUILD_WARNING")
604604
public static let DISABLE_STALE_FILE_REMOVAL = BuiltinMacros.declareBooleanMacro("DISABLE_STALE_FILE_REMOVAL")
605+
public static let DISABLE_SWIFT_SPAN_COMPATIBILITY_RPATH = BuiltinMacros.declareBooleanMacro("DISABLE_SWIFT_SPAN_COMPATIBILITY_RPATH")
605606
public static let DISABLE_TEST_HOST_PLATFORM_PROCESSING = BuiltinMacros.declareBooleanMacro("DISABLE_TEST_HOST_PLATFORM_PROCESSING")
606607
public static let DISABLE_XCFRAMEWORK_SIGNATURE_VALIDATION = BuiltinMacros.declareBooleanMacro("DISABLE_XCFRAMEWORK_SIGNATURE_VALIDATION")
607608
public static let DONT_CREATE_BUILT_PRODUCTS_DIR_SYMLINKS = BuiltinMacros.declareBooleanMacro("DONT_CREATE_BUILT_PRODUCTS_DIR_SYMLINKS")
@@ -1614,6 +1615,7 @@ public final class BuiltinMacros {
16141615
DISABLE_INFOPLIST_PLATFORM_PROCESSING,
16151616
DISABLE_MANUAL_TARGET_ORDER_BUILD_WARNING,
16161617
DISABLE_STALE_FILE_REMOVAL,
1618+
DISABLE_SWIFT_SPAN_COMPATIBILITY_RPATH,
16171619
DISABLE_TEST_HOST_PLATFORM_PROCESSING,
16181620
DISABLE_XCFRAMEWORK_SIGNATURE_VALIDATION,
16191621
DOCC_ARCHIVE_PATH,

Sources/SWBCore/Settings/Settings.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,11 @@ fileprivate struct PreOverridesSettings {
146146
table.push(BuiltinMacros.LM_SKIP_METADATA_EXTRACTION, BuiltinMacros.namespace.parseString("YES"))
147147
}
148148

149+
// This is a hack to prevent Span back deployment from causing excessive test churn when using an older Xcode in Swift CI.
150+
if core.xcodeProductBuildVersion <= (try! ProductBuildVersion("17A1")) {
151+
table.push(BuiltinMacros.DISABLE_SWIFT_SPAN_COMPATIBILITY_RPATH, BuiltinMacros.namespace.parseString("YES"))
152+
}
153+
149154
// Add the "calculated" settings.
150155
addCalculatedUniversalDefaults(&table)
151156

Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -294,11 +294,20 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec
294294
// And, if the deployment target does not support Swift Concurrency natively, then the rpath needs to be added as well so that the shim library can find the real implementation. Note that we assume `true` in the case where `supportsSwiftInTheOS` is `nil` as we don't have the platform data to make the correct choice; so fallback to existing behavior.
295295
// The all above discussion is only relevant for platforms that support Swift in the OS.
296296
let supportsSwiftConcurrencyNatively = cbc.producer.platform?.supportsSwiftConcurrencyNatively(cbc.scope, forceNextMajorVersion: false, considerTargetDeviceOSVersion: false) ?? true
297+
let supportsSwiftSpanNatively = cbc.producer.platform?.supportsSwiftSpanNatively(cbc.scope, forceNextMajorVersion: false, considerTargetDeviceOSVersion: false) ?? true
297298
let shouldEmitRPathForSwiftConcurrency = UserDefaults.allowRuntimeSearchPathAdditionForSwiftConcurrency && !supportsSwiftConcurrencyNatively
298-
if (cbc.producer.platform?.supportsSwiftInTheOS(cbc.scope, forceNextMajorVersion: true, considerTargetDeviceOSVersion: false) != true || cbc.producer.toolchains.usesSwiftOpenSourceToolchain || shouldEmitRPathForSwiftConcurrency) && isUsingSwift && cbc.producer.platform?.minimumOSForSwiftInTheOS != nil {
299-
// NOTE: For swift.org toolchains, this is fine as `DYLD_LIBRARY_PATH` is used to override these settings.
299+
let shouldEmitRPathForSwiftSpan = !cbc.scope.evaluate(BuiltinMacros.DISABLE_SWIFT_SPAN_COMPATIBILITY_RPATH) && !supportsSwiftSpanNatively
300+
if (
301+
cbc.producer.platform?.supportsSwiftInTheOS(cbc.scope, forceNextMajorVersion: true, considerTargetDeviceOSVersion: false) != true ||
302+
cbc.producer.toolchains.usesSwiftOpenSourceToolchain ||
303+
shouldEmitRPathForSwiftConcurrency ||
304+
shouldEmitRPathForSwiftSpan
305+
)
306+
&& isUsingSwift
307+
&& cbc.producer.platform?.minimumOSForSwiftInTheOS != nil {
308+
// NOTE: For swift.org toolchains, this is fine as `DYLD_LIBRARY_PATH` is used to override these settings.
300309
let swiftABIVersion = await (cbc.producer.swiftCompilerSpec.discoveredCommandLineToolSpecInfo(cbc.producer, cbc.scope, delegate) as? DiscoveredSwiftCompilerToolSpecInfo)?.swiftABIVersion
301-
runpathSearchPaths.insert( swiftABIVersion.flatMap { "/usr/lib/swift-\($0)" } ?? "/usr/lib/swift", at: 0)
310+
runpathSearchPaths.insert( swiftABIVersion.flatMap { "/usr/lib/swift-\($0)" } ?? "/usr/lib/swift", at: 0)
302311
}
303312

304313
return runpathSearchPaths

Sources/SWBCore/SpecImplementations/Tools/SwiftStdLibTool.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public final class SwiftStdLibToolSpec : GenericCommandLineToolSpec, SpecIdentif
2222
}
2323

2424
/// Construct a new task to run the Swift standard library tool.
25-
public func constructSwiftStdLibraryToolTask(_ cbc:CommandBuildContext, _ delegate: any TaskGenerationDelegate, foldersToScan: MacroStringListExpression?, filterForSwiftOS: Bool, backDeploySwiftConcurrency: Bool) async {
25+
public func constructSwiftStdLibraryToolTask(_ cbc:CommandBuildContext, _ delegate: any TaskGenerationDelegate, foldersToScan: MacroStringListExpression?, filterForSwiftOS: Bool, backDeploySwiftConcurrency: Bool, backDeploySwiftSpan: Bool) async {
2626
precondition(cbc.outputs.isEmpty, "Unexpected output paths \(cbc.outputs.map { "'\($0.str)'" }) passed to \(type(of: self)).")
2727

2828
let input = cbc.input
@@ -86,6 +86,10 @@ public final class SwiftStdLibToolSpec : GenericCommandLineToolSpec, SpecIdentif
8686
commandLine.append("--back-deploy-swift-concurrency")
8787
}
8888

89+
if backDeploySwiftSpan {
90+
commandLine.append("--back-deploy-swift-span")
91+
}
92+
8993
let outputs = [delegate.createVirtualNode("CopySwiftStdlib \(wrapperPathString.str)")]
9094

9195
delegate.createTask(type: self, dependencyData: .dependencyInfo(dependencyInfoFilePath), ruleInfo: ruleInfo, commandLine: commandLine, environment: EnvironmentBindings(environment.map { ($0, $1) }), workingDirectory: cbc.producer.defaultWorkingDirectory, inputs: [ delegate.createNode(input.absolutePath) ], outputs: outputs, mustPrecede: [], action: action, execDescription: resolveExecutionDescription(cbc, delegate, lookup: lookup), enableSandboxing: enableSandboxing)

Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/SwiftStandardLibrariesTaskProducer.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,13 @@ final class SwiftStandardLibrariesTaskProducer: PhasedTaskProducer, TaskProducer
104104
let supportsConcurrencyNatively = context.platform?.supportsSwiftConcurrencyNatively(scope)
105105
let backDeploySwiftConcurrency = supportsConcurrencyNatively != nil && supportsConcurrencyNatively != true
106106

107+
let supportsSpanNatively = context.platform?.supportsSwiftSpanNatively(scope, forceNextMajorVersion: false, considerTargetDeviceOSVersion: true)
108+
let backDeploySwiftSpan = supportsSpanNatively != nil && supportsSpanNatively != true
109+
107110
let cbc = CommandBuildContext(producer: context, scope: scope, inputs: [ input ])
108111
let foldersToScanExpr: MacroStringListExpression? = foldersToScan.count > 0 ? scope.namespace.parseLiteralStringList(foldersToScan): nil
109112
await appendGeneratedTasks(&tasks) { delegate in
110-
await context.swiftStdlibToolSpec.constructSwiftStdLibraryToolTask(cbc, delegate, foldersToScan: foldersToScanExpr, filterForSwiftOS: filterForSwiftOS, backDeploySwiftConcurrency: backDeploySwiftConcurrency)
113+
await context.swiftStdlibToolSpec.constructSwiftStdLibraryToolTask(cbc, delegate, foldersToScan: foldersToScanExpr, filterForSwiftOS: filterForSwiftOS, backDeploySwiftConcurrency: backDeploySwiftConcurrency, backDeploySwiftSpan: backDeploySwiftSpan)
111114
}
112115
}
113116

Sources/SWBTaskExecution/TaskActions/EmbedSwiftStdLibTaskAction.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,12 +161,18 @@ public final class EmbedSwiftStdLibTaskAction: TaskAction {
161161
// If true, then the Swift concurrency dylibs should be copied into the app/framework's bundles.
162162
var backDeploySwiftConcurrency = false
163163

164+
// If true, then the Swift Span dylibs should be copied into the app/framework's bundles.
165+
var backDeploySwiftSpan = false
166+
164167
// The allowed list of libraries that should *not* be filtered when `filterForSwiftOS=true`.
165168
let allowedLibsForSwiftOS = ["libswiftXCTest" ]
166169

167170
// The allowed list of libraries that should *not* be filtered when `backDeploySwiftConcurrency=true`.
168171
let allowedLibsForSwiftConcurrency = ["libswift_Concurrency"]
169172

173+
// The allowed list of libraries that should *not* be filtered when `backDeploySwiftSpan=true`.
174+
let allowedLibsForSwiftSpan = ["libswiftCompatibilitySpan"]
175+
170176
func absolutePath(_ path: Path) -> Path {
171177
return path.isAbsolute ? path : task.workingDirectory.join(path)
172178
}
@@ -207,7 +213,7 @@ public final class EmbedSwiftStdLibTaskAction: TaskAction {
207213

208214
func effectiveSourceDirectories(_ toolchainsDirs: OrderedSet<Path>, platform: String) -> [Path] {
209215
// FIXME: Maybe these should be defined within the toolchains or we could simply scan the toolchain directory as well.
210-
let swiftBackdeploymentDirs = ["usr/lib/swift-5.0", "usr/lib/swift-5.5"]
216+
let swiftBackdeploymentDirs = ["usr/lib/swift-5.0", "usr/lib/swift-5.5", "usr/lib/swift-6.2"]
211217

212218
var dirs = [Path]()
213219
for dir in toolchainsDirs {
@@ -369,6 +375,9 @@ public final class EmbedSwiftStdLibTaskAction: TaskAction {
369375
case "--back-deploy-swift-concurrency":
370376
self.backDeploySwiftConcurrency = true
371377

378+
case "--back-deploy-swift-span":
379+
self.backDeploySwiftSpan = true
380+
372381
default:
373382
throw StubError.error("unrecognized argument: \(arg)")
374383
}
@@ -788,6 +797,9 @@ public final class EmbedSwiftStdLibTaskAction: TaskAction {
788797
if backDeploySwiftConcurrency && allowedLibsForSwiftConcurrency.contains(item) {
789798
shouldInclude = true
790799
}
800+
if backDeploySwiftSpan && allowedLibsForSwiftSpan.contains(item) {
801+
shouldInclude = true
802+
}
791803

792804
return shouldInclude
793805
}

Sources/SWBTestSupport/SkippedTestSupport.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,10 @@ extension Trait where Self == Testing.ConditionTrait {
275275
})
276276
}
277277

278+
package static func requireXcode26(sourceLocation: SourceLocation = #_sourceLocation) -> Self {
279+
requireMinimumXcodeBuildVersion("17A1", sourceLocation: sourceLocation)
280+
}
281+
278282
/// Constructs a condition trait that causes a test to be disabled if not running against at least the given version of Xcode.
279283
package static func requireMinimumXcodeBuildVersion(_ version: ProductBuildVersion, sourceLocation: SourceLocation = #_sourceLocation) -> Self {
280284
requireXcodeBuildVersions(in: version..., sourceLocation: sourceLocation)

0 commit comments

Comments
 (0)