1#!/usr/bin/python3 2# 3# Copyright (C) 2023 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); you may not 6# use this file except in compliance with the License. You may obtain a copy of 7# 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, WITHOUT 13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14# License for the specific language governing permissions and limitations under 15# the License. 16 17import argparse 18import glob 19import os 20import subprocess 21import tempfile 22 23from build_chd_debug_ramdisk import build_chd_debug_ramdisk, ImageOptions 24from build_chd_utils import copy_files, merge_chd_sepolicy, unzip_otatools 25 26"""Test command: 27 28WORKSPACE=out/dist && \ 29python3 tools/treble/cuttlefish/build_cf_hybrid_device.py \ 30 --build_id 123456 \ 31 --otatools_zip $WORKSPACE/otatools.zip \ 32 --target chd-target \ 33 --output_dir $WORKSPACE \ 34 --framework_target_files_zip $WORKSPACE/device-target_files-*.zip \ 35 --vendor_target_files_zip $WORKSPACE/cf_arm64_only_phone-target_files-*.zip 36""" 37 38 39def _parse_args() -> argparse.Namespace: 40 """Parse the arguments for building cuttlefish hybrid devices. 41 42 Returns: 43 An object of the parsed arguments. 44 """ 45 parser = argparse.ArgumentParser() 46 47 parser.add_argument('--build_id', required=True, 48 help='Build id.') 49 parser.add_argument('--target', required=True, 50 help='Target name of the cuttlefish hybrid build.') 51 parser.add_argument('--otatools_zip', required=True, 52 help='Path to the otatools.zip.') 53 parser.add_argument('--output_dir', required=True, 54 help='Path to the output directory of the hybrid build.') 55 parser.add_argument('--framework_target_files_zip', required=True, 56 help='glob pattern of framework target_files zip.') 57 parser.add_argument('--vendor_target_files_zip', required=True, 58 help='glob pattern of vendor target_files zip.') 59 parser.add_argument('--copy_file', action='append', default=[], 60 help='The file to be copied to output directory. ' 61 'The format is <src glob pattern>:<dst path>.') 62 return parser.parse_args() 63 64 65def run(temp_dir: str) -> None: 66 args = _parse_args() 67 68 # unzip otatools 69 otatools = os.path.join(temp_dir, 'otatools') 70 unzip_otatools(args.otatools_zip, otatools) 71 72 # get framework and vendor target files 73 matched_framework_target_files = glob.glob(args.framework_target_files_zip) 74 if not matched_framework_target_files: 75 raise ValueError('framework target files zip ' 76 f'{args.framework_target_files_zip} not found.') 77 matched_vendor_target_files = glob.glob(args.vendor_target_files_zip) 78 if not matched_vendor_target_files: 79 raise ValueError('vendor target files zip ' 80 f'{args.vendor_target_files_zip} not found.') 81 82 # merge target files 83 framework_target_files = matched_framework_target_files[0] 84 vendor_target_files = matched_vendor_target_files[0] 85 merged_target_files = os.path.join( 86 args.output_dir, 87 f'{args.target}-target_files-{args.build_id}.zip') 88 command = [ 89 os.path.join(otatools, 'bin', 'merge_target_files'), 90 '--path', otatools, 91 '--framework-target-files', framework_target_files, 92 '--vendor-target-files', vendor_target_files, 93 '--output-target-files', merged_target_files, 94 '--avb-resolve-rollback-index-location-conflict' 95 ] 96 subprocess.run(command, check=True) 97 98 # create images from the merged target files 99 img_zip_path = os.path.join(args.output_dir, 100 f'{args.target}-img-{args.build_id}.zip') 101 command = [ 102 os.path.join(otatools, 'bin', 'img_from_target_files'), 103 merged_target_files, 104 img_zip_path] 105 subprocess.run(command, check=True) 106 107 # merge CHD debug sepolicy 108 # TODO (b/315474132): remove this when the CHD sepolicy issue is resolved. 109 chd_sepolicy = None 110 try: 111 chd_sepolicy = merge_chd_sepolicy( 112 framework_target_files, vendor_target_files, otatools, args.output_dir) 113 except Exception as error: 114 print(f'Warning - cannot generate chd_merged_sepolicy: {error}') 115 116 # copy files 117 copy_files(args.copy_file, args.output_dir) 118 119 # build the CHD vendor boot debug image by adding chd_sepolicy and 120 # chd_debug_prop (if present) into the Cuttlefish's vendor_boot-debug.img. 121 files_to_add = [] 122 if chd_sepolicy and os.path.exists(chd_sepolicy): 123 files_to_add.append(f'{chd_sepolicy}:precompiled_sepolicy') 124 chd_debug_prop = os.path.join(args.output_dir, 'chd_debug.prop') 125 if os.path.exists(chd_debug_prop): 126 # rename the debug prop file as `adb_debug.prop` because this is the 127 # file name that property init expects. 128 files_to_add.append(f'{chd_debug_prop}:adb_debug.prop') 129 130 cf_debug_img = os.path.join(args.output_dir, 'vendor_boot-debug.img') 131 chd_debug_image_userdebug = 'vendor_boot-chd_debug.img' 132 chd_debug_image_user = 'vendor_boot-chd_debug_user.img' 133 if os.path.exists(cf_debug_img): 134 for image_name in [chd_debug_image_userdebug, chd_debug_image_user]: 135 image_path = os.path.join(args.output_dir, image_name) 136 image_dir = os.path.join(temp_dir, image_name) 137 os.mkdir(image_dir) 138 image_option = ImageOptions( 139 input_image=cf_debug_img, 140 output_image=image_path, 141 otatools_dir=otatools, 142 temp_dir=image_dir, 143 files_to_add=files_to_add) 144 145 # Remove userdebug_plat_sepolicy.cil from CHD's debug ramdisk to build a 146 # debug ramdisk for user builds. 147 if image_name == chd_debug_image_user: 148 image_option.files_to_remove = ['userdebug_plat_sepolicy.cil'] 149 150 try: 151 build_chd_debug_ramdisk(image_option) 152 except Exception as error: 153 print(f'Warning - cannot build {image_name}: {error}') 154 155 156if __name__ == '__main__': 157 with tempfile.TemporaryDirectory() as temp_dir: 158 run(temp_dir) 159