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/*", "*/build.prop"] 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 virtual_ab = info_dict.get("virtual_ab") == "true" 80 virtual_ab_retrofit = info_dict.get("virtual_ab_retrofit") == "true" 81 retrofit = info_dict.get("dynamic_partition_retrofit") == "true" 82 block_devices = shlex.split(info_dict.get("super_block_devices", "").strip()) 83 groups = shlex.split(info_dict.get("super_partition_groups", "").strip()) 84 85 if ab_update and retrofit: 86 cmd += ["--metadata-slots", "2"] 87 elif ab_update: 88 cmd += ["--metadata-slots", "3"] 89 else: 90 cmd += ["--metadata-slots", "2"] 91 92 if ab_update and retrofit: 93 cmd.append("--auto-slot-suffixing") 94 if virtual_ab and not virtual_ab_retrofit: 95 cmd.append("--virtual-ab") 96 97 for device in block_devices: 98 size = info_dict["super_{}_device_size".format(device)] 99 cmd += ["--device", "{}:{}".format(device, size)] 100 101 append_suffix = ab_update and not retrofit 102 has_image = False 103 for group in groups: 104 group_size = info_dict["super_{}_group_size".format(group)] 105 if append_suffix: 106 cmd += ["--group", "{}_a:{}".format(group, group_size), 107 "--group", "{}_b:{}".format(group, group_size)] 108 else: 109 cmd += ["--group", "{}:{}".format(group, group_size)] 110 111 partition_list = shlex.split( 112 info_dict["super_{}_partition_list".format(group)].strip()) 113 114 for partition in partition_list: 115 image = info_dict.get("{}_image".format(partition)) 116 if image: 117 has_image = True 118 119 if not append_suffix: 120 cmd += GetArgumentsForImage(partition, group, image) 121 continue 122 123 # For A/B devices, super partition always contains sub-partitions in 124 # the _a slot, because this image should only be used for 125 # bootstrapping / initializing the device. When flashing the image, 126 # bootloader fastboot should always mark _a slot as bootable. 127 cmd += GetArgumentsForImage(partition + "_a", group + "_a", image) 128 129 other_image = None 130 if partition == "system" and "system_other_image" in info_dict: 131 other_image = info_dict["system_other_image"] 132 has_image = True 133 134 cmd += GetArgumentsForImage(partition + "_b", group + "_b", other_image) 135 136 if info_dict.get("build_non_sparse_super_partition") != "true": 137 cmd.append("--sparse") 138 139 cmd += ["--output", output] 140 141 common.RunAndCheckOutput(cmd) 142 143 if retrofit and has_image: 144 logger.info("Done writing images to directory %s", output) 145 else: 146 logger.info("Done writing image %s", output) 147 148 return True 149 150 151def BuildSuperImageFromExtractedTargetFiles(inp, out): 152 info_dict = common.LoadInfoDict(inp) 153 partition_list = shlex.split( 154 info_dict.get("dynamic_partition_list", "").strip()) 155 156 if "system" in partition_list: 157 image_path = os.path.join(inp, "IMAGES", "system_other.img") 158 if os.path.isfile(image_path): 159 info_dict["system_other_image"] = image_path 160 161 missing_images = [] 162 for partition in partition_list: 163 image_path = os.path.join(inp, "IMAGES", "{}.img".format(partition)) 164 if not os.path.isfile(image_path): 165 missing_images.append(image_path) 166 else: 167 info_dict["{}_image".format(partition)] = image_path 168 if missing_images: 169 logger.warning("Skip building super image because the following " 170 "images are missing from target files:\n%s", 171 "\n".join(missing_images)) 172 return False 173 return BuildSuperImageFromDict(info_dict, out) 174 175 176def BuildSuperImageFromTargetFiles(inp, out): 177 input_tmp = common.UnzipTemp(inp, UNZIP_PATTERN) 178 return BuildSuperImageFromExtractedTargetFiles(input_tmp, out) 179 180 181def BuildSuperImage(inp, out): 182 183 if isinstance(inp, dict): 184 logger.info("Building super image from info dict...") 185 return BuildSuperImageFromDict(inp, out) 186 187 if isinstance(inp, str): 188 if os.path.isdir(inp): 189 logger.info("Building super image from extracted target files...") 190 return BuildSuperImageFromExtractedTargetFiles(inp, out) 191 192 if zipfile.is_zipfile(inp): 193 logger.info("Building super image from target files...") 194 return BuildSuperImageFromTargetFiles(inp, out) 195 196 if os.path.isfile(inp): 197 with open(inp) as f: 198 lines = f.read() 199 logger.info("Building super image from info dict...") 200 return BuildSuperImageFromDict(common.LoadDictionaryFromLines(lines.split("\n")), out) 201 202 raise ValueError("{} is not a dictionary or a valid path".format(inp)) 203 204 205def main(argv): 206 207 args = common.ParseOptions(argv, __doc__) 208 209 if len(args) != 2: 210 common.Usage(__doc__) 211 sys.exit(1) 212 213 common.InitLogging() 214 215 BuildSuperImage(args[0], args[1]) 216 217 218if __name__ == "__main__": 219 try: 220 common.CloseInheritedPipes() 221 main(sys.argv[1:]) 222 except common.ExternalError: 223 logger.exception("\n ERROR:\n") 224 sys.exit(1) 225 finally: 226 common.Cleanup() 227