1#!/usr/bin/env python 2# 3# Copyright (C) 2018 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "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 "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 17""" 18Usage: build_super_image input_file output_dir_or_file 19 20input_file: one of the following: 21 - directory containing extracted target files. It will load info from 22 META/misc_info.txt and build full super image / split images using source 23 images from IMAGES/. 24 - target files package. Same as above, but extracts the archive before 25 building super image. 26 - a dictionary file containing input arguments to build. Check 27 `dump-super-image-info' for details. 28 In addition: 29 - If source images should be included in the output image (for super.img 30 and super split images), a list of "*_image" should be paths of each 31 source images. 32 33output_dir_or_file: 34 If a single super image is built (for super_empty.img, or super.img for 35 launch devices), this argument is the output file. 36 If a collection of split images are built (for retrofit devices), this 37 argument is the output directory. 38""" 39 40from __future__ import print_function 41 42import logging 43import os.path 44import shlex 45import sys 46import zipfile 47 48import common 49import sparse_img 50 51if sys.hexversion < 0x02070000: 52 print("Python 2.7 or newer is required.", file=sys.stderr) 53 sys.exit(1) 54 55logger = logging.getLogger(__name__) 56 57 58UNZIP_PATTERN = ["IMAGES/*", "META/*"] 59 60 61def GetArgumentsForImage(partition, group, image=None): 62 image_size = sparse_img.GetImagePartitionSize(image) if image else 0 63 64 cmd = ["--partition", 65 "{}:readonly:{}:{}".format(partition, image_size, group)] 66 if image: 67 cmd += ["--image", "{}={}".format(partition, image)] 68 69 return cmd 70 71 72def BuildSuperImageFromDict(info_dict, output): 73 74 cmd = [info_dict["lpmake"], 75 "--metadata-size", "65536", 76 "--super-name", info_dict["super_metadata_device"]] 77 78 ab_update = info_dict.get("ab_update") == "true" 79 retrofit = info_dict.get("dynamic_partition_retrofit") == "true" 80 block_devices = shlex.split(info_dict.get("super_block_devices", "").strip()) 81 groups = shlex.split(info_dict.get("super_partition_groups", "").strip()) 82 83 if ab_update and retrofit: 84 cmd += ["--metadata-slots", "2"] 85 elif ab_update: 86 cmd += ["--metadata-slots", "3"] 87 else: 88 cmd += ["--metadata-slots", "2"] 89 90 if ab_update and retrofit: 91 cmd.append("--auto-slot-suffixing") 92 93 for device in block_devices: 94 size = info_dict["super_{}_device_size".format(device)] 95 cmd += ["--device", "{}:{}".format(device, size)] 96 97 append_suffix = ab_update and not retrofit 98 has_image = False 99 for group in groups: 100 group_size = info_dict["super_{}_group_size".format(group)] 101 if append_suffix: 102 cmd += ["--group", "{}_a:{}".format(group, group_size), 103 "--group", "{}_b:{}".format(group, group_size)] 104 else: 105 cmd += ["--group", "{}:{}".format(group, group_size)] 106 107 partition_list = shlex.split( 108 info_dict["super_{}_partition_list".format(group)].strip()) 109 110 for partition in partition_list: 111 image = info_dict.get("{}_image".format(partition)) 112 if image: 113 has_image = True 114 115 if not append_suffix: 116 cmd += GetArgumentsForImage(partition, group, image) 117 continue 118 119 # For A/B devices, super partition always contains sub-partitions in 120 # the _a slot, because this image should only be used for 121 # bootstrapping / initializing the device. When flashing the image, 122 # bootloader fastboot should always mark _a slot as bootable. 123 cmd += GetArgumentsForImage(partition + "_a", group + "_a", image) 124 125 other_image = None 126 if partition == "system" and "system_other_image" in info_dict: 127 other_image = info_dict["system_other_image"] 128 has_image = True 129 130 cmd += GetArgumentsForImage(partition + "_b", group + "_b", other_image) 131 132 if info_dict.get("build_non_sparse_super_partition") != "true": 133 cmd.append("--sparse") 134 135 cmd += ["--output", output] 136 137 common.RunAndCheckOutput(cmd) 138 139 if retrofit and has_image: 140 logger.info("Done writing images to directory %s", output) 141 else: 142 logger.info("Done writing image %s", output) 143 144 return True 145 146 147def BuildSuperImageFromExtractedTargetFiles(inp, out): 148 info_dict = common.LoadInfoDict(inp) 149 partition_list = shlex.split( 150 info_dict.get("dynamic_partition_list", "").strip()) 151 152 if "system" in partition_list: 153 image_path = os.path.join(inp, "IMAGES", "system_other.img") 154 if os.path.isfile(image_path): 155 info_dict["system_other_image"] = image_path 156 157 missing_images = [] 158 for partition in partition_list: 159 image_path = os.path.join(inp, "IMAGES", "{}.img".format(partition)) 160 if not os.path.isfile(image_path): 161 missing_images.append(image_path) 162 else: 163 info_dict["{}_image".format(partition)] = image_path 164 if missing_images: 165 logger.warning("Skip building super image because the following " 166 "images are missing from target files:\n%s", 167 "\n".join(missing_images)) 168 return False 169 return BuildSuperImageFromDict(info_dict, out) 170 171 172def BuildSuperImageFromTargetFiles(inp, out): 173 input_tmp = common.UnzipTemp(inp, UNZIP_PATTERN) 174 return BuildSuperImageFromExtractedTargetFiles(input_tmp, out) 175 176 177def BuildSuperImage(inp, out): 178 179 if isinstance(inp, dict): 180 logger.info("Building super image from info dict...") 181 return BuildSuperImageFromDict(inp, out) 182 183 if isinstance(inp, str): 184 if os.path.isdir(inp): 185 logger.info("Building super image from extracted target files...") 186 return BuildSuperImageFromExtractedTargetFiles(inp, out) 187 188 if zipfile.is_zipfile(inp): 189 logger.info("Building super image from target files...") 190 return BuildSuperImageFromTargetFiles(inp, out) 191 192 if os.path.isfile(inp): 193 with open(inp) as f: 194 lines = f.read() 195 logger.info("Building super image from info dict...") 196 return BuildSuperImageFromDict(common.LoadDictionaryFromLines(lines.split("\n")), out) 197 198 raise ValueError("{} is not a dictionary or a valid path".format(inp)) 199 200 201def main(argv): 202 203 args = common.ParseOptions(argv, __doc__) 204 205 if len(args) != 2: 206 common.Usage(__doc__) 207 sys.exit(1) 208 209 common.InitLogging() 210 211 BuildSuperImage(args[0], args[1]) 212 213 214if __name__ == "__main__": 215 try: 216 common.CloseInheritedPipes() 217 main(sys.argv[1:]) 218 except common.ExternalError: 219 logger.exception("\n ERROR:\n") 220 sys.exit(1) 221 finally: 222 common.Cleanup() 223