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(File output, Classpath classpath)242 public void dex(File output, Classpath classpath) { 243 mkdir.mkdirs(output.getParentFile()); 244 245 String key = dexCache.makeKey(classpath); 246 if (key != null) { 247 boolean cacheHit = dexCache.getFromCache(output, key); 248 if (cacheHit) { 249 log.verbose("dex cache hit for " + classpath); 250 return; 251 } 252 } 253 254 /* 255 * We pass --core-library so that we can write tests in the 256 * same package they're testing, even when that's a core 257 * library package. If you're actually just using this tool to 258 * execute arbitrary code, this has the unfortunate 259 * side-effect of preventing "dx" from protecting you from 260 * yourself. 261 * 262 * Memory options pulled from build/core/definitions.mk to 263 * handle large dx input when building dex for APK. 264 */ 265 new Command.Builder(log) 266 .args("dx") 267 .args("-JXms16M") 268 .args("-JXmx1536M") 269 .args("--dex") 270 .args("--output=" + output) 271 .args("--core-library") 272 .args((Object[]) Strings.objectsToStrings(classpath.getElements())).execute(); 273 dexCache.insert(key, output); 274 } 275 packageApk(File apk, File manifest)276 public void packageApk(File apk, File manifest) { 277 new Command(log, "aapt", 278 "package", 279 "-F", apk.getPath(), 280 "-M", manifest.getPath(), 281 "-I", androidJarPath, 282 "--version-name", "1.0", 283 "--version-code", "1").execute(); 284 } 285 addToApk(File apk, File dex)286 public void addToApk(File apk, File dex) { 287 new Command(log, "aapt", "add", "-k", apk.getPath(), dex.getPath()).execute(); 288 } 289 install(File apk)290 public void install(File apk) { 291 new Command(log, "adb", "install", "-r", apk.getPath()).execute(); 292 } 293 uninstall(String packageName)294 public void uninstall(String packageName) { 295 new Command.Builder(log) 296 .args("adb", "uninstall", packageName) 297 .permitNonZeroExitStatus(true) 298 .execute(); 299 } 300 } 301