Skip to content

Commit 90d556b

Browse files
DougGregorowenv
andcommitted
Further handling of Swift back deployment + e2e tests
Co-authored-by: Owen Voorhees <[email protected]>
1 parent 94abd81 commit 90d556b

File tree

5 files changed

+92
-58
lines changed

5 files changed

+92
-58
lines changed

Sources/SWBTaskExecution/TaskActions/EmbedSwiftStdLibTaskAction.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ public final class EmbedSwiftStdLibTaskAction: TaskAction {
213213

214214
func effectiveSourceDirectories(_ toolchainsDirs: OrderedSet<Path>, platform: String) -> [Path] {
215215
// FIXME: Maybe these should be defined within the toolchains or we could simply scan the toolchain directory as well.
216-
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"]
217217

218218
var dirs = [Path]()
219219
for dir in toolchainsDirs {

Sources/SWBTestSupport/SkippedTestSupport.swift

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

278+
package static func requireXcode26(sourceLocation: SourceLocation = #_sourceLocation) -> Self {
279+
enabled("Xcode version is not suitable", sourceLocation: sourceLocation, {
280+
guard let installedVersion = try? await InstalledXcode.currentlySelected().productBuildVersion() else {
281+
return true
282+
}
283+
return installedVersion > (try ProductBuildVersion("17A1"))
284+
})
285+
}
286+
278287
/// Constructs a condition trait that causes a test to be disabled if not running against at least the given version of Xcode.
279288
package static func requireMinimumXcodeBuildVersion(_ version: ProductBuildVersion, sourceLocation: SourceLocation = #_sourceLocation) -> Self {
280289
requireXcodeBuildVersions(in: version..., sourceLocation: sourceLocation)

Tests/SWBBuildSystemTests/BuildOperationTests.swift

Lines changed: 78 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3562,27 +3562,53 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script
35623562
}
35633563
}
35643564

3565+
35653566
@Test(.requireSDKs(.macOS))
35663567
func copySwiftLibs_preSwiftOS_macos() async throws {
3567-
try await _testCopySwiftLibs(deploymentTarget: "10.14.3", shouldFilterSwiftLibs: false, shouldBackDeploySwiftConcurrency: false)
3568+
// Swift does not exist in the OS, so shouldFilterSwiftLibs is false. macOS 10.14.3 does not
3569+
// support use of back deployed span and concurrency, but if code using either has
3570+
// availability guards with a version supporting back deployment, the compatibility
3571+
// libraries will be weakly linked and should be copied.
3572+
try await _testCopySwiftLibs(deploymentTarget: "10.14.3", shouldFilterSwiftLibs: false, shouldBackDeploySwiftConcurrency: true, shouldBackDeploySwiftSpan: true)
35683573
}
35693574

35703575
@Test(.requireSDKs(.macOS))
35713576
func copySwiftLibs_postSwiftOS_macos() async throws {
3572-
try await _testCopySwiftLibs(deploymentTarget: "10.14.4", shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: true)
3577+
// macOS 10.14.4 is the first version with Swift in the OS, so shouldFilterSwiftLibs should
3578+
// be true on this and later versions. Both Concurrency and Span back deploy starting
3579+
// at this version.
3580+
try await _testCopySwiftLibs(deploymentTarget: "10.14.4", shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: true, shouldBackDeploySwiftSpan: true)
35733581
}
35743582

35753583
@Test(.requireSDKs(.macOS))
3576-
func copySwiftLibs_postSwiftConcurrency_macos() async throws {
3577-
try await _testCopySwiftLibs(deploymentTarget: "12.0", shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: false)
3584+
func copySwiftLibs_postSwiftOS_preSwiftConcurrency_macos() async throws {
3585+
// macOS 11.5 includes Swift in the OS, but predates the OS copy of Concurrency.
3586+
// Both Concurrency and Span should back deploy.
3587+
try await _testCopySwiftLibs(deploymentTarget: "11.5", shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: true, shouldBackDeploySwiftSpan: true)
35783588
}
35793589

35803590
@Test(.requireSDKs(.macOS))
3581-
func copySwiftLibs_postSwiftOS_preSwiftConcurrency_macos() async throws {
3582-
try await _testCopySwiftLibs(deploymentTarget: "11.5", shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: true)
3591+
func copySwiftLibs_postSwiftConcurrency_macos() async throws {
3592+
// macOS 12.0 includes Swift and Concurrency in the OS but not Span.
3593+
// Only Span should back deploy.
3594+
try await _testCopySwiftLibs(deploymentTarget: "12.0", shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: false, shouldBackDeploySwiftSpan: true)
3595+
}
3596+
3597+
@Test(.requireSDKs(.macOS), .requireXcode26())
3598+
func copySwiftLibs_postSwiftSpan_macos() async throws {
3599+
// macOS 26.0 includes Swift, Concurrency, and Span in the OS.
3600+
try await _testCopySwiftLibs(deploymentTarget: "26.0", shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: false, shouldBackDeploySwiftSpan: false)
35833601
}
35843602

3585-
func _testCopySwiftLibs(deploymentTarget: String, shouldFilterSwiftLibs: Bool, shouldBackDeploySwiftConcurrency: Bool, file: StaticString = #filePath, line: Int = #line) async throws {
3603+
func _testCopySwiftLibs(deploymentTarget: String, shouldFilterSwiftLibs: Bool, shouldBackDeploySwiftConcurrency: Bool, shouldBackDeploySwiftSpan: Bool, file: StaticString = #filePath, line: Int = #line) async throws {
3604+
let core = try await getCore()
3605+
let defaultDeploymentTarget = core.loadSDK(.macOS).defaultDeploymentTarget
3606+
let testsDeploymentTarget: String
3607+
if try Version(deploymentTarget) < Version(defaultDeploymentTarget) {
3608+
testsDeploymentTarget = defaultDeploymentTarget
3609+
} else {
3610+
testsDeploymentTarget = deploymentTarget
3611+
}
35863612
// Create a temporary test workspace, consisting of:
35873613
// - an application
35883614
// - a framework embedded directly inside the application
@@ -3591,7 +3617,6 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script
35913617
// All use Swift, and the point is to test that the right libswift libs
35923618
// get copied as new import statements are added to the sources that are
35933619
// only indirectly included inside the app.
3594-
let core = try await getCore()
35953620
try await withTemporaryDirectory { tmpDirPath async throws -> Void in
35963621
let testWorkspace = try await TestWorkspace(
35973622
"Test",
@@ -3757,7 +3782,7 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script
37573782
"Debug",
37583783
buildSettings: [
37593784
// Override the deployment target for tests so we don't get a warning that our deployment target is higher than XCTest's.
3760-
"MACOSX_DEPLOYMENT_TARGET": core.loadSDK(.macOS).defaultDeploymentTarget
3785+
"MACOSX_DEPLOYMENT_TARGET": testsDeploymentTarget
37613786
]
37623787
)
37633788
],
@@ -3784,6 +3809,8 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script
37843809
public func foo() -> NSString { return "Foo" }
37853810
@available(macOS 10.15, *)
37863811
public actor A { }
3812+
@available(macOS 10.14.4, *)
3813+
public func bar() { print(Span<Int>.self) }
37873814
"""
37883815
}
37893816
try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("TestProject/SysExMain.swift")) { contents in
@@ -3793,6 +3820,8 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script
37933820
@available(macOS 10.15, *)
37943821
public actor A { }
37953822
@main struct Main { public static func main() { } }
3823+
@available(macOS 10.14.4, *)
3824+
public func bar() { print(Span<Int>.self) }
37963825
"""
37973826
}
37983827
try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("TestProject/SubFrameworkSource.swift")) { contents in
@@ -3806,6 +3835,8 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script
38063835
@available(macOS 10.15, *)
38073836
public actor A { }
38083837
}
3838+
@available(macOS 10.14.4, *)
3839+
public func bar() { print(Span<Int>.self) }
38093840
"""
38103841
}
38113842

@@ -3883,26 +3914,25 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script
38833914
#expect(dependencyInfo.version == expectedDependencyInfo.version)
38843915
XCTAssertSuperset(Set(dependencyInfo.inputs), Set(expectedDependencyInfo.inputs))
38853916

3886-
// Ensure that the dependency info is correct. Due to the changing nature of how Swift overlays are added, there is no need to be explicit about each one, so only the mechanism is validated by checking a handful of stable Swift overlays.
3887-
if shouldFilterSwiftLibs && !shouldBackDeploySwiftConcurrency {
3917+
// Check the baseline dependency info when nothing is backdeployed.
3918+
if shouldFilterSwiftLibs && !shouldBackDeploySwiftConcurrency && !shouldBackDeploySwiftSpan {
38883919
#expect(dependencyInfo == expectedDependencyInfo)
38893920
}
3921+
#expect(dependencyInfo.outputs.sorted().contains(expectedDependencyInfo.outputs.sorted()))
38903922

3891-
if !shouldFilterSwiftLibs {
3892-
#expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/macosx/libswiftCore.dylib").str))
3893-
#expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/macosx/libswiftFoundation.dylib").str))
3894-
#expect(dependencyInfo.outputs.sorted().contains(expectedDependencyInfo.outputs.sorted()))
3895-
#expect(dependencyInfo.outputs.contains(buildDir.join("App.app/Contents/Frameworks/libswiftCore.dylib").str))
3896-
#expect(dependencyInfo.outputs.contains(buildDir.join("App.app/Contents/Frameworks/libswiftFoundation.dylib").str))
3897-
}
3923+
// If we should not filter Swift libs, check they exist in the dependency info.
3924+
#expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/macosx/libswiftCore.dylib").str) == !shouldFilterSwiftLibs)
3925+
#expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/macosx/libswiftFoundation.dylib").str) == !shouldFilterSwiftLibs)
3926+
#expect(dependencyInfo.outputs.contains(buildDir.join("App.app/Contents/Frameworks/libswiftCore.dylib").str) == !shouldFilterSwiftLibs)
3927+
#expect(dependencyInfo.outputs.contains(buildDir.join("App.app/Contents/Frameworks/libswiftFoundation.dylib").str) == !shouldFilterSwiftLibs)
38983928

3899-
if shouldBackDeploySwiftConcurrency {
3900-
// Note all toolchains have this yet...
3901-
if tester.fs.exists(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib")) {
3902-
#expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib").str))
3903-
#expect(dependencyInfo.outputs.contains(buildDir.join("App.app/Contents/Frameworks/libswift_Concurrency.dylib").str))
3904-
}
3905-
}
3929+
// If we should back deploy Concurrency, ensure it's in the dependency info.
3930+
#expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib").str) == shouldBackDeploySwiftConcurrency)
3931+
#expect(dependencyInfo.outputs.contains(buildDir.join("App.app/Contents/Frameworks/libswift_Concurrency.dylib").str) == shouldBackDeploySwiftConcurrency)
3932+
3933+
// If we should back deploy span, ensure it's in the dependency info.
3934+
#expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-6.2/macosx/libswiftCompatibilitySpan.dylib").str) == shouldBackDeploySwiftSpan)
3935+
#expect(dependencyInfo.outputs.contains(buildDir.join("App.app/Contents/Frameworks/libswiftCompatibilitySpan.dylib").str) == shouldBackDeploySwiftSpan)
39063936
}
39073937

39083938
do {
@@ -3928,47 +3958,42 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script
39283958
#expect(unitTestsDependencyInfo.version == unitTestsExpectedDependencyInfo.version)
39293959
XCTAssertSuperset(Set(unitTestsDependencyInfo.inputs), Set(unitTestsExpectedDependencyInfo.inputs))
39303960

3931-
// Ensure that the dependency info is correct. Due to the changing nature of how Swift overlays are added, there is no need to be explicit about each one, so only the mechanism is validated by checking a handful of stable Swift overlays.
3932-
if shouldFilterSwiftLibs && !shouldBackDeploySwiftConcurrency {
3961+
// Ensure that the baseline dependency info is correct when nothing is backdeployed.
3962+
if shouldFilterSwiftLibs && !shouldBackDeploySwiftConcurrency && !shouldBackDeploySwiftSpan {
39333963
#expect(unitTestsDependencyInfo == unitTestsExpectedDependencyInfo)
39343964
}
39353965

3936-
if !shouldFilterSwiftLibs {
3937-
if (try Version(core.loadSDK(.macOS).defaultDeploymentTarget)) < Version(10, 15) {
3938-
#expect(unitTestsDependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/macosx/libswiftCore.dylib").str))
3939-
#expect(unitTestsDependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/macosx/libswiftFoundation.dylib").str))
3940-
}
3966+
// As long as we weren't forced to raise the deployment target for tests, check the dependency info.
3967+
if testsDeploymentTarget == deploymentTarget {
39413968
#expect(unitTestsDependencyInfo.outputs.sorted().contains(unitTestsExpectedDependencyInfo.outputs.sorted()))
3942-
if (try Version(core.loadSDK(.macOS).defaultDeploymentTarget)) < Version(10, 15) {
3943-
#expect(unitTestsDependencyInfo.outputs.contains(buildDir.join("UnitTests.xctest/Contents/Frameworks/libswiftCore.dylib").str))
3944-
#expect(unitTestsDependencyInfo.outputs.contains(buildDir.join("UnitTests.xctest/Contents/Frameworks/libswiftFoundation.dylib").str))
3945-
}
3946-
}
39473969

3948-
if shouldBackDeploySwiftConcurrency {
3970+
#expect(unitTestsDependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/macosx/libswiftCore.dylib").str) == !shouldFilterSwiftLibs)
3971+
#expect(unitTestsDependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/macosx/libswiftFoundation.dylib").str) == !shouldFilterSwiftLibs)
3972+
#expect(unitTestsDependencyInfo.outputs.contains(buildDir.join("UnitTests.xctest/Contents/Frameworks/libswiftCore.dylib").str) == !shouldFilterSwiftLibs)
3973+
#expect(unitTestsDependencyInfo.outputs.contains(buildDir.join("UnitTests.xctest/Contents/Frameworks/libswiftFoundation.dylib").str) == !shouldFilterSwiftLibs)
3974+
3975+
3976+
#expect(unitTestsDependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib").str) == shouldBackDeploySwiftConcurrency)
3977+
#expect(unitTestsDependencyInfo.outputs.contains(buildDir.join("SwiftlessApp.app/Contents/Frameworks/libswift_Concurrency.dylib").str) == shouldBackDeploySwiftConcurrency)
3978+
39493979
// NOTE: Tests have their deployment target overridden.
3950-
if (try Version(core.loadSDK(.macOS).defaultDeploymentTarget)) < Version(10, 15) {
3951-
// Note all toolchains have this yet...
3952-
if tester.fs.exists(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib")) {
3953-
#expect(unitTestsDependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib").str))
3954-
#expect(unitTestsDependencyInfo.outputs.contains(buildDir.join("SwiftlessApp.app/Contents/Frameworks/libswift_Concurrency.dylib").str))
3955-
}
3956-
}
3980+
#expect(unitTestsDependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib").str) == shouldBackDeploySwiftSpan)
3981+
#expect(unitTestsDependencyInfo.outputs.contains(buildDir.join("SwiftlessApp.app/Contents/Frameworks/libswift_Concurrency.dylib").str) == shouldBackDeploySwiftSpan)
39573982
}
39583983
}
39593984

39603985
do {
3961-
// Checks that a Swift-free app which embeds a system extension using Swift Concurrency, embeds the back-deployment dylib.
3986+
// Checks that a Swift-free app which embeds a system extension using Swift Concurrency and Span, embeds the back-deployment dylibs.
39623987
let swiftDepsPath = tmpDirPath.join(Path("Test/TestProject/build/TestProject.build/Debug/SwiftlessSysExApp.build/SwiftStdLibToolInputDependencies.dep"))
39633988
let dependencyInfo = try DependencyInfo(bytes: tester.fs.read(swiftDepsPath).bytes)
39643989

3965-
if shouldBackDeploySwiftConcurrency {
3966-
// Note all toolchains have this yet...
3967-
if tester.fs.exists(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib")) {
3968-
#expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib").str))
3969-
#expect(dependencyInfo.outputs.contains(buildDir.join("SwiftlessSysExApp.app/Contents/Frameworks/libswift_Concurrency.dylib").str))
3970-
}
3971-
}
3990+
// If we should back deploy Concurrency, ensure it's in the dependency info.
3991+
#expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib").str) == shouldBackDeploySwiftConcurrency)
3992+
#expect(dependencyInfo.outputs.contains(buildDir.join("SwiftlessSysExApp.app/Contents/Frameworks/libswift_Concurrency.dylib").str) == shouldBackDeploySwiftConcurrency)
3993+
3994+
// If we should back deploy Span, ensure it's in the dependency info.
3995+
#expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-6.2/macosx/libswiftCompatibilitySpan.dylib").str) == shouldBackDeploySwiftSpan)
3996+
#expect(dependencyInfo.outputs.contains(buildDir.join("SwiftlessSysExApp.app/Contents/Frameworks/libswiftCompatibilitySpan.dylib").str) == shouldBackDeploySwiftSpan)
39723997
}
39733998
}
39743999

0 commit comments

Comments
 (0)