1 /*
2  * Copyright (C) 2009 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 
17 package vogar;
18 
19 import com.google.common.annotations.VisibleForTesting;
20 import com.google.common.collect.ImmutableList;
21 import com.google.common.collect.Lists;
22 
23 import java.io.File;
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.Set;
28 import java.util.LinkedHashSet;
29 
30 import vogar.android.AdbTarget;
31 import vogar.android.AndroidSdk;
32 import vogar.android.DeviceFileCache;
33 import vogar.android.DeviceFilesystem;
34 import vogar.commands.Mkdir;
35 import vogar.commands.Rm;
36 import vogar.util.Strings;
37 
38 /**
39  * Command line interface for running benchmarks and tests on dalvik.
40  */
41 public final class Vogar {
42     static final int LARGE_TIMEOUT_MULTIPLIER = 10;
43     public static final int NUM_PROCESSORS = Runtime.getRuntime().availableProcessors();
44 
45     private final List<File> actionFiles = new ArrayList<File>();
46     private final List<String> actionClassesAndPackages = new ArrayList<String>();
47     final List<String> targetArgs = new ArrayList<String>();
48     private final OptionParser optionParser = new OptionParser(this);
49     private File configFile = Vogar.dotFile(".vogarconfig");
50     private String[] configArgs;
51     public final static Console console = new Console.StreamingConsole();
52 
dotFile(String name)53     public static File dotFile (String name) {
54         return new File(System.getProperty("user.home", "."), name);
55     }
56 
57     @Option(names = { "--expectations" })
58     Set<File> expectationFiles = new LinkedHashSet<File>();
59     {
AndroidSdk.defaultExpectations()60         expectationFiles.addAll(AndroidSdk.defaultExpectations());
61     }
62 
63     @Option(names = { "--mode" })
64     ModeId modeId = ModeId.DEVICE;
65 
66     @Option(names = { "--variant" })
67     Variant variant = Variant.X32;
68 
69     @Option(names = { "--ssh" })
70     private String sshHost;
71 
72     @Option(names = { "--timeout" })
73     int timeoutSeconds = 60; // default is one minute;
74 
75     @Option(names = { "--first-monitor-port" })
76     int firstMonitorPort = -1;
77 
78     @Option(names = { "--clean-before" })
79     boolean cleanBefore = true;
80 
81     @Option(names = { "--clean-after" })
82     boolean cleanAfter = true;
83 
84     @Option(names = { "--clean" })
85     private boolean clean = true;
86 
87     @Option(names = { "--xml-reports-directory" })
88     File xmlReportsDirectory;
89 
90     @Option(names = { "--indent" })
91     private String indent = "  ";
92 
93     @Option(names = { "--verbose" })
94     private boolean verbose;
95 
96     @Option(names = { "--stream" })
97     boolean stream = true;
98 
99     @Option(names = { "--color" })
100     private boolean color = true;
101 
102     @Option(names = { "--pass-color" })
103     private int passColor = 32; // green
104 
105     @Option(names = { "--skip-color" })
106     private int skipColor = 33; // yellow
107 
108     @Option(names = { "--fail-color" })
109     private int failColor = 31; // red
110 
111     @Option(names = { "--warn-color" })
112     private int warnColor = 35; // purple
113 
114     @Option(names = { "--ansi" })
115     private boolean ansi = !"dumb".equals(System.getenv("TERM"));
116 
117     @Option(names = { "--debug" })
118     Integer debugPort;
119 
120     @Option(names = { "--debug-app" })
121     boolean debugApp;
122 
123     @Option(names = { "--device-dir" })
124     private File deviceDir;
125 
126     @Option(names = { "--vm-arg" })
127     List<String> vmArgs = new ArrayList<String>();
128 
129     @Option(names = { "--vm-command" })
130     String vmCommand;
131 
132     @Option(names = { "--dalvik-cache" })
133     String dalvikCache = "dalvik-cache";
134 
135     @Option(names = { "--java-home" })
136     File javaHome;
137 
138     @Option(names = { "--javac-arg" })
139     List<String> javacArgs = new ArrayList<String>();
140 
141     @Option(names = { "--multidex" })
142     boolean multidex = true;
143 
144     @Option(names = { "--use-bootclasspath" })
145     boolean useBootClasspath = false;
146 
147     @Option(names = { "--build-classpath" })
148     List<File> buildClasspath = new ArrayList<File>();
149 
150     @Option(names = { "--classpath", "-cp" })
151     List<File> classpath = new ArrayList<File>();
152 
153     @Option(names = { "--resource-classpath" })
154     List<File> resourceClasspath = new ArrayList<File>();
155 
156     @Option(names = { "--sourcepath" })
157     List<File> sourcepath = new ArrayList<File>();
158     {
AndroidSdk.defaultSourcePath()159         sourcepath.addAll(AndroidSdk.defaultSourcePath());
160     }
161 
162     @Option(names = { "--jar-search-dir" })
163     List<File> jarSearchDirs = Lists.newArrayList();
164 
165     @Option(names = { "--vogar-dir" })
166     File vogarDir = Vogar.dotFile(".vogar");
167 
168     @Option(names = { "--record-results" })
169     boolean recordResults = false;
170 
171     @Option(names = { "--results-dir" })
172     File resultsDir = null;
173 
174     @Option(names = { "--suggest-classpaths" })
175     boolean suggestClasspaths = false;
176 
177     @Option(names = { "--invoke-with" })
178     String invokeWith = null;
179 
180     @Option(names = { "--benchmark" })
181     boolean benchmark = false;
182 
183     @Option(names = { "--open-bugs-command" })
184     String openBugsCommand;
185 
186     @Option(names = { "--test-only" })
187     boolean testOnly = false;
188 
189     @Option(names = { "--toolchain" })
190     Toolchain toolchain = null;
191 
192     @Option(names = { "--language" })
193     Language language = Language.CUR;
194 
195     @Option(names = { "--check-jni" })
196     boolean checkJni = true;
197 
198     @Option(names = {"--runner-type"})
199     RunnerType runnerType;
200 
Vogar()201     @VisibleForTesting public Vogar() {}
202 
printUsage()203     private void printUsage() {
204         // have to reset fields so that "Default is: FOO" lines are accurate
205         optionParser.reset();
206 
207         System.out.println("Usage: Vogar [options]... <actions>... [-- target args]...");
208         System.out.println();
209         System.out.println("  <actions>: .java files, directories, or class names.");
210         System.out.println("      These should be JUnit tests, jtreg tests, Caliper benchmarks");
211         System.out.println("      or executable Java classes.");
212         System.out.println();
213         System.out.println("      When passing in a JUnit test class, it may have \"#method_name\"");
214         System.out.println("      appended to it, to specify a single test method.");
215         System.out.println();
216         System.out.println("  [target args]: arguments passed to the target process. This is only useful when");
217         System.out.println("      the target process is a Caliper benchmark or main method.");
218         System.out.println();
219         System.out.println("GENERAL OPTIONS");
220         System.out.println();
221         System.out.println("  --mode <ACTIVITY|APP_PROCESS|DEVICE|HOST|JVM>: specify which environment to run in.");
222         System.out.println("      ACTIVITY: runs in an Android application on a device or emulator");
223         System.out.println("      APP_PROCESS: runs in an app_process runtime on a device or emulator");
224         System.out.println("      DEVICE: runs in an ART runtime on a device or emulator");
225         System.out.println("      HOST: runs in an ART runtime on the local desktop built with any lunch combo.");
226         System.out.println("      JVM: runs in a Java VM on the local desktop");
227         System.out.println("      Default is: " + modeId);
228         System.out.println();
229         System.out.println("  --variant <X32|X64>: specify which dalvikvm variant to execute with");
230         System.out.println("      Used with --mode <host|device> only, not applicable for all devices.");
231         System.out.println("      x32: 32-bit, x64: 64-bit");
232         System.out.println("      Default is: " + variant);
233         System.out.println();
234         System.out.println("  --toolchain <DX|D8|JAVAC>: Which toolchain to use.");
235         System.out.println("      Default depends on --mode value (currently "
236                 + modeId.defaultToolchain() + " for --mode=" + modeId + ")");
237         System.out.println();
238         System.out.println("  --language <J17|JN|JO|CUR>: Which language level to use.");
239         System.out.println("      Default is: " + language);
240         System.out.println();
241         System.out.println("  --ssh <host:port>: target a remote machine via SSH.");
242         System.out.println();
243         System.out.println("  --clean: synonym for --clean-before and --clean-after (default).");
244         System.out.println("      Disable with --no-clean if you want no files removed.");
245         System.out.println();
246         System.out.println("  --stream: stream output as it is emitted.");
247         System.out.println();
248         System.out.println("  --benchmark: for use with dalvikvm, this dexes all files together,");
249         System.out.println("      and is mandatory for running Caliper benchmarks, and a good idea");
250         System.out.println("      for other performance sensitive code.");
251         System.out.println("      If you specify this without specifying --runner-type then it");
252         System.out.println("      assumes --runner-type="
253                 + RunnerType.CALIPER.name().toLowerCase());
254         System.out.println();
255         System.out.println("  --invoke-with: provide a command to invoke the VM with. Examples:");
256         System.out.println("      --mode host --invoke-with \"valgrind --leak-check=full\"");
257         System.out.println("      --mode device --invoke-with \"strace -f -o/sdcard/strace.txt\"");
258         System.out.println();
259         System.out.println("  --timeout <seconds>: maximum execution time of each action before the");
260         System.out.println("      runner aborts it. Specifying zero seconds or using --debug will");
261         System.out.println("      disable the execution timeout. Tests tagged with 'large' will time");
262         System.out.println("      out in " + LARGE_TIMEOUT_MULTIPLIER + "x this timeout.");
263         System.out.println("      Default is: " + timeoutSeconds);
264         System.out.println();
265         System.out.println("  --xml-reports-directory <path>: directory to emit JUnit-style");
266         System.out.println("      XML test results.");
267         System.out.println();
268         System.out.println("  --classpath <jar file>: add the .jar to both build and execute classpaths.");
269         System.out.println();
270         System.out.println("  --use-bootclasspath: use the classpath as search path for bootstrap classes.");
271         System.out.println();
272         System.out.println("  --build-classpath <element>: add the directory or .jar to the build");
273         System.out.println("      classpath. Such classes are available as build dependencies, but");
274         System.out.println("      not at runtime.");
275         System.out.println();
276         System.out.println("  --sourcepath <directory>: add the directory to the build sourcepath.");
277         System.out.println();
278         System.out.println("  --vogar-dir <directory>: directory in which to find Vogar");
279         System.out.println("      configuration information, caches, saved and results");
280         System.out.println("      unless they've been put explicitly elsewhere.");
281         System.out.println("      Default is: " + vogarDir);
282         System.out.println();
283         System.out.println("  --record-results: record test results for future comparison.");
284         System.out.println();
285         System.out.println("  --results-dir <directory>: read and write (if --record-results used)");
286         System.out.println("      results from and to this directory.");
287         System.out.println();
288         System.out.println("  --runner-type <default|caliper|main|junit>: specify which runner to use.");
289         System.out.println("      default: runs both JUnit tests and main() classes");
290         System.out.println("      caliper: runs Caliper benchmarks only");
291         System.out.println("      main: runs main() classes only");
292         System.out.println("      junit: runs JUnit tests only");
293         System.out.println("      Default is determined by --benchmark and --testonly, if they are");
294         System.out.println("      not specified then defaults to: default");
295         System.out.println();
296         System.out.println("  --test-only: only run JUnit tests.");
297         System.out.println("      Default is: " + testOnly);
298         System.out.println("      DEPRECATED: Use --runner-type="
299                 + RunnerType.JUNIT.name().toLowerCase());
300         System.out.println();
301         System.out.println("  --verbose: turn on persistent verbose output.");
302         System.out.println();
303         System.out.println("  --check-jni: enable CheckJNI mode.");
304         System.out.println("      See http://developer.android.com/training/articles/perf-jni.html.");
305         System.out.println("      Default is: " + checkJni + ", but disabled for --benchmark.");
306         System.out.println("");
307         System.out.println("TARGET OPTIONS");
308         System.out.println();
309         System.out.println("  --debug <port>: enable Java debugging on the specified port.");
310         System.out.println("      This port must be free both on the device and on the local");
311         System.out.println("      system. Disables the timeout specified by --timeout-seconds.");
312         System.out.println();
313         System.out.println("  --debug-app: enable debugging while running in an activity.");
314         System.out.println("      This will require the use of DDMS to connect to the activity");
315         System.out.println("      on the device, and expose the debugger on an appropriate port.");
316         System.out.println();
317         System.out.println("  --device-dir <directory>: use the specified directory for");
318         System.out.println("      on-device temporary files and code.");
319         System.out.println();
320         System.out.println("  --vm-arg <argument>: include the specified argument when spawning a");
321         System.out.println("      virtual machine. Examples: -Xint:fast, -ea, -Xmx16M");
322         System.out.println();
323         System.out.println("  --vm-command <argument>: override default vm executable name.");
324         System.out.println("      Default is 'java' for the JVM and a version of dalvikvm for the host and target.");
325         System.out.println();
326         System.out.println("  --java-home <java_home>: execute the actions on the local workstation");
327         System.out.println("      using the specified java home directory. This does not impact");
328         System.out.println("      which javac gets used. When unset, java is used from the PATH.");
329         System.out.println();
330         System.out.println("EXOTIC OPTIONS");
331         System.out.println();
332         System.out.println("  --suggest-classpaths: build an index of jar files under the");
333         System.out.println("      directories given by --jar-search-dir arguments. If Vogar then ");
334         System.out.println("      fails due to missing classes or packages, it will use the index to");
335         System.out.println("      diagnose the problem and suggest a fix.");
336         System.out.println();
337         System.out.println("      Currently only looks for jars called exactly \"classes.jar\".");
338         System.out.println();
339         System.out.println("  --jar-search-dir <directory>: a directory that should be searched for");
340         System.out.println("      jar files to add to the class file index for use with");
341         System.out.println("      --suggest-classpaths.");
342         System.out.println();
343         System.out.println("  --clean-before: remove working directories before building and");
344         System.out.println("      running (default). Disable with --no-clean-before if you are");
345         System.out.println("      using interactively with your own temporary input files.");
346         System.out.println();
347         System.out.println("  --clean-after: remove temporary files after running (default).");
348         System.out.println("      Disable with --no-clean-after and use with --verbose if");
349         System.out.println("      you'd like to manually re-run commands afterwards.");
350         System.out.println();
351         System.out.println("  --color: format output in technicolor.");
352         System.out.println();
353         System.out.println("  --pass-color: ANSI color code to use for passes.");
354         System.out.println("      Default: 32 (green)");
355         System.out.println();
356         System.out.println("  --skip-color: ANSI color code to use for skips.");
357         System.out.println("      Default: 33 (yellow)");
358         System.out.println();
359         System.out.println("  --warn-color: ANSI color code to use for warnings.");
360         System.out.println("      Default: 35 (purple)");
361         System.out.println();
362         System.out.println("  --fail-color: ANSI color code to use for failures.");
363         System.out.println("      Default: 31 (red)");
364         System.out.println();
365         System.out.println("  --ansi: use ANSI escape sequences to remove intermediate output.");
366         System.out.println();
367         System.out.println("  --expectations <file>: include the specified file when looking for");
368         System.out.println("      action expectations. The file should include qualified action names");
369         System.out.println("      and the corresponding expected output.");
370         System.out.println("      Default is: " + expectationFiles);
371         System.out.println();
372         System.out.println("  --indent: amount to indent action result output. Can be set to ''");
373         System.out.println("      (aka empty string) to simplify output parsing.");
374         System.out.println("      Default is: '" + indent + "'");
375         System.out.println();
376         System.out.println("  --javac-arg <argument>: include the specified argument when invoking");
377         System.out.println("      javac. Examples: --javac-arg -Xmaxerrs --javac-arg 1");
378         System.out.println();
379         System.out.println("  --multidex: whether to use native multidex support");
380         System.out.println("      Disable with --no-multidex.");
381         System.out.println("      Default is: " + multidex);
382         System.out.println();
383         System.out.println("  --dalvik-cache <argument>: override default dalvik-cache location.");
384         System.out.println("      Default is: " + dalvikCache);
385         System.out.println();
386         System.out.println("  --first-monitor-port <port>: the port on the host (and possibly target)");
387         System.out.println("      used to traffic control messages between vogar and forked processes.");
388         System.out.println("      Use this to avoid port conflicts when running multiple vogar instances");
389         System.out.println("      concurrently. Vogar will use up to N ports starting with this one,");
390         System.out.println("      where N is the number of processors on the host (" + NUM_PROCESSORS + "). ");
391         System.out.println();
392         System.out.println("  --open-bugs-command <command>: a command that will take bug IDs as parameters");
393         System.out.println("      and return those bugs that are still open. For example, if bugs 123 and");
394         System.out.println("      789 are both open, the command should echo those values:");
395         System.out.println("         $ ~/bin/bug-command 123 456 789");
396         System.out.println("         123");
397         System.out.println("         789");
398         System.out.println();
399         System.out.println("CONFIG FILE");
400         System.out.println();
401         System.out.println("  User-defined default arguments can be specified in ~/.vogarconfig. See");
402         System.out.println("  .vogarconfig.example for an example.");
403         System.out.println();
404     }
405 
406     @VisibleForTesting
parseArgs(String[] args)407     public boolean parseArgs(String[] args) {
408         // extract arguments from config file
409         configArgs = OptionParser.readFile(configFile);
410 
411         // config file args are added first so that in a conflict, the currently supplied
412         // arguments win.
413         List<String> actionsAndTargetArgs = optionParser.parse(configArgs);
414         if (!actionsAndTargetArgs.isEmpty()) {
415             throw new RuntimeException(
416                     "actions or targets given in .vogarconfig: " + actionsAndTargetArgs);
417         }
418 
419         try {
420             actionsAndTargetArgs.addAll(optionParser.parse(args));
421         } catch (RuntimeException e) {
422             System.out.println(e.getMessage());
423             return false;
424         }
425 
426         //
427         // Semantic error validation
428         //
429 
430         if (javaHome != null && !new File(javaHome, "/bin/java").exists()) {
431             System.out.println("Invalid java home: " + javaHome);
432             return false;
433         }
434 
435         // check vm option consistency
436         if (!modeId.acceptsVmArgs() && !vmArgs.isEmpty()) {
437             System.out.println("VM args " + vmArgs + " should not be specified for mode " + modeId);
438             return false;
439         }
440 
441         // Check variant / mode compatibility.
442         if (!modeId.supportsVariant(variant)) {
443             System.out.println("Variant " + variant + " not supported for mode " + modeId);
444             return false;
445         }
446 
447         if (toolchain == null) {
448             toolchain = modeId.defaultToolchain();
449             System.out.println("Defaulting --toolchain to " + toolchain);
450         } else if (!modeId.supportsToolchain(toolchain)) {
451             System.out.println("Toolchain " + toolchain + " not supported for mode " + modeId);
452             return false;
453         }
454 
455         if (xmlReportsDirectory != null && !xmlReportsDirectory.isDirectory()) {
456             System.out.println("Invalid XML reports directory: " + xmlReportsDirectory);
457             return false;
458         }
459 
460         if (!clean) {
461             cleanBefore = false;
462             cleanAfter = false;
463         }
464 
465         //
466         // Post-processing arguments
467         //
468 
469         if (vmCommand == null) {
470             vmCommand = modeId.defaultVmCommand(variant);
471         }
472 
473         // disable timeout when benchmarking or debugging
474         if (benchmark || debugPort != null) {
475             timeoutSeconds = 0;
476         }
477 
478         if (firstMonitorPort == -1) {
479             firstMonitorPort = modeId.isLocal() ? 8788 : 8787;
480         }
481 
482         // separate the actions and the target args
483         int index = 0;
484         for (; index < actionsAndTargetArgs.size(); index++) {
485             String arg = actionsAndTargetArgs.get(index);
486             if (arg.equals("--")) {
487                 index++;
488                 break;
489             }
490 
491             File file = new File(arg);
492             if (file.exists()) {
493                 if (arg.endsWith(".java") || file.isDirectory()) {
494                     actionFiles.add(file.getAbsoluteFile());
495                 } else {
496                     System.out.println("Expected a .jar file, .java file, directory, "
497                             + "package name or classname, but was: " + arg);
498                     return false;
499                 }
500             } else {
501                 actionClassesAndPackages.add(arg);
502             }
503         }
504 
505         targetArgs.addAll(actionsAndTargetArgs.subList(index, actionsAndTargetArgs.size()));
506 
507         if (actionFiles.isEmpty() && actionClassesAndPackages.isEmpty()) {
508             System.out.println("No actions provided.");
509             return false;
510         }
511 
512         if (!modeId.acceptsVmArgs() && !targetArgs.isEmpty()) {
513             System.out.println("Target args " + targetArgs + " should not be specified for mode " + modeId);
514             return false;
515         }
516 
517         if (modeId == ModeId.ACTIVITY && debugPort != null) {
518             System.out.println("Activity debugging requires the use of --debug-app and DDMS.");
519             return false;
520         }
521 
522         if (debugApp && modeId != ModeId.ACTIVITY) {
523             System.out.println("--debug-app can only be used in combination with --mode activity.");
524             return false;
525         }
526 
527         // When using --benchmark,
528         // caliper will spawn each benchmark as a new process (default dalvikvm).
529         //
530         // When using also --mode app_process, we want that new process to be app_process.
531         //
532         // Pass --vm app_process to it so that it knows not to use dalvikvm.
533         if ("app_process".equals(vmCommand) && benchmark) {
534           targetArgs.add("--vm");
535           targetArgs.add("app_process");
536         }
537 
538         return true;
539     }
540 
541     /**
542      * The type of the target.
543      */
544     private enum TargetType {
545         ADB(AdbTarget.defaultDeviceDir()),
546         LOCAL(LocalTarget.defaultDeviceDir()),
547         SSH(SshTarget.defaultDeviceDir());
548 
549         /**
550          * The default device dir.
551          */
552         private final File defaultDeviceDir;
553 
TargetType(File defaultDeviceDir)554         TargetType(File defaultDeviceDir) {
555             this.defaultDeviceDir = defaultDeviceDir;
556         }
557 
defaultDeviceDir()558         public File defaultDeviceDir() {
559             return defaultDeviceDir;
560         }
561     }
562 
run()563     private boolean run() throws IOException {
564         // Create a new Console for use by Run.
565         Console console = this.stream
566                 ? new Console.StreamingConsole()
567                 : new Console.MultiplexingConsole();
568         console.setUseColor(color, passColor, skipColor, failColor, warnColor);
569         console.setAnsi(ansi);
570         console.setIndent(indent);
571         console.setVerbose(verbose);
572 
573         Mkdir mkdir = new Mkdir(console);
574         Rm rm = new Rm(console);
575 
576         // Select the target type, this is needed in order to calculate the runnerDir, which is in
577         // turn needed for creating the AdbTarget below.
578         TargetType targetType;
579         if (sshHost != null) {
580             targetType = TargetType.SSH;
581         } else if (modeId.isLocal()) {
582             targetType = TargetType.LOCAL;
583         } else {
584             targetType = TargetType.ADB;
585         }
586 
587         File runnerDir = deviceDir != null
588                 ? new File(deviceDir, "run")
589                 : new File(targetType.defaultDeviceDir(), "run");
590 
591         // Create the target.
592         Target target;
593         switch (targetType) {
594             case ADB:
595                 DeviceFilesystem deviceFilesystem =
596                         new DeviceFilesystem(console, ImmutableList.of("adb", "shell"));
597                 DeviceFileCache deviceFileCache =
598                         new DeviceFileCache(console, runnerDir, deviceFilesystem);
599                 target = new AdbTarget(console, deviceFilesystem, deviceFileCache);
600                 break;
601             case SSH:
602                 target = new SshTarget(console, sshHost);
603                 break;
604             case LOCAL:
605                 target = new LocalTarget(console, mkdir, rm);
606                 break;
607             default:
608                 throw new IllegalStateException("Unknown target type: " + targetType);
609         }
610 
611         AndroidSdk androidSdk = null;
612         if (modeId.requiresAndroidSdk()) {
613             androidSdk = AndroidSdk.createAndroidSdk(console, mkdir, modeId, language);
614         }
615 
616         if (runnerType == null) {
617             if (benchmark) {
618                 if (testOnly) {
619                     throw new IllegalStateException(
620                             "--benchmark and --testOnly are mutually exclusive and deprecated,"
621                                     + " use --runner-type");
622                 }
623                 if (modeId == ModeId.ACTIVITY) {
624                     throw new IllegalStateException(
625                             "--benchmark and --mode activity are mutually exclusive");
626                 }
627                 runnerType = RunnerType.CALIPER;
628             } else if (testOnly) {
629                 runnerType = RunnerType.JUNIT;
630             } else {
631                 runnerType = RunnerType.DEFAULT;
632             }
633         } else {
634             if (testOnly) {
635                 throw new IllegalStateException(
636                         "--runnerType and --testOnly are mutually exclusive");
637             }
638 
639             if (runnerType.supportsCaliper()) {
640                 if (modeId == ModeId.ACTIVITY) {
641                     throw new IllegalStateException(
642                             "--runnerType caliper and --mode activity are mutually exclusive");
643                 }
644 
645                 // Assume --benchmark
646                 benchmark = true;
647             }
648         }
649 
650         Run run = new Run(this, toolchain, console, mkdir, androidSdk, rm,
651                 target, runnerDir);
652         if (configArgs.length > 0) {
653             run.console.verbose("loaded arguments from .vogarconfig: " +
654                                 Strings.join(" ", (Object)configArgs));
655         }
656         return run.driver.buildAndRun(actionFiles, actionClassesAndPackages);
657     }
658 
main(String[] args)659     public static void main(String[] args) throws IOException {
660         Vogar vogar = new Vogar();
661         if (!vogar.parseArgs(args)) {
662             vogar.printUsage();
663             System.exit(1);
664         }
665         boolean allSuccess = vogar.run();
666         System.exit(allSuccess ? 0 : 1);
667     }
668 }
669