1 /* 2 * Copyright (C) 2015 Google Inc. 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 com.google.caliper.platform.dalvik; 18 19 import static com.google.common.base.Preconditions.checkState; 20 21 import com.google.caliper.platform.Platform; 22 import com.google.common.base.Preconditions; 23 import com.google.common.base.Predicate; 24 import com.google.common.base.Predicates; 25 import com.google.common.collect.ImmutableList; 26 import com.google.common.collect.ImmutableSet; 27 import com.google.common.base.Joiner; 28 29 import java.io.File; 30 import java.util.Collection; 31 import java.util.Collections; 32 import java.util.Map; 33 34 /** 35 * An abstraction of the Dalvik (aka Android) platform. 36 * 37 * <p>Although this talks about dalvik it actually works with ART too. 38 */ 39 public final class DalvikPlatform extends Platform { 40 41 // By default, use dalvikvm. 42 // However with --vm (by calling customVmHomeDir) we can change this. 43 private String vmExecutable = "dalvikvm"; 44 private String vmAndroidRoot = System.getenv("ANDROID_ROOT"); 45 DalvikPlatform()46 public DalvikPlatform() { 47 super(Type.DALVIK); 48 49 if (vmAndroidRoot == null) { 50 // Shouldn't happen unless running it on the host and someone forgot to set it. 51 // On an actual device, ANDROID_ROOT is always set. 52 vmAndroidRoot = "/system"; 53 } 54 } 55 56 @Override vmExecutable(File vmHome)57 public File vmExecutable(File vmHome) { 58 File bin = new File(vmHome, "bin"); 59 Preconditions.checkState(bin.exists() && bin.isDirectory(), 60 "Could not find %s under android root %s", bin, vmHome); 61 String executableName = vmExecutable; 62 File dalvikvm = new File(bin, executableName); 63 if (!dalvikvm.exists() || dalvikvm.isDirectory()) { 64 throw new IllegalStateException( 65 String.format("Cannot find %s binary in %s", executableName, bin)); 66 } 67 68 return dalvikvm; 69 } 70 71 @Override commonInstrumentVmArgs()72 public ImmutableSet<String> commonInstrumentVmArgs() { 73 return ImmutableSet.of(); 74 } 75 76 @Override workerProcessArgs()77 public ImmutableSet<String> workerProcessArgs() { 78 if (vmExecutable.equals("app_process")) { 79 // app_process expects a parent_dir argument before the main class name, e.g. 80 // app_process -cp /path/to/dex/file /system/bin foo.bar.Main 81 return ImmutableSet.of(vmAndroidRoot + "/bin"); 82 } 83 return ImmutableSet.of(); 84 } 85 86 @Override workerClassPath()87 protected String workerClassPath() { 88 // TODO(user): Find a way to get the class path programmatically from the class loader. 89 return System.getProperty("java.class.path"); 90 } 91 92 /** 93 * Construct the set of arguments that specify the classpath which 94 * is passed to the worker. 95 * 96 * <p> 97 * Use {@code -Djava.class.path=$classpath} for dalvik because it is supported 98 * by dalvikvm and also by app_process (which doesn't recognize "-cp args"). 99 * </p> 100 */ 101 @Override workerClassPathArgs()102 public ImmutableList<String> workerClassPathArgs() { 103 String classPathArgs = String.format("-Djava.class.path=%s", workerClassPath()); 104 return ImmutableList.of(classPathArgs); 105 } 106 107 @Override inputArguments()108 public Collection<String> inputArguments() { 109 return Collections.emptyList(); 110 } 111 112 @Override vmPropertiesToRetain()113 public Predicate<String> vmPropertiesToRetain() { 114 return Predicates.alwaysFalse(); 115 } 116 117 @Override checkVmProperties(Map<String, String> options)118 public void checkVmProperties(Map<String, String> options) { 119 checkState(options.isEmpty()); 120 } 121 122 @Override customVmHomeDir(Map<String, String> vmGroupMap, String vmConfigName)123 public File customVmHomeDir(Map<String, String> vmGroupMap, String vmConfigName) { 124 // Support a handful of specific commands: 125 switch (vmConfigName) { 126 case "app_process": // run with Android framework code already initialized. 127 case "dalvikvm": // same as not using --vm (selects 64-bit on 64-bit, 32-bit on 32-bit) 128 case "dalvikvm32": // 32-bit specific dalvikvm (e.g. if running on 64-bit device) 129 case "dalvikvm64": // 64-bit specific dalvikvm (which is already default on 64-bit) 130 case "art": // similar to dalvikvm but goes through a script with better settings. 131 { 132 // Usually passed as vmHome to vmExecutable, but we don't get that passed here. 133 String vmHome = vmAndroidRoot; 134 135 // Do not return the binary here. We return the new vmHome used by #vmExecutable. 136 // Remember that the home directory was changed, and accordingly update the executable name. 137 vmExecutable = vmConfigName; 138 139 File androidRootFile = new File(vmHome); 140 141 if (!androidRootFile.exists()) { 142 throw new IllegalStateException(String.format("%s does not exist", androidRootFile)); 143 } else if (!androidRootFile.isDirectory()) { 144 throw new IllegalStateException(String.format("%s is not a directory", androidRootFile)); 145 } 146 147 return androidRootFile; 148 } 149 } 150 151 // Unknown vm, throw an exception. 152 153 Joiner.MapJoiner mapJoiner = Joiner.on(',').withKeyValueSeparator("="); 154 String mapString = (vmGroupMap == null) ? "<null>" : mapJoiner.join(vmGroupMap); 155 156 if (vmConfigName == null) { 157 vmConfigName = "<null>"; 158 } 159 160 throw new UnsupportedOperationException( 161 "Running with a custom Dalvik VM is not currently supported (group map = '" + mapString 162 + "', vmConfigName = '" + vmConfigName + "')"); 163 } 164 } 165