|  | #!/usr/bin/env python | 
|  | # | 
|  | # Copyright (C) 2018 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. | 
|  |  | 
|  | """ | 
|  | Usage: build_super_image input_file output_dir_or_file | 
|  |  | 
|  | input_file: one of the following: | 
|  | - directory containing extracted target files. It will load info from | 
|  | META/misc_info.txt and build full super image / split images using source | 
|  | images from IMAGES/. | 
|  | - target files package. Same as above, but extracts the archive before | 
|  | building super image. | 
|  | - a dictionary file containing input arguments to build. Check | 
|  | `dump_dynamic_partitions_info' for details. | 
|  | In addition: | 
|  | - "ab_update" needs to be true for A/B devices. | 
|  | - If source images should be included in the output image (for super.img | 
|  | and super split images), a list of "*_image" should be paths of each | 
|  | source images. | 
|  |  | 
|  | output_dir_or_file: | 
|  | If a single super image is built (for super_empty.img, or super.img for | 
|  | launch devices), this argument is the output file. | 
|  | If a collection of split images are built (for retrofit devices), this | 
|  | argument is the output directory. | 
|  | """ | 
|  |  | 
|  | from __future__ import print_function | 
|  |  | 
|  | import logging | 
|  | import os.path | 
|  | import shlex | 
|  | import sys | 
|  | import zipfile | 
|  |  | 
|  | import common | 
|  | import sparse_img | 
|  |  | 
|  | if sys.hexversion < 0x02070000: | 
|  | print("Python 2.7 or newer is required.", file=sys.stderr) | 
|  | sys.exit(1) | 
|  |  | 
|  | logger = logging.getLogger(__name__) | 
|  |  | 
|  |  | 
|  | UNZIP_PATTERN = ["IMAGES/*", "META/*"] | 
|  |  | 
|  |  | 
|  | def GetPartitionSizeFromImage(img): | 
|  | try: | 
|  | simg = sparse_img.SparseImage(img) | 
|  | return simg.blocksize * simg.total_blocks | 
|  | except ValueError: | 
|  | return os.path.getsize(img) | 
|  |  | 
|  |  | 
|  | def GetArgumentsForImage(partition, group, image=None): | 
|  | image_size = GetPartitionSizeFromImage(image) if image else 0 | 
|  |  | 
|  | cmd = ["--partition", | 
|  | "{}:readonly:{}:{}".format(partition, image_size, group)] | 
|  | if image: | 
|  | cmd += ["--image", "{}={}".format(partition, image)] | 
|  |  | 
|  | return cmd | 
|  |  | 
|  |  | 
|  | def BuildSuperImageFromDict(info_dict, output): | 
|  |  | 
|  | cmd = [info_dict["lpmake"], | 
|  | "--metadata-size", "65536", | 
|  | "--super-name", info_dict["super_metadata_device"]] | 
|  |  | 
|  | ab_update = info_dict.get("ab_update") == "true" | 
|  | retrofit = info_dict.get("dynamic_partition_retrofit") == "true" | 
|  | block_devices = shlex.split(info_dict.get("super_block_devices", "").strip()) | 
|  | groups = shlex.split(info_dict.get("super_partition_groups", "").strip()) | 
|  |  | 
|  | if ab_update: | 
|  | cmd += ["--metadata-slots", "2"] | 
|  | else: | 
|  | cmd += ["--metadata-slots", "1"] | 
|  |  | 
|  | if ab_update and retrofit: | 
|  | cmd.append("--auto-slot-suffixing") | 
|  |  | 
|  | for device in block_devices: | 
|  | size = info_dict["super_{}_device_size".format(device)] | 
|  | cmd += ["--device", "{}:{}".format(device, size)] | 
|  |  | 
|  | append_suffix = ab_update and not retrofit | 
|  | has_image = False | 
|  | for group in groups: | 
|  | group_size = info_dict["super_{}_group_size".format(group)] | 
|  | if append_suffix: | 
|  | cmd += ["--group", "{}_a:{}".format(group, group_size), | 
|  | "--group", "{}_b:{}".format(group, group_size)] | 
|  | else: | 
|  | cmd += ["--group", "{}:{}".format(group, group_size)] | 
|  |  | 
|  | partition_list = shlex.split( | 
|  | info_dict["super_{}_partition_list".format(group)].strip()) | 
|  |  | 
|  | for partition in partition_list: | 
|  | image = info_dict.get("{}_image".format(partition)) | 
|  | if image: | 
|  | has_image = True | 
|  |  | 
|  | if not append_suffix: | 
|  | cmd += GetArgumentsForImage(partition, group, image) | 
|  | continue | 
|  |  | 
|  | # For A/B devices, super partition always contains sub-partitions in | 
|  | # the _a slot, because this image should only be used for | 
|  | # bootstrapping / initializing the device. When flashing the image, | 
|  | # bootloader fastboot should always mark _a slot as bootable. | 
|  | cmd += GetArgumentsForImage(partition + "_a", group + "_a", image) | 
|  |  | 
|  | other_image = None | 
|  | if partition == "system" and "system_other_image" in info_dict: | 
|  | other_image = info_dict["system_other_image"] | 
|  | has_image = True | 
|  |  | 
|  | cmd += GetArgumentsForImage(partition + "_b", group + "_b", other_image) | 
|  |  | 
|  | if has_image: | 
|  | cmd.append("--sparse") | 
|  |  | 
|  | cmd += ["--output", output] | 
|  |  | 
|  | common.RunAndCheckOutput(cmd) | 
|  |  | 
|  | if retrofit and has_image: | 
|  | logger.info("Done writing images to directory %s", output) | 
|  | else: | 
|  | logger.info("Done writing image %s", output) | 
|  |  | 
|  | return True | 
|  |  | 
|  |  | 
|  | def BuildSuperImageFromExtractedTargetFiles(inp, out): | 
|  | info_dict = common.LoadInfoDict(inp) | 
|  | partition_list = shlex.split( | 
|  | info_dict.get("dynamic_partition_list", "").strip()) | 
|  |  | 
|  | if "system" in partition_list: | 
|  | image_path = os.path.join(inp, "IMAGES", "system_other.img") | 
|  | if os.path.isfile(image_path): | 
|  | info_dict["system_other_image"] = image_path | 
|  |  | 
|  | missing_images = [] | 
|  | for partition in partition_list: | 
|  | image_path = os.path.join(inp, "IMAGES", "{}.img".format(partition)) | 
|  | if not os.path.isfile(image_path): | 
|  | missing_images.append(image_path) | 
|  | else: | 
|  | info_dict["{}_image".format(partition)] = image_path | 
|  | if missing_images: | 
|  | logger.warning("Skip building super image because the following " | 
|  | "images are missing from target files:\n%s", | 
|  | "\n".join(missing_images)) | 
|  | return False | 
|  | return BuildSuperImageFromDict(info_dict, out) | 
|  |  | 
|  |  | 
|  | def BuildSuperImageFromTargetFiles(inp, out): | 
|  | input_tmp = common.UnzipTemp(inp, UNZIP_PATTERN) | 
|  | return BuildSuperImageFromExtractedTargetFiles(input_tmp, out) | 
|  |  | 
|  |  | 
|  | def BuildSuperImage(inp, out): | 
|  |  | 
|  | if isinstance(inp, dict): | 
|  | logger.info("Building super image from info dict...") | 
|  | return BuildSuperImageFromDict(inp, out) | 
|  |  | 
|  | if isinstance(inp, str): | 
|  | if os.path.isdir(inp): | 
|  | logger.info("Building super image from extracted target files...") | 
|  | return BuildSuperImageFromExtractedTargetFiles(inp, out) | 
|  |  | 
|  | if zipfile.is_zipfile(inp): | 
|  | logger.info("Building super image from target files...") | 
|  | return BuildSuperImageFromTargetFiles(inp, out) | 
|  |  | 
|  | if os.path.isfile(inp): | 
|  | with open(inp) as f: | 
|  | lines = f.read() | 
|  | logger.info("Building super image from info dict...") | 
|  | return BuildSuperImageFromDict(common.LoadDictionaryFromLines(lines.split("\n")), out) | 
|  |  | 
|  | raise ValueError("{} is not a dictionary or a valid path".format(inp)) | 
|  |  | 
|  |  | 
|  | def main(argv): | 
|  |  | 
|  | args = common.ParseOptions(argv, __doc__) | 
|  |  | 
|  | if len(args) != 2: | 
|  | common.Usage(__doc__) | 
|  | sys.exit(1) | 
|  |  | 
|  | common.InitLogging() | 
|  |  | 
|  | BuildSuperImage(args[0], args[1]) | 
|  |  | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | try: | 
|  | common.CloseInheritedPipes() | 
|  | main(sys.argv[1:]) | 
|  | except common.ExternalError: | 
|  | logger.exception("\n   ERROR:\n") | 
|  | sys.exit(1) | 
|  | finally: | 
|  | common.Cleanup() |