| # Copyright 2023 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Provide helpers for running Fuchsia's `ffx emu`.""" |
| |
| import argparse |
| import logging |
| import os |
| import random |
| |
| from contextlib import AbstractContextManager |
| |
| import monitors |
| |
| from common import run_ffx_command, IMAGES_ROOT, INTERNAL_IMAGES_ROOT, \ |
| DIR_SRC_ROOT |
| from compatible_utils import get_host_arch |
| |
| |
| class FfxEmulator(AbstractContextManager): |
| """A helper for managing emulators.""" |
| # pylint: disable=too-many-branches |
| def __init__(self, args: argparse.Namespace) -> None: |
| if args.product: |
| self._product = args.product |
| else: |
| if get_host_arch() == 'x64': |
| self._product = 'terminal.x64' |
| else: |
| self._product = 'terminal.qemu-arm64' |
| |
| self._enable_graphics = args.enable_graphics |
| self._logs_dir = args.logs_dir |
| self._with_network = args.with_network |
| if args.everlasting: |
| # Do not change the name, it will break the logic. |
| # ffx has a prefix-matching logic, so 'fuchsia-emulator' is not |
| # usable to avoid breaking local development workflow. I.e. |
| # developers can create an everlasting emulator and an ephemeral one |
| # without interfering each other. |
| self._node_name = 'fuchsia-everlasting-emulator' |
| assert self._everlasting() |
| else: |
| self._node_name = 'fuchsia-emulator-' + str(random.randint( |
| 1, 9999)) |
| self._device_spec = args.device_spec |
| |
| def _everlasting(self) -> bool: |
| return self._node_name == 'fuchsia-everlasting-emulator' |
| |
| def __enter__(self) -> str: |
| """Start the emulator. |
| |
| Returns: |
| The node name of the emulator. |
| """ |
| logging.info('Starting emulator %s', self._node_name) |
| prod, board = self._product.split('.', 1) |
| image_dir = os.path.join(IMAGES_ROOT, prod, board) |
| if not os.path.isdir(image_dir): |
| image_dir = os.path.join(INTERNAL_IMAGES_ROOT, prod, board) |
| emu_command = ['emu', 'start', image_dir, '--name', self._node_name] |
| configs = ['emu.start.timeout=300'] |
| if not self._enable_graphics: |
| emu_command.append('-H') |
| if self._logs_dir: |
| emu_command.extend( |
| ('-l', os.path.join(self._logs_dir, 'emulator_log'))) |
| if self._with_network: |
| emu_command.extend(['--net', 'tap']) |
| else: |
| emu_command.extend(['--net', 'user']) |
| if self._everlasting(): |
| emu_command.extend(['--reuse-with-check']) |
| if self._device_spec: |
| emu_command.extend(['--device', self._device_spec]) |
| |
| # fuchsia-sdk does not carry arm64 qemu binaries, so use overrides to |
| # allow it using the qemu-arm64 being downloaded separately. |
| if get_host_arch() == 'arm64': |
| configs.append( |
| 'sdk.overrides.qemu_internal=' + |
| os.path.join(DIR_SRC_ROOT, 'third_party', 'qemu-linux-arm64', |
| 'bin', 'qemu-system-aarch64')) |
| |
| # Always use qemu for arm64 images, no matter it runs on arm64 hosts or |
| # x64 hosts with simulation. |
| if self._product.endswith('arm64'): |
| emu_command.extend(['--engine', 'qemu']) |
| |
| with monitors.time_consumption('emulator', 'startup_time'): |
| run_ffx_command(cmd=emu_command, timeout=310, configs=configs) |
| |
| return self._node_name |
| |
| def __exit__(self, exc_type, exc_value, traceback) -> bool: |
| """Shutdown the emulator.""" |
| |
| logging.info('Stopping the emulator %s', self._node_name) |
| cmd = ['emu', 'stop', self._node_name] |
| if self._everlasting(): |
| cmd.extend(['--persist']) |
| # The emulator might have shut down unexpectedly, so this command |
| # might fail. |
| run_ffx_command(cmd=cmd, check=False) |
| # Do not suppress exceptions. |
| return False |