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', default='', 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 build_id_str = f'-{args.build_id}' if args.build_id else '' 86 merged_target_files = os.path.join( 87 args.output_dir, 88 f'{args.target}-target_files{build_id_str}.zip') 89 command = [ 90 os.path.join(otatools, 'bin', 'merge_target_files'), 91 '--path', otatools, 92 '--framework-target-files', framework_target_files, 93 '--vendor-target-files', vendor_target_files, 94 '--output-target-files', merged_target_files, 95 '--avb-resolve-rollback-index-location-conflict' 96 ] 97 subprocess.run(command, check=True) 98 99 # create images from the merged target files 100 img_zip_path = os.path.join(args.output_dir, 101 f'{args.target}-img{build_id_str}.zip') 102 command = [ 103 os.path.join(otatools, 'bin', 'img_from_target_files'), 104 merged_target_files, 105 img_zip_path] 106 subprocess.run(command, check=True) 107 108 # merge CHD debug sepolicy 109 # TODO (b/315474132): remove this when the CHD sepolicy issue is resolved. 110 chd_sepolicy = None 111 try: 112 chd_sepolicy = merge_chd_sepolicy( 113 framework_target_files, vendor_target_files, otatools, args.output_dir) 114 except Exception as error: 115 print(f'Warning - cannot generate chd_merged_sepolicy: {error}') 116 117 # copy files 118 copy_files(args.copy_file, args.output_dir) 119 120 # build the CHD vendor boot debug image by adding chd_sepolicy and 121 # chd_debug_prop (if present) into the Cuttlefish's vendor_boot-debug.img. 122 files_to_add = [] 123 if chd_sepolicy and os.path.exists(chd_sepolicy): 124 files_to_add.append(f'{chd_sepolicy}:precompiled_sepolicy') 125 chd_debug_prop = os.path.join(args.output_dir, 'chd_debug.prop') 126 if os.path.exists(chd_debug_prop): 127 # rename the debug prop file as `adb_debug.prop` because this is the 128 # file name that property init expects. 129 files_to_add.append(f'{chd_debug_prop}:adb_debug.prop') 130 131 cf_debug_img = os.path.join(args.output_dir, 'vendor_boot-debug.img') 132 chd_debug_image_userdebug = 'vendor_boot-chd_debug.img' 133 chd_debug_image_user = 'vendor_boot-chd_debug_user.img' 134 if os.path.exists(cf_debug_img): 135 for image_name in [chd_debug_image_userdebug, chd_debug_image_user]: 136 image_path = os.path.join(args.output_dir, image_name) 137 image_dir = os.path.join(temp_dir, image_name) 138 os.mkdir(image_dir) 139 image_option = ImageOptions( 140 input_image=cf_debug_img, 141 output_image=image_path, 142 otatools_dir=otatools, 143 temp_dir=image_dir, 144 files_to_add=files_to_add) 145 146 # Remove userdebug_plat_sepolicy.cil from CHD's debug ramdisk to build a 147 # debug ramdisk for user builds. 148 if image_name == chd_debug_image_user: 149 image_option.files_to_remove = ['userdebug_plat_sepolicy.cil'] 150 151 try: 152 build_chd_debug_ramdisk(image_option) 153 except Exception as error: 154 print(f'Warning - cannot build {image_name}: {error}') 155 156 157if __name__ == '__main__': 158 with tempfile.TemporaryDirectory() as temp_dir: 159 run(temp_dir) 160