xref: /aosp_15_r20/external/angle/build/fuchsia/test/flash_device.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1*8975f5c5SAndroid Build Coastguard Worker#!/usr/bin/env vpython3
2*8975f5c5SAndroid Build Coastguard Worker# Copyright 2022 The Chromium Authors
3*8975f5c5SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be
4*8975f5c5SAndroid Build Coastguard Worker# found in the LICENSE file.
5*8975f5c5SAndroid Build Coastguard Worker"""Implements commands for flashing a Fuchsia device."""
6*8975f5c5SAndroid Build Coastguard Worker
7*8975f5c5SAndroid Build Coastguard Workerimport argparse
8*8975f5c5SAndroid Build Coastguard Workerimport logging
9*8975f5c5SAndroid Build Coastguard Workerimport os
10*8975f5c5SAndroid Build Coastguard Workerimport subprocess
11*8975f5c5SAndroid Build Coastguard Workerimport sys
12*8975f5c5SAndroid Build Coastguard Worker
13*8975f5c5SAndroid Build Coastguard Workerfrom typing import Optional, Tuple
14*8975f5c5SAndroid Build Coastguard Worker
15*8975f5c5SAndroid Build Coastguard Workerimport common
16*8975f5c5SAndroid Build Coastguard Workerfrom boot_device import BootMode, StateTransitionError, boot_device
17*8975f5c5SAndroid Build Coastguard Workerfrom common import get_system_info, find_image_in_sdk, \
18*8975f5c5SAndroid Build Coastguard Worker                   register_device_args
19*8975f5c5SAndroid Build Coastguard Workerfrom compatible_utils import get_sdk_hash, running_unattended
20*8975f5c5SAndroid Build Coastguard Workerfrom lockfile import lock
21*8975f5c5SAndroid Build Coastguard Worker
22*8975f5c5SAndroid Build Coastguard Worker# Flash-file lock. Used to restrict number of flash operations per host.
23*8975f5c5SAndroid Build Coastguard Worker# File lock should be marked as stale after 15 mins.
24*8975f5c5SAndroid Build Coastguard Worker_FF_LOCK = os.path.join('/tmp', 'flash.lock')
25*8975f5c5SAndroid Build Coastguard Worker_FF_LOCK_STALE_SECS = 60 * 15
26*8975f5c5SAndroid Build Coastguard Worker_FF_LOCK_ACQ_TIMEOUT = _FF_LOCK_STALE_SECS
27*8975f5c5SAndroid Build Coastguard Worker
28*8975f5c5SAndroid Build Coastguard Worker
29*8975f5c5SAndroid Build Coastguard Workerdef _get_system_info(target: Optional[str],
30*8975f5c5SAndroid Build Coastguard Worker                     serial_num: Optional[str]) -> Tuple[str, str]:
31*8975f5c5SAndroid Build Coastguard Worker    """Retrieves installed OS version from device.
32*8975f5c5SAndroid Build Coastguard Worker
33*8975f5c5SAndroid Build Coastguard Worker    Args:
34*8975f5c5SAndroid Build Coastguard Worker        target: Target to get system info of.
35*8975f5c5SAndroid Build Coastguard Worker        serial_num: Serial number of device to get system info of.
36*8975f5c5SAndroid Build Coastguard Worker    Returns:
37*8975f5c5SAndroid Build Coastguard Worker        Tuple of strings, containing (product, version number).
38*8975f5c5SAndroid Build Coastguard Worker    """
39*8975f5c5SAndroid Build Coastguard Worker
40*8975f5c5SAndroid Build Coastguard Worker    if running_unattended():
41*8975f5c5SAndroid Build Coastguard Worker        try:
42*8975f5c5SAndroid Build Coastguard Worker            boot_device(target, BootMode.REGULAR, serial_num)
43*8975f5c5SAndroid Build Coastguard Worker        except (subprocess.CalledProcessError, StateTransitionError):
44*8975f5c5SAndroid Build Coastguard Worker            logging.warning('Could not boot device. Assuming in fastboot')
45*8975f5c5SAndroid Build Coastguard Worker            return ('', '')
46*8975f5c5SAndroid Build Coastguard Worker
47*8975f5c5SAndroid Build Coastguard Worker    return get_system_info(target)
48*8975f5c5SAndroid Build Coastguard Worker
49*8975f5c5SAndroid Build Coastguard Worker
50*8975f5c5SAndroid Build Coastguard Workerdef _update_required(
51*8975f5c5SAndroid Build Coastguard Worker        os_check,
52*8975f5c5SAndroid Build Coastguard Worker        system_image_dir: Optional[str],
53*8975f5c5SAndroid Build Coastguard Worker        target: Optional[str],
54*8975f5c5SAndroid Build Coastguard Worker        serial_num: Optional[str] = None) -> Tuple[bool, Optional[str]]:
55*8975f5c5SAndroid Build Coastguard Worker    """Returns True if a system update is required and path to image dir."""
56*8975f5c5SAndroid Build Coastguard Worker
57*8975f5c5SAndroid Build Coastguard Worker    if os_check == 'ignore':
58*8975f5c5SAndroid Build Coastguard Worker        return False, system_image_dir
59*8975f5c5SAndroid Build Coastguard Worker    if not system_image_dir:
60*8975f5c5SAndroid Build Coastguard Worker        raise ValueError('System image directory must be specified.')
61*8975f5c5SAndroid Build Coastguard Worker    if not os.path.exists(system_image_dir):
62*8975f5c5SAndroid Build Coastguard Worker        logging.warning(
63*8975f5c5SAndroid Build Coastguard Worker            'System image directory does not exist. Assuming it\'s '
64*8975f5c5SAndroid Build Coastguard Worker            'a product-bundle name and dynamically searching for '
65*8975f5c5SAndroid Build Coastguard Worker            'image directory')
66*8975f5c5SAndroid Build Coastguard Worker        path = find_image_in_sdk(system_image_dir)
67*8975f5c5SAndroid Build Coastguard Worker        if not path:
68*8975f5c5SAndroid Build Coastguard Worker            raise FileNotFoundError(
69*8975f5c5SAndroid Build Coastguard Worker                f'System image directory {system_image_dir} could not'
70*8975f5c5SAndroid Build Coastguard Worker                'be found')
71*8975f5c5SAndroid Build Coastguard Worker        system_image_dir = path
72*8975f5c5SAndroid Build Coastguard Worker    if (os_check == 'check'
73*8975f5c5SAndroid Build Coastguard Worker            and get_sdk_hash(system_image_dir) == _get_system_info(
74*8975f5c5SAndroid Build Coastguard Worker                target, serial_num)):
75*8975f5c5SAndroid Build Coastguard Worker        return False, system_image_dir
76*8975f5c5SAndroid Build Coastguard Worker    return True, system_image_dir
77*8975f5c5SAndroid Build Coastguard Worker
78*8975f5c5SAndroid Build Coastguard Worker
79*8975f5c5SAndroid Build Coastguard Workerdef _run_flash_command(system_image_dir: str, target_id: Optional[str]):
80*8975f5c5SAndroid Build Coastguard Worker    """Helper function for running `ffx target flash`."""
81*8975f5c5SAndroid Build Coastguard Worker    logging.info('Flashing %s to %s', system_image_dir, target_id)
82*8975f5c5SAndroid Build Coastguard Worker    # Flash only with a file lock acquired.
83*8975f5c5SAndroid Build Coastguard Worker    # This prevents multiple fastboot binaries from flashing concurrently,
84*8975f5c5SAndroid Build Coastguard Worker    # which should increase the odds of flashing success.
85*8975f5c5SAndroid Build Coastguard Worker    with lock(_FF_LOCK, timeout=_FF_LOCK_ACQ_TIMEOUT):
86*8975f5c5SAndroid Build Coastguard Worker        # The ffx.fastboot.inline_target has negative impact when ffx
87*8975f5c5SAndroid Build Coastguard Worker        # discovering devices in fastboot, so it's inlined here to limit its
88*8975f5c5SAndroid Build Coastguard Worker        # scope. See the discussion in https://fxbug.dev/issues/317228141.
89*8975f5c5SAndroid Build Coastguard Worker        logging.info(
90*8975f5c5SAndroid Build Coastguard Worker            'Flash result %s',
91*8975f5c5SAndroid Build Coastguard Worker            common.run_ffx_command(cmd=('target', 'flash', '-b',
92*8975f5c5SAndroid Build Coastguard Worker                                        system_image_dir,
93*8975f5c5SAndroid Build Coastguard Worker                                        '--no-bootloader-reboot'),
94*8975f5c5SAndroid Build Coastguard Worker                                   target_id=target_id,
95*8975f5c5SAndroid Build Coastguard Worker                                   configs=['ffx.fastboot.inline_target=true'],
96*8975f5c5SAndroid Build Coastguard Worker                                   capture_output=True).stdout)
97*8975f5c5SAndroid Build Coastguard Worker
98*8975f5c5SAndroid Build Coastguard Worker
99*8975f5c5SAndroid Build Coastguard Workerdef update(system_image_dir: str,
100*8975f5c5SAndroid Build Coastguard Worker           os_check: str,
101*8975f5c5SAndroid Build Coastguard Worker           target: Optional[str],
102*8975f5c5SAndroid Build Coastguard Worker           serial_num: Optional[str] = None) -> None:
103*8975f5c5SAndroid Build Coastguard Worker    """Conditionally updates target given.
104*8975f5c5SAndroid Build Coastguard Worker
105*8975f5c5SAndroid Build Coastguard Worker    Args:
106*8975f5c5SAndroid Build Coastguard Worker        system_image_dir: string, path to image directory.
107*8975f5c5SAndroid Build Coastguard Worker        os_check: <check|ignore|update>, which decides how to update the device.
108*8975f5c5SAndroid Build Coastguard Worker        target: Node-name string indicating device that should be updated.
109*8975f5c5SAndroid Build Coastguard Worker        serial_num: String of serial number of device that should be updated.
110*8975f5c5SAndroid Build Coastguard Worker    """
111*8975f5c5SAndroid Build Coastguard Worker    needs_update, actual_image_dir = _update_required(os_check,
112*8975f5c5SAndroid Build Coastguard Worker                                                      system_image_dir, target,
113*8975f5c5SAndroid Build Coastguard Worker                                                      serial_num)
114*8975f5c5SAndroid Build Coastguard Worker    logging.info('update_required %s, actual_image_dir %s', needs_update,
115*8975f5c5SAndroid Build Coastguard Worker                 actual_image_dir)
116*8975f5c5SAndroid Build Coastguard Worker    if not needs_update:
117*8975f5c5SAndroid Build Coastguard Worker        return
118*8975f5c5SAndroid Build Coastguard Worker    if serial_num:
119*8975f5c5SAndroid Build Coastguard Worker        boot_device(target, BootMode.BOOTLOADER, serial_num)
120*8975f5c5SAndroid Build Coastguard Worker        _run_flash_command(system_image_dir, serial_num)
121*8975f5c5SAndroid Build Coastguard Worker    else:
122*8975f5c5SAndroid Build Coastguard Worker        _run_flash_command(system_image_dir, target)
123*8975f5c5SAndroid Build Coastguard Worker
124*8975f5c5SAndroid Build Coastguard Worker
125*8975f5c5SAndroid Build Coastguard Workerdef register_update_args(arg_parser: argparse.ArgumentParser,
126*8975f5c5SAndroid Build Coastguard Worker                         default_os_check: Optional[str] = 'check') -> None:
127*8975f5c5SAndroid Build Coastguard Worker    """Register common arguments for device updating."""
128*8975f5c5SAndroid Build Coastguard Worker    serve_args = arg_parser.add_argument_group('update',
129*8975f5c5SAndroid Build Coastguard Worker                                               'device updating arguments')
130*8975f5c5SAndroid Build Coastguard Worker    serve_args.add_argument('--system-image-dir',
131*8975f5c5SAndroid Build Coastguard Worker                            help='Specify the directory that contains the '
132*8975f5c5SAndroid Build Coastguard Worker                            'Fuchsia image used to flash the device. Only '
133*8975f5c5SAndroid Build Coastguard Worker                            'needs to be specified if "os_check" is not '
134*8975f5c5SAndroid Build Coastguard Worker                            '"ignore".')
135*8975f5c5SAndroid Build Coastguard Worker    serve_args.add_argument('--serial-num',
136*8975f5c5SAndroid Build Coastguard Worker                            default=os.environ.get('FUCHSIA_FASTBOOT_SERNUM'),
137*8975f5c5SAndroid Build Coastguard Worker                            help='Serial number of the device. Should be '
138*8975f5c5SAndroid Build Coastguard Worker                            'specified for devices that do not have an image '
139*8975f5c5SAndroid Build Coastguard Worker                            'flashed.')
140*8975f5c5SAndroid Build Coastguard Worker    serve_args.add_argument('--os-check',
141*8975f5c5SAndroid Build Coastguard Worker                            choices=['check', 'update', 'ignore'],
142*8975f5c5SAndroid Build Coastguard Worker                            default=default_os_check,
143*8975f5c5SAndroid Build Coastguard Worker                            help='Sets the OS version enforcement policy. If '
144*8975f5c5SAndroid Build Coastguard Worker                            '"check", then the deployment process will halt '
145*8975f5c5SAndroid Build Coastguard Worker                            'if the target\'s version does not match. If '
146*8975f5c5SAndroid Build Coastguard Worker                            '"update", then the target device will '
147*8975f5c5SAndroid Build Coastguard Worker                            'be reflashed. If "ignore", then the OS version '
148*8975f5c5SAndroid Build Coastguard Worker                            'will not be checked.')
149*8975f5c5SAndroid Build Coastguard Worker
150*8975f5c5SAndroid Build Coastguard Worker
151*8975f5c5SAndroid Build Coastguard Workerdef main():
152*8975f5c5SAndroid Build Coastguard Worker    """Stand-alone function for flashing a device."""
153*8975f5c5SAndroid Build Coastguard Worker    parser = argparse.ArgumentParser()
154*8975f5c5SAndroid Build Coastguard Worker    register_device_args(parser)
155*8975f5c5SAndroid Build Coastguard Worker    register_update_args(parser, default_os_check='update')
156*8975f5c5SAndroid Build Coastguard Worker    args = parser.parse_args()
157*8975f5c5SAndroid Build Coastguard Worker    update(args.system_image_dir, args.os_check, args.target_id,
158*8975f5c5SAndroid Build Coastguard Worker           args.serial_num)
159*8975f5c5SAndroid Build Coastguard Worker
160*8975f5c5SAndroid Build Coastguard Worker
161*8975f5c5SAndroid Build Coastguard Workerif __name__ == '__main__':
162*8975f5c5SAndroid Build Coastguard Worker    sys.exit(main())
163