xref: /aosp_15_r20/external/vogar/src/vogar/Target.java (revision e17b455832b152077f3f109e08c3a59761ae9ee5)
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