blob: 2a811330e562e8fca0f5a09e78731f20e3b3a0c0 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Doug Zongkerea5d7a92010-09-12 15:26:16 -070015import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070016import errno
Doug Zongkereef39442009-04-02 12:14:19 -070017import getopt
18import getpass
Doug Zongker05d3dea2009-06-22 11:32:31 -070019import imp
Doug Zongkereef39442009-04-02 12:14:19 -070020import os
21import re
Doug Zongkerea5d7a92010-09-12 15:26:16 -070022import sha
Doug Zongkereef39442009-04-02 12:14:19 -070023import shutil
24import subprocess
25import sys
26import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070027import threading
28import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070029import zipfile
Doug Zongkereef39442009-04-02 12:14:19 -070030
31# missing in Python 2.4 and before
32if not hasattr(os, "SEEK_SET"):
33 os.SEEK_SET = 0
34
35class Options(object): pass
36OPTIONS = Options()
Doug Zongker602a84e2009-06-18 08:35:12 -070037OPTIONS.search_path = "out/host/linux-x86"
Doug Zongkereef39442009-04-02 12:14:19 -070038OPTIONS.max_image_size = {}
39OPTIONS.verbose = False
40OPTIONS.tempfiles = []
Doug Zongker05d3dea2009-06-22 11:32:31 -070041OPTIONS.device_specific = None
Doug Zongker8bec09e2009-11-30 15:37:14 -080042OPTIONS.extras = {}
Ying Wangd421f572010-08-25 20:39:41 -070043OPTIONS.mkyaffs2_extra_flags = None
Doug Zongkereef39442009-04-02 12:14:19 -070044
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080045
46# Values for "certificate" in apkcerts that mean special things.
47SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
48
49
Doug Zongkereef39442009-04-02 12:14:19 -070050class ExternalError(RuntimeError): pass
51
52
53def Run(args, **kwargs):
54 """Create and return a subprocess.Popen object, printing the command
55 line on the terminal if -v was specified."""
56 if OPTIONS.verbose:
57 print " running: ", " ".join(args)
58 return subprocess.Popen(args, **kwargs)
59
60
Doug Zongkerc19a8d52010-07-01 15:30:11 -070061def LoadInfoDict():
62 """Read and parse the META/misc_info.txt key/value pairs from the
63 input target files and return a dict."""
64
65 d = {}
66 try:
67 for line in open(os.path.join(OPTIONS.input_tmp, "META", "misc_info.txt")):
68 line = line.strip()
69 if not line or line.startswith("#"): continue
70 k, v = line.split("=", 1)
71 d[k] = v
72 except IOError, e:
73 if e.errno == errno.ENOENT:
74 # ok if misc_info.txt file doesn't exist
75 pass
76 else:
77 raise
78
79 if "fs_type" not in d: info["fs_type"] = "yaffs2"
80 if "partition_type" not in d: info["partition_type"] = "MTD"
81
82 return d
83
84
85def LoadMaxSizes(info):
Doug Zongkerfdd8e692009-08-03 17:27:48 -070086 """Load the maximum allowable images sizes from the input
Doug Zongkerc19a8d52010-07-01 15:30:11 -070087 target_files. Uses the imagesizes.txt file if it's available
88 (pre-honeycomb target_files), or the more general info dict (which
89 must be passed in) if not."""
Doug Zongkereef39442009-04-02 12:14:19 -070090 OPTIONS.max_image_size = {}
Doug Zongkerfdd8e692009-08-03 17:27:48 -070091 try:
92 for line in open(os.path.join(OPTIONS.input_tmp, "META", "imagesizes.txt")):
Doug Zongker1aca9622009-08-04 15:09:27 -070093 pieces = line.split()
94 if len(pieces) != 2: continue
95 image = pieces[0]
96 size = int(pieces[1])
Doug Zongkerfdd8e692009-08-03 17:27:48 -070097 OPTIONS.max_image_size[image + ".img"] = size
98 except IOError, e:
99 if e.errno == errno.ENOENT:
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700100 def copy(x, y):
101 if x in info: OPTIONS.max_image_size[x+".img"] = int(info[x+y])
102 copy("blocksize", "")
103 copy("boot", "_size")
104 copy("recovery", "_size")
105 copy("system", "_size")
106 copy("userdata", "_size")
107 else:
108 raise
Doug Zongkereef39442009-04-02 12:14:19 -0700109
110
Ying Wangd421f572010-08-25 20:39:41 -0700111def LoadMkyaffs2ExtraFlags():
112 """Load mkyaffs2 extra flags."""
113 try:
114 fn = os.path.join(OPTIONS.input_tmp, "META", "mkyaffs2-extra-flags.txt");
115 if os.access(fn, os.F_OK):
116 OPTIONS.mkyaffs2_extra_flags = open(fn).read().rstrip("\n")
117 except IOError, e:
118 if e.errno == errno.ENOENT:
119 pass
120
121
Doug Zongkereef39442009-04-02 12:14:19 -0700122def BuildAndAddBootableImage(sourcedir, targetname, output_zip):
123 """Take a kernel, cmdline, and ramdisk directory from the input (in
124 'sourcedir'), and turn them into a boot image. Put the boot image
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700125 into the output zip file under the name 'targetname'. Returns
126 targetname on success or None on failure (if sourcedir does not
127 appear to contain files for the requested image)."""
Doug Zongkereef39442009-04-02 12:14:19 -0700128
129 print "creating %s..." % (targetname,)
130
131 img = BuildBootableImage(sourcedir)
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700132 if img is None:
133 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700134
135 CheckSize(img, targetname)
Doug Zongker048e7ca2009-06-15 14:31:53 -0700136 ZipWriteStr(output_zip, targetname, img)
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700137 return targetname
Doug Zongkereef39442009-04-02 12:14:19 -0700138
139def BuildBootableImage(sourcedir):
140 """Take a kernel, cmdline, and ramdisk directory from the input (in
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700141 'sourcedir'), and turn them into a boot image. Return the image
142 data, or None if sourcedir does not appear to contains files for
143 building the requested image."""
144
145 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
146 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
147 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700148
149 ramdisk_img = tempfile.NamedTemporaryFile()
150 img = tempfile.NamedTemporaryFile()
151
152 p1 = Run(["mkbootfs", os.path.join(sourcedir, "RAMDISK")],
153 stdout=subprocess.PIPE)
Doug Zongker32da27a2009-05-29 09:35:56 -0700154 p2 = Run(["minigzip"],
155 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Doug Zongkereef39442009-04-02 12:14:19 -0700156
157 p2.wait()
158 p1.wait()
159 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
Doug Zongker32da27a2009-05-29 09:35:56 -0700160 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
Doug Zongkereef39442009-04-02 12:14:19 -0700161
Doug Zongker38a649f2009-06-17 09:07:09 -0700162 cmd = ["mkbootimg", "--kernel", os.path.join(sourcedir, "kernel")]
163
Doug Zongker171f1cd2009-06-15 22:36:37 -0700164 fn = os.path.join(sourcedir, "cmdline")
165 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700166 cmd.append("--cmdline")
167 cmd.append(open(fn).read().rstrip("\n"))
168
169 fn = os.path.join(sourcedir, "base")
170 if os.access(fn, os.F_OK):
171 cmd.append("--base")
172 cmd.append(open(fn).read().rstrip("\n"))
173
Ying Wang4de6b5b2010-08-25 14:29:34 -0700174 fn = os.path.join(sourcedir, "pagesize")
175 if os.access(fn, os.F_OK):
176 cmd.append("--pagesize")
177 cmd.append(open(fn).read().rstrip("\n"))
178
Doug Zongker38a649f2009-06-17 09:07:09 -0700179 cmd.extend(["--ramdisk", ramdisk_img.name,
180 "--output", img.name])
181
182 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700183 p.communicate()
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700184 assert p.returncode == 0, "mkbootimg of %s image failed" % (
185 os.path.basename(sourcedir),)
Doug Zongkereef39442009-04-02 12:14:19 -0700186
187 img.seek(os.SEEK_SET, 0)
188 data = img.read()
189
190 ramdisk_img.close()
191 img.close()
192
193 return data
194
195
196def AddRecovery(output_zip):
197 BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "RECOVERY"),
198 "recovery.img", output_zip)
199
200def AddBoot(output_zip):
201 BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "BOOT"),
202 "boot.img", output_zip)
203
Doug Zongker75f17362009-12-08 13:46:44 -0800204def UnzipTemp(filename, pattern=None):
Doug Zongkereef39442009-04-02 12:14:19 -0700205 """Unzip the given archive into a temporary directory and return the name."""
206
207 tmp = tempfile.mkdtemp(prefix="targetfiles-")
208 OPTIONS.tempfiles.append(tmp)
Doug Zongker75f17362009-12-08 13:46:44 -0800209 cmd = ["unzip", "-o", "-q", filename, "-d", tmp]
210 if pattern is not None:
211 cmd.append(pattern)
212 p = Run(cmd, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700213 p.communicate()
214 if p.returncode != 0:
215 raise ExternalError("failed to unzip input target-files \"%s\"" %
216 (filename,))
217 return tmp
218
219
220def GetKeyPasswords(keylist):
221 """Given a list of keys, prompt the user to enter passwords for
222 those which require them. Return a {key: password} dict. password
223 will be None if the key has no password."""
224
Doug Zongker8ce7c252009-05-22 13:34:54 -0700225 no_passwords = []
226 need_passwords = []
Doug Zongkereef39442009-04-02 12:14:19 -0700227 devnull = open("/dev/null", "w+b")
228 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800229 # We don't need a password for things that aren't really keys.
230 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700231 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700232 continue
233
Doug Zongker602a84e2009-06-18 08:35:12 -0700234 p = Run(["openssl", "pkcs8", "-in", k+".pk8",
235 "-inform", "DER", "-nocrypt"],
236 stdin=devnull.fileno(),
237 stdout=devnull.fileno(),
238 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700239 p.communicate()
240 if p.returncode == 0:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700241 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700242 else:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700243 need_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700244 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700245
246 key_passwords = PasswordManager().GetPasswords(need_passwords)
247 key_passwords.update(dict.fromkeys(no_passwords, None))
Doug Zongkereef39442009-04-02 12:14:19 -0700248 return key_passwords
249
250
Doug Zongker951495f2009-08-14 12:44:19 -0700251def SignFile(input_name, output_name, key, password, align=None,
252 whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700253 """Sign the input_name zip/jar/apk, producing output_name. Use the
254 given key and password (the latter may be None if the key does not
255 have a password.
256
257 If align is an integer > 1, zipalign is run to align stored files in
258 the output zip on 'align'-byte boundaries.
Doug Zongker951495f2009-08-14 12:44:19 -0700259
260 If whole_file is true, use the "-w" option to SignApk to embed a
261 signature that covers the whole file in the archive comment of the
262 zip file.
Doug Zongkereef39442009-04-02 12:14:19 -0700263 """
Doug Zongker951495f2009-08-14 12:44:19 -0700264
Doug Zongkereef39442009-04-02 12:14:19 -0700265 if align == 0 or align == 1:
266 align = None
267
268 if align:
269 temp = tempfile.NamedTemporaryFile()
270 sign_name = temp.name
271 else:
272 sign_name = output_name
273
Doug Zongker09cf5602009-08-14 15:25:06 -0700274 cmd = ["java", "-Xmx512m", "-jar",
Doug Zongker951495f2009-08-14 12:44:19 -0700275 os.path.join(OPTIONS.search_path, "framework", "signapk.jar")]
276 if whole_file:
277 cmd.append("-w")
278 cmd.extend([key + ".x509.pem", key + ".pk8",
279 input_name, sign_name])
280
281 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700282 if password is not None:
283 password += "\n"
284 p.communicate(password)
285 if p.returncode != 0:
286 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
287
288 if align:
Doug Zongker602a84e2009-06-18 08:35:12 -0700289 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
Doug Zongkereef39442009-04-02 12:14:19 -0700290 p.communicate()
291 if p.returncode != 0:
292 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
293 temp.close()
294
295
296def CheckSize(data, target):
297 """Check the data string passed against the max size limit, if
298 any, for the given target. Raise exception if the data is too big.
299 Print a warning if the data is nearing the maximum size."""
300 limit = OPTIONS.max_image_size.get(target, None)
301 if limit is None: return
302
303 size = len(data)
304 pct = float(size) * 100.0 / limit
305 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
306 if pct >= 99.0:
307 raise ExternalError(msg)
308 elif pct >= 95.0:
309 print
310 print " WARNING: ", msg
311 print
312 elif OPTIONS.verbose:
313 print " ", msg
314
315
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800316def ReadApkCerts(tf_zip):
317 """Given a target_files ZipFile, parse the META/apkcerts.txt file
318 and return a {package: cert} dict."""
319 certmap = {}
320 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
321 line = line.strip()
322 if not line: continue
323 m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+'
324 r'private_key="(.*)"$', line)
325 if m:
326 name, cert, privkey = m.groups()
327 if cert in SPECIAL_CERT_STRINGS and not privkey:
328 certmap[name] = cert
329 elif (cert.endswith(".x509.pem") and
330 privkey.endswith(".pk8") and
331 cert[:-9] == privkey[:-4]):
332 certmap[name] = cert[:-9]
333 else:
334 raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
335 return certmap
336
337
Doug Zongkereef39442009-04-02 12:14:19 -0700338COMMON_DOCSTRING = """
339 -p (--path) <dir>
Doug Zongker602a84e2009-06-18 08:35:12 -0700340 Prepend <dir>/bin to the list of places to search for binaries
341 run by this script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -0700342
Doug Zongker05d3dea2009-06-22 11:32:31 -0700343 -s (--device_specific) <file>
344 Path to the python module containing device-specific
345 releasetools code.
346
Doug Zongker8bec09e2009-11-30 15:37:14 -0800347 -x (--extra) <key=value>
348 Add a key/value pair to the 'extras' dict, which device-specific
349 extension code may look at.
350
Doug Zongkereef39442009-04-02 12:14:19 -0700351 -v (--verbose)
352 Show command lines being executed.
353
354 -h (--help)
355 Display this usage message and exit.
356"""
357
358def Usage(docstring):
359 print docstring.rstrip("\n")
360 print COMMON_DOCSTRING
361
362
363def ParseOptions(argv,
364 docstring,
365 extra_opts="", extra_long_opts=(),
366 extra_option_handler=None):
367 """Parse the options in argv and return any arguments that aren't
368 flags. docstring is the calling module's docstring, to be displayed
369 for errors and -h. extra_opts and extra_long_opts are for flags
370 defined by the caller, which are processed by passing them to
371 extra_option_handler."""
372
373 try:
374 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -0800375 argv, "hvp:s:x:" + extra_opts,
376 ["help", "verbose", "path=", "device_specific=", "extra="] +
Doug Zongker05d3dea2009-06-22 11:32:31 -0700377 list(extra_long_opts))
Doug Zongkereef39442009-04-02 12:14:19 -0700378 except getopt.GetoptError, err:
379 Usage(docstring)
380 print "**", str(err), "**"
381 sys.exit(2)
382
383 path_specified = False
384
385 for o, a in opts:
386 if o in ("-h", "--help"):
387 Usage(docstring)
388 sys.exit()
389 elif o in ("-v", "--verbose"):
390 OPTIONS.verbose = True
391 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -0700392 OPTIONS.search_path = a
Doug Zongker05d3dea2009-06-22 11:32:31 -0700393 elif o in ("-s", "--device_specific"):
394 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -0800395 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -0800396 key, value = a.split("=", 1)
397 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -0700398 else:
399 if extra_option_handler is None or not extra_option_handler(o, a):
400 assert False, "unknown option \"%s\"" % (o,)
401
Doug Zongker602a84e2009-06-18 08:35:12 -0700402 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
403 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -0700404
405 return args
406
407
408def Cleanup():
409 for i in OPTIONS.tempfiles:
410 if os.path.isdir(i):
411 shutil.rmtree(i)
412 else:
413 os.remove(i)
Doug Zongker8ce7c252009-05-22 13:34:54 -0700414
415
416class PasswordManager(object):
417 def __init__(self):
418 self.editor = os.getenv("EDITOR", None)
419 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
420
421 def GetPasswords(self, items):
422 """Get passwords corresponding to each string in 'items',
423 returning a dict. (The dict may have keys in addition to the
424 values in 'items'.)
425
426 Uses the passwords in $ANDROID_PW_FILE if available, letting the
427 user edit that file to add more needed passwords. If no editor is
428 available, or $ANDROID_PW_FILE isn't define, prompts the user
429 interactively in the ordinary way.
430 """
431
432 current = self.ReadFile()
433
434 first = True
435 while True:
436 missing = []
437 for i in items:
438 if i not in current or not current[i]:
439 missing.append(i)
440 # Are all the passwords already in the file?
441 if not missing: return current
442
443 for i in missing:
444 current[i] = ""
445
446 if not first:
447 print "key file %s still missing some passwords." % (self.pwfile,)
448 answer = raw_input("try to edit again? [y]> ").strip()
449 if answer and answer[0] not in 'yY':
450 raise RuntimeError("key passwords unavailable")
451 first = False
452
453 current = self.UpdateAndReadFile(current)
454
455 def PromptResult(self, current):
456 """Prompt the user to enter a value (password) for each key in
457 'current' whose value is fales. Returns a new dict with all the
458 values.
459 """
460 result = {}
461 for k, v in sorted(current.iteritems()):
462 if v:
463 result[k] = v
464 else:
465 while True:
466 result[k] = getpass.getpass("Enter password for %s key> "
467 % (k,)).strip()
468 if result[k]: break
469 return result
470
471 def UpdateAndReadFile(self, current):
472 if not self.editor or not self.pwfile:
473 return self.PromptResult(current)
474
475 f = open(self.pwfile, "w")
476 os.chmod(self.pwfile, 0600)
477 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
478 f.write("# (Additional spaces are harmless.)\n\n")
479
480 first_line = None
481 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
482 sorted.sort()
483 for i, (_, k, v) in enumerate(sorted):
484 f.write("[[[ %s ]]] %s\n" % (v, k))
485 if not v and first_line is None:
486 # position cursor on first line with no password.
487 first_line = i + 4
488 f.close()
489
490 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
491 _, _ = p.communicate()
492
493 return self.ReadFile()
494
495 def ReadFile(self):
496 result = {}
497 if self.pwfile is None: return result
498 try:
499 f = open(self.pwfile, "r")
500 for line in f:
501 line = line.strip()
502 if not line or line[0] == '#': continue
503 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
504 if not m:
505 print "failed to parse password file: ", line
506 else:
507 result[m.group(2)] = m.group(1)
508 f.close()
509 except IOError, e:
510 if e.errno != errno.ENOENT:
511 print "error reading password file: ", str(e)
512 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -0700513
514
515def ZipWriteStr(zip, filename, data, perms=0644):
516 # use a fixed timestamp so the output is repeatable.
517 zinfo = zipfile.ZipInfo(filename=filename,
518 date_time=(2009, 1, 1, 0, 0, 0))
519 zinfo.compress_type = zip.compression
520 zinfo.external_attr = perms << 16
521 zip.writestr(zinfo, data)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700522
523
524class DeviceSpecificParams(object):
525 module = None
526 def __init__(self, **kwargs):
527 """Keyword arguments to the constructor become attributes of this
528 object, which is passed to all functions in the device-specific
529 module."""
530 for k, v in kwargs.iteritems():
531 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -0800532 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -0700533
534 if self.module is None:
535 path = OPTIONS.device_specific
Doug Zongkerc18736b2009-09-30 09:20:32 -0700536 if not path: return
Doug Zongker8e2f2b92009-06-24 14:34:57 -0700537 try:
538 if os.path.isdir(path):
539 info = imp.find_module("releasetools", [path])
540 else:
541 d, f = os.path.split(path)
542 b, x = os.path.splitext(f)
543 if x == ".py":
544 f = b
545 info = imp.find_module(f, [d])
546 self.module = imp.load_module("device_specific", *info)
547 except ImportError:
548 print "unable to load device-specific module; assuming none"
Doug Zongker05d3dea2009-06-22 11:32:31 -0700549
550 def _DoCall(self, function_name, *args, **kwargs):
551 """Call the named function in the device-specific module, passing
552 the given args and kwargs. The first argument to the call will be
553 the DeviceSpecific object itself. If there is no module, or the
554 module does not define the function, return the value of the
555 'default' kwarg (which itself defaults to None)."""
556 if self.module is None or not hasattr(self.module, function_name):
557 return kwargs.get("default", None)
558 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
559
560 def FullOTA_Assertions(self):
561 """Called after emitting the block of assertions at the top of a
562 full OTA package. Implementations can add whatever additional
563 assertions they like."""
564 return self._DoCall("FullOTA_Assertions")
565
566 def FullOTA_InstallEnd(self):
567 """Called at the end of full OTA installation; typically this is
568 used to install the image for the device's baseband processor."""
569 return self._DoCall("FullOTA_InstallEnd")
570
571 def IncrementalOTA_Assertions(self):
572 """Called after emitting the block of assertions at the top of an
573 incremental OTA package. Implementations can add whatever
574 additional assertions they like."""
575 return self._DoCall("IncrementalOTA_Assertions")
576
577 def IncrementalOTA_VerifyEnd(self):
578 """Called at the end of the verification phase of incremental OTA
579 installation; additional checks can be placed here to abort the
580 script before any changes are made."""
581 return self._DoCall("IncrementalOTA_VerifyEnd")
582
583 def IncrementalOTA_InstallEnd(self):
584 """Called at the end of incremental OTA installation; typically
585 this is used to install the image for the device's baseband
586 processor."""
587 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -0700588
589class File(object):
590 def __init__(self, name, data):
591 self.name = name
592 self.data = data
593 self.size = len(data)
594 self.sha1 = sha.sha(data).hexdigest()
595
596 def WriteToTemp(self):
597 t = tempfile.NamedTemporaryFile()
598 t.write(self.data)
599 t.flush()
600 return t
601
602 def AddToZip(self, z):
603 ZipWriteStr(z, self.name, self.data)
604
605DIFF_PROGRAM_BY_EXT = {
606 ".gz" : "imgdiff",
607 ".zip" : ["imgdiff", "-z"],
608 ".jar" : ["imgdiff", "-z"],
609 ".apk" : ["imgdiff", "-z"],
610 ".img" : "imgdiff",
611 }
612
613class Difference(object):
614 def __init__(self, tf, sf):
615 self.tf = tf
616 self.sf = sf
617 self.patch = None
618
619 def ComputePatch(self):
620 """Compute the patch (as a string of data) needed to turn sf into
621 tf. Returns the same tuple as GetPatch()."""
622
623 tf = self.tf
624 sf = self.sf
625
626 ext = os.path.splitext(tf.name)[1]
627 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
628
629 ttemp = tf.WriteToTemp()
630 stemp = sf.WriteToTemp()
631
632 ext = os.path.splitext(tf.name)[1]
633
634 try:
635 ptemp = tempfile.NamedTemporaryFile()
636 if isinstance(diff_program, list):
637 cmd = copy.copy(diff_program)
638 else:
639 cmd = [diff_program]
640 cmd.append(stemp.name)
641 cmd.append(ttemp.name)
642 cmd.append(ptemp.name)
643 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
644 _, err = p.communicate()
645 if err or p.returncode != 0:
646 print "WARNING: failure running %s:\n%s\n" % (diff_program, err)
647 return None
648 diff = ptemp.read()
649 finally:
650 ptemp.close()
651 stemp.close()
652 ttemp.close()
653
654 self.patch = diff
655 return self.tf, self.sf, self.patch
656
657
658 def GetPatch(self):
659 """Return a tuple (target_file, source_file, patch_data).
660 patch_data may be None if ComputePatch hasn't been called, or if
661 computing the patch failed."""
662 return self.tf, self.sf, self.patch
663
664
665def ComputeDifferences(diffs):
666 """Call ComputePatch on all the Difference objects in 'diffs'."""
667 print len(diffs), "diffs to compute"
668
669 # Do the largest files first, to try and reduce the long-pole effect.
670 by_size = [(i.tf.size, i) for i in diffs]
671 by_size.sort(reverse=True)
672 by_size = [i[1] for i in by_size]
673
674 lock = threading.Lock()
675 diff_iter = iter(by_size) # accessed under lock
676
677 def worker():
678 try:
679 lock.acquire()
680 for d in diff_iter:
681 lock.release()
682 start = time.time()
683 d.ComputePatch()
684 dur = time.time() - start
685 lock.acquire()
686
687 tf, sf, patch = d.GetPatch()
688 if sf.name == tf.name:
689 name = tf.name
690 else:
691 name = "%s (%s)" % (tf.name, sf.name)
692 if patch is None:
693 print "patching failed! %s" % (name,)
694 else:
695 print "%8.2f sec %8d / %8d bytes (%6.2f%%) %s" % (
696 dur, len(patch), tf.size, 100.0 * len(patch) / tf.size, name)
697 lock.release()
698 except Exception, e:
699 print e
700 raise
701
702 # start worker threads; wait for them all to finish.
703 threads = [threading.Thread(target=worker)
704 for i in range(OPTIONS.worker_threads)]
705 for th in threads:
706 th.start()
707 while threads:
708 threads.pop().join()