Skip to content

Commit 80215d6

Browse files
committed
Further handling of Swift back deployment + e2e tests
Provided by Owen Voorhees
1 parent a64363c commit 80215d6

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
@@ -283,6 +283,15 @@ extension Trait where Self == Testing.ConditionTrait {
283283
})
284284
}
285285

286+
package static func requireXcode26(sourceLocation: SourceLocation = #_sourceLocation) -> Self {
287+
enabled("Xcode version is not suitable", sourceLocation: sourceLocation, {
288+
guard let installedVersion = try? await InstalledXcode.currentlySelected().productBuildVersion() else {
289+
return true
290+
}
291+
return installedVersion > (try ProductBuildVersion("17A1"))
292+
})
293+
}
294+
286295
/// Constructs a condition trait that causes a test to be disabled if not running against at least the given version of Xcode.
287296
package static func requireMinimumXcodeBuildVersion(_ version: ProductBuildVersion, sourceLocation: SourceLocation = #_sourceLocation) -> Self {
288297
requireXcodeBuildVersions(in: version..., sourceLocation: sourceLocation)

Tests/SWBBuildSystemTests/BuildOperationTests.swift

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

3599+
35993600
@Test(.requireSDKs(.macOS))
36003601
func copySwiftLibs_preSwiftOS_macos() async throws {
3601-
try await _testCopySwiftLibs(deploymentTarget: "10.14.3", shouldFilterSwiftLibs: false, shouldBackDeploySwiftConcurrency: false)
3602+
// Swift does not exist in the OS, so shouldFilterSwiftLibs is false. macOS 10.14.3 does not
3603+
// support use of back deployed span and concurrency, but if code using either has
3604+
// availability guards with a version supporting back deployment, the compatibility
3605+
// libraries will be weakly linked and should be copied.
3606+
try await _testCopySwiftLibs(deploymentTarget: "10.14.3", shouldFilterSwiftLibs: false, shouldBackDeploySwiftConcurrency: true, shouldBackDeploySwiftSpan: true)
36023607
}
36033608

36043609
@Test(.requireSDKs(.macOS))
36053610
func copySwiftLibs_postSwiftOS_macos() async throws {
3606-
try await _testCopySwiftLibs(deploymentTarget: "10.14.4", shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: true)
3611+
// macOS 10.14.4 is the first version with Swift in the OS, so shouldFilterSwiftLibs should
3612+
// be true on this and later versions. Both Concurrency and Span back deploy starting
3613+
// at this version.
3614+
try await _testCopySwiftLibs(deploymentTarget: "10.14.4", shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: true, shouldBackDeploySwiftSpan: true)
36073615
}
36083616

36093617
@Test(.requireSDKs(.macOS))
3610-
func copySwiftLibs_postSwiftConcurrency_macos() async throws {
3611-
try await _testCopySwiftLibs(deploymentTarget: "12.0", shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: false)
3618+
func copySwiftLibs_postSwiftOS_preSwiftConcurrency_macos() async throws {
3619+
// macOS 11.5 includes Swift in the OS, but predates the OS copy of Concurrency.
3620+
// Both Concurrency and Span should back deploy.
3621+
try await _testCopySwiftLibs(deploymentTarget: "11.5", shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: true, shouldBackDeploySwiftSpan: true)
36123622
}
36133623

36143624
@Test(.requireSDKs(.macOS))
3615-
func copySwiftLibs_postSwiftOS_preSwiftConcurrency_macos() async throws {
3616-
try await _testCopySwiftLibs(deploymentTarget: "11.5", shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: true)
3625+
func copySwiftLibs_postSwiftConcurrency_macos() async throws {
3626+
// macOS 12.0 includes Swift and Concurrency in the OS but not Span.
3627+
// Only Span should back deploy.
3628+
try await _testCopySwiftLibs(deploymentTarget: "12.0", shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: false, shouldBackDeploySwiftSpan: true)
3629+
}
3630+
3631+
@Test(.requireSDKs(.macOS), .requireXcode26())
3632+
func copySwiftLibs_postSwiftSpan_macos() async throws {
3633+
// macOS 26.0 includes Swift, Concurrency, and Span in the OS.
3634+
try await _testCopySwiftLibs(deploymentTarget: "26.0", shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: false, shouldBackDeploySwiftSpan: false)
36173635
}
36183636

3619-
func _testCopySwiftLibs(deploymentTarget: String, shouldFilterSwiftLibs: Bool, shouldBackDeploySwiftConcurrency: Bool, file: StaticString = #filePath, line: Int = #line) async throws {
3637+
func _testCopySwiftLibs(deploymentTarget: String, shouldFilterSwiftLibs: Bool, shouldBackDeploySwiftConcurrency: Bool, shouldBackDeploySwiftSpan: Bool, file: StaticString = #filePath, line: Int = #line) async throws {
3638+
let core = try await getCore()
3639+
let defaultDeploymentTarget = core.loadSDK(.macOS).defaultDeploymentTarget
3640+
let testsDeploymentTarget: String
3641+
if try Version(deploymentTarget) < Version(defaultDeploymentTarget) {
3642+
testsDeploymentTarget = defaultDeploymentTarget
3643+
} else {
3644+
testsDeploymentTarget = deploymentTarget
3645+
}
36203646
// Create a temporary test workspace, consisting of:
36213647
// - an application
36223648
// - a framework embedded directly inside the application
@@ -3625,7 +3651,6 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script
36253651
// All use Swift, and the point is to test that the right libswift libs
36263652
// get copied as new import statements are added to the sources that are
36273653
// only indirectly included inside the app.
3628-
let core = try await getCore()
36293654
try await withTemporaryDirectory { tmpDirPath async throws -> Void in
36303655
let testWorkspace = try await TestWorkspace(
36313656
"Test",
@@ -3791,7 +3816,7 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script
37913816
"Debug",
37923817
buildSettings: [
37933818
// Override the deployment target for tests so we don't get a warning that our deployment target is higher than XCTest's.
3794-
"MACOSX_DEPLOYMENT_TARGET": core.loadSDK(.macOS).defaultDeploymentTarget
3819+
"MACOSX_DEPLOYMENT_TARGET": testsDeploymentTarget
37953820
]
37963821
)
37973822
],
@@ -3818,6 +3843,8 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script
38183843
public func foo() -> NSString { return "Foo" }
38193844
@available(macOS 10.15, *)
38203845
public actor A { }
3846+
@available(macOS 10.14.4, *)
3847+
public func bar() { print(Span<Int>.self) }
38213848
"""
38223849
}
38233850
try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("TestProject/SysExMain.swift")) { contents in
@@ -3827,6 +3854,8 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script
38273854
@available(macOS 10.15, *)
38283855
public actor A { }
38293856
@main struct Main { public static func main() { } }
3857+
@available(macOS 10.14.4, *)
3858+
public func bar() { print(Span<Int>.self) }
38303859
"""
38313860
}
38323861
try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("TestProject/SubFrameworkSource.swift")) { contents in
@@ -3840,6 +3869,8 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script
38403869
@available(macOS 10.15, *)
38413870
public actor A { }
38423871
}
3872+
@available(macOS 10.14.4, *)
3873+
public func bar() { print(Span<Int>.self) }
38433874
"""
38443875
}
38453876

@@ -3917,26 +3948,25 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script
39173948
#expect(dependencyInfo.version == expectedDependencyInfo.version)
39183949
XCTAssertSuperset(Set(dependencyInfo.inputs), Set(expectedDependencyInfo.inputs))
39193950

3920-
// 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.
3921-
if shouldFilterSwiftLibs && !shouldBackDeploySwiftConcurrency {
3951+
// Check the baseline dependency info when nothing is backdeployed.
3952+
if shouldFilterSwiftLibs && !shouldBackDeploySwiftConcurrency && !shouldBackDeploySwiftSpan {
39223953
#expect(dependencyInfo == expectedDependencyInfo)
39233954
}
3955+
#expect(dependencyInfo.outputs.sorted().contains(expectedDependencyInfo.outputs.sorted()))
39243956

3925-
if !shouldFilterSwiftLibs {
3926-
#expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/macosx/libswiftCore.dylib").str))
3927-
#expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/macosx/libswiftFoundation.dylib").str))
3928-
#expect(dependencyInfo.outputs.sorted().contains(expectedDependencyInfo.outputs.sorted()))
3929-
#expect(dependencyInfo.outputs.contains(buildDir.join("App.app/Contents/Frameworks/libswiftCore.dylib").str))
3930-
#expect(dependencyInfo.outputs.contains(buildDir.join("App.app/Contents/Frameworks/libswiftFoundation.dylib").str))
3931-
}
3957+
// If we should not filter Swift libs, check they exist in the dependency info.
3958+
#expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/macosx/libswiftCore.dylib").str) == !shouldFilterSwiftLibs)
3959+
#expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/macosx/libswiftFoundation.dylib").str) == !shouldFilterSwiftLibs)
3960+
#expect(dependencyInfo.outputs.contains(buildDir.join("App.app/Contents/Frameworks/libswiftCore.dylib").str) == !shouldFilterSwiftLibs)
3961+
#expect(dependencyInfo.outputs.contains(buildDir.join("App.app/Contents/Frameworks/libswiftFoundation.dylib").str) == !shouldFilterSwiftLibs)
39323962

3933-
if shouldBackDeploySwiftConcurrency {
3934-
// Note all toolchains have this yet...
3935-
if tester.fs.exists(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib")) {
3936-
#expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib").str))
3937-
#expect(dependencyInfo.outputs.contains(buildDir.join("App.app/Contents/Frameworks/libswift_Concurrency.dylib").str))
3938-
}
3939-
}
3963+
// If we should back deploy Concurrency, ensure it's in the dependency info.
3964+
#expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib").str) == shouldBackDeploySwiftConcurrency)
3965+
#expect(dependencyInfo.outputs.contains(buildDir.join("App.app/Contents/Frameworks/libswift_Concurrency.dylib").str) == shouldBackDeploySwiftConcurrency)
3966+
3967+
// If we should back deploy span, ensure it's in the dependency info.
3968+
#expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-6.2/macosx/libswiftCompatibilitySpan.dylib").str) == shouldBackDeploySwiftSpan)
3969+
#expect(dependencyInfo.outputs.contains(buildDir.join("App.app/Contents/Frameworks/libswiftCompatibilitySpan.dylib").str) == shouldBackDeploySwiftSpan)
39403970
}
39413971

39423972
do {
@@ -3962,47 +3992,42 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script
39623992
#expect(unitTestsDependencyInfo.version == unitTestsExpectedDependencyInfo.version)
39633993
XCTAssertSuperset(Set(unitTestsDependencyInfo.inputs), Set(unitTestsExpectedDependencyInfo.inputs))
39643994

3965-
// 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.
3966-
if shouldFilterSwiftLibs && !shouldBackDeploySwiftConcurrency {
3995+
// Ensure that the baseline dependency info is correct when nothing is backdeployed.
3996+
if shouldFilterSwiftLibs && !shouldBackDeploySwiftConcurrency && !shouldBackDeploySwiftSpan {
39673997
#expect(unitTestsDependencyInfo == unitTestsExpectedDependencyInfo)
39683998
}
39693999

3970-
if !shouldFilterSwiftLibs {
3971-
if (try Version(core.loadSDK(.macOS).defaultDeploymentTarget)) < Version(10, 15) {
3972-
#expect(unitTestsDependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/macosx/libswiftCore.dylib").str))
3973-
#expect(unitTestsDependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/macosx/libswiftFoundation.dylib").str))
3974-
}
4000+
// As long as we weren't forced to raise the deployment target for tests, check the dependency info.
4001+
if testsDeploymentTarget == deploymentTarget {
39754002
#expect(unitTestsDependencyInfo.outputs.sorted().contains(unitTestsExpectedDependencyInfo.outputs.sorted()))
3976-
if (try Version(core.loadSDK(.macOS).defaultDeploymentTarget)) < Version(10, 15) {
3977-
#expect(unitTestsDependencyInfo.outputs.contains(buildDir.join("UnitTests.xctest/Contents/Frameworks/libswiftCore.dylib").str))
3978-
#expect(unitTestsDependencyInfo.outputs.contains(buildDir.join("UnitTests.xctest/Contents/Frameworks/libswiftFoundation.dylib").str))
3979-
}
3980-
}
39814003

3982-
if shouldBackDeploySwiftConcurrency {
4004+
#expect(unitTestsDependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/macosx/libswiftCore.dylib").str) == !shouldFilterSwiftLibs)
4005+
#expect(unitTestsDependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/macosx/libswiftFoundation.dylib").str) == !shouldFilterSwiftLibs)
4006+
#expect(unitTestsDependencyInfo.outputs.contains(buildDir.join("UnitTests.xctest/Contents/Frameworks/libswiftCore.dylib").str) == !shouldFilterSwiftLibs)
4007+
#expect(unitTestsDependencyInfo.outputs.contains(buildDir.join("UnitTests.xctest/Contents/Frameworks/libswiftFoundation.dylib").str) == !shouldFilterSwiftLibs)
4008+
4009+
4010+
#expect(unitTestsDependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib").str) == shouldBackDeploySwiftConcurrency)
4011+
#expect(unitTestsDependencyInfo.outputs.contains(buildDir.join("SwiftlessApp.app/Contents/Frameworks/libswift_Concurrency.dylib").str) == shouldBackDeploySwiftConcurrency)
4012+
39834013
// NOTE: Tests have their deployment target overridden.
3984-
if (try Version(core.loadSDK(.macOS).defaultDeploymentTarget)) < Version(10, 15) {
3985-
// Note all toolchains have this yet...
3986-
if tester.fs.exists(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib")) {
3987-
#expect(unitTestsDependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib").str))
3988-
#expect(unitTestsDependencyInfo.outputs.contains(buildDir.join("SwiftlessApp.app/Contents/Frameworks/libswift_Concurrency.dylib").str))
3989-
}
3990-
}
4014+
#expect(unitTestsDependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib").str) == shouldBackDeploySwiftSpan)
4015+
#expect(unitTestsDependencyInfo.outputs.contains(buildDir.join("SwiftlessApp.app/Contents/Frameworks/libswift_Concurrency.dylib").str) == shouldBackDeploySwiftSpan)
39914016
}
39924017
}
39934018

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

3999-
if shouldBackDeploySwiftConcurrency {
4000-
// Note all toolchains have this yet...
4001-
if tester.fs.exists(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib")) {
4002-
#expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib").str))
4003-
#expect(dependencyInfo.outputs.contains(buildDir.join("SwiftlessSysExApp.app/Contents/Frameworks/libswift_Concurrency.dylib").str))
4004-
}
4005-
}
4024+
// If we should back deploy Concurrency, ensure it's in the dependency info.
4025+
#expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib").str) == shouldBackDeploySwiftConcurrency)
4026+
#expect(dependencyInfo.outputs.contains(buildDir.join("SwiftlessSysExApp.app/Contents/Frameworks/libswift_Concurrency.dylib").str) == shouldBackDeploySwiftConcurrency)
4027+
4028+
// If we should back deploy Span, ensure it's in the dependency info.
4029+
#expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-6.2/macosx/libswiftCompatibilitySpan.dylib").str) == shouldBackDeploySwiftSpan)
4030+
#expect(dependencyInfo.outputs.contains(buildDir.join("SwiftlessSysExApp.app/Contents/Frameworks/libswiftCompatibilitySpan.dylib").str) == shouldBackDeploySwiftSpan)
40064031
}
40074032
}
40084033

0 commit comments

Comments
 (0)