mirror of
https://github.com/RevenueCat/purchases-ios
synced 2026-11-09 22:35:56 +05:45
Support Xcode 27 in Tuist-generated projects (#7208)
* ci: conditionally bump SDK deployment targets for Xcode 27 SDK Add [sdk=*27*] conditional deployment-target overrides to RevenueCat.xcodeproj so Carthage from-source builds compile at the Xcode-27-required floors (iOS 15 / macOS 12 / tvOS 15 / watchOS 8) while older Xcode keeps building at iOS 13. Lets integrators get the right floor automatically based on their Xcode. Co-authored-by: Cursor <cursoragent@cursor.com> * ci: bump watchOS Xcode 27 SDK override to 9.0 Xcode 27 requires a watchOS deployment target of at least 9.0 (and drops the deprecated armv7k arch at that floor), so raise the [sdk=watch*27*] conditional override from 8.0 to 9.0. Co-authored-by: Cursor <cursoragent@cursor.com> * ci: add Carthage installation test job on Xcode 27 Add installation-tests-carthage-xcode-27 and extract the shared steps into a reusable run-carthage-installation-tests command. Declare conditional [sdk=*27*] deployment targets on the Carthage consumer app to match the SDK project so the from-source build works on Xcode 27. Co-authored-by: Cursor <cursoragent@cursor.com> * ci: unify Carthage installation tests into a parameterized job Replace the duplicated installation-tests-carthage and installation-tests-carthage-xcode-27 jobs (and the shared command) with a single parameterized job invoked twice via a `name` override, and teach the on-demand generator to emit the parameterized invocation for the Xcode 27 variant. Co-authored-by: Cursor <cursoragent@cursor.com> * ci: don't require Xcode 27 Carthage job for releases Xcode 27 is still beta, so the installation-tests-carthage-xcode-27 job still runs on release branches but no longer blocks the all-tests-succeeded gate. Co-authored-by: Cursor <cursoragent@cursor.com> * ci: run automatic Carthage installation tests on Xcode 26 Xcode 26 still builds RevenueCat.xcodeproj at its declared deployment targets (its SDK floors are iOS 12 / macOS 10.13 / tvOS 12 / watchOS 4 / visionOS 1), so the from-source build needs no overrides there. Move the automatic installation-tests-carthage job from Xcode 16 to Xcode 26, which is a current App-Store-valid toolchain. The Xcode-27-only floor bump (iOS 15 / macOS 12 / tvOS 15 / watchOS 9) remains covered by the [sdk=*27*] overrides, and the installation-tests-carthage-xcode-27 variant stays available as a manual-only on-demand job instead of running automatically in CI. Co-authored-by: Cursor <cursoragent@cursor.com> * ci: run Carthage installation tests on iOS 26 simulator fastlane scan was defaulting to the pre-installed iOS 18.6 simulator, whose networking hangs under Xcode 26.5's CoreSimulator, causing testCanFetchCustomerInfo to deterministically time out. Pin the device to iPhone 17 to force an iOS 26.x runtime. Co-authored-by: Cursor <cursoragent@cursor.com> * ci: only pin Carthage test simulator on Xcode 26 Scope the iPhone 17 device override to Xcode 26 (whose CI image otherwise defaults scan to a stale iOS 18 simulator with broken networking); newer Xcodes already auto-select a matching runtime. Co-authored-by: Cursor <cursoragent@cursor.com> * feat: support Xcode 27 in Tuist-generated projects Add SDK-conditional [sdk=*27*] deployment-target overrides to Tuist manifests so the generated workspace builds on Xcode 27 (which enforces iOS 15 / tvOS 15 / watchOS 9 / macOS 12 minimums) while leaving the base deployment targets untouched for every other SDK. No environment flag, mirroring the RevenueCat.xcodeproj mechanism. Co-authored-by: Cursor <cursoragent@cursor.com> * feat: cover remaining sub-floor Tuist projects for Xcode 27 Apply the Xcode 27 SDK-conditional deployment-target overrides to the remaining workspace projects with sub-floor minimums: APITesters, BinarySizeTest, PurchaseTester and XCFrameworkInstallationTests. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: drop version from Xcode deployment-target override naming Rename addingXcode27DeploymentTargetOverrides to addingXcodeDeploymentTargetOverrides (and related identifiers/file) so the helper can accumulate future Xcode SDK floors without renaming. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: derive dependency lists from a single source in Package.swift Write the wrapped test dependency names once and derive both productTypes and the deployment-target targetSettings from it, instead of repeating the list in two places. Co-authored-by: Cursor <cursoragent@cursor.com> * docs: clarify productType grouping and trim override doc comment Co-authored-by: Cursor <cursoragent@cursor.com> * fix: scope iOS deployment floor to macOS SDK for Mac Catalyst Mac Catalyst builds against the macOS SDK but derive their minimum from the iOS deployment target (ignoring MACOSX_DEPLOYMENT_TARGET), so the Xcode 27 iOS floor must also be scoped to [sdk=macosx27*] for the Catalyst build validation. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
3a8178a34c
commit
454440d5b9
10 files changed
+135
-27
No files matched your search
@@ -92,7 +92,7 @@ let project = Project(
|
||||
],
|
||||
settings: .framework
|
||||
),
|
||||
],
|
||||
].addingXcodeDeploymentTargetOverrides(),
|
||||
schemes: [
|
||||
.scheme(
|
||||
name: "PurchaseTester",
|
||||
|
||||
@@ -136,7 +136,7 @@ let project = Project(
|
||||
],
|
||||
metadata: .metadata(tags: ["APITester"])
|
||||
)
|
||||
],
|
||||
].addingXcodeDeploymentTargetOverrides(),
|
||||
schemes: [
|
||||
.scheme(
|
||||
name: "All API Tests",
|
||||
|
||||
@@ -148,7 +148,7 @@ let project = Project(
|
||||
defaultSettings: .essential
|
||||
)
|
||||
)
|
||||
],
|
||||
].addingXcodeDeploymentTargetOverrides(),
|
||||
schemes: [
|
||||
.scheme(
|
||||
name: "BinarySizeTest",
|
||||
|
||||
@@ -73,7 +73,7 @@ let project = Project(
|
||||
]
|
||||
)
|
||||
|
||||
],
|
||||
].addingXcodeDeploymentTargetOverrides(),
|
||||
schemes: [
|
||||
.scheme(
|
||||
name: "RevenueCat",
|
||||
|
||||
@@ -296,7 +296,7 @@ let project = Project(
|
||||
metadata: .metadata(tags: ["RevenueCatTests"])
|
||||
)
|
||||
|
||||
],
|
||||
].addingXcodeDeploymentTargetOverrides(),
|
||||
schemes: [
|
||||
|
||||
.scheme(
|
||||
|
||||
@@ -24,7 +24,7 @@ let project = Project(
|
||||
base: ([:] as SettingsDictionary).appendingTuistSwiftConditions()
|
||||
)
|
||||
)
|
||||
],
|
||||
].addingXcodeDeploymentTargetOverrides(),
|
||||
schemes: [
|
||||
.scheme(
|
||||
name: "RevenueCatUI",
|
||||
|
||||
@@ -37,7 +37,7 @@ let project = Project(
|
||||
.target(name: "RulesEngineInternal")
|
||||
]
|
||||
)
|
||||
],
|
||||
].addingXcodeDeploymentTargetOverrides(),
|
||||
schemes: [
|
||||
.scheme(
|
||||
name: "RulesEngineInternal",
|
||||
|
||||
@@ -74,7 +74,7 @@ let project = Project(
|
||||
],
|
||||
settings: .appTarget
|
||||
)
|
||||
],
|
||||
].addingXcodeDeploymentTargetOverrides(),
|
||||
schemes: [
|
||||
.scheme(
|
||||
name: "XCFrameworkInstallationTests",
|
||||
|
||||
+42
-19
@@ -13,26 +13,49 @@ if !includeTestDependencies {
|
||||
#if TUIST
|
||||
import ProjectDescription
|
||||
|
||||
// Test dependencies that must be built as dynamic frameworks to fix
|
||||
// "Attempted to report a test failure to XCTest while no test case was running"
|
||||
// (see https://github.com/Quick/Nimble/issues/1101). These also declare deployment minimums
|
||||
// below the floors newer Xcode SDKs enforce, so they receive the overrides below.
|
||||
let testFrameworkDependencies = [
|
||||
"Nimble", "NimbleObjectiveC",
|
||||
"CwlPreconditionTesting", "CwlPosixPreconditionTesting",
|
||||
"CwlCatchException", "CwlMachBadInstructionHandler",
|
||||
"SnapshotTesting", "OHHTTPStubs", "OHHTTPStubsSwift"
|
||||
]
|
||||
|
||||
// Our own frameworks and their binary dependencies, built as dynamic frameworks (the default for
|
||||
// several of these would otherwise be `.staticFramework`).
|
||||
var productTypes: [String: ProjectDescription.Product] = [
|
||||
"RevenueCat": .framework,
|
||||
"RevenueCatUI": .framework,
|
||||
"Purchases": .framework,
|
||||
"GoogleMobileAds": .framework
|
||||
]
|
||||
testFrameworkDependencies.forEach { productTypes[$0] = .framework }
|
||||
|
||||
// SDK-conditional deployment-target overrides, mirroring `Target+DeploymentTargetOverrides.swift`.
|
||||
// Applied per target (target level), which overrides the deployment target Tuist derives from each
|
||||
// package's platforms only when building against a newer Xcode SDK. `CwlCatchExceptionSupport` is a
|
||||
// transitive Cwl target that is built (so it needs the override) but isn't forced to a framework.
|
||||
let xcodeDeploymentTargetOverrides: SettingsDictionary = [
|
||||
"IPHONEOS_DEPLOYMENT_TARGET[sdk=iphoneos27*]": "15.0",
|
||||
"IPHONEOS_DEPLOYMENT_TARGET[sdk=iphonesimulator27*]": "15.0",
|
||||
// Mac Catalyst derives its minimum from the iOS deployment target under the macOS SDK.
|
||||
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx27*]": "15.0",
|
||||
"TVOS_DEPLOYMENT_TARGET[sdk=appletvos27*]": "15.0",
|
||||
"TVOS_DEPLOYMENT_TARGET[sdk=appletvsimulator27*]": "15.0",
|
||||
"WATCHOS_DEPLOYMENT_TARGET[sdk=watchos27*]": "9.0",
|
||||
"WATCHOS_DEPLOYMENT_TARGET[sdk=watchsimulator27*]": "9.0",
|
||||
"MACOSX_DEPLOYMENT_TARGET[sdk=macosx27*]": "12.0"
|
||||
]
|
||||
let deploymentOverrideTargets = testFrameworkDependencies + ["CwlCatchExceptionSupport"]
|
||||
|
||||
let packageSettings = PackageSettings(
|
||||
productTypes: [
|
||||
// Nimble and its dependencies must be dynamic frameworks
|
||||
// to fix "Attempted to report a test failure to XCTest while no test case was running"
|
||||
// See: https://github.com/Quick/Nimble/issues/1101
|
||||
"Nimble": .framework,
|
||||
"NimbleObjectiveC": .framework,
|
||||
"CwlPreconditionTesting": .framework,
|
||||
"CwlPosixPreconditionTesting": .framework,
|
||||
"CwlCatchException": .framework,
|
||||
"CwlMachBadInstructionHandler": .framework,
|
||||
// Other frameworks
|
||||
"SnapshotTesting": .framework, // default is .staticFramework,
|
||||
"RevenueCat": .framework,
|
||||
"RevenueCatUI": .framework,
|
||||
"Purchases": .framework,
|
||||
"GoogleMobileAds": .framework,
|
||||
"OHHTTPStubs": .framework,
|
||||
"OHHTTPStubsSwift": .framework
|
||||
]
|
||||
productTypes: productTypes,
|
||||
targetSettings: deploymentOverrideTargets.reduce(into: [:]) { result, target in
|
||||
result[target] = .settings(base: xcodeDeploymentTargetOverrides)
|
||||
}
|
||||
)
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// Target+DeploymentTargetOverrides.swift
|
||||
//
|
||||
// Created by Antonio Pallares.
|
||||
//
|
||||
|
||||
import ProjectDescription
|
||||
|
||||
extension Target {
|
||||
|
||||
/// Returns a copy of the target with SDK-conditional deployment-target overrides.
|
||||
///
|
||||
/// Newer Xcode SDKs raise the minimum deployment target they'll build (e.g. Xcode 27 enforces
|
||||
/// iOS 15 / tvOS 15 / watchOS 9 / macOS 12). Instead of raising the SDK's true minimums everywhere
|
||||
/// (which would drop support for older OSes on every Xcode), this adds `[sdk=*]`-conditional
|
||||
/// overrides that only take effect when building against those newer SDKs. The base deployment
|
||||
/// target — and every other SDK — is left untouched, so a single generated workspace keeps
|
||||
/// building across Xcode versions with no environment flag. Add future SDK floors here.
|
||||
///
|
||||
/// It is safe to apply to every target: only platforms whose deployment target is *below* a floor
|
||||
/// are raised, so targets already at or above the floor are returned unchanged and no platform is
|
||||
/// ever lowered.
|
||||
public func addingXcodeDeploymentTargetOverrides() -> Target {
|
||||
guard let deploymentTargets else { return self }
|
||||
|
||||
var overrides: SettingsDictionary = [:]
|
||||
|
||||
func raiseToFloor(_ current: String?, floor: String, deviceSDK: String, simulatorSDK: String, buildSetting: String) {
|
||||
guard let current, current.rc_isVersionBelow(floor) else { return }
|
||||
overrides["\(buildSetting)[sdk=\(deviceSDK)27*]"] = .string(floor)
|
||||
overrides["\(buildSetting)[sdk=\(simulatorSDK)27*]"] = .string(floor)
|
||||
}
|
||||
|
||||
// Xcode 27 floors.
|
||||
raiseToFloor(deploymentTargets.iOS, floor: "15.0",
|
||||
deviceSDK: "iphoneos", simulatorSDK: "iphonesimulator",
|
||||
buildSetting: "IPHONEOS_DEPLOYMENT_TARGET")
|
||||
raiseToFloor(deploymentTargets.tvOS, floor: "15.0",
|
||||
deviceSDK: "appletvos", simulatorSDK: "appletvsimulator",
|
||||
buildSetting: "TVOS_DEPLOYMENT_TARGET")
|
||||
raiseToFloor(deploymentTargets.watchOS, floor: "9.0",
|
||||
deviceSDK: "watchos", simulatorSDK: "watchsimulator",
|
||||
buildSetting: "WATCHOS_DEPLOYMENT_TARGET")
|
||||
|
||||
if let iOS = deploymentTargets.iOS, iOS.rc_isVersionBelow("15.0") {
|
||||
overrides["IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx27*]"] = "15.0"
|
||||
}
|
||||
|
||||
// macOS exposes a single SDK name (no separate simulator), matching the committed .xcodeproj.
|
||||
if let macOS = deploymentTargets.macOS, macOS.rc_isVersionBelow("12.0") {
|
||||
overrides["MACOSX_DEPLOYMENT_TARGET[sdk=macosx27*]"] = "12.0"
|
||||
}
|
||||
|
||||
guard !overrides.isEmpty else { return self }
|
||||
|
||||
var target = self
|
||||
var settings = target.settings ?? .settings()
|
||||
settings.base = settings.base.merging(overrides)
|
||||
target.settings = settings
|
||||
return target
|
||||
}
|
||||
}
|
||||
|
||||
extension [Target] {
|
||||
|
||||
/// Applies ``Target/addingXcodeDeploymentTargetOverrides()`` to every target in the array.
|
||||
public func addingXcodeDeploymentTargetOverrides() -> [Target] {
|
||||
map { $0.addingXcodeDeploymentTargetOverrides() }
|
||||
}
|
||||
}
|
||||
|
||||
private extension String {
|
||||
|
||||
/// Compares dotted version strings (e.g. "13.0" < "15.0") numerically, component by component.
|
||||
func rc_isVersionBelow(_ other: String) -> Bool {
|
||||
let lhs = split(separator: ".").map { Int($0) ?? 0 }
|
||||
let rhs = other.split(separator: ".").map { Int($0) ?? 0 }
|
||||
for index in 0..<Swift.max(lhs.count, rhs.count) {
|
||||
let left = index < lhs.count ? lhs[index] : 0
|
||||
let right = index < rhs.count ? rhs[index] : 0
|
||||
if left != right { return left < right }
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user