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