Skip to content

[5.5] Add Mac Catalyst as a supported platform #3444

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,8 @@ extension PackageModel.Platform {
switch name.lowercased() {
case let name where name.contains("macos"):
self = PackageModel.Platform.macOS
case let name where name.contains("maccatalyst"):
self = PackageModel.Platform.macCatalyst
case let name where name.contains("ios"):
self = PackageModel.Platform.iOS
case let name where name.contains("tvos"):
Expand Down
51 changes: 51 additions & 0 deletions Sources/PackageDescription/SupportedPlatforms.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ public struct Platform: Encodable, Equatable {
/// The macOS platform.
public static let macOS: Platform = Platform(name: "macos")

/// The Mac Catalyst platform.
public static let macCatalyst: Platform = Platform(name: "maccatalyst")

/// The iOS platform.
public static let iOS: Platform = Platform(name: "ios")

Expand Down Expand Up @@ -102,6 +105,29 @@ public struct SupportedPlatform: Encodable, Equatable {
return SupportedPlatform(platform: .macOS, version: SupportedPlatform.MacOSVersion(string: versionString).version)
}

/// Configures the minimum deployment target version for the Mac Catalyst platform.
///
/// - Since: First available in PackageDescription 5.5
///
/// - Parameter version: The minimum deployment target that the package supports.
@available(_PackageDescription, introduced: 5.5)
public static func macCatalyst(_ version: SupportedPlatform.MacCatalystVersion) -> SupportedPlatform {
return SupportedPlatform(platform: .macCatalyst, version: version.version)
}

/// Configures the minimum deployment target version for the Mac Catalyst platform
/// using a version string.
///
/// The version string must be a series of two or three dot-separated integers, such as `13.0` or `13.0.1`.
///
/// - Since: First available in PackageDescription 5.5
///
/// - Parameter versionString: The minimum deployment target as a string representation of two or three dot-separated integers, such as `13.0.1`.
@available(_PackageDescription, introduced: 5.5)
public static func macCatalyst(_ versionString: String) -> SupportedPlatform {
return SupportedPlatform(platform: .macCatalyst, version: SupportedPlatform.MacCatalystVersion(string: versionString).version)
}

/// Configures the minimum deployment target version for the iOS platform.
///
/// - Since: First available in PackageDescription 5.0
Expand Down Expand Up @@ -290,6 +316,31 @@ extension SupportedPlatform {
public static let v14: TVOSVersion = .init(string: "14.0")
}

/// The supported Mac Catalyst version.
public struct MacCatalystVersion: Encodable, AppleOSVersion {
fileprivate static let name = "macCatalyst"
fileprivate static let minimumMajorVersion = 13

/// The underlying version representation.
let version: String

fileprivate init(uncheckedVersion version: String) {
self.version = version
}

/// The value that represents Mac Catalyst 13.0.
///
/// - Since: First available in PackageDescription 5.5
@available(_PackageDescription, introduced: 5.5)
public static let v13: MacCatalystVersion = .init(string: "13.0")

/// The value that represents Mac Catalyst 14.0.
///
/// - Since: First available in PackageDescription 5.5
@available(_PackageDescription, introduced: 5.5)
public static let v14: MacCatalystVersion = .init(string: "14.0")
}

/// The supported iOS version.
public struct IOSVersion: Encodable, AppleOSVersion {
fileprivate static let name = "iOS"
Expand Down
2 changes: 1 addition & 1 deletion Sources/PackageLoading/ManifestLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -772,7 +772,7 @@ public final class ManifestLoader: ManifestLoaderProtocol {
}

let version = try Self._packageDescriptionMinimumDeploymentTarget.memoize {
(try MinimumDeploymentTarget.computeMinimumDeploymentTarget(of: macOSPackageDescriptionPath))?.versionString ?? "10.15"
(try MinimumDeploymentTarget.computeMinimumDeploymentTarget(of: macOSPackageDescriptionPath, platform: .macOS))?.versionString ?? "10.15"
}
cmd += ["-target", "\(triple.tripleString(forPlatformVersion: version))"]
#endif
Expand Down
36 changes: 24 additions & 12 deletions Sources/PackageLoading/MinimumDeploymentTarget.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,31 @@ public struct MinimumDeploymentTarget {
}
}

static func computeMinimumDeploymentTarget(of binaryPath: AbsolutePath) throws -> PlatformVersion? {
static func computeMinimumDeploymentTarget(of binaryPath: AbsolutePath, platform: PackageModel.Platform) throws -> PlatformVersion? {
guard let (_, platformName) = platform.sdkNameAndPlatform else {
return nil
}

let runResult = try Process.popen(arguments: ["/usr/bin/xcrun", "vtool", "-show-build", binaryPath.pathString])
guard let versionString = try runResult.utf8Output().components(separatedBy: "\n").first(where: { $0.contains("minos") })?.components(separatedBy: " ").last else { return nil }
return PlatformVersion(versionString)
var lines = try runResult.utf8Output().components(separatedBy: "\n")
while !lines.isEmpty {
let first = lines.removeFirst()
if first.contains("platform \(platformName)"), let line = lines.first, line.contains("minos") {
return line.components(separatedBy: " ").last.map(PlatformVersion.init(stringLiteral:))
}
}
return nil
}

static func computeXCTestMinimumDeploymentTarget(with runResult: ProcessResult) throws -> PlatformVersion? {
static func computeXCTestMinimumDeploymentTarget(with runResult: ProcessResult, platform: PackageModel.Platform) throws -> PlatformVersion? {
guard let output = try runResult.utf8Output().spm_chuzzle() else { return nil }
let sdkPath = try AbsolutePath(validating: output)
let xcTestPath = sdkPath.appending(RelativePath("Developer/Library/Frameworks/XCTest.framework/XCTest"))
return try computeMinimumDeploymentTarget(of: xcTestPath)
return try computeMinimumDeploymentTarget(of: xcTestPath, platform: platform)
}

static func computeXCTestMinimumDeploymentTarget(for platform: PackageModel.Platform) -> PlatformVersion {
guard let sdkName = platform.sdkName else {
guard let (sdkName, _) = platform.sdkNameAndPlatform else {
return platform.oldestSupportedVersion
}

Expand All @@ -47,7 +57,7 @@ public struct MinimumDeploymentTarget {
do {
let runResult = try Process.popen(arguments: ["/usr/bin/xcrun", "--sdk", sdkName, "--show-sdk-platform-path"])

if let version = try computeXCTestMinimumDeploymentTarget(with: runResult) {
if let version = try computeXCTestMinimumDeploymentTarget(with: runResult, platform: platform) {
return version
}
} catch { } // we do not treat this a fatal and instead use the fallback minimum deployment target
Expand All @@ -58,16 +68,18 @@ public struct MinimumDeploymentTarget {
}

private extension PackageModel.Platform {
var sdkName: String? {
var sdkNameAndPlatform: (String, String)? {
switch self {
case .macOS:
return "macosx"
return ("macosx", "MACOS")
case .macCatalyst:
return ("macosx", "MACCATALYST")
case .iOS:
return "iphoneos"
return ("iphoneos", "IOS")
case .tvOS:
return "appletvos"
return ("appletvos", "TVOS")
case .watchOS:
return "watchos"
return ("watchos", "WATCHOS")
case .driverKit:
return nil // DriverKit does not support XCTest.
default:
Expand Down
2 changes: 1 addition & 1 deletion Sources/PackageLoading/PlatformRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ public final class PlatformRegistry {

/// The static list of known platforms.
private static var _knownPlatforms: [Platform] {
return [.macOS, .iOS, .tvOS, .watchOS, .linux, .windows, .android, .wasi, .driverKit]
return [.macOS, .macCatalyst, .iOS, .tvOS, .watchOS, .linux, .windows, .android, .wasi, .driverKit]
}
}
3 changes: 3 additions & 0 deletions Sources/PackageModel/ManifestSourceGeneration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ fileprivate extension SourceCodeFragment {
switch platform.platformName {
case "macos":
self.init(enum: "macOS", string: platform.version)
case "maccatalyst":
self.init(enum: "macCatalyst", string: platform.version)
case "ios":
self.init(enum: "iOS", string: platform.version)
case "tvos":
Expand Down Expand Up @@ -298,6 +300,7 @@ fileprivate extension SourceCodeFragment {
let platformNodes: [SourceCodeFragment] = condition.platformNames.map { platformName in
switch platformName {
case "macos": return SourceCodeFragment(enum: "macOS")
case "maccatalyst": return SourceCodeFragment(enum: "macCatalyst")
case "ios": return SourceCodeFragment(enum: "iOS")
case "tvos": return SourceCodeFragment(enum: "tvOS")
case "watchos": return SourceCodeFragment(enum: "watchOS")
Expand Down
1 change: 1 addition & 0 deletions Sources/PackageModel/Platform.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public struct Platform: Equatable, Hashable, Codable {
}

public static let macOS: Platform = Platform(name: "macos", oldestSupportedVersion: "10.10")
public static let macCatalyst: Platform = Platform(name: "maccatalyst", oldestSupportedVersion: "13.0")
public static let iOS: Platform = Platform(name: "ios", oldestSupportedVersion: "9.0")
public static let tvOS: Platform = Platform(name: "tvos", oldestSupportedVersion: "9.0")
public static let watchOS: Platform = Platform(name: "watchos", oldestSupportedVersion: "2.0")
Expand Down
7 changes: 6 additions & 1 deletion Sources/SPMTestSupport/PIFTester.swift
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,12 @@ public final class PIFBuildSettingsTester {
Array(buildSettings.multipleValueSettings.keys.map { $0.rawValue })
XCTAssert(uncheckedKeys.isEmpty, "settings are left unchecked: \(uncheckedKeys)", file: file, line: line)

for (platform, settings) in buildSettings.platformSpecificSettings {
for (platform, settings) in buildSettings.platformSpecificSingleValueSettings {
let uncheckedKeys = Array(settings.keys.map { $0.rawValue })
XCTAssert(uncheckedKeys.isEmpty, "\(platform) settings are left unchecked: \(uncheckedKeys)", file: file, line: line)
}

for (platform, settings) in buildSettings.platformSpecificMultipleValueSettings {
let uncheckedKeys = Array(settings.keys.map { $0.rawValue })
XCTAssert(uncheckedKeys.isEmpty, "\(platform) settings are left unchecked: \(uncheckedKeys)", file: file, line: line)
}
Expand Down
6 changes: 5 additions & 1 deletion Sources/Workspace/InitPackage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,8 @@ extension PackageModel.Platform {
switch self {
case .macOS:
return "macOS"
case .macCatalyst:
return "macCatalyst"
case .iOS:
return "iOS"
case .tvOS:
Expand All @@ -487,7 +489,7 @@ extension SupportedPlatform {
guard self.version.patch == 0 else {
return false
}
} else if [Platform.macOS, .iOS, .watchOS, .tvOS, .driverKit].contains(platform) {
} else if [Platform.macOS, .macCatalyst, .iOS, .watchOS, .tvOS, .driverKit].contains(platform) {
guard self.version.minor == 0, self.version.patch == 0 else {
return false
}
Expand All @@ -500,6 +502,8 @@ extension SupportedPlatform {
return (10...15).contains(version.minor)
case .macOS:
return (11...11).contains(version.major)
case .macCatalyst:
return (13...14).contains(version.major)
case .iOS:
return (8...14).contains(version.major)
case .tvOS:
Expand Down
62 changes: 46 additions & 16 deletions Sources/XCBuildSupport/PIF.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import Basics
import Foundation
import TSCBasic
import PackageModel

/// The Project Interchange Format (PIF) is a structured representation of the
/// project model created by clients (Xcode/SwiftPM) to send to XCBuild.
Expand Down Expand Up @@ -957,25 +958,39 @@ public enum PIF {

public enum Platform: String, CaseIterable, Codable {
case macOS = "macos"
case macCatalyst = "maccatalyst"
case iOS = "ios"
case tvOS = "tvos"
case watchOS = "watchos"
case driverKit = "driverkit"
case linux

public var conditions: [String] {
public var packageModelPlatform: PackageModel.Platform {
switch self {
case .macOS: return ["sdk=macosx*"]
case .iOS: return ["sdk=iphonesimulator*", "sdk=iphoneos*"]
case .tvOS: return ["sdk=appletvsimulator*", "sdk=appletvos*"]
case .watchOS: return ["sdk=watchsimulator*", "sdk=watchos*"]
case .driverKit: return ["sdk=driverkit*"]
case .linux: return ["sdk=linux*"]
case .macOS: return .macOS
case .macCatalyst: return .macCatalyst
case .iOS: return .iOS
case .tvOS: return .tvOS
case .watchOS: return .watchOS
case .driverKit: return .driverKit
case .linux: return .linux
}
}

public var conditions: [String] {
let filters = [PlatformsCondition(platforms: [packageModelPlatform])].toPlatformFilters().map { (filter: PIF.PlatformFilter) -> String in
if filter.environment.isEmpty {
return filter.platform
} else {
return "\(filter.platform)-\(filter.environment)"
}
}.sorted()
return ["__platform_filter=\(filters.joined(separator: ";"))"]
}
}

public private(set) var platformSpecificSettings = [Platform: [MultipleValueSetting: [String]]]()
public private(set) var platformSpecificSingleValueSettings = [Platform: [SingleValueSetting: String]]()
public private(set) var platformSpecificMultipleValueSettings = [Platform: [MultipleValueSetting: [String]]]()
public private(set) var singleValueSettings: [SingleValueSetting: String] = [:]
public private(set) var multipleValueSettings: [MultipleValueSetting: [String]] = [:]

Expand All @@ -984,6 +999,11 @@ public enum PIF {
set { singleValueSettings[setting] = newValue }
}

public subscript(_ setting: SingleValueSetting, for platform: Platform) -> String? {
get { platformSpecificSingleValueSettings[platform]?[setting] }
set { platformSpecificSingleValueSettings[platform, default: [:]][setting] = newValue }
}

public subscript(_ setting: SingleValueSetting, default defaultValue: @autoclosure () -> String) -> String {
get { singleValueSettings[setting, default: defaultValue()] }
set { singleValueSettings[setting] = newValue }
Expand All @@ -995,8 +1015,8 @@ public enum PIF {
}

public subscript(_ setting: MultipleValueSetting, for platform: Platform) -> [String]? {
get { platformSpecificSettings[platform]?[setting] }
set { platformSpecificSettings[platform, default: [:]][setting] = newValue }
get { platformSpecificMultipleValueSettings[platform]?[setting] }
set { platformSpecificMultipleValueSettings[platform, default: [:]][setting] = newValue }
}

public subscript(
Expand All @@ -1012,15 +1032,15 @@ public enum PIF {
for platform: Platform,
default defaultValue: @autoclosure () -> [String]
) -> [String] {
get { platformSpecificSettings[platform, default: [:]][setting, default: defaultValue()] }
set { platformSpecificSettings[platform, default: [:]][setting] = newValue }
get { platformSpecificMultipleValueSettings[platform, default: [:]][setting, default: defaultValue()] }
set { platformSpecificMultipleValueSettings[platform, default: [:]][setting] = newValue }
}

public init() {
}

private enum CodingKeys: CodingKey {
case platformSpecificSettings, singleValueSettings, multipleValueSettings
case platformSpecificSingleValueSettings, platformSpecificMultipleValueSettings, singleValueSettings, multipleValueSettings
}

public func encode(to encoder: Encoder) throws {
Expand All @@ -1029,7 +1049,8 @@ public enum PIF {
}

var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(platformSpecificSettings, forKey: .platformSpecificSettings)
try container.encode(platformSpecificSingleValueSettings, forKey: .platformSpecificSingleValueSettings)
try container.encode(platformSpecificMultipleValueSettings, forKey: .platformSpecificMultipleValueSettings)
try container.encode(singleValueSettings, forKey: .singleValueSettings)
try container.encode(multipleValueSettings, forKey: .multipleValueSettings)
}
Expand All @@ -1045,7 +1066,15 @@ public enum PIF {
try container.encode(value, forKey: StringKey(key.rawValue))
}

for (platform, values) in platformSpecificSettings {
for (platform, values) in platformSpecificSingleValueSettings {
for condition in platform.conditions {
for (key, value) in values {
try container.encode(value, forKey: "\(key.rawValue)[\(condition)]")
}
}
}

for (platform, values) in platformSpecificMultipleValueSettings {
for condition in platform.conditions {
for (key, value) in values {
try container.encode(value, forKey: "\(key.rawValue)[\(condition)]")
Expand All @@ -1057,7 +1086,8 @@ public enum PIF {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

platformSpecificSettings = try container.decodeIfPresent([Platform: [MultipleValueSetting: [String]]].self, forKey: .platformSpecificSettings) ?? .init()
platformSpecificSingleValueSettings = try container.decodeIfPresent([Platform: [SingleValueSetting: String]].self, forKey: .platformSpecificSingleValueSettings) ?? .init()
platformSpecificMultipleValueSettings = try container.decodeIfPresent([Platform: [MultipleValueSetting: [String]]].self, forKey: .platformSpecificMultipleValueSettings) ?? .init()
singleValueSettings = try container.decodeIfPresent([SingleValueSetting: String].self, forKey: .singleValueSettings) ?? [:]
multipleValueSettings = try container.decodeIfPresent([MultipleValueSetting: [String]] .self, forKey: .multipleValueSettings) ?? [:]
}
Expand Down
Loading