auto import from //depot/cupcake/@135843
diff --git a/tools/dexpreopt/Android.mk b/tools/dexpreopt/Android.mk
new file mode 100644
index 0000000..40aeee2
--- /dev/null
+++ b/tools/dexpreopt/Android.mk
@@ -0,0 +1,38 @@
+#
+# Copyright (C) 2008 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+ifneq ($(TARGET_SIMULATOR),true)
+ifneq ($(DISABLE_DEXPREOPT),true)
+
+LOCAL_PATH := $(my-dir)
+include $(CLEAR_VARS)
+LOCAL_PREBUILT_EXECUTABLES := dexpreopt.py
+include $(BUILD_SYSTEM)/host_prebuilt.mk
+DEXPREOPT := $(LOCAL_INSTALLED_MODULE)
+
+# The script uses some other tools; make sure that they're
+# installed along with it.
+tools := \
+ emulator$(HOST_EXECUTABLE_SUFFIX)
+
+$(DEXPREOPT): | $(addprefix $(HOST_OUT_EXECUTABLES)/,$(tools))
+
+subdir_makefiles := \
+ $(LOCAL_PATH)/dexopt-wrapper/Android.mk \
+ $(LOCAL_PATH)/afar/Android.mk
+include $(subdir_makefiles)
+
+endif # !disable_dexpreopt
+endif # !sim
diff --git a/tools/dexpreopt/Config.mk b/tools/dexpreopt/Config.mk
new file mode 100644
index 0000000..c6639b2
--- /dev/null
+++ b/tools/dexpreopt/Config.mk
@@ -0,0 +1,146 @@
+#
+# Copyright (C) 2008 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# Included by config/Makefile.
+# Defines the pieces necessary for the dexpreopt process.
+#
+# inputs: INSTALLED_RAMDISK_TARGET, BUILT_SYSTEMIMAGE_UNOPT
+# outputs: BUILT_SYSTEMIMAGE, SYSTEMIMAGE_SOURCE_DIR
+#
+LOCAL_PATH := $(my-dir)
+
+# TODO: see if we can make the .odex files not be product-specific.
+# They can't be completely common, though, because their format
+# depends on the architecture of the target system; ARM and x86
+# would have different versions.
+intermediates := \
+ $(call intermediates-dir-for,PACKAGING,dexpreopt)
+dexpreopt_system_dir := $(intermediates)/system
+built_afar := $(call intermediates-dir-for,EXECUTABLES,afar)/afar
+built_dowrapper := \
+ $(call intermediates-dir-for,EXECUTABLES,dexopt-wrapper)/dexopt-wrapper
+
+# Generate a stripped-down init.rc based on the real one.
+dexpreopt_initrc := $(intermediates)/etc/init.rc
+geninitrc_script := $(LOCAL_PATH)/geninitrc.awk
+$(dexpreopt_initrc): script := $(geninitrc_script)
+$(dexpreopt_initrc): system/core/rootdir/init.rc $(geninitrc_script)
+ @echo "Dexpreopt init.rc: $@"
+ @mkdir -p $(dir $@)
+ $(hide) awk -f $(script) < $< > $@
+
+BUILT_DEXPREOPT_RAMDISK := $(intermediates)/ramdisk.img
+$(BUILT_DEXPREOPT_RAMDISK): intermediates := $(intermediates)
+$(BUILT_DEXPREOPT_RAMDISK): dexpreopt_root_out := $(intermediates)/root
+$(BUILT_DEXPREOPT_RAMDISK): dexpreopt_initrc := $(dexpreopt_initrc)
+$(BUILT_DEXPREOPT_RAMDISK): built_afar := $(built_afar)
+$(BUILT_DEXPREOPT_RAMDISK): built_dowrapper := $(built_dowrapper)
+$(BUILT_DEXPREOPT_RAMDISK): \
+ $(INSTALLED_RAMDISK_TARGET) \
+ $(dexpreopt_initrc) \
+ $(built_afar) \
+ $(built_dowrapper) \
+ | $(MKBOOTFS) $(ACP)
+$(BUILT_DEXPREOPT_RAMDISK):
+ @echo "Dexpreopt ramdisk: $@"
+ $(hide) rm -f $@
+ $(hide) rm -rf $(dexpreopt_root_out)
+ $(hide) mkdir -p $(dexpreopt_root_out)
+ $(hide) $(ACP) -rd $(TARGET_ROOT_OUT) $(intermediates)
+ $(hide) $(ACP) -f $(dexpreopt_initrc) $(dexpreopt_root_out)/
+ $(hide) $(ACP) $(built_afar) $(dexpreopt_root_out)/sbin/
+ $(hide) $(ACP) $(built_dowrapper) $(dexpreopt_root_out)/sbin/
+ $(MKBOOTFS) $(dexpreopt_root_out) | gzip > $@
+
+sign_dexpreopt := true
+ifdef sign_dexpreopt
+ # Such a huge hack. We need to re-sign the .apks with the
+ # same certs that they were originally signed with.
+ dexpreopt_package_certs_file := $(intermediates)/package-certs
+ $(shell mkdir -p $(intermediates))
+ $(shell rm -f $(dexpreopt_package_certs_file))
+ $(foreach p,$(PACKAGES),\
+ $(shell echo "$(p) $(PACKAGES.$(p).CERTIFICATE) $(PACKAGES.$(p).PRIVATE_KEY)" >> $(dexpreopt_package_certs_file)))
+endif
+
+# Build an optimized image from the unoptimized image
+BUILT_DEXPREOPT_SYSTEMIMAGE := $(intermediates)/system.img
+$(BUILT_DEXPREOPT_SYSTEMIMAGE): $(BUILT_SYSTEMIMAGE_UNOPT)
+$(BUILT_DEXPREOPT_SYSTEMIMAGE): $(BUILT_DEXPREOPT_RAMDISK)
+$(BUILT_DEXPREOPT_SYSTEMIMAGE): | $(DEXPREOPT) $(ACP) $(ZIPALIGN)
+$(BUILT_DEXPREOPT_SYSTEMIMAGE): SYSTEM_DIR := $(dexpreopt_system_dir)
+$(BUILT_DEXPREOPT_SYSTEMIMAGE): DEXPREOPT_TMP := $(intermediates)/emutmp
+ifdef sign_dexpreopt
+$(BUILT_DEXPREOPT_SYSTEMIMAGE): | $(SIGNAPK_JAR)
+endif
+$(BUILT_DEXPREOPT_SYSTEMIMAGE):
+ @rm -f $@
+ @echo "dexpreopt: copy system to $(SYSTEM_DIR)"
+ @rm -rf $(SYSTEM_DIR)
+ @mkdir -p $(dir $(SYSTEM_DIR))
+ $(hide) $(ACP) -rd $(TARGET_OUT) $(SYSTEM_DIR)
+ @echo "dexpreopt: optimize dex files"
+ @rm -rf $(DEXPREOPT_TMP)
+ @mkdir -p $(DEXPREOPT_TMP)
+ $(hide) \
+ PATH=$(HOST_OUT_EXECUTABLES):$$PATH \
+ $(DEXPREOPT) \
+ --kernel prebuilt/android-arm/kernel/kernel-qemu \
+ --ramdisk $(BUILT_DEXPREOPT_RAMDISK) \
+ --image $(BUILT_SYSTEMIMAGE_UNOPT) \
+ --system $(PRODUCT_OUT) \
+ --tmpdir $(DEXPREOPT_TMP) \
+ --outsystemdir $(SYSTEM_DIR)
+ifdef sign_dexpreopt
+ @echo "dexpreopt: re-sign apk files"
+ $(hide) \
+ export PATH=$(HOST_OUT_EXECUTABLES):$$PATH; \
+ for apk in $(SYSTEM_DIR)/app/*.apk; do \
+ packageName=`basename $$apk`; \
+ packageName=`echo $$packageName | sed -e 's/.apk$$//'`; \
+ cert=`grep "^$$packageName " $(dexpreopt_package_certs_file) | \
+ awk '{print $$2}'`; \
+ pkey=`grep "^$$packageName " $(dexpreopt_package_certs_file) | \
+ awk '{print $$3}'`; \
+ if [ "$$cert" -a "$$pkey" ]; then \
+ echo "dexpreopt: re-sign app/"$$packageName".apk"; \
+ tmpApk=$$apk~; \
+ rm -f $$tmpApk; \
+ java -jar $(SIGNAPK_JAR) $$cert $$pkey $$apk $$tmpApk || \
+ exit 11; \
+ mv -f $$tmpApk $$apk; \
+ else \
+ echo "dexpreopt: no keys for app/"$$packageName".apk"; \
+ rm $(SYSTEM_DIR)/app/$$packageName.* && \
+ cp $(TARGET_OUT)/app/$$packageName.apk \
+ $(SYSTEM_DIR)/app || exit 12; \
+ fi; \
+ tmpApk=$$apk~; \
+ rm -f $$tmpApk; \
+ $(ZIPALIGN) -f 4 $$apk $$tmpApk || exit 13; \
+ mv -f $$tmpApk $$apk; \
+ done
+endif
+ @echo "Dexpreopt system image: $@"
+ $(hide) $(MKYAFFS2) -f $(SYSTEM_DIR) $@
+
+.PHONY: dexpreoptimage
+dexpreoptimage: $(BUILT_DEXPREOPT_SYSTEMIMAGE)
+
+# Tell our caller to use the optimized systemimage
+BUILT_SYSTEMIMAGE := $(BUILT_DEXPREOPT_SYSTEMIMAGE)
+SYSTEMIMAGE_SOURCE_DIR := $(dexpreopt_system_dir)
diff --git a/tools/dexpreopt/afar/Android.mk b/tools/dexpreopt/afar/Android.mk
new file mode 100644
index 0000000..d224675
--- /dev/null
+++ b/tools/dexpreopt/afar/Android.mk
@@ -0,0 +1,29 @@
+#
+# Copyright (C) 2008 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+LOCAL_PATH := $(my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ main.c
+
+# Just for adler32()
+LOCAL_C_INCLUDES := external/zlib
+LOCAL_SHARED_LIBRARIES := libz
+
+LOCAL_MODULE := afar
+LOCAL_MODULE_TAGS := tests
+
+include $(BUILD_EXECUTABLE)
diff --git a/tools/dexpreopt/afar/main.c b/tools/dexpreopt/afar/main.c
new file mode 100644
index 0000000..d66d59c
--- /dev/null
+++ b/tools/dexpreopt/afar/main.c
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+
+#include <stdarg.h>
+#include <fcntl.h>
+#include <termios.h>
+
+#include <zlib.h> // for adler32()
+
+static int verbose = 0;
+
+/*
+ * Android File Archive format:
+ *
+ * magic[5]: 'A' 'F' 'A' 'R' '\n'
+ * version[4]: 0x00 0x00 0x00 0x01
+ * for each file:
+ * file magic[4]: 'F' 'I' 'L' 'E'
+ * namelen[4]: Length of file name, including NUL byte (big-endian)
+ * name[*]: NUL-terminated file name
+ * datalen[4]: Length of file (big-endian)
+ * data[*]: Unencoded file data
+ * adler32[4]: adler32 of the unencoded file data (big-endian)
+ * file end magic[4]: 'f' 'i' 'l' 'e'
+ * end magic[4]: 'E' 'N' 'D' 0x00
+ *
+ * This format is about as simple as possible; it was designed to
+ * make it easier to transfer multiple files over an stdin/stdout
+ * pipe to another process, so word-alignment wasn't necessary.
+ */
+
+static void
+die(const char *why, ...)
+{
+ va_list ap;
+
+ va_start(ap, why);
+ fprintf(stderr, "error: ");
+ vfprintf(stderr, why, ap);
+ fprintf(stderr, "\n");
+ va_end(ap);
+ exit(1);
+}
+
+static void
+write_big_endian(size_t v)
+{
+ putchar((v >> 24) & 0xff);
+ putchar((v >> 16) & 0xff);
+ putchar((v >> 8) & 0xff);
+ putchar( v & 0xff);
+}
+
+static void
+_eject(struct stat *s, char *out, int olen, char *data, size_t datasize)
+{
+ unsigned long adler;
+
+ /* File magic.
+ */
+ printf("FILE");
+
+ /* Name length includes the NUL byte.
+ */
+ write_big_endian(olen + 1);
+
+ /* File name and terminating NUL.
+ */
+ printf("%s", out);
+ putchar('\0');
+
+ /* File length.
+ */
+ write_big_endian(datasize);
+
+ /* File data.
+ */
+ if (fwrite(data, 1, datasize, stdout) != datasize) {
+ die("Error writing file data");
+ }
+
+ /* Checksum.
+ */
+ adler = adler32(0, NULL, 0);
+ adler = adler32(adler, (unsigned char *)data, datasize);
+ write_big_endian(adler);
+
+ /* File end magic.
+ */
+ printf("file");
+}
+
+static void _archive(char *in, int ilen);
+
+static void
+_archive_dir(char *in, int ilen)
+{
+ int t;
+ DIR *d;
+ struct dirent *de;
+
+ if (verbose) {
+ fprintf(stderr, "_archive_dir('%s', %d)\n", in, ilen);
+ }
+
+ d = opendir(in);
+ if (d == 0) {
+ die("cannot open directory '%s'", in);
+ }
+
+ while ((de = readdir(d)) != 0) {
+ /* xxx: feature? maybe some dotfiles are okay */
+ if (strcmp(de->d_name, ".") == 0 ||
+ strcmp(de->d_name, "..") == 0)
+ {
+ continue;
+ }
+
+ t = strlen(de->d_name);
+ in[ilen] = '/';
+ memcpy(in + ilen + 1, de->d_name, t + 1);
+
+ _archive(in, ilen + t + 1);
+
+ in[ilen] = '\0';
+ }
+}
+
+static void
+_archive(char *in, int ilen)
+{
+ struct stat s;
+
+ if (verbose) {
+ fprintf(stderr, "_archive('%s', %d)\n", in, ilen);
+ }
+
+ if (lstat(in, &s)) {
+ die("could not stat '%s'\n", in);
+ }
+
+ if (S_ISREG(s.st_mode)) {
+ char *tmp;
+ int fd;
+
+ fd = open(in, O_RDONLY);
+ if (fd < 0) {
+ die("cannot open '%s' for read", in);
+ }
+
+ tmp = (char*) malloc(s.st_size);
+ if (tmp == 0) {
+ die("cannot allocate %d bytes", s.st_size);
+ }
+
+ if (read(fd, tmp, s.st_size) != s.st_size) {
+ die("cannot read %d bytes", s.st_size);
+ }
+
+ _eject(&s, in, ilen, tmp, s.st_size);
+
+ free(tmp);
+ close(fd);
+ } else if (S_ISDIR(s.st_mode)) {
+ _archive_dir(in, ilen);
+ } else {
+ /* We don't handle links, etc. */
+ die("Unknown '%s' (mode %d)?\n", in, s.st_mode);
+ }
+}
+
+void archive(const char *start)
+{
+ char in[8192];
+
+ strcpy(in, start);
+
+ _archive_dir(in, strlen(in));
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct termios old_termios;
+
+ if (argc == 1) {
+ die("usage: %s <dir-list>", argv[0]);
+ }
+ argc--;
+ argv++;
+
+ /* Force stdout into raw mode.
+ */
+ struct termios s;
+ if (tcgetattr(1, &s) < 0) {
+ die("Could not get termios for stdout");
+ }
+ old_termios = s;
+ cfmakeraw(&s);
+ if (tcsetattr(1, TCSANOW, &s) < 0) {
+ die("Could not set termios for stdout");
+ }
+
+ /* Print format magic and version.
+ */
+ printf("AFAR\n");
+ write_big_endian(1);
+
+ while (argc-- > 0) {
+ archive(*argv++);
+ }
+
+ /* Print end magic.
+ */
+ printf("END");
+ putchar('\0');
+
+ /* Restore stdout.
+ */
+ if (tcsetattr(1, TCSANOW, &old_termios) < 0) {
+ die("Could not restore termios for stdout");
+ }
+
+ return 0;
+}
diff --git a/tools/dexpreopt/dexopt-wrapper/Android.mk b/tools/dexpreopt/dexopt-wrapper/Android.mk
new file mode 100644
index 0000000..e6ca389
--- /dev/null
+++ b/tools/dexpreopt/dexopt-wrapper/Android.mk
@@ -0,0 +1,36 @@
+#
+# Copyright (C) 2008 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ DexOptWrapper.cpp
+
+LOCAL_C_INCLUDES += \
+ dalvik
+
+LOCAL_STATIC_LIBRARIES := \
+ libdex
+
+LOCAL_SHARED_LIBRARIES := \
+ libcutils
+
+LOCAL_MODULE := dexopt-wrapper
+
+LOCAL_MODULE_TAGS := tests
+
+include $(BUILD_EXECUTABLE)
diff --git a/tools/dexpreopt/dexopt-wrapper/DexOptWrapper.cpp b/tools/dexpreopt/dexopt-wrapper/DexOptWrapper.cpp
new file mode 100644
index 0000000..fde2d08
--- /dev/null
+++ b/tools/dexpreopt/dexopt-wrapper/DexOptWrapper.cpp
@@ -0,0 +1,173 @@
+/*
+ * dexopt invocation test.
+ *
+ * You must have BOOTCLASSPATH defined. On the simulator, you will also
+ * need ANDROID_ROOT.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/file.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "cutils/properties.h"
+
+//using namespace android;
+
+/*
+ * Privilege reduction function.
+ *
+ * Returns 0 on success, nonzero on failure.
+ */
+static int privFunc(void)
+{
+ printf("--- would reduce privs here\n");
+ return 0;
+}
+
+/*
+ * We're in the child process. exec dexopt.
+ */
+static void runDexopt(int zipFd, int odexFd, const char* inputFileName)
+{
+ static const char* kDexOptBin = "/bin/dexopt";
+ static const int kMaxIntLen = 12; // '-'+10dig+'\0' -OR- 0x+8dig
+ char zipNum[kMaxIntLen];
+ char odexNum[kMaxIntLen];
+ char dexoptFlags[PROPERTY_VALUE_MAX];
+ const char* androidRoot;
+ char* execFile;
+
+ /* pull optional configuration tweaks out of properties */
+ property_get("dalvik.vm.dexopt-flags", dexoptFlags, "");
+
+ /* find dexopt executable; this exists for simulator compatibility */
+ androidRoot = getenv("ANDROID_ROOT");
+ if (androidRoot == NULL)
+ androidRoot = "/system";
+ execFile = (char*) malloc(strlen(androidRoot) + strlen(kDexOptBin) +1);
+ sprintf(execFile, "%s%s", androidRoot, kDexOptBin);
+
+ sprintf(zipNum, "%d", zipFd);
+ sprintf(odexNum, "%d", odexFd);
+
+ execl(execFile, execFile, "--zip", zipNum, odexNum, inputFileName,
+ dexoptFlags, (char*) NULL);
+ fprintf(stderr, "execl(%s) failed: %s\n", kDexOptBin, strerror(errno));
+}
+
+/*
+ * Run dexopt on the specified Jar/APK.
+ *
+ * This uses fork() and exec() to mimic the way this would work in an
+ * installer; in practice for something this simple you could just exec()
+ * unless you really wanted the status messages.
+ *
+ * Returns 0 on success.
+ */
+int doStuff(const char* zipName, const char* odexName)
+{
+ int zipFd, odexFd;
+
+ /*
+ * Open the zip archive and the odex file, creating the latter (and
+ * failing if it already exists). This must be done while we still
+ * have sufficient privileges to read the source file and create a file
+ * in the target directory. The "classes.dex" file will be extracted.
+ */
+ zipFd = open(zipName, O_RDONLY, 0);
+ if (zipFd < 0) {
+ fprintf(stderr, "Unable to open '%s': %s\n", zipName, strerror(errno));
+ return 1;
+ }
+
+ odexFd = open(odexName, O_RDWR | O_CREAT | O_EXCL, 0644);
+ if (odexFd < 0) {
+ fprintf(stderr, "Unable to create '%s': %s\n",
+ odexName, strerror(errno));
+ close(zipFd);
+ return 1;
+ }
+
+ printf("--- BEGIN '%s' (bootstrap=%d) ---\n", zipName, 0);
+
+ /*
+ * Fork a child process.
+ */
+ pid_t pid = fork();
+ if (pid == 0) {
+ /* child -- drop privs */
+ if (privFunc() != 0)
+ exit(66);
+
+ /* lock the input file */
+ if (flock(odexFd, LOCK_EX | LOCK_NB) != 0) {
+ fprintf(stderr, "Unable to lock '%s': %s\n",
+ odexName, strerror(errno));
+ exit(65);
+ }
+
+ runDexopt(zipFd, odexFd, zipName); /* does not return */
+ exit(67); /* usually */
+ } else {
+ /* parent -- wait for child to finish */
+ printf("waiting for verify+opt, pid=%d\n", (int) pid);
+ int status, oldStatus;
+ pid_t gotPid;
+
+ close(zipFd);
+ close(odexFd);
+
+ /*
+ * Wait for the optimization process to finish.
+ */
+ while (true) {
+ gotPid = waitpid(pid, &status, 0);
+ if (gotPid == -1 && errno == EINTR) {
+ printf("waitpid interrupted, retrying\n");
+ } else {
+ break;
+ }
+ }
+ if (gotPid != pid) {
+ fprintf(stderr, "waitpid failed: wanted %d, got %d: %s\n",
+ (int) pid, (int) gotPid, strerror(errno));
+ return 1;
+ }
+
+ if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
+ printf("--- END '%s' (success) ---\n", zipName);
+ return 0;
+ } else {
+ printf("--- END '%s' --- status=0x%04x, process failed\n",
+ zipName, status);
+ return 1;
+ }
+ }
+
+ /* notreached */
+}
+
+/*
+ * Parse args, do stuff.
+ */
+int main(int argc, char** argv)
+{
+ if (argc < 3 || argc > 4) {
+ fprintf(stderr, "Usage: %s <input jar/apk> <output odex> "
+ "[<bootclasspath>]\n\n", argv[0]);
+ fprintf(stderr, "Example: dexopttest "
+ "/system/app/NotePad.apk /system/app/NotePad.odex\n");
+ return 2;
+ }
+
+ if (argc > 3) {
+ setenv("BOOTCLASSPATH", argv[3], 1);
+ }
+
+ return (doStuff(argv[1], argv[2]) != 0);
+}
diff --git a/tools/dexpreopt/dexpreopt.py b/tools/dexpreopt/dexpreopt.py
new file mode 100755
index 0000000..8a80e06
--- /dev/null
+++ b/tools/dexpreopt/dexpreopt.py
@@ -0,0 +1,981 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2008 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the 'License');
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""Creates optimized versions of APK files.
+
+A tool and associated functions to communicate with an Android
+emulator instance, run commands, and scrape out files.
+
+Requires at least python2.4.
+"""
+
+import array
+import datetime
+import optparse
+import os
+import posix
+import select
+import signal
+import struct
+import subprocess
+import sys
+import tempfile
+import time
+import zlib
+
+
+_emulator_popen = None
+_DEBUG_READ = 1
+
+
+def EnsureTempDir(path=None):
+ """Creates a temporary directory and returns its path.
+
+ Creates any necessary parent directories.
+
+ Args:
+ path: If specified, used as the temporary directory. If not specified,
+ a safe temporary path is created. The caller is responsible for
+ deleting the directory.
+
+ Returns:
+ The path to the new directory, or None if a problem occurred.
+ """
+ if path is None:
+ path = tempfile.mkdtemp('', 'dexpreopt-')
+ elif not os.path.exists(path):
+ os.makedirs(path)
+ elif not os.path.isdir(path):
+ return None
+ return path
+
+
+def CreateZeroedFile(path, length):
+ """Creates the named file and writes <length> zero bytes to it.
+
+ Unlinks the file first if it already exists.
+ Creates its containing directory if necessary.
+
+ Args:
+ path: The path to the file to create.
+ length: The number of zero bytes to write to the file.
+
+ Returns:
+ True on success.
+ """
+ subprocess.call(['rm', '-f', path])
+ d = os.path.dirname(path)
+ if d and not os.path.exists(d): os.makedirs(os.path.dirname(d))
+ # TODO: redirect child's stdout to /dev/null
+ ret = subprocess.call(['dd', 'if=/dev/zero', 'of=%s' % path,
+ 'bs=%d' % length, 'count=1'])
+ return not ret # i.e., ret == 0; i.e., the child exited successfully.
+
+
+def StartEmulator(exe_name='emulator', kernel=None,
+ ramdisk=None, image=None, userdata=None, system=None):
+ """Runs the emulator with the specified arguments.
+
+ Args:
+ exe_name: The name of the emulator to run. May be absolute, relative,
+ or unqualified (and left to exec() to find).
+ kernel: If set, passed to the emulator as "-kernel".
+ ramdisk: If set, passed to the emulator as "-ramdisk".
+ image: If set, passed to the emulator as "-image".
+ userdata: If set, passed to the emulator as "-initdata" and "-data".
+ system: If set, passed to the emulator as "-system".
+
+ Returns:
+ A subprocess.Popen that refers to the emulator process, or None if
+ a problem occurred.
+ """
+ #exe_name = './stuff'
+ args = [exe_name]
+ if kernel: args += ['-kernel', kernel]
+ if ramdisk: args += ['-ramdisk', ramdisk]
+ if image: args += ['-image', image]
+ if userdata: args += ['-initdata', userdata, '-data', userdata]
+ if system: args += ['-system', system]
+ args += ['-no-window', '-netfast', '-noaudio']
+
+ _USE_PIPE = True
+
+ if _USE_PIPE:
+ # Use dedicated fds instead of stdin/out to talk to the
+ # emulator so that the emulator doesn't try to tty-cook
+ # the data.
+ em_stdin_r, em_stdin_w = posix.pipe()
+ em_stdout_r, em_stdout_w = posix.pipe()
+ args += ['-shell-serial', 'fdpair:%d:%d' % (em_stdin_r, em_stdout_w)]
+ else:
+ args += ['-shell']
+
+ # Ensure that this environment variable isn't set;
+ # if it is, the emulator will print the log to stdout.
+ if os.environ.get('ANDROID_LOG_TAGS'):
+ del os.environ['ANDROID_LOG_TAGS']
+
+ try:
+ # bufsize=1 line-buffered, =0 unbuffered,
+ # <0 system default (fully buffered)
+ Trace('Running emulator: %s' % ' '.join(args))
+ if _USE_PIPE:
+ ep = subprocess.Popen(args)
+ else:
+ ep = subprocess.Popen(args, close_fds=True,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ if ep:
+ if _USE_PIPE:
+ # Hijack the Popen.stdin/.stdout fields to point to our
+ # pipes. These are the same fields that would have been set
+ # if we called Popen() with stdin=subprocess.PIPE, etc.
+ # Note that these names are from the point of view of the
+ # child process.
+ #
+ # Since we'll be using select.select() to read data a byte
+ # at a time, it's important that these files are unbuffered
+ # (bufsize=0). If Popen() took care of the pipes, they're
+ # already unbuffered.
+ ep.stdin = os.fdopen(em_stdin_w, 'w', 0)
+ ep.stdout = os.fdopen(em_stdout_r, 'r', 0)
+ return ep
+ except OSError, e:
+ print >>sys.stderr, 'Could not start emulator:', e
+ return None
+
+
+def IsDataAvailable(fo, timeout=0):
+ """Indicates whether or not data is available to be read from a file object.
+
+ Args:
+ fo: A file object to read from.
+ timeout: The number of seconds to wait for data, or zero for no timeout.
+
+ Returns:
+ True iff data is available to be read.
+ """
+ return select.select([fo], [], [], timeout) == ([fo], [], [])
+
+
+def ConsumeAvailableData(fo):
+ """Reads data from a file object while it's available.
+
+ Stops when no more data is immediately available or upon reaching EOF.
+
+ Args:
+ fo: A file object to read from.
+
+ Returns:
+ An unsigned byte array.array of the data that was read.
+ """
+ buf = array.array('B')
+ while IsDataAvailable(fo):
+ try:
+ buf.fromfile(fo, 1)
+ except EOFError:
+ break
+ return buf
+
+
+def ShowTimeout(timeout, end_time):
+ """For debugging, display the timeout info.
+
+ Args:
+ timeout: the timeout in seconds.
+ end_time: a time.time()-based value indicating when the timeout should
+ expire.
+ """
+ if _DEBUG_READ:
+ if timeout:
+ remaining = end_time - time.time()
+ Trace('ok, time remaining %.1f of %.1f' % (remaining, timeout))
+ else:
+ Trace('ok (no timeout)')
+
+
+def WaitForString(inf, pattern, timeout=0, max_len=0, eat_to_eol=True,
+ reset_on_activity=False):
+ """Reads from a file object and returns when the pattern matches the data.
+
+ Reads a byte at a time to avoid consuming extra data, so do not call
+ this function when you expect the pattern to match a large amount of data.
+
+ Args:
+ inf: The file object to read from.
+ pattern: The string to look for in the input data.
+ May be a tuple of strings.
+ timeout: How long to wait, in seconds. No timeout if it evaluates to False.
+ max_len: Return None if this many bytes have been read without matching.
+ No upper bound if it evaluates to False.
+ eat_to_eol: If true, the input data will be consumed until a '\\n' or EOF
+ is encountered.
+ reset_on_activity: If True, reset the timeout whenever a character is
+ read.
+
+ Returns:
+ The input data matching the expression as an unsigned char array,
+ or None if the operation timed out or didn't match after max_len bytes.
+
+ Raises:
+ IOError: An error occurred reading from the input file.
+ """
+ if timeout:
+ end_time = time.time() + timeout
+ else:
+ end_time = 0
+
+ if _DEBUG_READ:
+ Trace('WaitForString: "%s", %.1f' % (pattern, timeout))
+
+ buf = array.array('B') # unsigned char array
+ eating = False
+ while True:
+ if end_time:
+ remaining = end_time - time.time()
+ if remaining <= 0:
+ Trace('Timeout expired after %.1f seconds' % timeout)
+ return None
+ else:
+ remaining = None
+
+ if IsDataAvailable(inf, remaining):
+ if reset_on_activity and timeout:
+ end_time = time.time() + timeout
+
+ buf.fromfile(inf, 1)
+ if _DEBUG_READ:
+ c = buf.tostring()[-1:]
+ ci = ord(c)
+ if ci < 0x20: c = '.'
+ if _DEBUG_READ > 1:
+ print 'read [%c] 0x%02x' % (c, ci)
+
+ if not eating:
+ if buf.tostring().endswith(pattern):
+ if eat_to_eol:
+ if _DEBUG_READ > 1:
+ Trace('Matched; eating to EOL')
+ eating = True
+ else:
+ ShowTimeout(timeout, end_time)
+ return buf
+ if _DEBUG_READ > 2:
+ print '/%s/ ? "%s"' % (pattern, buf.tostring())
+ else:
+ if buf.tostring()[-1:] == '\n':
+ ShowTimeout(timeout, end_time)
+ return buf
+
+ if max_len and len(buf) >= max_len: return None
+
+
+def WaitForEmulator(ep, timeout=0):
+ """Waits for the emulator to start up and print the first prompt.
+
+ Args:
+ ep: A subprocess.Popen object referring to the emulator process.
+ timeout: How long to wait, in seconds. No timeout if it evaluates to False.
+
+ Returns:
+ True on success, False if the timeout occurred.
+ """
+ # Prime the pipe; the emulator doesn't start without this.
+ print >>ep.stdin, ''
+
+ # Wait until the console is ready and the first prompt appears.
+ buf = WaitForString(ep.stdout, '#', timeout=timeout, eat_to_eol=False)
+ if buf:
+ Trace('Saw the prompt: "%s"' % buf.tostring())
+ return True
+ return False
+
+
+def WaitForPrompt(ep, prompt=None, timeout=0, reset_on_activity=False):
+ """Blocks until the prompt appears on ep.stdout or the timeout elapses.
+
+ Args:
+ ep: A subprocess.Popen connection to the emulator process.
+ prompt: The prompt to wait for. If None, uses ep.prompt.
+ timeout: How many seconds to wait for the prompt. Waits forever
+ if timeout is zero.
+ reset_on_activity: If True, reset the timeout whenever a character is
+ read.
+
+ Returns:
+ A string containing the data leading up to the prompt. The string
+ will always end in '\\n'. Returns None if the prompt was not seen
+ within the timeout, or if some other error occurred.
+ """
+ if not prompt: prompt = ep.prompt
+ if prompt:
+ #Trace('waiting for prompt "%s"' % prompt)
+ data = WaitForString(ep.stdout, prompt,
+ timeout=timeout, reset_on_activity=reset_on_activity)
+ if data:
+ # data contains everything on ep.stdout up to and including the prompt,
+ # plus everything up 'til the newline. Scrape out the prompt
+ # and everything that follows, and ensure that the result ends
+ # in a newline (which is important if it would otherwise be empty).
+ s = data.tostring()
+ i = s.rfind(prompt)
+ s = s[:i]
+ if s[-1:] != '\n':
+ s += '\n'
+ if _DEBUG_READ:
+ print 'WaitForPrompt saw """\n%s"""' % s
+ return s
+ return None
+
+
+def ReplaceEmulatorPrompt(ep, prompt=None):
+ """Replaces PS1 in the emulator with a different value.
+
+ This is useful for making the prompt unambiguous; i.e., something
+ that probably won't appear in the output of another command.
+
+ Assumes that the emulator is already sitting at a prompt,
+ waiting for shell input.
+
+ Puts the new prompt in ep.prompt.
+
+ Args:
+ ep: A subprocess.Popen object referring to the emulator process.
+ prompt: The new prompt to use
+
+ Returns:
+ True on success, False if the timeout occurred.
+ """
+ if not prompt:
+ prompt = '-----DEXPREOPT-PROMPT-----'
+ print >>ep.stdin, 'PS1="%s\n"' % prompt
+ ep.prompt = prompt
+
+ # Eat the command echo.
+ data = WaitForPrompt(ep, timeout=2)
+ if not data:
+ return False
+
+ # Make sure it's actually there.
+ return WaitForPrompt(ep, timeout=2)
+
+
+def RunEmulatorCommand(ep, cmd, timeout=0):
+ """Sends the command to the emulator's shell and waits for the result.
+
+ Assumes that the emulator is already sitting at a prompt,
+ waiting for shell input.
+
+ Args:
+ ep: A subprocess.Popen object referring to the emulator process.
+ cmd: The shell command to run in the emulator.
+ timeout: The number of seconds to wait for the command to complete,
+ or zero for no timeout.
+
+ Returns:
+ If the command ran and returned to the console prompt before the
+ timeout, returns the output of the command as a string.
+ Returns None otherwise.
+ """
+ ConsumeAvailableData(ep.stdout)
+
+ Trace('Running "%s"' % cmd)
+ print >>ep.stdin, '%s' % cmd
+
+ # The console will echo the command.
+ #Trace('Waiting for echo')
+ if WaitForString(ep.stdout, cmd, timeout=timeout):
+ #Trace('Waiting for completion')
+ return WaitForPrompt(ep, timeout=timeout, reset_on_activity=True)
+
+ return None
+
+
+def ReadFileList(ep, dir_list, timeout=0):
+ """Returns a list of emulator files in each dir in dir_list.
+
+ Args:
+ ep: A subprocess.Popen object referring to the emulator process.
+ dir_list: List absolute paths to directories to read.
+ timeout: The number of seconds to wait for the command to complete,
+ or zero for no timeout.
+
+ Returns:
+ A list of absolute paths to files in the named directories,
+ in the context of the emulator's filesystem.
+ None on failure.
+ """
+ ret = []
+ for d in dir_list:
+ output = RunEmulatorCommand(ep, 'ls ' + d, timeout=timeout)
+ if not output:
+ Trace('Could not ls ' + d)
+ return None
+ ret += ['%s/%s' % (d, f) for f in output.splitlines()]
+ return ret
+
+
+def DownloadDirectoryHierarchy(ep, src, dest, timeout=0):
+ """Recursively downloads an emulator directory to the local filesystem.
+
+ Args:
+ ep: A subprocess.Popen object referring to the emulator process.
+ src: The path on the emulator's filesystem to download from.
+ dest: The path on the local filesystem to download to.
+ timeout: The number of seconds to wait for the command to complete,
+ or zero for no timeout. (CURRENTLY IGNORED)
+
+ Returns:
+ True iff the files downloaded successfully, False otherwise.
+ """
+ ConsumeAvailableData(ep.stdout)
+
+ if not os.path.exists(dest):
+ os.makedirs(dest)
+
+ cmd = 'afar %s' % src
+ Trace('Running "%s"' % cmd)
+ print >>ep.stdin, '%s' % cmd
+
+ # The console will echo the command.
+ #Trace('Waiting for echo')
+ if not WaitForString(ep.stdout, cmd, timeout=timeout):
+ return False
+
+ #TODO: use a signal to support timing out?
+
+ #
+ # Android File Archive format:
+ #
+ # magic[5]: 'A' 'F' 'A' 'R' '\n'
+ # version[4]: 0x00 0x00 0x00 0x01
+ # for each file:
+ # file magic[4]: 'F' 'I' 'L' 'E'
+ # namelen[4]: Length of file name, including NUL byte (big-endian)
+ # name[*]: NUL-terminated file name
+ # datalen[4]: Length of file (big-endian)
+ # data[*]: Unencoded file data
+ # adler32[4]: adler32 of the unencoded file data (big-endian)
+ # file end magic[4]: 'f' 'i' 'l' 'e'
+ # end magic[4]: 'E' 'N' 'D' 0x00
+ #
+
+ # Read the header.
+ HEADER = array.array('B', 'AFAR\n\000\000\000\001')
+ buf = array.array('B')
+ buf.fromfile(ep.stdout, len(HEADER))
+ if buf != HEADER:
+ Trace('Header does not match: "%s"' % buf)
+ return False
+
+ # Read the file entries.
+ FILE_START = array.array('B', 'FILE')
+ FILE_END = array.array('B', 'file')
+ END = array.array('B', 'END\000')
+ while True:
+ # Entry magic.
+ buf = array.array('B')
+ buf.fromfile(ep.stdout, 4)
+ if buf == FILE_START:
+ # Name length (4 bytes, big endian)
+ buf = array.array('B')
+ buf.fromfile(ep.stdout, 4)
+ (name_len,) = struct.unpack('>I', buf)
+ #Trace('name len %d' % name_len)
+
+ # Name, NUL-terminated.
+ buf = array.array('B')
+ buf.fromfile(ep.stdout, name_len)
+ buf.pop() # Remove trailing NUL byte.
+ file_name = buf.tostring()
+ Trace('FILE: %s' % file_name)
+
+ # File length (4 bytes, big endian)
+ buf = array.array('B')
+ buf.fromfile(ep.stdout, 4)
+ (file_len,) = struct.unpack('>I', buf)
+
+ # File data.
+ data = array.array('B')
+ data.fromfile(ep.stdout, file_len)
+ #Trace('FILE: read %d bytes from %s' % (file_len, file_name))
+
+ # adler32 (4 bytes, big endian)
+ buf = array.array('B')
+ buf.fromfile(ep.stdout, 4)
+ (adler32,) = struct.unpack('>i', buf) # adler32 wants a signed int ('i')
+ data_adler32 = zlib.adler32(data)
+ # Because of a difference in behavior of zlib.adler32 on 32-bit and 64-bit
+ # systems (one returns signed values, the other unsigned), we take the
+ # modulo 2**32 of the checksums, and compare those.
+ # See also http://bugs.python.org/issue1202
+ if (adler32 % (2**32)) != (data_adler32 % (2**32)):
+ Trace('adler32 does not match: calculated 0x%08x != expected 0x%08x' %
+ (data_adler32, adler32))
+ return False
+
+ # File end magic.
+ buf = array.array('B')
+ buf.fromfile(ep.stdout, 4)
+ if buf != FILE_END:
+ Trace('Unexpected file end magic "%s"' % buf)
+ return False
+
+ # Write to the output file
+ out_file_name = dest + '/' + file_name[len(src):]
+ p = os.path.dirname(out_file_name)
+ if not os.path.exists(p): os.makedirs(p)
+ fo = file(out_file_name, 'w+b')
+ fo.truncate(0)
+ Trace('FILE: Writing %d bytes to %s' % (len(data), out_file_name))
+ data.tofile(fo)
+ fo.close()
+
+ elif buf == END:
+ break
+ else:
+ Trace('Unexpected magic "%s"' % buf)
+ return False
+
+ return WaitForPrompt(ep, timeout=timeout, reset_on_activity=True)
+
+
+def ReadBootClassPath(ep, timeout=0):
+ """Reads and returns the default bootclasspath as a list of files.
+
+ Args:
+ ep: A subprocess.Popen object referring to the emulator process.
+ timeout: The number of seconds to wait for the command to complete,
+ or zero for no timeout.
+
+ Returns:
+ The bootclasspath as a list of strings.
+ None on failure.
+ """
+ bcp = RunEmulatorCommand(ep, 'echo $BOOTCLASSPATH', timeout=timeout)
+ if not bcp:
+ Trace('Could not find bootclasspath')
+ return None
+ return bcp.strip().split(':') # strip trailing newline
+
+
+def RunDexoptOnFileList(ep, files, dest_root, move=False, timeout=0):
+ """Creates the corresponding .odex file for all jar/apk files in 'files'.
+ Copies the .odex file to a location under 'dest_root'. If 'move' is True,
+ the file is moved instead of copied.
+
+ Args:
+ ep: A subprocess.Popen object referring to the emulator process.
+ files: The list of files to optimize
+ dest_root: directory to copy/move odex files to. Must already exist.
+ move: if True, move rather than copy files
+ timeout: The number of seconds to wait for the command to complete,
+ or zero for no timeout.
+
+ Returns:
+ True on success, False on failure.
+ """
+ for jar_file in files:
+ if jar_file.endswith('.apk') or jar_file.endswith('.jar'):
+ odex_file = jar_file[:jar_file.rfind('.')] + '.odex'
+ cmd = 'dexopt-wrapper %s %s' % (jar_file, odex_file)
+ if not RunEmulatorCommand(ep, cmd, timeout=timeout):
+ Trace('"%s" failed' % cmd)
+ return False
+
+ # Always copy the odex file. There's no cp(1), so we
+ # cat out to the new file.
+ dst_odex = dest_root + odex_file
+ cmd = 'cat %s > %s' % (odex_file, dst_odex) # no cp(1)
+ if not RunEmulatorCommand(ep, cmd, timeout=timeout):
+ Trace('"%s" failed' % cmd)
+ return False
+
+ # Move it if we're asked to. We can't use mv(1) because
+ # the files tend to move between filesystems.
+ if move:
+ cmd = 'rm %s' % odex_file
+ if not RunEmulatorCommand(ep, cmd, timeout=timeout):
+ Trace('"%s" failed' % cmd)
+ return False
+ return True
+
+
+def InstallCacheFiles(cache_system_dir, out_system_dir):
+ """Install files in cache_system_dir to the proper places in out_system_dir.
+
+ cache_system_dir contains various files from /system, plus .odex files
+ for most of the .apk/.jar files that live there.
+ This function copies each .odex file from the cache dir to the output dir
+ and removes "classes.dex" from each appropriate .jar/.apk.
+
+ E.g., <cache_system_dir>/app/NotePad.odex would be copied to
+ <out_system_dir>/app/NotePad.odex, and <out_system_dir>/app/NotePad.apk
+ would have its classes.dex file removed.
+
+ Args:
+ cache_system_dir: The directory containing the cache files scraped from
+ the emulator.
+ out_system_dir: The local directory that corresponds to "/system"
+ on the device filesystem. (the root of system.img)
+
+ Returns:
+ True if everything succeeded, False if any problems occurred.
+ """
+ # First, walk through cache_system_dir and copy every .odex file
+ # over to out_system_dir, ensuring that the destination directory
+ # contains the corresponding source file.
+ for root, dirs, files in os.walk(cache_system_dir):
+ for name in files:
+ if name.endswith('.odex'):
+ odex_file = os.path.join(root, name)
+
+ # Find the path to the .odex file's source apk/jar file.
+ out_stem = odex_file[len(cache_system_dir):odex_file.rfind('.')]
+ out_stem = out_system_dir + out_stem;
+ jar_file = out_stem + '.jar'
+ if not os.path.exists(jar_file):
+ jar_file = out_stem + '.apk'
+ if not os.path.exists(jar_file):
+ Trace('Cannot find source .jar/.apk for %s: %s' %
+ (odex_file, out_stem + '.{jar,apk}'))
+ return False
+
+ # Copy the cache file next to the source file.
+ cmd = ['cp', odex_file, out_stem + '.odex']
+ ret = subprocess.call(cmd)
+ if ret: # non-zero exit status
+ Trace('%s failed' % ' '.join(cmd))
+ return False
+
+ # Walk through the output /system directory, making sure
+ # that every .jar/.apk has an odex file. While we do this,
+ # remove the classes.dex entry from each source archive.
+ for root, dirs, files in os.walk(out_system_dir):
+ for name in files:
+ if name.endswith('.apk') or name.endswith('.jar'):
+ jar_file = os.path.join(root, name)
+ odex_file = jar_file[:jar_file.rfind('.')] + '.odex'
+ if not os.path.exists(odex_file):
+ if root.endswith('/system/app') or root.endswith('/system/framework'):
+ Trace('jar/apk %s has no .odex file %s' % (jar_file, odex_file))
+ return False
+ else:
+ continue
+
+ # Attempting to dexopt a jar with no classes.dex currently
+ # creates a 40-byte odex file.
+ # TODO: use a more reliable check
+ if os.path.getsize(odex_file) > 100:
+ # Remove classes.dex from the .jar file.
+ cmd = ['zip', '-dq', jar_file, 'classes.dex']
+ ret = subprocess.call(cmd)
+ if ret: # non-zero exit status
+ Trace('"%s" failed' % ' '.join(cmd))
+ return False
+ else:
+ # Some of the apk files don't contain any code.
+ if not name.endswith('.apk'):
+ Trace('%s has a zero-length odex file' % jar_file)
+ return False
+ cmd = ['rm', odex_file]
+ ret = subprocess.call(cmd)
+ if ret: # non-zero exit status
+ Trace('"%s" failed' % ' '.join(cmd))
+ return False
+
+ return True
+
+
+def KillChildProcess(p, sig=signal.SIGTERM, timeout=0):
+ """Waits for a child process to die without getting stuck in wait().
+
+ After Jean Brouwers's 2004 post to python-list.
+
+ Args:
+ p: A subprocess.Popen representing the child process to kill.
+ sig: The signal to send to the child process.
+ timeout: How many seconds to wait for the child process to die.
+ If zero, do not time out.
+
+ Returns:
+ The exit status of the child process, if it was successfully killed.
+ The final value of p.returncode if it wasn't.
+ """
+ os.kill(p.pid, sig)
+ if timeout > 0:
+ while p.poll() < 0:
+ if timeout > 0.5:
+ timeout -= 0.25
+ time.sleep(0.25)
+ else:
+ os.kill(p.pid, signal.SIGKILL)
+ time.sleep(0.5)
+ p.poll()
+ break
+ else:
+ p.wait()
+ return p.returncode
+
+
+def Trace(msg):
+ """Prints a message to stdout.
+
+ Args:
+ msg: The message to print.
+ """
+ #print 'dexpreopt: %s' % msg
+ when = datetime.datetime.now()
+ print '%02d:%02d.%d dexpreopt: %s' % (when.minute, when.second, when.microsecond, msg)
+
+
+def KillEmulator():
+ """Attempts to kill the emulator process, if it is running.
+
+ Returns:
+ The exit status of the emulator process, or None if the emulator
+ was not running or was unable to be killed.
+ """
+ global _emulator_popen
+ if _emulator_popen:
+ Trace('Killing emulator')
+ try:
+ ret = KillChildProcess(_emulator_popen, sig=signal.SIGINT, timeout=5)
+ except OSError:
+ Trace('Could not kill emulator')
+ ret = None
+ _emulator_popen = None
+ return ret
+ return None
+
+
+def Fail(msg=None):
+ """Prints an error and causes the process to exit.
+
+ Args:
+ msg: Additional error string to print (optional).
+
+ Returns:
+ Does not return.
+ """
+ s = 'dexpreopt: ERROR'
+ if msg: s += ': %s' % msg
+ print >>sys.stderr, msg
+ KillEmulator()
+ sys.exit(1)
+
+
+def PrintUsage(msg=None):
+ """Prints commandline usage information for the tool and exits with an error.
+
+ Args:
+ msg: Additional string to print (optional).
+
+ Returns:
+ Does not return.
+ """
+ if msg:
+ print >>sys.stderr, 'dexpreopt: %s', msg
+ print >>sys.stderr, """Usage: dexpreopt <options>
+Required options:
+ -kernel <kernel file> Kernel to use when running the emulator
+ -ramdisk <ramdisk.img file> Ramdisk to use when running the emulator
+ -image <system.img file> System image to use when running the
+ emulator. /system/app should contain the
+ .apk files to optimize, and any required
+ bootclasspath libraries must be present
+ in the correct locations.
+ -system <path> The product directory, which usually contains
+ files like 'system.img' (files other than
+ the kernel in that directory won't
+ be used)
+ -outsystemdir <path> A fully-populated /system directory, ready
+ to be modified to contain the optimized
+ files. The appropriate .jar/.apk files
+ will be stripped of their classes.dex
+ entries, and the optimized .dex files
+ will be added alongside the packages
+ that they came from.
+Optional:
+ -tmpdir <path> If specified, use this directory for
+ intermediate objects. If not specified,
+ a unique directory under the system
+ temp dir is used.
+ """
+ sys.exit(2)
+
+
+def ParseArgs(argv):
+ """Parses commandline arguments.
+
+ Args:
+ argv: A list of arguments; typically sys.argv[1:]
+
+ Returns:
+ A tuple containing two dictionaries; the first contains arguments
+ that will be passsed to the emulator, and the second contains other
+ arguments.
+ """
+ parser = optparse.OptionParser()
+
+ parser.add_option('--kernel', help='Passed to emulator')
+ parser.add_option('--ramdisk', help='Passed to emulator')
+ parser.add_option('--image', help='Passed to emulator')
+ parser.add_option('--system', help='Passed to emulator')
+ parser.add_option('--outsystemdir', help='Destination /system directory')
+ parser.add_option('--tmpdir', help='Optional temp directory to use')
+
+ options, args = parser.parse_args(args=argv)
+ if args: PrintUsage()
+
+ emulator_args = {}
+ other_args = {}
+ if options.kernel: emulator_args['kernel'] = options.kernel
+ if options.ramdisk: emulator_args['ramdisk'] = options.ramdisk
+ if options.image: emulator_args['image'] = options.image
+ if options.system: emulator_args['system'] = options.system
+ if options.outsystemdir: other_args['outsystemdir'] = options.outsystemdir
+ if options.tmpdir: other_args['tmpdir'] = options.tmpdir
+
+ return (emulator_args, other_args)
+
+
+def DexoptEverything(ep, dest_root):
+ """Logic for finding and dexopting files in the necessary order.
+
+ Args:
+ ep: A subprocess.Popen object referring to the emulator process.
+ dest_root: directory to copy/move odex files to
+
+ Returns:
+ True on success, False on failure.
+ """
+ _extra_tests = False
+ if _extra_tests:
+ if not RunEmulatorCommand(ep, 'ls /system/app', timeout=5):
+ Fail('Could not ls')
+
+ # We're very short on space, so remove a bunch of big stuff that we
+ # don't need.
+ cmd = 'rm -r /system/sounds /system/media /system/fonts /system/xbin'
+ if not RunEmulatorCommand(ep, cmd, timeout=40):
+ Trace('"%s" failed' % cmd)
+ return False
+
+ Trace('Read file list')
+ jar_dirs = ['/system/framework', '/system/app']
+ files = ReadFileList(ep, jar_dirs, timeout=5)
+ if not files:
+ Fail('Could not list files in %s' % ' '.join(jar_dirs))
+ #Trace('File list:\n"""\n%s\n"""' % '\n'.join(files))
+
+ bcp = ReadBootClassPath(ep, timeout=2)
+ if not files:
+ Fail('Could not sort by bootclasspath')
+
+ # Remove bootclasspath entries from the main file list.
+ for jar in bcp:
+ try:
+ files.remove(jar)
+ except ValueError:
+ Trace('File list does not contain bootclasspath entry "%s"' % jar)
+ return False
+
+ # Create the destination directories.
+ for d in ['', '/system'] + jar_dirs:
+ cmd = 'mkdir %s%s' % (dest_root, d)
+ if not RunEmulatorCommand(ep, cmd, timeout=4):
+ Trace('"%s" failed' % cmd)
+ return False
+
+ # First, dexopt the bootclasspath. Keep their cache files in place.
+ Trace('Dexopt %d bootclasspath files' % len(bcp))
+ if not RunDexoptOnFileList(ep, bcp, dest_root, timeout=120):
+ Trace('Could not dexopt bootclasspath')
+ return False
+
+ # dexopt the rest. To avoid running out of space on the emulator
+ # volume, move each cache file after it's been created.
+ Trace('Dexopt %d files' % len(files))
+ if not RunDexoptOnFileList(ep, files, dest_root, move=True, timeout=120):
+ Trace('Could not dexopt files')
+ return False
+
+ if _extra_tests:
+ if not RunEmulatorCommand(ep, 'ls /system/app', timeout=5):
+ Fail('Could not ls')
+
+ return True
+
+
+
+def MainInternal():
+ """Main function that can be wrapped in a try block.
+
+ Returns:
+ Nothing.
+ """
+ emulator_args, other_args = ParseArgs(sys.argv[1:])
+
+ tmp_dir = EnsureTempDir(other_args.get('tmpdir'))
+ if not tmp_dir: Fail('Could not create temp dir')
+
+ Trace('Creating data image')
+ userdata = '%s/data.img' % tmp_dir
+ if not CreateZeroedFile(userdata, 32 * 1024 * 1024):
+ Fail('Could not create data image file')
+ emulator_args['userdata'] = userdata
+
+ ep = StartEmulator(**emulator_args)
+ if not ep: Fail('Could not start emulator')
+ global _emulator_popen
+ _emulator_popen = ep
+
+ # TODO: unlink the big userdata file now, since the emulator
+ # has it open.
+
+ if not WaitForEmulator(ep, timeout=20): Fail('Emulator did not respond')
+ if not ReplaceEmulatorPrompt(ep): Fail('Could not replace prompt')
+
+ dest_root = '/data/dexpreopt-root'
+ if not DexoptEverything(ep, dest_root): Fail('Could not dexopt files')
+
+ # Grab the odex files that were left in dest_root.
+ cache_system_dir = tmp_dir + '/cache-system'
+ if not DownloadDirectoryHierarchy(ep, dest_root + '/system',
+ cache_system_dir,
+ timeout=20):
+ Fail('Could not download %s/system from emulator' % dest_root)
+
+ if not InstallCacheFiles(cache_system_dir=cache_system_dir,
+ out_system_dir=other_args['outsystemdir']):
+ Fail('Could not install files')
+
+ Trace('dexpreopt successful')
+ # Success!
+
+
+def main():
+ try:
+ MainInternal()
+ finally:
+ KillEmulator()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/dexpreopt/geninitrc.awk b/tools/dexpreopt/geninitrc.awk
new file mode 100644
index 0000000..4b67e78
--- /dev/null
+++ b/tools/dexpreopt/geninitrc.awk
@@ -0,0 +1,62 @@
+#
+# Copyright (C) 2009 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+BEGIN {
+ fixed_remount = 0;
+ console_state = 0;
+}
+
+/^ mount yaffs2 mtd@system \/system ro remount$/ {
+ fixed_remount = 1;
+ print " # dexpreopt needs to write to /system";
+ print " ### " $0;
+ next;
+}
+
+console_state == 0 && /^service console \/system\/bin\/sh$/ {
+ console_state = 1;
+ print;
+ next;
+}
+
+console_state == 1 && /^ console$/ {
+ console_state = 2;
+ print;
+ exit;
+}
+
+console_state == 1 {
+ # The second line of the console entry should always immediately
+ # follow the first.
+ exit;
+}
+
+{ print }
+
+END {
+ failed = 0;
+ if (fixed_remount != 1) {
+ print "ERROR: no match for remount line" > "/dev/stderr";
+ failed = 1;
+ }
+ if (console_state != 2) {
+ print "ERROR: no match for console lines" > "/dev/stderr";
+ failed = 1;
+ }
+ if (failed == 1) {
+ print ">>>> FAILED <<<<"
+ exit 1;
+ }
+}