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