diff options
Diffstat (limited to 'tools/dexpreopt')
| -rw-r--r-- | tools/dexpreopt/Android.mk | 38 | ||||
| -rw-r--r-- | tools/dexpreopt/Config.mk | 146 | ||||
| -rw-r--r-- | tools/dexpreopt/afar/Android.mk | 29 | ||||
| -rw-r--r-- | tools/dexpreopt/afar/main.c | 247 | ||||
| -rw-r--r-- | tools/dexpreopt/dexopt-wrapper/Android.mk | 36 | ||||
| -rw-r--r-- | tools/dexpreopt/dexopt-wrapper/DexOptWrapper.cpp | 173 | ||||
| -rwxr-xr-x | tools/dexpreopt/dexpreopt.py | 981 | ||||
| -rw-r--r-- | tools/dexpreopt/geninitrc.awk | 62 |
8 files changed, 1712 insertions, 0 deletions
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; + } +} |
