Skip to content

Commit

Permalink
Allow setting deployment target devices (#541)
Browse files Browse the repository at this point in the history
* Create `DeploymentTarget` descriptor

* Add `DeploymentTarget` to `Target` description

* Fix formatting issues

* Add `DeploymentTarget` to `TuistGenerator.Target`

* Update `TARGETED_DEVICE_FAMILY`

* Fix formatting

* Update `*_DEPLOYMENT_TARGET` in Build Settings

* Fix linter issue

* Make sure `platform` and `deploymentTarget` manifest parameters are compatible

* Move updating a deployment target to `updateTargetDervied` method

* Validate os version of deployment target

* Update Changelog

* Add tests to generator models

* Mark a breaking change in a changelog entry

* Move a regex utility function to `TuistCore`

* Make changes in public API of deployment target

* Fix unit tests of string regex

* Expose `version` property on deployment target

* Update Target documenatation

* Update acceptance tests

* Fix formatting

* Support mac device for iOS deployment target

* Fix lint formatting

* Enable catalyst support only if ipad is enabled for iOS deployment target

* Add deployment target tests to `ConfigGenerator`
  • Loading branch information
mollyIV authored and Pedro Piñera Buendía committed Oct 16, 2019
1 parent 166272b commit 2e04f8c
Show file tree
Hide file tree
Showing 22 changed files with 440 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/

- XCTAssertThrowsSpecific convenient function to test for specific errors https://github.com/tuist/tuist/pull/535 by @fortmarek
- `HTTPClient` utility class to `TuistEnvKit` https://github.com/tuist/tuist/pull/508 by @pepibumur.
- **Breaking** Allow specifying a deployment target within project manifests https://github.com/tuist/tuist/pull/541by @mollyIV

### Changed

Expand Down
15 changes: 15 additions & 0 deletions Sources/ProjectDescription/DeploymentDevice.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation

// MARK: - DeploymentDevice

public struct DeploymentDevice: OptionSet, Codable {
public static let iphone = DeploymentDevice(rawValue: 1 << 0)
public static let ipad = DeploymentDevice(rawValue: 1 << 1)
public static let mac = DeploymentDevice(rawValue: 1 << 2)

public let rawValue: UInt

public init(rawValue: UInt) {
self.rawValue = rawValue
}
}
47 changes: 47 additions & 0 deletions Sources/ProjectDescription/DeploymentTarget.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import Foundation

// MARK: - DeploymentTarget

public enum DeploymentTarget: Codable {
case iOS(targetVersion: String, devices: DeploymentDevice)
case macOS(targetVersion: String)
// TODO: 🙈 Add `watchOS` and `tvOS` support

private enum Kind: String, Codable {
case iOS
case macOS
}

enum CodingKeys: String, CodingKey {
case kind
case version
case deploymentDevices
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let kind = try container.decode(Kind.self, forKey: .kind)
switch kind {
case .iOS:
let version = try container.decode(String.self, forKey: .version)
let deploymentDevices = try container.decode(DeploymentDevice.self, forKey: .deploymentDevices)
self = .iOS(targetVersion: version, devices: deploymentDevices)
case .macOS:
let version = try container.decode(String.self, forKey: .version)
self = .macOS(targetVersion: version)
}
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case let .iOS(version, deploymentDevices):
try container.encode(Kind.iOS.self, forKey: .kind)
try container.encode(version, forKey: .version)
try container.encode(deploymentDevices, forKey: .deploymentDevices)
case let .macOS(version):
try container.encode(Kind.macOS.self, forKey: .kind)
try container.encode(version, forKey: .version)
}
}
}
6 changes: 6 additions & 0 deletions Sources/ProjectDescription/Target.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ public class Target: Codable {
/// this value to the name of the target.
public let productName: String?

/// Deployment target.
public let deploymentTarget: DeploymentTarget?

/// Relative path to the Info.plist file.
public let infoPlist: InfoPlist

Expand Down Expand Up @@ -66,6 +69,7 @@ public class Target: Codable {
case coreDataModels = "core_data_models"
case actions
case environment
case deploymentTarget
}

/// Initializes the target.
Expand All @@ -90,6 +94,7 @@ public class Target: Codable {
product: Product,
productName: String? = nil,
bundleId: String,
deploymentTarget: DeploymentTarget? = nil,
infoPlist: InfoPlist,
sources: SourceFilesList? = nil,
resources: [FileElement]? = nil,
Expand All @@ -115,5 +120,6 @@ public class Target: Codable {
self.actions = actions
self.coreDataModels = coreDataModels
self.environment = environment
self.deploymentTarget = deploymentTarget
}
}
12 changes: 12 additions & 0 deletions Sources/TuistCore/Extensions/String+Regex.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Foundation

// MARK: - Regex

extension String {
public func matches(pattern: String) -> Bool {
guard let range = self.range(of: pattern, options: .regularExpression) else {
return false
}
return range == self.range(of: self)
}
}
29 changes: 24 additions & 5 deletions Sources/TuistGenerator/Generator/ConfigGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ final class ConfigGenerator: ConfigGenerating {
let settingsHelper = SettingsHelper()
var settings = try defaultSettingsProvider.targetSettings(target: target,
buildConfiguration: buildConfiguration)
updateTargetDerived(buildSettings: &settings,
target: target,
graph: graph,
sourceRootPath: sourceRootPath)

settingsHelper.extend(buildSettings: &settings, with: target.settings?.base ?? [:])
settingsHelper.extend(buildSettings: &settings, with: configuration?.settings ?? [:])

Expand All @@ -139,11 +144,6 @@ final class ConfigGenerator: ConfigGenerating {
variantBuildConfiguration.baseConfiguration = fileReference
}

updateTargetDerived(buildSettings: &settings,
target: target,
graph: graph,
sourceRootPath: sourceRootPath)

variantBuildConfiguration.buildSettings = settings.toAny()
pbxproj.add(object: variantBuildConfiguration)
configurationList.buildConfigurations.append(variantBuildConfiguration)
Expand Down Expand Up @@ -191,5 +191,24 @@ final class ConfigGenerator: ConfigGenerating {
}
}
}

if let deploymentTarget = target.deploymentTarget {
switch deploymentTarget {
case let .iOS(version, devices):
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)

if devices.contains(.ipad), devices.contains(.mac) {
settings["SUPPORTS_MACCATALYST"] = "YES"
settings["DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER"] = "YES"
}
case let .macOS(version):
settings["MACOSX_DEPLOYMENT_TARGET"] = .string(version)
}
}
}
}
15 changes: 15 additions & 0 deletions Sources/TuistGenerator/Linter/SettingsLinter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ final class SettingsLinter: SettingsLinting {
if let settings = target.settings {
issues.append(contentsOf: lintConfigFilesExist(settings: settings))
}

if let deploymentTarget = target.deploymentTarget {
issues.append(contentsOf: lint(platform: target.platform, isCompatibleWith: deploymentTarget))
}
return issues
}

Expand Down Expand Up @@ -49,4 +53,15 @@ final class SettingsLinter: SettingsLinting {
}
return []
}

// TODO_MAJOR_CHANGE: Merge deploymentTarget and platform arguments together.
private func lint(platform: Platform, isCompatibleWith deploymentTarget: DeploymentTarget) -> [LintingIssue] {
let issue = LintingIssue(reason: "Found an inconsistency between a platform `\(platform.caseValue)` and deployment target `\(deploymentTarget.platform)`", severity: .error)

switch deploymentTarget {
case .iOS: if platform != .iOS { return [issue] }
case .macOS: if platform != .macOS { return [issue] }
}
return []
}
}
13 changes: 13 additions & 0 deletions Sources/TuistGenerator/Linter/TargetLinter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class TargetLinter: TargetLinting {
issues.append(contentsOf: lintHasSourceFiles(target: target))
issues.append(contentsOf: lintCopiedFiles(target: target))
issues.append(contentsOf: lintLibraryHasNoResources(target: target))
issues.append(contentsOf: lintDeploymentTarget(target: target))
issues.append(contentsOf: settingsLinter.lint(target: target))

target.actions.forEach { action in
Expand Down Expand Up @@ -139,4 +140,16 @@ class TargetLinter: TargetLinting {

return []
}

private func lintDeploymentTarget(target: Target) -> [LintingIssue] {
guard let deploymentTarget = target.deploymentTarget else {
return []
}

let issue = LintingIssue(reason: "The version of deployment target is incorrect", severity: .error)

let osVersionRegex = "\\b[0-9]+\\.[0-9]+(?:\\.[0-9]+)?\\b"
if !deploymentTarget.version.matches(pattern: osVersionRegex) { return [issue] }
return []
}
}
15 changes: 15 additions & 0 deletions Sources/TuistGenerator/Models/DeploymentDevice.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation

// MARK: - DeploymentDevice

public struct DeploymentDevice: OptionSet {
public static let iphone = DeploymentDevice(rawValue: 1 << 0)
public static let ipad = DeploymentDevice(rawValue: 1 << 1)
public static let mac = DeploymentDevice(rawValue: 1 << 2)

public let rawValue: UInt

public init(rawValue: UInt) {
self.rawValue = rawValue
}
}
23 changes: 23 additions & 0 deletions Sources/TuistGenerator/Models/DeploymentTarget.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Foundation

// MARK: - DeploymentTarget

public enum DeploymentTarget {
case iOS(String, DeploymentDevice)
case macOS(String)
// TODO: 🙈 Add `watchOS` and `tvOS` support

public var platform: String {
switch self {
case .iOS: return "iOS"
case .macOS: return "macOS"
}
}

public var version: String {
switch self {
case let .iOS(version, _): return version
case let .macOS(version): return version
}
}
}
3 changes: 3 additions & 0 deletions Sources/TuistGenerator/Models/Target.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class Target: Equatable, Hashable {
public let product: Product
public let bundleId: String
public let productName: String
public let deploymentTarget: DeploymentTarget?

// An info.plist file is needed for (dynamic) frameworks, applications and executables
// however is not needed for other products such as static libraries.
Expand All @@ -39,6 +40,7 @@ public class Target: Equatable, Hashable {
product: Product,
productName: String?,
bundleId: String,
deploymentTarget: DeploymentTarget? = nil,
infoPlist: InfoPlist? = nil,
entitlements: AbsolutePath? = nil,
settings: Settings? = nil,
Expand All @@ -55,6 +57,7 @@ public class Target: Equatable, Hashable {
self.platform = platform
self.bundleId = bundleId
self.productName = productName ?? name.replacingOccurrences(of: "-", with: "_")
self.deploymentTarget = deploymentTarget
self.infoPlist = infoPlist
self.entitlements = entitlements
self.settings = settings
Expand Down
13 changes: 13 additions & 0 deletions Sources/TuistKit/Generator/GeneratorModelLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ extension TuistGenerator.Target {

let bundleId = manifest.bundleId
let productName = manifest.productName
let deploymentTarget = manifest.deploymentTarget.map { TuistGenerator.DeploymentTarget.from(manifest: $0) }

let dependencies = manifest.dependencies.map { TuistGenerator.Dependency.from(manifest: $0) }

Expand Down Expand Up @@ -336,6 +337,7 @@ extension TuistGenerator.Target {
product: product,
productName: productName,
bundleId: bundleId,
deploymentTarget: deploymentTarget,
infoPlist: infoPlist,
entitlements: entitlements,
settings: settings,
Expand Down Expand Up @@ -699,3 +701,14 @@ extension TuistGenerator.SDKStatus {
}
}
}

extension TuistGenerator.DeploymentTarget {
static func from(manifest: ProjectDescription.DeploymentTarget) -> TuistGenerator.DeploymentTarget {
switch manifest {
case let .iOS(version, devices):
return .iOS(version, DeploymentDevice(rawValue: devices.rawValue))
case let .macOS(version):
return .macOS(version)
}
}
}
19 changes: 19 additions & 0 deletions Tests/ProjectDescriptionTests/DeploymentTargetTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Foundation
import TuistCoreTesting
import XCTest

@testable import ProjectDescription

final class DeploymentTargetTests: XCTestCase {
func test_toJSON_whenIOS() {
let subject = DeploymentTarget.iOS(targetVersion: "13.1", devices: [.iphone, .ipad])
let expected = "{\"kind\":\"iOS\",\"version\":\"13.1\",\"deploymentDevices\":3}"
XCTAssertCodableEqualToJson(subject, expected)
}

func test_toJSON_whenMacOS() {
let subject = DeploymentTarget.macOS(targetVersion: "10.15")
let expected = "{\"kind\":\"macOS\",\"version\":\"10.15\"}"
XCTAssertCodableEqualToJson(subject, expected)
}
}
6 changes: 6 additions & 0 deletions Tests/ProjectDescriptionTests/TargetTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ final class TargetTests: XCTestCase {
product: .app,
productName: "product_name",
bundleId: "bundle_id",
deploymentTarget: .iOS(targetVersion: "13.1", devices: [.iphone, .ipad]),
infoPlist: "info.plist",
sources: "sources/*",
resources: "resources/*",
Expand All @@ -37,6 +38,11 @@ final class TargetTests: XCTestCase {

let expected = """
{
"deploymentTarget": {
"kind": "iOS",
"version": "13.1",
"deploymentDevices": 3
},
"headers": {
"public": { "globs": ["public\\/*"] },
"private": { "globs": ["private\\/*"] },
Expand Down
19 changes: 19 additions & 0 deletions Tests/TuistCoreTests/Extensions/String+RegexTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import SPMUtility
import XCTest
@testable import TuistCore

final class StringRegexTests: XCTestCase {
func test_string_regex() {
let osVersionPattern = "\\b[0-9]+\\.[0-9]+(?:\\.[0-9]+)?\\b"
XCTAssertTrue("10.0.1".matches(pattern: osVersionPattern))
XCTAssertFalse("tuist".matches(pattern: osVersionPattern))

let twoDigitsOnlyPattern = "^[0-9]{2}$"
XCTAssertTrue("10".matches(pattern: twoDigitsOnlyPattern))
XCTAssertFalse("10.0.1".matches(pattern: twoDigitsOnlyPattern))

let singleWordPattern = "project*"
XCTAssertTrue("project".matches(pattern: singleWordPattern))
XCTAssertFalse("This is a project".matches(pattern: singleWordPattern))
}
}

0 comments on commit 2e04f8c

Please sign in to comment.