# 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