reset-simulators.rb 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  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. # CoreSimulatorService doesn't exit when sent SIGTERM
  53. system('pkill -9 Simulator 2>/dev/null')
  54. end
  55. wait_for_core_simulator_service
  56. puts ' done!'
  57. print 'Shut down existing simulator devices...'
  58. # Shut down any running simulator devices. This may take multiple attempts if some
  59. # simulators are currently in the process of booting or being created.
  60. all_available_devices = []
  61. (0..5).each do |shutdown_attempt|
  62. begin
  63. devices_json = simctl('list devices -j')[0]
  64. all_devices = JSON.parse(devices_json)['devices'].flat_map { |_, devices| devices }
  65. rescue JSON::ParserError
  66. sleep shutdown_attempt if shutdown_attempt > 0
  67. next
  68. end
  69. # Exclude devices marked as unavailable as they're from a different version of Xcode.
  70. all_available_devices = all_devices.reject { |device| device['availability'] =~ /unavailable/ }
  71. break if running_devices(all_available_devices).empty?
  72. shutdown_simulator_devices all_available_devices
  73. sleep shutdown_attempt if shutdown_attempt > 0
  74. end
  75. puts ' done!'
  76. # Delete all simulators.
  77. print 'Deleting all simulators...'
  78. (0..5).each do |delete_attempt|
  79. break if all_available_devices.empty?
  80. all_available_devices.each do |device|
  81. simctl("delete #{device['udid']}")
  82. end
  83. begin
  84. devices_json = simctl('list devices -j')[0]
  85. all_devices = JSON.parse(devices_json)['devices'].flat_map { |_, devices| devices }
  86. rescue JSON::ParserError
  87. sleep shutdown_attempt if shutdown_attempt > 0
  88. next
  89. end
  90. all_available_devices = all_devices.reject { |device| device['availability'] =~ /unavailable/ }
  91. break if all_available_devices.empty?
  92. end
  93. puts ' done!'
  94. if not all_available_devices.empty?
  95. raise "Failed to delete devices #{all_available_devices}"
  96. end
  97. # Recreate all simulators.
  98. runtimes = JSON.parse(simctl('list runtimes -j')[0])['runtimes']
  99. device_types = JSON.parse(simctl('list devicetypes -j')[0])['devicetypes']
  100. runtimes_by_platform = Hash.new { |hash, key| hash[key] = [] }
  101. runtimes.each do |runtime|
  102. next unless runtime['availability'] == '(available)' || runtime['isAvailable'] == true
  103. runtimes_by_platform[platform_for_runtime(runtime)] << runtime
  104. end
  105. firstOnly = prelaunch_simulator == '-firstOnly'
  106. print 'Creating fresh simulators...'
  107. device_types.each do |device_type|
  108. platform = platform_for_device_type(device_type)
  109. runtimes_by_platform[platform].each do |runtime|
  110. output, ec = simctl("create '#{device_type['name']}' '#{device_type['identifier']}' '#{runtime['identifier']}' 2>&1")
  111. if ec == 0
  112. if firstOnly
  113. # We only want to create a single simulator for each device type so
  114. # skip the rest.
  115. runtimes_by_platform[platform] = []
  116. break
  117. else
  118. next
  119. end
  120. end
  121. # Not all runtime and device pairs are valid as newer simulator runtimes
  122. # don't support older devices. The exact error code for this changes
  123. # every few versions of Xcode, so this just lists all the ones we've
  124. # seen.
  125. next if /domain=com.apple.CoreSimulator.SimError, code=(?<code>\d+)/ =~ output and [161, 162, 163, 403].include? code.to_i
  126. puts "Failed to create device of type #{device_type['identifier']} with runtime #{runtime['identifier']}:"
  127. output.each_line do |line|
  128. puts " #{line}"
  129. end
  130. end
  131. end
  132. puts ' done!'
  133. if firstOnly
  134. exit 0
  135. end
  136. if prelaunch_simulator.include? 'tvos'
  137. print 'Booting Apple TV simulator...'
  138. system("xcrun simctl boot 'Apple TV'") or raise "Failed to boot Apple TV simulator"
  139. else
  140. print 'Booting iPhone 8 simulator...'
  141. system("xcrun simctl boot 'iPhone 8'") or raise "Failed to boot iPhone 8 simulator"
  142. end
  143. puts ' done!'
  144. print 'Waiting for dyld shared cache to update...'
  145. while system('pgrep -q update_dyld_sim_shared_cache')
  146. sleep 15
  147. end
  148. puts ' done!'
  149. rescue => e
  150. if (attempts += 1) < 5
  151. puts ''
  152. puts e.message
  153. e.backtrace.each { |line| puts line }
  154. puts ''
  155. puts 'Retrying...'
  156. retry
  157. end
  158. system('ps auxwww')
  159. system('xcrun simctl list')
  160. raise
  161. end