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