1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package vogar.android; 18 19 import com.google.common.annotations.VisibleForTesting; 20 import java.io.File; 21 import java.io.FilenameFilter; 22 import java.util.ArrayList; 23 import java.util.Arrays; 24 import java.util.Collection; 25 import java.util.Collections; 26 import java.util.List; 27 import vogar.Classpath; 28 import vogar.HostFileCache; 29 import vogar.Log; 30 import vogar.Md5Cache; 31 import vogar.ModeId; 32 import vogar.commands.Command; 33 import vogar.commands.Mkdir; 34 import vogar.util.Strings; 35 36 /** 37 * Android SDK commands such as adb, aapt and dx. 38 */ 39 public class AndroidSdk { 40 41 private final Log log; 42 private final Mkdir mkdir; 43 private final File[] compilationClasspath; 44 private final String androidJarPath; 45 private final Md5Cache dexCache; 46 defaultExpectations()47 public static Collection<File> defaultExpectations() { 48 File[] files = new File("libcore/expectations").listFiles(new FilenameFilter() { 49 // ignore obviously temporary files 50 public boolean accept(File dir, String name) { 51 return !name.endsWith("~") && !name.startsWith("."); 52 } 53 }); 54 return (files != null) ? Arrays.asList(files) : Collections.<File>emptyList(); 55 } 56 57 /** 58 * Create an {@link AndroidSdk}. 59 * 60 * <p>Searches the PATH used to run this and scans the file system in order to determine the 61 * compilation class path and android jar path. 62 */ createAndroidSdk( Log log, Mkdir mkdir, ModeId modeId, boolean useJack)63 public static AndroidSdk createAndroidSdk( 64 Log log, Mkdir mkdir, ModeId modeId, boolean useJack) { 65 List<String> path = new Command.Builder(log).args("which", "dx") 66 .permitNonZeroExitStatus(true) 67 .execute(); 68 if (path.isEmpty()) { 69 throw new RuntimeException("dx not found"); 70 } 71 File dx = new File(path.get(0)).getAbsoluteFile(); 72 String parentFileName = getParentFileNOrLast(dx, 1).getName(); 73 74 List<String> adbPath = new Command.Builder(log) 75 .args("which", "adb") 76 .permitNonZeroExitStatus(true) 77 .execute(); 78 79 File adb; 80 if (!adbPath.isEmpty()) { 81 adb = new File(adbPath.get(0)); 82 } else { 83 adb = null; // Could not find adb. 84 } 85 86 /* 87 * Determine if we are running with a provided SDK or in the AOSP source tree. 88 * 89 * On Android SDK v23 (Marshmallow) the structure looks like: 90 * <sdk>/build-tools/23.0.1/aapt 91 * <sdk>/platform-tools/adb 92 * <sdk>/build-tools/23.0.1/dx 93 * <sdk>/platforms/android-23/android.jar 94 * 95 * Android build tree (target): 96 * ${ANDROID_BUILD_TOP}/out/host/linux-x86/bin/aapt 97 * ${ANDROID_BUILD_TOP}/out/host/linux-x86/bin/adb 98 * ${ANDROID_BUILD_TOP}/out/host/linux-x86/bin/dx 99 * ${ANDROID_BUILD_TOP}/out/target/common/obj/JAVA_LIBRARIES/core-libart_intermediates 100 * /classes.jar 101 */ 102 103 File[] compilationClasspath; 104 String androidJarPath; 105 106 // Accept that we are running in an SDK if the user has added the build-tools or 107 // platform-tools to their path. 108 boolean dxSdkPathValid = "build-tools".equals(getParentFileNOrLast(dx, 2).getName()); 109 boolean isAdbPathValid = (adb != null) && 110 "platform-tools".equals(getParentFileNOrLast(adb, 1).getName()); 111 if (dxSdkPathValid || isAdbPathValid) { 112 File sdkRoot = dxSdkPathValid ? getParentFileNOrLast(dx, 3) // if dx path invalid then 113 : getParentFileNOrLast(adb, 2); // adb must be valid. 114 File newestPlatform = getNewestPlatform(sdkRoot); 115 log.verbose("Using android platform: " + newestPlatform); 116 compilationClasspath = new File[] { new File(newestPlatform, "android.jar") }; 117 androidJarPath = new File(newestPlatform.getAbsolutePath(), "android.jar") 118 .getAbsolutePath(); 119 log.verbose("using android sdk: " + sdkRoot); 120 } else if ("bin".equals(parentFileName)) { 121 log.verbose("Using android source build mode to find dependencies."); 122 String tmpJarPath = "prebuilts/sdk/current/android.jar"; 123 String androidBuildTop = System.getenv("ANDROID_BUILD_TOP"); 124 if (!com.google.common.base.Strings.isNullOrEmpty(androidBuildTop)) { 125 tmpJarPath = androidBuildTop + "/prebuilts/sdk/current/android.jar"; 126 } else { 127 log.warn("Assuming current directory is android build tree root."); 128 } 129 androidJarPath = tmpJarPath; 130 131 String outDir = System.getenv("OUT_DIR"); 132 if (Strings.isNullOrEmpty(outDir)) { 133 if (Strings.isNullOrEmpty(androidBuildTop)) { 134 outDir = "."; 135 log.warn("Assuming we are in android build tree root to find libraries."); 136 } else { 137 log.verbose("Using ANDROID_BUILD_TOP to find built libraries."); 138 outDir = androidBuildTop; 139 } 140 outDir += "/out/"; 141 } else { 142 log.verbose("Using OUT_DIR environment variable for finding built libs."); 143 outDir += "/"; 144 } 145 146 String pattern = outDir + "target/common/obj/JAVA_LIBRARIES/%s_intermediates/classes"; 147 if (modeId.isHost()) { 148 pattern = outDir + "host/common/obj/JAVA_LIBRARIES/%s_intermediates/classes"; 149 } 150 pattern += ((useJack) ? ".jack" : ".jar"); 151 152 String[] jarNames = modeId.getJarNames(); 153 compilationClasspath = new File[jarNames.length]; 154 for (int i = 0; i < jarNames.length; i++) { 155 String jar = jarNames[i]; 156 compilationClasspath[i] = new File(String.format(pattern, jar)); 157 } 158 } else { 159 throw new RuntimeException("Couldn't derive Android home from " + dx); 160 } 161 162 return new AndroidSdk(log, mkdir, compilationClasspath, androidJarPath, 163 new HostFileCache(log, mkdir)); 164 } 165 166 @VisibleForTesting AndroidSdk(Log log, Mkdir mkdir, File[] compilationClasspath, String androidJarPath, HostFileCache hostFileCache)167 AndroidSdk(Log log, Mkdir mkdir, File[] compilationClasspath, String androidJarPath, 168 HostFileCache hostFileCache) { 169 this.log = log; 170 this.mkdir = mkdir; 171 this.compilationClasspath = compilationClasspath; 172 this.androidJarPath = androidJarPath; 173 this.dexCache = new Md5Cache(log, "dex", hostFileCache); 174 } 175 176 // Goes up N levels in the filesystem hierarchy. Return the last file that exists if this goes 177 // past /. getParentFileNOrLast(File f, int n)178 private static File getParentFileNOrLast(File f, int n) { 179 File lastKnownExists = f; 180 for (int i = 0; i < n; i++) { 181 File parentFile = lastKnownExists.getParentFile(); 182 if (parentFile == null) { 183 return lastKnownExists; 184 } 185 lastKnownExists = parentFile; 186 } 187 return lastKnownExists; 188 } 189 190 /** 191 * Returns the platform directory that has the highest API version. API 192 * platform directories are named like "android-9" or "android-11". 193 */ getNewestPlatform(File sdkRoot)194 private static File getNewestPlatform(File sdkRoot) { 195 File newestPlatform = null; 196 int newestPlatformVersion = 0; 197 File[] platforms = new File(sdkRoot, "platforms").listFiles(); 198 if (platforms != null) { 199 for (File platform : platforms) { 200 try { 201 int version = 202 Integer.parseInt(platform.getName().substring("android-".length())); 203 if (version > newestPlatformVersion) { 204 newestPlatform = platform; 205 newestPlatformVersion = version; 206 } 207 } catch (NumberFormatException ignore) { 208 // Ignore non-numeric preview versions like android-Honeycomb 209 } 210 } 211 } 212 if (newestPlatform == null) { 213 throw new IllegalStateException("Cannot find newest platform in " + sdkRoot); 214 } 215 return newestPlatform; 216 } 217 defaultSourcePath()218 public static Collection<File> defaultSourcePath() { 219 return filterNonExistentPathsFrom("libcore/support/src/test/java", 220 "external/mockwebserver/src/main/java/"); 221 } 222 filterNonExistentPathsFrom(String... paths)223 private static Collection<File> filterNonExistentPathsFrom(String... paths) { 224 ArrayList<File> result = new ArrayList<File>(); 225 String buildRoot = System.getenv("ANDROID_BUILD_TOP"); 226 for (String path : paths) { 227 File file = new File(buildRoot, path); 228 if (file.exists()) { 229 result.add(file); 230 } 231 } 232 return result; 233 } 234 getCompilationClasspath()235 public File[] getCompilationClasspath() { 236 return compilationClasspath; 237 } 238 239 /** 240 * Converts all the .class files on 'classpath' into a dex file written to 'output'. 241 */ dex(boolean multidex, File output, Classpath classpath)242 public void dex(boolean multidex, File output, Classpath classpath) { 243 mkdir.mkdirs(output.getParentFile()); 244 245 String classpathSubKey = dexCache.makeKey(classpath); 246 String cacheKey = null; 247 if (classpathSubKey != null) { 248 String multidexSubKey = "mdex=" + multidex; 249 cacheKey = dexCache.makeKey(classpathSubKey, multidexSubKey); 250 boolean cacheHit = dexCache.getFromCache(output, cacheKey); 251 if (cacheHit) { 252 log.verbose("dex cache hit for " + classpath); 253 return; 254 } 255 } 256 257 /* 258 * We pass --core-library so that we can write tests in the 259 * same package they're testing, even when that's a core 260 * library package. If you're actually just using this tool to 261 * execute arbitrary code, this has the unfortunate 262 * side-effect of preventing "dx" from protecting you from 263 * yourself. 264 * 265 * Memory options pulled from build/core/definitions.mk to 266 * handle large dx input when building dex for APK. 267 */ 268 Command.Builder builder = new Command.Builder(log) 269 .args("dx") 270 .args("-JXms16M") 271 .args("-JXmx1536M"); 272 if (multidex) { 273 builder.args("--multi-dex"); 274 } 275 builder.args("--dex") 276 .args("--output=" + output) 277 .args("--core-library") 278 .args((Object[]) Strings.objectsToStrings(classpath.getElements())); 279 builder.execute(); 280 dexCache.insert(cacheKey, output); 281 } 282 packageApk(File apk, File manifest)283 public void packageApk(File apk, File manifest) { 284 new Command(log, "aapt", 285 "package", 286 "-F", apk.getPath(), 287 "-M", manifest.getPath(), 288 "-I", androidJarPath, 289 "--version-name", "1.0", 290 "--version-code", "1").execute(); 291 } 292 addToApk(File apk, File dex)293 public void addToApk(File apk, File dex) { 294 new Command(log, "aapt", "add", "-k", apk.getPath(), dex.getPath()).execute(); 295 } 296 install(File apk)297 public void install(File apk) { 298 new Command(log, "adb", "install", "-r", apk.getPath()).execute(); 299 } 300 uninstall(String packageName)301 public void uninstall(String packageName) { 302 new Command.Builder(log) 303 .args("adb", "uninstall", packageName) 304 .permitNonZeroExitStatus(true) 305 .execute(); 306 } 307 } 308