1/*
2 * Copyright (C) 2011 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
17package vogar;
18
19import com.google.common.base.Splitter;
20import java.io.File;
21import java.io.IOException;
22import java.net.URL;
23import java.util.Collections;
24import java.util.Date;
25import java.util.HashSet;
26import java.util.List;
27import java.util.Set;
28import java.util.UUID;
29import vogar.android.ActivityMode;
30import vogar.android.AdbTarget;
31import vogar.android.AndroidSdk;
32import vogar.android.DeviceFileCache;
33import vogar.android.DeviceRuntime;
34import vogar.android.HostRuntime;
35import vogar.commands.Mkdir;
36import vogar.commands.Rm;
37import vogar.tasks.TaskQueue;
38import vogar.util.Strings;
39
40public final class Run {
41    /** A list of generic names that we avoid when naming generated files. */
42    private static final Set<String> BANNED_NAMES = new HashSet<String>();
43    static {
44        BANNED_NAMES.add("classes");
45        BANNED_NAMES.add("javalib");
46    }
47
48    public final File xmlReportsDirectory;
49    public final File resultsDir;
50    public final boolean recordResults;
51    public final ExpectationStore expectationStore;
52    public final Date date;
53    public final String invokeWith;
54    public final File keystore;
55    public final Log log;
56    public final Classpath classpath;
57    public final Classpath buildClasspath;
58    public final Classpath resourceClasspath;
59    public final List<File> sourcepath;
60    public final Mkdir mkdir;
61    public final Rm rm;
62    public final int firstMonitorPort;
63    public final int timeoutSeconds;
64    public final boolean profile;
65    public final boolean profileBinary;
66    public final int profileDepth;
67    public final int profileInterval;
68    public final boolean profileThreadGroup;
69    public final File profileFile;
70    public final File javaHome;
71    public final Integer debugPort;
72    public final List<String> javacArgs;
73    public final boolean benchmark;
74    public final File runnerDir;
75    public final boolean cleanBefore;
76    public final boolean cleanAfter;
77    public final File localTemp;
78    public final int maxConcurrentActions;
79    public final File deviceUserHome;
80    public final Console console;
81    public final int smallTimeoutSeconds;
82    public final String vmCommand;
83    public final String dalvikCache;
84    public final List<String> additionalVmArgs;
85    public final List<String> targetArgs;
86    public final boolean useBootClasspath;
87    public final int largeTimeoutSeconds;
88    public final RetrievedFilesFilter retrievedFiles;
89    public final Driver driver;
90    public final Mode mode;
91    public final Target target;
92    public final AndroidSdk androidSdk;
93    public final XmlReportPrinter reportPrinter;
94    public final JarSuggestions jarSuggestions;
95    public final ClassFileIndex classFileIndex;
96    public final OutcomeStore outcomeStore;
97    public final TaskQueue taskQueue;
98    public final boolean testOnly;
99
100    public Run(Vogar vogar) throws IOException {
101        this.console = vogar.stream
102                ? new Console.StreamingConsole()
103                : new Console.MultiplexingConsole();
104        console.setUseColor(
105            vogar.color, vogar.passColor, vogar.skipColor, vogar.failColor, vogar.warnColor);
106        console.setAnsi(vogar.ansi);
107        console.setIndent(vogar.indent);
108        console.setVerbose(vogar.verbose);
109
110        this.localTemp = new File("/tmp/vogar/" + UUID.randomUUID());
111        this.log = console;
112
113        if (vogar.sshHost != null) {
114            this.target = new SshTarget(vogar.sshHost, log);
115        } else if (vogar.modeId.isLocal()) {
116            this.target = new LocalTarget(this);
117        } else {
118            this.target = new AdbTarget(this);
119        }
120
121        this.vmCommand = vogar.vmCommand;
122        this.dalvikCache = vogar.dalvikCache;
123        this.additionalVmArgs = vogar.vmArgs;
124        this.benchmark = vogar.benchmark;
125        this.cleanBefore = vogar.cleanBefore;
126        this.cleanAfter = vogar.cleanAfter;
127        this.date = new Date();
128        this.debugPort = vogar.debugPort;
129        this.runnerDir = vogar.deviceDir != null
130                ? new File(vogar.deviceDir, "run")
131                : new File(target.defaultDeviceDir(), "run");
132        this.deviceUserHome = new File(runnerDir, "user.home");
133        this.mkdir = new Mkdir(console);
134        this.rm = new Rm(console);
135        this.firstMonitorPort = vogar.firstMonitorPort;
136        this.invokeWith = vogar.invokeWith;
137        this.javacArgs = vogar.javacArgs;
138        this.javaHome = vogar.javaHome;
139        this.largeTimeoutSeconds = vogar.timeoutSeconds * Vogar.LARGE_TIMEOUT_MULTIPLIER;
140        this.maxConcurrentActions = (vogar.stream || vogar.modeId == ModeId.ACTIVITY)
141                    ? 1
142                    : Vogar.NUM_PROCESSORS;
143        this.timeoutSeconds = vogar.timeoutSeconds;
144        this.smallTimeoutSeconds = vogar.timeoutSeconds;
145        this.sourcepath = vogar.sourcepath;
146        this.resourceClasspath = Classpath.of(vogar.resourceClasspath);
147        this.useBootClasspath = vogar.useBootClasspath;
148        this.targetArgs = vogar.targetArgs;
149        this.xmlReportsDirectory = vogar.xmlReportsDirectory;
150        this.profile = vogar.profile;
151        this.profileBinary = vogar.profileBinary;
152        this.profileFile = vogar.profileFile;
153        this.profileDepth = vogar.profileDepth;
154        this.profileInterval = vogar.profileInterval;
155        this.profileThreadGroup = vogar.profileThreadGroup;
156        this.recordResults = vogar.recordResults;
157        this.resultsDir =  vogar.resultsDir == null
158                ? new File(vogar.vogarDir, "results")
159                : vogar.resultsDir;
160        this.keystore = localFile("activity", "vogar.keystore");
161        this.classpath = Classpath.of(vogar.classpath);
162        this.classpath.addAll(vogarJar());
163        this.testOnly = vogar.testOnly;
164
165        if (vogar.modeId.requiresAndroidSdk()) {
166            androidSdk = new AndroidSdk(log, mkdir, vogar.modeId);
167            androidSdk.setCaches(new HostFileCache(log, mkdir),
168                    new DeviceFileCache(log, runnerDir, androidSdk));
169        } else {
170            androidSdk = null;
171        }
172
173        expectationStore = ExpectationStore.parse(
174            console, vogar.expectationFiles, vogar.modeId, vogar.variant);
175        if (vogar.openBugsCommand != null) {
176            expectationStore.loadBugStatuses(new CommandBugDatabase(log, vogar.openBugsCommand));
177        }
178
179        this.mode = createMode(vogar.modeId, vogar.variant);
180
181        this.buildClasspath = Classpath.of(vogar.buildClasspath);
182        if (vogar.modeId.requiresAndroidSdk()) {
183            buildClasspath.addAll(androidSdk.getCompilationClasspath());
184        }
185
186        this.classFileIndex = new ClassFileIndex(log, mkdir, vogar.jarSearchDirs);
187        if (vogar.suggestClasspaths) {
188            classFileIndex.createIndex();
189        }
190
191        this.retrievedFiles = new RetrievedFilesFilter(profile, profileFile);
192        this.reportPrinter = new XmlReportPrinter(xmlReportsDirectory, expectationStore, date);
193        this.jarSuggestions = new JarSuggestions();
194        this.outcomeStore = new OutcomeStore(log, mkdir, rm, resultsDir, recordResults,
195                expectationStore, date);
196        this.driver = new Driver(this);
197        this.taskQueue = new TaskQueue(console, maxConcurrentActions);
198    }
199
200    private Mode createMode(ModeId modeId, Variant variant) {
201        switch (modeId) {
202            case JVM:
203                return new JavaVm(this);
204            case HOST:
205            case HOST_DALVIK:
206            case HOST_ART_KITKAT:
207                return new HostRuntime(this, modeId, variant);
208            case DEVICE:
209            case DEVICE_DALVIK:
210            case DEVICE_ART_KITKAT:
211            case APP_PROCESS:
212                return new DeviceRuntime(this, modeId, variant);
213            case ACTIVITY:
214                return new ActivityMode(this);
215            default:
216                throw new IllegalArgumentException("Unsupported mode: " + modeId);
217        }
218    }
219
220    public final File localFile(Object... path) {
221        return new File(localTemp + "/" + Strings.join("/", path));
222    }
223
224    private File vogarJar() {
225        URL jarUrl = Vogar.class.getResource("/vogar/Vogar.class");
226        if (jarUrl == null) {
227            // should we add an option for IDE users, to use a user-specified vogar.jar?
228            throw new IllegalStateException("Vogar cannot find its own .jar");
229        }
230
231        /*
232         * Parse a URI like jar:file:/Users/jessewilson/vogar/vogar.jar!/vogar/Vogar.class
233         * to yield a .jar file like /Users/jessewilson/vogar/vogar.jar.
234         */
235        String url = jarUrl.toString();
236        int bang = url.indexOf("!");
237        String JAR_URI_PREFIX = "jar:file:";
238        if (url.startsWith(JAR_URI_PREFIX) && bang != -1) {
239            return new File(url.substring(JAR_URI_PREFIX.length(), bang));
240        } else {
241            throw new IllegalStateException("Vogar cannot find the .jar file in " + jarUrl);
242        }
243    }
244
245    public final File hostJar(Object nameOrAction) {
246        return localFile(nameOrAction, nameOrAction + ".jar");
247    }
248
249    /**
250     * Returns a path for a Java tool such as java, javac, jar where
251     * the Java home is used if present, otherwise assumes it will
252     * come from the path.
253     */
254    public String javaPath(String tool) {
255        return (javaHome == null)
256            ? tool
257            : new File(new File(javaHome, "bin"), tool).getPath();
258    }
259
260    public File targetDexFile(String name) {
261        return new File(runnerDir, name + ".dex.jar");
262    }
263
264    public File localDexFile(String name) {
265        return localFile(name, name + ".dex.jar");
266    }
267
268    /**
269     * Returns a recognizable readable name for the given generated .jar file,
270     * appropriate for use in naming derived files.
271     *
272     * @param file a product of the android build system, such as
273     *     "out/core-libart_intermediates/javalib.jar".
274     * @return a recognizable base name like "core-libart_intermediates".
275     */
276    public String basenameOfJar(File file) {
277        String name = file.getName().replaceAll("\\.jar$", "");
278        while (BANNED_NAMES.contains(name)) {
279            file = file.getParentFile();
280            name = file.getName();
281        }
282        return name;
283    }
284
285    public File vogarTemp() {
286        return new File(runnerDir, "tmp");
287    }
288
289    public File dalvikCache() {
290        return new File(runnerDir.getParentFile(), dalvikCache);
291    }
292
293    /**
294     * Returns an environment variable assignment to configure where the VM will
295     * store its dexopt files. This must be set on production devices and is
296     * optional for development devices.
297     */
298    public String getAndroidData() {
299        // The VM wants the parent directory of a directory named "dalvik-cache"
300        return "ANDROID_DATA=" + dalvikCache().getParentFile();
301    }
302
303    /**
304     * Returns a parsed list of the --invoke-with command and its
305     * arguments, or an empty list if no --invoke-with was provided.
306     */
307    public Iterable<String> invokeWith() {
308        if (invokeWith == null) {
309            return Collections.emptyList();
310        }
311        return Splitter.onPattern("\\s+").omitEmptyStrings().split(invokeWith);
312    }
313}
314