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