3 # Copyright (C) 2008 The Android Open Source Project
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
18 Given a target-files zipfile, produces an OTA package that installs
19 that build. An incremental OTA is produced if -i is given, otherwise
20 a full OTA is produced.
22 Usage: ota_from_target_files [flags] input_target_files output_ota_package
24 -b (--board_config) <file>
27 -k (--package_key) <key>
28 Key to use to sign the package (default is
29 "build/target/product/security/testkey").
31 -i (--incremental_from) <file>
32 Generate an incremental OTA using the given target-files zip as
36 Generate an OTA package that will wipe the user data partition
40 Omit the timestamp prereq check normally included at the top of
41 the build scripts (used for developer OTA packages which
42 legitimately need to go back and forth).
44 -e (--extra_script) <file>
45 Insert the contents of file at the end of the update script.
47 -m (--script_mode) <mode>
48 Specify 'amend' or 'edify' scripts, or 'auto' to pick
49 automatically (this is the default).
55 if sys.hexversion < 0x02040000:
56 print >> sys.stderr, "Python 2.4 or newer is required."
69 import amend_generator
70 import edify_generator
73 OPTIONS = common.OPTIONS
74 OPTIONS.package_key = "build/target/product/security/testkey"
75 OPTIONS.incremental_source = None
76 OPTIONS.require_verbatim = set()
77 OPTIONS.prohibit_verbatim = set(("system/build.prop",))
78 OPTIONS.patch_threshold = 0.95
79 OPTIONS.wipe_user_data = False
80 OPTIONS.omit_prereq = False
81 OPTIONS.extra_script = None
82 OPTIONS.script_mode = 'auto'
84 def MostPopularKey(d, default):
85 """Given a dict, return the key corresponding to the largest
86 value. Returns 'default' if the dict is empty."""
87 x = [(v, k) for (k, v) in d.iteritems()]
88 if not x: return default
94 """Return true if the zipfile.ZipInfo object passed in represents a
96 return (info.external_attr >> 16) == 0120777
101 """Items represent the metadata (user, group, mode) of files and
102 directories in the system image."""
104 def __init__(self, name, dir=False):
112 self.parent = Item.Get(os.path.dirname(name), dir=True)
113 self.parent.children.append(self)
119 def Dump(self, indent=0):
120 if self.uid is not None:
121 print "%s%s %d %d %o" % (" "*indent, self.name, self.uid, self.gid, self.mode)
123 print "%s%s %s %s %s" % (" "*indent, self.name, self.uid, self.gid, self.mode)
125 print "%s%s" % (" "*indent, self.descendants)
126 print "%s%s" % (" "*indent, self.best_subtree)
127 for i in self.children:
128 i.Dump(indent=indent+1)
131 def Get(cls, name, dir=False):
132 if name not in cls.ITEMS:
133 cls.ITEMS[name] = Item(name, dir=dir)
134 return cls.ITEMS[name]
137 def GetMetadata(cls):
138 """Run the external 'fs_config' program to determine the desired
139 uid, gid, and mode for every Item object."""
140 p = common.Run(["fs_config"], stdin=subprocess.PIPE,
141 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
142 suffix = { False: "", True: "/" }
143 input = "".join(["%s%s\n" % (i.name, suffix[i.dir])
144 for i in cls.ITEMS.itervalues() if i.name])
145 output, error = p.communicate(input)
148 for line in output.split("\n"):
149 if not line: continue
150 name, uid, gid, mode = line.split()
154 i.mode = int(mode, 8)
156 i.children.sort(key=lambda i: i.name)
158 def CountChildMetadata(self):
159 """Count up the (uid, gid, mode) tuples for all children and
160 determine the best strategy for using set_perm_recursive and
161 set_perm to correctly chown/chmod all the files to their desired
162 values. Recursively calls itself for all descendants.
164 Returns a dict of {(uid, gid, dmode, fmode): count} counting up
165 all descendants of this node. (dmode or fmode may be None.) Also
166 sets the best_subtree of each directory Item to the (uid, gid,
167 dmode, fmode) tuple that will match the most descendants of that
172 d = self.descendants = {(self.uid, self.gid, self.mode, None): 1}
173 for i in self.children:
175 for k, v in i.CountChildMetadata().iteritems():
176 d[k] = d.get(k, 0) + v
178 k = (i.uid, i.gid, None, i.mode)
179 d[k] = d.get(k, 0) + 1
181 # Find the (uid, gid, dmode, fmode) tuple that matches the most
184 # First, find the (uid, gid) pair that matches the most
187 for (uid, gid, _, _), count in d.iteritems():
188 ug[(uid, gid)] = ug.get((uid, gid), 0) + count
189 ug = MostPopularKey(ug, (0, 0))
191 # Now find the dmode and fmode that match the most descendants
192 # with that (uid, gid), and choose those.
193 best_dmode = (0, 0755)
194 best_fmode = (0, 0644)
195 for k, count in d.iteritems():
196 if k[:2] != ug: continue
197 if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
198 if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
199 self.best_subtree = ug + (best_dmode[1], best_fmode[1])
203 def SetPermissions(self, script):
204 """Append set_perm/set_perm_recursive commands to 'script' to
205 set all permissions, users, and groups for the tree of files
208 self.CountChildMetadata()
210 def recurse(item, current):
211 # current is the (uid, gid, dmode, fmode) tuple that the current
212 # item (and all its children) have already been set to. We only
213 # need to issue set_perm/set_perm_recursive commands if we're
214 # supposed to be something different.
216 if current != item.best_subtree:
217 script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
218 current = item.best_subtree
220 if item.uid != current[0] or item.gid != current[1] or \
221 item.mode != current[2]:
222 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
224 for i in item.children:
227 if item.uid != current[0] or item.gid != current[1] or \
228 item.mode != current[3]:
229 script.SetPermissions("/"+item.name, item.uid, item.gid, item.mode)
231 recurse(self, (-1, -1, -1, -1))
234 def CopySystemFiles(input_zip, output_zip=None,
236 """Copies files underneath system/ in the input zip to the output
237 zip. Populates the Item class with their metadata, and returns a
238 list of symlinks. output_zip may be None, in which case the copy is
239 skipped (but the other side effects still happen). substitute is an
240 optional dict of {output filename: contents} to be output instead of
246 for info in input_zip.infolist():
247 if info.filename.startswith("SYSTEM/"):
248 basefilename = info.filename[7:]
250 symlinks.append((input_zip.read(info.filename),
251 "/system/" + basefilename))
253 info2 = copy.copy(info)
254 fn = info2.filename = "system/" + basefilename
255 if substitute and fn in substitute and substitute[fn] is None:
257 if output_zip is not None:
258 if substitute and fn in substitute:
259 data = substitute[fn]
261 data = input_zip.read(info.filename)
262 output_zip.writestr(info2, data)
264 Item.Get(fn[:-1], dir=True)
266 Item.Get(fn, dir=False)
272 def SignOutput(temp_zip_name, output_zip_name):
273 key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
274 pw = key_passwords[OPTIONS.package_key]
276 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw)
279 def AppendAssertions(script, input_zip):
280 device = GetBuildProp("ro.product.device", input_zip)
281 script.AssertDevice(device)
283 info = input_zip.read("OTA/android-info.txt")
284 m = re.search(r"require\s+version-bootloader\s*=\s*(\S+)", info)
286 bootloaders = m.group(1).split("|")
287 if "*" not in bootloaders:
288 script.AssertSomeBootloader(*bootloaders)
291 def MakeRecoveryPatch(output_zip, recovery_img, boot_img):
292 """Generate a binary patch that creates the recovery image starting
293 with the boot image. (Most of the space in these images is just the
294 kernel, which is identical for the two, so the resulting patch
295 should be efficient.) Add it to the output zip, along with a shell
296 script that is run from init.rc on first boot to actually do the
297 patching and install the new recovery image.
299 recovery_img and boot_img should be File objects for the
300 corresponding images.
302 Returns an Item for the shell script, which must be made
306 patch = Difference(recovery_img, boot_img, "imgdiff")
307 common.ZipWriteStr(output_zip, "system/recovery-from-boot.p", patch)
308 Item.Get("system/recovery-from-boot.p", dir=False)
310 # Images with different content will have a different first page, so
311 # we check to see if this recovery has already been installed by
312 # testing just the first 2k.
314 header_sha1 = sha.sha(recovery_img.data[:HEADER_SIZE]).hexdigest()
315 sh = """#!/system/bin/sh
316 if ! applypatch -c MTD:recovery:%(header_size)d:%(header_sha1)s; then
317 log -t recovery "Installing new recovery image"
318 applypatch MTD:boot:%(boot_size)d:%(boot_sha1)s MTD:recovery %(recovery_sha1)s %(recovery_size)d %(boot_sha1)s:/system/recovery-from-boot.p
320 log -t recovery "Recovery image already installed"
322 """ % { 'boot_size': boot_img.size,
323 'boot_sha1': boot_img.sha1,
324 'header_size': HEADER_SIZE,
325 'header_sha1': header_sha1,
326 'recovery_size': recovery_img.size,
327 'recovery_sha1': recovery_img.sha1 }
328 common.ZipWriteStr(output_zip, "system/etc/install-recovery.sh", sh)
329 return Item.Get("system/etc/install-recovery.sh", dir=False)
332 def WriteFullOTAPackage(input_zip, output_zip):
333 if OPTIONS.script_mode == "auto":
334 script = both_generator.BothGenerator(2)
335 elif OPTIONS.script_mode == "amend":
336 script = amend_generator.AmendGenerator()
338 # TODO: how to determine this? We don't know what version it will
339 # be installed on top of. For now, we expect the API just won't
341 script = edify_generator.EdifyGenerator(2)
343 device_specific = common.DeviceSpecificParams(
345 output_zip=output_zip,
347 input_tmp=OPTIONS.input_tmp)
349 if not OPTIONS.omit_prereq:
350 ts = GetBuildProp("ro.build.date.utc", input_zip)
351 script.AssertOlderBuild(ts)
353 AppendAssertions(script, input_zip)
354 device_specific.FullOTA_Assertions()
356 script.ShowProgress(0.5, 0)
358 if OPTIONS.wipe_user_data:
359 script.FormatPartition("userdata")
361 script.FormatPartition("system")
362 script.Mount("MTD", "system", "/system")
363 script.UnpackPackageDir("system", "/system")
365 symlinks = CopySystemFiles(input_zip, output_zip)
366 script.MakeSymlinks(symlinks)
368 boot_img = File("boot.img", common.BuildBootableImage(
369 os.path.join(OPTIONS.input_tmp, "BOOT")))
370 recovery_img = File("recovery.img", common.BuildBootableImage(
371 os.path.join(OPTIONS.input_tmp, "RECOVERY")))
372 i = MakeRecoveryPatch(output_zip, recovery_img, boot_img)
376 # GetMetadata uses the data in android_filesystem_config.h to assign
377 # the uid/gid/mode of all files. We want to override that for the
378 # recovery patching shell script to make it executable.
382 Item.Get("system").SetPermissions(script)
384 common.CheckSize(boot_img.data, "boot.img")
385 common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
386 script.ShowProgress(0.2, 0)
388 script.ShowProgress(0.2, 10)
389 script.WriteRawImage("boot", "boot.img")
391 script.ShowProgress(0.1, 0)
392 device_specific.FullOTA_InstallEnd()
394 if OPTIONS.extra_script is not None:
395 script.AppendExtra(OPTIONS.extra_script)
397 script.AddToZip(input_zip, output_zip)
401 def __init__(self, name, data):
404 self.size = len(data)
405 self.sha1 = sha.sha(data).hexdigest()
407 def WriteToTemp(self):
408 t = tempfile.NamedTemporaryFile()
413 def AddToZip(self, z):
414 common.ZipWriteStr(z, self.name, self.data)
417 def LoadSystemFiles(z):
418 """Load all the files from SYSTEM/... in a given target-files
419 ZipFile, and return a dict of {filename: File object}."""
421 for info in z.infolist():
422 if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
423 fn = "system/" + info.filename[7:]
424 data = z.read(info.filename)
425 out[fn] = File(fn, data)
429 def Difference(tf, sf, diff_program):
430 """Return the patch (as a string of data) needed to turn sf into tf.
431 diff_program is the name of an external program (or list, if
432 additional arguments are desired) to run to generate the diff.
435 ttemp = tf.WriteToTemp()
436 stemp = sf.WriteToTemp()
438 ext = os.path.splitext(tf.name)[1]
441 ptemp = tempfile.NamedTemporaryFile()
442 if isinstance(diff_program, list):
443 cmd = copy.copy(diff_program)
446 cmd.append(stemp.name)
447 cmd.append(ttemp.name)
448 cmd.append(ptemp.name)
450 _, err = p.communicate()
451 if err or p.returncode != 0:
452 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
463 def GetBuildProp(property, z):
464 """Return the fingerprint of the build of a given target-files
466 bp = z.read("SYSTEM/build.prop")
469 m = re.search(re.escape(property) + r"=(.*)\n", bp)
471 raise common.ExternalError("couldn't find %s in build.prop" % (property,))
472 return m.group(1).strip()
475 def GetRecoveryAPIVersion(zip):
476 """Returns the version of the recovery API. Version 0 is the older
477 amend code (no separate binary)."""
479 version = zip.read("META/recovery-api-version.txt")
483 # version one didn't have the recovery-api-version.txt file, but
484 # it did include an updater binary.
485 zip.getinfo("OTA/bin/updater")
490 def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
491 source_version = GetRecoveryAPIVersion(source_zip)
493 if OPTIONS.script_mode == 'amend':
494 script = amend_generator.AmendGenerator()
495 elif OPTIONS.script_mode == 'edify':
496 if source_version == 0:
497 print ("WARNING: generating edify script for a source that "
499 script = edify_generator.EdifyGenerator(source_version)
500 elif OPTIONS.script_mode == 'auto':
501 if source_version > 0:
502 script = edify_generator.EdifyGenerator(source_version)
504 script = amend_generator.AmendGenerator()
506 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
508 device_specific = common.DeviceSpecificParams(
509 source_zip=source_zip,
510 target_zip=target_zip,
511 output_zip=output_zip,
514 print "Loading target..."
515 target_data = LoadSystemFiles(target_zip)
516 print "Loading source..."
517 source_data = LoadSystemFiles(source_zip)
519 verbatim_targets = []
521 largest_source_size = 0
522 for fn in sorted(target_data.keys()):
524 sf = source_data.get(fn, None)
526 if sf is None or fn in OPTIONS.require_verbatim:
527 # This file should be included verbatim
528 if fn in OPTIONS.prohibit_verbatim:
529 raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
530 print "send", fn, "verbatim"
531 tf.AddToZip(output_zip)
532 verbatim_targets.append((fn, tf.size))
533 elif tf.sha1 != sf.sha1:
534 # File is different; consider sending as a patch
535 diff_method = "bsdiff"
536 if tf.name.endswith(".gz"):
537 diff_method = "imgdiff"
538 d = Difference(tf, sf, diff_method)
540 print fn, tf.size, len(d), (float(len(d)) / tf.size)
541 if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
542 # patch is almost as big as the file; don't bother patching
543 tf.AddToZip(output_zip)
544 verbatim_targets.append((fn, tf.size))
546 common.ZipWriteStr(output_zip, "patch/" + fn + ".p", d)
547 patch_list.append((fn, tf, sf, tf.size))
548 largest_source_size = max(largest_source_size, sf.size)
550 # Target file identical to source.
553 total_verbatim_size = sum([i[1] for i in verbatim_targets])
554 total_patched_size = sum([i[3] for i in patch_list])
556 source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
557 target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
559 script.Mount("MTD", "system", "/system")
560 script.AssertSomeFingerprint(source_fp, target_fp)
562 source_boot = File("/tmp/boot.img",
563 common.BuildBootableImage(
564 os.path.join(OPTIONS.source_tmp, "BOOT")))
565 target_boot = File("/tmp/boot.img",
566 common.BuildBootableImage(
567 os.path.join(OPTIONS.target_tmp, "BOOT")))
568 updating_boot = (source_boot.data != target_boot.data)
570 source_recovery = File("system/recovery.img",
571 common.BuildBootableImage(
572 os.path.join(OPTIONS.source_tmp, "RECOVERY")))
573 target_recovery = File("system/recovery.img",
574 common.BuildBootableImage(
575 os.path.join(OPTIONS.target_tmp, "RECOVERY")))
576 updating_recovery = (source_recovery.data != target_recovery.data)
578 # We reserve the last 0.3 of the progress bar for the
579 # device-specific IncrementalOTA_InstallEnd() call at the end, which
580 # will typically install a radio image.
581 progress_bar_total = 0.7
583 progress_bar_total -= 0.1
585 AppendAssertions(script, target_zip)
586 device_specific.IncrementalOTA_Assertions()
588 script.Print("Verifying current system...")
590 pb_verify = progress_bar_total * 0.3 * \
591 (total_patched_size /
592 float(total_patched_size+total_verbatim_size+1))
594 for i, (fn, tf, sf, size) in enumerate(patch_list):
596 next_sizes = sum([i[3] for i in patch_list[i:i+5]])
597 script.ShowProgress(next_sizes * pb_verify / (total_patched_size+1), 1)
599 script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
602 d = Difference(target_boot, source_boot, "imgdiff")
603 print "boot target: %d source: %d diff: %d" % (
604 target_boot.size, source_boot.size, len(d))
606 common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
608 script.PatchCheck("MTD:boot:%d:%s:%d:%s" %
609 (source_boot.size, source_boot.sha1,
610 target_boot.size, target_boot.sha1))
612 if patch_list or updating_recovery or updating_boot:
613 script.CacheFreeSpaceCheck(largest_source_size)
614 script.Print("Unpacking patches...")
615 script.UnpackPackageDir("patch", "/tmp/patchtmp")
617 device_specific.IncrementalOTA_VerifyEnd()
619 script.Comment("---- start making changes here ----")
621 if OPTIONS.wipe_user_data:
622 script.Print("Erasing user data...")
623 script.FormatPartition("userdata")
625 script.Print("Removing unneeded files...")
626 script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
627 ["/"+i for i in sorted(source_data)
628 if i not in target_data])
631 # Produce the boot image by applying a patch to the current
632 # contents of the boot partition, and write it back to the
634 script.Print("Patching boot image...")
635 script.ApplyPatch("MTD:boot:%d:%s:%d:%s"
636 % (source_boot.size, source_boot.sha1,
637 target_boot.size, target_boot.sha1),
639 target_boot.size, target_boot.sha1,
640 source_boot.sha1, "/tmp/patchtmp/boot.img.p")
641 print "boot image changed; including."
643 print "boot image unchanged; skipping."
645 if updating_recovery:
646 # Is it better to generate recovery as a patch from the current
647 # boot image, or from the previous recovery image? For large
648 # updates with significant kernel changes, probably the former.
649 # For small updates where the kernel hasn't changed, almost
650 # certainly the latter. We pick the first option. Future
651 # complicated schemes may let us effectively use both.
653 # A wacky possibility: as long as there is room in the boot
654 # partition, include the binaries and image files from recovery in
655 # the boot image (though not in the ramdisk) so they can be used
656 # as fodder for constructing the recovery image.
657 recovery_sh_item = MakeRecoveryPatch(output_zip,
658 target_recovery, target_boot)
659 print "recovery image changed; including as patch from boot."
661 print "recovery image unchanged; skipping."
663 script.Print("Patching system files...")
664 pb_apply = progress_bar_total * 0.7 * \
665 (total_patched_size /
666 float(total_patched_size+total_verbatim_size+1))
667 for i, (fn, tf, sf, size) in enumerate(patch_list):
669 next_sizes = sum([i[3] for i in patch_list[i:i+5]])
670 script.ShowProgress(next_sizes * pb_apply / (total_patched_size+1), 1)
671 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1,
672 sf.sha1, "/tmp/patchtmp/"+fn+".p")
674 target_symlinks = CopySystemFiles(target_zip, None)
676 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
677 temp_script = script.MakeTemporary()
679 if updating_recovery:
680 recovery_sh_item.uid = 0
681 recovery_sh_item.gid = 0
682 recovery_sh_item.mode = 0544
683 Item.Get("system").SetPermissions(temp_script)
685 # Note that this call will mess up the tree of Items, so make sure
686 # we're done with it.
687 source_symlinks = CopySystemFiles(source_zip, None)
688 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
690 # Delete all the symlinks in source that aren't in target. This
691 # needs to happen before verbatim files are unpacked, in case a
692 # symlink in the source is replaced by a real file in the target.
694 for dest, link in source_symlinks:
695 if link not in target_symlinks_d:
696 to_delete.append(link)
697 script.DeleteFiles(to_delete)
700 pb_verbatim = progress_bar_total * \
701 (total_verbatim_size /
702 float(total_patched_size+total_verbatim_size+1))
703 script.ShowProgress(pb_verbatim, 5)
704 script.Print("Unpacking new files...")
705 script.UnpackPackageDir("system", "/system")
707 script.Print("Symlinks and permissions...")
709 # Create all the symlinks that don't already exist, or point to
710 # somewhere different than what we want. Delete each symlink before
711 # creating it, since the 'symlink' command won't overwrite.
713 for dest, link in target_symlinks:
714 if link in source_symlinks_d:
715 if dest != source_symlinks_d[link]:
716 to_create.append((dest, link))
718 to_create.append((dest, link))
719 script.DeleteFiles([i[1] for i in to_create])
720 script.MakeSymlinks(to_create)
722 # Now that the symlinks are created, we can set all the
724 script.AppendScript(temp_script)
726 # Write the radio image, if necessary.
727 script.ShowProgress(0.3, 10)
728 device_specific.IncrementalOTA_InstallEnd()
730 if OPTIONS.extra_script is not None:
731 scirpt.AppendExtra(OPTIONS.extra_script)
733 script.AddToZip(target_zip, output_zip)
738 def option_handler(o, a):
739 if o in ("-b", "--board_config"):
741 elif o in ("-k", "--package_key"):
742 OPTIONS.package_key = a
743 elif o in ("-i", "--incremental_from"):
744 OPTIONS.incremental_source = a
745 elif o in ("-w", "--wipe_user_data"):
746 OPTIONS.wipe_user_data = True
747 elif o in ("-n", "--no_prereq"):
748 OPTIONS.omit_prereq = True
749 elif o in ("-e", "--extra_script"):
750 OPTIONS.extra_script = a
751 elif o in ("-m", "--script_mode"):
752 OPTIONS.script_mode = a
757 args = common.ParseOptions(argv, __doc__,
758 extra_opts="b:k:i:d:wne:m:",
759 extra_long_opts=["board_config=",
766 extra_option_handler=option_handler)
769 common.Usage(__doc__)
772 if OPTIONS.script_mode not in ("amend", "edify", "auto"):
773 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
775 if OPTIONS.extra_script is not None:
776 OPTIONS.extra_script = open(OPTIONS.extra_script).read()
778 print "unzipping target target-files..."
779 OPTIONS.input_tmp = common.UnzipTemp(args[0])
781 common.LoadMaxSizes()
782 if not OPTIONS.max_image_size:
784 print " WARNING: Failed to load max image sizes; will not enforce"
785 print " image size limits."
788 OPTIONS.target_tmp = OPTIONS.input_tmp
789 input_zip = zipfile.ZipFile(args[0], "r")
790 if OPTIONS.package_key:
791 temp_zip_file = tempfile.NamedTemporaryFile()
792 output_zip = zipfile.ZipFile(temp_zip_file, "w",
793 compression=zipfile.ZIP_DEFLATED)
795 output_zip = zipfile.ZipFile(args[1], "w",
796 compression=zipfile.ZIP_DEFLATED)
798 if OPTIONS.incremental_source is None:
799 WriteFullOTAPackage(input_zip, output_zip)
801 print "unzipping source target-files..."
802 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
803 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
804 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
807 if OPTIONS.package_key:
808 SignOutput(temp_zip_file.name, args[1])
809 temp_zip_file.close()
816 if __name__ == '__main__':
819 except common.ExternalError, e:
821 print " ERROR: %s" % (e,)