OSDN Git Service

am 5729dbfa: am e6fed880: Merge "docs: accept the enter key on key-down, rather than...
[android-x86/build.git] / tools / dexpreopt / dexpreopt.py
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
20 A tool and associated functions to communicate with an Android
21 emulator instance, run commands, and scrape out files.
22
23 Requires at least python2.4.
24 """
25
26 import array
27 import datetime
28 import optparse
29 import os
30 import posix
31 import select
32 import signal
33 import struct
34 import subprocess
35 import sys
36 import tempfile
37 import time
38 import zlib
39
40
41 _emulator_popen = None
42 _DEBUG_READ = 1
43
44
45 def 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
67 def 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
89 def 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
164 def 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
177 def 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
197 def 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
213 def 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
289 def 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
310 def 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
347 def 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
379 def 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
410 def 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
434 def 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
559 def 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
578 def 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
620 def 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
706 def 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
737 def 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
748 def 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
768 def 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
784 def 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>
796 Required 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.
815 Optional:
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
824 def 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
859 def 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
928 def 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
974 def main():
975   try:
976     MainInternal()
977   finally:
978     KillEmulator()
979
980
981 if __name__ == '__main__':
982   main()