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,
280 def AppendAssertions(script, input_zip):
281 device = GetBuildProp("ro.product.device", input_zip)
282 script.AssertDevice(device)
284 info = input_zip.read("OTA/android-info.txt")
285 m = re.search(r"require\s+version-bootloader\s*=\s*(\S+)", info)
287 bootloaders = m.group(1).split("|")
288 if "*" not in bootloaders:
289 script.AssertSomeBootloader(*bootloaders)
292 def MakeRecoveryPatch(output_zip, recovery_img, boot_img):
293 """Generate a binary patch that creates the recovery image starting
294 with the boot image. (Most of the space in these images is just the
295 kernel, which is identical for the two, so the resulting patch
296 should be efficient.) Add it to the output zip, along with a shell
297 script that is run from init.rc on first boot to actually do the
298 patching and install the new recovery image.
300 recovery_img and boot_img should be File objects for the
301 corresponding images.
303 Returns an Item for the shell script, which must be made
307 patch = Difference(recovery_img, boot_img, "imgdiff")
308 common.ZipWriteStr(output_zip, "system/recovery-from-boot.p", patch)
309 Item.Get("system/recovery-from-boot.p", dir=False)
311 # Images with different content will have a different first page, so
312 # we check to see if this recovery has already been installed by
313 # testing just the first 2k.
315 header_sha1 = sha.sha(recovery_img.data[:HEADER_SIZE]).hexdigest()
316 sh = """#!/system/bin/sh
317 if ! applypatch -c MTD:recovery:%(header_size)d:%(header_sha1)s; then
318 log -t recovery "Installing new recovery image"
319 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
321 log -t recovery "Recovery image already installed"
323 """ % { 'boot_size': boot_img.size,
324 'boot_sha1': boot_img.sha1,
325 'header_size': HEADER_SIZE,
326 'header_sha1': header_sha1,
327 'recovery_size': recovery_img.size,
328 'recovery_sha1': recovery_img.sha1 }
329 common.ZipWriteStr(output_zip, "system/etc/install-recovery.sh", sh)
330 return Item.Get("system/etc/install-recovery.sh", dir=False)
333 def WriteFullOTAPackage(input_zip, output_zip):
334 if OPTIONS.script_mode == "auto":
335 script = both_generator.BothGenerator(2)
336 elif OPTIONS.script_mode == "amend":
337 script = amend_generator.AmendGenerator()
339 # TODO: how to determine this? We don't know what version it will
340 # be installed on top of. For now, we expect the API just won't
342 script = edify_generator.EdifyGenerator(2)
344 device_specific = common.DeviceSpecificParams(
346 output_zip=output_zip,
348 input_tmp=OPTIONS.input_tmp)
350 if not OPTIONS.omit_prereq:
351 ts = GetBuildProp("ro.build.date.utc", input_zip)
352 script.AssertOlderBuild(ts)
354 AppendAssertions(script, input_zip)
355 device_specific.FullOTA_Assertions()
357 script.ShowProgress(0.5, 0)
359 if OPTIONS.wipe_user_data:
360 script.FormatPartition("userdata")
362 script.FormatPartition("system")
363 script.Mount("MTD", "system", "/system")
364 script.UnpackPackageDir("system", "/system")
366 symlinks = CopySystemFiles(input_zip, output_zip)
367 script.MakeSymlinks(symlinks)
369 boot_img = File("boot.img", common.BuildBootableImage(
370 os.path.join(OPTIONS.input_tmp, "BOOT")))
371 recovery_img = File("recovery.img", common.BuildBootableImage(
372 os.path.join(OPTIONS.input_tmp, "RECOVERY")))
373 i = MakeRecoveryPatch(output_zip, recovery_img, boot_img)
377 # GetMetadata uses the data in android_filesystem_config.h to assign
378 # the uid/gid/mode of all files. We want to override that for the
379 # recovery patching shell script to make it executable.
383 Item.Get("system").SetPermissions(script)
385 common.CheckSize(boot_img.data, "boot.img")
386 common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
387 script.ShowProgress(0.2, 0)
389 script.ShowProgress(0.2, 10)
390 script.WriteRawImage("boot", "boot.img")
392 script.ShowProgress(0.1, 0)
393 device_specific.FullOTA_InstallEnd()
395 if OPTIONS.extra_script is not None:
396 script.AppendExtra(OPTIONS.extra_script)
398 script.AddToZip(input_zip, output_zip)
402 def __init__(self, name, data):
405 self.size = len(data)
406 self.sha1 = sha.sha(data).hexdigest()
408 def WriteToTemp(self):
409 t = tempfile.NamedTemporaryFile()
414 def AddToZip(self, z):
415 common.ZipWriteStr(z, self.name, self.data)
418 def LoadSystemFiles(z):
419 """Load all the files from SYSTEM/... in a given target-files
420 ZipFile, and return a dict of {filename: File object}."""
422 for info in z.infolist():
423 if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
424 fn = "system/" + info.filename[7:]
425 data = z.read(info.filename)
426 out[fn] = File(fn, data)
430 def Difference(tf, sf, diff_program):
431 """Return the patch (as a string of data) needed to turn sf into tf.
432 diff_program is the name of an external program (or list, if
433 additional arguments are desired) to run to generate the diff.
436 ttemp = tf.WriteToTemp()
437 stemp = sf.WriteToTemp()
439 ext = os.path.splitext(tf.name)[1]
442 ptemp = tempfile.NamedTemporaryFile()
443 if isinstance(diff_program, list):
444 cmd = copy.copy(diff_program)
447 cmd.append(stemp.name)
448 cmd.append(ttemp.name)
449 cmd.append(ptemp.name)
451 _, err = p.communicate()
452 if err or p.returncode != 0:
453 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
464 def GetBuildProp(property, z):
465 """Return the fingerprint of the build of a given target-files
467 bp = z.read("SYSTEM/build.prop")
470 m = re.search(re.escape(property) + r"=(.*)\n", bp)
472 raise common.ExternalError("couldn't find %s in build.prop" % (property,))
473 return m.group(1).strip()
476 def GetRecoveryAPIVersion(zip):
477 """Returns the version of the recovery API. Version 0 is the older
478 amend code (no separate binary)."""
480 version = zip.read("META/recovery-api-version.txt")
484 # version one didn't have the recovery-api-version.txt file, but
485 # it did include an updater binary.
486 zip.getinfo("OTA/bin/updater")
491 def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
492 source_version = GetRecoveryAPIVersion(source_zip)
494 if OPTIONS.script_mode == 'amend':
495 script = amend_generator.AmendGenerator()
496 elif OPTIONS.script_mode == 'edify':
497 if source_version == 0:
498 print ("WARNING: generating edify script for a source that "
500 script = edify_generator.EdifyGenerator(source_version)
501 elif OPTIONS.script_mode == 'auto':
502 if source_version > 0:
503 script = edify_generator.EdifyGenerator(source_version)
505 script = amend_generator.AmendGenerator()
507 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
509 device_specific = common.DeviceSpecificParams(
510 source_zip=source_zip,
511 target_zip=target_zip,
512 output_zip=output_zip,
515 print "Loading target..."
516 target_data = LoadSystemFiles(target_zip)
517 print "Loading source..."
518 source_data = LoadSystemFiles(source_zip)
520 verbatim_targets = []
522 largest_source_size = 0
523 for fn in sorted(target_data.keys()):
525 sf = source_data.get(fn, None)
527 if sf is None or fn in OPTIONS.require_verbatim:
528 # This file should be included verbatim
529 if fn in OPTIONS.prohibit_verbatim:
530 raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
531 print "send", fn, "verbatim"
532 tf.AddToZip(output_zip)
533 verbatim_targets.append((fn, tf.size))
534 elif tf.sha1 != sf.sha1:
535 # File is different; consider sending as a patch
536 diff_method = "bsdiff"
537 if tf.name.endswith(".gz"):
538 diff_method = "imgdiff"
539 d = Difference(tf, sf, diff_method)
541 print fn, tf.size, len(d), (float(len(d)) / tf.size)
542 if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
543 # patch is almost as big as the file; don't bother patching
544 tf.AddToZip(output_zip)
545 verbatim_targets.append((fn, tf.size))
547 common.ZipWriteStr(output_zip, "patch/" + fn + ".p", d)
548 patch_list.append((fn, tf, sf, tf.size))
549 largest_source_size = max(largest_source_size, sf.size)
551 # Target file identical to source.
554 total_verbatim_size = sum([i[1] for i in verbatim_targets])
555 total_patched_size = sum([i[3] for i in patch_list])
557 source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
558 target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
560 script.Mount("MTD", "system", "/system")
561 script.AssertSomeFingerprint(source_fp, target_fp)
563 source_boot = File("/tmp/boot.img",
564 common.BuildBootableImage(
565 os.path.join(OPTIONS.source_tmp, "BOOT")))
566 target_boot = File("/tmp/boot.img",
567 common.BuildBootableImage(
568 os.path.join(OPTIONS.target_tmp, "BOOT")))
569 updating_boot = (source_boot.data != target_boot.data)
571 source_recovery = File("system/recovery.img",
572 common.BuildBootableImage(
573 os.path.join(OPTIONS.source_tmp, "RECOVERY")))
574 target_recovery = File("system/recovery.img",
575 common.BuildBootableImage(
576 os.path.join(OPTIONS.target_tmp, "RECOVERY")))
577 updating_recovery = (source_recovery.data != target_recovery.data)
579 # We reserve the last 0.3 of the progress bar for the
580 # device-specific IncrementalOTA_InstallEnd() call at the end, which
581 # will typically install a radio image.
582 progress_bar_total = 0.7
584 progress_bar_total -= 0.1
586 AppendAssertions(script, target_zip)
587 device_specific.IncrementalOTA_Assertions()
589 script.Print("Verifying current system...")
591 pb_verify = progress_bar_total * 0.3 * \
592 (total_patched_size /
593 float(total_patched_size+total_verbatim_size+1))
595 for i, (fn, tf, sf, size) in enumerate(patch_list):
597 next_sizes = sum([i[3] for i in patch_list[i:i+5]])
598 script.ShowProgress(next_sizes * pb_verify / (total_patched_size+1), 1)
600 script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
603 d = Difference(target_boot, source_boot, "imgdiff")
604 print "boot target: %d source: %d diff: %d" % (
605 target_boot.size, source_boot.size, len(d))
607 common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
609 script.PatchCheck("MTD:boot:%d:%s:%d:%s" %
610 (source_boot.size, source_boot.sha1,
611 target_boot.size, target_boot.sha1))
613 if patch_list or updating_recovery or updating_boot:
614 script.CacheFreeSpaceCheck(largest_source_size)
615 script.Print("Unpacking patches...")
616 script.UnpackPackageDir("patch", "/tmp/patchtmp")
618 device_specific.IncrementalOTA_VerifyEnd()
620 script.Comment("---- start making changes here ----")
622 if OPTIONS.wipe_user_data:
623 script.Print("Erasing user data...")
624 script.FormatPartition("userdata")
626 script.Print("Removing unneeded files...")
627 script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
628 ["/"+i for i in sorted(source_data)
629 if i not in target_data])
632 # Produce the boot image by applying a patch to the current
633 # contents of the boot partition, and write it back to the
635 script.Print("Patching boot image...")
636 script.ApplyPatch("MTD:boot:%d:%s:%d:%s"
637 % (source_boot.size, source_boot.sha1,
638 target_boot.size, target_boot.sha1),
640 target_boot.size, target_boot.sha1,
641 source_boot.sha1, "/tmp/patchtmp/boot.img.p")
642 print "boot image changed; including."
644 print "boot image unchanged; skipping."
646 if updating_recovery:
647 # Is it better to generate recovery as a patch from the current
648 # boot image, or from the previous recovery image? For large
649 # updates with significant kernel changes, probably the former.
650 # For small updates where the kernel hasn't changed, almost
651 # certainly the latter. We pick the first option. Future
652 # complicated schemes may let us effectively use both.
654 # A wacky possibility: as long as there is room in the boot
655 # partition, include the binaries and image files from recovery in
656 # the boot image (though not in the ramdisk) so they can be used
657 # as fodder for constructing the recovery image.
658 recovery_sh_item = MakeRecoveryPatch(output_zip,
659 target_recovery, target_boot)
660 print "recovery image changed; including as patch from boot."
662 print "recovery image unchanged; skipping."
664 script.Print("Patching system files...")
665 pb_apply = progress_bar_total * 0.7 * \
666 (total_patched_size /
667 float(total_patched_size+total_verbatim_size+1))
668 for i, (fn, tf, sf, size) in enumerate(patch_list):
670 next_sizes = sum([i[3] for i in patch_list[i:i+5]])
671 script.ShowProgress(next_sizes * pb_apply / (total_patched_size+1), 1)
672 script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1,
673 sf.sha1, "/tmp/patchtmp/"+fn+".p")
675 target_symlinks = CopySystemFiles(target_zip, None)
677 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
678 temp_script = script.MakeTemporary()
680 if updating_recovery:
681 recovery_sh_item.uid = 0
682 recovery_sh_item.gid = 0
683 recovery_sh_item.mode = 0544
684 Item.Get("system").SetPermissions(temp_script)
686 # Note that this call will mess up the tree of Items, so make sure
687 # we're done with it.
688 source_symlinks = CopySystemFiles(source_zip, None)
689 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
691 # Delete all the symlinks in source that aren't in target. This
692 # needs to happen before verbatim files are unpacked, in case a
693 # symlink in the source is replaced by a real file in the target.
695 for dest, link in source_symlinks:
696 if link not in target_symlinks_d:
697 to_delete.append(link)
698 script.DeleteFiles(to_delete)
701 pb_verbatim = progress_bar_total * \
702 (total_verbatim_size /
703 float(total_patched_size+total_verbatim_size+1))
704 script.ShowProgress(pb_verbatim, 5)
705 script.Print("Unpacking new files...")
706 script.UnpackPackageDir("system", "/system")
708 script.Print("Symlinks and permissions...")
710 # Create all the symlinks that don't already exist, or point to
711 # somewhere different than what we want. Delete each symlink before
712 # creating it, since the 'symlink' command won't overwrite.
714 for dest, link in target_symlinks:
715 if link in source_symlinks_d:
716 if dest != source_symlinks_d[link]:
717 to_create.append((dest, link))
719 to_create.append((dest, link))
720 script.DeleteFiles([i[1] for i in to_create])
721 script.MakeSymlinks(to_create)
723 # Now that the symlinks are created, we can set all the
725 script.AppendScript(temp_script)
727 # Write the radio image, if necessary.
728 script.ShowProgress(0.3, 10)
729 device_specific.IncrementalOTA_InstallEnd()
731 if OPTIONS.extra_script is not None:
732 scirpt.AppendExtra(OPTIONS.extra_script)
734 script.AddToZip(target_zip, output_zip)
739 def option_handler(o, a):
740 if o in ("-b", "--board_config"):
742 elif o in ("-k", "--package_key"):
743 OPTIONS.package_key = a
744 elif o in ("-i", "--incremental_from"):
745 OPTIONS.incremental_source = a
746 elif o in ("-w", "--wipe_user_data"):
747 OPTIONS.wipe_user_data = True
748 elif o in ("-n", "--no_prereq"):
749 OPTIONS.omit_prereq = True
750 elif o in ("-e", "--extra_script"):
751 OPTIONS.extra_script = a
752 elif o in ("-m", "--script_mode"):
753 OPTIONS.script_mode = a
758 args = common.ParseOptions(argv, __doc__,
759 extra_opts="b:k:i:d:wne:m:",
760 extra_long_opts=["board_config=",
767 extra_option_handler=option_handler)
770 common.Usage(__doc__)
773 if OPTIONS.script_mode not in ("amend", "edify", "auto"):
774 raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))
776 if OPTIONS.extra_script is not None:
777 OPTIONS.extra_script = open(OPTIONS.extra_script).read()
779 print "unzipping target target-files..."
780 OPTIONS.input_tmp = common.UnzipTemp(args[0])
782 common.LoadMaxSizes()
783 if not OPTIONS.max_image_size:
785 print " WARNING: Failed to load max image sizes; will not enforce"
786 print " image size limits."
789 OPTIONS.target_tmp = OPTIONS.input_tmp
790 input_zip = zipfile.ZipFile(args[0], "r")
791 if OPTIONS.package_key:
792 temp_zip_file = tempfile.NamedTemporaryFile()
793 output_zip = zipfile.ZipFile(temp_zip_file, "w",
794 compression=zipfile.ZIP_DEFLATED)
796 output_zip = zipfile.ZipFile(args[1], "w",
797 compression=zipfile.ZIP_DEFLATED)
799 if OPTIONS.incremental_source is None:
800 WriteFullOTAPackage(input_zip, output_zip)
802 print "unzipping source target-files..."
803 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
804 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
805 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
808 if OPTIONS.package_key:
809 SignOutput(temp_zip_file.name, args[1])
810 temp_zip_file.close()
817 if __name__ == '__main__':
820 except common.ExternalError, e:
822 print " ERROR: %s" % (e,)