1#!/usr/bin/env python3 2# 3# Copyright 2024 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the', help="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', help="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 17import glob 18import logging 19import os 20from pathlib import Path 21import platform 22import re 23import shutil 24import zipfile 25 26from environment import get_default_environment 27from tasks.task import Task 28from utils import ( 29 AOSP_ROOT, 30 EMULATOR_ARTIFACT_PATH, 31 binary_extension, 32 run, 33) 34 35OBJS_DIR = AOSP_ROOT / "tools" / "netsim" / "objs" 36PLATFORM_SYSTEM = platform.system() 37PLATFORM_MACHINE = platform.machine() 38 39 40class InstallEmulatorTask(Task): 41 42 def __init__(self, args): 43 super().__init__("InstallEmulator") 44 self.buildbot = args.buildbot 45 self.out_dir = args.out_dir 46 # Local fetching use only - default to emulator-linux_x64 47 self.target = args.emulator_target 48 # Local Emulator directory 49 self.local_emulator_dir = args.local_emulator_dir 50 51 def do_run(self): 52 install_emulator_manager = InstallEmulatorManager( 53 self.buildbot, self.out_dir, self.target, self.local_emulator_dir 54 ) 55 return install_emulator_manager.process() 56 57 58class InstallEmulatorManager: 59 """Manager for installing emulator artifacts into netsim build 60 61 The InstallEmulatorManager checks if the conditions are met 62 to fetch the emulator artifact zip file and installs it with the 63 newly built netsim. The manager contains processing logic for 64 both local and pre/post submit. 65 66 Attributes: 67 buildbot: A boolean indicating if it's being invoked with Android Build Bots 68 out_dir: A str or None representing the directory of out/. This is priamrily 69 used for Android Build Bots. 70 """ 71 72 def __init__(self, buildbot, out_dir, target, local_emulator_dir): 73 """Initializes the instances based on environment 74 75 Args: 76 buildbot: Defines if it's being invoked with Build Bots 77 out_dir: Defines the out directory of the build environment 78 target: The emulator build target to install 79 """ 80 self.buildbot = buildbot 81 self.out_dir = out_dir 82 self.target = target 83 self.local_emulator_dir = local_emulator_dir 84 85 def __os_name_fetch(self): 86 """Obtains the os substring of the emulator artifact""" 87 if PLATFORM_SYSTEM == "Linux" and PLATFORM_MACHINE == "x86_64": 88 return "linux" 89 elif PLATFORM_SYSTEM == "Darwin": 90 if PLATFORM_MACHINE == "x86_64": 91 return "darwin" 92 elif PLATFORM_MACHINE == "arm64": 93 return "darwin_aarch64" 94 elif PLATFORM_SYSTEM == "Windows": 95 return "windows" 96 else: 97 logging.info("Unsupported OS:", PLATFORM_SYSTEM, ",", PLATFORM_MACHINE) 98 return None 99 100 def __prerequisites(self) -> bool: 101 """Prerequisite checks for invalid cases""" 102 if self.buildbot: 103 # out_dir is not provided 104 if not self.out_dir: 105 logging.info("Error: please specify '--out_dir' when using buildbots") 106 return False 107 # If out_dir does not exist 108 elif not Path(self.out_dir).exists(): 109 logging.info(f"Error: {self.out_dir} does not exist") 110 return False 111 else: 112 # Without buildbots, this scripts is only runnable on Linux 113 # TODO: support local builds for Mac and Windows 114 if PLATFORM_SYSTEM != "Linux" and not self.local_emulator_dir: 115 logging.info( 116 "The local case only works for Linux if you don't have" 117 " --local_emulator_dir specified" 118 ) 119 return False 120 # Check if the netsim has been built prior to install_emulator 121 if not ( 122 OBJS_DIR.exists() 123 and (OBJS_DIR / binary_extension("netsim")).exists() 124 and (OBJS_DIR / binary_extension("netsimd")).exists() 125 ): 126 logging.info( 127 "Please run 'scripts/build_tools.sh --Compile' " 128 "before running InstallEmulator" 129 ) 130 return False 131 return True 132 133 def __unzip_emulator_artifacts(self, os_name_artifact) -> bool: 134 """unzips the emulator artifacts inside EMULATOR_ARTIFACT_PATH""" 135 # Unzipping emulator artifacts 136 zip_file_exists = False 137 for filename in os.listdir(EMULATOR_ARTIFACT_PATH): 138 # Check if the filename matches the pattern 139 if re.match( 140 rf"^sdk-repo-{os_name_artifact}-emulator-\d+\.zip$", filename 141 ): 142 zip_file_exists = True 143 logging.info(f"Unzipping {filename}...") 144 with zipfile.ZipFile(EMULATOR_ARTIFACT_PATH / filename, "r") as zip_ref: 145 zip_ref.extractall(EMULATOR_ARTIFACT_PATH) 146 # Preserve permission bits 147 for info in zip_ref.infolist(): 148 filename = EMULATOR_ARTIFACT_PATH / info.filename 149 original_permissions = info.external_attr >> 16 150 if original_permissions: 151 os.chmod(filename, original_permissions) 152 # Log and return False if the artifact does not exist 153 if not zip_file_exists: 154 logging.info("Emulator artifact prebuilt is not found!") 155 return False 156 # Remove all zip files 157 files = glob.glob( 158 str( 159 EMULATOR_ARTIFACT_PATH 160 / f"sdk-repo-{os_name_artifact}-emulator-*.zip" 161 ) 162 ) 163 for file in files: 164 os.remove(EMULATOR_ARTIFACT_PATH / file) 165 return True 166 167 def __copy_artifacts(self, emulator_filepath): 168 """Copy artifacts into desired location 169 170 In the local case, the emulator artifacts get copied into objs/ 171 In the buildbot case, the netsim artifacts get copied into 172 EMULATOR_ARTIFACT_PATH 173 174 Note that the downloaded netsim artifacts are removed before copying. 175 """ 176 # Remove all downloaded netsim artifacts 177 files = glob.glob(str(emulator_filepath / "netsim*")) 178 for fname in files: 179 file = emulator_filepath / fname 180 if os.path.isdir(file): 181 shutil.rmtree(file) 182 else: 183 os.remove(file) 184 # Copy artifacts 185 if self.buildbot: 186 shutil.copytree( 187 Path(self.out_dir) / "distribution" / "emulator", 188 emulator_filepath, 189 symlinks=True, 190 dirs_exist_ok=True, 191 ) 192 else: 193 shutil.copytree( 194 emulator_filepath, 195 OBJS_DIR, 196 symlinks=True, 197 dirs_exist_ok=True, 198 ) 199 shutil.copytree( 200 emulator_filepath, 201 OBJS_DIR / "distribution" / "emulator", 202 symlinks=True, 203 dirs_exist_ok=True, 204 ) 205 206 def process(self) -> bool: 207 """Process the emulator installation 208 209 The process will terminate if sub-function calls returns 210 a None or False 211 """ 212 # Obtain OS name of the artifact 213 os_name_artifact = self.__os_name_fetch() 214 if not os_name_artifact: 215 return False 216 217 # Invalid Case checks 218 if not self.__prerequisites(): 219 return False 220 221 if self.local_emulator_dir: 222 # If local_emulator_dir is provided, copy the artifacts from this directory. 223 self.__copy_artifacts(Path(self.local_emulator_dir)) 224 else: 225 # Artifact fetching for local case 226 if not self.buildbot: 227 # Simulating the shell command 228 run( 229 [ 230 "/google/data/ro/projects/android/fetch_artifact", 231 "--latest", 232 "--target", 233 self.target, 234 "--branch", 235 "aosp-emu-master-dev", 236 "sdk-repo-linux-emulator-*.zip", 237 ], 238 get_default_environment(AOSP_ROOT), 239 "install_emulator", 240 cwd=EMULATOR_ARTIFACT_PATH, 241 ) 242 243 # Unzipping emulator artifacts and remove zip files 244 if not self.__unzip_emulator_artifacts(os_name_artifact): 245 return False 246 247 # Copy artifacts after removing downloaded netsim artifacts 248 self.__copy_artifacts(EMULATOR_ARTIFACT_PATH / "emulator") 249 250 # Remove the EMULATOR_ARTIFACT_PATH in local case 251 if not self.buildbot: 252 shutil.rmtree(EMULATOR_ARTIFACT_PATH, ignore_errors=True) 253 254 logging.info("Emulator installation completed!") 255 return True 256