| #!/usr/bin/env python3 |
| # |
| # Copyright 2024 - The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the', help="License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an', help="AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| import glob |
| import logging |
| import os |
| from pathlib import Path |
| import platform |
| import re |
| import shutil |
| import zipfile |
| |
| from environment import get_default_environment |
| from tasks.task import Task |
| from utils import ( |
| AOSP_ROOT, |
| EMULATOR_ARTIFACT_PATH, |
| binary_extension, |
| run, |
| ) |
| |
| OBJS_DIR = AOSP_ROOT / "tools" / "netsim" / "objs" |
| PLATFORM_SYSTEM = platform.system() |
| PLATFORM_MACHINE = platform.machine() |
| |
| |
| class InstallEmulatorTask(Task): |
| |
| def __init__(self, args): |
| super().__init__("InstallEmulator") |
| self.buildbot = args.buildbot |
| self.out_dir = args.out_dir |
| |
| def do_run(self): |
| install_emulator_manager = InstallEmulatorManager( |
| self.buildbot, self.out_dir |
| ) |
| return install_emulator_manager.process() |
| |
| |
| class InstallEmulatorManager: |
| """Manager for installing emulator artifacts into netsim build |
| |
| The InstallEmulatorManager checks if the conditions are met |
| to fetch the emulator artifact zip file and installs it with the |
| newly built netsim. The manager contains processing logic for |
| both local and pre/post submit. |
| |
| Attributes: |
| buildbot: A boolean indicating if it's being invoked with Android Build Bots |
| out_dir: A str or None representing the directory of out/. This is priamrily |
| used for Android Build Bots. |
| """ |
| |
| def __init__(self, buildbot, out_dir): |
| """Initializes the instances based on environment |
| |
| Args: |
| buildbot: Defines if it's being invoked with Build Bots |
| out_dir: Defines the out directory of the build environment |
| """ |
| self.buildbot = buildbot |
| self.out_dir = out_dir |
| |
| def __os_name_fetch(self): |
| """Obtains the os substring of the emulator artifact""" |
| if PLATFORM_SYSTEM == "Linux" and PLATFORM_MACHINE == "x86_64": |
| return "linux" |
| elif PLATFORM_SYSTEM == "Darwin": |
| if PLATFORM_MACHINE == "x86_64": |
| return "darwin" |
| elif PLATFORM_MACHINE == "arm64": |
| return "darwin_aarch64" |
| elif PLATFORM_SYSTEM == "Windows": |
| return "windows" |
| else: |
| logging.info("Unsupported OS:", PLATFORM_SYSTEM, ",", PLATFORM_MACHINE) |
| return None |
| |
| def __prerequisites(self) -> bool: |
| """Prerequisite checks for invalid cases""" |
| if self.buildbot: |
| # out_dir is not provided |
| if not self.out_dir: |
| logging.info("Error: please specify '--out_dir' when using buildbots") |
| return False |
| # If out_dir does not exist |
| elif not Path(self.out_dir).exists(): |
| logging.info(f"Error: {self.out_dir} does not exist") |
| return False |
| else: |
| # Without buildbots, this scripts is only runnable on Linux |
| # TODO: support local builds for Mac and Windows |
| if PLATFORM_SYSTEM != "Linux": |
| logging.info("The local case only works for Linux") |
| return False |
| # Check if the netsim has been built prior to install_emulator |
| if not ( |
| OBJS_DIR.exists() |
| and (OBJS_DIR / binary_extension("netsim")).exists() |
| and (OBJS_DIR / binary_extension("netsimd")).exists() |
| ): |
| logging.info( |
| "Please run 'scripts/build_tools.sh --Compile' " |
| "before running InstallEmulator" |
| ) |
| return False |
| return True |
| |
| def __unzip_emulator_artifacts(self, os_name_artifact) -> bool: |
| """unzips the emulator artifacts inside EMULATOR_ARTIFACT_PATH""" |
| # Unzipping emulator artifacts |
| zip_file_exists = False |
| for filename in os.listdir(EMULATOR_ARTIFACT_PATH): |
| # Check if the filename matches the pattern |
| if re.match( |
| rf"^sdk-repo-{os_name_artifact}-emulator-\d+\.zip$", filename |
| ): |
| zip_file_exists = True |
| logging.info(f"Unzipping {filename}...") |
| with zipfile.ZipFile(EMULATOR_ARTIFACT_PATH / filename, "r") as zip_ref: |
| zip_ref.extractall(EMULATOR_ARTIFACT_PATH) |
| # Preserve permission bits |
| for info in zip_ref.infolist(): |
| filename = EMULATOR_ARTIFACT_PATH / info.filename |
| original_permissions = info.external_attr >> 16 |
| if original_permissions: |
| os.chmod(filename, original_permissions) |
| # Log and return False if the artifact does not exist |
| if not zip_file_exists: |
| logging.info("Emulator artifact prebuilt is not found!") |
| return False |
| # Remove all zip files |
| files = glob.glob( |
| str( |
| EMULATOR_ARTIFACT_PATH |
| / f"sdk-repo-{os_name_artifact}-emulator-*.zip" |
| ) |
| ) |
| for file in files: |
| os.remove(EMULATOR_ARTIFACT_PATH / file) |
| return True |
| |
| def __copy_artifacts(self): |
| """Copy artifacts into desired location |
| |
| In the local case, the emulator artifacts get copied into objs/ |
| In the buildbot case, the netsim artifacts get copied into |
| EMULATOR_ARTIFACT_PATH |
| |
| Note that the downloaded netsim artifacts are removed before copying. |
| """ |
| emulator_filepath = EMULATOR_ARTIFACT_PATH / "emulator" |
| # Remove all downloaded netsim artifacts |
| files = glob.glob(str(emulator_filepath / "netsim*")) |
| for fname in files: |
| file = emulator_filepath / fname |
| if os.path.isdir(file): |
| shutil.rmtree(file) |
| else: |
| os.remove(file) |
| # Copy artifacts |
| if self.buildbot: |
| shutil.copytree( |
| Path(self.out_dir) / "distribution" / "emulator", |
| emulator_filepath, |
| symlinks=True, |
| dirs_exist_ok=True, |
| ) |
| else: |
| shutil.copytree( |
| emulator_filepath, |
| OBJS_DIR, |
| symlinks=True, |
| dirs_exist_ok=True, |
| ) |
| |
| def process(self) -> bool: |
| """Process the emulator installation |
| |
| The process will terminate if sub-function calls returns |
| a None or False |
| """ |
| # Obtain OS name of the artifact |
| os_name_artifact = self.__os_name_fetch() |
| if not os_name_artifact: |
| return False |
| |
| # Invalid Case checks |
| if not self.__prerequisites(): |
| return False |
| |
| # Artifact fetching for local case |
| if not self.buildbot: |
| # Simulating the shell command |
| run( |
| [ |
| "/google/data/ro/projects/android/fetch_artifact", |
| "--latest", |
| "--target", |
| "emulator-linux_x64", |
| "--branch", |
| "aosp-emu-master-dev", |
| "sdk-repo-linux-emulator-*.zip", |
| ], |
| get_default_environment(AOSP_ROOT), |
| "install_emulator", |
| cwd=EMULATOR_ARTIFACT_PATH, |
| ) |
| |
| # Unzipping emulator artifacts and remove zip files |
| if not self.__unzip_emulator_artifacts(os_name_artifact): |
| return False |
| |
| # Copy artifacts after removing downloaded netsim artifacts |
| self.__copy_artifacts() |
| |
| # Remove the EMULATOR_ARTIFACT_PATH in local case |
| if not self.buildbot: |
| shutil.rmtree(EMULATOR_ARTIFACT_PATH, ignore_errors=True) |
| |
| logging.info("Emulator installation completed!") |
| return True |