1# Copyright 2023 The Chromium Authors 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4"""Provide helpers for running Fuchsia's `ffx emu`.""" 5 6import argparse 7import logging 8import os 9import random 10 11from contextlib import AbstractContextManager 12 13import monitors 14 15from common import run_ffx_command, IMAGES_ROOT, INTERNAL_IMAGES_ROOT, \ 16 DIR_SRC_ROOT 17from compatible_utils import get_host_arch 18 19 20class FfxEmulator(AbstractContextManager): 21 """A helper for managing emulators.""" 22 # pylint: disable=too-many-branches 23 def __init__(self, args: argparse.Namespace) -> None: 24 if args.product: 25 self._product = args.product 26 else: 27 if get_host_arch() == 'x64': 28 self._product = 'terminal.x64' 29 else: 30 self._product = 'terminal.qemu-arm64' 31 32 self._enable_graphics = args.enable_graphics 33 self._logs_dir = args.logs_dir 34 self._with_network = args.with_network 35 if args.everlasting: 36 # Do not change the name, it will break the logic. 37 # ffx has a prefix-matching logic, so 'fuchsia-emulator' is not 38 # usable to avoid breaking local development workflow. I.e. 39 # developers can create an everlasting emulator and an ephemeral one 40 # without interfering each other. 41 self._node_name = 'fuchsia-everlasting-emulator' 42 assert self._everlasting() 43 else: 44 self._node_name = 'fuchsia-emulator-' + str(random.randint( 45 1, 9999)) 46 self._device_spec = args.device_spec 47 48 def _everlasting(self) -> bool: 49 return self._node_name == 'fuchsia-everlasting-emulator' 50 51 def __enter__(self) -> str: 52 """Start the emulator. 53 54 Returns: 55 The node name of the emulator. 56 """ 57 logging.info('Starting emulator %s', self._node_name) 58 prod, board = self._product.split('.', 1) 59 image_dir = os.path.join(IMAGES_ROOT, prod, board) 60 if not os.path.isdir(image_dir): 61 image_dir = os.path.join(INTERNAL_IMAGES_ROOT, prod, board) 62 emu_command = ['emu', 'start', image_dir, '--name', self._node_name] 63 configs = ['emu.start.timeout=300'] 64 if not self._enable_graphics: 65 emu_command.append('-H') 66 if self._logs_dir: 67 emu_command.extend( 68 ('-l', os.path.join(self._logs_dir, 'emulator_log'))) 69 if self._with_network: 70 emu_command.extend(['--net', 'tap']) 71 else: 72 emu_command.extend(['--net', 'user']) 73 if self._everlasting(): 74 emu_command.extend(['--reuse-with-check']) 75 if self._device_spec: 76 emu_command.extend(['--device', self._device_spec]) 77 78 # fuchsia-sdk does not carry arm64 qemu binaries, so use overrides to 79 # allow it using the qemu-arm64 being downloaded separately. 80 if get_host_arch() == 'arm64': 81 configs.append( 82 'sdk.overrides.qemu_internal=' + 83 os.path.join(DIR_SRC_ROOT, 'third_party', 'qemu-linux-arm64', 84 'bin', 'qemu-system-aarch64')) 85 configs.append('sdk.overrides.uefi_internal=' + 86 os.path.join(DIR_SRC_ROOT, 'third_party', 'edk2', 87 'qemu-arm64', 'QEMU_EFI.fd')) 88 89 # Always use qemu for arm64 images, no matter it runs on arm64 hosts or 90 # x64 hosts with simulation. 91 if self._product.endswith('arm64'): 92 emu_command.extend(['--engine', 'qemu']) 93 94 with monitors.time_consumption('emulator', 'startup_time'): 95 run_ffx_command(cmd=emu_command, timeout=310, configs=configs) 96 97 return self._node_name 98 99 def __exit__(self, exc_type, exc_value, traceback) -> bool: 100 """Shutdown the emulator.""" 101 102 logging.info('Stopping the emulator %s', self._node_name) 103 cmd = ['emu', 'stop', self._node_name] 104 if self._everlasting(): 105 cmd.extend(['--persist']) 106 # The emulator might have shut down unexpectedly, so this command 107 # might fail. 108 run_ffx_command(cmd=cmd, check=False) 109 # Do not suppress exceptions. 110 return False 111