fastlane-plugin-create_simulator_devices 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (19) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +68 -0
  4. data/lib/fastlane/plugin/create_simulator_devices/actions/create_simulator_devices_action.rb +102 -0
  5. data/lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/models/apple_build_version.rb +101 -0
  6. data/lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/models/required_device.rb +26 -0
  7. data/lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/models/required_runtime.rb +44 -0
  8. data/lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/models/simctl/device.rb +45 -0
  9. data/lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/models/simctl/device_type.rb +36 -0
  10. data/lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/models/simctl/runtime.rb +114 -0
  11. data/lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/models/simctl/runtime_supported_device_type.rb +33 -0
  12. data/lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/models/xcodebuild/sdk.rb +63 -0
  13. data/lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/models.rb +17 -0
  14. data/lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/runner.rb +143 -0
  15. data/lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/runtime_helper.rb +208 -0
  16. data/lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/shell_helper.rb +181 -0
  17. data/lib/fastlane/plugin/create_simulator_devices/version.rb +7 -0
  18. data/lib/fastlane/plugin/create_simulator_devices.rb +19 -0
  19. metadata +60 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0e9e16ed0de4857cf85fa712f346be0ceda11335896fd9fea73550fdd36c1fde
4
+ data.tar.gz: 6d1e956f7127169be6bc8505c543313be366be0f41063a417459a512801127ab
5
+ SHA512:
6
+ metadata.gz: bd836c8046d442771c35103fcb4501f5a811939f1cc66e9d46f80f55b87e29facaa999fcca5c7841457461eb9f9297a1ca6526b5b2c7a5d5f37e3d5b74a0ae9b
7
+ data.tar.gz: 9f26874142099e933d8eb96da7d30b51f078fcce9b472501c356b653b2e06f37c9e0a0e8fd5ca36db1ebb25086b3e50acd970cd6de83fc1d9d2179d123831648
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 MacPaw Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # create_simulator_devices plugin
2
+
3
+ [![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](https://rubygems.org/gems/fastlane-plugin-create_simulator_devices)
4
+
5
+ ## About create_simulator_devices
6
+
7
+ This action creates simulator devices.
8
+
9
+ Usage sample:
10
+
11
+ ```ruby
12
+ available_simulators_list = create_simulator_devices(
13
+ devices: ['iPhone 15 (17.0)', 'iPhone 16', 'iPhone 14 (16.3)']
14
+ verbose: false
15
+ )
16
+ # or
17
+ available_simulators_list = create_simulator_devices(
18
+ devices: 'iPhone 15 (17.0), iPhone 16,iPhone 14 (16.3)'
19
+ verbose: false
20
+ )
21
+ # Then run tests with a available_simulators_list
22
+ run_tests(
23
+ devices: available_simulators_list
24
+ )
25
+ ```
26
+
27
+ ## Getting Started
28
+
29
+ This project is a [_fastlane_](https://github.com/fastlane/fastlane) plugin. To get started with `fastlane-plugin-create_simulator_devices`, add it to your project by running:
30
+
31
+ ```bash
32
+ fastlane add_plugin create_simulator_devices
33
+ ```
34
+
35
+ ## Example
36
+
37
+ Check out the [example `Fastfile`](fastlane/Fastfile) to see how to use this plugin. Try it by cloning the repo, running `fastlane install_plugins` and `bundle exec fastlane test`.
38
+
39
+ # Contributions
40
+
41
+ ## Run tests for this plugin
42
+
43
+ To run both the tests, and code style validation, run
44
+
45
+ ```
46
+ rake
47
+ ```
48
+
49
+ To automatically fix many of the styling issues, use
50
+ ```
51
+ rubocop -a
52
+ ```
53
+
54
+ ## Issues and Feedback
55
+
56
+ For any other issues and feedback about this plugin, please submit it to this repository.
57
+
58
+ ## Troubleshooting
59
+
60
+ If you have trouble using plugins, check out the [Plugins Troubleshooting](https://docs.fastlane.tools/plugins/plugins-troubleshooting/) guide.
61
+
62
+ ## Using _fastlane_ Plugins
63
+
64
+ For more information about how the `fastlane` plugin system works, check out the [Plugins documentation](https://docs.fastlane.tools/plugins/create-plugin/).
65
+
66
+ ## About _fastlane_
67
+
68
+ _fastlane_ is the easiest way to automate beta deployments and releases for your iOS and Android apps. To learn more, check out [fastlane.tools](https://fastlane.tools).
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fastlane'
4
+ require 'spaceship'
5
+ require_relative '../helpers/create_simulator_devices/runner'
6
+ require_relative '../helpers/create_simulator_devices/models'
7
+
8
+ module Fastlane
9
+ module Actions
10
+ module SharedValues
11
+ AVAILABLE_SIMULATOR_DEVICES = :AVAILABLE_SIMULATOR_DEVICES
12
+ end
13
+
14
+ CreateSimulatorDevices = ::Fastlane::CreateSimulatorDevices
15
+
16
+ # Create simulator devices.
17
+ class CreateSimulatorDevicesAction < Fastlane::Action
18
+ UI = ::Fastlane::UI unless defined?(UI)
19
+
20
+ attr_accessor :shell_helper
21
+
22
+ def self.run(params)
23
+ verbose = params[:verbose]
24
+ params[:devices] = params[:devices].split(',').map(&:strip) if params[:devices].is_a?(String)
25
+ required_devices = params[:devices]
26
+ UI.user_error!('No devices specified') if required_devices.nil? || required_devices.empty?
27
+
28
+ shell_helper = CreateSimulatorDevices::ShellHelper.new(verbose:, action_context: self)
29
+ runtime_helper = CreateSimulatorDevices::RuntimeHelper.new(cache_dir: params[:cache_dir], shell_helper:, verbose:)
30
+
31
+ runner = CreateSimulatorDevices::Runner.new(
32
+ runtime_helper: runtime_helper,
33
+ shell_helper: shell_helper,
34
+ verbose: verbose
35
+ )
36
+
37
+ available_simulator_devices = runner.run(required_devices)
38
+
39
+ Actions.lane_context[SharedValues::AVAILABLE_SIMULATOR_DEVICES] = available_simulator_devices
40
+
41
+ available_simulator_devices
42
+ end
43
+
44
+ #####################################################
45
+ # @!group Documentation
46
+ #####################################################
47
+
48
+ def self.description
49
+ 'Creates simulator devices and installs missing runtimes if needed'
50
+ end
51
+
52
+ def self.details
53
+ "This action does it best to create simulator devices.
54
+
55
+ Usage sample:
56
+
57
+ available_simulators_list = create_simulator_devices(
58
+ devices: ['iPhone 15 (17.0)', 'iPhone 16', 'iPhone 14 (16.3)']
59
+ verbose: false
60
+ )"
61
+ end
62
+
63
+ def self.available_options
64
+ [
65
+ ::FastlaneCore::ConfigItem.new(key: :devices,
66
+ env_name: 'SCAN_DEVICES',
67
+ description: 'A list of simulator devices to install (e.g. "iPhone 16")',
68
+ is_string: false,
69
+ default_value: 'iPhone 16'),
70
+ ::FastlaneCore::ConfigItem.new(key: :cache_dir,
71
+ description: 'The directory to cache the simulator runtimes',
72
+ is_string: true,
73
+ default_value: "#{Dir.home}/.cache/create_simulator_devices"),
74
+ ::FastlaneCore::ConfigItem.new(key: :verbose,
75
+ env_name: 'VERBOSE',
76
+ description: 'Verbose output',
77
+ is_string: false,
78
+ default_value: ::FastlaneCore::Globals.verbose?,
79
+ default_value_dynamic: true)
80
+ ]
81
+ end
82
+
83
+ def self.output
84
+ [
85
+ ['AVAILABLE_SIMULATOR_DEVICES', 'A list of available simulator devices']
86
+ ]
87
+ end
88
+
89
+ def self.return_value
90
+ 'Returns a list of available simulator devices'
91
+ end
92
+
93
+ def self.authors
94
+ ['nekrich']
95
+ end
96
+
97
+ def self.is_supported?(_platform) # rubocop:disable Naming/PredicatePrefix
98
+ true
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fastlane
4
+ # Create simulator devices.
5
+ module CreateSimulatorDevices
6
+ # Compare model for Apple build versions.
7
+ class AppleBuildVersion
8
+ def initialize(build_version)
9
+ return if build_version.nil? || build_version.empty?
10
+
11
+ @build_version = build_version
12
+ end
13
+
14
+ def beta?
15
+ @build_version.length >= 8
16
+ end
17
+
18
+ def <=>(other)
19
+ lhs_build_version = @build_version
20
+ rhs_build_version = other.to_s
21
+
22
+ return 0 if lhs_build_version == rhs_build_version
23
+
24
+ lhs_sdk_version = lhs_build_version[0...3]
25
+ rhs_sdk_version = rhs_build_version[0...3]
26
+
27
+ # Check if the SDK major.minor versions (first 3 characters) are the same.
28
+ # If they are the same, compare only SDK versions.
29
+ return lhs_sdk_version <=> rhs_sdk_version if lhs_sdk_version != rhs_sdk_version
30
+
31
+ lhs_is_beta = beta?
32
+ # In case we compare with a string, convert it to the AppleBuildVersion object.
33
+ rhs_is_beta = AppleBuildVersion.new(rhs_build_version).beta?
34
+
35
+ return lhs_is_beta ? -1 : 1 if lhs_is_beta != rhs_is_beta
36
+
37
+ # If the build versions are the same length, compare them lexicographically.
38
+ # Otherwise, compare the length of the build versions.
39
+ if lhs_build_version.length == rhs_build_version.length
40
+ lhs_build_version <=> rhs_build_version
41
+ else
42
+ # iOS 16.0 Release: 22A3362
43
+ # iOS 16.0 RC1: 22A348
44
+ # Trying to compare them lexicographically will return false ("22A3362" < "22A348").
45
+ # So we need to compare the length of the build versions.
46
+ lhs_build_version.length <=> rhs_build_version.length
47
+ end
48
+ end
49
+
50
+ def almost_equal?(other)
51
+ return false if other.nil?
52
+
53
+ lhs_build_version = @build_version
54
+ rhs_build_version = other.to_s
55
+
56
+ lhs_is_beta = beta?
57
+ rhs_is_beta = AppleBuildVersion.new(rhs_build_version).beta?
58
+
59
+ return self == other unless lhs_is_beta && rhs_is_beta && lhs_build_version.length == rhs_build_version.length
60
+
61
+ lhs_build_version.chop == rhs_build_version.chop
62
+ end
63
+
64
+ def <(other)
65
+ (self <=> other).negative?
66
+ end
67
+
68
+ def >(other)
69
+ (self <=> other).positive?
70
+ end
71
+
72
+ def <=(other)
73
+ !(self <=> other).positive?
74
+ end
75
+
76
+ def >=(other)
77
+ !(self <=> other).negative?
78
+ end
79
+
80
+ def eql?(other)
81
+ (self <=> other).zero?
82
+ end
83
+
84
+ def eq?(other)
85
+ eql?(other)
86
+ end
87
+
88
+ def ==(other)
89
+ eql?(other)
90
+ end
91
+
92
+ def hash
93
+ @build_version.hash
94
+ end
95
+
96
+ def to_s
97
+ @build_version
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fastlane
4
+ module CreateSimulatorDevices
5
+ # Represents a required runtime.
6
+ class RequiredDevice
7
+ attr_accessor :device_type, :os_name, :required_runtime, :available_runtime, :available_device
8
+
9
+ def initialize(device_type:, os_name:, required_runtime:, available_runtime:, available_device:)
10
+ self.device_type = device_type
11
+ self.os_name = os_name
12
+ self.required_runtime = required_runtime
13
+ self.available_runtime = available_runtime
14
+ self.available_device = available_device
15
+ end
16
+
17
+ def description
18
+ if required_runtime
19
+ "#{device_type.name} (#{required_runtime.product_version})"
20
+ else
21
+ device_type.name
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fastlane
4
+ module CreateSimulatorDevices
5
+ # Represents a required runtime.
6
+ class RequiredRuntime
7
+ attr_accessor :sdk_platform, :os_name, :product_version, :product_build_version, :is_latest
8
+
9
+ def initialize(sdk_platform:, os_name:, product_version:, product_build_version:, is_latest: false)
10
+ self.sdk_platform = sdk_platform
11
+ self.os_name = os_name
12
+ self.product_version = product_version
13
+ self.product_build_version = product_build_version
14
+ self.is_latest = is_latest
15
+ end
16
+
17
+ def runtime_name
18
+ [os_name, product_version].compact.join(' ')
19
+ end
20
+
21
+ def latest?
22
+ is_latest
23
+ end
24
+
25
+ def beta?
26
+ return false unless product_build_version
27
+
28
+ product_build_version.beta?
29
+ end
30
+
31
+ def eql?(other)
32
+ other.sdk_platform == sdk_platform && other.os_name == os_name && other.product_version == product_version && other.product_build_version == product_build_version
33
+ end
34
+
35
+ def hash
36
+ [sdk_platform, os_name, product_version, product_build_version].compact.hash
37
+ end
38
+
39
+ def description
40
+ "#{os_name} (#{product_version}, #{product_build_version})"
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fastlane
4
+ module CreateSimulatorDevices
5
+ module SimCTL
6
+ # Represents a device.
7
+ class Device
8
+ attr_accessor :udid, :name, :device_type_identifier, :available
9
+
10
+ def initialize(name:, udid:, device_type_identifier:, available:)
11
+ self.name = name
12
+ self.udid = udid
13
+ self.device_type_identifier = device_type_identifier
14
+ self.available = available
15
+ end
16
+
17
+ def available?
18
+ available
19
+ end
20
+
21
+ def self.from_hash(hash)
22
+ new(
23
+ name: hash[:name],
24
+ udid: hash[:udid],
25
+ device_type_identifier: hash[:deviceTypeIdentifier],
26
+ available: hash[:isAvailable]
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ # Example of a device object from `xcrun simctl list devices --json` output:
35
+ #
36
+ # {
37
+ # "dataPath": "/Users/username/Library/Developer/CoreSimulator/Devices/1C1796E3-AB33-41AB-B3EB-9836CF39E57B/data",
38
+ # "dataPathSize": 4096,
39
+ # "logPath": "/Users/username/Library/Logs/CoreSimulator/1C1796E3-AB33-41AB-B3EB-9836CF39E57B",
40
+ # "udid": "1C1796E3-AB33-41AB-B3EB-9836CF39E57B",
41
+ # "isAvailable": true,
42
+ # "deviceTypeIdentifier": "com.apple.CoreSimulator.SimDeviceType.Apple-TV-1080p",
43
+ # "state": "Shutdown",
44
+ # "name": "Apple TV"
45
+ # }
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fastlane
4
+ module CreateSimulatorDevices
5
+ module SimCTL
6
+ # Represents a device type.
7
+ class DeviceType
8
+ attr_accessor :identifier, :name, :product_family
9
+
10
+ def initialize(identifier:, name:, product_family:)
11
+ self.identifier = identifier
12
+ self.name = name
13
+ self.product_family = product_family
14
+ end
15
+
16
+ def self.from_hash(hash)
17
+ new(identifier: hash[:identifier], name: hash[:name], product_family: hash[:productFamily])
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ # Example of a device type object from `xcrun simctl list devicetypes --json` output:
25
+ #
26
+ # {
27
+ # "productFamily" : "iPhone",
28
+ # "bundlePath" : "/Library/Developer/CoreSimulator/Profiles/DeviceTypes/iPod touch (7th generation).simdevicetype",
29
+ # "maxRuntimeVersion" : 1048575,
30
+ # "maxRuntimeVersionString" : "15.255.255",
31
+ # "identifier" : "com.apple.CoreSimulator.SimDeviceType.iPod-touch--7th-generation-",
32
+ # "modelIdentifier" : "iPod9,1",
33
+ # "minRuntimeVersionString" : "12.3.1",
34
+ # "minRuntimeVersion" : 787201,
35
+ # "name" : "iPod touch (7th generation)"
36
+ # }
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'runtime_supported_device_type'
4
+ require_relative '../apple_build_version'
5
+
6
+ module Fastlane
7
+ module CreateSimulatorDevices
8
+ module SimCTL
9
+ # Represents a runtime from `xcrun simctl runtime list --json` output.
10
+ class RuntimeWithState
11
+ attr_accessor :identifier, :version, :build, :state, :deletable
12
+
13
+ def initialize(identifier:, version:, build:, state:, deletable:)
14
+ self.identifier = identifier
15
+ self.version = version
16
+ self.build = build
17
+ self.state = state
18
+ self.deletable = deletable
19
+ end
20
+
21
+ def ready?
22
+ state == 'Ready'
23
+ end
24
+
25
+ def deletable?
26
+ deletable
27
+ end
28
+
29
+ def unusable?
30
+ state == 'Unusable'
31
+ end
32
+
33
+ def self.from_hash(hash)
34
+ new(
35
+ identifier: hash[:identifier],
36
+ version: Gem::Version.new(hash[:version]),
37
+ build: AppleBuildVersion.new(hash[:build]),
38
+ state: hash[:state],
39
+ deletable: hash[:deletable]
40
+ )
41
+ end
42
+ end
43
+
44
+ # Represents a runtime from `xcrun simctl list runtimes --json` output.
45
+ class Runtime
46
+ attr_accessor :identifier, :platform, :version, :supported_device_types, :build_version, :is_available
47
+
48
+ def initialize(identifier:, platform:, version:, supported_device_types:, build_version:, is_available:) # rubocop:disable Metrics/ParameterLists
49
+ self.identifier = identifier
50
+ self.platform = platform
51
+ self.version = version
52
+ self.supported_device_types = supported_device_types
53
+ self.build_version = build_version
54
+ self.is_available = is_available
55
+ end
56
+
57
+ def self.from_hash(hash)
58
+ build_version = AppleBuildVersion.new(hash[:buildversion]) if hash[:buildversion]
59
+
60
+ new(
61
+ identifier: hash[:identifier],
62
+ platform: hash[:platform],
63
+ version: Gem::Version.new(hash[:version]),
64
+ supported_device_types: hash[:supportedDeviceTypes].map { |device_type| Runtime::SupportedDeviceType.from_hash(device_type) },
65
+ build_version:,
66
+ is_available: hash[:isAvailable]
67
+ )
68
+ end
69
+
70
+ def runtime_name
71
+ "#{[platform, version].join(' ')} (build #{build_version})"
72
+ end
73
+
74
+ def available?
75
+ is_available
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ # Example of a Runtime object from `xcrun simctl list runtimes --json` output:
83
+ #
84
+ # {
85
+ # "isAvailable": true,
86
+ # "version": "17.4",
87
+ # "isInternal": false,
88
+ # "buildversion" : "23M5279f",
89
+ # "supportedArchitectures" : [
90
+ # "arm64"
91
+ # ],
92
+ # "supportedDeviceTypes": [
93
+ # {
94
+ # "bundlePath": "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/DeviceTypes/iPhone 15.simdevicetype",
95
+ # "name": "iPhone 15",
96
+ # "identifier": "com.apple.CoreSimulator.SimDeviceType.iPhone-15",
97
+ # "productFamily": "iPhone"
98
+ # },
99
+ # {
100
+ # "bundlePath": "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/DeviceTypes/iPhone 15 Pro.simdevicetype",
101
+ # "name": "iPhone 15 Pro",
102
+ # "identifier": "com.apple.CoreSimulator.SimDeviceType.iPhone-15-Pro",
103
+ # "productFamily": "iPhone"
104
+ # }
105
+ # ],
106
+ # "identifier": "com.apple.CoreSimulator.SimRuntime.iOS-17-4",
107
+ # "platform": "iOS",
108
+ # "bundlePath": "",
109
+ # "runtimeRoot": "",
110
+ # "lastUsage": {
111
+ # "arm64" : "0001-01-01T00:00:00Z"
112
+ # },
113
+ # "name": "iOS 17.4"
114
+ # }
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fastlane
4
+ module CreateSimulatorDevices
5
+ module SimCTL
6
+ class Runtime
7
+ # Represents a supported device type by a runtime.
8
+ class SupportedDeviceType
9
+ attr_accessor :identifier, :name, :product_family
10
+
11
+ def initialize(identifier:, name:, product_family:)
12
+ self.identifier = identifier
13
+ self.name = name
14
+ self.product_family = product_family
15
+ end
16
+
17
+ def self.from_hash(hash)
18
+ new(identifier: hash[:identifier], name: hash[:name], product_family: hash[:productFamily])
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ # Example of a supported device type object from `xcrun simctl list runtimes --json` output:
27
+ #
28
+ # {
29
+ # "bundlePath": "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/DeviceTypes/iPhone 15.simdevicetype",
30
+ # "name": "iPhone 15",
31
+ # "identifier": "com.apple.CoreSimulator.SimDeviceType.iPhone-15",
32
+ # "productFamily": "iPhone"
33
+ # }
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../apple_build_version'
4
+
5
+ module Fastlane
6
+ module CreateSimulatorDevices
7
+ module Xcodebuild
8
+ # Represents a SDK.
9
+ class SDK
10
+ attr_accessor :build_id, :canonical_name, :display_name, :platform, :platform_version, :product_build_version, :sdk_version, :product_name, :product_version
11
+
12
+ def initialize(build_id:, canonical_name:, display_name:, platform:, platform_version:, sdk_version:, product_name:, product_version:, product_build_version:) # rubocop:disable Metrics/ParameterLists
13
+ self.build_id = build_id
14
+ self.canonical_name = canonical_name
15
+ self.display_name = display_name
16
+ self.platform = platform
17
+ self.platform_version = platform_version
18
+ self.sdk_version = sdk_version
19
+ self.product_name = product_name
20
+ self.product_version = product_version
21
+ self.product_build_version = product_build_version
22
+ end
23
+
24
+ def self.from_hash(hash)
25
+ product_version = Gem::Version.new(hash[:productVersion]) if hash[:productVersion]
26
+ new(
27
+ build_id: hash[:buildID],
28
+ canonical_name: hash[:canonicalName],
29
+ display_name: hash[:displayName],
30
+ platform: hash[:platform],
31
+ platform_version: Gem::Version.new(hash[:platformVersion]),
32
+ sdk_version: Gem::Version.new(hash[:sdkVersion]),
33
+ product_name: hash[:productName],
34
+ product_version: product_version,
35
+ product_build_version: AppleBuildVersion.new(hash[:productBuildVersion])
36
+ )
37
+ end
38
+
39
+ def simulator?
40
+ platform.end_with?('simulator')
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ # Example of a SDK object from `xcrun xcodebuild -showsdks -json` output:
48
+ #
49
+ # {
50
+ # "buildID": "F8821DD8-570E-11F0-99A4-5B96CBB013DB",
51
+ # "canonicalName": "watchsimulator26.0",
52
+ # "displayName": "Simulator - watchOS 26.0",
53
+ # "isBaseSdk": true,
54
+ # "platform": "watchsimulator",
55
+ # "platformPath": "/Applications/Xcode_26_beta_3.app/Contents/Developer/Platforms/WatchSimulator.platform",
56
+ # "platformVersion": "26.0",
57
+ # "productBuildVersion": "23R5307e",
58
+ # "productCopyright": "1983-2025 Apple Inc.",
59
+ # "productName": "Watch OS",
60
+ # "productVersion": "26.0",
61
+ # "sdkPath": "/Applications/Xcode_26_beta_3.app/Contents/Developer/Platforms/WatchSimulator.platform/Developer/SDKs/WatchSimulator26.0.sdk",
62
+ # "sdkVersion": "26.0"
63
+ # }
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'models/simctl/device_type'
4
+ require_relative 'models/simctl/device'
5
+ require_relative 'models/simctl/runtime'
6
+ require_relative 'models/simctl/runtime_supported_device_type'
7
+ require_relative 'models/xcodebuild/sdk'
8
+ require_relative 'models/required_device'
9
+ require_relative 'models/required_runtime'
10
+ require_relative 'models/apple_build_version'
11
+
12
+ module Fastlane
13
+ # Create simulator devices.
14
+ module CreateSimulatorDevices
15
+ UI = ::Fastlane::UI unless defined?(UI)
16
+ end
17
+ end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'runtime_helper'
4
+
5
+ module Fastlane
6
+ # Create simulator devices.
7
+ module CreateSimulatorDevices
8
+ # Does all the work to create simulator devices.
9
+ class Runner
10
+ UI = ::Fastlane::UI unless defined?(UI)
11
+
12
+ attr_accessor :shell_helper, :verbose, :runtime_helper
13
+
14
+ def initialize(runtime_helper:, shell_helper:, verbose:)
15
+ self.shell_helper = shell_helper
16
+ self.verbose = verbose || shell_helper.verbose
17
+ self.runtime_helper = runtime_helper
18
+ end
19
+
20
+ def run(devices)
21
+ shell_helper.stop_core_simulator_services
22
+
23
+ # Delete unusable runtimes and unavailable devices.
24
+ runtime_helper.delete_unusable_runtimes
25
+ delete_unavailable_devices
26
+
27
+ # Create distict required devices from a given list of device strings.
28
+ required_devices = devices
29
+ .filter_map { |device| required_device_for_device(device) }
30
+ .uniq { |required_device| [required_device.device_type.name, required_device.required_runtime.product_version] }
31
+
32
+ # Install missing runtimes if needed.
33
+ runtime_helper.install_missing_runtimes(required_devices)
34
+
35
+ # Create missing devices for required devices.
36
+ create_missing_devices(required_devices)
37
+
38
+ # Return distinct matched devices strings
39
+ matched_devices = required_devices
40
+ .reject { |required_device| required_device.available_device.nil? }
41
+ .map(&:description)
42
+
43
+ UI.user_error!('No available devices found') if matched_devices.empty?
44
+
45
+ matched_devices
46
+ end
47
+
48
+ def delete_unavailable_devices
49
+ return unless shell_helper.available_devices_for_runtimes.values.flatten.any?(&:available?)
50
+
51
+ shell_helper.delete_unavailable_devices
52
+
53
+ shell_helper.available_devices_for_runtimes(force: true)
54
+ end
55
+
56
+ def available_device_for_required_device(required_device)
57
+ return nil if required_device.available_runtime.nil?
58
+
59
+ available_devices = shell_helper.available_devices_for_runtimes[required_device.available_runtime.identifier.to_sym]
60
+
61
+ available_devices.detect { |device| device.device_type_identifier == required_device.device_type.identifier }
62
+ end
63
+
64
+ def create_missing_devices(required_devices)
65
+ find_runtime_and_device_for_required_devices(required_devices)
66
+
67
+ missing_devices = required_devices
68
+ .select { |required_device| required_device.available_device.nil? }
69
+
70
+ return if missing_devices.empty?
71
+
72
+ UI.message('Creating missing devices')
73
+ missing_devices.each do |missing_device|
74
+ shell_helper.create_device(
75
+ missing_device.description,
76
+ missing_device.device_type.identifier,
77
+ missing_device.available_runtime.identifier
78
+ )
79
+ end
80
+
81
+ shell_helper.available_devices_for_runtimes(force: true)
82
+
83
+ find_runtime_and_device_for_required_devices(missing_devices)
84
+ end
85
+
86
+ def find_runtime_and_device_for_required_devices(required_devices)
87
+ UI.message('Searching for matching available devices...')
88
+ required_devices.each do |required_device|
89
+ required_device.available_runtime = runtime_helper.available_runtime_for_required_device(required_device)
90
+ required_device.available_device = available_device_for_required_device(required_device)
91
+ end
92
+ end
93
+
94
+ PRODUCT_FAMILY_TO_OS_NAME = {
95
+ 'iPhone' => 'iOS',
96
+ 'iPad' => 'iOS',
97
+ 'iPod' => 'iOS',
98
+ 'Apple TV' => 'tvOS',
99
+ 'Apple Watch' => 'watchOS',
100
+ 'Apple Vision' => 'xrOS'
101
+ }.freeze
102
+
103
+ def required_device_for_device(device)
104
+ available_device_types = shell_helper.available_device_types
105
+
106
+ device_type = available_device_types.detect do |available_device_type|
107
+ # Avoid matching "iPhone 16" for the "iPhone 16e" device.
108
+ "#{device} ".start_with?("#{available_device_type.name} ")
109
+ end
110
+
111
+ unless device_type
112
+ UI.important("Device type not found for device #{device}")
113
+ return nil
114
+ end
115
+
116
+ product_family = device_type.product_family
117
+
118
+ os_name = PRODUCT_FAMILY_TO_OS_NAME[product_family]
119
+ device_os_version = device.delete_prefix(device_type.name).strip
120
+ device_os_version = device_os_version[/\(([\d.]*?)\)/, 1]
121
+
122
+ runtime_version = nil
123
+ unless device_os_version.nil? || device_os_version.empty?
124
+ device_os_version += '.0' if device_os_version.scan('.').none?
125
+
126
+ runtime_version = Gem::Version.new(device_os_version)
127
+ end
128
+
129
+ required_device = RequiredDevice.new(
130
+ device_type:,
131
+ os_name:,
132
+ required_runtime: nil,
133
+ available_runtime: nil,
134
+ available_device: nil
135
+ )
136
+
137
+ required_device.required_runtime = runtime_helper.required_runtime_for_device(required_device, runtime_version)
138
+
139
+ required_device.required_runtime.nil? ? nil : required_device
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require_relative 'shell_helper'
5
+
6
+ module Fastlane
7
+ module CreateSimulatorDevices
8
+ # Helper class for managing simulator runtimes.
9
+ class RuntimeHelper
10
+ UI = ::Fastlane::UI unless defined?(UI)
11
+
12
+ attr_accessor :cache_dir, :shell_helper, :verbose
13
+
14
+ def initialize(cache_dir:, shell_helper:, verbose:)
15
+ self.cache_dir = cache_dir
16
+ self.shell_helper = shell_helper
17
+ self.verbose = verbose
18
+ end
19
+
20
+ SDK_PLATFORM_TO_OS_NAME = {
21
+ 'iphonesimulator' => 'iOS',
22
+ 'appletvsimulator' => 'tvOS',
23
+ 'watchsimulator' => 'watchOS',
24
+ 'xrsimulator' => 'xrOS'
25
+ }.freeze
26
+
27
+ def delete_unusable_runtimes
28
+ deletable_runtimes = shell_helper.installed_runtimes_with_state
29
+ .select { |runtime| runtime.unusable? && runtime.deletable? }
30
+
31
+ return if deletable_runtimes.empty?
32
+
33
+ deletable_runtimes.each do |runtime|
34
+ shell_helper.delete_runtime(runtime.identifier)
35
+ end
36
+
37
+ shell_helper.available_runtimes(force: true)
38
+ end
39
+
40
+ def install_missing_runtimes(required_devices)
41
+ needed_runtimes = required_devices.filter_map(&:required_runtime).uniq
42
+
43
+ missing_runtimes = missing_runtimes(needed_runtimes)
44
+
45
+ return if missing_runtimes.empty?
46
+
47
+ missing_runtimes.each do |missing_runtime|
48
+ download_and_install_missing_runtime(missing_runtime)
49
+ end
50
+
51
+ # Update available_runtimes after installing the runtimes.
52
+ shell_helper.installed_runtimes_with_state
53
+ shell_helper.available_runtimes(force: true)
54
+
55
+ # Check if missing runtimes are available after installing
56
+ missing_runtimes = missing_runtimes(missing_runtimes)
57
+
58
+ # List missing runtimes after attempt to install the runtimes.
59
+ missing_runtimes(missing_runtimes)
60
+ .each do |missing_runtime|
61
+ UI.important("Failed to find/download/install runtime #{missing_runtime.runtime_name}")
62
+ end
63
+ end
64
+
65
+ def missing_runtimes(needed_runtimes)
66
+ needed_runtimes.select do |needed_runtime|
67
+ # Check if available runtimes contain the needed runtime.
68
+ available_runtime_matching_needed_runtime?(needed_runtime).nil?
69
+ end
70
+ end
71
+
72
+ def available_runtime_matching_needed_runtime?(needed_runtime)
73
+ matching_runtimes = shell_helper.available_runtimes
74
+ .select do |available_runtime|
75
+ next false if needed_runtime.os_name != available_runtime.platform ||
76
+ needed_runtime.product_version != available_runtime.version
77
+
78
+ needed_runtime.product_build_version = [needed_runtime.product_build_version, available_runtime.build_version].compact.max
79
+
80
+ needed_runtime.product_build_version.almost_equal?(available_runtime.build_version)
81
+ end
82
+
83
+ matching_runtimes.max_by { |available_runtime| [available_runtime.version, available_runtime.build_version] }
84
+ end
85
+
86
+ def available_runtime_for_required_device(required_device)
87
+ available_runtime = available_runtime_matching_needed_runtime?(required_device.required_runtime)
88
+
89
+ if available_runtime.nil?
90
+ UI.important("Runtime #{required_device.required_runtime.description} not found. Skipping simulator creation for #{required_device.description}...")
91
+ return nil
92
+ end
93
+
94
+ # Check if the runtime supports the device type.
95
+ if available_runtime.supported_device_types
96
+ .none? { |supported_device_type| supported_device_type.identifier == required_device.device_type.identifier }
97
+ UI.important("Device type #{required_device.device_type.name} is not supported by runtime #{available_runtime.identifier}. Skipping simulator creation for #{required_device.description}...")
98
+ return nil
99
+ end
100
+
101
+ if available_runtime.nil?
102
+ UI.important("Runtime #{required_device.required_runtime.description} not found. Skipping simulator creation for #{required_device.description}...")
103
+ return nil
104
+ end
105
+
106
+ available_runtime
107
+ end
108
+
109
+ def install_missing_runtime(missing_runtime, cached_runtime_file)
110
+ runtime_name = missing_runtime.runtime_name
111
+
112
+ if missing_runtime.product_build_version.nil?
113
+ UI.important("Failed to find runtime build version for #{runtime_name}")
114
+ return
115
+ end
116
+
117
+ shell_helper.import_runtime(cached_runtime_file, runtime_name)
118
+ end
119
+
120
+ def download_and_install_missing_runtime(missing_runtime)
121
+ UI.message("Attempting to install #{missing_runtime.runtime_name} runtime.")
122
+
123
+ downloaded_runtime_file = cached_runtime_file(missing_runtime)
124
+
125
+ if downloaded_runtime_file.nil?
126
+ shell_helper.download_runtime(missing_runtime, cache_dir)
127
+ downloaded_runtime_file = cached_runtime_file(missing_runtime)
128
+ end
129
+
130
+ shell_helper.import_runtime(downloaded_runtime_file, missing_runtime.runtime_name)
131
+ end
132
+
133
+ def runtime_build_version_for_filename(filename)
134
+ return nil unless filename
135
+
136
+ # iphonesimulator_18.4_22E238.dmg
137
+ # Format: iphonesimulator_VERSION_BUILD.dmg
138
+ build_version = File.basename(filename, '.dmg').split('_').last
139
+
140
+ AppleBuildVersion.new(build_version)
141
+ end
142
+
143
+ def cached_runtime_file(missing_runtime)
144
+ FileUtils.mkdir_p(cache_dir)
145
+
146
+ runtime_dmg_search_pattern = "#{cache_dir}/#{missing_runtime.sdk_platform}_#{missing_runtime.product_version}_"
147
+
148
+ # Remove the last character of the build version if it is the latest beta.
149
+ # Apple can create a new Runtime version and block product build version
150
+ # shipped with Xcode betas and use the same product version.
151
+ # E.g. Xcode 26.0 Beta 3 has iOS 26.0 (23A5287e) SDK, but
152
+ # xcodebuild downloads iphonesimulator_26.0_23A5287g.dmg as latest.
153
+ runtime_dmg_search_pattern += missing_runtime.product_build_version.to_s.chop if missing_runtime.product_build_version
154
+ runtime_dmg_search_pattern += '*.dmg'
155
+
156
+ runtime_file = Dir
157
+ .glob(runtime_dmg_search_pattern)
158
+ .max_by { |filename| runtime_build_version_for_filename(filename) }
159
+
160
+ return nil if runtime_file.nil?
161
+
162
+ missing_runtime.product_build_version ||= runtime_build_version_for_filename(runtime_file)
163
+
164
+ UI.message("Found existing #{missing_runtime.runtime_name} runtime image in #{cache_dir}: #{runtime_file}")
165
+
166
+ runtime_file
167
+ end
168
+
169
+ def required_runtime_for_device(required_device, runtime_version)
170
+ sdk = max_available_simulator_sdks[required_device.os_name]
171
+
172
+ # If the runtime version is the same as the SDK version, use the SDK build version.
173
+ # This will allow to use different runtimes for the same version but different Xcode beta versions.
174
+ product_build_version = sdk.product_build_version if runtime_version.nil? || sdk.product_version == runtime_version
175
+
176
+ if !runtime_version.nil? && runtime_version > sdk.product_version
177
+ UI.important("Runtime version for #{required_device.device_type.name} (#{runtime_version}) is higher than maximum supported by the Xcode: #{sdk.product_version}")
178
+ return nil
179
+ end
180
+
181
+ RequiredRuntime.new(
182
+ sdk_platform: sdk.platform,
183
+ os_name: required_device.os_name,
184
+ product_version: runtime_version || sdk.product_version,
185
+ product_build_version:,
186
+ is_latest: sdk.product_build_version.almost_equal?(product_build_version)
187
+ )
188
+ end
189
+
190
+ # Returns a hash where key is platform string and value is sdk version.
191
+ def max_available_simulator_sdks
192
+ return @max_available_simulator_sdks unless @max_available_simulator_sdks.nil?
193
+
194
+ @max_available_simulator_sdks = shell_helper.available_sdks
195
+ # Only simulators
196
+ .filter { |sdk| sdk.platform.include?('simulator') }
197
+ # Calculate max version for each product name
198
+ .each_with_object({}) do |sdk, sdk_versions|
199
+ os_name = SDK_PLATFORM_TO_OS_NAME[sdk.platform]
200
+ stored_sdk = sdk_versions[os_name]
201
+ sdk_versions[os_name] = sdk if stored_sdk.nil? || sdk.product_version > stored_sdk.product_version
202
+ end
203
+
204
+ @max_available_simulator_sdks
205
+ end
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'models'
4
+
5
+ module Fastlane
6
+ # Create simulator devices.
7
+ module CreateSimulatorDevices
8
+ # Shell helper
9
+ class ShellHelper
10
+ UI = ::Fastlane::UI unless defined?(UI)
11
+
12
+ # Proprty verbose
13
+ attr_accessor :verbose, :action_context
14
+
15
+ def initialize(verbose: false, action_context: nil)
16
+ self.verbose = verbose
17
+ self.action_context = action_context
18
+ end
19
+
20
+ def sh(command:, print_command: verbose, print_command_output: verbose)
21
+ if action_context
22
+ action_context.sh(command, print_command:, print_command_output:)
23
+ else
24
+ # Fallback for testing or direct usage
25
+ require 'open3'
26
+ stdout, stderr, status = Open3.capture3(command)
27
+
28
+ UI.message command if print_command
29
+
30
+ if status.success?
31
+ UI.message stdout if print_command_output
32
+ stdout
33
+ else
34
+ error_message = "Command failed: #{command}\n#{stderr}"
35
+ UI.error error_message
36
+ raise StandardError, error_message
37
+ end
38
+ end
39
+ end
40
+
41
+ def stop_core_simulator_services
42
+ UI.message('Stop CoreSimulator')
43
+ services_to_stop = [
44
+ 'com.apple.CoreSimulator.CoreSimulatorService',
45
+ 'com.apple.CoreSimulator.SimulatorTrampoline',
46
+ 'com.apple.CoreSimulator.SimLaunchHost-arm64',
47
+ 'com.apple.CoreSimulator.SimLaunchHost-x86'
48
+ ]
49
+ services_to_stop.each do |service|
50
+ sh(command: "launchctl remove #{service} || true")
51
+ end
52
+ end
53
+
54
+ def available_device_types(force: false)
55
+ return @available_device_types unless force || @available_device_types.nil?
56
+
57
+ UI.message('Fetching available device types...')
58
+ json = sh(command: 'xcrun simctl list --json --no-escape-slashes devicetypes')
59
+
60
+ @available_device_types = JSON
61
+ .parse(json, symbolize_names: true)[:devicetypes]
62
+ .map { |device_type| SimCTL::DeviceType.from_hash(device_type) }
63
+
64
+ @available_device_types
65
+ end
66
+
67
+ def delete_device(udid)
68
+ UI.message("Deleting device #{udid}...")
69
+ sh(command: "xcrun simctl delete #{udid.shellescape}")
70
+ end
71
+
72
+ def delete_unavailable_devices
73
+ UI.message('Deleting unavailable devices...')
74
+ sh(command: 'xcrun simctl delete unavailable')
75
+ end
76
+
77
+ def available_devices_for_runtimes(force: false)
78
+ return @available_devices_for_runtimes unless force || @available_devices_for_runtimes.nil?
79
+
80
+ UI.message('Fetching available devices...')
81
+ json = sh(command: 'xcrun simctl list --json --no-escape-slashes devices')
82
+
83
+ @available_devices_for_runtimes = JSON
84
+ .parse(json, symbolize_names: true)[:devices]
85
+ .transform_values { |devices| devices.map { |device| SimCTL::Device.from_hash(device) } }
86
+
87
+ @available_devices_for_runtimes
88
+ end
89
+
90
+ def available_runtimes(force: false)
91
+ return @available_runtimes unless force || @available_runtimes.nil?
92
+
93
+ UI.message('Fetching available runtimes...')
94
+ json = sh(command: 'xcrun simctl list --json --no-escape-slashes runtimes')
95
+
96
+ @available_runtimes = JSON
97
+ .parse(json, symbolize_names: true)[:runtimes]
98
+ .map { |runtime| SimCTL::Runtime.from_hash(runtime) }
99
+ .select(&:available?)
100
+
101
+ @available_runtimes
102
+ end
103
+
104
+ def installed_runtimes_with_state
105
+ UI.message('Fetching runtimes with state...')
106
+ json = sh(command: 'xcrun simctl runtime list --json')
107
+
108
+ JSON
109
+ .parse(json, symbolize_names: true)
110
+ .map { |_, runtime| SimCTL::RuntimeWithState.from_hash(runtime) }
111
+ end
112
+
113
+ def available_sdks(force: false)
114
+ return @available_sdks unless force || @available_sdks.nil?
115
+
116
+ UI.message('Fetching available sdks...')
117
+ json = sh(command: 'xcrun xcodebuild -showsdks -json')
118
+
119
+ @available_sdks = JSON
120
+ .parse(json, symbolize_names: true)
121
+ .map { |sdk| Xcodebuild::SDK.from_hash(sdk) }
122
+
123
+ @available_sdks
124
+ end
125
+
126
+ def create_device(name, device_type_identifier, runtime_identifier)
127
+ UI.message("Creating device #{name}")
128
+ sh(command: "xcrun simctl create #{name.shellescape} #{device_type_identifier.shellescape} #{runtime_identifier.shellescape}")
129
+ end
130
+
131
+ def delete_runtime(runtime_identifier)
132
+ UI.message("Deleting runtime #{runtime_identifier}...")
133
+ sh(command: "xcrun simctl runtime delete #{runtime_identifier.shellescape}")
134
+ end
135
+
136
+ def download_runtime(missing_runtime, cache_dir)
137
+ UI.message("Downloading #{missing_runtime.runtime_name} to #{cache_dir}. This may take a while...")
138
+
139
+ command = [
140
+ 'xcrun',
141
+ 'xcodebuild',
142
+ '-verbose',
143
+
144
+ '-exportPath',
145
+ cache_dir.shellescape,
146
+
147
+ '-downloadPlatform',
148
+ missing_runtime.os_name.shellescape
149
+ ]
150
+
151
+ unless missing_runtime.latest?
152
+ command << '-buildVersion'
153
+ # Prefer the build version if available, otherwise use the product version.
154
+ command << (missing_runtime.product_build_version || missing_runtime.product_version.to_s).shellescape
155
+ end
156
+
157
+ sh(command: command.join(' '), print_command: true, print_command_output: true)
158
+ end
159
+
160
+ def import_runtime(runtime_dmg_filename, runtime_name)
161
+ UI.message("Importing runtime #{runtime_name} image from #{runtime_dmg_filename}...")
162
+ import_platform_command = "xcrun xcodebuild -verbose -importPlatform #{runtime_dmg_filename.shellescape}"
163
+ begin
164
+ sh(command: import_platform_command)
165
+ rescue StandardError => e
166
+ UI.important("Failed to import runtime #{runtime_name} with '#{import_platform_command}' :\n#{e}")
167
+ end
168
+ end
169
+
170
+ # def add_runtime(runtime_dmg_filename, runtime_name)
171
+ # UI.message("Adding runtime #{runtime_name}...")
172
+ # add_runtime_command = "xcrun simctl runtime add #{runtime_dmg_filename.shellescape}"
173
+ # begin
174
+ # sh(command: add_runtime_command)
175
+ # rescue StandardError => e
176
+ # UI.important("Failed to add runtime #{runtime_name} with '#{add_runtime_command}':\n#{e}")
177
+ # end
178
+ # end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fastlane
4
+ module CreateSimulatorDevices
5
+ VERSION = '0.0.1'
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fastlane/plugin/create_simulator_devices/version'
4
+
5
+ module Fastlane
6
+ # Creates missing simulator devices.
7
+ module CreateSimulatorDevices
8
+ # Return all .rb files inside the "actions" and "helper" directory
9
+ def self.all_classes
10
+ Dir[File.expand_path('**/{actions,helpers}/*.rb', File.dirname(__FILE__))]
11
+ end
12
+ end
13
+ end
14
+
15
+ # By default we want to import all available actions and helpers
16
+ # A plugin can contain any number of actions and plugins
17
+ Fastlane::CreateSimulatorDevices.all_classes.each do |current|
18
+ require current
19
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fastlane-plugin-create_simulator_devices
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Vitalii Budnik
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-07-17 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email: developers@setapp.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - LICENSE
20
+ - README.md
21
+ - lib/fastlane/plugin/create_simulator_devices.rb
22
+ - lib/fastlane/plugin/create_simulator_devices/actions/create_simulator_devices_action.rb
23
+ - lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/models.rb
24
+ - lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/models/apple_build_version.rb
25
+ - lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/models/required_device.rb
26
+ - lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/models/required_runtime.rb
27
+ - lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/models/simctl/device.rb
28
+ - lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/models/simctl/device_type.rb
29
+ - lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/models/simctl/runtime.rb
30
+ - lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/models/simctl/runtime_supported_device_type.rb
31
+ - lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/models/xcodebuild/sdk.rb
32
+ - lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/runner.rb
33
+ - lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/runtime_helper.rb
34
+ - lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/shell_helper.rb
35
+ - lib/fastlane/plugin/create_simulator_devices/version.rb
36
+ homepage:
37
+ licenses:
38
+ - MIT
39
+ metadata:
40
+ rubygems_mfa_required: 'true'
41
+ post_install_message:
42
+ rdoc_options: []
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 3.1.1
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ requirements: []
56
+ rubygems_version: 3.3.7
57
+ signing_key:
58
+ specification_version: 4
59
+ summary: Fastlane plugin to create simulator devices
60
+ test_files: []