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