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. 16"""Common code used by acloud create methods/classes.""" 17 18import logging 19import os 20import re 21import shutil 22 23from acloud import errors 24from acloud.internal import constants 25from acloud.internal.lib import android_build_client 26from acloud.internal.lib import auth 27from acloud.internal.lib import utils 28 29 30logger = logging.getLogger(__name__) 31 32 33def ParseKeyValuePairArgs(dict_str, item_separator=",", key_value_separator=":"): 34 """Helper function to initialize a dict object from string. 35 36 e.g. 37 cpu:2,dpi:240,resolution:1280x800 38 -> {"cpu":"2", "dpi":"240", "resolution":"1280x800"} 39 40 Args: 41 dict_str: A String to be converted to dict object. 42 item_separator: String character to separate items. 43 key_value_separator: String character to separate key and value. 44 45 Returns: 46 Dict created from key:val pairs in dict_str. 47 48 Raises: 49 error.MalformedDictStringError: If dict_str is malformed. 50 """ 51 args_dict = {} 52 if not dict_str: 53 return args_dict 54 55 for item in dict_str.split(item_separator): 56 if key_value_separator not in item: 57 raise errors.MalformedDictStringError( 58 "Expecting ':' in '%s' to make a key-val pair" % item) 59 key, value = item.split(key_value_separator) 60 if not value or not key: 61 raise errors.MalformedDictStringError( 62 "Missing key or value in %s, expecting form of 'a:b'" % item) 63 args_dict[key.strip()] = value.strip() 64 65 return args_dict 66 67 68def GetCvdHostPackage(): 69 """Get cvd host package path. 70 71 Look for the host package in $ANDROID_HOST_OUT and dist dir then verify 72 existence and get cvd host package path. 73 74 Return: 75 A string, the path to the host package. 76 77 Raises: 78 errors.GetCvdLocalHostPackageError: Can't find cvd host package. 79 """ 80 dirs_to_check = list( 81 filter(None, [ 82 os.environ.get(constants.ENV_ANDROID_SOONG_HOST_OUT), 83 os.environ.get(constants.ENV_ANDROID_HOST_OUT) 84 ])) 85 dist_dir = utils.GetDistDir() 86 if dist_dir: 87 dirs_to_check.append(dist_dir) 88 89 for path in dirs_to_check: 90 cvd_host_package = os.path.join(path, constants.CVD_HOST_PACKAGE) 91 if os.path.exists(cvd_host_package): 92 logger.debug("cvd host package: %s", cvd_host_package) 93 return cvd_host_package 94 raise errors.GetCvdLocalHostPackageError( 95 "Can't find the cvd host package (Try lunching a cuttlefish target" 96 " like aosp_cf_x86_phone-userdebug and running 'm'): \n%s" % 97 '\n'.join(dirs_to_check)) 98 99 100def FindLocalImage(path, default_name_pattern): 101 """Find an image file in the given path. 102 103 Args: 104 path: The path to the file or the parent directory. 105 default_name_pattern: A regex string, the file to look for if the path 106 is a directory. 107 108 Returns: 109 The absolute path to the image file. 110 111 Raises: 112 errors.GetLocalImageError if this method cannot find exactly one image. 113 """ 114 path = os.path.abspath(path) 115 if os.path.isdir(path): 116 names = [name for name in os.listdir(path) if 117 re.fullmatch(default_name_pattern, name)] 118 if not names: 119 raise errors.GetLocalImageError("No image in %s." % path) 120 if len(names) != 1: 121 raise errors.GetLocalImageError("More than one image in %s: %s" % 122 (path, " ".join(names))) 123 path = os.path.join(path, names[0]) 124 if os.path.isfile(path): 125 return path 126 raise errors.GetLocalImageError("%s is not a file." % path) 127 128 129def DownloadRemoteArtifact(cfg, build_target, build_id, artifact, extract_path, 130 decompress=False): 131 """Download remote artifact. 132 133 Args: 134 cfg: An AcloudConfig instance. 135 build_target: String, the build target, e.g. cf_x86_phone-userdebug. 136 build_id: String, Build id, e.g. "2263051", "P2804227" 137 artifact: String, zip image or cvd host package artifact. 138 extract_path: String, a path include extracted files. 139 decompress: Boolean, if true decompress the artifact. 140 """ 141 build_client = android_build_client.AndroidBuildClient( 142 auth.CreateCredentials(cfg)) 143 temp_file = os.path.join(extract_path, artifact) 144 build_client.DownloadArtifact( 145 build_target, 146 build_id, 147 artifact, 148 temp_file) 149 if decompress: 150 utils.Decompress(temp_file, extract_path) 151 try: 152 os.remove(temp_file) 153 logger.debug("Deleted temporary file %s", temp_file) 154 except OSError as e: 155 logger.error("Failed to delete temporary file: %s", str(e)) 156 157 158def PrepareLocalInstanceDir(instance_dir, avd_spec): 159 """Create a directory for a local cuttlefish or goldfish instance. 160 161 If avd_spec has the local instance directory, this method creates a 162 symbolic link from instance_dir to the directory. Otherwise, it creates an 163 empty directory at instance_dir. 164 165 Args: 166 instance_dir: The absolute path to the default instance directory. 167 avd_spec: AVDSpec object that provides the instance directory. 168 """ 169 if os.path.islink(instance_dir): 170 os.remove(instance_dir) 171 else: 172 shutil.rmtree(instance_dir, ignore_errors=True) 173 174 if avd_spec.local_instance_dir: 175 abs_instance_dir = os.path.abspath(avd_spec.local_instance_dir) 176 if instance_dir != abs_instance_dir: 177 os.symlink(abs_instance_dir, instance_dir) 178 return 179 if not os.path.exists(instance_dir): 180 os.makedirs(instance_dir) 181