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