Newer
Older
bremer-ios-app / Pods / Realm / scripts / reset-simulators.rb
#!/usr/bin/ruby

require 'json'
require 'open3'

prelaunch_simulator = ARGV[0] || ''

def platform_for_runtime(runtime)
  runtime['identifier'].gsub(/com.apple.CoreSimulator.SimRuntime.([^-]+)-.*/, '\1')
end

def platform_for_device_type(device_type)
  case device_type['identifier']
  when /Watch/
    'watchOS'
  when /TV/
    'tvOS'
  else
    'iOS'
  end
end

def simctl(args)
  # When running on a machine with Xcode 11 installed, Xcode 10 sometimes
  # incorrectly thinks that it has not completed its first-run installation.
  # This results in it printing errors related to that to stdout in front of
  # the actual JSON output that we want.
  Open3.popen3('xcrun simctl ' + args) do |stdin, stdout, strerr, wait_thr|
    while line = stdout.gets
      if not line.start_with? 'Install'
        return line + stdout.read, wait_thr.value.exitstatus
      end
    end
  end
end

def wait_for_core_simulator_service
  # Run until we get a result since switching simulator versions often causes CoreSimulatorService to throw an exception.
  while simctl('list devices')[0].empty?
  end
end

def running_devices(devices)
  devices.select { |device| device['state'] != 'Shutdown' }
end

def shutdown_simulator_devices(devices)
  # Shut down any simulators that need it.
  running_devices(devices).each do |device|
    puts "Shutting down simulator #{device['udid']}"
    system("xcrun simctl shutdown #{device['udid']}") or puts "    Failed to shut down simulator #{device['udid']}"
  end
end

attempts = 0
begin
  # Kill all the current simulator processes as they may be from a different Xcode version
  print 'Killing running Simulator processes...'
  while system('pgrep -q Simulator')
    system('pkill Simulator 2>/dev/null')
    system('pkill -9 update_dyld_sim_shared_cache 2>/dev/null')
    # CoreSimulatorService doesn't exit when sent SIGTERM
    system('pkill -9 Simulator 2>/dev/null')
  end
  wait_for_core_simulator_service
  puts ' done!'

  print 'Shut down existing simulator devices...'
  # Shut down any running simulator devices. This may take multiple attempts if some
  # simulators are currently in the process of booting or being created.
  all_available_devices = []
  (0..5).each do |shutdown_attempt|
    begin
      devices_json = simctl('list devices -j')[0]
      all_devices = JSON.parse(devices_json)['devices'].flat_map { |_, devices| devices }
    rescue JSON::ParserError
      sleep shutdown_attempt if shutdown_attempt > 0
      next
    end

    # Exclude devices marked as unavailable as they're from a different version of Xcode.
    all_available_devices = all_devices.reject { |device| device['availability'] =~ /unavailable/ }

    break if running_devices(all_available_devices).empty?

    shutdown_simulator_devices all_available_devices
    sleep shutdown_attempt if shutdown_attempt > 0
  end
  puts ' done!'

  # Delete all simulators.
  print 'Deleting all simulators...'
  (0..5).each do |delete_attempt|
    break if all_available_devices.empty?

    all_available_devices.each do |device|
      simctl("delete #{device['udid']}")
    end

    begin
      devices_json = simctl('list devices -j')[0]
      all_devices = JSON.parse(devices_json)['devices'].flat_map { |_, devices| devices }
    rescue JSON::ParserError
      sleep shutdown_attempt if shutdown_attempt > 0
      next
    end

    all_available_devices = all_devices.reject { |device| device['availability'] =~ /unavailable/ }
    break if all_available_devices.empty?
  end
  puts ' done!'

  if not all_available_devices.empty?
    raise "Failed to delete devices #{all_available_devices}"
  end

  # Recreate all simulators.
  runtimes = JSON.parse(simctl('list runtimes -j')[0])['runtimes']
  device_types = JSON.parse(simctl('list devicetypes -j')[0])['devicetypes']

  runtimes_by_platform = Hash.new { |hash, key| hash[key] = [] }
  runtimes.each do |runtime|
    next unless runtime['availability'] == '(available)' || runtime['isAvailable'] == true
    runtimes_by_platform[platform_for_runtime(runtime)] << runtime
  end

  firstOnly = prelaunch_simulator == '-firstOnly'

  print 'Creating fresh simulators...'
  device_types.each do |device_type|
    platform = platform_for_device_type(device_type)
    runtimes_by_platform[platform].each do |runtime|
      output, ec = simctl("create '#{device_type['name']}' '#{device_type['identifier']}' '#{runtime['identifier']}' 2>&1")
      if ec == 0
        if firstOnly
          # We only want to create a single simulator for each device type so
          # skip the rest.
          runtimes_by_platform[platform] = []
          break
        else
          next
        end
      end

      # Not all runtime and device pairs are valid as newer simulator runtimes
      # don't support older devices. The exact error code for this changes
      # every few versions of Xcode, so this just lists all the ones we've
      # seen.
      next if /domain=com.apple.CoreSimulator.SimError, code=(?<code>\d+)/ =~ output and [161, 162, 163, 403].include? code.to_i

      puts "Failed to create device of type #{device_type['identifier']} with runtime #{runtime['identifier']}:"
      output.each_line do |line|
        puts "    #{line}"
      end
    end
  end
  puts ' done!'

  if firstOnly
    exit 0
  end

  if prelaunch_simulator.include? 'tvos'
    print 'Booting Apple TV simulator...'
    system("xcrun simctl boot 'Apple TV'") or raise "Failed to boot Apple TV simulator"
  else
    print 'Booting iPhone 8 simulator...'
    system("xcrun simctl boot 'iPhone 8'") or raise "Failed to boot iPhone 8 simulator"
  end
  puts ' done!'

  print 'Waiting for dyld shared cache to update...'
  10.times do
    break unless system('pgrep -q update_dyld_sim_shared_cache')
    sleep 15
  end
  puts ' done!'

rescue => e
  if (attempts += 1) < 5
    puts ''
    puts e.message
    e.backtrace.each { |line| puts line }
    puts ''
    puts 'Retrying...'
    retry
  end
  system('ps auxwww')
  system('xcrun simctl list')
  raise
end