Add support for disabling `Mac (designed for iOS)` destination for iOS deployment target (#5095)

* Add logic to remove iPad plist information when not needed

* Update tests

* Apply suggestions from code review

* Fix parameter name

* Update hash values in tests

* Add documentation
This commit is contained in:
TheInkedEngineer 2023-03-14 08:39:56 +01:00 committed by GitHub
parent 3edcc9801e
commit c0d1a5be83
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 149 additions and 58 deletions

View File

@ -4,8 +4,8 @@ import Foundation
/// A supported minimum deployment target.
public enum DeploymentTarget: Codable, Hashable {
/// The minimum iOS version and the list of devices your product will support.
case iOS(targetVersion: String, devices: DeploymentDevice)
/// The minimum iOS version, the list of devices your product will support, and whether or not the target should run on mac devices.
case iOS(targetVersion: String, devices: DeploymentDevice, supportsMacDesignedForIOS: Bool = true)
/// The minimum macOS version your product will support.
case macOS(targetVersion: String)
/// The minimum watchOS version your product will support.
@ -23,7 +23,7 @@ public enum DeploymentTarget: Codable, Hashable {
/// The target platform version
public var targetVersion: String {
switch self {
case let .iOS(targetVersion, _), let .macOS(targetVersion), let .watchOS(targetVersion), let .tvOS(targetVersion):
case let .iOS(targetVersion, _, _), let .macOS(targetVersion), let .watchOS(targetVersion), let .tvOS(targetVersion):
return targetVersion
}
}

View File

@ -22,8 +22,8 @@ public final class DeploymentTargetContentHasher: DeploymentTargetContentHashing
public func hash(deploymentTarget: DeploymentTarget) throws -> String {
let stringToHash: String
switch deploymentTarget {
case let .iOS(version, device):
stringToHash = "iOS-\(version)-\(device.rawValue)"
case let .iOS(version, device, supportsMacDesignedForIOS):
stringToHash = "iOS-\(version)-\(device.rawValue)-\(supportsMacDesignedForIOS)"
case let .macOS(version):
stringToHash = "macOS-\(version)"
case let .watchOS(version):

View File

@ -1146,8 +1146,12 @@ extension ProjectDescription.DefaultSettings {
extension ProjectDescription.DeploymentTarget {
fileprivate static func from(deploymentTarget: TuistGraph.DeploymentTarget) -> Self {
switch deploymentTarget {
case let .iOS(version, devices):
return .iOS(targetVersion: version, devices: .from(devices: devices))
case let .iOS(version, devices, supportsMacDesignedForIOS):
return .iOS(
targetVersion: version,
devices: .from(devices: devices),
supportsMacDesignedForIOS: supportsMacDesignedForIOS
)
case let .macOS(version):
return .macOS(targetVersion: version)
case let .tvOS(version):

View File

@ -291,13 +291,14 @@ final class ConfigGenerator: ConfigGenerating {
var settings: SettingsDictionary = [:]
switch deploymentTarget {
case let .iOS(version, devices):
case let .iOS(version, devices, supportsMacDesignedForIOS):
var deviceFamilyValues: [Int] = []
if devices.contains(.iphone) { deviceFamilyValues.append(1) }
if devices.contains(.ipad) { deviceFamilyValues.append(2) }
settings["TARGETED_DEVICE_FAMILY"] = .string(deviceFamilyValues.map { "\($0)" }.joined(separator: ","))
settings["IPHONEOS_DEPLOYMENT_TARGET"] = .string(version)
settings["SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD"] = supportsMacDesignedForIOS ? "YES" : "NO"
if devices.contains(.ipad), devices.contains(.mac) {
settings["SUPPORTS_MACCATALYST"] = "YES"

View File

@ -41,7 +41,11 @@ final class InfoPlistContentProvider: InfoPlistContentProviding {
// iOS app
if target.product == .app, target.platform == .iOS {
extend(&content, with: iosApp())
if case let .iOS(_, devices, _) = target.deploymentTarget, !devices.contains(.ipad) {
extend(&content, with: iosApp(iPadSupport: false))
} else {
extend(&content, with: iosApp(iPadSupport: true))
}
}
// macOS app
@ -145,9 +149,10 @@ final class InfoPlistContentProvider: InfoPlistContentProviding {
/// Returns the default Info.plist content that iOS apps should have.
///
/// - Parameter iPadSupport: Wether the `iOS` application supports `iPadOS`.
/// - Returns: Info.plist content.
func iosApp() -> [String: Any] {
[
func iosApp(iPadSupport: Bool) -> [String: Any] {
var baseInfo: [String: Any] = [
"LSRequiresIPhoneOS": true,
"UIRequiredDeviceCapabilities": [
"armv7",
@ -157,17 +162,22 @@ final class InfoPlistContentProvider: InfoPlistContentProviding {
"UIInterfaceOrientationLandscapeLeft",
"UIInterfaceOrientationLandscapeRight",
],
"UISupportedInterfaceOrientations~ipad": [
"UIInterfaceOrientationPortrait",
"UIInterfaceOrientationPortraitUpsideDown",
"UIInterfaceOrientationLandscapeLeft",
"UIInterfaceOrientationLandscapeRight",
],
"UIApplicationSceneManifest": [
"UIApplicationSupportsMultipleScenes": false,
"UISceneConfigurations": [:],
],
]
if iPadSupport {
baseInfo["UISupportedInterfaceOrientations~ipad"] = [
"UIInterfaceOrientationPortrait",
"UIInterfaceOrientationPortraitUpsideDown",
"UIInterfaceOrientationLandscapeLeft",
"UIInterfaceOrientationLandscapeRight",
]
}
return baseInfo
}
/// Returns the default Info.plist content that macOS apps should have.

View File

@ -3,7 +3,7 @@ import Foundation
// MARK: - DeploymentTarget
public enum DeploymentTarget: Hashable, Codable {
case iOS(String, DeploymentDevice)
case iOS(String, DeploymentDevice, supportsMacDesignedForIOS: Bool)
case macOS(String)
case watchOS(String)
case tvOS(String)
@ -19,7 +19,7 @@ public enum DeploymentTarget: Hashable, Codable {
public var version: String {
switch self {
case let .iOS(version, _): return version
case let .iOS(version, _, _): return version
case let .macOS(version): return version
case let .watchOS(version): return version
case let .tvOS(version): return version

View File

@ -234,9 +234,9 @@ public struct Target: Equatable, Hashable, Comparable, Codable {
/// with Catalyst compatibility.
public var targetDependencyBuildFilesPlatformFilter: BuildFilePlatformFilter? {
switch deploymentTarget {
case let .iOS(_, devices) where devices.contains(.all):
case let .iOS(_, devices, _) where devices.contains(.all):
return nil
case let .iOS(_, devices):
case let .iOS(_, devices, _):
if devices.contains(.mac) {
return .catalyst
} else {

View File

@ -11,7 +11,7 @@ extension Target {
product: Product = .app,
productName: String? = nil,
bundleId: String? = nil,
deploymentTarget: DeploymentTarget? = .iOS("13.1", [.iphone, .ipad]),
deploymentTarget: DeploymentTarget? = .iOS("13.1", [.iphone, .ipad], supportsMacDesignedForIOS: true),
infoPlist: InfoPlist? = nil,
entitlements: AbsolutePath? = nil,
settings: Settings? = Settings.test(),

View File

@ -10,8 +10,12 @@ extension TuistGraph.DeploymentTarget {
/// - generatorPaths: Generator paths.
static func from(manifest: ProjectDescription.DeploymentTarget) -> TuistGraph.DeploymentTarget {
switch manifest {
case let .iOS(version, devices):
return .iOS(version, DeploymentDevice(rawValue: devices.rawValue))
case let .iOS(version, devices, supportsMacDesignedForIOS):
return .iOS(
version,
DeploymentDevice(rawValue: devices.rawValue),
supportsMacDesignedForIOS: supportsMacDesignedForIOS
)
case let .macOS(version):
return .macOS(version)
case let .watchOS(version):

View File

@ -178,8 +178,8 @@ final class ContentHashingIntegrationTests: TuistUnitTestCase {
)
// Then
XCTAssertEqual(contentHash[framework1], "d31f7192381862aa6b74c7bbf2f097ce")
XCTAssertEqual(contentHash[framework2], "d0229bc4f00ff0b52bccb45668b9baa9")
XCTAssertEqual(contentHash[framework1], "d4631574745e74fc2ab6b7b9cceab088")
XCTAssertEqual(contentHash[framework2], "86a8f13a091f073b5395eddb452f14da")
}
func test_contentHashes_hashChangesWithCacheOutputType() throws {

View File

@ -27,21 +27,21 @@ final class DeploymentTargetContentHasherTests: TuistUnitTestCase {
func test_hash_whenIosIphoneV1_callsContentHasherWithExpectedStrings() throws {
// When
let deploymentTarget = DeploymentTarget.iOS("v1", .iphone)
let deploymentTarget = DeploymentTarget.iOS("v1", .iphone, supportsMacDesignedForIOS: false)
// Then
let hash = try subject.hash(deploymentTarget: deploymentTarget)
XCTAssertEqual(hash, "iOS-v1-1-hash")
XCTAssertEqual(hash, "iOS-v1-1-false-hash")
XCTAssertEqual(mockContentHasher.hashStringCallCount, 1)
}
func test_hash_whenIosIpadV2_callsContentHasherWithExpectedStrings() throws {
// When
let deploymentTarget = DeploymentTarget.iOS("v2", .ipad)
let deploymentTarget = DeploymentTarget.iOS("v2", .ipad, supportsMacDesignedForIOS: true)
// Then
let hash = try subject.hash(deploymentTarget: deploymentTarget)
XCTAssertEqual(hash, "iOS-v2-2-hash")
XCTAssertEqual(hash, "iOS-v2-2-true-hash")
XCTAssertEqual(mockContentHasher.hashStringCallCount, 1)
}

View File

@ -230,10 +230,12 @@ final class ConfigGeneratorTests: TuistUnitTestCase {
assert(config: releaseConfig, contains: testHostSettings)
}
func test_generateTargetWithDeploymentTarget_whenIOS() throws {
func test_generateTargetWithDeploymentTarget_whenIOS_withMacForIPhoneSupport() throws {
// Given
let project = Project.test()
let target = Target.test(deploymentTarget: .iOS("12.0", [.iphone, .ipad]))
let target = Target.test(
deploymentTarget: .iOS("12.0", [.iphone, .ipad], supportsMacDesignedForIOS: true)
)
let graph = Graph.test(path: project.path)
let graphTraverser = GraphTraverser(graph: graph)
@ -257,6 +259,43 @@ final class ConfigGeneratorTests: TuistUnitTestCase {
let expectedSettings = [
"TARGETED_DEVICE_FAMILY": "1,2",
"IPHONEOS_DEPLOYMENT_TARGET": "12.0",
"SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD": "YES",
]
assert(config: debugConfig, contains: expectedSettings)
assert(config: releaseConfig, contains: expectedSettings)
}
func test_generateTargetWithDeploymentTarget_whenIOS_withoutMacForIPhoneSupport() throws {
// Given
let project = Project.test()
let target = Target.test(
deploymentTarget: .iOS("12.0", [.iphone, .ipad], supportsMacDesignedForIOS: false)
)
let graph = Graph.test(path: project.path)
let graphTraverser = GraphTraverser(graph: graph)
// When
try subject.generateTargetConfig(
target,
project: project,
pbxTarget: pbxTarget,
pbxproj: pbxproj,
projectSettings: .default,
fileElements: ProjectFileElements(),
graphTraverser: graphTraverser,
sourceRootPath: AbsolutePath("/project")
)
// Then
let configurationList = pbxTarget.buildConfigurationList
let debugConfig = configurationList?.configuration(name: "Debug")
let releaseConfig = configurationList?.configuration(name: "Release")
let expectedSettings = [
"TARGETED_DEVICE_FAMILY": "1,2",
"IPHONEOS_DEPLOYMENT_TARGET": "12.0",
"SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD": "NO",
]
assert(config: debugConfig, contains: expectedSettings)
@ -265,7 +304,10 @@ final class ConfigGeneratorTests: TuistUnitTestCase {
func test_generateTargetWithDeploymentTarget_whenIOS_for_framework() throws {
// Given
let target = Target.test(product: .framework, deploymentTarget: .iOS("13.0", [.iphone, .ipad]))
let target = Target.test(
product: .framework,
deploymentTarget: .iOS("13.0", [.iphone, .ipad], supportsMacDesignedForIOS: true)
)
let project = Project.test(targets: [target])
let graph = Graph.test(path: project.path)
let graphTraverser = GraphTraverser(graph: graph)
@ -332,7 +374,9 @@ final class ConfigGeneratorTests: TuistUnitTestCase {
func test_generateTargetWithDeploymentTarget_whenCatalyst() throws {
// Given
let project = Project.test()
let target = Target.test(deploymentTarget: .iOS("13.1", [.iphone, .ipad, .mac]))
let target = Target.test(
deploymentTarget: .iOS("13.1", [.iphone, .ipad, .mac], supportsMacDesignedForIOS: false)
)
let graph = Graph.test(path: project.path)
let graphTraverser = GraphTraverser(graph: graph)
@ -358,6 +402,7 @@ final class ConfigGeneratorTests: TuistUnitTestCase {
"IPHONEOS_DEPLOYMENT_TARGET": "13.1",
"SUPPORTS_MACCATALYST": "YES",
"DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER": "YES",
"SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD": "NO",
]
assert(config: debugConfig, contains: expectedSettings)

View File

@ -19,9 +19,13 @@ final class InfoPlistContentProviderTests: XCTestCase {
super.tearDown()
}
func test_content_wheniOSApp() {
func test_content_wheniOSApp_withiPadSupport() {
// Given
let target = Target.test(platform: .iOS, product: .app)
let target = Target.test(
platform: .iOS,
product: .app,
deploymentTarget: .iOS("16.0", [.iphone, .ipad], supportsMacDesignedForIOS: true)
)
// When
let got = subject.content(
@ -61,6 +65,46 @@ final class InfoPlistContentProviderTests: XCTestCase {
])
}
func test_content_wheniOSApp_withoutiPadSupport() {
// Given
let target = Target.test(
platform: .iOS,
product: .app,
deploymentTarget: .iOS("16.0", .iphone, supportsMacDesignedForIOS: true)
)
// When
let got = subject.content(
project: .empty(),
target: target,
extendedWith: ["ExtraAttribute": "Value"]
)
// Then
assertEqual(got, [
"CFBundleName": "$(PRODUCT_NAME)",
"CFBundleIdentifier": "$(PRODUCT_BUNDLE_IDENTIFIER)",
"UIRequiredDeviceCapabilities": ["armv7"],
"UISupportedInterfaceOrientations": [
"UIInterfaceOrientationPortrait",
"UIInterfaceOrientationLandscapeLeft",
"UIInterfaceOrientationLandscapeRight",
],
"CFBundleShortVersionString": "1.0",
"LSRequiresIPhoneOS": true,
"CFBundleDevelopmentRegion": "$(DEVELOPMENT_LANGUAGE)",
"CFBundlePackageType": "APPL",
"CFBundleVersion": "1",
"ExtraAttribute": "Value",
"CFBundleExecutable": "$(EXECUTABLE_NAME)",
"CFBundleInfoDictionaryVersion": "6.0",
"UIApplicationSceneManifest": [
"UIApplicationSupportsMultipleScenes": false,
"UISceneConfigurations": [:],
],
])
}
func test_content_whenMacosApp() {
// Given
let target = Target.test(platform: .macOS, product: .app)

View File

@ -7,7 +7,7 @@ import XCTest
final class DeploymentTargetTests: TuistUnitTestCase {
func test_codable_iOS() {
// Given
let subject = DeploymentTarget.iOS("12.1", [.iphone, .mac])
let subject = DeploymentTarget.iOS("12.1", [.iphone, .mac], supportsMacDesignedForIOS: true)
// Then
XCTAssertCodable(subject)

View File

@ -299,7 +299,7 @@ final class TargetTests: TuistUnitTestCase {
func test_targetDependencyBuildFilesPlatformFilter_when_iOS_targets_mac() {
// Given
let target = Target.test(deploymentTarget: .iOS("14.0", [.mac]))
let target = Target.test(deploymentTarget: .iOS("14.0", [.mac], supportsMacDesignedForIOS: false))
// When
let got = target.targetDependencyBuildFilesPlatformFilter
@ -310,7 +310,7 @@ final class TargetTests: TuistUnitTestCase {
func test_targetDependencyBuildFilesPlatformFilter_when_iOS_and_doesnt_target_mac() {
// Given
let target = Target.test(deploymentTarget: .iOS("14.0", [.iphone]))
let target = Target.test(deploymentTarget: .iOS("14.0", [.iphone], supportsMacDesignedForIOS: false))
// When
let got = target.targetDependencyBuildFilesPlatformFilter

View File

@ -19,7 +19,7 @@ final class DeploymentTargetManifestMapperTests: TuistUnitTestCase {
let got = TuistGraph.DeploymentTarget.from(manifest: manifest)
// Then
guard case let .iOS(version, devices) = got
guard case let .iOS(version, devices, supportsMacDesignedForIOS) = got
else {
XCTFail("Deployment target should be iOS")
return
@ -28,5 +28,6 @@ final class DeploymentTargetManifestMapperTests: TuistUnitTestCase {
XCTAssertEqual(version, "13.1")
XCTAssertTrue(devices.contains(.iphone))
XCTAssertFalse(devices.contains(.ipad))
XCTAssertTrue(supportsMacDesignedForIOS)
}
}

View File

@ -198,24 +198,6 @@
"version" : "1.0.4"
}
},
{
"identity" : "swift-docc-plugin",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-docc-plugin.git",
"state" : {
"revision" : "10bc670db657d11bdd561e07de30a9041311b2b1",
"version" : "1.1.0"
}
},
{
"identity" : "swift-docc-symbolkit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-docc-symbolkit",
"state" : {
"revision" : "b45d1f2ed151d057b54504d653e0da5552844e34",
"version" : "1.0.0"
}
},
{
"identity" : "swift-log",
"kind" : "remoteSourceControl",