Module: Everywhere::Simulator

Defined in:
lib/everywhere/simulator.rb

Overview

Thin wrappers around xcrun simctl — just enough to boot a Simulator, install the stamped shell and launch it. Anything fancier (choosing devices, multiple simulators) belongs in the user's own Xcode workflow.

Class Method Summary collapse

Class Method Details

.boot_default!Object

Boot the newest available iPhone (or reuse a booted one) and bring the Simulator app to the foreground. Returns the UDID.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/everywhere/simulator.rb', line 21

def boot_default!
  if (udid = booted_udid)
    Shellout.run?("open", "-a", "Simulator", quiet: true)
    return udid
  end

  device = devices.reverse.find { |d| d["name"].to_s.match?(/\AiPhone/) } || devices.last
  UI.die!("no iOS Simulator devices — open Xcode once to install a Simulator runtime") unless device

  UI.step("booting simulator #{UI.bold(device["name"])}")
  Shellout.run?("xcrun", "simctl", "boot", device["udid"], quiet: true) # races `open` starting it; harmless
  Shellout.run?("open", "-a", "Simulator", quiet: true)
  wait_until_booted(device["udid"])
  device["udid"]
end

.booted_udidObject

UDID of a booted iPhone Simulator, or nil.



15
16
17
# File 'lib/everywhere/simulator.rb', line 15

def booted_udid
  devices.find { |d| d["state"] == "Booted" }&.fetch("udid", nil)
end

.devicesObject

Available devices of the current runtime set, oldest runtime first — .last / reverse.find therefore prefer the newest.



55
56
57
58
59
60
61
62
# File 'lib/everywhere/simulator.rb', line 55

def devices
  out, status = Shellout.capture("xcrun", "simctl", "list", "devices", "available", "-j")
  return [] unless status&.success?

  JSON.parse(out).fetch("devices", {}).sort.flat_map { |_runtime, list| list }
rescue JSON::ParserError
  []
end

.install(udid, app_path) ⇒ Object



37
38
39
40
# File 'lib/everywhere/simulator.rb', line 37

def install(udid, app_path)
  out, status = Shellout.capture("xcrun", "simctl", "install", udid, app_path)
  UI.die!("simctl install failed: #{out.strip}") unless status&.success?
end

.launch(udid, bundle_id, env: {}) ⇒ Object

Launch (relaunching if already running). Env vars are forwarded into the app's process via simctl's SIMCTL_CHILD_ prefix — how the dev URL reaches the shell without restamping the bundle.



45
46
47
48
49
50
51
# File 'lib/everywhere/simulator.rb', line 45

def launch(udid, bundle_id, env: {})
  child_env = env.transform_keys { |k| "SIMCTL_CHILD_#{k}" }
  out, status = Shellout.capture(child_env,
                                 "xcrun", "simctl", "launch",
                                 "--terminate-running-process", udid, bundle_id)
  UI.die!("simctl launch failed: #{out.strip}") unless status&.success?
end

.wait_until_booted(udid, timeout: 60) ⇒ Object



64
65
66
67
68
69
70
71
72
# File 'lib/everywhere/simulator.rb', line 64

def wait_until_booted(udid, timeout: 60)
  deadline = Time.now + timeout
  until Time.now > deadline
    return if devices.any? { |d| d["udid"] == udid && d["state"] == "Booted" }

    sleep 1
  end
  UI.die!("simulator never finished booting")
end