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