xref: /aosp_15_r20/tools/acloud/create/avd_spec.py (revision 800a58d989c669b8eb8a71d8df53b1ba3d411444)
1#!/usr/bin/env python
2#
3# Copyright 2018 - The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16r"""AVDSpec class.
17
18AVDSpec will take in args from the user and be the main data type that will
19get passed into the create classes. The inferring magic will happen within
20initialization of AVDSpec (like LKGB build id, image branch, etc).
21"""
22
23import glob
24import logging
25import os
26import re
27import subprocess
28import tempfile
29import threading
30
31from acloud import errors
32from acloud.create import create_common
33from acloud.internal import constants
34from acloud.internal.lib import android_build_client
35from acloud.internal.lib import auth
36from acloud.internal.lib import utils
37from acloud.list import list as list_instance
38from acloud.public import config
39
40
41logger = logging.getLogger(__name__)
42
43# Default values for build target.
44_BRANCH_RE = re.compile(r"^Manifest branch: (?P<branch>.+)")
45_COMMAND_REPO_INFO = "repo info platform/tools/acloud"
46_REPO_TIMEOUT = 3
47_CF_ZIP_PATTERN = "*img*.zip"
48_DEFAULT_BUILD_BITNESS = "x86_64"
49_DEFAULT_BUILD_TYPE = "userdebug"
50_ENV_ANDROID_PRODUCT_OUT = "ANDROID_PRODUCT_OUT"
51_ENV_ANDROID_BUILD_TOP = "ANDROID_BUILD_TOP"
52_GCE_LOCAL_IMAGE_CANDIDATES = ["avd-system.tar.gz",
53                               "android_system_disk_syslinux.img"]
54_LOCAL_ZIP_WARNING_MSG = "'adb sync' will take a long time if using images " \
55                         "built with `m dist`. Building with just `m` will " \
56                         "enable a faster 'adb sync' process."
57_RE_ANSI_ESCAPE = re.compile(r"(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]")
58_RE_FLAVOR = re.compile(r"^.+_(?P<flavor>.+)-img.+")
59_RE_MEMORY = re.compile(r"(?P<gb_size>\d+)g$|(?P<mb_size>\d+)m$",
60                        re.IGNORECASE)
61_RE_INT = re.compile(r"^\d+$")
62_RE_RES = re.compile(r"^(?P<x_res>\d+)x(?P<y_res>\d+)$")
63_X_RES = "x_res"
64_Y_RES = "y_res"
65_COMMAND_GIT_REMOTE = ["git", "remote"]
66
67# The branch prefix is necessary for the Android Build system to know what we're
68# talking about. For instance, on an aosp remote repo in the main branch,
69# Android Build will recognize it as aosp-main.
70_BRANCH_PREFIX = {"aosp": "aosp-"}
71_DEFAULT_BRANCH_PREFIX = "git_"
72_DEFAULT_BRANCH = "aosp-main"
73
74# The target prefix is needed to help concoct the lunch target name given a
75# the branch, avd type and device flavor:
76# aosp, cf and phone -> aosp_cf_x86_phone.
77_BRANCH_TARGET_PREFIX = {"aosp": "aosp_"}
78_BRANCH_TARGET_TRUNK_STAGEING = {"aosp-main": "-trunk_staging",
79                                 "git_main": "-trunk_staging"}
80
81
82def EscapeAnsi(line):
83    """Remove ANSI control sequences (e.g. temrinal color codes...)
84
85    Args:
86        line: String, one line of command output.
87
88    Returns:
89        String without ANSI code.
90    """
91    return _RE_ANSI_ESCAPE.sub('', line)
92
93
94# pylint: disable=too-many-public-methods,too-many-lines,too-many-statements
95class AVDSpec():
96    """Class to store data on the type of AVD to create."""
97
98    def __init__(self, args):
99        """Process the args into class vars.
100
101        Args:
102            args: Namespace object from argparse.parse_args.
103        """
104        # Let's define the private class vars here and then process the user
105        # args afterwards.
106        self._client_adb_port = args.adb_port
107        self._autoconnect = None
108        self._cvd_host_package = None
109        self._instance_name_to_reuse = None
110        self._unlock_screen = None
111        self._report_internal_ip = None
112        self._disable_external_ip = None
113        self._extra_files = None
114        self._avd_type = None
115        self._flavor = None
116        self._force_sync = None
117        self._image_source = None
118        self._instance_type = None
119        self._launch_args = None
120        self._local_image_dir = None
121        self._local_image_artifact = None
122        self._local_instance_dir = None
123        self._local_kernel_image = None
124        self._local_system_image = None
125        self._local_system_dlkm_image = None
126        self._local_vendor_image = None
127        self._local_vendor_boot_image = None
128        self._local_tool_dirs = None
129        self._image_download_dir = None
130        self._num_of_instances = None
131        self._num_avds_per_instance = None
132        self._no_pull_log = None
133        self._mkcert = None
134        self._oxygen = None
135        self._openwrt = None
136        self._remote_image = {}
137        self._system_build_info = {}
138        self._kernel_build_info = {}
139        self._boot_build_info = {}
140        self._ota_build_info = {}
141        self._host_package_build_info = {}
142        self._bootloader_build_info = {}
143        self._android_efi_loader_build_info = {}
144        self._hw_property = None
145        self._hw_customize = False
146        self._remote_host = None
147        self._remote_image_dir = None
148        self._gce_metadata = None
149        self._gce_only = None
150        self._host_user = None
151        self._host_ssh_private_key_path = None
152        self._gpu = None
153        self._disk_type = None
154        self._base_instance_num = None
155        self._stable_host_image_name = None
156        self._use_launch_cvd = None
157        self._remote_fetch = None
158        self._webrtc_device_id = None
159        self._connect_hostname = None
160        self._fetch_cvd_wrapper = None
161        self._fetch_cvd_version = None
162
163        # Create config instance for android_build_client to query build api.
164        self._cfg = config.GetAcloudConfig(args)
165        # Reporting args.
166        self._serial_log_file = None
167        # emulator_* are only used for goldfish avd_type.
168        self._emulator_build_id = None
169        self._emulator_build_target = None
170        self._emulator_zip = None
171
172        # Fields only used for cheeps type.
173        self._stable_cheeps_host_image_name = None
174        self._stable_cheeps_host_image_project = None
175        self._username = None
176        self._password = None
177        self._cheeps_betty_image = None
178        self._cheeps_features = None
179
180        # Fields only used for trusty type.
181        self._local_trusty_image = None
182        self._trusty_host_package = None
183        self._trusty_build_info = {}
184
185        # The maximum time in seconds used to wait for the AVD to boot.
186        self._boot_timeout_secs = None
187        # The maximum time in seconds used to wait for the instance ready.
188        self._ins_timeout_secs = None
189
190        # The local instance id
191        self._local_instance_id = None
192
193        self._ProcessArgs(args)
194
195    def __repr__(self):
196        """Let's make it easy to see what this class is holding."""
197        # TODO: I'm pretty sure there's a better way to do this, but I'm not
198        # quite sure what that would be.
199        representation = []
200        representation.append("")
201        representation.append(" - instance_type: %s" % self._instance_type)
202        representation.append(" - avd type: %s" % self._avd_type)
203        representation.append(" - flavor: %s" % self._flavor)
204        representation.append(" - autoconnect: %s" % self._autoconnect)
205        representation.append(" - num of instances requested: %s" %
206                              self._num_of_instances)
207        representation.append(" - image source type: %s" %
208                              self._image_source)
209        image_summary = None
210        image_details = None
211        if self._image_source == constants.IMAGE_SRC_LOCAL:
212            image_summary = "local image dir"
213            image_details = self._local_image_dir
214            representation.append(" - instance id: %s" % self._local_instance_id)
215        elif self._image_source == constants.IMAGE_SRC_REMOTE:
216            image_summary = "remote image details"
217            image_details = self._remote_image
218        representation.append(" - %s: %s" % (image_summary, image_details))
219        representation.append(" - hw properties: %s" %
220                              self._hw_property)
221        return "\n".join(representation)
222
223    def _ProcessArgs(self, args):
224        """Main entry point to process args for the different type of args.
225
226        Split up the arg processing into related areas (image, instance type,
227        etc) so that we don't have one huge monolilthic method that does
228        everything. It makes it easier to review, write tests, and maintain.
229
230        Args:
231            args: Namespace object from argparse.parse_args.
232        """
233        self._ProcessMiscArgs(args)
234        self._ProcessImageArgs(args)
235        self._ProcessHWPropertyArgs(args)
236        self._ProcessAutoconnect()
237
238    def _ProcessAutoconnect(self):
239        """Process autoconnect.
240
241        Only Cuttlefish AVD support 'webrtc' and need to default use 'webrtc'.
242        Other AVD types(goldfish, cheeps..etc.) still keep using ‘vnc’.
243        """
244        if self._autoconnect == constants.INS_KEY_WEBRTC:
245            if self.avd_type != constants.TYPE_CF:
246                self._autoconnect = constants.INS_KEY_VNC
247
248    def _ProcessImageArgs(self, args):
249        """ Process Image Args.
250
251        Args:
252            args: Namespace object from argparse.parse_args.
253        """
254        # If user didn't specify --local-image, infer remote image args
255        if args.local_image is None:
256            self._image_source = constants.IMAGE_SRC_REMOTE
257            self._ProcessRemoteBuildArgs(args)
258        else:
259            self._image_source = constants.IMAGE_SRC_LOCAL
260            self._ProcessLocalImageArgs(args)
261
262        if args.local_kernel_image is not None:
263            self._local_kernel_image = self._GetLocalImagePath(
264                args.local_kernel_image)
265
266        if args.local_system_image is not None:
267            self._local_system_image = self._GetLocalImagePath(
268                args.local_system_image)
269
270        if args.local_system_dlkm_image is not None:
271            self._local_system_dlkm_image = self._GetLocalImagePath(
272                args.local_system_dlkm_image)
273
274        if args.local_vendor_image is not None:
275            self._local_vendor_image = self._GetLocalImagePath(
276                args.local_vendor_image)
277
278        if args.local_vendor_boot_image is not None:
279            self._local_vendor_boot_image = self._GetLocalImagePath(
280                args.local_vendor_boot_image)
281
282        self.image_download_dir = (
283            args.image_download_dir if args.image_download_dir
284            else tempfile.gettempdir())
285
286    @staticmethod
287    def _ParseHWPropertyStr(hw_property_str):
288        """Parse string to dict.
289
290        Args:
291            hw_property_str: A hw properties string.
292
293        Returns:
294            Dict converted from a string.
295
296        Raises:
297            error.MalformedHWPropertyError: If hw_property_str is malformed.
298        """
299        hw_dict = create_common.ParseKeyValuePairArgs(hw_property_str)
300        arg_hw_properties = {}
301        for key, value in hw_dict.items():
302            # Parsing HW properties int to avdspec.
303            if key == constants.HW_ALIAS_RESOLUTION:
304                match = _RE_RES.match(value)
305                if match:
306                    arg_hw_properties[_X_RES] = match.group("x_res")
307                    arg_hw_properties[_Y_RES] = match.group("y_res")
308                else:
309                    raise errors.InvalidHWPropertyError(
310                        "[%s] is an invalid resolution. Example:1280x800" % value)
311            elif key in [constants.HW_ALIAS_MEMORY, constants.HW_ALIAS_DISK]:
312                match = _RE_MEMORY.match(value)
313                if match and match.group("gb_size"):
314                    arg_hw_properties[key] = str(
315                        int(match.group("gb_size")) * 1024)
316                elif match and match.group("mb_size"):
317                    arg_hw_properties[key] = match.group("mb_size")
318                else:
319                    raise errors.InvalidHWPropertyError(
320                        "Expected gb size.[%s] is not allowed. Example:4g" % value)
321            elif key in [constants.HW_ALIAS_CPUS, constants.HW_ALIAS_DPI]:
322                if not _RE_INT.match(value):
323                    raise errors.InvalidHWPropertyError(
324                        "%s value [%s] is not an integer." % (key, value))
325                arg_hw_properties[key] = value
326
327        return arg_hw_properties
328
329    def _ProcessHWPropertyArgs(self, args):
330        """Get the HW properties from argparse.parse_args.
331
332        This method will initialize _hw_property in the following
333        manner:
334        1. Get default hw properties from flavor.
335        2. Override hw properties from config.
336        3. Override by hw_property args.
337
338        Args:
339            args: Namespace object from argparse.parse_args.
340        """
341        self._hw_property = {}
342        default_property = self._cfg.GetDefaultHwProperty(self._flavor,
343                                                          self._instance_type)
344        self._hw_property = self._ParseHWPropertyStr(default_property)
345        logger.debug("Default hw property for [%s] flavor: %s", self._flavor,
346                     self._hw_property)
347        if self._cfg.hw_property:
348            self._hw_customize = True
349            cfg_hw_property = self._ParseHWPropertyStr(self._cfg.hw_property)
350            logger.debug("Hw property from config: %s", cfg_hw_property)
351            self._hw_property.update(cfg_hw_property)
352
353        if args.hw_property:
354            self._hw_customize = True
355            arg_hw_property = self._ParseHWPropertyStr(args.hw_property)
356            logger.debug("Use custom hw property: %s", arg_hw_property)
357            self._hw_property.update(arg_hw_property)
358
359    def _ProcessMiscArgs(self, args):
360        """These args we can take as and don't belong to a group of args.
361
362        Args:
363            args: Namespace object from argparse.parse_args.
364        """
365        self._autoconnect = args.autoconnect
366        self._unlock_screen = args.unlock_screen
367        self._report_internal_ip = args.report_internal_ip
368        self._disable_external_ip = args.disable_external_ip
369        self._avd_type = args.avd_type
370        self._extra_files = create_common.ParseExtraFilesArgs(args.extra_files)
371        self._flavor = args.flavor or constants.FLAVOR_PHONE
372        self._force_sync = args.force_sync
373        if args.remote_host:
374            self._instance_type = constants.INSTANCE_TYPE_HOST
375        else:
376            self._instance_type = (constants.INSTANCE_TYPE_REMOTE
377                                   if args.local_instance is None else
378                                   constants.INSTANCE_TYPE_LOCAL)
379        self._remote_host = args.remote_host
380        self._remote_image_dir = args.remote_image_dir
381        self._host_user = args.host_user
382        self._host_ssh_private_key_path = args.host_ssh_private_key_path
383        self._local_instance_id = args.local_instance
384        self._local_instance_dir = args.local_instance_dir
385        self._local_tool_dirs = args.local_tool
386        self._cvd_host_package = args.cvd_host_package
387        self._trusty_host_package = args.trusty_host_package
388        self._num_of_instances = args.num
389        self._num_avds_per_instance = args.num_avds_per_instance
390        self._no_pull_log = args.no_pull_log
391        self._mkcert = args.mkcert
392        self._oxygen = args.oxygen
393        self._openwrt = args.openwrt
394        self._use_launch_cvd = args.use_launch_cvd
395        self._serial_log_file = args.serial_log_file
396        self._emulator_build_id = args.emulator_build_id
397        self._emulator_build_target = (args.emulator_build_target
398                                       or self._cfg.emulator_build_target)
399        self._emulator_zip = args.emulator_zip
400        self._gpu = args.gpu
401        self._disk_type = (args.disk_type or self._cfg.disk_type)
402        self._base_instance_num = args.base_instance_num
403        self._gce_only = args.gce_only
404        self._gce_metadata = create_common.ParseKeyValuePairArgs(args.gce_metadata)
405        self._stable_host_image_name = (
406            args.stable_host_image_name or self._cfg.stable_host_image_name)
407
408        self._stable_cheeps_host_image_name = args.stable_cheeps_host_image_name
409        self._stable_cheeps_host_image_project = args.stable_cheeps_host_image_project
410        self._username = args.username
411        self._password = args.password
412        self._cheeps_betty_image = (
413            args.cheeps_betty_image or self._cfg.betty_image)
414        self._cheeps_features = args.cheeps_features
415
416        self._boot_timeout_secs = args.boot_timeout_secs
417        self._ins_timeout_secs = args.ins_timeout_secs
418        self._launch_args = " ".join(
419            list(filter(None, [self._cfg.launch_args, args.launch_args])))
420        self._remote_fetch = args.remote_fetch
421        self._webrtc_device_id = args.webrtc_device_id
422        self._connect_hostname = args.connect_hostname or self._cfg.connect_hostname
423        self._fetch_cvd_wrapper = args.fetch_cvd_wrapper
424        self._fetch_cvd_version = self._GetFetchCVDVersion(args)
425
426        if args.reuse_gce:
427            if args.reuse_gce != constants.SELECT_ONE_GCE_INSTANCE:
428                if list_instance.GetInstancesFromInstanceNames(
429                        self._cfg, [args.reuse_gce]):
430                    self._instance_name_to_reuse = args.reuse_gce
431            if self._instance_name_to_reuse is None:
432                instance = list_instance.ChooseOneRemoteInstance(self._cfg)
433                self._instance_name_to_reuse = instance.name
434
435        self._local_trusty_image = args.local_trusty_image
436        self._trusty_build_info = {
437            constants.BUILD_ID: args.trusty_build_id,
438            constants.BUILD_BRANCH: args.trusty_branch,
439            constants.BUILD_TARGET: args.trusty_build_target}
440
441    def _GetFetchCVDVersion(self, args):
442        """Get the fetch_cvd version.
443
444        Acloud will get the LKGB of fetch_cvd if no version specified.
445
446        Args:
447            args: Namespace object from argparse.parse_args.
448
449        Returns:
450            The build id of fetch_cvd.
451        """
452        if args.fetch_cvd_build_id:
453            return args.fetch_cvd_build_id
454        return constants.LKGB
455
456    @staticmethod
457    def _GetFlavorFromString(flavor_string):
458        """Get flavor name from flavor string.
459
460        Flavor string can come from the zipped image name or the lunch target.
461        e.g.
462        If flavor_string come from zipped name:aosp_cf_x86_phone-img-5455843.zip
463        , then "phone" is the flavor.
464        If flavor_string come from a lunch'd target:aosp_cf_x86_auto-userdebug,
465        then "auto" is the flavor.
466
467        Args:
468            flavor_string: String which contains flavor.It can be a
469                           build target or filename.
470
471        Returns:
472            String of flavor name. None if flavor can't be determined.
473        """
474        for flavor in constants.ALL_FLAVORS:
475            if re.match(r"(.*_)?%s" % flavor, flavor_string):
476                return flavor
477
478        logger.debug("Unable to determine flavor from build target: %s",
479                     flavor_string)
480        return None
481
482    def _ProcessLocalImageArgs(self, args):
483        """Get local image path.
484
485        Args:
486            args: Namespace object from argparse.parse_args.
487        """
488        if self._avd_type == constants.TYPE_CF:
489            self._ProcessCFLocalImageArgs(args.local_image, args.flavor)
490        elif self._avd_type == constants.TYPE_FVP:
491            self._ProcessFVPLocalImageArgs()
492        elif self._avd_type == constants.TYPE_TRUSTY:
493            self._ProcessTrustyLocalImageArgs(args.local_image)
494        elif self._avd_type == constants.TYPE_GF:
495            local_image_path = self._GetLocalImagePath(args.local_image)
496            if os.path.isdir(local_image_path):
497                self._local_image_dir = local_image_path
498            else:
499                self._local_image_artifact = local_image_path
500        elif self._avd_type == constants.TYPE_GCE:
501            self._local_image_artifact = self._GetGceLocalImagePath(
502                args.local_image)
503        else:
504            raise errors.CreateError(
505                "Local image doesn't support the AVD type: %s" % self._avd_type
506            )
507
508    @staticmethod
509    def _GetGceLocalImagePath(local_image_dir):
510        """Get gce local image path.
511
512        Choose image file in local_image_dir over $ANDROID_PRODUCT_OUT.
513        There are various img files so we prioritize returning the one we find
514        first based in the specified order in _GCE_LOCAL_IMAGE_CANDIDATES.
515
516        Args:
517            local_image_dir: A string to specify local image dir.
518
519        Returns:
520            String, image file path if exists.
521
522        Raises:
523            errors.ImgDoesNotExist if image doesn't exist.
524        """
525        # IF the user specified a file, return it
526        if local_image_dir and os.path.isfile(local_image_dir):
527            return local_image_dir
528
529        # If the user didn't specify a dir, assume $ANDROID_PRODUCT_OUT
530        if not local_image_dir:
531            local_image_dir = utils.GetBuildEnvironmentVariable(
532                _ENV_ANDROID_PRODUCT_OUT)
533
534        for img_name in _GCE_LOCAL_IMAGE_CANDIDATES:
535            full_file_path = os.path.join(local_image_dir, img_name)
536            if os.path.exists(full_file_path):
537                return full_file_path
538
539        raise errors.ImgDoesNotExist("Could not find any GCE images (%s), you "
540                                     "can build them via \"m dist\"" %
541                                     ", ".join(_GCE_LOCAL_IMAGE_CANDIDATES))
542
543    @staticmethod
544    def _GetLocalImagePath(local_image_arg):
545        """Get local image path from argument or environment variable.
546
547        Args:
548            local_image_arg: The path to the unzipped image package. If the
549                             value is empty, this method returns
550                             ANDROID_PRODUCT_OUT in build environment.
551
552        Returns:
553            String, the path to the image file or directory.
554
555        Raises:
556            errors.GetLocalImageError if the path does not exist.
557        """
558        if local_image_arg == constants.FIND_IN_BUILD_ENV:
559            image_path = utils.GetBuildEnvironmentVariable(
560                constants.ENV_ANDROID_PRODUCT_OUT)
561        else:
562            image_path = local_image_arg
563
564        if not os.path.exists(image_path):
565            raise errors.GetLocalImageError("%s does not exist." %
566                                            local_image_arg)
567        return image_path
568
569    def _ProcessCFLocalImageArgs(self, local_image_arg, flavor_arg):
570        """Get local built image path for cuttlefish-type AVD.
571
572        Two scenarios of using --local-image:
573        - Without a following argument
574          Set flavor string if the required images are in $ANDROID_PRODUCT_OUT,
575        - With a following filename/dirname
576          Set flavor string from the specified image/dir name.
577
578        Args:
579            local_image_arg: String of local image args.
580            flavor_arg: String of flavor arg
581
582        """
583        flavor_from_build_string = None
584        if local_image_arg == constants.FIND_IN_BUILD_ENV:
585            self._CheckCFBuildTarget(self._instance_type)
586            local_image_path = utils.GetBuildEnvironmentVariable(
587                _ENV_ANDROID_PRODUCT_OUT)
588            # Since dir is provided, check that any images exist to ensure user
589            # didn't forget to 'make' before launch AVD.
590            image_list = glob.glob(os.path.join(local_image_path, "*.img"))
591            if not image_list:
592                raise errors.GetLocalImageError(
593                    "No image found(Did you choose a lunch target and run `m`?)"
594                    ": %s.\n " % local_image_path)
595        else:
596            local_image_path = local_image_arg
597
598        if os.path.isfile(local_image_path):
599            self._local_image_artifact = local_image_arg
600            flavor_from_build_string = self._GetFlavorFromString(
601                self._local_image_artifact)
602            # Since file is provided and I assume it's a zip, so print the
603            # warning message.
604            utils.PrintColorString(_LOCAL_ZIP_WARNING_MSG,
605                                   utils.TextColors.WARNING)
606        else:
607            self._local_image_dir = local_image_path
608            try:
609                flavor_from_build_string = self._GetFlavorFromString(
610                    utils.GetBuildEnvironmentVariable(constants.ENV_BUILD_TARGET))
611            except errors.GetAndroidBuildEnvVarError:
612                logger.debug("Unable to determine flavor from env variable: %s",
613                             constants.ENV_BUILD_TARGET)
614
615        if flavor_from_build_string and not flavor_arg:
616            self._flavor = flavor_from_build_string
617
618    def _ProcessFVPLocalImageArgs(self):
619        """Get local built image path for FVP-type AVD."""
620        build_target = utils.GetBuildEnvironmentVariable(
621            constants.ENV_BUILD_TARGET)
622        if build_target != "fvp":
623            utils.PrintColorString(
624                "%s is not an fvp target (Try lunching fvp-eng "
625                "and running 'm')" % build_target,
626                utils.TextColors.WARNING)
627        self._local_image_dir = utils.GetBuildEnvironmentVariable(
628            _ENV_ANDROID_PRODUCT_OUT)
629
630        # Since dir is provided, so checking that any images exist to ensure
631        # user didn't forget to 'make' before launch AVD.
632        image_list = glob.glob(os.path.join(self.local_image_dir, "*.img"))
633        if not image_list:
634            raise errors.GetLocalImageError(
635                "No image found(Did you choose a lunch target and run `m`?)"
636                ": %s.\n " % self._local_image_dir)
637
638    def _ProcessTrustyLocalImageArgs(self, local_image_arg):
639        """Get local built image path for Trusty-type AVD."""
640        if local_image_arg == constants.FIND_IN_BUILD_ENV:
641            build_target = utils.GetBuildEnvironmentVariable(
642                constants.ENV_BUILD_TARGET)
643            if build_target != "qemu_trusty_arm64":
644                utils.PrintColorString(
645                    f"{build_target} is not a trusty target (Try lunching "
646                    "qemu_trusty_arm64-trunk_staging-userdebug "
647                    "and running 'm')",
648                    utils.TextColors.WARNING)
649            local_image_path = utils.GetBuildEnvironmentVariable(
650                _ENV_ANDROID_PRODUCT_OUT)
651            # Since dir is provided, check that any images exist to ensure user
652            # didn't forget to 'make' before launch AVD.
653            image_list = glob.glob(os.path.join(local_image_path, "*.img"))
654            if not image_list:
655                raise errors.GetLocalImageError(
656                    "No image found(Did you choose a lunch target and run `m`?)" +
657                    f": {local_image_path}.\n ")
658        else:
659            local_image_path = local_image_arg
660
661        if os.path.isfile(local_image_path):
662            self._local_image_artifact = local_image_arg
663            # Since file is provided and I assume it's a zip, so print the
664            # warning message.
665            utils.PrintColorString(_LOCAL_ZIP_WARNING_MSG,
666                                   utils.TextColors.WARNING)
667        else:
668            self._local_image_dir = local_image_path
669
670    def _ProcessRemoteBuildArgs(self, args):
671        """Get the remote build args.
672
673        Some of the acloud magic happens here, we will infer some of these
674        values if the user hasn't specified them.
675
676        Args:
677            args: Namespace object from argparse.parse_args.
678        """
679        self._remote_image[constants.BUILD_BRANCH] = args.branch
680        if not self._remote_image[constants.BUILD_BRANCH]:
681            self._remote_image[constants.BUILD_BRANCH] = self._GetBuildBranch(
682                args.build_id, args.build_target)
683
684        self._remote_image[constants.BUILD_TARGET] = args.build_target
685        if not self._remote_image[constants.BUILD_TARGET]:
686            self._remote_image[constants.BUILD_TARGET] = self._GetBuildTarget(
687                args, self._remote_image[constants.BUILD_BRANCH])
688        else:
689            # If flavor isn't specified, try to infer it from build target,
690            # if we can't, just default to phone flavor.
691            self._flavor = args.flavor or self._GetFlavorFromString(
692                self._remote_image[constants.BUILD_TARGET]) or constants.FLAVOR_PHONE
693            # infer avd_type from build_target.
694            for avd_type, avd_type_abbr in constants.AVD_TYPES_MAPPING.items():
695                if re.match(r"(.*_)?%s_" % avd_type_abbr,
696                            self._remote_image[constants.BUILD_TARGET]):
697                    self._avd_type = avd_type
698                    break
699
700        self._remote_image[constants.BUILD_ID] = args.build_id
701        if not self._remote_image[constants.BUILD_ID]:
702            build_client = android_build_client.AndroidBuildClient(
703                auth.CreateCredentials(self._cfg))
704
705            self._remote_image[constants.BUILD_ID] = build_client.GetLKGB(
706                self._remote_image[constants.BUILD_TARGET],
707                self._remote_image[constants.BUILD_BRANCH])
708
709        # Process system image, kernel image, bootloader, and otatools.
710        self._system_build_info = {constants.BUILD_ID: args.system_build_id,
711                                   constants.BUILD_BRANCH: args.system_branch,
712                                   constants.BUILD_TARGET: args.system_build_target}
713        self._ota_build_info = {constants.BUILD_ID: args.ota_build_id,
714                                constants.BUILD_BRANCH: args.ota_branch,
715                                constants.BUILD_TARGET: args.ota_build_target}
716        self._kernel_build_info = {constants.BUILD_ID: args.kernel_build_id,
717                                   constants.BUILD_BRANCH: args.kernel_branch,
718                                   constants.BUILD_TARGET: args.kernel_build_target}
719        self._boot_build_info = {constants.BUILD_ID: args.boot_build_id,
720                                 constants.BUILD_BRANCH: args.boot_branch,
721                                 constants.BUILD_TARGET: args.boot_build_target,
722                                 constants.BUILD_ARTIFACT: args.boot_artifact}
723        self._bootloader_build_info = {
724            constants.BUILD_ID: args.bootloader_build_id,
725            constants.BUILD_BRANCH: args.bootloader_branch,
726            constants.BUILD_TARGET: args.bootloader_build_target}
727        self._android_efi_loader_build_info = {
728            constants.BUILD_ID: args.android_efi_loader_build_id,
729            constants.BUILD_ARTIFACT: args.android_efi_loader_artifact}
730        self._host_package_build_info = {
731            constants.BUILD_ID: args.host_package_build_id,
732            constants.BUILD_BRANCH: args.host_package_branch,
733            constants.BUILD_TARGET: args.host_package_build_target}
734
735    @staticmethod
736    def _CheckCFBuildTarget(instance_type):
737        """Check build target for the given instance type
738
739        Args:
740            instance_type: String of instance type
741
742        Raises:
743            errors.GetLocalImageError if the pattern is not match with
744                current build target.
745        """
746        build_target = utils.GetBuildEnvironmentVariable(
747            constants.ENV_BUILD_TARGET)
748        pattern = constants.CF_AVD_BUILD_TARGET_PATTERN_MAPPING[instance_type]
749        if pattern not in build_target:
750            utils.PrintColorString(
751                "%s is not a %s target (Try lunching a proper cuttlefish "
752                "target and running 'm')" % (build_target, pattern),
753                utils.TextColors.WARNING)
754
755    @staticmethod
756    def _GetGitRemote():
757        """Get the remote repo.
758
759        We'll go to a project we know exists (tools/acloud) and grab the git
760        remote output from there.
761
762        Returns:
763            remote: String, git remote (e.g. "aosp").
764        """
765        try:
766            android_build_top = os.environ[constants.ENV_ANDROID_BUILD_TOP]
767        except KeyError as e:
768            raise errors.GetAndroidBuildEnvVarError(
769                "Could not get environment var: %s\n"
770                "Try to run '#source build/envsetup.sh && lunch <target>'"
771                % _ENV_ANDROID_BUILD_TOP) from e
772
773        acloud_project = os.path.join(android_build_top, "tools", "acloud")
774        return EscapeAnsi(utils.CheckOutput(_COMMAND_GIT_REMOTE,
775                                            cwd=acloud_project).strip())
776
777    def _GetBuildBranch(self, build_id, build_target):
778        """Infer build branch if user didn't specify branch name.
779
780        Args:
781            build_id: String, Build id, e.g. "2263051", "P2804227"
782            build_target: String, the build target, e.g. cf_x86_phone-userdebug
783
784        Returns:
785            String, name of build branch.
786        """
787        # Infer branch from build_target and build_id
788        if build_id and build_target:
789            build_client = android_build_client.AndroidBuildClient(
790                auth.CreateCredentials(self._cfg))
791            return build_client.GetBranch(build_target, build_id)
792
793        return self._GetBranchFromRepo()
794
795    def _GetBranchFromRepo(self):
796        """Get branch information from command "repo info".
797
798        If branch can't get from "repo info", it will be set as default branch
799        "aosp-main".
800
801        Returns:
802            branch: String, git branch name. e.g. "aosp-main"
803        """
804        branch = None
805        # TODO(149460014): Migrate acloud to py3, then remove this
806        # workaround.
807        env = os.environ.copy()
808        env.pop("PYTHONPATH", None)
809        logger.info("Running command \"%s\"", _COMMAND_REPO_INFO)
810        # TODO(154173071): Migrate acloud to py3, then apply Popen to append with encoding
811        process = subprocess.Popen(_COMMAND_REPO_INFO, shell=True, stdin=None,
812                                   stdout=subprocess.PIPE,
813                                   stderr=subprocess.STDOUT, env=env,
814                                   universal_newlines=True)
815        timer = threading.Timer(_REPO_TIMEOUT, process.kill)
816        timer.start()
817        stdout, _ = process.communicate()
818        if stdout:
819            for line in stdout.splitlines():
820                match = _BRANCH_RE.match(EscapeAnsi(line))
821                if match:
822                    branch_prefix = _BRANCH_PREFIX.get(self._GetGitRemote(),
823                                                       _DEFAULT_BRANCH_PREFIX)
824                    branch = branch_prefix + match.group("branch")
825        timer.cancel()
826        if branch:
827            return branch
828        utils.PrintColorString(
829            "Unable to determine your repo branch, defaulting to %s"
830            % _DEFAULT_BRANCH, utils.TextColors.WARNING)
831        return _DEFAULT_BRANCH
832
833    def _GetBuildTarget(self, args, branch):
834        """Infer build target if user doesn't specified target name.
835
836        Target = {REPO_PREFIX}{avd_type}_{bitness}_{flavor}-
837            {DEFAULT_BUILD_TARGET_TYPE}.
838        Example target: aosp_cf_x86_64_phone-userdebug
839
840        Args:
841            args: Namespace object from argparse.parse_args.
842            branch: String, name of build branch.
843
844        Returns:
845            build_target: String, name of build target.
846        """
847        branch_prefix = re.split("-|_", branch)[0]
848        return "%s%s_%s_%s%s-%s" % (
849            _BRANCH_TARGET_PREFIX.get(branch_prefix, ""),
850            constants.AVD_TYPES_MAPPING[args.avd_type],
851            _DEFAULT_BUILD_BITNESS, self._flavor,
852            _BRANCH_TARGET_TRUNK_STAGEING.get(branch, ""),
853            _DEFAULT_BUILD_TYPE)
854
855    @property
856    def instance_type(self):
857        """Return the instance type."""
858        return self._instance_type
859
860    @property
861    def image_source(self):
862        """Return the image type."""
863        return self._image_source
864
865    @property
866    def hw_property(self):
867        """Return the hw_property."""
868        return self._hw_property
869
870    @property
871    def hw_customize(self):
872        """Return the hw_customize."""
873        return self._hw_customize
874
875    @property
876    def local_image_dir(self):
877        """Return local image dir."""
878        return self._local_image_dir
879
880    @property
881    def local_image_artifact(self):
882        """Return local image artifact."""
883        return self._local_image_artifact
884
885    @property
886    def local_instance_dir(self):
887        """Return local instance directory."""
888        return self._local_instance_dir
889
890    @property
891    def local_kernel_image(self):
892        """Return local kernel image path."""
893        return self._local_kernel_image
894
895    @property
896    def local_system_image(self):
897        """Return local system image path."""
898        return self._local_system_image
899
900    @property
901    def local_system_dlkm_image(self):
902        """Return local system_dlkm image path."""
903        return self._local_system_dlkm_image
904
905    @property
906    def local_vendor_image(self):
907        """Return local vendor image path."""
908        return self._local_vendor_image
909
910    @property
911    def local_vendor_boot_image(self):
912        """Return local vendor boot image path."""
913        return self._local_vendor_boot_image
914
915    @property
916    def local_trusty_image(self):
917        """Return local trusty qemu package path."""
918        return self._local_trusty_image
919
920    @property
921    def local_tool_dirs(self):
922        """Return a list of local tool directories."""
923        return self._local_tool_dirs
924
925    @property
926    def avd_type(self):
927        """Return the avd type."""
928        return self._avd_type
929
930    @property
931    def autoconnect(self):
932        """autoconnect.
933
934        args.autoconnect could pass as Boolean or String.
935
936        Return: Boolean, True only if self._autoconnect is not False.
937        """
938        return self._autoconnect is not False
939
940    @property
941    def connect_adb(self):
942        """Auto-connect to adb.
943
944        Return: Boolean, whether autoconnect is enabled.
945        """
946        return self._autoconnect is not False
947
948    @property
949    def connect_vnc(self):
950        """Launch vnc.
951
952        Return: Boolean, True if self._autoconnect is 'vnc'.
953        """
954        return self._autoconnect == constants.INS_KEY_VNC
955
956    @property
957    def connect_webrtc(self):
958        """Auto-launch webRTC AVD on the browser.
959
960        Return: Boolean, True if args.autoconnect is "webrtc".
961        """
962        return self._autoconnect == constants.INS_KEY_WEBRTC
963
964    @property
965    def unlock_screen(self):
966        """Return unlock_screen."""
967        return self._unlock_screen
968
969    @property
970    def remote_image(self):
971        """Return the remote image."""
972        return self._remote_image
973
974    @property
975    def remote_fetch(self):
976        """Fetch cvd in remote host.
977
978        Return: Boolean, whether fetch cvd in remote host.
979        """
980        return self._remote_fetch is True
981
982    @property
983    def fetch_cvd_wrapper(self):
984        """use fetch_cvd wrapper
985
986        Return: Boolean, whether fetch cvd in remote host.
987        """
988        return self._fetch_cvd_wrapper
989
990    @property
991    def fetch_cvd_version(self):
992        """Return fetch_cvd_version."""
993        return self._fetch_cvd_version
994
995    @property
996    def num(self):
997        """Return num of instances."""
998        return self._num_of_instances
999
1000    @property
1001    def num_avds_per_instance(self):
1002        """Return num_avds_per_instance."""
1003        return self._num_avds_per_instance
1004
1005    @property
1006    def report_internal_ip(self):
1007        """Return report internal ip."""
1008        return self._report_internal_ip
1009
1010    @property
1011    def disable_external_ip(self):
1012        """Return disable_external_ip."""
1013        return self._disable_external_ip
1014
1015    @property
1016    def kernel_build_info(self):
1017        """Return kernel build info."""
1018        return self._kernel_build_info
1019
1020    @property
1021    def boot_build_info(self):
1022        """Return boot build info."""
1023        return self._boot_build_info
1024
1025    @property
1026    def bootloader_build_info(self):
1027        """Return bootloader build info."""
1028        return self._bootloader_build_info
1029
1030    @property
1031    def android_efi_loader_build_info(self):
1032        """Return android efi loader build info."""
1033        return self._android_efi_loader_build_info
1034
1035    @property
1036    def flavor(self):
1037        """Return flavor."""
1038        return self._flavor
1039
1040    @property
1041    def cfg(self):
1042        """Return cfg instance."""
1043        return self._cfg
1044
1045    @property
1046    def image_download_dir(self):
1047        """Return image download dir."""
1048        return self._image_download_dir
1049
1050    @image_download_dir.setter
1051    def image_download_dir(self, value):
1052        """Set image download dir."""
1053        self._image_download_dir = value
1054
1055    @property
1056    def serial_log_file(self):
1057        """Return serial log file path."""
1058        return self._serial_log_file
1059
1060    @property
1061    def disk_type(self):
1062        """Return disk type."""
1063        return self._disk_type
1064
1065    @property
1066    def base_instance_num(self):
1067        """Return base instance num."""
1068        return self._base_instance_num
1069
1070    @property
1071    def gpu(self):
1072        """Return gpu."""
1073        return self._gpu
1074
1075    @property
1076    def emulator_build_id(self):
1077        """Return emulator_build_id."""
1078        return self._emulator_build_id
1079
1080    @property
1081    def emulator_build_target(self):
1082        """Return emulator_build_target."""
1083        return self._emulator_build_target
1084
1085    @property
1086    def emulator_zip(self):
1087        """Return emulator_zip."""
1088        return self._emulator_zip
1089
1090    @property
1091    def client_adb_port(self):
1092        """Return the client adb port."""
1093        return self._client_adb_port
1094
1095    @property
1096    def stable_host_image_name(self):
1097        """Return the Cuttlefish host image name."""
1098        return self._stable_host_image_name
1099
1100    @property
1101    def stable_cheeps_host_image_name(self):
1102        """Return the Cheeps host image name."""
1103        return self._stable_cheeps_host_image_name
1104
1105    # pylint: disable=invalid-name
1106    @property
1107    def stable_cheeps_host_image_project(self):
1108        """Return the project hosting the Cheeps host image."""
1109        return self._stable_cheeps_host_image_project
1110
1111    @property
1112    def username(self):
1113        """Return username."""
1114        return self._username
1115
1116    @property
1117    def password(self):
1118        """Return password."""
1119        return self._password
1120
1121    @property
1122    def cheeps_betty_image(self):
1123        """Return cheeps_betty_image."""
1124        return self._cheeps_betty_image
1125
1126    @property
1127    def cheeps_features(self):
1128        """Return cheeps_features."""
1129        return self._cheeps_features
1130
1131    @property
1132    def boot_timeout_secs(self):
1133        """Return boot_timeout_secs."""
1134        return self._boot_timeout_secs
1135
1136    @property
1137    def ins_timeout_secs(self):
1138        """Return ins_timeout_secs."""
1139        return self._ins_timeout_secs
1140
1141    @property
1142    def ota_build_info(self):
1143        """Return ota_build_info."""
1144        return self._ota_build_info
1145
1146    @property
1147    def host_package_build_info(self):
1148        """Return host_package_build_info."""
1149        return self._host_package_build_info
1150
1151    @property
1152    def system_build_info(self):
1153        """Return system_build_info."""
1154        return self._system_build_info
1155
1156    @property
1157    def local_instance_id(self):
1158        """Return local_instance_id."""
1159        return self._local_instance_id
1160
1161    @property
1162    def instance_name_to_reuse(self):
1163        """Return instance_name_to_reuse."""
1164        return self._instance_name_to_reuse
1165
1166    @property
1167    def remote_host(self):
1168        """Return host."""
1169        return self._remote_host
1170
1171    @property
1172    def remote_image_dir(self):
1173        """Return remote_image_dir."""
1174        return self._remote_image_dir
1175
1176    @property
1177    def host_user(self):
1178        """Return host_user."""
1179        return self._host_user
1180
1181    @property
1182    def host_ssh_private_key_path(self):
1183        """Return host_ssh_private_key_path."""
1184        return self._host_ssh_private_key_path
1185
1186    @property
1187    def no_pull_log(self):
1188        """Return no_pull_log."""
1189        return self._no_pull_log
1190
1191    @property
1192    def mkcert(self):
1193        """Return mkcert."""
1194        return self._mkcert
1195
1196    @property
1197    def gce_metadata(self):
1198        """Return gce_metadata."""
1199        return self._gce_metadata
1200
1201    @property
1202    def gce_only(self):
1203        """Return gce_only."""
1204        return self._gce_only
1205
1206    @property
1207    def oxygen(self):
1208        """Return oxygen."""
1209        return self._oxygen
1210
1211    @property
1212    def openwrt(self):
1213        """Return openwrt."""
1214        return self._openwrt
1215
1216    @property
1217    def use_launch_cvd(self):
1218        """Return use_launch_cvd."""
1219        return self._use_launch_cvd
1220
1221    @property
1222    def launch_args(self):
1223        """Return launch_args."""
1224        return self._launch_args
1225
1226    @property
1227    def cvd_host_package(self):
1228        """Return cvd_host_package."""
1229        return self._cvd_host_package
1230
1231    @property
1232    def trusty_host_package(self):
1233        """Return trusty_host_package."""
1234        return self._trusty_host_package
1235
1236    @property
1237    def trusty_build_info(self):
1238        """Return trusty_build_info."""
1239        return self._trusty_build_info
1240
1241    @property
1242    def extra_files(self):
1243        """Return extra_files."""
1244        return self._extra_files
1245
1246    @property
1247    def force_sync(self):
1248        """Return force_sync."""
1249        return self._force_sync
1250
1251    @property
1252    def webrtc_device_id(self):
1253        """Return webrtc_device_id."""
1254        return self._webrtc_device_id
1255
1256    @property
1257    def connect_hostname(self):
1258        """Return connect_hostname"""
1259        return self._connect_hostname
1260