17850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com/* 27850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Copyright (C) 2010 The Android Open Source Project 37850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * 47850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Licensed under the Apache License, Version 2.0 (the "License"); 57850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * you may not use this file except in compliance with the License. 67850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * You may obtain a copy of the License at 77850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * 87850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * http://www.apache.org/licenses/LICENSE-2.0 97850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * 107850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Unless required by applicable law or agreed to in writing, software 117850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * distributed under the License is distributed on an "AS IS" BASIS, 127850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 137850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * See the License for the specific language governing permissions and 147850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * limitations under the License. 157850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com */ 167850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com 177850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.compackage vogar; 187850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com 19411cb1fb67241125203629229600ecbd821eb9c7Paul Duffinimport com.google.common.annotations.VisibleForTesting; 20411cb1fb67241125203629229600ecbd821eb9c7Paul Duffinimport com.google.common.base.Joiner; 21411cb1fb67241125203629229600ecbd821eb9c7Paul Duffinimport com.google.common.collect.ImmutableList; 227850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.comimport java.io.File; 230eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.comimport java.io.FileNotFoundException; 24411cb1fb67241125203629229600ecbd821eb9c7Paul Duffinimport java.util.ArrayList; 250eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.comimport java.util.List; 26411cb1fb67241125203629229600ecbd821eb9c7Paul Duffinimport java.util.Map; 27f83be5e4273263df2bb9ef609946b911695b3996jessewilson@google.comimport vogar.tasks.Task; 287850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com 297850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com/** 307850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * A target runtime environment such as a remote device or the local host 317850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com */ 320eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.compublic abstract class Target { 33411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin protected abstract ImmutableList<String> targetProcessPrefix(); 340eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com public abstract String getDeviceUserName(); 350eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com 360eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com public abstract List<File> ls(File directory) throws FileNotFoundException; 370eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com public abstract void await(File nonEmptyDirectory); 380eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com public abstract void rm(File file); 390eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com public abstract void mkdirs(File file); 400eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com public abstract void forwardTcp(int port); 410eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com public abstract void push(File local, File remote); 420eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com public abstract void pull(File remote, File local); 430eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com 440eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com public final Task pushTask(final File local, final File remote) { 450eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com return new Task("push " + remote) { 460eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com @Override protected Result execute() throws Exception { 470eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com push(local, remote); 480eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com return Result.SUCCESS; 490eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com } 500eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com }; 510eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com } 520eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com 530eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com public final Task rmTask(final File remote) { 540eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com return new Task("rm " + remote) { 550eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com @Override protected Result execute() throws Exception { 560eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com rm(remote); 570eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com return Result.SUCCESS; 580eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com } 590eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com }; 600eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com } 61411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin 62411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin /** 63411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin * Create a {@link ScriptBuilder} appropriate for this target. 64411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin */ 65411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin public ScriptBuilder newScriptBuilder() { 66411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin return new ScriptBuilder(targetProcessPrefix()); 67411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin } 68411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin 69411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin /** 70411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin * Responsible for constructing a one line script appropriate for a specific target. 71411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin */ 72411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin public static class ScriptBuilder { 73411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin 74411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin private static final Joiner SCRIPT_JOINER = Joiner.on(" "); 75411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin 76411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin /** 77411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin * Escape any special shell characters so that the target shell treats them as literal 78411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin * characters. 79411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin * 809a8263b4e14fe4d9498db2c44130eae47695444cPaul Duffin * <p>e.g. an escaped space will not be treated as a token separator, an escaped dollar will 81411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin * not cause shell substitution. 82411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin */ 83411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin @VisibleForTesting 84411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin static String escape(String token) { 85411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin int length = token.length(); 86411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin StringBuilder builder = new StringBuilder(length); 87411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin for (int i = 0; i < length; ++i) { 88411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin char c = token.charAt(i); 89411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin if (Character.isWhitespace(c) 90411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin || c == '\'' || c == '\"' 91411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin || c == '|' || c == '&' 92411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin || c == '<' || c == '>' 93411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin || c == '$' || c == '!' 94411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin || c == '(' || c == ')') { 95411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin builder.append('\\'); 96411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin } 97411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin 98411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin builder.append(c); 99411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin } 100411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin 101411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin return builder.toString(); 102411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin } 103411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin 104411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin /** 105411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin * The prefix to insert before the script to produce a command line that will execute the 106411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin * script within a shell. 107411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin */ 108411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin private final ImmutableList<String> commandLinePrefix; 109411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin 110411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin /** 111411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin * The list of tokens making up the script, they were escaped where necessary before they 112411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin * were added to the list. 113411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin */ 114411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin private final List<String> escapedTokens; 115411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin 116411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin private ScriptBuilder(ImmutableList<String> commandLinePrefix) { 117411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin this.commandLinePrefix = commandLinePrefix; 118411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin escapedTokens = new ArrayList<>(); 119411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin } 120411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin 121411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin /** 122411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin * Set the working directory in the target shell before running the command. 123411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin */ 124411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin public ScriptBuilder workingDirectory(File workingDirectory) { 125411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin escapedTokens.add("cd"); 126411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin escapedTokens.add(escape(workingDirectory.getPath())); 127411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin escapedTokens.add("&&"); 128411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin return this; 129411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin } 130411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin 131411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin /** 132411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin * Set inline environment variables on the target shell that will affect the command. 133411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin */ 134411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin public void env(Map<String, String> env) { 135411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin for (Map.Entry<String, String> entry : env.entrySet()) { 136411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin String name = entry.getKey(); 137411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin String value = entry.getValue(); 138411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin escapedTokens.add(name + "=" + escape(value)); 139411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin } 140411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin } 141411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin 142411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin /** 143411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin * Add tokens to the script. 144411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin * 145411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin * <p>Each token is escaped before adding it to the list. This method is suitable for adding 146411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin * the command line name and arguments. 147411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin */ 148411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin public ScriptBuilder tokens(List<String> tokens) { 149411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin for (String token : tokens) { 150411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin escapedTokens.add(escape(token)); 151411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin } 152411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin return this; 153411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin } 154411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin 155411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin /** 156411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin * Add tokens to the script. 157411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin * 158411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin * <p>Each token is escaped before adding it to the list. This method is suitable for adding 159411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin * the command line name and arguments. 160411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin */ 161411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin public ScriptBuilder tokens(String... tokens) { 162411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin for (String token : tokens) { 163411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin escapedTokens.add(escape(token)); 164411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin } 165411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin return this; 166411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin } 167411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin 168411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin /** 169411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin * Construct a command line to execute the script in the target shell. 170411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin */ 171411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin public List<String> commandLine() { 172411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin // Group the tokens into a single String argument. This is necessary for running in 173411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin // a local shell where the first argument after the -c option is the script and the 174411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin // remainder are treated as arguments to the script. This has no effect on either 175411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin // adb or ssh shells as they both concatenate all their arguments into one single 176411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin // string before parsing. 177411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin String grouped = SCRIPT_JOINER.join(escapedTokens); 178411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin return new ImmutableList.Builder<String>() 179411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin .addAll(commandLinePrefix) 180411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin .add(grouped) 181411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin .build(); 182411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin } 183411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin } 1847850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com} 185