diff --git a/Sources/SWBApplePlatform/Specs/MetalCompiler.xcspec b/Sources/SWBApplePlatform/Specs/MetalCompiler.xcspec index b697a333..67c10a8e 100644 --- a/Sources/SWBApplePlatform/Specs/MetalCompiler.xcspec +++ b/Sources/SWBApplePlatform/Specs/MetalCompiler.xcspec @@ -252,6 +252,7 @@ Metal30, Metal31, Metal32, + Metal40, ); Category = BuildOptions; }, @@ -272,6 +273,7 @@ Metal30, Metal31, Metal32, + Metal40, ); CommandLineArgs = { UseDeploymentTarget = ( ); @@ -286,6 +288,7 @@ Metal30 = ( "-std=metal3.0", ); Metal31 = ( "-std=metal3.1", ); Metal32 = ( "-std=metal3.2", ); + Metal40 = ( "-std=metal4.0", ); }; }, { diff --git a/Sources/SWBApplePlatform/Specs/MetalLinker.xcspec b/Sources/SWBApplePlatform/Specs/MetalLinker.xcspec index cefed1ad..57359ef7 100644 --- a/Sources/SWBApplePlatform/Specs/MetalLinker.xcspec +++ b/Sources/SWBApplePlatform/Specs/MetalLinker.xcspec @@ -83,6 +83,7 @@ Metal30 = ( "-std=metal3.0", ); Metal31 = ( "-std=metal3.1", ); Metal32 = ( "-std=metal3.2", ); + Metal40 = ( "-std=metal4.0", ); }; }, { diff --git a/Sources/SWBApplePlatform/Specs/en.lproj/com.apple.compilers.metal.strings b/Sources/SWBApplePlatform/Specs/en.lproj/com.apple.compilers.metal.strings index 150c27d7..565687a1 100644 --- a/Sources/SWBApplePlatform/Specs/en.lproj/com.apple.compilers.metal.strings +++ b/Sources/SWBApplePlatform/Specs/en.lproj/com.apple.compilers.metal.strings @@ -90,6 +90,8 @@ "[MTL_LANGUAGE_REVISION]-description-[Metal31]" = "Metal 3.1"; "[MTL_LANGUAGE_REVISION]-value-[Metal32]" = "Metal 3.2"; "[MTL_LANGUAGE_REVISION]-description-[Metal32]" = "Metal 3.2"; +"[MTL_LANGUAGE_REVISION]-value-[Metal40]" = "Metal 4.0"; +"[MTL_LANGUAGE_REVISION]-description-[Metal40]" = "Metal 4.0"; "[MTL_ENABLE_DEBUG_INFO]-name" = "Produce Debugging Information"; "[MTL_ENABLE_DEBUG_INFO]-description" = "Debugging information is required for shader debugging and profiling."; diff --git a/Sources/SWBApplePlatform/Specs/macOSCoreBuildSystem.xcspec b/Sources/SWBApplePlatform/Specs/macOSCoreBuildSystem.xcspec index 87a32f0a..cbace039 100644 --- a/Sources/SWBApplePlatform/Specs/macOSCoreBuildSystem.xcspec +++ b/Sources/SWBApplePlatform/Specs/macOSCoreBuildSystem.xcspec @@ -30,6 +30,12 @@ DefaultValue = ""; Category = "Deployment"; }, + { Name = "REGISTER_APP_GROUPS"; + Type = Boolean; + DefaultValue = NO; + Category = "Code Signing"; + Description = "Register app groups in profiles."; + }, ); }, diff --git a/Sources/SWBApplePlatform/Specs/macOSNativeBuildSystem.xcspec b/Sources/SWBApplePlatform/Specs/macOSNativeBuildSystem.xcspec index 80989788..21fb7663 100644 --- a/Sources/SWBApplePlatform/Specs/macOSNativeBuildSystem.xcspec +++ b/Sources/SWBApplePlatform/Specs/macOSNativeBuildSystem.xcspec @@ -31,6 +31,12 @@ Category = "Deployment"; Description = "Path to a file specifying additional requirements for a product archive."; }, + { Name = "REGISTER_APP_GROUPS"; + Type = Boolean; + DefaultValue = NO; + Category = "Code Signing"; + Description = "Register app groups in profiles."; + }, ); } ) diff --git a/Sources/SWBCore/PlatformRegistry.swift b/Sources/SWBCore/PlatformRegistry.swift index bf4ec315..9aefcffa 100644 --- a/Sources/SWBCore/PlatformRegistry.swift +++ b/Sources/SWBCore/PlatformRegistry.swift @@ -73,9 +73,12 @@ public final class Platform: Sendable { /// Minimum OS version for Swift-in-the-OS support. If this is `nil`, the platform does not support Swift-in-the-OS at all. fileprivate(set) var minimumOSForSwiftInTheOS: Version? = nil - /// Minimum OS version for built-in Swift concurrency support. If this is `nil`, the platform does not support Swift concurrency at all. + /// Minimum OS version for Swift concurrency (Swift 5.5). If this is `nil`, the platform does not support Swift concurrency at all. fileprivate(set) var minimumOSForSwiftConcurrency: Version? = nil + /// Minimum OS version for Span in the standard library (Swift 6.2). If this is `nil`, Span, MutableSpan, and related types are not available. + fileprivate(set) var minimumOSForSwiftSpan: Version? = nil + /// The canonical name of the public SDK for this platform. /// - remark: This does not mean that this SDK exists, just that this is its canonical name if it does exist. @_spi(Testing) public let sdkCanonicalName: String? @@ -244,6 +247,11 @@ extension Platform { return self.minimumOSForSwiftConcurrency ?? self.correspondingDevicePlatform?.minimumOSForSwiftConcurrency ?? nil } + /// Determines the deployment version to use for Swift Span support. + fileprivate func swiftOSSpanVersion(_ deploymentTarget: StringMacroDeclaration) -> Version? { + return self.minimumOSForSwiftSpan ?? self.correspondingDevicePlatform?.minimumOSForSwiftSpan ?? nil + } + /// Determines if the platform supports Swift in the OS. public func supportsSwiftInTheOS(_ scope: MacroEvaluationScope, forceNextMajorVersion: Bool = false, considerTargetDeviceOSVersion: Bool = true) -> Bool { guard let deploymentTarget = self.deploymentTargetMacro else { return false } @@ -265,7 +273,7 @@ extension Platform { return version >= minimumSwiftInTheOSVersion } - /// 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. + /// 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. public func supportsSwiftConcurrencyNatively(_ scope: MacroEvaluationScope, forceNextMajorVersion: Bool = false, considerTargetDeviceOSVersion: Bool = true) -> Bool? { guard let deploymentTarget = self.deploymentTargetMacro else { return false } @@ -287,6 +295,29 @@ extension Platform { return version >= minimumSwiftConcurrencyVersion } + + /// 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. + public func supportsSwiftSpanNatively(_ scope: MacroEvaluationScope, forceNextMajorVersion: Bool, considerTargetDeviceOSVersion: Bool) -> Bool? { + guard let deploymentTarget = self.deploymentTargetMacro else { return false } + + // If we have target device info and its platform matches the build platform, compare the device OS version + let targetDeviceVersion: Version? + if considerTargetDeviceOSVersion && scope.evaluate(BuiltinMacros.TARGET_DEVICE_PLATFORM_NAME) == self.name { + targetDeviceVersion = try? Version(scope.evaluate(BuiltinMacros.TARGET_DEVICE_OS_VERSION)) + } else { + targetDeviceVersion = nil + } + + // Otherwise fall back to comparing the minimum deployment target + let deploymentTargetVersion = try? Version(scope.evaluate(deploymentTarget)) + + guard let version = targetDeviceVersion ?? deploymentTargetVersion else { return false } + + // Return `nil` here as there is no metadata for the platform to allow downstream clients to be aware of this. + guard let minimumSwiftSpanVersion = swiftOSSpanVersion(deploymentTarget) else { return nil } + + return version >= minimumSwiftSpanVersion + } } extension Platform: CustomStringConvertible { @@ -633,6 +664,7 @@ public final class PlatformRegistry { if let variant = platform.defaultSDKVariant { platform.minimumOSForSwiftInTheOS = variant.minimumOSForSwiftInTheOS platform.minimumOSForSwiftConcurrency = variant.minimumOSForSwiftConcurrency + platform.minimumOSForSwiftSpan = variant.minimumOSForSwiftSpan } } diff --git a/Sources/SWBCore/SDKRegistry.swift b/Sources/SWBCore/SDKRegistry.swift index 6764af75..67cb0898 100644 --- a/Sources/SWBCore/SDKRegistry.swift +++ b/Sources/SWBCore/SDKRegistry.swift @@ -313,9 +313,12 @@ public final class SDKVariant: PlatformInfoProvider, Sendable { /// Minimum OS version for Swift-in-the-OS support. If this is `nil`, the platform does not support Swift-in-the-OS at all. public let minimumOSForSwiftInTheOS: Version? - /// Minimum OS version for built-in Swift concurrency support. If this is `nil`, the platform does not support Swift concurrency at all. + /// 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. public let minimumOSForSwiftConcurrency: Version? + /// 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. + public let minimumOSForSwiftSpan: Version? + /// The path prefix under which all built content produced by this SDK variant should be installed, relative to the system root. /// /// Empty string if content should be installed directly into the system root (default). @@ -392,9 +395,10 @@ public final class SDKVariant: PlatformInfoProvider, Sendable { self.clangRuntimeLibraryPlatformName = supportedTargetDict["ClangRuntimeLibraryPlatformName"]?.stringValue ?? Self.fallbackClangRuntimeLibraryPlatformName(variantName: name) - let (os, concurrency) = Self.fallbackSwiftVersions(variantName: name) + let (os, concurrency, span) = Self.fallbackSwiftVersions(variantName: name) self.minimumOSForSwiftInTheOS = try (supportedTargetDict["SwiftOSRuntimeMinimumDeploymentTarget"]?.stringValue ?? os).map { try Version($0) } self.minimumOSForSwiftConcurrency = try (supportedTargetDict["SwiftConcurrencyMinimumDeploymentTarget"]?.stringValue ?? concurrency).map { try Version($0) } + self.minimumOSForSwiftSpan = try (supportedTargetDict["SwiftSpanMinimumDeploymentTarget"]?.stringValue ?? span).map { try Version($0) } self.systemPrefix = supportedTargetDict["SystemPrefix"]?.stringValue ?? { switch name { @@ -445,12 +449,12 @@ public final class SDKVariant: PlatformInfoProvider, Sendable { } } - private static func fallbackSwiftVersions(variantName name: String) -> (String?, String?) { + private static func fallbackSwiftVersions(variantName name: String) -> (os: String?, concurrency: String?, span: String?) { switch name { case "macos", "macosx": - return ("10.14.4", "12.0") + return ("10.14.4", "12.0", "26.0") default: - return (nil, nil) + return (nil, nil, "26.0") } } diff --git a/Sources/SWBCore/SWBFeatureFlag.swift b/Sources/SWBCore/SWBFeatureFlag.swift index 22122d42..942c779b 100644 --- a/Sources/SWBCore/SWBFeatureFlag.swift +++ b/Sources/SWBCore/SWBFeatureFlag.swift @@ -157,5 +157,5 @@ public enum SWBFeatureFlag { public static let enableCacheMetricsLogs = SWBFeatureFlagProperty("EnableCacheMetricsLogs", defaultValue: false) - public static let enableAppSandboxConflictingValuesEmitsWarning = SWBFeatureFlagProperty("AppSandboxConflictingValuesEmitsWarning", defaultValue: false) + public static let enableAppSandboxConflictingValuesEmitsWarning = SWBFeatureFlagProperty("AppSandboxConflictingValuesEmitsWarning", defaultValue: true) } diff --git a/Sources/SWBCore/Settings/BuiltinMacros.swift b/Sources/SWBCore/Settings/BuiltinMacros.swift index 4d862460..a5658e7c 100644 --- a/Sources/SWBCore/Settings/BuiltinMacros.swift +++ b/Sources/SWBCore/Settings/BuiltinMacros.swift @@ -602,6 +602,7 @@ public final class BuiltinMacros { public static let DISABLE_INFOPLIST_PLATFORM_PROCESSING = BuiltinMacros.declareBooleanMacro("DISABLE_INFOPLIST_PLATFORM_PROCESSING") public static let DISABLE_MANUAL_TARGET_ORDER_BUILD_WARNING = BuiltinMacros.declareBooleanMacro("DISABLE_MANUAL_TARGET_ORDER_BUILD_WARNING") public static let DISABLE_STALE_FILE_REMOVAL = BuiltinMacros.declareBooleanMacro("DISABLE_STALE_FILE_REMOVAL") + public static let DISABLE_SWIFT_SPAN_COMPATIBILITY_RPATH = BuiltinMacros.declareBooleanMacro("DISABLE_SWIFT_SPAN_COMPATIBILITY_RPATH") public static let DISABLE_TEST_HOST_PLATFORM_PROCESSING = BuiltinMacros.declareBooleanMacro("DISABLE_TEST_HOST_PLATFORM_PROCESSING") public static let DISABLE_XCFRAMEWORK_SIGNATURE_VALIDATION = BuiltinMacros.declareBooleanMacro("DISABLE_XCFRAMEWORK_SIGNATURE_VALIDATION") public static let DONT_CREATE_BUILT_PRODUCTS_DIR_SYMLINKS = BuiltinMacros.declareBooleanMacro("DONT_CREATE_BUILT_PRODUCTS_DIR_SYMLINKS") @@ -1620,6 +1621,7 @@ public final class BuiltinMacros { DISABLE_INFOPLIST_PLATFORM_PROCESSING, DISABLE_MANUAL_TARGET_ORDER_BUILD_WARNING, DISABLE_STALE_FILE_REMOVAL, + DISABLE_SWIFT_SPAN_COMPATIBILITY_RPATH, DISABLE_TEST_HOST_PLATFORM_PROCESSING, DISABLE_XCFRAMEWORK_SIGNATURE_VALIDATION, DOCC_ARCHIVE_PATH, diff --git a/Sources/SWBCore/Settings/Settings.swift b/Sources/SWBCore/Settings/Settings.swift index 7a74b731..06cb4af5 100644 --- a/Sources/SWBCore/Settings/Settings.swift +++ b/Sources/SWBCore/Settings/Settings.swift @@ -146,6 +146,11 @@ fileprivate struct PreOverridesSettings { table.push(BuiltinMacros.LM_SKIP_METADATA_EXTRACTION, BuiltinMacros.namespace.parseString("YES")) } + // This is a hack to prevent Span back deployment from causing excessive test churn when using an older Xcode in Swift CI. + if core.xcodeProductBuildVersion <= (try! ProductBuildVersion("17A1")) { + table.push(BuiltinMacros.DISABLE_SWIFT_SPAN_COMPATIBILITY_RPATH, BuiltinMacros.namespace.parseString("YES")) + } + // Add the "calculated" settings. addCalculatedUniversalDefaults(&table) diff --git a/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift b/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift index 779d207f..293d6dba 100644 --- a/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift +++ b/Sources/SWBCore/SpecImplementations/Tools/LinkerTools.swift @@ -284,11 +284,20 @@ public final class LdLinkerSpec : GenericLinkerSpec, SpecIdentifierType, @unchec // 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. // The all above discussion is only relevant for platforms that support Swift in the OS. let supportsSwiftConcurrencyNatively = cbc.producer.platform?.supportsSwiftConcurrencyNatively(cbc.scope, forceNextMajorVersion: false, considerTargetDeviceOSVersion: false) ?? true + let supportsSwiftSpanNatively = cbc.producer.platform?.supportsSwiftSpanNatively(cbc.scope, forceNextMajorVersion: false, considerTargetDeviceOSVersion: false) ?? true let shouldEmitRPathForSwiftConcurrency = UserDefaults.allowRuntimeSearchPathAdditionForSwiftConcurrency && !supportsSwiftConcurrencyNatively - if (cbc.producer.platform?.supportsSwiftInTheOS(cbc.scope, forceNextMajorVersion: true, considerTargetDeviceOSVersion: false) != true || cbc.producer.toolchains.usesSwiftOpenSourceToolchain || shouldEmitRPathForSwiftConcurrency) && isUsingSwift && cbc.producer.platform?.minimumOSForSwiftInTheOS != nil { - // NOTE: For swift.org toolchains, this is fine as `DYLD_LIBRARY_PATH` is used to override these settings. + let shouldEmitRPathForSwiftSpan = !cbc.scope.evaluate(BuiltinMacros.DISABLE_SWIFT_SPAN_COMPATIBILITY_RPATH) && !supportsSwiftSpanNatively + if ( + cbc.producer.platform?.supportsSwiftInTheOS(cbc.scope, forceNextMajorVersion: true, considerTargetDeviceOSVersion: false) != true || + cbc.producer.toolchains.usesSwiftOpenSourceToolchain || + shouldEmitRPathForSwiftConcurrency || + shouldEmitRPathForSwiftSpan + ) + && isUsingSwift + && cbc.producer.platform?.minimumOSForSwiftInTheOS != nil { + // NOTE: For swift.org toolchains, this is fine as `DYLD_LIBRARY_PATH` is used to override these settings. let swiftABIVersion = await (cbc.producer.swiftCompilerSpec.discoveredCommandLineToolSpecInfo(cbc.producer, cbc.scope, delegate) as? DiscoveredSwiftCompilerToolSpecInfo)?.swiftABIVersion - runpathSearchPaths.insert( swiftABIVersion.flatMap { "/usr/lib/swift-\($0)" } ?? "/usr/lib/swift", at: 0) + runpathSearchPaths.insert( swiftABIVersion.flatMap { "/usr/lib/swift-\($0)" } ?? "/usr/lib/swift", at: 0) } return runpathSearchPaths diff --git a/Sources/SWBCore/SpecImplementations/Tools/SwiftStdLibTool.swift b/Sources/SWBCore/SpecImplementations/Tools/SwiftStdLibTool.swift index ce7edc49..985a6988 100644 --- a/Sources/SWBCore/SpecImplementations/Tools/SwiftStdLibTool.swift +++ b/Sources/SWBCore/SpecImplementations/Tools/SwiftStdLibTool.swift @@ -22,7 +22,7 @@ public final class SwiftStdLibToolSpec : GenericCommandLineToolSpec, SpecIdentif } /// Construct a new task to run the Swift standard library tool. - public func constructSwiftStdLibraryToolTask(_ cbc:CommandBuildContext, _ delegate: any TaskGenerationDelegate, foldersToScan: MacroStringListExpression?, filterForSwiftOS: Bool, backDeploySwiftConcurrency: Bool) async { + public func constructSwiftStdLibraryToolTask(_ cbc:CommandBuildContext, _ delegate: any TaskGenerationDelegate, foldersToScan: MacroStringListExpression?, filterForSwiftOS: Bool, backDeploySwiftConcurrency: Bool, backDeploySwiftSpan: Bool) async { precondition(cbc.outputs.isEmpty, "Unexpected output paths \(cbc.outputs.map { "'\($0.str)'" }) passed to \(type(of: self)).") let input = cbc.input @@ -86,6 +86,10 @@ public final class SwiftStdLibToolSpec : GenericCommandLineToolSpec, SpecIdentif commandLine.append("--back-deploy-swift-concurrency") } + if backDeploySwiftSpan { + commandLine.append("--back-deploy-swift-span") + } + let outputs = [delegate.createVirtualNode("CopySwiftStdlib \(wrapperPathString.str)")] 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) diff --git a/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/SwiftStandardLibrariesTaskProducer.swift b/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/SwiftStandardLibrariesTaskProducer.swift index c74e1843..1735c324 100644 --- a/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/SwiftStandardLibrariesTaskProducer.swift +++ b/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/SwiftStandardLibrariesTaskProducer.swift @@ -104,10 +104,13 @@ final class SwiftStandardLibrariesTaskProducer: PhasedTaskProducer, TaskProducer let supportsConcurrencyNatively = context.platform?.supportsSwiftConcurrencyNatively(scope) let backDeploySwiftConcurrency = supportsConcurrencyNatively != nil && supportsConcurrencyNatively != true + let supportsSpanNatively = context.platform?.supportsSwiftSpanNatively(scope, forceNextMajorVersion: false, considerTargetDeviceOSVersion: true) + let backDeploySwiftSpan = supportsSpanNatively != nil && supportsSpanNatively != true + let cbc = CommandBuildContext(producer: context, scope: scope, inputs: [ input ]) let foldersToScanExpr: MacroStringListExpression? = foldersToScan.count > 0 ? scope.namespace.parseLiteralStringList(foldersToScan): nil await appendGeneratedTasks(&tasks) { delegate in - await context.swiftStdlibToolSpec.constructSwiftStdLibraryToolTask(cbc, delegate, foldersToScan: foldersToScanExpr, filterForSwiftOS: filterForSwiftOS, backDeploySwiftConcurrency: backDeploySwiftConcurrency) + await context.swiftStdlibToolSpec.constructSwiftStdLibraryToolTask(cbc, delegate, foldersToScan: foldersToScanExpr, filterForSwiftOS: filterForSwiftOS, backDeploySwiftConcurrency: backDeploySwiftConcurrency, backDeploySwiftSpan: backDeploySwiftSpan) } } diff --git a/Sources/SWBTaskExecution/TaskActions/EmbedSwiftStdLibTaskAction.swift b/Sources/SWBTaskExecution/TaskActions/EmbedSwiftStdLibTaskAction.swift index 4c995f5d..40509fc2 100644 --- a/Sources/SWBTaskExecution/TaskActions/EmbedSwiftStdLibTaskAction.swift +++ b/Sources/SWBTaskExecution/TaskActions/EmbedSwiftStdLibTaskAction.swift @@ -161,12 +161,18 @@ public final class EmbedSwiftStdLibTaskAction: TaskAction { // If true, then the Swift concurrency dylibs should be copied into the app/framework's bundles. var backDeploySwiftConcurrency = false + // If true, then the Swift Span dylibs should be copied into the app/framework's bundles. + var backDeploySwiftSpan = false + // The allowed list of libraries that should *not* be filtered when `filterForSwiftOS=true`. let allowedLibsForSwiftOS = ["libswiftXCTest" ] // The allowed list of libraries that should *not* be filtered when `backDeploySwiftConcurrency=true`. let allowedLibsForSwiftConcurrency = ["libswift_Concurrency"] + // The allowed list of libraries that should *not* be filtered when `backDeploySwiftSpan=true`. + let allowedLibsForSwiftSpan = ["libswiftCompatibilitySpan"] + func absolutePath(_ path: Path) -> Path { return path.isAbsolute ? path : task.workingDirectory.join(path) } @@ -207,7 +213,7 @@ public final class EmbedSwiftStdLibTaskAction: TaskAction { func effectiveSourceDirectories(_ toolchainsDirs: OrderedSet, platform: String) -> [Path] { // FIXME: Maybe these should be defined within the toolchains or we could simply scan the toolchain directory as well. - let swiftBackdeploymentDirs = ["usr/lib/swift-5.0", "usr/lib/swift-5.5"] + let swiftBackdeploymentDirs = ["usr/lib/swift-5.0", "usr/lib/swift-5.5", "usr/lib/swift-6.2"] var dirs = [Path]() for dir in toolchainsDirs { @@ -369,6 +375,9 @@ public final class EmbedSwiftStdLibTaskAction: TaskAction { case "--back-deploy-swift-concurrency": self.backDeploySwiftConcurrency = true + case "--back-deploy-swift-span": + self.backDeploySwiftSpan = true + default: throw StubError.error("unrecognized argument: \(arg)") } @@ -788,6 +797,9 @@ public final class EmbedSwiftStdLibTaskAction: TaskAction { if backDeploySwiftConcurrency && allowedLibsForSwiftConcurrency.contains(item) { shouldInclude = true } + if backDeploySwiftSpan && allowedLibsForSwiftSpan.contains(item) { + shouldInclude = true + } return shouldInclude } diff --git a/Sources/SWBTestSupport/SkippedTestSupport.swift b/Sources/SWBTestSupport/SkippedTestSupport.swift index ca3eed21..10decc7a 100644 --- a/Sources/SWBTestSupport/SkippedTestSupport.swift +++ b/Sources/SWBTestSupport/SkippedTestSupport.swift @@ -283,6 +283,10 @@ extension Trait where Self == Testing.ConditionTrait { }) } + package static func requireXcode26(sourceLocation: SourceLocation = #_sourceLocation) -> Self { + requireMinimumXcodeBuildVersion("17A1", sourceLocation: sourceLocation) + } + /// Constructs a condition trait that causes a test to be disabled if not running against at least the given version of Xcode. package static func requireMinimumXcodeBuildVersion(_ version: ProductBuildVersion, sourceLocation: SourceLocation = #_sourceLocation) -> Self { requireXcodeBuildVersions(in: version..., sourceLocation: sourceLocation) diff --git a/Sources/SWBUtil/POSIX.swift b/Sources/SWBUtil/POSIX.swift index 1d3576c6..6a4e85d4 100644 --- a/Sources/SWBUtil/POSIX.swift +++ b/Sources/SWBUtil/POSIX.swift @@ -14,12 +14,10 @@ import SWBLibc public import protocol Foundation.LocalizedError -#if os(Windows) #if canImport(System) -import System +public import System #else -import SystemPackage -#endif +public import SystemPackage #endif public enum POSIX: Sendable { @@ -78,12 +76,16 @@ public enum POSIX: Sendable { } public struct POSIXError: Error, LocalizedError, CustomStringConvertible, Equatable { - public let code: Int32 + public let underlyingError: Errno public let context: String? public let arguments: [String] + public var code: Int32 { + underlyingError.rawValue + } + public init(_ code: Int32, context: String? = nil, _ arguments: [String]) { - self.code = code + self.underlyingError = Errno(rawValue: code) self.context = context self.arguments = arguments } @@ -93,7 +95,7 @@ public struct POSIXError: Error, LocalizedError, CustomStringConvertible, Equata } public var description: String { - let end = "\(String(cString: strerror(code))) (\(code))" + let end = "\(underlyingError.description) (\(code))" if let context { return "\(context)(\(arguments.joined(separator: ", "))): \(end)" } diff --git a/Sources/SWBUtil/PbxCp.swift b/Sources/SWBUtil/PbxCp.swift index ee9b67a3..f94fc7b7 100644 --- a/Sources/SWBUtil/PbxCp.swift +++ b/Sources/SWBUtil/PbxCp.swift @@ -432,7 +432,8 @@ fileprivate func copyTree(_ srcPath: Path, _ dstPath: Path, options: CopyOptions do { _srcPath = try localFS.realpath(srcPath) } catch let error as POSIXError { - outStream <<< "error: \(srcPath.str): \(String(cString: strerror(error.code)))\n" + // TODO: Does this really need to print specially for POSIXError? + outStream <<< "error: \(srcPath.str): \(error.underlyingError.description)\n" return false } catch { outStream <<< "error: \(srcPath.str): \(error.localizedDescription)\n" @@ -441,7 +442,8 @@ fileprivate func copyTree(_ srcPath: Path, _ dstPath: Path, options: CopyOptions do { _dstPath = try localFS.realpath(dstPath) } catch let error as POSIXError { - outStream <<< "error: \(dstPath.str): \(String(cString: strerror(error.code)))\n" + // TODO: Does this really need to print specially for POSIXError? + outStream <<< "error: \(dstPath.str): \(error.underlyingError.description)\n" return false } catch { outStream <<< "error: \(srcPath.str): \(error.localizedDescription)\n" diff --git a/Tests/SWBBuildSystemTests/BuildOperationTests.swift b/Tests/SWBBuildSystemTests/BuildOperationTests.swift index d6806ebc..8e2963f7 100644 --- a/Tests/SWBBuildSystemTests/BuildOperationTests.swift +++ b/Tests/SWBBuildSystemTests/BuildOperationTests.swift @@ -3574,27 +3574,53 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script } } - @Test(.requireSDKs(.macOS)) + + @Test(.requireSDKs(.macOS), .requireXcode26()) func copySwiftLibs_preSwiftOS_macos() async throws { - try await _testCopySwiftLibs(deploymentTarget: "10.14.3", shouldFilterSwiftLibs: false, shouldBackDeploySwiftConcurrency: false) + // Swift does not exist in the OS, so shouldFilterSwiftLibs is false. macOS 10.14.3 does not + // support use of back deployed span and concurrency, but if code using either has + // availability guards with a version supporting back deployment, the compatibility + // libraries will be weakly linked and should be copied. + try await _testCopySwiftLibs(deploymentTarget: "10.14.3", shouldFilterSwiftLibs: false, shouldBackDeploySwiftConcurrency: true, shouldBackDeploySwiftSpan: true) } - @Test(.requireSDKs(.macOS)) + @Test(.requireSDKs(.macOS), .requireXcode26()) func copySwiftLibs_postSwiftOS_macos() async throws { - try await _testCopySwiftLibs(deploymentTarget: "10.14.4", shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: true) + // macOS 10.14.4 is the first version with Swift in the OS, so shouldFilterSwiftLibs should + // be true on this and later versions. Both Concurrency and Span back deploy starting + // at this version. + try await _testCopySwiftLibs(deploymentTarget: "10.14.4", shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: true, shouldBackDeploySwiftSpan: true) } - @Test(.requireSDKs(.macOS)) + @Test(.requireSDKs(.macOS), .requireXcode26()) + func copySwiftLibs_postSwiftOS_preSwiftConcurrency_macos() async throws { + // macOS 11.5 includes Swift in the OS, but predates the OS copy of Concurrency. + // Both Concurrency and Span should back deploy. + try await _testCopySwiftLibs(deploymentTarget: "11.5", shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: true, shouldBackDeploySwiftSpan: true) + } + + @Test(.requireSDKs(.macOS), .requireXcode26()) func copySwiftLibs_postSwiftConcurrency_macos() async throws { - try await _testCopySwiftLibs(deploymentTarget: "12.0", shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: false) + // macOS 12.0 includes Swift and Concurrency in the OS but not Span. + // Only Span should back deploy. + try await _testCopySwiftLibs(deploymentTarget: "12.0", shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: false, shouldBackDeploySwiftSpan: true) } - @Test(.requireSDKs(.macOS)) - func copySwiftLibs_postSwiftOS_preSwiftConcurrency_macos() async throws { - try await _testCopySwiftLibs(deploymentTarget: "11.5", shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: true) + @Test(.requireSDKs(.macOS), .requireXcode26()) + func copySwiftLibs_postSwiftSpan_macos() async throws { + // macOS 26.0 includes Swift, Concurrency, and Span in the OS. + try await _testCopySwiftLibs(deploymentTarget: "26.0", shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: false, shouldBackDeploySwiftSpan: false) } - func _testCopySwiftLibs(deploymentTarget: String, shouldFilterSwiftLibs: Bool, shouldBackDeploySwiftConcurrency: Bool, file: StaticString = #filePath, line: Int = #line) async throws { + func _testCopySwiftLibs(deploymentTarget: String, shouldFilterSwiftLibs: Bool, shouldBackDeploySwiftConcurrency: Bool, shouldBackDeploySwiftSpan: Bool, file: StaticString = #filePath, line: Int = #line) async throws { + let core = try await getCore() + let defaultDeploymentTarget = core.loadSDK(.macOS).defaultDeploymentTarget + let testsDeploymentTarget: String + if try Version(deploymentTarget) < Version(defaultDeploymentTarget) { + testsDeploymentTarget = defaultDeploymentTarget + } else { + testsDeploymentTarget = deploymentTarget + } // Create a temporary test workspace, consisting of: // - an application // - a framework embedded directly inside the application @@ -3603,7 +3629,6 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script // All use Swift, and the point is to test that the right libswift libs // get copied as new import statements are added to the sources that are // only indirectly included inside the app. - let core = try await getCore() try await withTemporaryDirectory { tmpDirPath async throws -> Void in let testWorkspace = try await TestWorkspace( "Test", @@ -3769,7 +3794,7 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script "Debug", buildSettings: [ // Override the deployment target for tests so we don't get a warning that our deployment target is higher than XCTest's. - "MACOSX_DEPLOYMENT_TARGET": core.loadSDK(.macOS).defaultDeploymentTarget + "MACOSX_DEPLOYMENT_TARGET": testsDeploymentTarget ] ) ], @@ -3796,6 +3821,8 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script public func foo() -> NSString { return "Foo" } @available(macOS 10.15, *) public actor A { } + @available(macOS 10.14.4, *) + public func bar() { print(Span.self) } """ } try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("TestProject/SysExMain.swift")) { contents in @@ -3805,6 +3832,8 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script @available(macOS 10.15, *) public actor A { } @main struct Main { public static func main() { } } + @available(macOS 10.14.4, *) + public func bar() { print(Span.self) } """ } try await tester.fs.writeFileContents(testWorkspace.sourceRoot.join("TestProject/SubFrameworkSource.swift")) { contents in @@ -3818,6 +3847,8 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script @available(macOS 10.15, *) public actor A { } } + @available(macOS 10.14.4, *) + public func bar() { print(Span.self) } """ } @@ -3895,26 +3926,25 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script #expect(dependencyInfo.version == expectedDependencyInfo.version) XCTAssertSuperset(Set(dependencyInfo.inputs), Set(expectedDependencyInfo.inputs)) - // 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. - if shouldFilterSwiftLibs && !shouldBackDeploySwiftConcurrency { + // Check the baseline dependency info when nothing is backdeployed. + if shouldFilterSwiftLibs && !shouldBackDeploySwiftConcurrency && !shouldBackDeploySwiftSpan { #expect(dependencyInfo == expectedDependencyInfo) } + #expect(dependencyInfo.outputs.sorted().contains(expectedDependencyInfo.outputs.sorted())) - if !shouldFilterSwiftLibs { - #expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/macosx/libswiftCore.dylib").str)) - #expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/macosx/libswiftFoundation.dylib").str)) - #expect(dependencyInfo.outputs.sorted().contains(expectedDependencyInfo.outputs.sorted())) - #expect(dependencyInfo.outputs.contains(buildDir.join("App.app/Contents/Frameworks/libswiftCore.dylib").str)) - #expect(dependencyInfo.outputs.contains(buildDir.join("App.app/Contents/Frameworks/libswiftFoundation.dylib").str)) - } + // If we should not filter Swift libs, check they exist in the dependency info. + #expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/macosx/libswiftCore.dylib").str) == !shouldFilterSwiftLibs) + #expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/macosx/libswiftFoundation.dylib").str) == !shouldFilterSwiftLibs) + #expect(dependencyInfo.outputs.contains(buildDir.join("App.app/Contents/Frameworks/libswiftCore.dylib").str) == !shouldFilterSwiftLibs) + #expect(dependencyInfo.outputs.contains(buildDir.join("App.app/Contents/Frameworks/libswiftFoundation.dylib").str) == !shouldFilterSwiftLibs) - if shouldBackDeploySwiftConcurrency { - // Note all toolchains have this yet... - if tester.fs.exists(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib")) { - #expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib").str)) - #expect(dependencyInfo.outputs.contains(buildDir.join("App.app/Contents/Frameworks/libswift_Concurrency.dylib").str)) - } - } + // If we should back deploy Concurrency, ensure it's in the dependency info. + #expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib").str) == shouldBackDeploySwiftConcurrency) + #expect(dependencyInfo.outputs.contains(buildDir.join("App.app/Contents/Frameworks/libswift_Concurrency.dylib").str) == shouldBackDeploySwiftConcurrency) + + // If we should back deploy span, ensure it's in the dependency info. + #expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-6.2/macosx/libswiftCompatibilitySpan.dylib").str) == shouldBackDeploySwiftSpan) + #expect(dependencyInfo.outputs.contains(buildDir.join("App.app/Contents/Frameworks/libswiftCompatibilitySpan.dylib").str) == shouldBackDeploySwiftSpan) } do { @@ -3940,47 +3970,42 @@ That command depends on command in Target 'agg2' (project \'aProject\'): script #expect(unitTestsDependencyInfo.version == unitTestsExpectedDependencyInfo.version) XCTAssertSuperset(Set(unitTestsDependencyInfo.inputs), Set(unitTestsExpectedDependencyInfo.inputs)) - // 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. - if shouldFilterSwiftLibs && !shouldBackDeploySwiftConcurrency { + // Ensure that the baseline dependency info is correct when nothing is backdeployed. + if shouldFilterSwiftLibs && !shouldBackDeploySwiftConcurrency && !shouldBackDeploySwiftSpan { #expect(unitTestsDependencyInfo == unitTestsExpectedDependencyInfo) } - if !shouldFilterSwiftLibs { - if (try Version(core.loadSDK(.macOS).defaultDeploymentTarget)) < Version(10, 15) { - #expect(unitTestsDependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/macosx/libswiftCore.dylib").str)) - #expect(unitTestsDependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/macosx/libswiftFoundation.dylib").str)) - } + // As long as we weren't forced to raise the deployment target for tests, check the dependency info. + if testsDeploymentTarget == deploymentTarget { #expect(unitTestsDependencyInfo.outputs.sorted().contains(unitTestsExpectedDependencyInfo.outputs.sorted())) - if (try Version(core.loadSDK(.macOS).defaultDeploymentTarget)) < Version(10, 15) { - #expect(unitTestsDependencyInfo.outputs.contains(buildDir.join("UnitTests.xctest/Contents/Frameworks/libswiftCore.dylib").str)) - #expect(unitTestsDependencyInfo.outputs.contains(buildDir.join("UnitTests.xctest/Contents/Frameworks/libswiftFoundation.dylib").str)) - } - } - if shouldBackDeploySwiftConcurrency { + #expect(unitTestsDependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/macosx/libswiftCore.dylib").str) == !shouldFilterSwiftLibs) + #expect(unitTestsDependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.0/macosx/libswiftFoundation.dylib").str) == !shouldFilterSwiftLibs) + #expect(unitTestsDependencyInfo.outputs.contains(buildDir.join("UnitTests.xctest/Contents/Frameworks/libswiftCore.dylib").str) == !shouldFilterSwiftLibs) + #expect(unitTestsDependencyInfo.outputs.contains(buildDir.join("UnitTests.xctest/Contents/Frameworks/libswiftFoundation.dylib").str) == !shouldFilterSwiftLibs) + + + #expect(unitTestsDependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib").str) == shouldBackDeploySwiftConcurrency) + #expect(unitTestsDependencyInfo.outputs.contains(buildDir.join("SwiftlessApp.app/Contents/Frameworks/libswift_Concurrency.dylib").str) == shouldBackDeploySwiftConcurrency) + // NOTE: Tests have their deployment target overridden. - if (try Version(core.loadSDK(.macOS).defaultDeploymentTarget)) < Version(10, 15) { - // Note all toolchains have this yet... - if tester.fs.exists(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib")) { - #expect(unitTestsDependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib").str)) - #expect(unitTestsDependencyInfo.outputs.contains(buildDir.join("SwiftlessApp.app/Contents/Frameworks/libswift_Concurrency.dylib").str)) - } - } + #expect(unitTestsDependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib").str) == shouldBackDeploySwiftSpan) + #expect(unitTestsDependencyInfo.outputs.contains(buildDir.join("SwiftlessApp.app/Contents/Frameworks/libswift_Concurrency.dylib").str) == shouldBackDeploySwiftSpan) } } do { - // Checks that a Swift-free app which embeds a system extension using Swift Concurrency, embeds the back-deployment dylib. + // Checks that a Swift-free app which embeds a system extension using Swift Concurrency and Span, embeds the back-deployment dylibs. let swiftDepsPath = tmpDirPath.join(Path("Test/TestProject/build/TestProject.build/Debug/SwiftlessSysExApp.build/SwiftStdLibToolInputDependencies.dep")) let dependencyInfo = try DependencyInfo(bytes: tester.fs.read(swiftDepsPath).bytes) - if shouldBackDeploySwiftConcurrency { - // Note all toolchains have this yet... - if tester.fs.exists(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib")) { - #expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib").str)) - #expect(dependencyInfo.outputs.contains(buildDir.join("SwiftlessSysExApp.app/Contents/Frameworks/libswift_Concurrency.dylib").str)) - } - } + // If we should back deploy Concurrency, ensure it's in the dependency info. + #expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/libswift_Concurrency.dylib").str) == shouldBackDeploySwiftConcurrency) + #expect(dependencyInfo.outputs.contains(buildDir.join("SwiftlessSysExApp.app/Contents/Frameworks/libswift_Concurrency.dylib").str) == shouldBackDeploySwiftConcurrency) + + // If we should back deploy Span, ensure it's in the dependency info. + #expect(dependencyInfo.inputs.contains(core.developerPath.path.join("Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-6.2/macosx/libswiftCompatibilitySpan.dylib").str) == shouldBackDeploySwiftSpan) + #expect(dependencyInfo.outputs.contains(buildDir.join("SwiftlessSysExApp.app/Contents/Frameworks/libswiftCompatibilitySpan.dylib").str) == shouldBackDeploySwiftSpan) } } diff --git a/Tests/SWBCoreTests/CommandLineSpecTests.swift b/Tests/SWBCoreTests/CommandLineSpecTests.swift index 826e08ab..c00dfc25 100644 --- a/Tests/SWBCoreTests/CommandLineSpecTests.swift +++ b/Tests/SWBCoreTests/CommandLineSpecTests.swift @@ -681,7 +681,7 @@ import SWBMacro let cbc = CommandBuildContext(producer: producer, scope: mockScope, inputs: [FileToBuild(absolutePath: Path.root.join("tmp/input"), fileType: mockFileType)], output: nil) // Check that task construction sets the correct env bindings. - await stdlibTool.constructSwiftStdLibraryToolTask(cbc, delegate, foldersToScan: nil, filterForSwiftOS: false, backDeploySwiftConcurrency: false) + await stdlibTool.constructSwiftStdLibraryToolTask(cbc, delegate, foldersToScan: nil, filterForSwiftOS: false, backDeploySwiftConcurrency: false, backDeploySwiftSpan: false) #expect(delegate.shellTasks.count == 1) let task = try #require(delegate.shellTasks[safe: 0]) #expect(task.environment.bindingsDictionary == ["CODESIGN_ALLOCATE": "/path/to/codesign_allocate"]) diff --git a/Tests/SWBTaskConstructionTests/BuildToolTaskConstructionTests.swift b/Tests/SWBTaskConstructionTests/BuildToolTaskConstructionTests.swift index 5bcdaf39..38f8ed00 100644 --- a/Tests/SWBTaskConstructionTests/BuildToolTaskConstructionTests.swift +++ b/Tests/SWBTaskConstructionTests/BuildToolTaskConstructionTests.swift @@ -1567,6 +1567,7 @@ fileprivate struct BuildToolTaskConstructionTests: CoreBasedTests { ["macosx", "Metal30"] : "-std=metal3.0", ["macosx", "Metal31"] : "-std=metal3.1", ["macosx", "Metal32"] : "-std=metal3.2", + ["macosx", "Metal40"] : "-std=metal4.0", ["iphoneos", "UseDeploymentTarget"] : "", ["iphoneos", "iOSMetal10"] : "-std=ios-metal1.0", @@ -1580,6 +1581,7 @@ fileprivate struct BuildToolTaskConstructionTests: CoreBasedTests { ["iphoneos", "Metal30"] : "-std=metal3.0", ["iphoneos", "Metal31"] : "-std=metal3.1", ["iphoneos", "Metal32"] : "-std=metal3.2", + ["iphoneos", "Metal40"] : "-std=metal4.0", ["appletvos", "UseDeploymentTarget"] : "", ["appletvos", "Metal11"] : "-std=ios-metal1.1", @@ -1592,10 +1594,12 @@ fileprivate struct BuildToolTaskConstructionTests: CoreBasedTests { ["appletvos", "Metal30"] : "-std=metal3.0", ["appletvos", "Metal31"] : "-std=metal3.1", ["appletvos", "Metal32"] : "-std=metal3.2", + ["appletvos", "Metal40"] : "-std=metal4.0", ["xros", "UseDeploymentTarget"] : "", ["xros", "Metal31"] : "-std=metal3.1", ["xros", "Metal32"] : "-std=metal3.2", + ["xros", "Metal40"] : "-std=metal4.0", ] for (language, expectedOption) in optionForLanguage { diff --git a/Tests/SWBTaskConstructionTests/SwiftTaskConstructionTests.swift b/Tests/SWBTaskConstructionTests/SwiftTaskConstructionTests.swift index 8bb8d8f9..62d4d887 100644 --- a/Tests/SWBTaskConstructionTests/SwiftTaskConstructionTests.swift +++ b/Tests/SWBTaskConstructionTests/SwiftTaskConstructionTests.swift @@ -71,35 +71,42 @@ fileprivate struct SwiftTaskConstructionTests: CoreBasedTests { @Test(.requireSDKs(.macOS)) func swiftAppBasics_preSwiftOS() async throws { - try await _testSwiftAppBasics(deploymentTargetVersion: "10.14.0", shouldEmitSwiftRPath: true, shouldFilterSwiftLibs: false, shouldBackDeploySwiftConcurrency: true) + try await _testSwiftAppBasics(deploymentTargetVersion: "10.14.0", shouldEmitSwiftRPath: true, shouldFilterSwiftLibs: false, shouldBackDeploySwiftConcurrency: true, shouldBackDeploySwiftSpan: true) } @Test(.requireSDKs(.macOS)) func swiftAppBasics_postSwiftOS() async throws { - try await _testSwiftAppBasics(deploymentTargetVersion: "12.0", shouldEmitSwiftRPath: false, shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: false) + try await _testSwiftAppBasics(deploymentTargetVersion: "12.0", shouldEmitSwiftRPath: true, shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: false, shouldBackDeploySwiftSpan: true, missingSpanCompatibilitySuppressesRPath: true) + } + + @Test(.requireSDKs(.macOS), .requireXcode26()) + func swiftAppBasics_postSwiftSpan() async throws { + try await _testSwiftAppBasics(deploymentTargetVersion: "26.0", shouldEmitSwiftRPath: false, shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: false, shouldBackDeploySwiftSpan: false) } @Test(.requireSDKs(.macOS)) func swiftAppBasics_preSwiftOSDeploymentTarget_postSwiftOSTargetDevice() async throws { - try await _testSwiftAppBasics(deploymentTargetVersion: "10.14.0", targetDeviceOSVersion: "11.0", targetDevicePlatformName: "macosx", shouldEmitSwiftRPath: true, shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: true) + try await _testSwiftAppBasics(deploymentTargetVersion: "10.14.0", targetDeviceOSVersion: "11.0", targetDevicePlatformName: "macosx", shouldEmitSwiftRPath: true, shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: true, shouldBackDeploySwiftSpan: true) } @Test(.requireSDKs(.macOS)) func swiftAppBasics_preSwiftOSDeploymentTarget_postSwiftOSTargetDevice_mixedPlatform() async throws { - try await _testSwiftAppBasics(deploymentTargetVersion: "10.14.0", targetDeviceOSVersion: "14.0", targetDevicePlatformName: "iphoneos", shouldEmitSwiftRPath: true, shouldFilterSwiftLibs: false, shouldBackDeploySwiftConcurrency: true) + try await _testSwiftAppBasics(deploymentTargetVersion: "10.14.0", targetDeviceOSVersion: "14.0", targetDevicePlatformName: "iphoneos", shouldEmitSwiftRPath: true, shouldFilterSwiftLibs: false, shouldBackDeploySwiftConcurrency: true, shouldBackDeploySwiftSpan: true) } @Test(.requireSDKs(.macOS)) func swiftAppBasics_postSwiftOSDeploymentTarget_preSwiftConcurrencySupportedNatively() async throws { - try await _testSwiftAppBasics(deploymentTargetVersion: "11.0", shouldEmitSwiftRPath: true, shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: true) + try await _testSwiftAppBasics(deploymentTargetVersion: "11.0", shouldEmitSwiftRPath: true, shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: true, shouldBackDeploySwiftSpan: true) } @Test(.requireSDKs(.macOS), .userDefaults(["AllowRuntimeSearchPathAdditionForSwiftConcurrency": "0"])) func swiftAppBasics_postSwiftOSDeploymentTarget_preSwiftConcurrencySupportedNatively_DisallowRpathInjection() async throws { - try await _testSwiftAppBasics(deploymentTargetVersion: "11.0", shouldEmitSwiftRPath:false, shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: true) + try await _testSwiftAppBasics(deploymentTargetVersion: "11.0", shouldEmitSwiftRPath:true, shouldFilterSwiftLibs: true, shouldBackDeploySwiftConcurrency: true, shouldBackDeploySwiftSpan: true, + missingSpanCompatibilitySuppressesRPath: true) } - func _testSwiftAppBasics(deploymentTargetVersion: String, targetDeviceOSVersion: String? = nil, targetDevicePlatformName: String? = nil, toolchain toolchainIdentifier: String = "default", shouldEmitSwiftRPath: Bool, shouldFilterSwiftLibs: Bool, shouldBackDeploySwiftConcurrency: Bool) async throws { + func _testSwiftAppBasics(deploymentTargetVersion: String, targetDeviceOSVersion: String? = nil, targetDevicePlatformName: String? = nil, toolchain toolchainIdentifier: String = "default", shouldEmitSwiftRPath: Bool, shouldFilterSwiftLibs: Bool, shouldBackDeploySwiftConcurrency: Bool, shouldBackDeploySwiftSpan: Bool, + missingSpanCompatibilitySuppressesRPath: Bool = false) async throws { let swiftCompilerPath = try await self.swiftCompilerPath let swiftVersion = try await self.swiftVersion let swiftFeatures = try await self.swiftFeatures @@ -184,6 +191,12 @@ fileprivate struct SwiftTaskConstructionTests: CoreBasedTests { let defaultToolchain = try #require(core.toolchainRegistry.defaultToolchain) let effectiveToolchain = core.toolchainRegistry.lookup(toolchainIdentifier) ?? defaultToolchain + // Disable the expectation that we'll have the Swift rpath if we're testing with a version that + // predates the inclusion of the Span back-deployment library. + // NOTE: This should be removed when CI moves forward to Xcode 26 everywhere. + let predatesCompatibilitySpan = core.xcodeProductBuildVersion <= (try! ProductBuildVersion("17A1")) + let shouldEmitSwiftRPath = shouldEmitSwiftRPath && !(predatesCompatibilitySpan && missingSpanCompatibilitySuppressesRPath) + // Check the debug build. await tester.checkBuild(parameters, runDestination: .macOS, fs: fs) { results in results.checkTarget("AppTarget") { target in @@ -412,7 +425,7 @@ fileprivate struct SwiftTaskConstructionTests: CoreBasedTests { // There should be a 'CopySwiftLibs' task. results.checkTask(.matchTarget(target), .matchRuleType("CopySwiftLibs")) { task -> Void in task.checkRuleInfo(["CopySwiftLibs", "\(SRCROOT)/build/Debug/AppTarget.app"]) - task.checkCommandLine(([["builtin-swiftStdLibTool", "--copy", "--verbose", "--scan-executable", "\(SRCROOT)/build/Debug/AppTarget.app/Contents/MacOS/AppTarget", "--scan-folder", "\(SRCROOT)/build/Debug/AppTarget.app/Contents/Frameworks", "--scan-folder", "\(SRCROOT)/build/Debug/AppTarget.app/Contents/PlugIns", "--scan-folder", "\(SRCROOT)/build/Debug/AppTarget.app/Contents/Library/SystemExtensions", "--scan-folder", "\(SRCROOT)/build/Debug/AppTarget.app/Contents/Extensions", "--scan-folder", "\(SRCROOT)/build/Debug/FwkTarget.framework", "--platform", "macosx", "--toolchain", effectiveToolchain.path.str], (toolchainIdentifier == "default" ? [] : ["--toolchain", defaultToolchain.path.str]), ["--destination", "\(SRCROOT)/build/Debug/AppTarget.app/Contents/Frameworks", "--strip-bitcode", "--strip-bitcode-tool", "\(effectiveToolchain.path.str)/usr/bin/bitcode_strip", "--emit-dependency-info", "\(SRCROOT)/build/aProject.build/Debug/AppTarget.build/SwiftStdLibToolInputDependencies.dep"], shouldFilterSwiftLibs ? ["--filter-for-swift-os"] : [], shouldBackDeploySwiftConcurrency ? ["--back-deploy-swift-concurrency"] : []] as [[String]]).reduce([], +)) + task.checkCommandLine(([["builtin-swiftStdLibTool", "--copy", "--verbose", "--scan-executable", "\(SRCROOT)/build/Debug/AppTarget.app/Contents/MacOS/AppTarget", "--scan-folder", "\(SRCROOT)/build/Debug/AppTarget.app/Contents/Frameworks", "--scan-folder", "\(SRCROOT)/build/Debug/AppTarget.app/Contents/PlugIns", "--scan-folder", "\(SRCROOT)/build/Debug/AppTarget.app/Contents/Library/SystemExtensions", "--scan-folder", "\(SRCROOT)/build/Debug/AppTarget.app/Contents/Extensions", "--scan-folder", "\(SRCROOT)/build/Debug/FwkTarget.framework", "--platform", "macosx", "--toolchain", effectiveToolchain.path.str], (toolchainIdentifier == "default" ? [] : ["--toolchain", defaultToolchain.path.str]), ["--destination", "\(SRCROOT)/build/Debug/AppTarget.app/Contents/Frameworks", "--strip-bitcode", "--strip-bitcode-tool", "\(effectiveToolchain.path.str)/usr/bin/bitcode_strip", "--emit-dependency-info", "\(SRCROOT)/build/aProject.build/Debug/AppTarget.build/SwiftStdLibToolInputDependencies.dep"], shouldFilterSwiftLibs ? ["--filter-for-swift-os"] : [], shouldBackDeploySwiftConcurrency ? ["--back-deploy-swift-concurrency"] : [], shouldBackDeploySwiftSpan ? ["--back-deploy-swift-span"] : []] as [[String]]).reduce([], +)) task.checkInputs([ .path("\(SRCROOT)/build/Debug/AppTarget.app/Contents/MacOS/AppTarget"), diff --git a/Tests/SWBTaskConstructionTests/UnitTestTaskConstructionTests.swift b/Tests/SWBTaskConstructionTests/UnitTestTaskConstructionTests.swift index ce4515df..36c60d39 100644 --- a/Tests/SWBTaskConstructionTests/UnitTestTaskConstructionTests.swift +++ b/Tests/SWBTaskConstructionTests/UnitTestTaskConstructionTests.swift @@ -23,7 +23,7 @@ fileprivate struct UnitTestTaskConstructionTests: CoreBasedTests { // MARK: Framework test target /// Test task construction for a unit test target for macOS which is testing a framework. - @Test(.requireSDKs(.macOS)) + @Test(.requireSDKs(.macOS), .requireXcode26()) func frameworkUnitTestTarget_macOS() async throws { let swiftCompilerPath = try await self.swiftCompilerPath let swiftVersion = try await self.swiftVersion @@ -387,7 +387,7 @@ fileprivate struct UnitTestTaskConstructionTests: CoreBasedTests { /// Test task construction for an application target on macOS with two test targets which are testing it using TEST_HOST. Both debug and install builds are tested. /// /// Two targets are used to check that the test frameworks are only copied once, and the app target is only re-signed once. - @Test(.requireSDKs(.macOS)) + @Test(.requireSDKs(.macOS), .requireXcode26()) func applicationUnitTestTarget_macOS() async throws { let swiftCompilerPath = try await self.swiftCompilerPath let swiftVersion = try await self.swiftVersion @@ -2184,7 +2184,7 @@ fileprivate struct UnitTestTaskConstructionTests: CoreBasedTests { /// Test task construction for a UI test target for macOS. Both debug and install builds are tested. - @Test(.requireSDKs(.macOS)) + @Test(.requireSDKs(.macOS), .requireXcode26()) func uITestTarget_macOS() async throws { let swiftCompilerPath = try await self.swiftCompilerPath let swiftVersion = try await self.swiftVersion @@ -3159,7 +3159,7 @@ fileprivate struct UnitTestTaskConstructionTests: CoreBasedTests { // There should be a 'CopySwiftLibs' task that includes a reference to the libXCTestSwiftSupport.dylib executable. results.checkTask(.matchTarget(target), .matchRuleType("CopySwiftLibs")) { task in task.checkRuleInfo(["CopySwiftLibs", "\(SRCROOT)/build/Debug-iphoneos/UITestTarget-Runner.app/PlugIns/UITestTarget.xctest"]) - task.checkCommandLine(["builtin-swiftStdLibTool", "--copy", "--verbose", "--sign", "105DE4E702E4", "--scan-executable", "\(SRCROOT)/build/Debug-iphoneos/UITestTarget-Runner.app/PlugIns/UITestTarget.xctest/UITestTarget", "--scan-folder", "\(SRCROOT)/build/Debug-iphoneos/UITestTarget-Runner.app/PlugIns/UITestTarget.xctest/Frameworks", "--scan-folder", "\(SRCROOT)/build/Debug-iphoneos/UITestTarget-Runner.app/PlugIns/UITestTarget.xctest/PlugIns", "--scan-folder", "\(SRCROOT)/build/Debug-iphoneos/UITestTarget-Runner.app/PlugIns/UITestTarget.xctest/SystemExtensions", "--scan-folder", "\(SRCROOT)/build/Debug-iphoneos/UITestTarget-Runner.app/PlugIns/UITestTarget.xctest/Extensions", "--platform", "iphoneos", "--toolchain", "\(core.developerPath.path.str)/Toolchains/XcodeDefault.xctoolchain", "--destination", "\(SRCROOT)/build/Debug-iphoneos/UITestTarget-Runner.app/PlugIns/UITestTarget.xctest/Frameworks", "--strip-bitcode", "--scan-executable", "\(core.developerPath.path.str)/Platforms/iPhoneOS.platform/Developer/usr/lib/libXCTestSwiftSupport.dylib", "--strip-bitcode-tool", "\(defaultToolchain.path.str)/usr/bin/bitcode_strip", "--emit-dependency-info", "\(SRCROOT)/build/aProject.build/Debug-iphoneos/UITestTarget.build/SwiftStdLibToolInputDependencies.dep", "--back-deploy-swift-concurrency"]) + task.checkCommandLine(["builtin-swiftStdLibTool", "--copy", "--verbose", "--sign", "105DE4E702E4", "--scan-executable", "\(SRCROOT)/build/Debug-iphoneos/UITestTarget-Runner.app/PlugIns/UITestTarget.xctest/UITestTarget", "--scan-folder", "\(SRCROOT)/build/Debug-iphoneos/UITestTarget-Runner.app/PlugIns/UITestTarget.xctest/Frameworks", "--scan-folder", "\(SRCROOT)/build/Debug-iphoneos/UITestTarget-Runner.app/PlugIns/UITestTarget.xctest/PlugIns", "--scan-folder", "\(SRCROOT)/build/Debug-iphoneos/UITestTarget-Runner.app/PlugIns/UITestTarget.xctest/SystemExtensions", "--scan-folder", "\(SRCROOT)/build/Debug-iphoneos/UITestTarget-Runner.app/PlugIns/UITestTarget.xctest/Extensions", "--platform", "iphoneos", "--toolchain", "\(core.developerPath.path.str)/Toolchains/XcodeDefault.xctoolchain", "--destination", "\(SRCROOT)/build/Debug-iphoneos/UITestTarget-Runner.app/PlugIns/UITestTarget.xctest/Frameworks", "--strip-bitcode", "--scan-executable", "\(core.developerPath.path.str)/Platforms/iPhoneOS.platform/Developer/usr/lib/libXCTestSwiftSupport.dylib", "--strip-bitcode-tool", "\(defaultToolchain.path.str)/usr/bin/bitcode_strip", "--emit-dependency-info", "\(SRCROOT)/build/aProject.build/Debug-iphoneos/UITestTarget.build/SwiftStdLibToolInputDependencies.dep", "--back-deploy-swift-concurrency", "--back-deploy-swift-span"]) } }