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