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