xref: /aosp_15_r20/external/angle/build/fuchsia/test/ffx_emulator.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
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