diff --git a/Sources/Commands/Utilities/TestingSupport.swift b/Sources/Commands/Utilities/TestingSupport.swift index a99ec3985b7..63b55b616db 100644 --- a/Sources/Commands/Utilities/TestingSupport.swift +++ b/Sources/Commands/Utilities/TestingSupport.swift @@ -186,10 +186,11 @@ enum TestingSupport { return env #else // Add the sdk platform path if we have it. - if let sdkPlatformFrameworksPath = try? SwiftSDK.sdkPlatformFrameworkPaths() { + // Since XCTestHelper targets macOS, we need the macOS platform paths here. + if let sdkPlatformPaths = try? SwiftSDK.sdkPlatformPaths(for: .macOS) { // appending since we prefer the user setting (if set) to the one we inject - env.appendPath(key: "DYLD_FRAMEWORK_PATH", value: sdkPlatformFrameworksPath.fwk.pathString) - env.appendPath(key: "DYLD_LIBRARY_PATH", value: sdkPlatformFrameworksPath.lib.pathString) + env.appendPath(key: "DYLD_FRAMEWORK_PATH", value: sdkPlatformPaths.frameworks.pathString) + env.appendPath(key: "DYLD_LIBRARY_PATH", value: sdkPlatformPaths.libraries.pathString) } // We aren't using XCTest's harness logic to run Swift Testing tests. diff --git a/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift b/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift index 8ae4b7bcbbe..c9cc039fb6c 100644 --- a/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift +++ b/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift @@ -524,6 +524,25 @@ public struct SwiftSDK: Equatable { environment: Environment = .current, observabilityScope: ObservabilityScope? = nil, fileSystem: any FileSystem = localFileSystem + ) throws -> SwiftSDK { + try self.systemSwiftSDK( + binDir, + environment: environment, + observabilityScope: observabilityScope, + fileSystem: fileSystem + ) + } + + /// A default Swift SDK on the host. + /// + /// Equivalent to `hostSwiftSDK`, except on macOS, where passing a non-nil `darwinPlatformOverride` + /// will result in the SDK for the corresponding Darwin platform. + private static func systemSwiftSDK( + _ binDir: AbsolutePath? = nil, + environment: Environment = .current, + observabilityScope: ObservabilityScope? = nil, + fileSystem: any FileSystem = localFileSystem, + darwinPlatformOverride: DarwinPlatform? = nil ) throws -> SwiftSDK { // Select the correct binDir. if environment["SWIFTPM_CUSTOM_BINDIR"] != nil { @@ -535,13 +554,16 @@ public struct SwiftSDK: Equatable { let sdkPath: AbsolutePath? #if os(macOS) + let darwinPlatform = darwinPlatformOverride ?? .macOS // Get the SDK. if let value = environment["SDKROOT"] { sdkPath = try AbsolutePath(validating: value) + } else if let value = environment[EnvironmentKey("SWIFTPM_SDKROOT_\(darwinPlatform.xcrunName)")] { + sdkPath = try AbsolutePath(validating: value) } else { // No value in env, so search for it. let sdkPathStr = try AsyncProcess.checkNonZeroExit( - arguments: ["/usr/bin/xcrun", "--sdk", "macosx", "--show-sdk-path"], + arguments: ["/usr/bin/xcrun", "--sdk", darwinPlatform.xcrunName, "--show-sdk-path"], environment: environment ).spm_chomp() guard !sdkPathStr.isEmpty else { @@ -559,11 +581,11 @@ public struct SwiftSDK: Equatable { var extraSwiftCFlags: [String] = [] #if os(macOS) do { - let sdkPaths = try SwiftSDK.sdkPlatformFrameworkPaths(environment: environment) - extraCCFlags += ["-F", sdkPaths.fwk.pathString] - extraSwiftCFlags += ["-F", sdkPaths.fwk.pathString] - extraSwiftCFlags += ["-I", sdkPaths.lib.pathString] - extraSwiftCFlags += ["-L", sdkPaths.lib.pathString] + let sdkPaths = try SwiftSDK.sdkPlatformPaths(for: darwinPlatform, environment: environment) + extraCCFlags += ["-F", sdkPaths.frameworks.pathString] + extraSwiftCFlags += ["-F", sdkPaths.frameworks.pathString] + extraSwiftCFlags += ["-I", sdkPaths.libraries.pathString] + extraSwiftCFlags += ["-L", sdkPaths.libraries.pathString] xctestSupport = .supported } catch { xctestSupport = .unsupported(reason: String(describing: error)) @@ -589,15 +611,40 @@ public struct SwiftSDK: Equatable { ) } + /// Auxiliary platform frameworks and libraries. + /// + /// The referenced directories may contain, for example, test support utilities. + /// + /// - SeeAlso: ``sdkPlatformPaths(for:environment:)`` + public struct PlatformPaths { + /// Path to the directory containing auxiliary platform frameworks. + public var frameworks: AbsolutePath + + /// Path to the directory containing auxiliary platform libraries. + public var libraries: AbsolutePath + } + /// Returns `macosx` sdk platform framework path. + @available(*, deprecated, message: "use sdkPlatformPaths(for:) instead") public static func sdkPlatformFrameworkPaths( environment: Environment = .current ) throws -> (fwk: AbsolutePath, lib: AbsolutePath) { - if let path = _sdkPlatformFrameworkPath { + let paths = try sdkPlatformPaths(for: .macOS, environment: environment) + return (fwk: paths.frameworks, lib: paths.libraries) + } + + /// Returns ``SwiftSDK/PlatformPaths`` for the provided Darwin platform. + public static func sdkPlatformPaths( + for darwinPlatform: DarwinPlatform, + environment: Environment = .current + ) throws -> PlatformPaths { + if let path = _sdkPlatformFrameworkPath[darwinPlatform] { return path } - let platformPath = try AsyncProcess.checkNonZeroExit( - arguments: ["/usr/bin/xcrun", "--sdk", "macosx", "--show-sdk-platform-path"], + let platformPath = try environment[ + EnvironmentKey("SWIFTPM_PLATFORM_PATH_\(darwinPlatform.xcrunName)") + ] ?? AsyncProcess.checkNonZeroExit( + arguments: ["/usr/bin/xcrun", "--sdk", darwinPlatform.xcrunName, "--show-sdk-platform-path"], environment: environment ).spm_chomp() @@ -615,33 +662,26 @@ public struct SwiftSDK: Equatable { components: "Developer", "usr", "lib" ) - let sdkPlatformFrameworkPath = (fwk, lib) - _sdkPlatformFrameworkPath = sdkPlatformFrameworkPath + let sdkPlatformFrameworkPath = PlatformPaths(frameworks: fwk, libraries: lib) + _sdkPlatformFrameworkPath[darwinPlatform] = sdkPlatformFrameworkPath return sdkPlatformFrameworkPath } - // FIXME: convert this from a tuple to a proper struct with documented properties - /// Cache storage for sdk platform path. - private static var _sdkPlatformFrameworkPath: (fwk: AbsolutePath, lib: AbsolutePath)? = nil + /// Cache storage for sdk platform paths. + private static var _sdkPlatformFrameworkPath: [DarwinPlatform: PlatformPaths] = [:] /// Returns a default Swift SDK for a given target environment @available(*, deprecated, renamed: "defaultSwiftSDK") public static func defaultDestination(for triple: Triple, host: SwiftSDK) -> SwiftSDK? { - if triple.isWASI() { - let wasiSysroot = host.toolset.rootPaths.first? - .parentDirectory // usr - .appending(components: "share", "wasi-sysroot") - return SwiftSDK( - targetTriple: triple, - toolset: host.toolset, - pathsConfiguration: .init(sdkRootPath: wasiSysroot) - ) - } - return nil + defaultSwiftSDK(for: triple, hostSDK: host) } /// Returns a default Swift SDK of a given target environment. - public static func defaultSwiftSDK(for targetTriple: Triple, hostSDK: SwiftSDK) -> SwiftSDK? { + public static func defaultSwiftSDK( + for targetTriple: Triple, + hostSDK: SwiftSDK, + environment: Environment = .current + ) -> SwiftSDK? { if targetTriple.isWASI() { let wasiSysroot = hostSDK.toolset.rootPaths.first? .parentDirectory // usr @@ -652,6 +692,20 @@ public struct SwiftSDK: Equatable { pathsConfiguration: .init(sdkRootPath: wasiSysroot) ) } + + #if os(macOS) + if let darwinPlatform = targetTriple.darwinPlatform { + // the Darwin SDKs are trivially available on macOS + var sdk = try? self.systemSwiftSDK( + hostSDK.toolset.rootPaths.first, + environment: environment, + darwinPlatformOverride: darwinPlatform + ) + sdk?.targetTriple = targetTriple + return sdk + } + #endif + return nil } @@ -688,12 +742,23 @@ public struct SwiftSDK: Equatable { } else { throw SwiftSDKError.noSwiftSDKDecoded(customDestination) } - } else if let triple = customCompileTriple, - let targetSwiftSDK = SwiftSDK.defaultSwiftSDK(for: triple, hostSDK: hostSwiftSDK) + } else if let targetTriple = customCompileTriple, + let targetSwiftSDK = SwiftSDK.defaultSwiftSDK(for: targetTriple, hostSDK: hostSwiftSDK) { swiftSDK = targetSwiftSDK } else if let swiftSDKSelector { - swiftSDK = try store.selectBundle(matching: swiftSDKSelector, hostTriple: hostTriple) + do { + swiftSDK = try store.selectBundle(matching: swiftSDKSelector, hostTriple: hostTriple) + } catch { + // If a user-installed bundle for the selector doesn't exist, check if the + // selector is recognized as a default SDK. + if let targetTriple = try? Triple(swiftSDKSelector), + let defaultSDK = SwiftSDK.defaultSwiftSDK(for: targetTriple, hostSDK: hostSwiftSDK) { + swiftSDK = defaultSDK + } else { + throw error + } + } } else { // Otherwise use the host toolchain. swiftSDK = hostSwiftSDK @@ -970,6 +1035,18 @@ extension SwiftSDK { } } +extension DarwinPlatform { + /// The name xcrun uses to identify this platform. + fileprivate var xcrunName: String { + switch self { + case .iOS(.catalyst): + return "macosx" + default: + return platformName + } + } +} + /// Integer version of the schema of `destination.json` files used for cross-compilation. private struct VersionInfo: Codable { let version: Int diff --git a/Tests/PackageModelTests/SwiftSDKBundleTests.swift b/Tests/PackageModelTests/SwiftSDKBundleTests.swift index ab9da497dd2..9fbd8d0c1fa 100644 --- a/Tests/PackageModelTests/SwiftSDKBundleTests.swift +++ b/Tests/PackageModelTests/SwiftSDKBundleTests.swift @@ -432,6 +432,19 @@ final class SwiftSDKBundleTests: XCTestCase { XCTAssertEqual(targetSwiftSDK.toolset.rootPaths, [toolsetRootPath] + hostSwiftSDK.toolset.rootPaths) } + do { + let targetSwiftSDK = try SwiftSDK.deriveTargetSwiftSDK( + hostSwiftSDK: hostSwiftSDK, + hostTriple: hostTriple, + swiftSDKSelector: "wasm32-unknown-wasi", + store: store, + observabilityScope: system.topScope, + fileSystem: fileSystem + ) + // Ensure that triples that have a `defaultSwiftSDK` are handled + XCTAssertEqual(targetSwiftSDK.targetTriple?.triple, "wasm32-unknown-wasi") + } + do { // Check explicit overriding options. let customCompileSDK = AbsolutePath("/path/to/sdk") diff --git a/Tests/PackageModelTests/SwiftSDKTests.swift b/Tests/PackageModelTests/SwiftSDKTests.swift index 5cd15f0bb4d..2d11360b9ea 100644 --- a/Tests/PackageModelTests/SwiftSDKTests.swift +++ b/Tests/PackageModelTests/SwiftSDKTests.swift @@ -616,4 +616,29 @@ final class DestinationTests: XCTestCase { parsedDestinationV2GNU ) } + + func testDefaultSDKs() throws { + let hostSDK = try SwiftSDK.hostSwiftSDK("/prefix/bin") + + #if os(macOS) + let iOSPlatform = try AbsolutePath(validating: "/usr/share/iPhoneOS.platform") + let iOSRoot = try AbsolutePath(validating: "/usr/share/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk") + let iOSTriple = try Triple("arm64-apple-ios") + let iOS = try XCTUnwrap(SwiftSDK.defaultSwiftSDK( + for: iOSTriple, + hostSDK: hostSDK, + environment: [ + "SWIFTPM_PLATFORM_PATH_iphoneos": iOSPlatform.pathString, + "SWIFTPM_SDKROOT_iphoneos": iOSRoot.pathString, + ] + )) + XCTAssertEqual(iOS.toolset.rootPaths, hostSDK.toolset.rootPaths) + + XCTAssertEqual(iOS.pathsConfiguration.sdkRootPath, iOSRoot) + + let cFlags = iOS.toolset.knownTools[.cCompiler]?.extraCLIOptions ?? [] + XCTAssert(cFlags.contains(["-F", "\(iOSPlatform.pathString)/Developer/Library/Frameworks"])) + XCTAssertFalse(cFlags.contains { $0.lowercased().contains("macos") }, "Found macOS path in \(cFlags)") + #endif + } }