add_img_to_target_files.py revision aa7318c3849095aeb3bea00efbf303c0c40a089d
1#!/usr/bin/env python
2#
3# Copyright (C) 2014 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"""
18Given a target-files zipfile that does not contain images (ie, does
19not have an IMAGES/ top-level subdirectory), produce the images and
20add them to the zipfile.
21
22Usage:  add_img_to_target_files target_files
23"""
24
25import sys
26
27if sys.hexversion < 0x02070000:
28  print >> sys.stderr, "Python 2.7 or newer is required."
29  sys.exit(1)
30
31import errno
32import os
33import shutil
34import tempfile
35import zipfile
36
37import build_image
38import common
39
40OPTIONS = common.OPTIONS
41
42OPTIONS.add_missing = False
43OPTIONS.rebuild_recovery = False
44
45def AddSystem(output_zip, prefix="IMAGES/", recovery_img=None, boot_img=None):
46  """Turn the contents of SYSTEM into a system image and store it in
47  output_zip."""
48
49  prebuilt_path = os.path.join(OPTIONS.input_tmp, prefix, "system.img")
50  if os.path.exists(prebuilt_path):
51    print "system.img already exists in %s, no need to rebuild..." % (prefix,)
52    return
53
54  def output_sink(fn, data):
55    ofile = open(os.path.join(OPTIONS.input_tmp, "SYSTEM", fn), "w")
56    ofile.write(data)
57    ofile.close()
58
59  if OPTIONS.rebuild_recovery:
60    print "Building new recovery patch"
61    common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink, recovery_img,
62                             boot_img, info_dict=OPTIONS.info_dict)
63
64  block_list = common.MakeTempFile(prefix="system-blocklist-", suffix=".map")
65  imgname = BuildSystem(OPTIONS.input_tmp, OPTIONS.info_dict,
66                        block_list=block_list)
67  common.ZipWrite(output_zip, imgname, prefix + "system.img")
68  common.ZipWrite(output_zip, block_list, prefix + "system.map")
69
70
71def BuildSystem(input_dir, info_dict, block_list=None):
72  """Build the (sparse) system image and return the name of a temp
73  file containing it."""
74  return CreateImage(input_dir, info_dict, "system", block_list=block_list)
75
76
77def AddVendor(output_zip, prefix="IMAGES/"):
78  """Turn the contents of VENDOR into a vendor image and store in it
79  output_zip."""
80
81  prebuilt_path = os.path.join(OPTIONS.input_tmp, prefix, "vendor.img")
82  if os.path.exists(prebuilt_path):
83    print "vendor.img already exists in %s, no need to rebuild..." % (prefix,)
84    return
85
86  block_list = common.MakeTempFile(prefix="vendor-blocklist-", suffix=".map")
87  imgname = BuildVendor(OPTIONS.input_tmp, OPTIONS.info_dict,
88                        block_list=block_list)
89  common.ZipWrite(output_zip, imgname, prefix + "vendor.img")
90  common.ZipWrite(output_zip, block_list, prefix + "vendor.map")
91
92
93def BuildVendor(input_dir, info_dict, block_list=None):
94  """Build the (sparse) vendor image and return the name of a temp
95  file containing it."""
96  return CreateImage(input_dir, info_dict, "vendor", block_list=block_list)
97
98
99def CreateImage(input_dir, info_dict, what, block_list=None):
100  print "creating " + what + ".img..."
101
102  img = common.MakeTempFile(prefix=what + "-", suffix=".img")
103
104  # The name of the directory it is making an image out of matters to
105  # mkyaffs2image.  It wants "system" but we have a directory named
106  # "SYSTEM", so create a symlink.
107  try:
108    os.symlink(os.path.join(input_dir, what.upper()),
109               os.path.join(input_dir, what))
110  except OSError as e:
111    # bogus error on my mac version?
112    #   File "./build/tools/releasetools/img_from_target_files"
113    #     os.path.join(OPTIONS.input_tmp, "system"))
114    # OSError: [Errno 17] File exists
115    if e.errno == errno.EEXIST:
116      pass
117
118  image_props = build_image.ImagePropFromGlobalDict(info_dict, what)
119  fstab = info_dict["fstab"]
120  if fstab:
121    image_props["fs_type"] = fstab["/" + what].fs_type
122
123  if what == "system":
124    fs_config_prefix = ""
125  else:
126    fs_config_prefix = what + "_"
127
128  fs_config = os.path.join(
129      input_dir, "META/" + fs_config_prefix + "filesystem_config.txt")
130  if not os.path.exists(fs_config):
131    fs_config = None
132
133  # Override values loaded from info_dict.
134  if fs_config:
135    image_props["fs_config"] = fs_config
136  if block_list:
137    image_props["block_list"] = block_list
138  if image_props.get("system_root_image") == "true":
139    image_props["ramdisk_dir"] = os.path.join(input_dir, "BOOT/RAMDISK")
140    image_props["ramdisk_fs_config"] = os.path.join(
141        input_dir, "META/boot_filesystem_config.txt")
142
143  succ = build_image.BuildImage(os.path.join(input_dir, what),
144                                image_props, img)
145  assert succ, "build " + what + ".img image failed"
146
147  return img
148
149
150def AddUserdata(output_zip, prefix="IMAGES/"):
151  """Create a userdata image and store it in output_zip.
152
153  In most case we just create and store an empty userdata.img;
154  But the invoker can also request to create userdata.img with real
155  data from the target files, by setting "userdata_img_with_data=true"
156  in OPTIONS.info_dict.
157  """
158
159  prebuilt_path = os.path.join(OPTIONS.input_tmp, prefix, "userdata.img")
160  if os.path.exists(prebuilt_path):
161    print "userdata.img already exists in %s, no need to rebuild..." % (prefix,)
162    return
163
164  image_props = build_image.ImagePropFromGlobalDict(OPTIONS.info_dict, "data")
165  # We only allow yaffs to have a 0/missing partition_size.
166  # Extfs, f2fs must have a size. Skip userdata.img if no size.
167  if (not image_props.get("fs_type", "").startswith("yaffs") and
168      not image_props.get("partition_size")):
169    return
170
171  print "creating userdata.img..."
172
173  # The name of the directory it is making an image out of matters to
174  # mkyaffs2image.  So we create a temp dir, and within it we create an
175  # empty dir named "data", or a symlink to the DATA dir,
176  # and build the image from that.
177  temp_dir = tempfile.mkdtemp()
178  user_dir = os.path.join(temp_dir, "data")
179  empty = (OPTIONS.info_dict.get("userdata_img_with_data") != "true")
180  if empty:
181    # Create an empty dir.
182    os.mkdir(user_dir)
183  else:
184    # Symlink to the DATA dir.
185    os.symlink(os.path.join(OPTIONS.input_tmp, "DATA"),
186               user_dir)
187
188  img = tempfile.NamedTemporaryFile()
189
190  fstab = OPTIONS.info_dict["fstab"]
191  if fstab:
192    image_props["fs_type"] = fstab["/data"].fs_type
193  succ = build_image.BuildImage(user_dir, image_props, img.name)
194  assert succ, "build userdata.img image failed"
195
196  common.CheckSize(img.name, "userdata.img", OPTIONS.info_dict)
197  common.ZipWrite(output_zip, img.name, prefix + "userdata.img")
198  img.close()
199  shutil.rmtree(temp_dir)
200
201
202def AddCache(output_zip, prefix="IMAGES/"):
203  """Create an empty cache image and store it in output_zip."""
204
205  prebuilt_path = os.path.join(OPTIONS.input_tmp, prefix, "cache.img")
206  if os.path.exists(prebuilt_path):
207    print "cache.img already exists in %s, no need to rebuild..." % (prefix,)
208    return
209
210  image_props = build_image.ImagePropFromGlobalDict(OPTIONS.info_dict, "cache")
211  # The build system has to explicitly request for cache.img.
212  if "fs_type" not in image_props:
213    return
214
215  print "creating cache.img..."
216
217  # The name of the directory it is making an image out of matters to
218  # mkyaffs2image.  So we create a temp dir, and within it we create an
219  # empty dir named "cache", and build the image from that.
220  temp_dir = tempfile.mkdtemp()
221  user_dir = os.path.join(temp_dir, "cache")
222  os.mkdir(user_dir)
223  img = tempfile.NamedTemporaryFile()
224
225  fstab = OPTIONS.info_dict["fstab"]
226  if fstab:
227    image_props["fs_type"] = fstab["/cache"].fs_type
228  succ = build_image.BuildImage(user_dir, image_props, img.name)
229  assert succ, "build cache.img image failed"
230
231  common.CheckSize(img.name, "cache.img", OPTIONS.info_dict)
232  common.ZipWrite(output_zip, img.name, prefix + "cache.img")
233  img.close()
234  os.rmdir(user_dir)
235  os.rmdir(temp_dir)
236
237
238def AddImagesToTargetFiles(filename):
239  OPTIONS.input_tmp, input_zip = common.UnzipTemp(filename)
240
241  if not OPTIONS.add_missing:
242    for n in input_zip.namelist():
243      if n.startswith("IMAGES/"):
244        print "target_files appears to already contain images."
245        sys.exit(1)
246
247  try:
248    input_zip.getinfo("VENDOR/")
249    has_vendor = True
250  except KeyError:
251    has_vendor = False
252
253  OPTIONS.info_dict = common.LoadInfoDict(input_zip, OPTIONS.input_tmp)
254
255  common.ZipClose(input_zip)
256  output_zip = zipfile.ZipFile(filename, "a",
257                               compression=zipfile.ZIP_DEFLATED)
258
259  def banner(s):
260    print "\n\n++++ " + s + " ++++\n\n"
261
262  banner("boot")
263  prebuilt_path = os.path.join(OPTIONS.input_tmp, "IMAGES", "boot.img")
264  boot_image = None
265  if os.path.exists(prebuilt_path):
266    print "boot.img already exists in IMAGES/, no need to rebuild..."
267    if OPTIONS.rebuild_recovery:
268      boot_image = common.GetBootableImage(
269          "IMAGES/boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
270  else:
271    boot_image = common.GetBootableImage(
272        "IMAGES/boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
273    if boot_image:
274      boot_image.AddToZip(output_zip)
275
276  banner("recovery")
277  recovery_image = None
278  prebuilt_path = os.path.join(OPTIONS.input_tmp, "IMAGES", "recovery.img")
279  if os.path.exists(prebuilt_path):
280    print "recovery.img already exists in IMAGES/, no need to rebuild..."
281    if OPTIONS.rebuild_recovery:
282      recovery_image = common.GetBootableImage(
283          "IMAGES/recovery.img", "recovery.img", OPTIONS.input_tmp, "RECOVERY")
284  else:
285    recovery_image = common.GetBootableImage(
286        "IMAGES/recovery.img", "recovery.img", OPTIONS.input_tmp, "RECOVERY")
287    if recovery_image:
288      recovery_image.AddToZip(output_zip)
289
290  banner("system")
291  AddSystem(output_zip, recovery_img=recovery_image, boot_img=boot_image)
292  if has_vendor:
293    banner("vendor")
294    AddVendor(output_zip)
295  banner("userdata")
296  AddUserdata(output_zip)
297  banner("cache")
298  AddCache(output_zip)
299
300  common.ZipClose(output_zip)
301
302def main(argv):
303  def option_handler(o, _):
304    if o in ("-a", "--add_missing"):
305      OPTIONS.add_missing = True
306    elif o in ("-r", "--rebuild_recovery",):
307      OPTIONS.rebuild_recovery = True
308    else:
309      return False
310    return True
311
312  args = common.ParseOptions(
313      argv, __doc__, extra_opts="ar",
314      extra_long_opts=["add_missing", "rebuild_recovery"],
315      extra_option_handler=option_handler)
316
317
318  if len(args) != 1:
319    common.Usage(__doc__)
320    sys.exit(1)
321
322  AddImagesToTargetFiles(args[0])
323  print "done."
324
325if __name__ == '__main__':
326  try:
327    common.CloseInheritedPipes()
328    main(sys.argv[1:])
329  except common.ExternalError as e:
330    print
331    print "   ERROR: %s" % (e,)
332    print
333    sys.exit(1)
334  finally:
335    common.Cleanup()
336