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)
50 def LoadBoardConfig(fn):
51 """Parse a board_config.mk file looking for lines that specify the
52 maximum size of various images, and parse them into the
53 OPTIONS.max_image_size dict."""
54 OPTIONS.max_image_size = {}
57 m = re.match(r"BOARD_(BOOT|RECOVERY|SYSTEM|USERDATA)IMAGE_MAX_SIZE"
58 r"\s*:=\s*(\d+)", line)
61 OPTIONS.max_image_size[m.group(1).lower() + ".img"] = int(m.group(2))
64 def BuildAndAddBootableImage(sourcedir, targetname, output_zip):
65 """Take a kernel, cmdline, and ramdisk directory from the input (in
66 'sourcedir'), and turn them into a boot image. Put the boot image
67 into the output zip file under the name 'targetname'. Returns
68 targetname on success or None on failure (if sourcedir does not
69 appear to contain files for the requested image)."""
71 print "creating %s..." % (targetname,)
73 img = BuildBootableImage(sourcedir)
77 CheckSize(img, targetname)
78 ZipWriteStr(output_zip, targetname, img)
81 def BuildBootableImage(sourcedir):
82 """Take a kernel, cmdline, and ramdisk directory from the input (in
83 'sourcedir'), and turn them into a boot image. Return the image
84 data, or None if sourcedir does not appear to contains files for
85 building the requested image."""
87 if (not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK) or
88 not os.access(os.path.join(sourcedir, "kernel"), os.F_OK)):
91 ramdisk_img = tempfile.NamedTemporaryFile()
92 img = tempfile.NamedTemporaryFile()
94 p1 = Run(["mkbootfs", os.path.join(sourcedir, "RAMDISK")],
95 stdout=subprocess.PIPE)
96 p2 = Run(["minigzip"],
97 stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
101 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
102 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (targetname,)
104 cmd = ["mkbootimg", "--kernel", os.path.join(sourcedir, "kernel")]
106 fn = os.path.join(sourcedir, "cmdline")
107 if os.access(fn, os.F_OK):
108 cmd.append("--cmdline")
109 cmd.append(open(fn).read().rstrip("\n"))
111 fn = os.path.join(sourcedir, "base")
112 if os.access(fn, os.F_OK):
114 cmd.append(open(fn).read().rstrip("\n"))
116 cmd.extend(["--ramdisk", ramdisk_img.name,
117 "--output", img.name])
119 p = Run(cmd, stdout=subprocess.PIPE)
121 assert p.returncode == 0, "mkbootimg of %s image failed" % (
122 os.path.basename(sourcedir),)
124 img.seek(os.SEEK_SET, 0)
133 def AddRecovery(output_zip):
134 BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "RECOVERY"),
135 "recovery.img", output_zip)
137 def AddBoot(output_zip):
138 BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "BOOT"),
139 "boot.img", output_zip)
141 def UnzipTemp(filename):
142 """Unzip the given archive into a temporary directory and return the name."""
144 tmp = tempfile.mkdtemp(prefix="targetfiles-")
145 OPTIONS.tempfiles.append(tmp)
146 p = Run(["unzip", "-q", filename, "-d", tmp], stdout=subprocess.PIPE)
148 if p.returncode != 0:
149 raise ExternalError("failed to unzip input target-files \"%s\"" %
154 def GetKeyPasswords(keylist):
155 """Given a list of keys, prompt the user to enter passwords for
156 those which require them. Return a {key: password} dict. password
157 will be None if the key has no password."""
161 devnull = open("/dev/null", "w+b")
162 for k in sorted(keylist):
163 # An empty-string key is used to mean don't re-sign this package.
164 # Obviously we don't need a password for this non-key.
166 no_passwords.append(k)
169 p = Run(["openssl", "pkcs8", "-in", k+".pk8",
170 "-inform", "DER", "-nocrypt"],
171 stdin=devnull.fileno(),
172 stdout=devnull.fileno(),
173 stderr=subprocess.STDOUT)
175 if p.returncode == 0:
176 no_passwords.append(k)
178 need_passwords.append(k)
181 key_passwords = PasswordManager().GetPasswords(need_passwords)
182 key_passwords.update(dict.fromkeys(no_passwords, None))
186 def SignFile(input_name, output_name, key, password, align=None):
187 """Sign the input_name zip/jar/apk, producing output_name. Use the
188 given key and password (the latter may be None if the key does not
191 If align is an integer > 1, zipalign is run to align stored files in
192 the output zip on 'align'-byte boundaries.
194 if align == 0 or align == 1:
198 temp = tempfile.NamedTemporaryFile()
199 sign_name = temp.name
201 sign_name = output_name
203 p = Run(["java", "-jar",
204 os.path.join(OPTIONS.search_path, "framework", "signapk.jar"),
207 input_name, sign_name],
208 stdin=subprocess.PIPE,
209 stdout=subprocess.PIPE)
210 if password is not None:
212 p.communicate(password)
213 if p.returncode != 0:
214 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
217 p = Run(["zipalign", "-f", str(align), sign_name, output_name])
219 if p.returncode != 0:
220 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
224 def CheckSize(data, target):
225 """Check the data string passed against the max size limit, if
226 any, for the given target. Raise exception if the data is too big.
227 Print a warning if the data is nearing the maximum size."""
228 limit = OPTIONS.max_image_size.get(target, None)
229 if limit is None: return
232 pct = float(size) * 100.0 / limit
233 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
235 raise ExternalError(msg)
238 print " WARNING: ", msg
240 elif OPTIONS.verbose:
244 COMMON_DOCSTRING = """
246 Prepend <dir>/bin to the list of places to search for binaries
247 run by this script, and expect to find jars in <dir>/framework.
249 -s (--device_specific) <file>
250 Path to the python module containing device-specific
254 Show command lines being executed.
257 Display this usage message and exit.
260 def Usage(docstring):
261 print docstring.rstrip("\n")
262 print COMMON_DOCSTRING
265 def ParseOptions(argv,
267 extra_opts="", extra_long_opts=(),
268 extra_option_handler=None):
269 """Parse the options in argv and return any arguments that aren't
270 flags. docstring is the calling module's docstring, to be displayed
271 for errors and -h. extra_opts and extra_long_opts are for flags
272 defined by the caller, which are processed by passing them to
273 extra_option_handler."""
276 opts, args = getopt.getopt(
277 argv, "hvp:s:" + extra_opts,
278 ["help", "verbose", "path=", "device_specific="] +
279 list(extra_long_opts))
280 except getopt.GetoptError, err:
282 print "**", str(err), "**"
285 path_specified = False
288 if o in ("-h", "--help"):
291 elif o in ("-v", "--verbose"):
292 OPTIONS.verbose = True
293 elif o in ("-p", "--path"):
294 OPTIONS.search_path = a
295 elif o in ("-s", "--device_specific"):
296 OPTIONS.device_specific = a
298 if extra_option_handler is None or not extra_option_handler(o, a):
299 assert False, "unknown option \"%s\"" % (o,)
301 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
302 os.pathsep + os.environ["PATH"])
308 for i in OPTIONS.tempfiles:
315 class PasswordManager(object):
317 self.editor = os.getenv("EDITOR", None)
318 self.pwfile = os.getenv("ANDROID_PW_FILE", None)
320 def GetPasswords(self, items):
321 """Get passwords corresponding to each string in 'items',
322 returning a dict. (The dict may have keys in addition to the
325 Uses the passwords in $ANDROID_PW_FILE if available, letting the
326 user edit that file to add more needed passwords. If no editor is
327 available, or $ANDROID_PW_FILE isn't define, prompts the user
328 interactively in the ordinary way.
331 current = self.ReadFile()
337 if i not in current or not current[i]:
339 # Are all the passwords already in the file?
340 if not missing: return current
346 print "key file %s still missing some passwords." % (self.pwfile,)
347 answer = raw_input("try to edit again? [y]> ").strip()
348 if answer and answer[0] not in 'yY':
349 raise RuntimeError("key passwords unavailable")
352 current = self.UpdateAndReadFile(current)
354 def PromptResult(self, current):
355 """Prompt the user to enter a value (password) for each key in
356 'current' whose value is fales. Returns a new dict with all the
360 for k, v in sorted(current.iteritems()):
365 result[k] = getpass.getpass("Enter password for %s key> "
370 def UpdateAndReadFile(self, current):
371 if not self.editor or not self.pwfile:
372 return self.PromptResult(current)
374 f = open(self.pwfile, "w")
375 os.chmod(self.pwfile, 0600)
376 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
377 f.write("# (Additional spaces are harmless.)\n\n")
380 sorted = [(not v, k, v) for (k, v) in current.iteritems()]
382 for i, (_, k, v) in enumerate(sorted):
383 f.write("[[[ %s ]]] %s\n" % (v, k))
384 if not v and first_line is None:
385 # position cursor on first line with no password.
389 p = Run([self.editor, "+%d" % (first_line,), self.pwfile])
390 _, _ = p.communicate()
392 return self.ReadFile()
396 if self.pwfile is None: return result
398 f = open(self.pwfile, "r")
401 if not line or line[0] == '#': continue
402 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
404 print "failed to parse password file: ", line
406 result[m.group(2)] = m.group(1)
409 if e.errno != errno.ENOENT:
410 print "error reading password file: ", str(e)
414 def ZipWriteStr(zip, filename, data, perms=0644):
415 # use a fixed timestamp so the output is repeatable.
416 zinfo = zipfile.ZipInfo(filename=filename,
417 date_time=(2009, 1, 1, 0, 0, 0))
418 zinfo.compress_type = zip.compression
419 zinfo.external_attr = perms << 16
420 zip.writestr(zinfo, data)
423 class DeviceSpecificParams(object):
425 def __init__(self, **kwargs):
426 """Keyword arguments to the constructor become attributes of this
427 object, which is passed to all functions in the device-specific
429 for k, v in kwargs.iteritems():
432 if self.module is None:
433 path = OPTIONS.device_specific
434 if path is None: return
436 if os.path.isdir(path):
437 info = imp.find_module("releasetools", [path])
439 d, f = os.path.split(path)
440 b, x = os.path.splitext(f)
443 info = imp.find_module(f, [d])
444 self.module = imp.load_module("device_specific", *info)
446 print "unable to load device-specific module; assuming none"
448 def _DoCall(self, function_name, *args, **kwargs):
449 """Call the named function in the device-specific module, passing
450 the given args and kwargs. The first argument to the call will be
451 the DeviceSpecific object itself. If there is no module, or the
452 module does not define the function, return the value of the
453 'default' kwarg (which itself defaults to None)."""
454 if self.module is None or not hasattr(self.module, function_name):
455 return kwargs.get("default", None)
456 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
458 def FullOTA_Assertions(self):
459 """Called after emitting the block of assertions at the top of a
460 full OTA package. Implementations can add whatever additional
461 assertions they like."""
462 return self._DoCall("FullOTA_Assertions")
464 def FullOTA_InstallEnd(self):
465 """Called at the end of full OTA installation; typically this is
466 used to install the image for the device's baseband processor."""
467 return self._DoCall("FullOTA_InstallEnd")
469 def IncrementalOTA_Assertions(self):
470 """Called after emitting the block of assertions at the top of an
471 incremental OTA package. Implementations can add whatever
472 additional assertions they like."""
473 return self._DoCall("IncrementalOTA_Assertions")
475 def IncrementalOTA_VerifyEnd(self):
476 """Called at the end of the verification phase of incremental OTA
477 installation; additional checks can be placed here to abort the
478 script before any changes are made."""
479 return self._DoCall("IncrementalOTA_VerifyEnd")
481 def IncrementalOTA_InstallEnd(self):
482 """Called at the end of incremental OTA installation; typically
483 this is used to install the image for the device's baseband
485 return self._DoCall("IncrementalOTA_InstallEnd")