1#!/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 "-system". 99 userdata: If set, passed to the emulator as "-initdata" and "-data". 100 system: If set, passed to the emulator as "-sysdir". 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 += ['-system', image] 111 if userdata: args += ['-initdata', userdata, '-data', userdata] 112 if system: args += ['-sysdir', system] 113 args += ['-partition-size', '128'] 114 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() 983