diff --git a/CHANGELOG.md b/CHANGELOG.md index f4886713..cdb133cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ ### Changed - Targets in the generated project now follow the declaration order from the source spec (Xcode sidebar, `xcodebuild -list` output). Previously they were always sorted alphabetically. Applies to both YAML and JSON specs. Declaration order is now also preserved for targets whose `platform`/`name` come from a target template and for targets whose key is a `${VARIABLE}`. #1619 @mirkokg +### Fixed +- Apply per-platform deployment targets to targets using `supportedDestinations` #1641 @arhxam + ### Internal - Update to XcodeProj 9.14.0 #1629 @philprime diff --git a/Sources/ProjectSpec/Target.swift b/Sources/ProjectSpec/Target.swift index 9d94c7d8..566efe5e 100644 --- a/Sources/ProjectSpec/Target.swift +++ b/Sources/ProjectSpec/Target.swift @@ -55,6 +55,7 @@ public struct Target: ProjectTarget { public var scheme: TargetScheme? public var legacy: LegacyTarget? public var deploymentTarget: Version? + public var deploymentTargets: DeploymentTarget? public var attributes: [String: Any] public var productName: String public var onlyCopyFilesOnInstall: Bool @@ -82,6 +83,7 @@ public struct Target: ProjectTarget { supportedDestinations: [SupportedDestination]? = nil, productName: String? = nil, deploymentTarget: Version? = nil, + deploymentTargets: DeploymentTarget? = nil, settings: Settings = .empty, configFiles: [String: String] = [:], sources: [TargetSource] = [], @@ -107,6 +109,7 @@ public struct Target: ProjectTarget { self.platform = platform self.supportedDestinations = supportedDestinations self.deploymentTarget = deploymentTarget + self.deploymentTargets = deploymentTargets self.productName = productName ?? name self.settings = settings self.configFiles = configFiles @@ -227,6 +230,7 @@ extension Target: Equatable { lhs.type == rhs.type && lhs.platform == rhs.platform && lhs.deploymentTarget == rhs.deploymentTarget && + lhs.deploymentTargets == rhs.deploymentTargets && lhs.transitivelyLinkDependencies == rhs.transitivelyLinkDependencies && lhs.requiresObjCLinking == rhs.requiresObjCLinking && lhs.directlyEmbedCarthageDependencies == rhs.directlyEmbedCarthageDependencies && @@ -314,12 +318,18 @@ extension Target: NamedJSONDictionaryConvertible { throw SpecParsingError.unknownTargetPlatform(platformString) } - if let string: String = jsonDictionary.json(atKeyPath: "deploymentTarget") { + if let dictionary = jsonDictionary["deploymentTarget"] as? JSONDictionary { + deploymentTarget = nil + deploymentTargets = try DeploymentTarget(jsonDictionary: dictionary) + } else if let string: String = jsonDictionary.json(atKeyPath: "deploymentTarget") { deploymentTarget = try Version.parse(string) + deploymentTargets = nil } else if let double: Double = jsonDictionary.json(atKeyPath: "deploymentTarget") { deploymentTarget = try Version.parse(String(double)) + deploymentTargets = nil } else { deploymentTarget = nil + deploymentTargets = nil } settings = try BuildSettingsParser(jsonDictionary: jsonDictionary).parse() @@ -395,7 +405,6 @@ extension Target: JSONEncodable { "buildToolPlugins": buildToolPlugins.map { $0.toJSONValue() }, "postbuildScripts": postBuildScripts.map { $0.toJSONValue() }, "buildRules": buildRules.map { $0.toJSONValue() }, - "deploymentTarget": deploymentTarget?.deploymentTarget, "info": info?.toJSONValue(), "entitlements": entitlements?.toJSONValue(), "transitivelyLinkDependencies": transitivelyLinkDependencies, @@ -405,6 +414,12 @@ extension Target: JSONEncodable { "legacy": legacy?.toJSONValue(), ] + if let deploymentTargets { + dict["deploymentTarget"] = deploymentTargets.toJSONValue() + } else { + dict["deploymentTarget"] = deploymentTarget?.deploymentTarget + } + if productName != name { dict["productName"] = productName } diff --git a/Sources/XcodeGenKit/SettingsBuilder.swift b/Sources/XcodeGenKit/SettingsBuilder.swift index 573c960d..043e9658 100644 --- a/Sources/XcodeGenKit/SettingsBuilder.swift +++ b/Sources/XcodeGenKit/SettingsBuilder.swift @@ -88,16 +88,15 @@ extension Project { } // apply custom platform version - if let version = target.deploymentTarget { - if !specSupportedDestinations.isEmpty { - for supportedDestination in specSupportedDestinations { - if let platform = Platform(rawValue: supportedDestination.rawValue) { - buildSettings[platform.deploymentTargetSetting] = .string(version.deploymentTarget) - } + if !specSupportedDestinations.isEmpty { + for supportedDestination in specSupportedDestinations { + if let platform = Platform(rawValue: supportedDestination.rawValue), + let version = target.deploymentTargets?.version(for: platform) ?? target.deploymentTarget { + buildSettings[platform.deploymentTargetSetting] = .string(version.deploymentTarget) } - } else { - buildSettings[target.platform.deploymentTargetSetting] = .string(version.deploymentTarget) } + } else if let version = target.deploymentTargets?.version(for: target.platform) ?? target.deploymentTarget { + buildSettings[target.platform.deploymentTargetSetting] = .string(version.deploymentTarget) } // Prevent setting presets from overrwriting settings in target xcconfig files diff --git a/Tests/ProjectSpecTests/SpecLoadingTests.swift b/Tests/ProjectSpecTests/SpecLoadingTests.swift index 54875e75..2bab9bac 100644 --- a/Tests/ProjectSpecTests/SpecLoadingTests.swift +++ b/Tests/ProjectSpecTests/SpecLoadingTests.swift @@ -695,6 +695,26 @@ class SpecLoadingTests: XCTestCase { try expect(project.targets) == [target] try expect(project.targets.first?.supportedDestinations) == [.macCatalyst, .iOS] } + + $0.it("encodes per-platform deployment targets") { + let deploymentTargets = DeploymentTarget( + iOS: Version(major: 18, minor: 0, patch: 0), + macOS: Version(major: 15, minor: 0, patch: 0) + ) + let target = Target( + name: "Framework", + type: .framework, + platform: .auto, + supportedDestinations: [.iOS, .macOS], + deploymentTargets: deploymentTargets + ) + + let targetJSON = target.toJSONValue() as! [String: Any?] + let deploymentTargetJSON = targetJSON["deploymentTarget"] as! [String: String?] + + try expect(deploymentTargetJSON["iOS"]!) == "18.0.0" + try expect(deploymentTargetJSON["macOS"]!) == "15.0.0" + } $0.it("invalid target platform because platform is an array and supported destinations is in use") { let expectedError = SpecParsingError.invalidTargetPlatformAsArray diff --git a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift index dd07d770..11114501 100644 --- a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift @@ -359,6 +359,26 @@ class ProjectGeneratorTests: XCTestCase { try expect(targetConfig1.buildSettings["ASSETCATALOG_COMPILER_APPICON_NAME"]?.stringValue) == "AppIcon" try expect(targetConfig1.buildSettings["CODE_SIGN_IDENTITY"]?.stringValue) == "iPhone Developer" } + + $0.it("supportedDestinations applies per-platform deployment targets") { + let target = try Target(name: "Target", jsonDictionary: [ + "type": "application", + "supportedDestinations": ["iOS", "macOS"], + "deploymentTarget": [ + "iOS": "18.0", + "macOS": "15.0", + "tvOS": "17.0", + ], + ]) + let project = Project(name: "", targets: [target]) + + let pbxProject = try project.generatePbxProj() + let targetConfig = try unwrap(pbxProject.nativeTargets.first?.buildConfigurationList?.buildConfigurations.first) + + try expect(targetConfig.buildSettings["IPHONEOS_DEPLOYMENT_TARGET"]?.stringValue) == "18.0" + try expect(targetConfig.buildSettings["MACOSX_DEPLOYMENT_TARGET"]?.stringValue) == "15.0" + try expect(targetConfig.buildSettings["TVOS_DEPLOYMENT_TARGET"]).beNil() + } $0.it("supportedDestinations merges settings - iOS, visionOS") { let target = Target(name: "Target", type: .application, platform: .auto, supportedDestinations: [.visionOS, .iOS])