aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools/android/java/com/google/devtools/build/android/incrementaldeployment
diff options
context:
space:
mode:
authorGravatar Lukacs Berki <lberki@google.com>2015-07-28 08:12:43 +0000
committerGravatar Lukacs Berki <lberki@google.com>2015-07-29 15:58:16 +0000
commit81af4c5f31cd0124013a4feb77df68f07bf65261 (patch)
treeaf3ee2867e176af0615e009fddc4ae6c4efc6d8a /src/tools/android/java/com/google/devtools/build/android/incrementaldeployment
parentecbf24248bbabfd10281b96efa5f72ee21416f4d (diff)
Make the stub application support incremental deployment of native libraries.
Care is taken so that this works without Bazel or the installer script supporting it so that the changes can be submitted separately. -- MOS_MIGRATED_REVID=99258564
Diffstat (limited to 'src/tools/android/java/com/google/devtools/build/android/incrementaldeployment')
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/incrementaldeployment/IncrementalClassLoader.java16
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/incrementaldeployment/StubApplication.java152
2 files changed, 160 insertions, 8 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/incrementaldeployment/IncrementalClassLoader.java b/src/tools/android/java/com/google/devtools/build/android/incrementaldeployment/IncrementalClassLoader.java
index 1000b18890..e2adb192e7 100644
--- a/src/tools/android/java/com/google/devtools/build/android/incrementaldeployment/IncrementalClassLoader.java
+++ b/src/tools/android/java/com/google/devtools/build/android/incrementaldeployment/IncrementalClassLoader.java
@@ -30,14 +30,14 @@ import java.util.List;
public class IncrementalClassLoader extends ClassLoader {
private final DelegateClassLoader delegateClassLoader;
- public IncrementalClassLoader(
- ClassLoader original, String packageName, String codeCacheDir, List<String> dexes) {
+ public IncrementalClassLoader(ClassLoader original,
+ String packageName, String codeCacheDir, String nativeLibDir, List<String> dexes) {
super(original.getParent());
// TODO(bazel-team): For some mysterious reason, we need to use two class loaders so that
// everything works correctly. Investigate why that is the case so that the code can be
// simplified.
- delegateClassLoader = createDelegateClassLoader(packageName, codeCacheDir, dexes, original);
+ delegateClassLoader = createDelegateClassLoader(codeCacheDir, nativeLibDir, dexes, original);
}
@Override
@@ -61,7 +61,7 @@ public class IncrementalClassLoader extends ClassLoader {
}
private static DelegateClassLoader createDelegateClassLoader(
- String packageName, String codeCacheDir, List<String> dexes, ClassLoader original) {
+ String codeCacheDir, String nativeLibDir, List<String> dexes, ClassLoader original) {
StringBuilder pathBuilder = new StringBuilder();
boolean first = true;
for (String dex : dexes) {
@@ -75,8 +75,9 @@ public class IncrementalClassLoader extends ClassLoader {
}
Log.v("IncrementalClassLoader", "Incremental dex path is " + pathBuilder);
+ Log.v("IncrementalClassLoader", "Native lib dir is " + nativeLibDir);
return new DelegateClassLoader(pathBuilder.toString(), new File(codeCacheDir),
- "/data/data/" + packageName + "/lib", original);
+ nativeLibDir, original);
}
private static void setParent(ClassLoader classLoader, ClassLoader newParent) {
@@ -90,9 +91,10 @@ public class IncrementalClassLoader extends ClassLoader {
}
public static void inject(
- ClassLoader classLoader, String packageName, String codeCacheDir, List<String> dexes) {
+ ClassLoader classLoader, String packageName, String codeCacheDir,
+ String nativeLibDir, List<String> dexes) {
IncrementalClassLoader incrementalClassLoader =
- new IncrementalClassLoader(classLoader, packageName, codeCacheDir, dexes);
+ new IncrementalClassLoader(classLoader, packageName, codeCacheDir, nativeLibDir, dexes);
setParent(classLoader, incrementalClassLoader);
}
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/incrementaldeployment/StubApplication.java b/src/tools/android/java/com/google/devtools/build/android/incrementaldeployment/StubApplication.java
index 89a19bc656..6ddcd1b3da 100644
--- a/src/tools/android/java/com/google/devtools/build/android/incrementaldeployment/StubApplication.java
+++ b/src/tools/android/java/com/google/devtools/build/android/incrementaldeployment/StubApplication.java
@@ -22,19 +22,27 @@ import android.content.res.Resources;
import android.util.ArrayMap;
import android.util.Log;
+import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
-
+import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* A stub application that patches the class loader, then replaces itself with the real application
@@ -56,6 +64,12 @@ import java.util.Map;
public class StubApplication extends Application {
private static final String INCREMENTAL_DEPLOYMENT_DIR = "/data/local/tmp/incrementaldeployment";
+ private static final FilenameFilter SO = new FilenameFilter() {
+ public boolean accept(File dir, String name) {
+ return name.endsWith(".so");
+ }
+ };
+
private final String realClassName;
private final String packageName;
@@ -280,10 +294,22 @@ public class StubApplication extends Application {
private void instantiateRealApplication(String codeCacheDir) {
externalResourceFile = getExternalResourceFile();
+ String nativeLibDir;
+ try {
+ // We cannot use the .so files pushed by adb for some reason: even if permissions are 777
+ // and they are chowned to the user of the app from a root shell, dlopen() returns with
+ // "Permission denied". For some reason, copying them over makes them work (at the cost of
+ // some execution time and complexity here, of course)
+ nativeLibDir = copyNativeLibs();
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+
IncrementalClassLoader.inject(
StubApplication.class.getClassLoader(),
packageName,
codeCacheDir,
+ nativeLibDir,
getDexList(packageName));
try {
@@ -297,6 +323,130 @@ public class StubApplication extends Application {
}
}
+ private String copyNativeLibs() throws IOException {
+ File nativeLibDir = new File(INCREMENTAL_DEPLOYMENT_DIR + "/" + packageName + "/native");
+ File newManifestFile = new File(nativeLibDir, "native_manifest");
+ File incrementalDir = new File("/data/data/" + packageName + "/incrementallib");
+ File installedManifestFile = new File(incrementalDir, "manifest");
+
+ if (!newManifestFile.exists()) {
+ // Native libraries are not installed incrementally. Just use the regular directory.
+ return "/data/data/" + packageName + "/lib";
+ }
+
+ Map<String, String> newManifest = parseManifest(newManifestFile);
+ Map<String, String> installedManifest = new HashMap<String, String>();
+ Set<String> libsToDelete = new HashSet<String>();
+ Set<String> libsToUpdate = new HashSet<String>();
+
+ if (!incrementalDir.exists()) {
+ if (!incrementalDir.mkdirs()) {
+ throw new IOException("Could not mkdir " + incrementalDir);
+ }
+ }
+
+ if (installedManifestFile.exists()) {
+ installedManifest = parseManifest(installedManifestFile);
+ } else {
+ // Delete old libraries, in case things got out of sync.
+ for (String installed : incrementalDir.list(SO)) {
+ libsToDelete.add(installed);
+ }
+ }
+
+ for (String installed : installedManifest.keySet()) {
+ if (!newManifest.containsKey(installed)
+ || !newManifest.get(installed).equals(installedManifest.get(installed))) {
+ libsToDelete.add(installed);
+ }
+ }
+
+ for (String newLib : newManifest.keySet()) {
+ if (!installedManifest.containsKey(newLib)
+ || !installedManifest.get(newLib).equals(newManifest.get(newLib))) {
+ libsToUpdate.add(newLib);
+ }
+ }
+
+ if (libsToDelete.isEmpty() && libsToUpdate.isEmpty()) {
+ // Nothing to be done. Be lazy.
+ return incrementalDir.toString();
+ }
+
+ // Delete the installed manifest file. If anything below goes wrong, everything will be
+ // reinstalled the next time the app starts up.
+ installedManifestFile.delete();
+
+ for (String toDelete : libsToDelete) {
+ File fileToDelete = new File(incrementalDir + "/" + toDelete);
+ Log.v("StubApplication", "Deleting " + fileToDelete);
+ if (fileToDelete.exists() && !fileToDelete.delete()) {
+ throw new IOException("Could not delete " + fileToDelete);
+ }
+ }
+
+ for (String toUpdate : libsToUpdate) {
+ Log.v("StubApplication", "Copying: " + toUpdate);
+ File src = new File(nativeLibDir + "/" + toUpdate);
+ copy(src, new File(incrementalDir + "/" + toUpdate));
+ }
+
+ try {
+ copy(newManifestFile, installedManifestFile);
+ } finally {
+ // If we can't write the installed manifest file, delete it completely so that the next
+ // time we get here we can start with a clean slate.
+ installedManifestFile.delete();
+ }
+ return incrementalDir.toString();
+ }
+
+ private static Map<String, String> parseManifest(File file) throws IOException {
+ Map<String, String> result = new HashMap<>();
+ BufferedReader reader = new BufferedReader(new FileReader(file));
+ try {
+ while (true) {
+ String line = reader.readLine();
+ if (line == null) {
+ break;
+ }
+
+ String[] items = line.split(" ");
+ result.put(items[0], items[1]);
+ }
+ } finally {
+ reader.close();
+ }
+
+ return result;
+ }
+
+
+ private static void copy(File src, File dst) throws IOException {
+ Log.v("StubApplication", "Copying " + src + " -> " + dst);
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ in = new FileInputStream(src);
+ out = new FileOutputStream(dst);
+
+ // Transfer bytes from in to out
+ byte[] buf = new byte[1048576];
+ int len;
+ while ((len = in.read(buf)) > 0) {
+ out.write(buf, 0, len);
+ }
+ } finally {
+ if (in != null) {
+ in.close();
+ }
+
+ if (out != null) {
+ out.close();
+ }
+ }
+ }
+
@Override
protected void attachBaseContext(Context context) {
instantiateRealApplication(context.getCacheDir().getPath());