blob: 364f751b93ec5fbfd1ce5265f6f00516fed44357 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001#!/usr/bin/env python
2#
3# Copyright (C) 2008 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, produces an OTA package that installs
19that build. An incremental OTA is produced if -i is given, otherwise
20a full OTA is produced.
21
22Usage: ota_from_target_files [flags] input_target_files output_ota_package
23
24 -b (--board_config) <file>
25 Specifies a BoardConfig.mk file containing image max sizes
26 against which the generated image files are checked.
27
28 -k (--package_key) <key>
29 Key to use to sign the package (default is
30 "build/target/product/security/testkey").
31
32 -i (--incremental_from) <file>
33 Generate an incremental OTA using the given target-files zip as
34 the starting build.
35
Doug Zongkerdbfaae52009-04-21 17:12:54 -070036 -w (--wipe_user_data)
37 Generate an OTA package that will wipe the user data partition
38 when installed.
39
Doug Zongker962069c2009-04-23 11:41:58 -070040 -n (--no_prereq)
41 Omit the timestamp prereq check normally included at the top of
42 the build scripts (used for developer OTA packages which
43 legitimately need to go back and forth).
44
Doug Zongker1c390a22009-05-14 19:06:36 -070045 -e (--extra_script) <file>
46 Insert the contents of file at the end of the update script.
47
Doug Zongkereef39442009-04-02 12:14:19 -070048"""
49
50import sys
51
52if sys.hexversion < 0x02040000:
53 print >> sys.stderr, "Python 2.4 or newer is required."
54 sys.exit(1)
55
56import copy
57import os
58import re
59import sha
60import subprocess
61import tempfile
62import time
63import zipfile
64
65import common
66
67OPTIONS = common.OPTIONS
68OPTIONS.package_key = "build/target/product/security/testkey"
69OPTIONS.incremental_source = None
70OPTIONS.require_verbatim = set()
71OPTIONS.prohibit_verbatim = set(("system/build.prop",))
72OPTIONS.patch_threshold = 0.95
Doug Zongkerdbfaae52009-04-21 17:12:54 -070073OPTIONS.wipe_user_data = False
Doug Zongker962069c2009-04-23 11:41:58 -070074OPTIONS.omit_prereq = False
Doug Zongker1c390a22009-05-14 19:06:36 -070075OPTIONS.extra_script = None
Doug Zongkereef39442009-04-02 12:14:19 -070076
77def MostPopularKey(d, default):
78 """Given a dict, return the key corresponding to the largest
79 value. Returns 'default' if the dict is empty."""
80 x = [(v, k) for (k, v) in d.iteritems()]
81 if not x: return default
82 x.sort()
83 return x[-1][1]
84
85
86def IsSymlink(info):
87 """Return true if the zipfile.ZipInfo object passed in represents a
88 symlink."""
89 return (info.external_attr >> 16) == 0120777
90
91
92
93class Item:
94 """Items represent the metadata (user, group, mode) of files and
95 directories in the system image."""
96 ITEMS = {}
97 def __init__(self, name, dir=False):
98 self.name = name
99 self.uid = None
100 self.gid = None
101 self.mode = None
102 self.dir = dir
103
104 if name:
105 self.parent = Item.Get(os.path.dirname(name), dir=True)
106 self.parent.children.append(self)
107 else:
108 self.parent = None
109 if dir:
110 self.children = []
111
112 def Dump(self, indent=0):
113 if self.uid is not None:
114 print "%s%s %d %d %o" % (" "*indent, self.name, self.uid, self.gid, self.mode)
115 else:
116 print "%s%s %s %s %s" % (" "*indent, self.name, self.uid, self.gid, self.mode)
117 if self.dir:
118 print "%s%s" % (" "*indent, self.descendants)
119 print "%s%s" % (" "*indent, self.best_subtree)
120 for i in self.children:
121 i.Dump(indent=indent+1)
122
123 @classmethod
124 def Get(cls, name, dir=False):
125 if name not in cls.ITEMS:
126 cls.ITEMS[name] = Item(name, dir=dir)
127 return cls.ITEMS[name]
128
129 @classmethod
130 def GetMetadata(cls):
131 """Run the external 'fs_config' program to determine the desired
132 uid, gid, and mode for every Item object."""
133 p = common.Run(["fs_config"], stdin=subprocess.PIPE,
134 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
135 suffix = { False: "", True: "/" }
136 input = "".join(["%s%s\n" % (i.name, suffix[i.dir])
137 for i in cls.ITEMS.itervalues() if i.name])
138 output, error = p.communicate(input)
139 assert not error
140
141 for line in output.split("\n"):
142 if not line: continue
143 name, uid, gid, mode = line.split()
144 i = cls.ITEMS[name]
145 i.uid = int(uid)
146 i.gid = int(gid)
147 i.mode = int(mode, 8)
148 if i.dir:
149 i.children.sort(key=lambda i: i.name)
150
151 def CountChildMetadata(self):
152 """Count up the (uid, gid, mode) tuples for all children and
153 determine the best strategy for using set_perm_recursive and
154 set_perm to correctly chown/chmod all the files to their desired
155 values. Recursively calls itself for all descendants.
156
157 Returns a dict of {(uid, gid, dmode, fmode): count} counting up
158 all descendants of this node. (dmode or fmode may be None.) Also
159 sets the best_subtree of each directory Item to the (uid, gid,
160 dmode, fmode) tuple that will match the most descendants of that
161 Item.
162 """
163
164 assert self.dir
165 d = self.descendants = {(self.uid, self.gid, self.mode, None): 1}
166 for i in self.children:
167 if i.dir:
168 for k, v in i.CountChildMetadata().iteritems():
169 d[k] = d.get(k, 0) + v
170 else:
171 k = (i.uid, i.gid, None, i.mode)
172 d[k] = d.get(k, 0) + 1
173
174 # Find the (uid, gid, dmode, fmode) tuple that matches the most
175 # descendants.
176
177 # First, find the (uid, gid) pair that matches the most
178 # descendants.
179 ug = {}
180 for (uid, gid, _, _), count in d.iteritems():
181 ug[(uid, gid)] = ug.get((uid, gid), 0) + count
182 ug = MostPopularKey(ug, (0, 0))
183
184 # Now find the dmode and fmode that match the most descendants
185 # with that (uid, gid), and choose those.
186 best_dmode = (0, 0755)
187 best_fmode = (0, 0644)
188 for k, count in d.iteritems():
189 if k[:2] != ug: continue
190 if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
191 if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
192 self.best_subtree = ug + (best_dmode[1], best_fmode[1])
193
194 return d
195
196 def SetPermissions(self, script, renamer=lambda x: x):
197 """Append set_perm/set_perm_recursive commands to 'script' to
198 set all permissions, users, and groups for the tree of files
199 rooted at 'self'. 'renamer' turns the filenames stored in the
200 tree of Items into the strings used in the script."""
201
202 self.CountChildMetadata()
203
204 def recurse(item, current):
205 # current is the (uid, gid, dmode, fmode) tuple that the current
206 # item (and all its children) have already been set to. We only
207 # need to issue set_perm/set_perm_recursive commands if we're
208 # supposed to be something different.
209 if item.dir:
210 if current != item.best_subtree:
211 script.append("set_perm_recursive %d %d 0%o 0%o %s" %
212 (item.best_subtree + (renamer(item.name),)))
213 current = item.best_subtree
214
215 if item.uid != current[0] or item.gid != current[1] or \
216 item.mode != current[2]:
217 script.append("set_perm %d %d 0%o %s" %
218 (item.uid, item.gid, item.mode, renamer(item.name)))
219
220 for i in item.children:
221 recurse(i, current)
222 else:
223 if item.uid != current[0] or item.gid != current[1] or \
224 item.mode != current[3]:
225 script.append("set_perm %d %d 0%o %s" %
226 (item.uid, item.gid, item.mode, renamer(item.name)))
227
228 recurse(self, (-1, -1, -1, -1))
229
230
231def CopySystemFiles(input_zip, output_zip=None,
232 substitute=None):
233 """Copies files underneath system/ in the input zip to the output
234 zip. Populates the Item class with their metadata, and returns a
235 list of symlinks. output_zip may be None, in which case the copy is
236 skipped (but the other side effects still happen). substitute is an
237 optional dict of {output filename: contents} to be output instead of
238 certain input files.
239 """
240
241 symlinks = []
242
243 for info in input_zip.infolist():
244 if info.filename.startswith("SYSTEM/"):
245 basefilename = info.filename[7:]
246 if IsSymlink(info):
247 symlinks.append((input_zip.read(info.filename),
248 "SYSTEM:" + basefilename))
249 else:
250 info2 = copy.copy(info)
251 fn = info2.filename = "system/" + basefilename
252 if substitute and fn in substitute and substitute[fn] is None:
253 continue
254 if output_zip is not None:
255 if substitute and fn in substitute:
256 data = substitute[fn]
257 else:
258 data = input_zip.read(info.filename)
259 output_zip.writestr(info2, data)
260 if fn.endswith("/"):
261 Item.Get(fn[:-1], dir=True)
262 else:
263 Item.Get(fn, dir=False)
264
265 symlinks.sort()
266 return symlinks
267
268
269def AddScript(script, output_zip):
270 now = time.localtime()
271 i = zipfile.ZipInfo("META-INF/com/google/android/update-script",
272 (now.tm_year, now.tm_mon, now.tm_mday,
273 now.tm_hour, now.tm_min, now.tm_sec))
274 output_zip.writestr(i, "\n".join(script) + "\n")
275
276
277def SignOutput(temp_zip_name, output_zip_name):
278 key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
279 pw = key_passwords[OPTIONS.package_key]
280
281 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw)
282
283
284def SubstituteRoot(s):
285 if s == "system": return "SYSTEM:"
286 assert s.startswith("system/")
287 return "SYSTEM:" + s[7:]
288
289def FixPermissions(script):
290 Item.GetMetadata()
291 root = Item.Get("system")
292 root.SetPermissions(script, renamer=SubstituteRoot)
293
294def DeleteFiles(script, to_delete):
295 line = []
296 t = 0
297 for i in to_delete:
298 line.append(i)
299 t += len(i) + 1
300 if t > 80:
301 script.append("delete " + " ".join(line))
302 line = []
303 t = 0
304 if line:
305 script.append("delete " + " ".join(line))
306
307def AppendAssertions(script, input_zip):
308 script.append('assert compatible_with("0.2") == "true"')
309
310 device = GetBuildProp("ro.product.device", input_zip)
311 script.append('assert getprop("ro.product.device") == "%s" || '
312 'getprop("ro.build.product") == "%s"' % (device, device))
313
314 info = input_zip.read("OTA/android-info.txt")
315 m = re.search(r"require\s+version-bootloader\s*=\s*(\S+)", info)
316 if not m:
317 raise ExternalError("failed to find required bootloaders in "
318 "android-info.txt")
319 bootloaders = m.group(1).split("|")
320 script.append("assert " +
321 " || ".join(['getprop("ro.bootloader") == "%s"' % (b,)
322 for b in bootloaders]))
323
324
325def IncludeBinary(name, input_zip, output_zip):
326 try:
327 data = input_zip.read(os.path.join("OTA/bin", name))
328 output_zip.writestr(name, data)
329 except IOError:
330 raise ExternalError('unable to include device binary "%s"' % (name,))
331
332
333def WriteFullOTAPackage(input_zip, output_zip):
334 script = []
335
Doug Zongker962069c2009-04-23 11:41:58 -0700336 if not OPTIONS.omit_prereq:
337 ts = GetBuildProp("ro.build.date.utc", input_zip)
338 script.append("run_program PACKAGE:check_prereq %s" % (ts,))
339 IncludeBinary("check_prereq", input_zip, output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -0700340
341 AppendAssertions(script, input_zip)
342
343 script.append("format BOOT:")
344 script.append("show_progress 0.1 0")
345
346 output_zip.writestr("radio.img", input_zip.read("RADIO/image"))
347 script.append("write_radio_image PACKAGE:radio.img")
348 script.append("show_progress 0.5 0")
349
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700350 if OPTIONS.wipe_user_data:
351 script.append("format DATA:")
352
Doug Zongkereef39442009-04-02 12:14:19 -0700353 script.append("format SYSTEM:")
354 script.append("copy_dir PACKAGE:system SYSTEM:")
355
356 symlinks = CopySystemFiles(input_zip, output_zip)
357 script.extend(["symlink %s %s" % s for s in symlinks])
358
359 common.BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "RECOVERY"),
360 "system/recovery.img", output_zip)
361 Item.Get("system/recovery.img", dir=False)
362
363 FixPermissions(script)
364
365 common.AddBoot(output_zip)
366 script.append("show_progress 0.2 0")
367 script.append("write_raw_image PACKAGE:boot.img BOOT:")
368 script.append("show_progress 0.2 10")
369
Doug Zongker1c390a22009-05-14 19:06:36 -0700370 if OPTIONS.extra_script is not None:
371 script.append(OPTIONS.extra_script)
372
Doug Zongkereef39442009-04-02 12:14:19 -0700373 AddScript(script, output_zip)
374
375
376class File(object):
377 def __init__(self, name, data):
378 self.name = name
379 self.data = data
380 self.size = len(data)
381 self.sha1 = sha.sha(data).hexdigest()
382
383 def WriteToTemp(self):
384 t = tempfile.NamedTemporaryFile()
385 t.write(self.data)
386 t.flush()
387 return t
388
389 def AddToZip(self, z):
390 z.writestr(self.name, self.data)
391
392
393def LoadSystemFiles(z):
394 """Load all the files from SYSTEM/... in a given target-files
395 ZipFile, and return a dict of {filename: File object}."""
396 out = {}
397 for info in z.infolist():
398 if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
399 fn = "system/" + info.filename[7:]
400 data = z.read(info.filename)
401 out[fn] = File(fn, data)
402 return out
403
404
405def Difference(tf, sf):
406 """Return the patch (as a string of data) needed to turn sf into tf."""
407
408 ttemp = tf.WriteToTemp()
409 stemp = sf.WriteToTemp()
410
411 ext = os.path.splitext(tf.name)[1]
412
413 try:
414 ptemp = tempfile.NamedTemporaryFile()
415 p = common.Run(["bsdiff", stemp.name, ttemp.name, ptemp.name])
416 _, err = p.communicate()
417 if err:
418 raise ExternalError("failure running bsdiff:\n%s\n" % (err,))
419 diff = ptemp.read()
420 ptemp.close()
421 finally:
422 stemp.close()
423 ttemp.close()
424
425 return diff
426
427
428def GetBuildProp(property, z):
429 """Return the fingerprint of the build of a given target-files
430 ZipFile object."""
431 bp = z.read("SYSTEM/build.prop")
432 if not property:
433 return bp
434 m = re.search(re.escape(property) + r"=(.*)\n", bp)
435 if not m:
436 raise ExternalException("couldn't find %s in build.prop" % (property,))
437 return m.group(1).strip()
438
439
440def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
441 script = []
442
443 print "Loading target..."
444 target_data = LoadSystemFiles(target_zip)
445 print "Loading source..."
446 source_data = LoadSystemFiles(source_zip)
447
448 verbatim_targets = []
449 patch_list = []
450 largest_source_size = 0
451 for fn in sorted(target_data.keys()):
452 tf = target_data[fn]
453 sf = source_data.get(fn, None)
454
455 if sf is None or fn in OPTIONS.require_verbatim:
456 # This file should be included verbatim
457 if fn in OPTIONS.prohibit_verbatim:
458 raise ExternalError("\"%s\" must be sent verbatim" % (fn,))
459 print "send", fn, "verbatim"
460 tf.AddToZip(output_zip)
461 verbatim_targets.append((fn, tf.size))
462 elif tf.sha1 != sf.sha1:
463 # File is different; consider sending as a patch
464 d = Difference(tf, sf)
465 print fn, tf.size, len(d), (float(len(d)) / tf.size)
466 if len(d) > tf.size * OPTIONS.patch_threshold:
467 # patch is almost as big as the file; don't bother patching
468 tf.AddToZip(output_zip)
469 verbatim_targets.append((fn, tf.size))
470 else:
471 output_zip.writestr("patch/" + fn + ".p", d)
472 patch_list.append((fn, tf, sf, tf.size))
473 largest_source_size = max(largest_source_size, sf.size)
474 else:
475 # Target file identical to source.
476 pass
477
478 total_verbatim_size = sum([i[1] for i in verbatim_targets])
479 total_patched_size = sum([i[3] for i in patch_list])
480
481 source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
482 target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
483
484 script.append(('assert file_contains("SYSTEM:build.prop", '
485 '"ro.build.fingerprint=%s") == "true" || '
486 'file_contains("SYSTEM:build.prop", '
487 '"ro.build.fingerprint=%s") == "true"') %
488 (source_fp, target_fp))
489
490 source_boot = common.BuildBootableImage(
491 os.path.join(OPTIONS.source_tmp, "BOOT"))
492 target_boot = common.BuildBootableImage(
493 os.path.join(OPTIONS.target_tmp, "BOOT"))
494 updating_boot = (source_boot != target_boot)
495
496 source_recovery = common.BuildBootableImage(
497 os.path.join(OPTIONS.source_tmp, "RECOVERY"))
498 target_recovery = common.BuildBootableImage(
499 os.path.join(OPTIONS.target_tmp, "RECOVERY"))
500 updating_recovery = (source_recovery != target_recovery)
501
502 source_radio = source_zip.read("RADIO/image")
503 target_radio = target_zip.read("RADIO/image")
504 updating_radio = (source_radio != target_radio)
505
506 # The last 0.1 is reserved for creating symlinks, fixing
507 # permissions, and writing the boot image (if necessary).
508 progress_bar_total = 1.0
509 if updating_boot:
510 progress_bar_total -= 0.1
511 if updating_radio:
512 progress_bar_total -= 0.3
513
514 AppendAssertions(script, target_zip)
515
516 pb_verify = progress_bar_total * 0.3 * \
517 (total_patched_size /
518 float(total_patched_size+total_verbatim_size))
519
520 for i, (fn, tf, sf, size) in enumerate(patch_list):
521 if i % 5 == 0:
522 next_sizes = sum([i[3] for i in patch_list[i:i+5]])
523 script.append("show_progress %f 1" %
524 (next_sizes * pb_verify / total_patched_size,))
525 script.append("run_program PACKAGE:applypatch -c /%s %s %s" %
526 (fn, tf.sha1, sf.sha1))
527
528 if patch_list:
529 script.append("run_program PACKAGE:applypatch -s %d" %
530 (largest_source_size,))
531 script.append("copy_dir PACKAGE:patch CACHE:../tmp/patchtmp")
532 IncludeBinary("applypatch", target_zip, output_zip)
533
534 script.append("\n# ---- start making changes here\n")
535
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700536 if OPTIONS.wipe_user_data:
537 script.append("format DATA:")
538
Doug Zongkereef39442009-04-02 12:14:19 -0700539 DeleteFiles(script, [SubstituteRoot(i[0]) for i in verbatim_targets])
540
541 if updating_boot:
542 script.append("format BOOT:")
543 output_zip.writestr("boot.img", target_boot)
544 print "boot image changed; including."
545 else:
546 print "boot image unchanged; skipping."
547
548 if updating_recovery:
549 output_zip.writestr("system/recovery.img", target_recovery)
550 print "recovery image changed; including."
551 else:
552 print "recovery image unchanged; skipping."
553
554 if updating_radio:
555 script.append("show_progress 0.3 10")
556 script.append("write_radio_image PACKAGE:radio.img")
557 output_zip.writestr("radio.img", target_radio)
558 print "radio image changed; including."
559 else:
560 print "radio image unchanged; skipping."
561
562 pb_apply = progress_bar_total * 0.7 * \
563 (total_patched_size /
564 float(total_patched_size+total_verbatim_size))
565 for i, (fn, tf, sf, size) in enumerate(patch_list):
566 if i % 5 == 0:
567 next_sizes = sum([i[3] for i in patch_list[i:i+5]])
568 script.append("show_progress %f 1" %
569 (next_sizes * pb_apply / total_patched_size,))
570 script.append(("run_program PACKAGE:applypatch "
Doug Zongkeref85ea62009-05-08 13:46:25 -0700571 "/%s - %s %d %s:/tmp/patchtmp/%s.p") %
Doug Zongkereef39442009-04-02 12:14:19 -0700572 (fn, tf.sha1, tf.size, sf.sha1, fn))
573
574 target_symlinks = CopySystemFiles(target_zip, None)
575
576 target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
577 temp_script = []
578 FixPermissions(temp_script)
579
580 # Note that this call will mess up the tree of Items, so make sure
581 # we're done with it.
582 source_symlinks = CopySystemFiles(source_zip, None)
583 source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
584
585 # Delete all the symlinks in source that aren't in target. This
586 # needs to happen before verbatim files are unpacked, in case a
587 # symlink in the source is replaced by a real file in the target.
588 to_delete = []
589 for dest, link in source_symlinks:
590 if link not in target_symlinks_d:
591 to_delete.append(link)
592 DeleteFiles(script, to_delete)
593
594 if verbatim_targets:
595 pb_verbatim = progress_bar_total * \
596 (total_verbatim_size /
597 float(total_patched_size+total_verbatim_size))
598 script.append("show_progress %f 5" % (pb_verbatim,))
599 script.append("copy_dir PACKAGE:system SYSTEM:")
600
601 # Create all the symlinks that don't already exist, or point to
602 # somewhere different than what we want. Delete each symlink before
603 # creating it, since the 'symlink' command won't overwrite.
604 to_create = []
605 for dest, link in target_symlinks:
606 if link in source_symlinks_d:
607 if dest != source_symlinks_d[link]:
608 to_create.append((dest, link))
609 else:
610 to_create.append((dest, link))
611 DeleteFiles(script, [i[1] for i in to_create])
612 script.extend(["symlink %s %s" % s for s in to_create])
613
614 # Now that the symlinks are created, we can set all the
615 # permissions.
616 script.extend(temp_script)
617
618 if updating_boot:
619 script.append("show_progress 0.1 5")
620 script.append("write_raw_image PACKAGE:boot.img BOOT:")
621
Doug Zongker1c390a22009-05-14 19:06:36 -0700622 if OPTIONS.extra_script is not None:
623 script.append(OPTIONS.extra_script)
624
Doug Zongkereef39442009-04-02 12:14:19 -0700625 AddScript(script, output_zip)
626
627
628def main(argv):
629
630 def option_handler(o, a):
631 if o in ("-b", "--board_config"):
632 common.LoadBoardConfig(a)
Doug Zongkereef39442009-04-02 12:14:19 -0700633 elif o in ("-k", "--package_key"):
634 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -0700635 elif o in ("-i", "--incremental_from"):
636 OPTIONS.incremental_source = a
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700637 elif o in ("-w", "--wipe_user_data"):
638 OPTIONS.wipe_user_data = True
Doug Zongker962069c2009-04-23 11:41:58 -0700639 elif o in ("-n", "--no_prereq"):
640 OPTIONS.omit_prereq = True
Doug Zongker1c390a22009-05-14 19:06:36 -0700641 elif o in ("-e", "--extra_script"):
642 OPTIONS.extra_script = a
Doug Zongkereef39442009-04-02 12:14:19 -0700643 else:
644 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700645 return True
Doug Zongkereef39442009-04-02 12:14:19 -0700646
647 args = common.ParseOptions(argv, __doc__,
Doug Zongker1c390a22009-05-14 19:06:36 -0700648 extra_opts="b:k:i:d:wne:",
Doug Zongkereef39442009-04-02 12:14:19 -0700649 extra_long_opts=["board_config=",
650 "package_key=",
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700651 "incremental_from=",
Doug Zongker962069c2009-04-23 11:41:58 -0700652 "wipe_user_data",
Doug Zongker1c390a22009-05-14 19:06:36 -0700653 "no_prereq",
654 "extra_script="],
Doug Zongkereef39442009-04-02 12:14:19 -0700655 extra_option_handler=option_handler)
656
657 if len(args) != 2:
658 common.Usage(__doc__)
659 sys.exit(1)
660
661 if not OPTIONS.max_image_size:
662 print
663 print " WARNING: No board config specified; will not check image"
664 print " sizes against limits. Use -b to make sure the generated"
665 print " images don't exceed partition sizes."
666 print
667
Doug Zongker1c390a22009-05-14 19:06:36 -0700668 if OPTIONS.extra_script is not None:
669 OPTIONS.extra_script = open(OPTIONS.extra_script).read()
670
Doug Zongkereef39442009-04-02 12:14:19 -0700671 print "unzipping target target-files..."
672 OPTIONS.input_tmp = common.UnzipTemp(args[0])
673 OPTIONS.target_tmp = OPTIONS.input_tmp
674 input_zip = zipfile.ZipFile(args[0], "r")
675 if OPTIONS.package_key:
676 temp_zip_file = tempfile.NamedTemporaryFile()
677 output_zip = zipfile.ZipFile(temp_zip_file, "w",
678 compression=zipfile.ZIP_DEFLATED)
679 else:
680 output_zip = zipfile.ZipFile(args[1], "w",
681 compression=zipfile.ZIP_DEFLATED)
682
683 if OPTIONS.incremental_source is None:
684 WriteFullOTAPackage(input_zip, output_zip)
685 else:
686 print "unzipping source target-files..."
687 OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
688 source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
689 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
690
691 output_zip.close()
692 if OPTIONS.package_key:
693 SignOutput(temp_zip_file.name, args[1])
694 temp_zip_file.close()
695
696 common.Cleanup()
697
698 print "done."
699
700
701if __name__ == '__main__':
702 try:
703 main(sys.argv[1:])
704 except common.ExternalError, e:
705 print
706 print " ERROR: %s" % (e,)
707 print
708 sys.exit(1)