1 # Copyright (C) 2008 The Android Open Source Project
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
27 # missing in Python 2.4 and before
28 if not hasattr(os, "SEEK_SET"):
31 class Options(object): pass
33 OPTIONS.search_path = "out/host/linux-x86"
34 OPTIONS.max_image_size = {}
35 OPTIONS.verbose = False
36 OPTIONS.tempfiles = []
37 OPTIONS.device_specific = None
39 class ExternalError(RuntimeError): pass
42 def Run(args, **kwargs):
43 """Create and return a subprocess.Popen object, printing the command
44 line on the terminal if -v was specified."""
46 print " running: ", " ".join(args)
47 return subprocess.Popen(args, **kwargs)
51 """Load the maximum allowable images sizes from the input
53 OPTIONS.max_image_size = {}
55 for line in open(os.path.join(OPTIONS.input_tmp, "META", "imagesizes.txt")):
57 if len(pieces) != 2: continue
60 OPTIONS.max_image_size[image + ".img"] = size
62 if e.errno == errno.ENOENT:
66 def BuildAndAddBootableImage(sourcedir, targetname, output_zip):
67 """Take a kernel, cmdline, and ramdisk directory from the input (in
68 'sourcedir'), and turn them into a boot image. Put the boot image
69 into the output zip file under the name 'targetname'. Returns
70 targetname on success or None on failure (if sourcedir does not
71 appear to contain files for the requested image)."""
73 print "creating %s..." % (targetname,)
75 img = BuildBootableImage(sourcedir)
79 CheckSize(img, targetname)
80 ZipWriteStr(output_zip, targetname, img)
83 def BuildBootableImage(sourcedir):
84 """Take a kernel, cmdline, and ramdisk directory from the input (in
85 'sourcedir'), and turn them into a boot image. Return the image
86 data, or None if sourcedir does not appear to contains files for
87 building the requested image."""
89 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
90 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
93 ramdisk_img = tempfile.NamedTemporaryFile()
94 img = tempfile.NamedTemporaryFile()
96 p1 = Run(["mkbootfs", os.path.join(sourcedir, "RAMDISK")],
97 stdout=subprocess.PIPE)
98 p2 = Run(["minigzip"],
99 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
103 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
104 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
106 cmd = ["mkbootimg", "--kernel", os.path.join(sourcedir, "kernel")]
108 fn = os.path.join(sourcedir, "cmdline")
109 if os.access(fn, os.F_OK):
110 cmd.append("--cmdline")
111 cmd.append(open(fn).read().rstrip("\n"))
113 fn = os.path.join(sourcedir, "base")
114 if os.access(fn, os.F_OK):
116 cmd.append(open(fn).read().rstrip("\n"))
118 cmd.extend(["--ramdisk", ramdisk_img.name,
119 "--output", img.name])
121 p = Run(cmd, stdout=subprocess.PIPE)
123 assert p.returncode == 0, "mkbootimg of %s image failed" % (
124 os.path.basename(sourcedir),)
126 img.seek(os.SEEK_SET, 0)
135 def AddRecovery(output_zip):
136 BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "RECOVERY"),
137 "recovery.img", output_zip)
139 def AddBoot(output_zip):
140 BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "BOOT"),
141 "boot.img", output_zip)
143 def UnzipTemp(filename):
144 """Unzip the given archive into a temporary directory and return the name."""
146 tmp = tempfile.mkdtemp(prefix="targetfiles-")
147 OPTIONS.tempfiles.append(tmp)
148 p = Run(["unzip", "-o", "-q", filename, "-d", tmp], stdout=subprocess.PIPE)
150 if p.returncode != 0:
151 raise ExternalError("failed to unzip input target-files \"%s\"" %
156 def GetKeyPasswords(keylist):
157 """Given a list of keys, prompt the user to enter passwords for
158 those which require them. Return a {key: password} dict. password
159 will be None if the key has no password."""
163 devnull = open("/dev/null", "w+b")
164 for k in sorted(keylist):
165 # An empty-string key is used to mean don't re-sign this package.
166 # Obviously we don't need a password for this non-key.
168 no_passwords.append(k)
171 p = Run(["openssl", "pkcs8", "-in", k+".pk8",
172 "-inform", "DER", "-nocrypt"],
173 stdin=devnull.fileno(),
174 stdout=devnull.fileno(),
175 stderr=subprocess.STDOUT)
177 if p.returncode == 0:
178 no_passwords.append(k)
180 need_passwords.append(k)
183 key_passwords = PasswordManager().GetPasswords(need_passwords)
184 key_passwords.update(dict.fromkeys(no_passwords, None))
188 def SignFile(input_name, output_name, key, password, align=None,
190 """Sign the input_name zip/jar/apk, producing output_name. Use the
191 given key and password (the latter may be None if the key does not
194 If align is an integer > 1, zipalign is run to align stored files in
195 the output zip on 'align'-byte boundaries.
197 If whole_file is true, use the "-w" option to SignApk to embed a
198 signature that covers the whole file in the archive comment of the
202 if align == 0 or align == 1:
206 temp = tempfile.NamedTemporaryFile()
207 sign_name = temp.name
209 sign_name = output_name
211 cmd = ["java", "-Xmx512m", "-jar",
212 os.path.join(OPTIONS.search_path, "framework", "signapk.jar")]
215 cmd.extend([key + ".x509.pem", key + ".pk8",
216 input_name, sign_name])
218 p = Run(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
219 if password is not None:
221 p.communicate(password)
222 if p.returncode != 0:
223 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
226 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
228 if p.returncode != 0:
229 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
233 def CheckSize(data, target):
234 """Check the data string passed against the max size limit, if
235 any, for the given target. Raise exception if the data is too big.
236 Print a warning if the data is nearing the maximum size."""
237 limit = OPTIONS.max_image_size.get(target, None)
238 if limit is None: return
241 pct = float(size) * 100.0 / limit
242 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
244 raise ExternalError(msg)
247 print " WARNING: ", msg
249 elif OPTIONS.verbose:
253 COMMON_DOCSTRING = """
255 Prepend <dir>/bin to the list of places to search for binaries
256 run by this script, and expect to find jars in <dir>/framework.
258 -s (--device_specific) <file>
259 Path to the python module containing device-specific
263 Show command lines being executed.
266 Display this usage message and exit.
269 def Usage(docstring):
270 print docstring.rstrip("\n")
271 print COMMON_DOCSTRING
274 def ParseOptions(argv,
276 extra_opts="", extra_long_opts=(),
277 extra_option_handler=None):
278 """Parse the options in argv and return any arguments that aren't
279 flags. docstring is the calling module's docstring, to be displayed
280 for errors and -h. extra_opts and extra_long_opts are for flags
281 defined by the caller, which are processed by passing them to
282 extra_option_handler."""
285 opts, args = getopt.getopt(
286 argv, "hvp:s:" + extra_opts,
287 ["help", "verbose", "path=", "device_specific="] +
288 list(extra_long_opts))
289 except getopt.GetoptError, err:
291 print "**", str(err), "**"
294 path_specified = False
297 if o in ("-h", "--help"):
300 elif o in ("-v", "--verbose"):
301 OPTIONS.verbose = True
302 elif o in ("-p", "--path"):
303 OPTIONS.search_path = a
304 elif o in ("-s", "--device_specific"):
305 OPTIONS.device_specific = a
307 if extra_option_handler is None or not extra_option_handler(o, a):
308 assert False, "unknown option \"%s\"" % (o,)
310 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
311 os.pathsep + os.environ["PATH"])
317 for i in OPTIONS.tempfiles:
324 class PasswordManager(object):
326 self.editor = os.getenv("EDITOR", None)
327 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
329 def GetPasswords(self, items):
330 """Get passwords corresponding to each string in 'items',
331 returning a dict. (The dict may have keys in addition to the
334 Uses the passwords in $ANDROID_PW_FILE if available, letting the
335 user edit that file to add more needed passwords. If no editor is
336 available, or $ANDROID_PW_FILE isn't define, prompts the user
337 interactively in the ordinary way.
340 current = self.ReadFile()
346 if i not in current or not current[i]:
348 # Are all the passwords already in the file?
349 if not missing: return current
355 print "key file %s still missing some passwords." % (self.pwfile,)
356 answer = raw_input("try to edit again? [y]> ").strip()
357 if answer and answer[0] not in 'yY':
358 raise RuntimeError("key passwords unavailable")
361 current = self.UpdateAndReadFile(current)
363 def PromptResult(self, current):
364 """Prompt the user to enter a value (password) for each key in
365 'current' whose value is fales. Returns a new dict with all the
369 for k, v in sorted(current.iteritems()):
374 result[k] = getpass.getpass("Enter password for %s key> "
379 def UpdateAndReadFile(self, current):
380 if not self.editor or not self.pwfile:
381 return self.PromptResult(current)
383 f = open(self.pwfile, "w")
384 os.chmod(self.pwfile, 0600)
385 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
386 f.write("# (Additional spaces are harmless.)\n\n")
389 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
391 for i, (_, k, v) in enumerate(sorted):
392 f.write("[[[ %s ]]] %s\n" % (v, k))
393 if not v and first_line is None:
394 # position cursor on first line with no password.
398 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
399 _, _ = p.communicate()
401 return self.ReadFile()
405 if self.pwfile is None: return result
407 f = open(self.pwfile, "r")
410 if not line or line[0] == '#': continue
411 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
413 print "failed to parse password file: ", line
415 result[m.group(2)] = m.group(1)
418 if e.errno != errno.ENOENT:
419 print "error reading password file: ", str(e)
423 def ZipWriteStr(zip, filename, data, perms=0644):
424 # use a fixed timestamp so the output is repeatable.
425 zinfo = zipfile.ZipInfo(filename=filename,
426 date_time=(2009, 1, 1, 0, 0, 0))
427 zinfo.compress_type = zip.compression
428 zinfo.external_attr = perms << 16
429 zip.writestr(zinfo, data)
432 class DeviceSpecificParams(object):
434 def __init__(self, **kwargs):
435 """Keyword arguments to the constructor become attributes of this
436 object, which is passed to all functions in the device-specific
438 for k, v in kwargs.iteritems():
441 if self.module is None:
442 path = OPTIONS.device_specific
445 if os.path.isdir(path):
446 info = imp.find_module("releasetools", [path])
448 d, f = os.path.split(path)
449 b, x = os.path.splitext(f)
452 info = imp.find_module(f, [d])
453 self.module = imp.load_module("device_specific", *info)
455 print "unable to load device-specific module; assuming none"
457 def _DoCall(self, function_name, *args, **kwargs):
458 """Call the named function in the device-specific module, passing
459 the given args and kwargs. The first argument to the call will be
460 the DeviceSpecific object itself. If there is no module, or the
461 module does not define the function, return the value of the
462 'default' kwarg (which itself defaults to None)."""
463 if self.module is None or not hasattr(self.module, function_name):
464 return kwargs.get("default", None)
465 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
467 def FullOTA_Assertions(self):
468 """Called after emitting the block of assertions at the top of a
469 full OTA package. Implementations can add whatever additional
470 assertions they like."""
471 return self._DoCall("FullOTA_Assertions")
473 def FullOTA_InstallEnd(self):
474 """Called at the end of full OTA installation; typically this is
475 used to install the image for the device's baseband processor."""
476 return self._DoCall("FullOTA_InstallEnd")
478 def IncrementalOTA_Assertions(self):
479 """Called after emitting the block of assertions at the top of an
480 incremental OTA package. Implementations can add whatever
481 additional assertions they like."""
482 return self._DoCall("IncrementalOTA_Assertions")
484 def IncrementalOTA_VerifyEnd(self):
485 """Called at the end of the verification phase of incremental OTA
486 installation; additional checks can be placed here to abort the
487 script before any changes are made."""
488 return self._DoCall("IncrementalOTA_VerifyEnd")
490 def IncrementalOTA_InstallEnd(self):
491 """Called at the end of incremental OTA installation; typically
492 this is used to install the image for the device's baseband
494 return self._DoCall("IncrementalOTA_InstallEnd")