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