1*e17b4558SAndroid Build Coastguard Worker /* 2*e17b4558SAndroid Build Coastguard Worker * Copyright (C) 2010 The Android Open Source Project 3*e17b4558SAndroid Build Coastguard Worker * 4*e17b4558SAndroid Build Coastguard Worker * Licensed under the Apache License, Version 2.0 (the "License"); 5*e17b4558SAndroid Build Coastguard Worker * you may not use this file except in compliance with the License. 6*e17b4558SAndroid Build Coastguard Worker * You may obtain a copy of the License at 7*e17b4558SAndroid Build Coastguard Worker * 8*e17b4558SAndroid Build Coastguard Worker * http://www.apache.org/licenses/LICENSE-2.0 9*e17b4558SAndroid Build Coastguard Worker * 10*e17b4558SAndroid Build Coastguard Worker * Unless required by applicable law or agreed to in writing, software 11*e17b4558SAndroid Build Coastguard Worker * distributed under the License is distributed on an "AS IS" BASIS, 12*e17b4558SAndroid Build Coastguard Worker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*e17b4558SAndroid Build Coastguard Worker * See the License for the specific language governing permissions and 14*e17b4558SAndroid Build Coastguard Worker * limitations under the License. 15*e17b4558SAndroid Build Coastguard Worker */ 16*e17b4558SAndroid Build Coastguard Worker 17*e17b4558SAndroid Build Coastguard Worker package vogar; 18*e17b4558SAndroid Build Coastguard Worker 19*e17b4558SAndroid Build Coastguard Worker import com.google.common.annotations.VisibleForTesting; 20*e17b4558SAndroid Build Coastguard Worker import com.google.common.base.Joiner; 21*e17b4558SAndroid Build Coastguard Worker import com.google.common.collect.ImmutableList; 22*e17b4558SAndroid Build Coastguard Worker import java.io.File; 23*e17b4558SAndroid Build Coastguard Worker import java.io.FileNotFoundException; 24*e17b4558SAndroid Build Coastguard Worker import java.util.ArrayList; 25*e17b4558SAndroid Build Coastguard Worker import java.util.List; 26*e17b4558SAndroid Build Coastguard Worker import java.util.Map; 27*e17b4558SAndroid Build Coastguard Worker import vogar.tasks.Task; 28*e17b4558SAndroid Build Coastguard Worker 29*e17b4558SAndroid Build Coastguard Worker /** 30*e17b4558SAndroid Build Coastguard Worker * A target runtime environment such as a remote device or the local host 31*e17b4558SAndroid Build Coastguard Worker */ 32*e17b4558SAndroid Build Coastguard Worker public abstract class Target { 33*e17b4558SAndroid Build Coastguard Worker 34*e17b4558SAndroid Build Coastguard Worker /** 35*e17b4558SAndroid Build Coastguard Worker * Return the process prefix for this target. 36*e17b4558SAndroid Build Coastguard Worker * <p> 37*e17b4558SAndroid Build Coastguard Worker * A process prefix is a list of tokens added in front of a script (VM command) to be run on 38*e17b4558SAndroid Build Coastguard Worker * this target. 39*e17b4558SAndroid Build Coastguard Worker * 40*e17b4558SAndroid Build Coastguard Worker * @see AdbTarget#targetProcessPrefix() 41*e17b4558SAndroid Build Coastguard Worker * @see AdbChrootTarget#targetProcessPrefix() 42*e17b4558SAndroid Build Coastguard Worker * @see SshTarget#targetProcessPrefix() 43*e17b4558SAndroid Build Coastguard Worker * 44*e17b4558SAndroid Build Coastguard Worker * @see Target#targetProcessWrapper() 45*e17b4558SAndroid Build Coastguard Worker * @see ScriptBuilder#commandLine() 46*e17b4558SAndroid Build Coastguard Worker */ 47*e17b4558SAndroid Build Coastguard Worker targetProcessPrefix()48*e17b4558SAndroid Build Coastguard Worker protected abstract ImmutableList<String> targetProcessPrefix(); 49*e17b4558SAndroid Build Coastguard Worker 50*e17b4558SAndroid Build Coastguard Worker /** 51*e17b4558SAndroid Build Coastguard Worker * Return the process wrapper for this target if there is one, or {@code null} otherwise. 52*e17b4558SAndroid Build Coastguard Worker * <p> 53*e17b4558SAndroid Build Coastguard Worker * A process wrapper is a command that is used to execute a script (VM command) to be run on 54*e17b4558SAndroid Build Coastguard Worker * this target. The script, preceded by the process prefix, is surrounded with double quotes 55*e17b4558SAndroid Build Coastguard Worker * and passed as an argument to the process wrapper: 56*e17b4558SAndroid Build Coastguard Worker * <ul> 57*e17b4558SAndroid Build Coastguard Worker * <li>{@code <process-wrapper> "<process-prefix> <script>"} 58*e17b4558SAndroid Build Coastguard Worker * </ul> 59*e17b4558SAndroid Build Coastguard Worker * A {@code null} process wrapper means that the script will be executed as-is (but still 60*e17b4558SAndroid Build Coastguard Worker * preceded by the process prefix): 61*e17b4558SAndroid Build Coastguard Worker * <ul> 62*e17b4558SAndroid Build Coastguard Worker * <li>{@code <process-prefix> <script>} 63*e17b4558SAndroid Build Coastguard Worker * </ul> 64*e17b4558SAndroid Build Coastguard Worker * 65*e17b4558SAndroid Build Coastguard Worker * @see AdbChrootTarget#targetProcessWrapper() 66*e17b4558SAndroid Build Coastguard Worker * 67*e17b4558SAndroid Build Coastguard Worker * @see Target#targetProcessPrefix() 68*e17b4558SAndroid Build Coastguard Worker * @see ScriptBuilder#commandLine() 69*e17b4558SAndroid Build Coastguard Worker */ targetProcessWrapper()70*e17b4558SAndroid Build Coastguard Worker protected String targetProcessWrapper() { 71*e17b4558SAndroid Build Coastguard Worker // By default, targets don't have a process wrapper. 72*e17b4558SAndroid Build Coastguard Worker return null; 73*e17b4558SAndroid Build Coastguard Worker }; 74*e17b4558SAndroid Build Coastguard Worker getDeviceUserName()75*e17b4558SAndroid Build Coastguard Worker public abstract String getDeviceUserName(); 76*e17b4558SAndroid Build Coastguard Worker ls(File directory)77*e17b4558SAndroid Build Coastguard Worker public abstract List<File> ls(File directory) throws FileNotFoundException; await(File nonEmptyDirectory)78*e17b4558SAndroid Build Coastguard Worker public abstract void await(File nonEmptyDirectory); rm(File file)79*e17b4558SAndroid Build Coastguard Worker public abstract void rm(File file); mkdirs(File file)80*e17b4558SAndroid Build Coastguard Worker public abstract void mkdirs(File file); forwardTcp(int port)81*e17b4558SAndroid Build Coastguard Worker public abstract void forwardTcp(int port); push(File local, File remote)82*e17b4558SAndroid Build Coastguard Worker public abstract void push(File local, File remote); pull(File remote, File local)83*e17b4558SAndroid Build Coastguard Worker public abstract void pull(File remote, File local); 84*e17b4558SAndroid Build Coastguard Worker pushTask(final File local, final File remote)85*e17b4558SAndroid Build Coastguard Worker public final Task pushTask(final File local, final File remote) { 86*e17b4558SAndroid Build Coastguard Worker return new Task("push " + remote) { 87*e17b4558SAndroid Build Coastguard Worker @Override protected Result execute() throws Exception { 88*e17b4558SAndroid Build Coastguard Worker push(local, remote); 89*e17b4558SAndroid Build Coastguard Worker return Result.SUCCESS; 90*e17b4558SAndroid Build Coastguard Worker } 91*e17b4558SAndroid Build Coastguard Worker }; 92*e17b4558SAndroid Build Coastguard Worker } 93*e17b4558SAndroid Build Coastguard Worker 94*e17b4558SAndroid Build Coastguard Worker public final Task rmTask(final File remote) { 95*e17b4558SAndroid Build Coastguard Worker return new Task("rm " + remote) { 96*e17b4558SAndroid Build Coastguard Worker @Override protected Result execute() throws Exception { 97*e17b4558SAndroid Build Coastguard Worker rm(remote); 98*e17b4558SAndroid Build Coastguard Worker return Result.SUCCESS; 99*e17b4558SAndroid Build Coastguard Worker } 100*e17b4558SAndroid Build Coastguard Worker }; 101*e17b4558SAndroid Build Coastguard Worker } 102*e17b4558SAndroid Build Coastguard Worker 103*e17b4558SAndroid Build Coastguard Worker /** 104*e17b4558SAndroid Build Coastguard Worker * Create a {@link ScriptBuilder} appropriate for this target. 105*e17b4558SAndroid Build Coastguard Worker */ 106*e17b4558SAndroid Build Coastguard Worker public ScriptBuilder newScriptBuilder() { 107*e17b4558SAndroid Build Coastguard Worker return new ScriptBuilder(targetProcessPrefix(), targetProcessWrapper()); 108*e17b4558SAndroid Build Coastguard Worker } 109*e17b4558SAndroid Build Coastguard Worker 110*e17b4558SAndroid Build Coastguard Worker /** 111*e17b4558SAndroid Build Coastguard Worker * Responsible for constructing a one line script appropriate for a specific target. 112*e17b4558SAndroid Build Coastguard Worker */ 113*e17b4558SAndroid Build Coastguard Worker public static class ScriptBuilder { 114*e17b4558SAndroid Build Coastguard Worker 115*e17b4558SAndroid Build Coastguard Worker private static final Joiner SCRIPT_JOINER = Joiner.on(" "); 116*e17b4558SAndroid Build Coastguard Worker 117*e17b4558SAndroid Build Coastguard Worker /** 118*e17b4558SAndroid Build Coastguard Worker * Escape any special shell characters so that the target shell treats them as literal 119*e17b4558SAndroid Build Coastguard Worker * characters. 120*e17b4558SAndroid Build Coastguard Worker * 121*e17b4558SAndroid Build Coastguard Worker * <p>e.g. an escaped space will not be treated as a token separator, an escaped dollar will 122*e17b4558SAndroid Build Coastguard Worker * not cause shell substitution. 123*e17b4558SAndroid Build Coastguard Worker */ 124*e17b4558SAndroid Build Coastguard Worker @VisibleForTesting 125*e17b4558SAndroid Build Coastguard Worker static String escape(String token) { 126*e17b4558SAndroid Build Coastguard Worker int length = token.length(); 127*e17b4558SAndroid Build Coastguard Worker StringBuilder builder = new StringBuilder(length); 128*e17b4558SAndroid Build Coastguard Worker for (int i = 0; i < length; ++i) { 129*e17b4558SAndroid Build Coastguard Worker char c = token.charAt(i); 130*e17b4558SAndroid Build Coastguard Worker if (Character.isWhitespace(c) 131*e17b4558SAndroid Build Coastguard Worker || c == '\'' || c == '\"' 132*e17b4558SAndroid Build Coastguard Worker || c == '|' || c == '&' 133*e17b4558SAndroid Build Coastguard Worker || c == '<' || c == '>' 134*e17b4558SAndroid Build Coastguard Worker || c == '$' || c == '!' 135*e17b4558SAndroid Build Coastguard Worker || c == '(' || c == ')') { 136*e17b4558SAndroid Build Coastguard Worker builder.append('\\'); 137*e17b4558SAndroid Build Coastguard Worker } 138*e17b4558SAndroid Build Coastguard Worker 139*e17b4558SAndroid Build Coastguard Worker builder.append(c); 140*e17b4558SAndroid Build Coastguard Worker } 141*e17b4558SAndroid Build Coastguard Worker 142*e17b4558SAndroid Build Coastguard Worker return builder.toString(); 143*e17b4558SAndroid Build Coastguard Worker } 144*e17b4558SAndroid Build Coastguard Worker 145*e17b4558SAndroid Build Coastguard Worker /** 146*e17b4558SAndroid Build Coastguard Worker * The prefix to insert before the script to produce a command line that will execute the 147*e17b4558SAndroid Build Coastguard Worker * script within a shell. 148*e17b4558SAndroid Build Coastguard Worker */ 149*e17b4558SAndroid Build Coastguard Worker private final ImmutableList<String> commandLinePrefix; 150*e17b4558SAndroid Build Coastguard Worker 151*e17b4558SAndroid Build Coastguard Worker /** 152*e17b4558SAndroid Build Coastguard Worker * An optional wrapper of the command line. This can be used e.g. to wrap 'COMMAND' into 153*e17b4558SAndroid Build Coastguard Worker * 'sh -c "COMMAND"' before execution. 154*e17b4558SAndroid Build Coastguard Worker */ 155*e17b4558SAndroid Build Coastguard Worker private final String commandLineWrapper; 156*e17b4558SAndroid Build Coastguard Worker 157*e17b4558SAndroid Build Coastguard Worker /** 158*e17b4558SAndroid Build Coastguard Worker * The list of tokens making up the script, they were escaped where necessary before they 159*e17b4558SAndroid Build Coastguard Worker * were added to the list. 160*e17b4558SAndroid Build Coastguard Worker */ 161*e17b4558SAndroid Build Coastguard Worker private final List<String> escapedTokens; 162*e17b4558SAndroid Build Coastguard Worker 163*e17b4558SAndroid Build Coastguard Worker private ScriptBuilder(ImmutableList<String> commandLinePrefix, String commandLineWrapper) { 164*e17b4558SAndroid Build Coastguard Worker this.commandLinePrefix = commandLinePrefix; 165*e17b4558SAndroid Build Coastguard Worker this.commandLineWrapper = commandLineWrapper; 166*e17b4558SAndroid Build Coastguard Worker escapedTokens = new ArrayList<>(); 167*e17b4558SAndroid Build Coastguard Worker } 168*e17b4558SAndroid Build Coastguard Worker 169*e17b4558SAndroid Build Coastguard Worker /** 170*e17b4558SAndroid Build Coastguard Worker * Set the working directory in the target shell before running the command. 171*e17b4558SAndroid Build Coastguard Worker */ 172*e17b4558SAndroid Build Coastguard Worker public ScriptBuilder workingDirectory(File workingDirectory) { 173*e17b4558SAndroid Build Coastguard Worker escapedTokens.add("cd"); 174*e17b4558SAndroid Build Coastguard Worker escapedTokens.add(escape(workingDirectory.getPath())); 175*e17b4558SAndroid Build Coastguard Worker escapedTokens.add("&&"); 176*e17b4558SAndroid Build Coastguard Worker return this; 177*e17b4558SAndroid Build Coastguard Worker } 178*e17b4558SAndroid Build Coastguard Worker 179*e17b4558SAndroid Build Coastguard Worker /** 180*e17b4558SAndroid Build Coastguard Worker * Set inline environment variables on the target shell that will affect the command. 181*e17b4558SAndroid Build Coastguard Worker */ 182*e17b4558SAndroid Build Coastguard Worker public void env(Map<String, String> env) { 183*e17b4558SAndroid Build Coastguard Worker for (Map.Entry<String, String> entry : env.entrySet()) { 184*e17b4558SAndroid Build Coastguard Worker String name = entry.getKey(); 185*e17b4558SAndroid Build Coastguard Worker String value = entry.getValue(); 186*e17b4558SAndroid Build Coastguard Worker escapedTokens.add(name + "=" + escape(value)); 187*e17b4558SAndroid Build Coastguard Worker } 188*e17b4558SAndroid Build Coastguard Worker } 189*e17b4558SAndroid Build Coastguard Worker 190*e17b4558SAndroid Build Coastguard Worker /** 191*e17b4558SAndroid Build Coastguard Worker * Add tokens to the script. 192*e17b4558SAndroid Build Coastguard Worker * 193*e17b4558SAndroid Build Coastguard Worker * <p>Each token is escaped before adding it to the list. This method is suitable for adding 194*e17b4558SAndroid Build Coastguard Worker * the command line name and arguments. 195*e17b4558SAndroid Build Coastguard Worker */ 196*e17b4558SAndroid Build Coastguard Worker public ScriptBuilder tokens(List<String> tokens) { 197*e17b4558SAndroid Build Coastguard Worker for (String token : tokens) { 198*e17b4558SAndroid Build Coastguard Worker escapedTokens.add(escape(token)); 199*e17b4558SAndroid Build Coastguard Worker } 200*e17b4558SAndroid Build Coastguard Worker return this; 201*e17b4558SAndroid Build Coastguard Worker } 202*e17b4558SAndroid Build Coastguard Worker 203*e17b4558SAndroid Build Coastguard Worker /** 204*e17b4558SAndroid Build Coastguard Worker * Add tokens to the script. 205*e17b4558SAndroid Build Coastguard Worker * 206*e17b4558SAndroid Build Coastguard Worker * <p>Each token is escaped before adding it to the list. This method is suitable for adding 207*e17b4558SAndroid Build Coastguard Worker * the command line name and arguments. 208*e17b4558SAndroid Build Coastguard Worker */ 209*e17b4558SAndroid Build Coastguard Worker public ScriptBuilder tokens(String... tokens) { 210*e17b4558SAndroid Build Coastguard Worker for (String token : tokens) { 211*e17b4558SAndroid Build Coastguard Worker escapedTokens.add(escape(token)); 212*e17b4558SAndroid Build Coastguard Worker } 213*e17b4558SAndroid Build Coastguard Worker return this; 214*e17b4558SAndroid Build Coastguard Worker } 215*e17b4558SAndroid Build Coastguard Worker 216*e17b4558SAndroid Build Coastguard Worker /** 217*e17b4558SAndroid Build Coastguard Worker * Construct a command line to execute the script in the target shell. 218*e17b4558SAndroid Build Coastguard Worker */ 219*e17b4558SAndroid Build Coastguard Worker public List<String> commandLine() { 220*e17b4558SAndroid Build Coastguard Worker // Group the tokens into a single String argument. This is necessary for running in 221*e17b4558SAndroid Build Coastguard Worker // a local shell where the first argument after the -c option is the script and the 222*e17b4558SAndroid Build Coastguard Worker // remainder are treated as arguments to the script. This has no effect on either 223*e17b4558SAndroid Build Coastguard Worker // adb or ssh shells as they both concatenate all their arguments into one single 224*e17b4558SAndroid Build Coastguard Worker // string before parsing. 225*e17b4558SAndroid Build Coastguard Worker String grouped = SCRIPT_JOINER.join(escapedTokens); 226*e17b4558SAndroid Build Coastguard Worker // Honor a wrapper if there is one. 227*e17b4558SAndroid Build Coastguard Worker if (commandLineWrapper != null) { 228*e17b4558SAndroid Build Coastguard Worker grouped = commandLineWrapper + " \"" + grouped + "\""; 229*e17b4558SAndroid Build Coastguard Worker } 230*e17b4558SAndroid Build Coastguard Worker return new ImmutableList.Builder<String>() 231*e17b4558SAndroid Build Coastguard Worker .addAll(commandLinePrefix) 232*e17b4558SAndroid Build Coastguard Worker .add(grouped) 233*e17b4558SAndroid Build Coastguard Worker .build(); 234*e17b4558SAndroid Build Coastguard Worker } 235*e17b4558SAndroid Build Coastguard Worker } 236*e17b4558SAndroid Build Coastguard Worker } 237