Skip to content

Commit aa15494

Browse files
authored
CI: ensure required simulators exist (#482)
1 parent e756832 commit aa15494

File tree

4 files changed

+130
-2
lines changed

4 files changed

+130
-2
lines changed

.github/workflows/ci.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,14 @@ jobs:
170170
max_attempts: 3
171171
command: sudo xcodes runtimes install '${{ matrix.runtime }}'
172172

173-
- name: "List Available Runtimes, Simulators, and Destinations"
173+
- if: ${{ matrix.platform[0] != 'macOS' }}
174+
name: Create Required Simulators
175+
run: |
176+
set -eo pipefail
177+
xcrun simctl delete all
178+
fastlane create_simulators platform:${{ matrix.platform[0] }} version:${{ matrix.platform[1] }}
179+
180+
- name: List Available Runtimes, Simulators, and Destinations
174181
run: |
175182
xcrun simctl list
176183
xcodebuild -scheme "SwiftUIIntrospect" -showdestinations

Examples/Showcase/Showcase.xcodeproj/project.pbxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@
239239
SWIFT_STRICT_CONCURRENCY = complete;
240240
SWIFT_VERSION = 6.0;
241241
TVOS_DEPLOYMENT_TARGET = 15.0;
242+
WATCHOS_DEPLOYMENT_TARGET = 8.0;
242243
XROS_DEPLOYMENT_TARGET = 1.0;
243244
};
244245
name = Debug;
@@ -304,6 +305,7 @@
304305
SWIFT_VERSION = 6.0;
305306
TVOS_DEPLOYMENT_TARGET = 15.0;
306307
VALIDATE_PRODUCT = YES;
308+
WATCHOS_DEPLOYMENT_TARGET = 8.0;
307309
XROS_DEPLOYMENT_TARGET = 1.0;
308310
};
309311
name = Release;

Sources/ViewTypes/View.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@
6060
/// ```
6161
public struct ViewType: IntrospectableViewType {}
6262

63+
// TODO: I think if Swift ever gets parameterized extensions we could introduce subtypes like:
64+
//
65+
// public struct ViewType<PlatformViewType: PlatformView>: IntrospectableViewType {}
66+
//
67+
// extension <V: PlatformView> IntrospectableViewType where Self == ViewType<V> {
68+
// public static func view<V>(ofType: V.Type) -> Self { ... }
69+
// }
70+
6371
extension IntrospectableViewType where Self == ViewType {
6472
public static var view: Self { .init() }
6573
}

fastlane/Fastfile

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,122 @@ devices = {
2424
},
2525
"visionos" => {
2626
1 => ["Apple Vision Pro (at 2732x2048) (1.2)"],
27-
2 => ["Apple Vision Pro (2.5)"],
27+
2 => ["Apple Vision Pro (at 2732x2048) (2.5)"],
2828
26 => ["Apple Vision Pro (26.0)"],
2929
},
3030
}
3131

32+
before_all do |lane, options|
33+
next if lane == :create_simulators
34+
create_simulators(platform: options[:platform], version: options[:version])
35+
end
36+
37+
lane :create_simulators do |options|
38+
require 'json'
39+
require 'set'
40+
41+
# map Fastfile platform keys to display names used by CoreSimulator runtimes
42+
platforms_to_os = {
43+
"ios" => "iOS",
44+
"tvos" => "tvOS",
45+
"watchos" => "watchOS",
46+
"visionos" => "visionOS",
47+
}
48+
49+
# Build lookup tables from CoreSimulator for robust name→identifier mapping
50+
begin
51+
list_json = sh("xcrun simctl list -j", log: false)
52+
list = JSON.parse(list_json)
53+
devtypes = list["devicetypes"] || []
54+
runtimes = list["runtimes"] || []
55+
56+
# Build a set of existing simulator names to prevent duplicates
57+
devices_json = sh("xcrun simctl list -j devices", log: false)
58+
devices_list = JSON.parse(devices_json)
59+
existing_names = (devices_list["devices"] || {}).values.flatten.map { |d| d["name"] }.compact.to_set
60+
rescue => e
61+
UI.message("Failed to read simctl lists: #{e}")
62+
devtypes = []
63+
runtimes = []
64+
existing_names = Set.new
65+
end
66+
67+
device_name_to_id = devtypes.each_with_object({}) do |dt, h|
68+
name = dt["name"]; id = dt["identifier"]
69+
h[name] = id if name && id
70+
end
71+
72+
runtime_name_to_id = runtimes.each_with_object({}) do |rt, h|
73+
next unless rt["isAvailable"]
74+
name = rt["name"]; id = rt["identifier"]
75+
h[name] = id if name && id
76+
end
77+
78+
# Fallback builders when exact matches are not present in the lookup tables
79+
build_device_type_id = proc do |device_name|
80+
s = device_name.gsub(/[()]/, '').gsub(/\s+/, '-').gsub(/[^A-Za-z0-9-]/, '')
81+
"com.apple.CoreSimulator.SimDeviceType.#{s}"
82+
end
83+
84+
build_runtime_id = proc do |os_name, version|
85+
"com.apple.CoreSimulator.SimRuntime.#{os_name}-#{version.tr('.', '-')}"
86+
end
87+
88+
platform_opt = options && options[:platform] ? options[:platform].to_s.downcase : nil
89+
version_opt = options && options[:version] ? options[:version].to_i : nil
90+
91+
local_devices = if platform_opt && devices.key?(platform_opt)
92+
subset_versions = devices[platform_opt]
93+
if version_opt && subset_versions.key?(version_opt)
94+
{ platform_opt => { version_opt => subset_versions[version_opt] } }
95+
else
96+
{ platform_opt => subset_versions }
97+
end
98+
else
99+
devices
100+
end
101+
102+
local_devices.each do |platform, versions|
103+
os_name = platforms_to_os[platform]
104+
next if os_name.nil?
105+
106+
versions.values.flatten.each do |descriptor|
107+
# descriptor examples:
108+
# "iPhone 14 Pro (16.4)"
109+
# "iPad Pro (11-inch) (4th generation) (16.4)"
110+
# "Apple Vision Pro (2.5)"
111+
begin
112+
# Parse trailing "(x.y)" and derive device name
113+
if descriptor =~ /\s*\(([^()]+)\)\s*\z/
114+
runtime_version = $1
115+
device_name = descriptor.sub(/\s*\([^()]+\)\s*\z/, '')
116+
else
117+
UI.message("Could not parse runtime version from '#{descriptor}', skipping")
118+
next
119+
end
120+
121+
runtime_name = "#{os_name} #{runtime_version}"
122+
123+
device_type_id = device_name_to_id[device_name] || build_device_type_id.call(device_name)
124+
runtime_id = runtime_name_to_id[runtime_name] || build_runtime_id.call(os_name, runtime_version)
125+
126+
# Use the device name without the version suffix as the simulator name
127+
sim_name = device_name
128+
129+
if existing_names.include?(sim_name)
130+
UI.message("Already exists: #{sim_name} (#{runtime_version}), skipping")
131+
next
132+
end
133+
134+
sh(%(xcrun simctl create "#{sim_name}" "#{device_type_id}" "#{runtime_id}" || true))
135+
existing_names.add(sim_name)
136+
rescue => e
137+
UI.message("Skipping #{descriptor}: #{e}")
138+
end
139+
end
140+
end
141+
end
142+
32143
lane :build do |options|
33144
platform = options[:platform].to_s.downcase
34145
version = options[:version].to_i

0 commit comments

Comments
 (0)