#!/usr/bin/env python3 # # Copyright 2023 - The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "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 "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. """Mix shared system images with a super image and disable vbmeta. Example: ./development/gsi/repack_super_image/mix_ssi_with_device_image.py \ --device-image-files aosp_cf_arm64_phone-img.zip \ --ssi-files ssi-target_files.zip \ --misc-info misc_info.txt \ --ota-tools otatools.zip \ --output-dir ./output or ANDROID_PRODUCT_OUT=out/target/product/vsoc_arm64 \ ANDROID_HOST_OUT=out/host/linux-x86/ \ ./development/gsi/repack_super_image/mix_ssi_with_device_image.py \ --ssi-files out/target/product/ssi/IMAGES/ \ --output-dir ./output """ import argparse import os import shutil import subprocess import tempfile import zipfile import repack_super_image SSI_PARTITIONS = ["system", "system_ext", "product"] def add_arguments(parser): parser.add_argument("--device-image-files", default=os.getenv("ANDROID_PRODUCT_OUT"), help="The path to the img zip or directory containing " "device images to be mixed with the given SSI. " "Caution: If this is a directory, super.img and " "vbmeta.img under this directory will be modified. " "Default: $ANDROID_PRODUCT_OUT") parser.add_argument("--ssi-files", required=True, help="The path to the target_files zip or directory " "containing shared system images for mixing.") parser.add_argument("--ota-tools", default=os.getenv("ANDROID_HOST_OUT"), help="The path to the device OTA tools zip or directory " "for mixing images. " "Default: $ANDROID_HOST_OUT") parser.add_argument("--misc-info", help="The device misc_info.txt for mixing images. " "Default: {args.device_image_files}/misc_info.txt.") parser.add_argument("--output-dir", help="The output directory for the mixed image. " "Default: {args.device_image_files}.") def unzip_ssi_images(ssi_target_files, output_dir): """Unzip shared system images from the target files zipfile.""" if not os.path.exists(ssi_target_files): raise FileNotFoundError(f"{ssi_target_files} does not exist.") with zipfile.ZipFile(ssi_target_files) as ssi_zip: for part_img in SSI_PARTITIONS: try: ssi_zip.extract(f"IMAGES/{part_img}.img", output_dir) except KeyError: pass return os.path.join(output_dir, "IMAGES") def unzip_super_images(device_img_artifact, output_dir): """Unzip super.img from the device image artifact zipfile.""" if not os.path.exists(device_img_artifact): raise FileNotFoundError(f"{device_img_artifact} does not exist.") with zipfile.ZipFile(device_img_artifact) as device_img_zip: device_img_zip.extract("super.img", output_dir) return os.path.join(output_dir, "super.img") def collect_ssi(ssi_dir): """Collect system, system_ext and product images for mixing.""" ssi_imgs = dict() for part_img in SSI_PARTITIONS: img_path = os.path.join(ssi_dir, f"{part_img}.img") if os.path.exists(img_path): ssi_imgs[part_img] = img_path else: # system.img is mandatory, while other images are optional in SSI if part_img == "system": raise FileNotFoundError(f"{img_path} does not exist.") else: print(f"No {part_img}.img") ssi_imgs[part_img] = "" return ssi_imgs def main(): parser = argparse.ArgumentParser() add_arguments(parser) args = parser.parse_args() if not args.device_image_files: raise ValueError("device image path is not set.") output_dir = args.output_dir if args.output_dir else args.device_image_files if not os.path.isdir(output_dir): raise ValueError(f"output directory {output_dir} is not valid.") print(f"Output directory {output_dir}") temp_dirs = [] try: if os.path.isdir(args.ssi_files): ssi_dir = args.ssi_files else: temp_dir = tempfile.mkdtemp(prefix="ssi_") temp_dirs.append(temp_dir) ssi_dir = unzip_ssi_images(args.ssi_files, temp_dir) device_misc_info = args.misc_info if os.path.isdir(args.device_image_files): device_image_dir = args.device_image_files super_img = os.path.join(device_image_dir, "super.img") if not device_misc_info: device_misc_info = os.path.join(device_image_dir, "misc_info.txt") else: super_img = unzip_super_images(args.device_image_files, output_dir) if not device_misc_info or not os.path.exists(device_misc_info): raise FileNotFoundError(f"misc_info {device_misc_info} does not exist.") if not os.path.exists(super_img): raise FileNotFoundError(f"{super_img} does not exist.") if not args.ota_tools or not os.path.exists(args.ota_tools): raise FileNotFoundError(f"otatools {args.ota_tools} does not exist.") if os.path.isdir(args.ota_tools): ota_tools_dir = args.ota_tools else: print("Unzip OTA tools.") ota_tools_dir = tempfile.mkdtemp(prefix="ota_tools") temp_dirs.append(ota_tools_dir) with zipfile.ZipFile(args.ota_tools) as ota_tools_zip: repack_super_image.unzip_ota_tools(ota_tools_zip, ota_tools_dir) mix_part_imgs = collect_ssi(ssi_dir) output_super_img = os.path.join(output_dir, "super.img") repack_super_image.repack_super_image(ota_tools_dir, device_misc_info, super_img, mix_part_imgs, output_super_img) print(f"Created mixed super.img at {output_super_img}") avbtool_path = os.path.join(ota_tools_dir, "bin", "avbtool") vbmeta_img = os.path.join(output_dir, "vbmeta.img") subprocess.check_call([avbtool_path, "make_vbmeta_image", "--flag", "2", "--output", vbmeta_img]) print(f"Created vbmeta.img at {vbmeta_img}") finally: for temp_dir in temp_dirs: shutil.rmtree(temp_dir, ignore_errors=True) if __name__ == "__main__": main()