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