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