blob: 5dde642e76279ef8338bf4288dfca1cf6455b235 [file] [log] [blame]
The Android Open Source Project88b60792009-03-03 19:28:42 -08001#!/usr/bin/env python
2#
3# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the 'License');
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an 'AS IS' BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18"""Creates optimized versions of APK files.
19
20A tool and associated functions to communicate with an Android
21emulator instance, run commands, and scrape out files.
22
23Requires at least python2.4.
24"""
25
26import array
27import datetime
28import optparse
29import os
30import posix
31import select
32import signal
33import struct
34import subprocess
35import sys
36import tempfile
37import time
38import zlib
39
40
41_emulator_popen = None
42_DEBUG_READ = 1
43
44
45def EnsureTempDir(path=None):
46 """Creates a temporary directory and returns its path.
47
48 Creates any necessary parent directories.
49
50 Args:
51 path: If specified, used as the temporary directory. If not specified,
52 a safe temporary path is created. The caller is responsible for
53 deleting the directory.
54
55 Returns:
56 The path to the new directory, or None if a problem occurred.
57 """
58 if path is None:
59 path = tempfile.mkdtemp('', 'dexpreopt-')
60 elif not os.path.exists(path):
61 os.makedirs(path)
62 elif not os.path.isdir(path):
63 return None
64 return path
65
66
67def CreateZeroedFile(path, length):
68 """Creates the named file and writes <length> zero bytes to it.
69
70 Unlinks the file first if it already exists.
71 Creates its containing directory if necessary.
72
73 Args:
74 path: The path to the file to create.
75 length: The number of zero bytes to write to the file.
76
77 Returns:
78 True on success.
79 """
80 subprocess.call(['rm', '-f', path])
81 d = os.path.dirname(path)
82 if d and not os.path.exists(d): os.makedirs(os.path.dirname(d))
83 # TODO: redirect child's stdout to /dev/null
84 ret = subprocess.call(['dd', 'if=/dev/zero', 'of=%s' % path,
85 'bs=%d' % length, 'count=1'])
86 return not ret # i.e., ret == 0; i.e., the child exited successfully.
87
88
89def StartEmulator(exe_name='emulator', kernel=None,
90 ramdisk=None, image=None, userdata=None, system=None):
91 """Runs the emulator with the specified arguments.
92
93 Args:
94 exe_name: The name of the emulator to run. May be absolute, relative,
95 or unqualified (and left to exec() to find).
96 kernel: If set, passed to the emulator as "-kernel".
97 ramdisk: If set, passed to the emulator as "-ramdisk".
The Android Open Source Project6bce2052009-03-13 13:04:19 -070098 image: If set, passed to the emulator as "-system".
The Android Open Source Project88b60792009-03-03 19:28:42 -080099 userdata: If set, passed to the emulator as "-initdata" and "-data".
The Android Open Source Project6bce2052009-03-13 13:04:19 -0700100 system: If set, passed to the emulator as "-sysdir".
The Android Open Source Project88b60792009-03-03 19:28:42 -0800101
102 Returns:
103 A subprocess.Popen that refers to the emulator process, or None if
104 a problem occurred.
105 """
106 #exe_name = './stuff'
107 args = [exe_name]
108 if kernel: args += ['-kernel', kernel]
109 if ramdisk: args += ['-ramdisk', ramdisk]
The Android Open Source Project6bce2052009-03-13 13:04:19 -0700110 if image: args += ['-system', image]
The Android Open Source Project88b60792009-03-03 19:28:42 -0800111 if userdata: args += ['-initdata', userdata, '-data', userdata]
The Android Open Source Project6bce2052009-03-13 13:04:19 -0700112 if system: args += ['-sysdir', system]
113 args += ['-partition-size', '128']
The Android Open Source Project88b60792009-03-03 19:28:42 -0800114 args += ['-no-window', '-netfast', '-noaudio']
115
116 _USE_PIPE = True
117
118 if _USE_PIPE:
119 # Use dedicated fds instead of stdin/out to talk to the
120 # emulator so that the emulator doesn't try to tty-cook
121 # the data.
122 em_stdin_r, em_stdin_w = posix.pipe()
123 em_stdout_r, em_stdout_w = posix.pipe()
124 args += ['-shell-serial', 'fdpair:%d:%d' % (em_stdin_r, em_stdout_w)]
125 else:
126 args += ['-shell']
127
128 # Ensure that this environment variable isn't set;
129 # if it is, the emulator will print the log to stdout.
130 if os.environ.get('ANDROID_LOG_TAGS'):
131 del os.environ['ANDROID_LOG_TAGS']
132
133 try:
134 # bufsize=1 line-buffered, =0 unbuffered,
135 # <0 system default (fully buffered)
136 Trace('Running emulator: %s' % ' '.join(args))
137 if _USE_PIPE:
138 ep = subprocess.Popen(args)
139 else:
140 ep = subprocess.Popen(args, close_fds=True,
141 stdin=subprocess.PIPE,
142 stdout=subprocess.PIPE,
143 stderr=subprocess.PIPE)
144 if ep:
145 if _USE_PIPE:
146 # Hijack the Popen.stdin/.stdout fields to point to our
147 # pipes. These are the same fields that would have been set
148 # if we called Popen() with stdin=subprocess.PIPE, etc.
149 # Note that these names are from the point of view of the
150 # child process.
151 #
152 # Since we'll be using select.select() to read data a byte
153 # at a time, it's important that these files are unbuffered
154 # (bufsize=0). If Popen() took care of the pipes, they're
155 # already unbuffered.
156 ep.stdin = os.fdopen(em_stdin_w, 'w', 0)
157 ep.stdout = os.fdopen(em_stdout_r, 'r', 0)
158 return ep
159 except OSError, e:
160 print >>sys.stderr, 'Could not start emulator:', e
161 return None
162
163
164def IsDataAvailable(fo, timeout=0):
165 """Indicates whether or not data is available to be read from a file object.
166
167 Args:
168 fo: A file object to read from.
169 timeout: The number of seconds to wait for data, or zero for no timeout.
170
171 Returns:
172 True iff data is available to be read.
173 """
174 return select.select([fo], [], [], timeout) == ([fo], [], [])
175
176
177def ConsumeAvailableData(fo):
178 """Reads data from a file object while it's available.
179
180 Stops when no more data is immediately available or upon reaching EOF.
181
182 Args:
183 fo: A file object to read from.
184
185 Returns:
186 An unsigned byte array.array of the data that was read.
187 """
188 buf = array.array('B')
189 while IsDataAvailable(fo):
190 try:
191 buf.fromfile(fo, 1)
192 except EOFError:
193 break
194 return buf
195
196
197def ShowTimeout(timeout, end_time):
198 """For debugging, display the timeout info.
199
200 Args:
201 timeout: the timeout in seconds.
202 end_time: a time.time()-based value indicating when the timeout should
203 expire.
204 """
205 if _DEBUG_READ:
206 if timeout:
207 remaining = end_time - time.time()
208 Trace('ok, time remaining %.1f of %.1f' % (remaining, timeout))
209 else:
210 Trace('ok (no timeout)')
211
212
213def WaitForString(inf, pattern, timeout=0, max_len=0, eat_to_eol=True,
214 reset_on_activity=False):
215 """Reads from a file object and returns when the pattern matches the data.
216
217 Reads a byte at a time to avoid consuming extra data, so do not call
218 this function when you expect the pattern to match a large amount of data.
219
220 Args:
221 inf: The file object to read from.
222 pattern: The string to look for in the input data.
223 May be a tuple of strings.
224 timeout: How long to wait, in seconds. No timeout if it evaluates to False.
225 max_len: Return None if this many bytes have been read without matching.
226 No upper bound if it evaluates to False.
227 eat_to_eol: If true, the input data will be consumed until a '\\n' or EOF
228 is encountered.
229 reset_on_activity: If True, reset the timeout whenever a character is
230 read.
231
232 Returns:
233 The input data matching the expression as an unsigned char array,
234 or None if the operation timed out or didn't match after max_len bytes.
235
236 Raises:
237 IOError: An error occurred reading from the input file.
238 """
239 if timeout:
240 end_time = time.time() + timeout
241 else:
242 end_time = 0
243
244 if _DEBUG_READ:
245 Trace('WaitForString: "%s", %.1f' % (pattern, timeout))
246
247 buf = array.array('B') # unsigned char array
248 eating = False
249 while True:
250 if end_time:
251 remaining = end_time - time.time()
252 if remaining <= 0:
253 Trace('Timeout expired after %.1f seconds' % timeout)
254 return None
255 else:
256 remaining = None
257
258 if IsDataAvailable(inf, remaining):
259 if reset_on_activity and timeout:
260 end_time = time.time() + timeout
261
262 buf.fromfile(inf, 1)
263 if _DEBUG_READ:
264 c = buf.tostring()[-1:]
265 ci = ord(c)
266 if ci < 0x20: c = '.'
267 if _DEBUG_READ > 1:
268 print 'read [%c] 0x%02x' % (c, ci)
269
270 if not eating:
271 if buf.tostring().endswith(pattern):
272 if eat_to_eol:
273 if _DEBUG_READ > 1:
274 Trace('Matched; eating to EOL')
275 eating = True
276 else:
277 ShowTimeout(timeout, end_time)
278 return buf
279 if _DEBUG_READ > 2:
280 print '/%s/ ? "%s"' % (pattern, buf.tostring())
281 else:
282 if buf.tostring()[-1:] == '\n':
283 ShowTimeout(timeout, end_time)
284 return buf
285
286 if max_len and len(buf) >= max_len: return None
287
288
289def WaitForEmulator(ep, timeout=0):
290 """Waits for the emulator to start up and print the first prompt.
291
292 Args:
293 ep: A subprocess.Popen object referring to the emulator process.
294 timeout: How long to wait, in seconds. No timeout if it evaluates to False.
295
296 Returns:
297 True on success, False if the timeout occurred.
298 """
299 # Prime the pipe; the emulator doesn't start without this.
300 print >>ep.stdin, ''
301
302 # Wait until the console is ready and the first prompt appears.
303 buf = WaitForString(ep.stdout, '#', timeout=timeout, eat_to_eol=False)
304 if buf:
305 Trace('Saw the prompt: "%s"' % buf.tostring())
306 return True
307 return False
308
309
310def WaitForPrompt(ep, prompt=None, timeout=0, reset_on_activity=False):
311 """Blocks until the prompt appears on ep.stdout or the timeout elapses.
312
313 Args:
314 ep: A subprocess.Popen connection to the emulator process.
315 prompt: The prompt to wait for. If None, uses ep.prompt.
316 timeout: How many seconds to wait for the prompt. Waits forever
317 if timeout is zero.
318 reset_on_activity: If True, reset the timeout whenever a character is
319 read.
320
321 Returns:
322 A string containing the data leading up to the prompt. The string
323 will always end in '\\n'. Returns None if the prompt was not seen
324 within the timeout, or if some other error occurred.
325 """
326 if not prompt: prompt = ep.prompt
327 if prompt:
328 #Trace('waiting for prompt "%s"' % prompt)
329 data = WaitForString(ep.stdout, prompt,
330 timeout=timeout, reset_on_activity=reset_on_activity)
331 if data:
332 # data contains everything on ep.stdout up to and including the prompt,
333 # plus everything up 'til the newline. Scrape out the prompt
334 # and everything that follows, and ensure that the result ends
335 # in a newline (which is important if it would otherwise be empty).
336 s = data.tostring()
337 i = s.rfind(prompt)
338 s = s[:i]
339 if s[-1:] != '\n':
340 s += '\n'
341 if _DEBUG_READ:
342 print 'WaitForPrompt saw """\n%s"""' % s
343 return s
344 return None
345
346
347def ReplaceEmulatorPrompt(ep, prompt=None):
348 """Replaces PS1 in the emulator with a different value.
349
350 This is useful for making the prompt unambiguous; i.e., something
351 that probably won't appear in the output of another command.
352
353 Assumes that the emulator is already sitting at a prompt,
354 waiting for shell input.
355
356 Puts the new prompt in ep.prompt.
357
358 Args:
359 ep: A subprocess.Popen object referring to the emulator process.
360 prompt: The new prompt to use
361
362 Returns:
363 True on success, False if the timeout occurred.
364 """
365 if not prompt:
366 prompt = '-----DEXPREOPT-PROMPT-----'
367 print >>ep.stdin, 'PS1="%s\n"' % prompt
368 ep.prompt = prompt
369
370 # Eat the command echo.
371 data = WaitForPrompt(ep, timeout=2)
372 if not data:
373 return False
374
375 # Make sure it's actually there.
376 return WaitForPrompt(ep, timeout=2)
377
378
379def RunEmulatorCommand(ep, cmd, timeout=0):
380 """Sends the command to the emulator's shell and waits for the result.
381
382 Assumes that the emulator is already sitting at a prompt,
383 waiting for shell input.
384
385 Args:
386 ep: A subprocess.Popen object referring to the emulator process.
387 cmd: The shell command to run in the emulator.
388 timeout: The number of seconds to wait for the command to complete,
389 or zero for no timeout.
390
391 Returns:
392 If the command ran and returned to the console prompt before the
393 timeout, returns the output of the command as a string.
394 Returns None otherwise.
395 """
396 ConsumeAvailableData(ep.stdout)
397
398 Trace('Running "%s"' % cmd)
399 print >>ep.stdin, '%s' % cmd
400
401 # The console will echo the command.
402 #Trace('Waiting for echo')
403 if WaitForString(ep.stdout, cmd, timeout=timeout):
404 #Trace('Waiting for completion')
405 return WaitForPrompt(ep, timeout=timeout, reset_on_activity=True)
406
407 return None
408
409
410def ReadFileList(ep, dir_list, timeout=0):
411 """Returns a list of emulator files in each dir in dir_list.
412
413 Args:
414 ep: A subprocess.Popen object referring to the emulator process.
415 dir_list: List absolute paths to directories to read.
416 timeout: The number of seconds to wait for the command to complete,
417 or zero for no timeout.
418
419 Returns:
420 A list of absolute paths to files in the named directories,
421 in the context of the emulator's filesystem.
422 None on failure.
423 """
424 ret = []
425 for d in dir_list:
426 output = RunEmulatorCommand(ep, 'ls ' + d, timeout=timeout)
427 if not output:
428 Trace('Could not ls ' + d)
429 return None
430 ret += ['%s/%s' % (d, f) for f in output.splitlines()]
431 return ret
432
433
434def DownloadDirectoryHierarchy(ep, src, dest, timeout=0):
435 """Recursively downloads an emulator directory to the local filesystem.
436
437 Args:
438 ep: A subprocess.Popen object referring to the emulator process.
439 src: The path on the emulator's filesystem to download from.
440 dest: The path on the local filesystem to download to.
441 timeout: The number of seconds to wait for the command to complete,
442 or zero for no timeout. (CURRENTLY IGNORED)
443
444 Returns:
445 True iff the files downloaded successfully, False otherwise.
446 """
447 ConsumeAvailableData(ep.stdout)
448
449 if not os.path.exists(dest):
450 os.makedirs(dest)
451
452 cmd = 'afar %s' % src
453 Trace('Running "%s"' % cmd)
454 print >>ep.stdin, '%s' % cmd
455
456 # The console will echo the command.
457 #Trace('Waiting for echo')
458 if not WaitForString(ep.stdout, cmd, timeout=timeout):
459 return False
460
461 #TODO: use a signal to support timing out?
462
463 #
464 # Android File Archive format:
465 #
466 # magic[5]: 'A' 'F' 'A' 'R' '\n'
467 # version[4]: 0x00 0x00 0x00 0x01
468 # for each file:
469 # file magic[4]: 'F' 'I' 'L' 'E'
470 # namelen[4]: Length of file name, including NUL byte (big-endian)
471 # name[*]: NUL-terminated file name
472 # datalen[4]: Length of file (big-endian)
473 # data[*]: Unencoded file data
474 # adler32[4]: adler32 of the unencoded file data (big-endian)
475 # file end magic[4]: 'f' 'i' 'l' 'e'
476 # end magic[4]: 'E' 'N' 'D' 0x00
477 #
478
479 # Read the header.
480 HEADER = array.array('B', 'AFAR\n\000\000\000\001')
481 buf = array.array('B')
482 buf.fromfile(ep.stdout, len(HEADER))
483 if buf != HEADER:
484 Trace('Header does not match: "%s"' % buf)
485 return False
486
487 # Read the file entries.
488 FILE_START = array.array('B', 'FILE')
489 FILE_END = array.array('B', 'file')
490 END = array.array('B', 'END\000')
491 while True:
492 # Entry magic.
493 buf = array.array('B')
494 buf.fromfile(ep.stdout, 4)
495 if buf == FILE_START:
496 # Name length (4 bytes, big endian)
497 buf = array.array('B')
498 buf.fromfile(ep.stdout, 4)
499 (name_len,) = struct.unpack('>I', buf)
500 #Trace('name len %d' % name_len)
501
502 # Name, NUL-terminated.
503 buf = array.array('B')
504 buf.fromfile(ep.stdout, name_len)
505 buf.pop() # Remove trailing NUL byte.
506 file_name = buf.tostring()
507 Trace('FILE: %s' % file_name)
508
509 # File length (4 bytes, big endian)
510 buf = array.array('B')
511 buf.fromfile(ep.stdout, 4)
512 (file_len,) = struct.unpack('>I', buf)
513
514 # File data.
515 data = array.array('B')
516 data.fromfile(ep.stdout, file_len)
517 #Trace('FILE: read %d bytes from %s' % (file_len, file_name))
518
519 # adler32 (4 bytes, big endian)
520 buf = array.array('B')
521 buf.fromfile(ep.stdout, 4)
522 (adler32,) = struct.unpack('>i', buf) # adler32 wants a signed int ('i')
523 data_adler32 = zlib.adler32(data)
524 # Because of a difference in behavior of zlib.adler32 on 32-bit and 64-bit
525 # systems (one returns signed values, the other unsigned), we take the
526 # modulo 2**32 of the checksums, and compare those.
527 # See also http://bugs.python.org/issue1202
528 if (adler32 % (2**32)) != (data_adler32 % (2**32)):
529 Trace('adler32 does not match: calculated 0x%08x != expected 0x%08x' %
530 (data_adler32, adler32))
531 return False
532
533 # File end magic.
534 buf = array.array('B')
535 buf.fromfile(ep.stdout, 4)
536 if buf != FILE_END:
537 Trace('Unexpected file end magic "%s"' % buf)
538 return False
539
540 # Write to the output file
541 out_file_name = dest + '/' + file_name[len(src):]
542 p = os.path.dirname(out_file_name)
543 if not os.path.exists(p): os.makedirs(p)
544 fo = file(out_file_name, 'w+b')
545 fo.truncate(0)
546 Trace('FILE: Writing %d bytes to %s' % (len(data), out_file_name))
547 data.tofile(fo)
548 fo.close()
549
550 elif buf == END:
551 break
552 else:
553 Trace('Unexpected magic "%s"' % buf)
554 return False
555
556 return WaitForPrompt(ep, timeout=timeout, reset_on_activity=True)
557
558
559def ReadBootClassPath(ep, timeout=0):
560 """Reads and returns the default bootclasspath as a list of files.
561
562 Args:
563 ep: A subprocess.Popen object referring to the emulator process.
564 timeout: The number of seconds to wait for the command to complete,
565 or zero for no timeout.
566
567 Returns:
568 The bootclasspath as a list of strings.
569 None on failure.
570 """
571 bcp = RunEmulatorCommand(ep, 'echo $BOOTCLASSPATH', timeout=timeout)
572 if not bcp:
573 Trace('Could not find bootclasspath')
574 return None
575 return bcp.strip().split(':') # strip trailing newline
576
577
578def RunDexoptOnFileList(ep, files, dest_root, move=False, timeout=0):
579 """Creates the corresponding .odex file for all jar/apk files in 'files'.
580 Copies the .odex file to a location under 'dest_root'. If 'move' is True,
581 the file is moved instead of copied.
582
583 Args:
584 ep: A subprocess.Popen object referring to the emulator process.
585 files: The list of files to optimize
586 dest_root: directory to copy/move odex files to. Must already exist.
587 move: if True, move rather than copy files
588 timeout: The number of seconds to wait for the command to complete,
589 or zero for no timeout.
590
591 Returns:
592 True on success, False on failure.
593 """
594 for jar_file in files:
595 if jar_file.endswith('.apk') or jar_file.endswith('.jar'):
596 odex_file = jar_file[:jar_file.rfind('.')] + '.odex'
597 cmd = 'dexopt-wrapper %s %s' % (jar_file, odex_file)
598 if not RunEmulatorCommand(ep, cmd, timeout=timeout):
599 Trace('"%s" failed' % cmd)
600 return False
601
602 # Always copy the odex file. There's no cp(1), so we
603 # cat out to the new file.
604 dst_odex = dest_root + odex_file
605 cmd = 'cat %s > %s' % (odex_file, dst_odex) # no cp(1)
606 if not RunEmulatorCommand(ep, cmd, timeout=timeout):
607 Trace('"%s" failed' % cmd)
608 return False
609
610 # Move it if we're asked to. We can't use mv(1) because
611 # the files tend to move between filesystems.
612 if move:
613 cmd = 'rm %s' % odex_file
614 if not RunEmulatorCommand(ep, cmd, timeout=timeout):
615 Trace('"%s" failed' % cmd)
616 return False
617 return True
618
619
620def InstallCacheFiles(cache_system_dir, out_system_dir):
621 """Install files in cache_system_dir to the proper places in out_system_dir.
622
623 cache_system_dir contains various files from /system, plus .odex files
624 for most of the .apk/.jar files that live there.
625 This function copies each .odex file from the cache dir to the output dir
626 and removes "classes.dex" from each appropriate .jar/.apk.
627
628 E.g., <cache_system_dir>/app/NotePad.odex would be copied to
629 <out_system_dir>/app/NotePad.odex, and <out_system_dir>/app/NotePad.apk
630 would have its classes.dex file removed.
631
632 Args:
633 cache_system_dir: The directory containing the cache files scraped from
634 the emulator.
635 out_system_dir: The local directory that corresponds to "/system"
636 on the device filesystem. (the root of system.img)
637
638 Returns:
639 True if everything succeeded, False if any problems occurred.
640 """
641 # First, walk through cache_system_dir and copy every .odex file
642 # over to out_system_dir, ensuring that the destination directory
643 # contains the corresponding source file.
644 for root, dirs, files in os.walk(cache_system_dir):
645 for name in files:
646 if name.endswith('.odex'):
647 odex_file = os.path.join(root, name)
648
649 # Find the path to the .odex file's source apk/jar file.
650 out_stem = odex_file[len(cache_system_dir):odex_file.rfind('.')]
651 out_stem = out_system_dir + out_stem;
652 jar_file = out_stem + '.jar'
653 if not os.path.exists(jar_file):
654 jar_file = out_stem + '.apk'
655 if not os.path.exists(jar_file):
656 Trace('Cannot find source .jar/.apk for %s: %s' %
657 (odex_file, out_stem + '.{jar,apk}'))
658 return False
659
660 # Copy the cache file next to the source file.
661 cmd = ['cp', odex_file, out_stem + '.odex']
662 ret = subprocess.call(cmd)
663 if ret: # non-zero exit status
664 Trace('%s failed' % ' '.join(cmd))
665 return False
666
667 # Walk through the output /system directory, making sure
668 # that every .jar/.apk has an odex file. While we do this,
669 # remove the classes.dex entry from each source archive.
670 for root, dirs, files in os.walk(out_system_dir):
671 for name in files:
672 if name.endswith('.apk') or name.endswith('.jar'):
673 jar_file = os.path.join(root, name)
674 odex_file = jar_file[:jar_file.rfind('.')] + '.odex'
675 if not os.path.exists(odex_file):
676 if root.endswith('/system/app') or root.endswith('/system/framework'):
677 Trace('jar/apk %s has no .odex file %s' % (jar_file, odex_file))
678 return False
679 else:
680 continue
681
682 # Attempting to dexopt a jar with no classes.dex currently
683 # creates a 40-byte odex file.
684 # TODO: use a more reliable check
685 if os.path.getsize(odex_file) > 100:
686 # Remove classes.dex from the .jar file.
687 cmd = ['zip', '-dq', jar_file, 'classes.dex']
688 ret = subprocess.call(cmd)
689 if ret: # non-zero exit status
690 Trace('"%s" failed' % ' '.join(cmd))
691 return False
692 else:
693 # Some of the apk files don't contain any code.
694 if not name.endswith('.apk'):
695 Trace('%s has a zero-length odex file' % jar_file)
696 return False
697 cmd = ['rm', odex_file]
698 ret = subprocess.call(cmd)
699 if ret: # non-zero exit status
700 Trace('"%s" failed' % ' '.join(cmd))
701 return False
702
703 return True
704
705
706def KillChildProcess(p, sig=signal.SIGTERM, timeout=0):
707 """Waits for a child process to die without getting stuck in wait().
708
709 After Jean Brouwers's 2004 post to python-list.
710
711 Args:
712 p: A subprocess.Popen representing the child process to kill.
713 sig: The signal to send to the child process.
714 timeout: How many seconds to wait for the child process to die.
715 If zero, do not time out.
716
717 Returns:
718 The exit status of the child process, if it was successfully killed.
719 The final value of p.returncode if it wasn't.
720 """
721 os.kill(p.pid, sig)
722 if timeout > 0:
723 while p.poll() < 0:
724 if timeout > 0.5:
725 timeout -= 0.25
726 time.sleep(0.25)
727 else:
728 os.kill(p.pid, signal.SIGKILL)
729 time.sleep(0.5)
730 p.poll()
731 break
732 else:
733 p.wait()
734 return p.returncode
735
736
737def Trace(msg):
738 """Prints a message to stdout.
739
740 Args:
741 msg: The message to print.
742 """
743 #print 'dexpreopt: %s' % msg
744 when = datetime.datetime.now()
745 print '%02d:%02d.%d dexpreopt: %s' % (when.minute, when.second, when.microsecond, msg)
746
747
748def KillEmulator():
749 """Attempts to kill the emulator process, if it is running.
750
751 Returns:
752 The exit status of the emulator process, or None if the emulator
753 was not running or was unable to be killed.
754 """
755 global _emulator_popen
756 if _emulator_popen:
757 Trace('Killing emulator')
758 try:
759 ret = KillChildProcess(_emulator_popen, sig=signal.SIGINT, timeout=5)
760 except OSError:
761 Trace('Could not kill emulator')
762 ret = None
763 _emulator_popen = None
764 return ret
765 return None
766
767
768def Fail(msg=None):
769 """Prints an error and causes the process to exit.
770
771 Args:
772 msg: Additional error string to print (optional).
773
774 Returns:
775 Does not return.
776 """
777 s = 'dexpreopt: ERROR'
778 if msg: s += ': %s' % msg
779 print >>sys.stderr, msg
780 KillEmulator()
781 sys.exit(1)
782
783
784def PrintUsage(msg=None):
785 """Prints commandline usage information for the tool and exits with an error.
786
787 Args:
788 msg: Additional string to print (optional).
789
790 Returns:
791 Does not return.
792 """
793 if msg:
794 print >>sys.stderr, 'dexpreopt: %s', msg
795 print >>sys.stderr, """Usage: dexpreopt <options>
796Required options:
797 -kernel <kernel file> Kernel to use when running the emulator
798 -ramdisk <ramdisk.img file> Ramdisk to use when running the emulator
799 -image <system.img file> System image to use when running the
800 emulator. /system/app should contain the
801 .apk files to optimize, and any required
802 bootclasspath libraries must be present
803 in the correct locations.
804 -system <path> The product directory, which usually contains
805 files like 'system.img' (files other than
806 the kernel in that directory won't
807 be used)
808 -outsystemdir <path> A fully-populated /system directory, ready
809 to be modified to contain the optimized
810 files. The appropriate .jar/.apk files
811 will be stripped of their classes.dex
812 entries, and the optimized .dex files
813 will be added alongside the packages
814 that they came from.
815Optional:
816 -tmpdir <path> If specified, use this directory for
817 intermediate objects. If not specified,
818 a unique directory under the system
819 temp dir is used.
820 """
821 sys.exit(2)
822
823
824def ParseArgs(argv):
825 """Parses commandline arguments.
826
827 Args:
828 argv: A list of arguments; typically sys.argv[1:]
829
830 Returns:
831 A tuple containing two dictionaries; the first contains arguments
832 that will be passsed to the emulator, and the second contains other
833 arguments.
834 """
835 parser = optparse.OptionParser()
836
837 parser.add_option('--kernel', help='Passed to emulator')
838 parser.add_option('--ramdisk', help='Passed to emulator')
839 parser.add_option('--image', help='Passed to emulator')
840 parser.add_option('--system', help='Passed to emulator')
841 parser.add_option('--outsystemdir', help='Destination /system directory')
842 parser.add_option('--tmpdir', help='Optional temp directory to use')
843
844 options, args = parser.parse_args(args=argv)
845 if args: PrintUsage()
846
847 emulator_args = {}
848 other_args = {}
849 if options.kernel: emulator_args['kernel'] = options.kernel
850 if options.ramdisk: emulator_args['ramdisk'] = options.ramdisk
851 if options.image: emulator_args['image'] = options.image
852 if options.system: emulator_args['system'] = options.system
853 if options.outsystemdir: other_args['outsystemdir'] = options.outsystemdir
854 if options.tmpdir: other_args['tmpdir'] = options.tmpdir
855
856 return (emulator_args, other_args)
857
858
859def DexoptEverything(ep, dest_root):
860 """Logic for finding and dexopting files in the necessary order.
861
862 Args:
863 ep: A subprocess.Popen object referring to the emulator process.
864 dest_root: directory to copy/move odex files to
865
866 Returns:
867 True on success, False on failure.
868 """
869 _extra_tests = False
870 if _extra_tests:
871 if not RunEmulatorCommand(ep, 'ls /system/app', timeout=5):
872 Fail('Could not ls')
873
874 # We're very short on space, so remove a bunch of big stuff that we
875 # don't need.
876 cmd = 'rm -r /system/sounds /system/media /system/fonts /system/xbin'
877 if not RunEmulatorCommand(ep, cmd, timeout=40):
878 Trace('"%s" failed' % cmd)
879 return False
880
881 Trace('Read file list')
882 jar_dirs = ['/system/framework', '/system/app']
883 files = ReadFileList(ep, jar_dirs, timeout=5)
884 if not files:
885 Fail('Could not list files in %s' % ' '.join(jar_dirs))
886 #Trace('File list:\n"""\n%s\n"""' % '\n'.join(files))
887
888 bcp = ReadBootClassPath(ep, timeout=2)
889 if not files:
890 Fail('Could not sort by bootclasspath')
891
892 # Remove bootclasspath entries from the main file list.
893 for jar in bcp:
894 try:
895 files.remove(jar)
896 except ValueError:
897 Trace('File list does not contain bootclasspath entry "%s"' % jar)
898 return False
899
900 # Create the destination directories.
901 for d in ['', '/system'] + jar_dirs:
902 cmd = 'mkdir %s%s' % (dest_root, d)
903 if not RunEmulatorCommand(ep, cmd, timeout=4):
904 Trace('"%s" failed' % cmd)
905 return False
906
907 # First, dexopt the bootclasspath. Keep their cache files in place.
908 Trace('Dexopt %d bootclasspath files' % len(bcp))
909 if not RunDexoptOnFileList(ep, bcp, dest_root, timeout=120):
910 Trace('Could not dexopt bootclasspath')
911 return False
912
913 # dexopt the rest. To avoid running out of space on the emulator
914 # volume, move each cache file after it's been created.
915 Trace('Dexopt %d files' % len(files))
916 if not RunDexoptOnFileList(ep, files, dest_root, move=True, timeout=120):
917 Trace('Could not dexopt files')
918 return False
919
920 if _extra_tests:
921 if not RunEmulatorCommand(ep, 'ls /system/app', timeout=5):
922 Fail('Could not ls')
923
924 return True
925
926
927
928def MainInternal():
929 """Main function that can be wrapped in a try block.
930
931 Returns:
932 Nothing.
933 """
934 emulator_args, other_args = ParseArgs(sys.argv[1:])
935
936 tmp_dir = EnsureTempDir(other_args.get('tmpdir'))
937 if not tmp_dir: Fail('Could not create temp dir')
938
939 Trace('Creating data image')
940 userdata = '%s/data.img' % tmp_dir
941 if not CreateZeroedFile(userdata, 32 * 1024 * 1024):
942 Fail('Could not create data image file')
943 emulator_args['userdata'] = userdata
944
945 ep = StartEmulator(**emulator_args)
946 if not ep: Fail('Could not start emulator')
947 global _emulator_popen
948 _emulator_popen = ep
949
950 # TODO: unlink the big userdata file now, since the emulator
951 # has it open.
952
953 if not WaitForEmulator(ep, timeout=20): Fail('Emulator did not respond')
954 if not ReplaceEmulatorPrompt(ep): Fail('Could not replace prompt')
955
956 dest_root = '/data/dexpreopt-root'
957 if not DexoptEverything(ep, dest_root): Fail('Could not dexopt files')
958
959 # Grab the odex files that were left in dest_root.
960 cache_system_dir = tmp_dir + '/cache-system'
961 if not DownloadDirectoryHierarchy(ep, dest_root + '/system',
962 cache_system_dir,
963 timeout=20):
964 Fail('Could not download %s/system from emulator' % dest_root)
965
966 if not InstallCacheFiles(cache_system_dir=cache_system_dir,
967 out_system_dir=other_args['outsystemdir']):
968 Fail('Could not install files')
969
970 Trace('dexpreopt successful')
971 # Success!
972
973
974def main():
975 try:
976 MainInternal()
977 finally:
978 KillEmulator()
979
980
981if __name__ == '__main__':
982 main()