OSDN Git Service

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