blob: 8a80e062949e33b99182ec57f698d2bfd7057cb6 [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".
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 # Because of a difference in behavior of zlib.adler32 on 32-bit and 64-bit
524 # systems (one returns signed values, the other unsigned), we take the
525 # modulo 2**32 of the checksums, and compare those.
526 # See also http://bugs.python.org/issue1202
527 if (adler32 % (2**32)) != (data_adler32 % (2**32)):
528 Trace('adler32 does not match: calculated 0x%08x != expected 0x%08x' %
529 (data_adler32, adler32))
530 return False
531
532 # File end magic.
533 buf = array.array('B')
534 buf.fromfile(ep.stdout, 4)
535 if buf != FILE_END:
536 Trace('Unexpected file end magic "%s"' % buf)
537 return False
538
539 # Write to the output file
540 out_file_name = dest + '/' + file_name[len(src):]
541 p = os.path.dirname(out_file_name)
542 if not os.path.exists(p): os.makedirs(p)
543 fo = file(out_file_name, 'w+b')
544 fo.truncate(0)
545 Trace('FILE: Writing %d bytes to %s' % (len(data), out_file_name))
546 data.tofile(fo)
547 fo.close()
548
549 elif buf == END:
550 break
551 else:
552 Trace('Unexpected magic "%s"' % buf)
553 return False
554
555 return WaitForPrompt(ep, timeout=timeout, reset_on_activity=True)
556
557
558def ReadBootClassPath(ep, timeout=0):
559 """Reads and returns the default bootclasspath as a list of files.
560
561 Args:
562 ep: A subprocess.Popen object referring to the emulator process.
563 timeout: The number of seconds to wait for the command to complete,
564 or zero for no timeout.
565
566 Returns:
567 The bootclasspath as a list of strings.
568 None on failure.
569 """
570 bcp = RunEmulatorCommand(ep, 'echo $BOOTCLASSPATH', timeout=timeout)
571 if not bcp:
572 Trace('Could not find bootclasspath')
573 return None
574 return bcp.strip().split(':') # strip trailing newline
575
576
577def RunDexoptOnFileList(ep, files, dest_root, move=False, timeout=0):
578 """Creates the corresponding .odex file for all jar/apk files in 'files'.
579 Copies the .odex file to a location under 'dest_root'. If 'move' is True,
580 the file is moved instead of copied.
581
582 Args:
583 ep: A subprocess.Popen object referring to the emulator process.
584 files: The list of files to optimize
585 dest_root: directory to copy/move odex files to. Must already exist.
586 move: if True, move rather than copy files
587 timeout: The number of seconds to wait for the command to complete,
588 or zero for no timeout.
589
590 Returns:
591 True on success, False on failure.
592 """
593 for jar_file in files:
594 if jar_file.endswith('.apk') or jar_file.endswith('.jar'):
595 odex_file = jar_file[:jar_file.rfind('.')] + '.odex'
596 cmd = 'dexopt-wrapper %s %s' % (jar_file, odex_file)
597 if not RunEmulatorCommand(ep, cmd, timeout=timeout):
598 Trace('"%s" failed' % cmd)
599 return False
600
601 # Always copy the odex file. There's no cp(1), so we
602 # cat out to the new file.
603 dst_odex = dest_root + odex_file
604 cmd = 'cat %s > %s' % (odex_file, dst_odex) # no cp(1)
605 if not RunEmulatorCommand(ep, cmd, timeout=timeout):
606 Trace('"%s" failed' % cmd)
607 return False
608
609 # Move it if we're asked to. We can't use mv(1) because
610 # the files tend to move between filesystems.
611 if move:
612 cmd = 'rm %s' % odex_file
613 if not RunEmulatorCommand(ep, cmd, timeout=timeout):
614 Trace('"%s" failed' % cmd)
615 return False
616 return True
617
618
619def InstallCacheFiles(cache_system_dir, out_system_dir):
620 """Install files in cache_system_dir to the proper places in out_system_dir.
621
622 cache_system_dir contains various files from /system, plus .odex files
623 for most of the .apk/.jar files that live there.
624 This function copies each .odex file from the cache dir to the output dir
625 and removes "classes.dex" from each appropriate .jar/.apk.
626
627 E.g., <cache_system_dir>/app/NotePad.odex would be copied to
628 <out_system_dir>/app/NotePad.odex, and <out_system_dir>/app/NotePad.apk
629 would have its classes.dex file removed.
630
631 Args:
632 cache_system_dir: The directory containing the cache files scraped from
633 the emulator.
634 out_system_dir: The local directory that corresponds to "/system"
635 on the device filesystem. (the root of system.img)
636
637 Returns:
638 True if everything succeeded, False if any problems occurred.
639 """
640 # First, walk through cache_system_dir and copy every .odex file
641 # over to out_system_dir, ensuring that the destination directory
642 # contains the corresponding source file.
643 for root, dirs, files in os.walk(cache_system_dir):
644 for name in files:
645 if name.endswith('.odex'):
646 odex_file = os.path.join(root, name)
647
648 # Find the path to the .odex file's source apk/jar file.
649 out_stem = odex_file[len(cache_system_dir):odex_file.rfind('.')]
650 out_stem = out_system_dir + out_stem;
651 jar_file = out_stem + '.jar'
652 if not os.path.exists(jar_file):
653 jar_file = out_stem + '.apk'
654 if not os.path.exists(jar_file):
655 Trace('Cannot find source .jar/.apk for %s: %s' %
656 (odex_file, out_stem + '.{jar,apk}'))
657 return False
658
659 # Copy the cache file next to the source file.
660 cmd = ['cp', odex_file, out_stem + '.odex']
661 ret = subprocess.call(cmd)
662 if ret: # non-zero exit status
663 Trace('%s failed' % ' '.join(cmd))
664 return False
665
666 # Walk through the output /system directory, making sure
667 # that every .jar/.apk has an odex file. While we do this,
668 # remove the classes.dex entry from each source archive.
669 for root, dirs, files in os.walk(out_system_dir):
670 for name in files:
671 if name.endswith('.apk') or name.endswith('.jar'):
672 jar_file = os.path.join(root, name)
673 odex_file = jar_file[:jar_file.rfind('.')] + '.odex'
674 if not os.path.exists(odex_file):
675 if root.endswith('/system/app') or root.endswith('/system/framework'):
676 Trace('jar/apk %s has no .odex file %s' % (jar_file, odex_file))
677 return False
678 else:
679 continue
680
681 # Attempting to dexopt a jar with no classes.dex currently
682 # creates a 40-byte odex file.
683 # TODO: use a more reliable check
684 if os.path.getsize(odex_file) > 100:
685 # Remove classes.dex from the .jar file.
686 cmd = ['zip', '-dq', jar_file, 'classes.dex']
687 ret = subprocess.call(cmd)
688 if ret: # non-zero exit status
689 Trace('"%s" failed' % ' '.join(cmd))
690 return False
691 else:
692 # Some of the apk files don't contain any code.
693 if not name.endswith('.apk'):
694 Trace('%s has a zero-length odex file' % jar_file)
695 return False
696 cmd = ['rm', odex_file]
697 ret = subprocess.call(cmd)
698 if ret: # non-zero exit status
699 Trace('"%s" failed' % ' '.join(cmd))
700 return False
701
702 return True
703
704
705def KillChildProcess(p, sig=signal.SIGTERM, timeout=0):
706 """Waits for a child process to die without getting stuck in wait().
707
708 After Jean Brouwers's 2004 post to python-list.
709
710 Args:
711 p: A subprocess.Popen representing the child process to kill.
712 sig: The signal to send to the child process.
713 timeout: How many seconds to wait for the child process to die.
714 If zero, do not time out.
715
716 Returns:
717 The exit status of the child process, if it was successfully killed.
718 The final value of p.returncode if it wasn't.
719 """
720 os.kill(p.pid, sig)
721 if timeout > 0:
722 while p.poll() < 0:
723 if timeout > 0.5:
724 timeout -= 0.25
725 time.sleep(0.25)
726 else:
727 os.kill(p.pid, signal.SIGKILL)
728 time.sleep(0.5)
729 p.poll()
730 break
731 else:
732 p.wait()
733 return p.returncode
734
735
736def Trace(msg):
737 """Prints a message to stdout.
738
739 Args:
740 msg: The message to print.
741 """
742 #print 'dexpreopt: %s' % msg
743 when = datetime.datetime.now()
744 print '%02d:%02d.%d dexpreopt: %s' % (when.minute, when.second, when.microsecond, msg)
745
746
747def KillEmulator():
748 """Attempts to kill the emulator process, if it is running.
749
750 Returns:
751 The exit status of the emulator process, or None if the emulator
752 was not running or was unable to be killed.
753 """
754 global _emulator_popen
755 if _emulator_popen:
756 Trace('Killing emulator')
757 try:
758 ret = KillChildProcess(_emulator_popen, sig=signal.SIGINT, timeout=5)
759 except OSError:
760 Trace('Could not kill emulator')
761 ret = None
762 _emulator_popen = None
763 return ret
764 return None
765
766
767def Fail(msg=None):
768 """Prints an error and causes the process to exit.
769
770 Args:
771 msg: Additional error string to print (optional).
772
773 Returns:
774 Does not return.
775 """
776 s = 'dexpreopt: ERROR'
777 if msg: s += ': %s' % msg
778 print >>sys.stderr, msg
779 KillEmulator()
780 sys.exit(1)
781
782
783def PrintUsage(msg=None):
784 """Prints commandline usage information for the tool and exits with an error.
785
786 Args:
787 msg: Additional string to print (optional).
788
789 Returns:
790 Does not return.
791 """
792 if msg:
793 print >>sys.stderr, 'dexpreopt: %s', msg
794 print >>sys.stderr, """Usage: dexpreopt <options>
795Required options:
796 -kernel <kernel file> Kernel to use when running the emulator
797 -ramdisk <ramdisk.img file> Ramdisk to use when running the emulator
798 -image <system.img file> System image to use when running the
799 emulator. /system/app should contain the
800 .apk files to optimize, and any required
801 bootclasspath libraries must be present
802 in the correct locations.
803 -system <path> The product directory, which usually contains
804 files like 'system.img' (files other than
805 the kernel in that directory won't
806 be used)
807 -outsystemdir <path> A fully-populated /system directory, ready
808 to be modified to contain the optimized
809 files. The appropriate .jar/.apk files
810 will be stripped of their classes.dex
811 entries, and the optimized .dex files
812 will be added alongside the packages
813 that they came from.
814Optional:
815 -tmpdir <path> If specified, use this directory for
816 intermediate objects. If not specified,
817 a unique directory under the system
818 temp dir is used.
819 """
820 sys.exit(2)
821
822
823def ParseArgs(argv):
824 """Parses commandline arguments.
825
826 Args:
827 argv: A list of arguments; typically sys.argv[1:]
828
829 Returns:
830 A tuple containing two dictionaries; the first contains arguments
831 that will be passsed to the emulator, and the second contains other
832 arguments.
833 """
834 parser = optparse.OptionParser()
835
836 parser.add_option('--kernel', help='Passed to emulator')
837 parser.add_option('--ramdisk', help='Passed to emulator')
838 parser.add_option('--image', help='Passed to emulator')
839 parser.add_option('--system', help='Passed to emulator')
840 parser.add_option('--outsystemdir', help='Destination /system directory')
841 parser.add_option('--tmpdir', help='Optional temp directory to use')
842
843 options, args = parser.parse_args(args=argv)
844 if args: PrintUsage()
845
846 emulator_args = {}
847 other_args = {}
848 if options.kernel: emulator_args['kernel'] = options.kernel
849 if options.ramdisk: emulator_args['ramdisk'] = options.ramdisk
850 if options.image: emulator_args['image'] = options.image
851 if options.system: emulator_args['system'] = options.system
852 if options.outsystemdir: other_args['outsystemdir'] = options.outsystemdir
853 if options.tmpdir: other_args['tmpdir'] = options.tmpdir
854
855 return (emulator_args, other_args)
856
857
858def DexoptEverything(ep, dest_root):
859 """Logic for finding and dexopting files in the necessary order.
860
861 Args:
862 ep: A subprocess.Popen object referring to the emulator process.
863 dest_root: directory to copy/move odex files to
864
865 Returns:
866 True on success, False on failure.
867 """
868 _extra_tests = False
869 if _extra_tests:
870 if not RunEmulatorCommand(ep, 'ls /system/app', timeout=5):
871 Fail('Could not ls')
872
873 # We're very short on space, so remove a bunch of big stuff that we
874 # don't need.
875 cmd = 'rm -r /system/sounds /system/media /system/fonts /system/xbin'
876 if not RunEmulatorCommand(ep, cmd, timeout=40):
877 Trace('"%s" failed' % cmd)
878 return False
879
880 Trace('Read file list')
881 jar_dirs = ['/system/framework', '/system/app']
882 files = ReadFileList(ep, jar_dirs, timeout=5)
883 if not files:
884 Fail('Could not list files in %s' % ' '.join(jar_dirs))
885 #Trace('File list:\n"""\n%s\n"""' % '\n'.join(files))
886
887 bcp = ReadBootClassPath(ep, timeout=2)
888 if not files:
889 Fail('Could not sort by bootclasspath')
890
891 # Remove bootclasspath entries from the main file list.
892 for jar in bcp:
893 try:
894 files.remove(jar)
895 except ValueError:
896 Trace('File list does not contain bootclasspath entry "%s"' % jar)
897 return False
898
899 # Create the destination directories.
900 for d in ['', '/system'] + jar_dirs:
901 cmd = 'mkdir %s%s' % (dest_root, d)
902 if not RunEmulatorCommand(ep, cmd, timeout=4):
903 Trace('"%s" failed' % cmd)
904 return False
905
906 # First, dexopt the bootclasspath. Keep their cache files in place.
907 Trace('Dexopt %d bootclasspath files' % len(bcp))
908 if not RunDexoptOnFileList(ep, bcp, dest_root, timeout=120):
909 Trace('Could not dexopt bootclasspath')
910 return False
911
912 # dexopt the rest. To avoid running out of space on the emulator
913 # volume, move each cache file after it's been created.
914 Trace('Dexopt %d files' % len(files))
915 if not RunDexoptOnFileList(ep, files, dest_root, move=True, timeout=120):
916 Trace('Could not dexopt files')
917 return False
918
919 if _extra_tests:
920 if not RunEmulatorCommand(ep, 'ls /system/app', timeout=5):
921 Fail('Could not ls')
922
923 return True
924
925
926
927def MainInternal():
928 """Main function that can be wrapped in a try block.
929
930 Returns:
931 Nothing.
932 """
933 emulator_args, other_args = ParseArgs(sys.argv[1:])
934
935 tmp_dir = EnsureTempDir(other_args.get('tmpdir'))
936 if not tmp_dir: Fail('Could not create temp dir')
937
938 Trace('Creating data image')
939 userdata = '%s/data.img' % tmp_dir
940 if not CreateZeroedFile(userdata, 32 * 1024 * 1024):
941 Fail('Could not create data image file')
942 emulator_args['userdata'] = userdata
943
944 ep = StartEmulator(**emulator_args)
945 if not ep: Fail('Could not start emulator')
946 global _emulator_popen
947 _emulator_popen = ep
948
949 # TODO: unlink the big userdata file now, since the emulator
950 # has it open.
951
952 if not WaitForEmulator(ep, timeout=20): Fail('Emulator did not respond')
953 if not ReplaceEmulatorPrompt(ep): Fail('Could not replace prompt')
954
955 dest_root = '/data/dexpreopt-root'
956 if not DexoptEverything(ep, dest_root): Fail('Could not dexopt files')
957
958 # Grab the odex files that were left in dest_root.
959 cache_system_dir = tmp_dir + '/cache-system'
960 if not DownloadDirectoryHierarchy(ep, dest_root + '/system',
961 cache_system_dir,
962 timeout=20):
963 Fail('Could not download %s/system from emulator' % dest_root)
964
965 if not InstallCacheFiles(cache_system_dir=cache_system_dir,
966 out_system_dir=other_args['outsystemdir']):
967 Fail('Could not install files')
968
969 Trace('dexpreopt successful')
970 # Success!
971
972
973def main():
974 try:
975 MainInternal()
976 finally:
977 KillEmulator()
978
979
980if __name__ == '__main__':
981 main()