reset-simulators.rb 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. #!/usr/bin/ruby
  2. require 'json'
  3. require 'open3'
  4. prelaunch_simulator = ARGV[0] || ''
  5. def platform_for_runtime(runtime)
  6. runtime['identifier'].gsub(/com.apple.CoreSimulator.SimRuntime.([^-]+)-.*/, '\1')
  7. end
  8. def platform_for_device_type(device_type)
  9. case device_type['identifier']
  10. when /Watch/
  11. 'watchOS'
  12. when /TV/
  13. 'tvOS'
  14. else
  15. 'iOS'
  16. end
  17. end
  18. def simctl(args)
  19. # When running on a machine with Xcode 11 installed, Xcode 10 sometimes
  20. # incorrectly thinks that it has not completed its first-run installation.
  21. # This results in it printing errors related to that to stdout in front of
  22. # the actual JSON output that we want.
  23. Open3.popen3('xcrun simctl ' + args) do |stdin, stdout, strerr, wait_thr|
  24. while line = stdout.gets
  25. if not line.start_with? 'Install'
  26. return line + stdout.read, wait_thr.value.exitstatus
  27. end
  28. end
  29. end
  30. end
  31. def wait_for_core_simulator_service
  32. # Run until we get a result since switching simulator versions often causes CoreSimulatorService to throw an exception.
  33. while simctl('list devices')[0].empty?
  34. end
  35. end
  36. def running_devices(devices)
  37. devices.select { |device| device['state'] != 'Shutdown' }
  38. end
  39. def shutdown_simulator_devices(devices)
  40. # Shut down any simulators that need it.
  41. running_devices(devices).each do |device|
  42. puts "Shutting down simulator #{device['udid']}"
  43. system("xcrun simctl shutdown #{device['udid']}") or puts " Failed to shut down simulator #{device['udid']}"
  44. end
  45. end
  46. attempts = 0
  47. begin
  48. # Kill all the current simulator processes as they may be from a different Xcode version
  49. print 'Killing running Simulator processes...'
  50. while system('pgrep -q Simulator')
  51. system('pkill Simulator 2>/dev/null')
  52. system('pkill -9 update_dyld_sim_shared_cache 2>/dev/null')
  53. # CoreSimulatorService doesn't exit when sent SIGTERM
  54. system('pkill -9 Simulator 2>/dev/null')
  55. end
  56. wait_for_core_simulator_service
  57. puts ' done!'
  58. print 'Shut down existing simulator devices...'
  59. # Shut down any running simulator devices. This may take multiple attempts if some
  60. # simulators are currently in the process of booting or being created.
  61. all_available_devices = []
  62. (0..5).each do |shutdown_attempt|
  63. begin
  64. devices_json = simctl('list devices -j')[0]
  65. all_devices = JSON.parse(devices_json)['devices'].flat_map { |_, devices| devices }
  66. rescue JSON::ParserError
  67. sleep shutdown_attempt if shutdown_attempt > 0
  68. next
  69. end
  70. # Exclude devices marked as unavailable as they're from a different version of Xcode.
  71. all_available_devices = all_devices.reject { |device| device['availability'] =~ /unavailable/ }
  72. break if running_devices(all_available_devices).empty?
  73. shutdown_simulator_devices all_available_devices
  74. sleep shutdown_attempt if shutdown_attempt > 0
  75. end
  76. puts ' done!'
  77. # Delete all simulators.
  78. print 'Deleting all simulators...'
  79. (0..5).each do |delete_attempt|
  80. break if all_available_devices.empty?
  81. all_available_devices.each do |device|
  82. simctl("delete #{device['udid']}")
  83. end
  84. begin
  85. devices_json = simctl('list devices -j')[0]
  86. all_devices = JSON.parse(devices_json)['devices'].flat_map { |_, devices| devices }
  87. rescue JSON::ParserError
  88. sleep shutdown_attempt if shutdown_attempt > 0
  89. next
  90. end
  91. all_available_devices = all_devices.reject { |device| device['availability'] =~ /unavailable/ }
  92. break if all_available_devices.empty?
  93. end
  94. puts ' done!'
  95. if not all_available_devices.empty?
  96. raise "Failed to delete devices #{all_available_devices}"
  97. end
  98. # Recreate all simulators.
  99. runtimes = JSON.parse(simctl('list runtimes -j')[0])['runtimes']
  100. device_types = JSON.parse(simctl('list devicetypes -j')[0])['devicetypes']
  101. runtimes_by_platform = Hash.new { |hash, key| hash[key] = [] }
  102. runtimes.each do |runtime|
  103. next unless runtime['availability'] == '(available)' || runtime['isAvailable'] == true
  104. runtimes_by_platform[platform_for_runtime(runtime)] << runtime
  105. end
  106. firstOnly = prelaunch_simulator == '-firstOnly'
  107. print 'Creating fresh simulators...'
  108. device_types.each do |device_type|
  109. platform = platform_for_device_type(device_type)
  110. runtimes_by_platform[platform].each do |runtime|
  111. output, ec = simctl("create '#{device_type['name']}' '#{device_type['identifier']}' '#{runtime['identifier']}' 2>&1")
  112. if ec == 0
  113. if firstOnly
  114. # We only want to create a single simulator for each device type so
  115. # skip the rest.
  116. runtimes_by_platform[platform] = []
  117. break
  118. else
  119. next
  120. end
  121. end
  122. # Not all runtime and device pairs are valid as newer simulator runtimes
  123. # don't support older devices. The exact error code for this changes
  124. # every few versions of Xcode, so this just lists all the ones we've
  125. # seen.
  126. next if /domain=com.apple.CoreSimulator.SimError, code=(?<code>\d+)/ =~ output and [161, 162, 163, 403].include? code.to_i
  127. puts "Failed to create device of type #{device_type['identifier']} with runtime #{runtime['identifier']}:"
  128. output.each_line do |line|
  129. puts " #{line}"
  130. end
  131. end
  132. end
  133. puts ' done!'
  134. if firstOnly
  135. exit 0
  136. end
  137. if prelaunch_simulator.include? 'tvos'
  138. print 'Booting Apple TV simulator...'
  139. system("xcrun simctl boot 'Apple TV'") or raise "Failed to boot Apple TV simulator"
  140. else
  141. print 'Booting iPhone 11 simulator...'
  142. system("xcrun simctl boot 'iPhone 11'") or raise "Failed to boot iPhone 11 simulator"
  143. end
  144. puts ' done!'
  145. print 'Waiting for dyld shared cache to update...'
  146. 10.times do
  147. break unless system('pgrep -q update_dyld_sim_shared_cache')
  148. sleep 15
  149. end
  150. puts ' done!'
  151. rescue => e
  152. if (attempts += 1) < 5
  153. puts ''
  154. puts e.message
  155. e.backtrace.each { |line| puts line }
  156. puts ''
  157. puts 'Retrying...'
  158. retry
  159. end
  160. system('ps auxwww')
  161. system('xcrun simctl list')
  162. raise
  163. end