Skip to content

Commit f78fc4e

Browse files
authored
Implicitly support Darwin SDKs on macOS (#6828)
This change makes it possible to run e.g. `swift build --triple arm64-apple-ios` on macOS to build for iOS. ### Motivation: With #6732 in place, it's possible to use `swift-build` for most Darwin targets on macOS with `swift build --triple arm64-apple-ios --sdk $(xcrun --sdk iphoneos --show-sdk-path)` etc. The `SwiftSDK` type already has support for computing a "default" SDK given a triple, so we can elide the `--sdk` argument by automatically inferring it on macOS. ### Modifications: - Generalize `hostSwiftSDK` as a `systemSwiftSDK` function that additionally accepts a Darwin platform as an argument. On macOS, this allows us to compute a `SwiftSDK` that works for arbitrary Darwin targets, instead of just macosx. - Modify `defaultSwiftSDK(for:)` to return a non-macOS Darwin `systemSwiftSDK` if requested. - Modify the `SwiftTool._targetToolchain` computation to handle default SDKs passed via `--swift-sdk`, not just `--triple`. (Open to discussing whether this change is a good idea.) ### Result: Darwin targets get free Swift SDK support on macOS 🎉 ### Open questions: Should default Swift SDKs be usable via `--swift-sdk`, and should they show up in `swift sdk list`? These two changes would ensure homogeneity; for example, if/when SourceKit-LSP supports SDK selection this would enable Darwin targets on macOS there too.
1 parent 5efd210 commit f78fc4e

File tree

4 files changed

+148
-32
lines changed

4 files changed

+148
-32
lines changed

Sources/Commands/Utilities/TestingSupport.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,10 +216,11 @@ enum TestingSupport {
216216
return env
217217
#else
218218
// Add the sdk platform path if we have it.
219-
if let sdkPlatformFrameworksPath = try? SwiftSDK.sdkPlatformFrameworkPaths() {
219+
// Since XCTestHelper targets macOS, we need the macOS platform paths here.
220+
if let sdkPlatformPaths = try? SwiftSDK.sdkPlatformPaths(for: .macOS) {
220221
// appending since we prefer the user setting (if set) to the one we inject
221-
env.appendPath(key: "DYLD_FRAMEWORK_PATH", value: sdkPlatformFrameworksPath.fwk.pathString)
222-
env.appendPath(key: "DYLD_LIBRARY_PATH", value: sdkPlatformFrameworksPath.lib.pathString)
222+
env.appendPath(key: "DYLD_FRAMEWORK_PATH", value: sdkPlatformPaths.frameworks.pathString)
223+
env.appendPath(key: "DYLD_LIBRARY_PATH", value: sdkPlatformPaths.libraries.pathString)
223224
}
224225

225226
// We aren't using XCTest's harness logic to run Swift Testing tests.

Sources/PackageModel/SwiftSDKs/SwiftSDK.swift

Lines changed: 106 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,25 @@ public struct SwiftSDK: Equatable {
524524
environment: Environment = .current,
525525
observabilityScope: ObservabilityScope? = nil,
526526
fileSystem: any FileSystem = localFileSystem
527+
) throws -> SwiftSDK {
528+
try self.systemSwiftSDK(
529+
binDir,
530+
environment: environment,
531+
observabilityScope: observabilityScope,
532+
fileSystem: fileSystem
533+
)
534+
}
535+
536+
/// A default Swift SDK on the host.
537+
///
538+
/// Equivalent to `hostSwiftSDK`, except on macOS, where passing a non-nil `darwinPlatformOverride`
539+
/// will result in the SDK for the corresponding Darwin platform.
540+
private static func systemSwiftSDK(
541+
_ binDir: AbsolutePath? = nil,
542+
environment: Environment = .current,
543+
observabilityScope: ObservabilityScope? = nil,
544+
fileSystem: any FileSystem = localFileSystem,
545+
darwinPlatformOverride: DarwinPlatform? = nil
527546
) throws -> SwiftSDK {
528547
// Select the correct binDir.
529548
if environment["SWIFTPM_CUSTOM_BINDIR"] != nil {
@@ -535,13 +554,16 @@ public struct SwiftSDK: Equatable {
535554

536555
let sdkPath: AbsolutePath?
537556
#if os(macOS)
557+
let darwinPlatform = darwinPlatformOverride ?? .macOS
538558
// Get the SDK.
539559
if let value = environment["SDKROOT"] {
540560
sdkPath = try AbsolutePath(validating: value)
561+
} else if let value = environment[EnvironmentKey("SWIFTPM_SDKROOT_\(darwinPlatform.xcrunName)")] {
562+
sdkPath = try AbsolutePath(validating: value)
541563
} else {
542564
// No value in env, so search for it.
543565
let sdkPathStr = try AsyncProcess.checkNonZeroExit(
544-
arguments: ["/usr/bin/xcrun", "--sdk", "macosx", "--show-sdk-path"],
566+
arguments: ["/usr/bin/xcrun", "--sdk", darwinPlatform.xcrunName, "--show-sdk-path"],
545567
environment: environment
546568
).spm_chomp()
547569
guard !sdkPathStr.isEmpty else {
@@ -559,11 +581,11 @@ public struct SwiftSDK: Equatable {
559581
var extraSwiftCFlags: [String] = []
560582
#if os(macOS)
561583
do {
562-
let sdkPaths = try SwiftSDK.sdkPlatformFrameworkPaths(environment: environment)
563-
extraCCFlags += ["-F", sdkPaths.fwk.pathString]
564-
extraSwiftCFlags += ["-F", sdkPaths.fwk.pathString]
565-
extraSwiftCFlags += ["-I", sdkPaths.lib.pathString]
566-
extraSwiftCFlags += ["-L", sdkPaths.lib.pathString]
584+
let sdkPaths = try SwiftSDK.sdkPlatformPaths(for: darwinPlatform, environment: environment)
585+
extraCCFlags += ["-F", sdkPaths.frameworks.pathString]
586+
extraSwiftCFlags += ["-F", sdkPaths.frameworks.pathString]
587+
extraSwiftCFlags += ["-I", sdkPaths.libraries.pathString]
588+
extraSwiftCFlags += ["-L", sdkPaths.libraries.pathString]
567589
xctestSupport = .supported
568590
} catch {
569591
xctestSupport = .unsupported(reason: String(describing: error))
@@ -589,15 +611,40 @@ public struct SwiftSDK: Equatable {
589611
)
590612
}
591613

614+
/// Auxiliary platform frameworks and libraries.
615+
///
616+
/// The referenced directories may contain, for example, test support utilities.
617+
///
618+
/// - SeeAlso: ``sdkPlatformPaths(for:environment:)``
619+
public struct PlatformPaths {
620+
/// Path to the directory containing auxiliary platform frameworks.
621+
public var frameworks: AbsolutePath
622+
623+
/// Path to the directory containing auxiliary platform libraries.
624+
public var libraries: AbsolutePath
625+
}
626+
592627
/// Returns `macosx` sdk platform framework path.
628+
@available(*, deprecated, message: "use sdkPlatformPaths(for:) instead")
593629
public static func sdkPlatformFrameworkPaths(
594630
environment: Environment = .current
595631
) throws -> (fwk: AbsolutePath, lib: AbsolutePath) {
596-
if let path = _sdkPlatformFrameworkPath {
632+
let paths = try sdkPlatformPaths(for: .macOS, environment: environment)
633+
return (fwk: paths.frameworks, lib: paths.libraries)
634+
}
635+
636+
/// Returns ``SwiftSDK/PlatformPaths`` for the provided Darwin platform.
637+
public static func sdkPlatformPaths(
638+
for darwinPlatform: DarwinPlatform,
639+
environment: Environment = .current
640+
) throws -> PlatformPaths {
641+
if let path = _sdkPlatformFrameworkPath[darwinPlatform] {
597642
return path
598643
}
599-
let platformPath = try AsyncProcess.checkNonZeroExit(
600-
arguments: ["/usr/bin/xcrun", "--sdk", "macosx", "--show-sdk-platform-path"],
644+
let platformPath = try environment[
645+
EnvironmentKey("SWIFTPM_PLATFORM_PATH_\(darwinPlatform.xcrunName)")
646+
] ?? AsyncProcess.checkNonZeroExit(
647+
arguments: ["/usr/bin/xcrun", "--sdk", darwinPlatform.xcrunName, "--show-sdk-platform-path"],
601648
environment: environment
602649
).spm_chomp()
603650

@@ -615,33 +662,26 @@ public struct SwiftSDK: Equatable {
615662
components: "Developer", "usr", "lib"
616663
)
617664

618-
let sdkPlatformFrameworkPath = (fwk, lib)
619-
_sdkPlatformFrameworkPath = sdkPlatformFrameworkPath
665+
let sdkPlatformFrameworkPath = PlatformPaths(frameworks: fwk, libraries: lib)
666+
_sdkPlatformFrameworkPath[darwinPlatform] = sdkPlatformFrameworkPath
620667
return sdkPlatformFrameworkPath
621668
}
622669

623-
// FIXME: convert this from a tuple to a proper struct with documented properties
624-
/// Cache storage for sdk platform path.
625-
private static var _sdkPlatformFrameworkPath: (fwk: AbsolutePath, lib: AbsolutePath)? = nil
670+
/// Cache storage for sdk platform paths.
671+
private static var _sdkPlatformFrameworkPath: [DarwinPlatform: PlatformPaths] = [:]
626672

627673
/// Returns a default Swift SDK for a given target environment
628674
@available(*, deprecated, renamed: "defaultSwiftSDK")
629675
public static func defaultDestination(for triple: Triple, host: SwiftSDK) -> SwiftSDK? {
630-
if triple.isWASI() {
631-
let wasiSysroot = host.toolset.rootPaths.first?
632-
.parentDirectory // usr
633-
.appending(components: "share", "wasi-sysroot")
634-
return SwiftSDK(
635-
targetTriple: triple,
636-
toolset: host.toolset,
637-
pathsConfiguration: .init(sdkRootPath: wasiSysroot)
638-
)
639-
}
640-
return nil
676+
defaultSwiftSDK(for: triple, hostSDK: host)
641677
}
642678

643679
/// Returns a default Swift SDK of a given target environment.
644-
public static func defaultSwiftSDK(for targetTriple: Triple, hostSDK: SwiftSDK) -> SwiftSDK? {
680+
public static func defaultSwiftSDK(
681+
for targetTriple: Triple,
682+
hostSDK: SwiftSDK,
683+
environment: Environment = .current
684+
) -> SwiftSDK? {
645685
if targetTriple.isWASI() {
646686
let wasiSysroot = hostSDK.toolset.rootPaths.first?
647687
.parentDirectory // usr
@@ -652,6 +692,20 @@ public struct SwiftSDK: Equatable {
652692
pathsConfiguration: .init(sdkRootPath: wasiSysroot)
653693
)
654694
}
695+
696+
#if os(macOS)
697+
if let darwinPlatform = targetTriple.darwinPlatform {
698+
// the Darwin SDKs are trivially available on macOS
699+
var sdk = try? self.systemSwiftSDK(
700+
hostSDK.toolset.rootPaths.first,
701+
environment: environment,
702+
darwinPlatformOverride: darwinPlatform
703+
)
704+
sdk?.targetTriple = targetTriple
705+
return sdk
706+
}
707+
#endif
708+
655709
return nil
656710
}
657711

@@ -690,12 +744,23 @@ public struct SwiftSDK: Equatable {
690744
} else {
691745
throw SwiftSDKError.noSwiftSDKDecoded(customDestination)
692746
}
693-
} else if let triple = customCompileTriple,
694-
let targetSwiftSDK = SwiftSDK.defaultSwiftSDK(for: triple, hostSDK: hostSwiftSDK)
747+
} else if let targetTriple = customCompileTriple,
748+
let targetSwiftSDK = SwiftSDK.defaultSwiftSDK(for: targetTriple, hostSDK: hostSwiftSDK)
695749
{
696750
swiftSDK = targetSwiftSDK
697751
} else if let swiftSDKSelector {
698-
swiftSDK = try store.selectBundle(matching: swiftSDKSelector, hostTriple: hostTriple)
752+
do {
753+
swiftSDK = try store.selectBundle(matching: swiftSDKSelector, hostTriple: hostTriple)
754+
} catch {
755+
// If a user-installed bundle for the selector doesn't exist, check if the
756+
// selector is recognized as a default SDK.
757+
if let targetTriple = try? Triple(swiftSDKSelector),
758+
let defaultSDK = SwiftSDK.defaultSwiftSDK(for: targetTriple, hostSDK: hostSwiftSDK) {
759+
swiftSDK = defaultSDK
760+
} else {
761+
throw error
762+
}
763+
}
699764
} else {
700765
// Otherwise use the host toolchain.
701766
swiftSDK = hostSwiftSDK
@@ -991,6 +1056,18 @@ extension SwiftSDK {
9911056
}
9921057
}
9931058

1059+
extension DarwinPlatform {
1060+
/// The name xcrun uses to identify this platform.
1061+
fileprivate var xcrunName: String {
1062+
switch self {
1063+
case .iOS(.catalyst):
1064+
return "macosx"
1065+
default:
1066+
return platformName
1067+
}
1068+
}
1069+
}
1070+
9941071
/// Integer version of the schema of `destination.json` files used for cross-compilation.
9951072
private struct VersionInfo: Codable {
9961073
let version: Int

Tests/PackageModelTests/SwiftSDKBundleTests.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,19 @@ final class SwiftSDKBundleTests: XCTestCase {
450450
XCTAssertEqual(targetSwiftSDK.toolset.rootPaths, [toolsetRootPath] + hostSwiftSDK.toolset.rootPaths)
451451
}
452452

453+
do {
454+
let targetSwiftSDK = try SwiftSDK.deriveTargetSwiftSDK(
455+
hostSwiftSDK: hostSwiftSDK,
456+
hostTriple: hostTriple,
457+
swiftSDKSelector: "wasm32-unknown-wasi",
458+
store: store,
459+
observabilityScope: system.topScope,
460+
fileSystem: fileSystem
461+
)
462+
// Ensure that triples that have a `defaultSwiftSDK` are handled
463+
XCTAssertEqual(targetSwiftSDK.targetTriple?.triple, "wasm32-unknown-wasi")
464+
}
465+
453466
do {
454467
// Check explicit overriding options.
455468
let customCompileSDK = AbsolutePath("/path/to/sdk")

Tests/PackageModelTests/SwiftSDKTests.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,4 +616,29 @@ final class DestinationTests: XCTestCase {
616616
parsedDestinationV2GNU
617617
)
618618
}
619+
620+
func testDefaultSDKs() throws {
621+
let hostSDK = try SwiftSDK.hostSwiftSDK("/prefix/bin")
622+
623+
#if os(macOS)
624+
let iOSPlatform = try AbsolutePath(validating: "/usr/share/iPhoneOS.platform")
625+
let iOSRoot = try AbsolutePath(validating: "/usr/share/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk")
626+
let iOSTriple = try Triple("arm64-apple-ios")
627+
let iOS = try XCTUnwrap(SwiftSDK.defaultSwiftSDK(
628+
for: iOSTriple,
629+
hostSDK: hostSDK,
630+
environment: [
631+
"SWIFTPM_PLATFORM_PATH_iphoneos": iOSPlatform.pathString,
632+
"SWIFTPM_SDKROOT_iphoneos": iOSRoot.pathString,
633+
]
634+
))
635+
XCTAssertEqual(iOS.toolset.rootPaths, hostSDK.toolset.rootPaths)
636+
637+
XCTAssertEqual(iOS.pathsConfiguration.sdkRootPath, iOSRoot)
638+
639+
let cFlags = iOS.toolset.knownTools[.cCompiler]?.extraCLIOptions ?? []
640+
XCTAssert(cFlags.contains(["-F", "\(iOSPlatform.pathString)/Developer/Library/Frameworks"]))
641+
XCTAssertFalse(cFlags.contains { $0.lowercased().contains("macos") }, "Found macOS path in \(cFlags)")
642+
#endif
643+
}
619644
}

0 commit comments

Comments
 (0)