def discover_best_compatible_simulator()

in apple/internal/templates/ios_sim.template.py [0:0]


def discover_best_compatible_simulator(simctl_path, minimum_os, sim_device,
                                       sim_os_version):
  """Discovers the best compatible simulator device type and device.

  Args:
    simctl_path: The path to the `simctl` binary.
    minimum_os: The minimum OS version required by the ios_application() target.
    sim_device: Optional name of the device (e.g. "iPhone 8 Plus").
    sim_os_version: Optional version of the iOS runtime (e.g. "13.2").

  Returns:
    A tuple (device_type, device) containing the DeviceType and Device
    of the best compatible simulator (might be None if no match was found).

  Raises:
    subprocess.SubprocessError: if `simctl list` fails or times out.
  """
  # The `simctl list` CLI provides only very basic case-insensitive description
  # matching search term functionality.
  #
  # This code needs to enforce a numeric floor on `minimum_os`, so it directly
  # parses the JSON output by `simctl list` instead of repeatedly invoking
  # `simctl list` with search terms.
  cmd = [simctl_path, "list", "-j"]
  with subprocess.Popen(cmd, stdout=subprocess.PIPE) as process:
    simctl_data = json.load(process.stdout)
    if process.wait() != os.EX_OK:
      raise subprocess.CalledProcessError(process.returncode, cmd)
  compatible_device_types = []
  minimum_runtime_version = minimum_os_to_simctl_runtime_version(minimum_os)
  # Prepare the device name for case-insensitive matching.
  sim_device = sim_device and sim_device.casefold()
  # `simctl list` orders device types from oldest to newest. Remember
  # the index of each device type to preserve that ordering when
  # sorting device types.
  for (simctl_list_index, device_type) in enumerate(simctl_data["devicetypes"]):
    device_type = DeviceType(device_type, simctl_list_index)
    if not (device_type.is_iphone() or device_type.is_ipad()):
      continue
    # Some older simulators are missing `maxRuntimeVersion`. Assume those
    # simulators support all OSes (even though it's not true).
    max_runtime_version = device_type.get("maxRuntimeVersion")
    if max_runtime_version and max_runtime_version < minimum_runtime_version:
      continue
    if sim_device and device_type["name"].casefold().find(sim_device) == -1:
      continue
    compatible_device_types.append(device_type)
  compatible_device_types.sort()
  logger.debug("Found %d compatible device types.",
               len(compatible_device_types))
  compatible_runtime_identifiers = set()
  for runtime in simctl_data["runtimes"]:
    if not runtime["isAvailable"]:
      continue
    if sim_os_version and runtime["version"] != sim_os_version:
      continue
    compatible_runtime_identifiers.add(runtime["identifier"])
  compatible_devices = []
  for runtime_identifier, devices in simctl_data["devices"].items():
    if runtime_identifier not in compatible_runtime_identifiers:
      continue
    for device in devices:
      if not device["isAvailable"]:
        continue
      compatible_device = None
      for device_type in compatible_device_types:
        if device["deviceTypeIdentifier"] == device_type["identifier"]:
          compatible_device = Device(device, device_type)
          break
      if not compatible_device:
        continue
      compatible_devices.append(compatible_device)
  compatible_devices.sort()
  logger.debug("Found %d compatible devices.", len(compatible_devices))
  if compatible_device_types:
    best_compatible_device_type = compatible_device_types[-1]
  else:
    best_compatible_device_type = None
  if compatible_devices:
    best_compatible_device = compatible_devices[-1]
  else:
    best_compatible_device = None
  return (best_compatible_device_type, best_compatible_device)