Class: Fastlane::CreateSimulatorDevices::RuntimeHelper

Inherits:
Object
  • Object
show all
Defined in:
lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/runtime_helper.rb

Overview

Helper class for managing simulator runtimes.

Constant Summary collapse

UI =
::Fastlane::UI
SDK_PLATFORM_TO_OS_NAME =
{
  'iphonesimulator' => 'iOS',
  'appletvsimulator' => 'tvOS',
  'watchsimulator' => 'watchOS',
  'xrsimulator' => 'xrOS'
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(cache_dir:, shell_helper:, verbose:) ⇒ RuntimeHelper

Returns a new instance of RuntimeHelper.



14
15
16
17
18
# File 'lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/runtime_helper.rb', line 14

def initialize(cache_dir:, shell_helper:, verbose:)
  self.cache_dir = cache_dir
  self.shell_helper = shell_helper
  self.verbose = verbose
end

Instance Attribute Details

#cache_dirObject

Returns the value of attribute cache_dir.



12
13
14
# File 'lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/runtime_helper.rb', line 12

def cache_dir
  @cache_dir
end

#shell_helperObject

Returns the value of attribute shell_helper.



12
13
14
# File 'lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/runtime_helper.rb', line 12

def shell_helper
  @shell_helper
end

#verboseObject

Returns the value of attribute verbose.



12
13
14
# File 'lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/runtime_helper.rb', line 12

def verbose
  @verbose
end

Instance Method Details

#available_runtime_for_required_device(required_device) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/runtime_helper.rb', line 86

def available_runtime_for_required_device(required_device)
  available_runtime = available_runtime_matching_needed_runtime?(required_device.required_runtime)

  if available_runtime.nil?
    UI.important("Runtime #{required_device.required_runtime.description} not found. Skipping simulator creation for #{required_device.description}...")
    return nil
  end

  # Check if the runtime supports the device type.
  if available_runtime.supported_device_types
      .none? { |supported_device_type| supported_device_type.identifier == required_device.device_type.identifier }
    UI.important("Device type #{required_device.device_type.name} is not supported by runtime #{available_runtime.identifier}. Skipping simulator creation for #{required_device.description}...")
    return nil
  end

  if available_runtime.nil?
    UI.important("Runtime #{required_device.required_runtime.description} not found. Skipping simulator creation for #{required_device.description}...")
    return nil
  end

  available_runtime
end

#available_runtime_matching_needed_runtime?(needed_runtime) ⇒ Boolean

Returns:

  • (Boolean)


72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/runtime_helper.rb', line 72

def available_runtime_matching_needed_runtime?(needed_runtime)
  matching_runtimes = shell_helper.available_runtimes
    .select do |available_runtime|
      next false if needed_runtime.os_name != available_runtime.platform ||
                    needed_runtime.product_version != available_runtime.version

      needed_runtime.product_build_version = [needed_runtime.product_build_version, available_runtime.build_version].compact.max

      needed_runtime.product_build_version.almost_equal?(available_runtime.build_version)
    end

  matching_runtimes.max_by { |available_runtime| [available_runtime.version, available_runtime.build_version] }
end

#cached_runtime_file(missing_runtime) ⇒ Object



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/runtime_helper.rb', line 143

def cached_runtime_file(missing_runtime)
  FileUtils.mkdir_p(cache_dir)

  runtime_dmg_search_pattern = "#{cache_dir}/#{missing_runtime.sdk_platform}_#{missing_runtime.product_version}_"

  # Remove the last character of the build version if it is the latest beta.
  # Apple can create a new Runtime version and block product build version
  # shipped with Xcode betas and use the same product version.
  # E.g. Xcode 26.0 Beta 3 has iOS 26.0 (23A5287e) SDK, but
  # xcodebuild downloads iphonesimulator_26.0_23A5287g.dmg as latest.
  runtime_dmg_search_pattern += missing_runtime.product_build_version.to_s.chop if missing_runtime.product_build_version
  runtime_dmg_search_pattern += '*.dmg'

  runtime_file = Dir
    .glob(runtime_dmg_search_pattern)
    .max_by { |filename| runtime_build_version_for_filename(filename) }

  return nil if runtime_file.nil?

  missing_runtime.product_build_version ||= runtime_build_version_for_filename(runtime_file)

  UI.message("Found existing #{missing_runtime.runtime_name} runtime image in #{cache_dir}: #{runtime_file}")

  runtime_file
end

#delete_unusable_runtimesObject



27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/runtime_helper.rb', line 27

def delete_unusable_runtimes
  deletable_runtimes = shell_helper.installed_runtimes_with_state
    .select { |runtime| runtime.unusable? && runtime.deletable? }

  return if deletable_runtimes.empty?

  deletable_runtimes.each do |runtime|
    shell_helper.delete_runtime(runtime.identifier)
  end

  shell_helper.available_runtimes(force: true)
end

#download_and_install_missing_runtime(missing_runtime) ⇒ Object



120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/runtime_helper.rb', line 120

def download_and_install_missing_runtime(missing_runtime)
  UI.message("Attempting to install #{missing_runtime.runtime_name} runtime.")

  downloaded_runtime_file = cached_runtime_file(missing_runtime)

  if downloaded_runtime_file.nil?
    shell_helper.download_runtime(missing_runtime, cache_dir)
    downloaded_runtime_file = cached_runtime_file(missing_runtime)
  end

  shell_helper.import_runtime(downloaded_runtime_file, missing_runtime.runtime_name)
end

#install_missing_runtime(missing_runtime, cached_runtime_file) ⇒ Object



109
110
111
112
113
114
115
116
117
118
# File 'lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/runtime_helper.rb', line 109

def install_missing_runtime(missing_runtime, cached_runtime_file)
  runtime_name = missing_runtime.runtime_name

  if missing_runtime.product_build_version.nil?
    UI.important("Failed to find runtime build version for #{runtime_name}")
    return
  end

  shell_helper.import_runtime(cached_runtime_file, runtime_name)
end

#install_missing_runtimes(required_devices) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/runtime_helper.rb', line 40

def install_missing_runtimes(required_devices)
  needed_runtimes = required_devices.filter_map(&:required_runtime).uniq

  missing_runtimes = missing_runtimes(needed_runtimes)

  return if missing_runtimes.empty?

  missing_runtimes.each do |missing_runtime|
    download_and_install_missing_runtime(missing_runtime)
  end

  # Update available_runtimes after installing the runtimes.
  shell_helper.installed_runtimes_with_state
  shell_helper.available_runtimes(force: true)

  # Check if missing runtimes are available after installing
  missing_runtimes = missing_runtimes(missing_runtimes)

  # List missing runtimes after attempt to install the runtimes.
  missing_runtimes(missing_runtimes)
    .each do |missing_runtime|
      UI.important("Failed to find/download/install runtime #{missing_runtime.runtime_name}")
    end
end

#max_available_simulator_sdksObject

Returns a hash where key is platform string and value is sdk version.



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/runtime_helper.rb', line 191

def max_available_simulator_sdks
  return @max_available_simulator_sdks unless @max_available_simulator_sdks.nil?

  @max_available_simulator_sdks = shell_helper.available_sdks
    # Only simulators
    .filter { |sdk| sdk.platform.include?('simulator') }
    # Calculate max version for each product name
    .each_with_object({}) do |sdk, sdk_versions|
      os_name = SDK_PLATFORM_TO_OS_NAME[sdk.platform]
      stored_sdk = sdk_versions[os_name]
      sdk_versions[os_name] = sdk if stored_sdk.nil? || sdk.product_version > stored_sdk.product_version
    end

  @max_available_simulator_sdks
end

#missing_runtimes(needed_runtimes) ⇒ Object



65
66
67
68
69
70
# File 'lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/runtime_helper.rb', line 65

def missing_runtimes(needed_runtimes)
  needed_runtimes.select do |needed_runtime|
    # Check if available runtimes contain the needed runtime.
    available_runtime_matching_needed_runtime?(needed_runtime).nil?
  end
end

#required_runtime_for_device(required_device, runtime_version) ⇒ Object



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/runtime_helper.rb', line 169

def required_runtime_for_device(required_device, runtime_version)
  sdk = max_available_simulator_sdks[required_device.os_name]

  # If the runtime version is the same as the SDK version, use the SDK build version.
  # This will allow to use different runtimes for the same version but different Xcode beta versions.
  product_build_version = sdk.product_build_version if runtime_version.nil? || sdk.product_version == runtime_version

  if !runtime_version.nil? && runtime_version > sdk.product_version
    UI.important("Runtime version for #{required_device.device_type.name} (#{runtime_version}) is higher than maximum supported by the Xcode: #{sdk.product_version}")
    return nil
  end

  RequiredRuntime.new(
    sdk_platform: sdk.platform,
    os_name: required_device.os_name,
    product_version: runtime_version || sdk.product_version,
    product_build_version:,
    is_latest: sdk.product_build_version.almost_equal?(product_build_version)
  )
end

#runtime_build_version_for_filename(filename) ⇒ Object



133
134
135
136
137
138
139
140
141
# File 'lib/fastlane/plugin/create_simulator_devices/helpers/create_simulator_devices/runtime_helper.rb', line 133

def runtime_build_version_for_filename(filename)
  return nil unless filename

  # iphonesimulator_18.4_22E238.dmg
  # Format: iphonesimulator_VERSION_BUILD.dmg
  build_version = File.basename(filename, '.dmg').split('_').last

  AppleBuildVersion.new(build_version)
end