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