| #!/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-super-image-info' for details. |
| In addition: |
| - 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/*", "*/build.prop"] |
| |
| |
| def GetArgumentsForImage(partition, group, image=None): |
| image_size = sparse_img.GetImagePartitionSize(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" |
| virtual_ab = info_dict.get("virtual_ab") == "true" |
| virtual_ab_retrofit = info_dict.get("virtual_ab_retrofit") == "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 and retrofit: |
| cmd += ["--metadata-slots", "2"] |
| elif ab_update: |
| cmd += ["--metadata-slots", "3"] |
| else: |
| cmd += ["--metadata-slots", "2"] |
| |
| if ab_update and retrofit: |
| cmd.append("--auto-slot-suffixing") |
| if virtual_ab and not virtual_ab_retrofit: |
| cmd.append("--virtual-ab") |
| |
| 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 info_dict.get("build_non_sparse_super_partition") != "true": |
| 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() |