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