blob: 705ed84afb4983cd51cc269b998133d41d102bd4 [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
15import getopt
16import getpass
17import os
18import re
19import shutil
20import subprocess
21import sys
22import tempfile
23
24# missing in Python 2.4 and before
25if not hasattr(os, "SEEK_SET"):
26 os.SEEK_SET = 0
27
28class Options(object): pass
29OPTIONS = Options()
30OPTIONS.signapk_jar = "out/host/linux-x86/framework/signapk.jar"
31OPTIONS.max_image_size = {}
32OPTIONS.verbose = False
33OPTIONS.tempfiles = []
34
35
36class ExternalError(RuntimeError): pass
37
38
39def Run(args, **kwargs):
40 """Create and return a subprocess.Popen object, printing the command
41 line on the terminal if -v was specified."""
42 if OPTIONS.verbose:
43 print " running: ", " ".join(args)
44 return subprocess.Popen(args, **kwargs)
45
46
47def LoadBoardConfig(fn):
48 """Parse a board_config.mk file looking for lines that specify the
49 maximum size of various images, and parse them into the
50 OPTIONS.max_image_size dict."""
51 OPTIONS.max_image_size = {}
52 for line in open(fn):
53 line = line.strip()
54 m = re.match(r"BOARD_(BOOT|RECOVERY|SYSTEM|USERDATA)IMAGE_MAX_SIZE"
55 r"\s*:=\s*(\d+)", line)
56 if not m: continue
57
58 OPTIONS.max_image_size[m.group(1).lower() + ".img"] = int(m.group(2))
59
60
61def BuildAndAddBootableImage(sourcedir, targetname, output_zip):
62 """Take a kernel, cmdline, and ramdisk directory from the input (in
63 'sourcedir'), and turn them into a boot image. Put the boot image
64 into the output zip file under the name 'targetname'."""
65
66 print "creating %s..." % (targetname,)
67
68 img = BuildBootableImage(sourcedir)
69
70 CheckSize(img, targetname)
71 output_zip.writestr(targetname, img)
72
73def BuildBootableImage(sourcedir):
74 """Take a kernel, cmdline, and ramdisk directory from the input (in
75 'sourcedir'), and turn them into a boot image. Return the image data."""
76
77 ramdisk_img = tempfile.NamedTemporaryFile()
78 img = tempfile.NamedTemporaryFile()
79
80 p1 = Run(["mkbootfs", os.path.join(sourcedir, "RAMDISK")],
81 stdout=subprocess.PIPE)
82 p2 = Run(["gzip", "-n"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
83
84 p2.wait()
85 p1.wait()
86 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (targetname,)
87 assert p2.returncode == 0, "gzip of %s ramdisk failed" % (targetname,)
88
89 cmdline = open(os.path.join(sourcedir, "cmdline")).read().rstrip("\n")
90 p = Run(["mkbootimg",
91 "--kernel", os.path.join(sourcedir, "kernel"),
92 "--cmdline", cmdline,
93 "--ramdisk", ramdisk_img.name,
94 "--output", img.name],
95 stdout=subprocess.PIPE)
96 p.communicate()
97 assert p.returncode == 0, "mkbootimg of %s image failed" % (targetname,)
98
99 img.seek(os.SEEK_SET, 0)
100 data = img.read()
101
102 ramdisk_img.close()
103 img.close()
104
105 return data
106
107
108def AddRecovery(output_zip):
109 BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "RECOVERY"),
110 "recovery.img", output_zip)
111
112def AddBoot(output_zip):
113 BuildAndAddBootableImage(os.path.join(OPTIONS.input_tmp, "BOOT"),
114 "boot.img", output_zip)
115
116def UnzipTemp(filename):
117 """Unzip the given archive into a temporary directory and return the name."""
118
119 tmp = tempfile.mkdtemp(prefix="targetfiles-")
120 OPTIONS.tempfiles.append(tmp)
121 p = Run(["unzip", "-q", filename, "-d", tmp], stdout=subprocess.PIPE)
122 p.communicate()
123 if p.returncode != 0:
124 raise ExternalError("failed to unzip input target-files \"%s\"" %
125 (filename,))
126 return tmp
127
128
129def GetKeyPasswords(keylist):
130 """Given a list of keys, prompt the user to enter passwords for
131 those which require them. Return a {key: password} dict. password
132 will be None if the key has no password."""
133
134 key_passwords = {}
135 devnull = open("/dev/null", "w+b")
136 for k in sorted(keylist):
137 p = subprocess.Popen(["openssl", "pkcs8", "-in", k+".pk8",
138 "-inform", "DER", "-nocrypt"],
139 stdin=devnull.fileno(),
140 stdout=devnull.fileno(),
141 stderr=subprocess.STDOUT)
142 p.communicate()
143 if p.returncode == 0:
144 print "%s.pk8 does not require a password" % (k,)
145 key_passwords[k] = None
146 else:
147 key_passwords[k] = getpass.getpass("Enter password for %s.pk8> " % (k,))
148 devnull.close()
149 print
150 return key_passwords
151
152
153def SignFile(input_name, output_name, key, password, align=None):
154 """Sign the input_name zip/jar/apk, producing output_name. Use the
155 given key and password (the latter may be None if the key does not
156 have a password.
157
158 If align is an integer > 1, zipalign is run to align stored files in
159 the output zip on 'align'-byte boundaries.
160 """
161 if align == 0 or align == 1:
162 align = None
163
164 if align:
165 temp = tempfile.NamedTemporaryFile()
166 sign_name = temp.name
167 else:
168 sign_name = output_name
169
170 p = subprocess.Popen(["java", "-jar", OPTIONS.signapk_jar,
171 key + ".x509.pem",
172 key + ".pk8",
173 input_name, sign_name],
174 stdin=subprocess.PIPE,
175 stdout=subprocess.PIPE)
176 if password is not None:
177 password += "\n"
178 p.communicate(password)
179 if p.returncode != 0:
180 raise ExternalError("signapk.jar failed: return code %s" % (p.returncode,))
181
182 if align:
183 p = subprocess.Popen(["zipalign", "-f", str(align), sign_name, output_name])
184 p.communicate()
185 if p.returncode != 0:
186 raise ExternalError("zipalign failed: return code %s" % (p.returncode,))
187 temp.close()
188
189
190def CheckSize(data, target):
191 """Check the data string passed against the max size limit, if
192 any, for the given target. Raise exception if the data is too big.
193 Print a warning if the data is nearing the maximum size."""
194 limit = OPTIONS.max_image_size.get(target, None)
195 if limit is None: return
196
197 size = len(data)
198 pct = float(size) * 100.0 / limit
199 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
200 if pct >= 99.0:
201 raise ExternalError(msg)
202 elif pct >= 95.0:
203 print
204 print " WARNING: ", msg
205 print
206 elif OPTIONS.verbose:
207 print " ", msg
208
209
210COMMON_DOCSTRING = """
211 -p (--path) <dir>
212 Prepend <dir> to the list of places to search for binaries run
213 by this script.
214
215 -v (--verbose)
216 Show command lines being executed.
217
218 -h (--help)
219 Display this usage message and exit.
220"""
221
222def Usage(docstring):
223 print docstring.rstrip("\n")
224 print COMMON_DOCSTRING
225
226
227def ParseOptions(argv,
228 docstring,
229 extra_opts="", extra_long_opts=(),
230 extra_option_handler=None):
231 """Parse the options in argv and return any arguments that aren't
232 flags. docstring is the calling module's docstring, to be displayed
233 for errors and -h. extra_opts and extra_long_opts are for flags
234 defined by the caller, which are processed by passing them to
235 extra_option_handler."""
236
237 try:
238 opts, args = getopt.getopt(
239 argv, "hvp:" + extra_opts,
240 ["help", "verbose", "path="] + list(extra_long_opts))
241 except getopt.GetoptError, err:
242 Usage(docstring)
243 print "**", str(err), "**"
244 sys.exit(2)
245
246 path_specified = False
247
248 for o, a in opts:
249 if o in ("-h", "--help"):
250 Usage(docstring)
251 sys.exit()
252 elif o in ("-v", "--verbose"):
253 OPTIONS.verbose = True
254 elif o in ("-p", "--path"):
255 os.environ["PATH"] = a + os.pathsep + os.environ["PATH"]
256 path_specified = True
257 else:
258 if extra_option_handler is None or not extra_option_handler(o, a):
259 assert False, "unknown option \"%s\"" % (o,)
260
261 if not path_specified:
262 os.environ["PATH"] = ("out/host/linux-x86/bin" + os.pathsep +
263 os.environ["PATH"])
264
265 return args
266
267
268def Cleanup():
269 for i in OPTIONS.tempfiles:
270 if os.path.isdir(i):
271 shutil.rmtree(i)
272 else:
273 os.remove(i)