1 /* 2 * Copyright 2004-2008 Sun Microsystems, Inc. All Rights Reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Sun designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Sun in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, 22 * CA 95054 USA or visit www.sun.com if you need additional information or 23 * have any questions. 24 */ 25 26 package com.sun.tools.apt.main; 27 28 import java.io.File; 29 import java.io.FileWriter; 30 import java.io.IOException; 31 import java.io.PrintWriter; 32 import java.text.MessageFormat; 33 import java.util.ResourceBundle; 34 import java.util.MissingResourceException; 35 import java.util.StringTokenizer; 36 import java.util.Map; 37 import java.util.HashMap; 38 import java.util.Collections; 39 40 import java.net.URLClassLoader; 41 import java.net.URL; 42 import java.net.MalformedURLException; 43 44 import javax.tools.JavaFileManager; 45 import javax.tools.StandardLocation; 46 47 import com.sun.tools.javac.file.JavacFileManager; 48 import com.sun.tools.javac.code.Source; 49 import com.sun.tools.javac.code.Symbol; 50 import com.sun.tools.javac.code.Type; 51 import com.sun.tools.javac.jvm.Target; 52 import com.sun.tools.javac.util.*; 53 54 import com.sun.tools.apt.comp.AnnotationProcessingError; 55 import com.sun.tools.apt.comp.UsageMessageNeededException; 56 import com.sun.tools.apt.util.Bark; 57 import com.sun.mirror.apt.AnnotationProcessorFactory; 58 59 /** This class provides a commandline interface to the apt build-time 60 * tool. 61 * 62 * <p><b>This is NOT part of any API supported by Sun Microsystems. 63 * If you write code that depends on this, you do so at your own 64 * risk. This code and its internal interfaces are subject to change 65 * or deletion without notice.</b> 66 */ 67 public class Main { 68 69 /** For testing: enter any options you want to be set implicitly 70 * here. 71 */ 72 static String[] forcedOpts = { 73 // Preserve parameter names from class files if the class was 74 // compiled with debug enabled 75 "-XDsave-parameter-names" 76 }; 77 78 /** The name of the compiler, for use in diagnostics. 79 */ 80 String ownName; 81 82 /** The writer to use for diagnostic output. 83 */ 84 PrintWriter out; 85 86 87 /** Instantiated factory to use in lieu of discovery process. 88 */ 89 AnnotationProcessorFactory providedFactory = null; 90 91 /** Map representing original command-line arguments. 92 */ 93 Map<String,String> origOptions = new HashMap<String, String>(); 94 95 /** Classloader to use for finding factories. 96 */ 97 ClassLoader aptCL = null; 98 99 /** Result codes. 100 */ 101 static final int 102 EXIT_OK = 0, // Compilation completed with no errors. 103 EXIT_ERROR = 1, // Completed but reported errors. 104 EXIT_CMDERR = 2, // Bad command-line arguments 105 EXIT_SYSERR = 3, // System error or resource exhaustion. 106 EXIT_ABNORMAL = 4; // Compiler terminated abnormally 107 108 /** This class represents an option recognized by the main program 109 */ 110 private class Option { 111 /** Whether or not the option is used only aptOnly. 112 */ 113 boolean aptOnly = false; 114 115 /** Option string. 116 */ 117 String name; 118 119 /** Documentation key for arguments. 120 */ 121 String argsNameKey; 122 123 /** Documentation key for description. 124 */ 125 String descrKey; 126 127 /** Suffix option (-foo=bar or -foo:bar) 128 */ 129 boolean hasSuffix; 130 131 Option(String name, String argsNameKey, String descrKey) { 132 this.name = name; 133 this.argsNameKey = argsNameKey; 134 this.descrKey = descrKey; 135 char lastChar = name.charAt(name.length()-1); 136 hasSuffix = lastChar == ':' || lastChar == '='; 137 } 138 Option(String name, String descrKey) { 139 this(name, null, descrKey); 140 } 141 142 public String toString() { 143 return name; 144 } 145 146 /** Does this option take a (separate) operand? 147 */ 148 boolean hasArg() { 149 return argsNameKey != null && !hasSuffix; 150 } 151 152 /** Does argument string match option pattern? 153 * @param arg The command line argument string. 154 */ 155 boolean matches(String arg) { 156 return hasSuffix ? arg.startsWith(name) : arg.equals(name); 157 } 158 159 /** For javac-only options, print nothing. 160 */ 161 void help() { 162 } 163 164 String helpSynopsis() { 165 return name + 166 (argsNameKey == null ? "" : 167 ((hasSuffix ? "" : " ") + 168 getLocalizedString(argsNameKey))); 169 } 170 171 /** Print a line of documentation describing this option, if non-standard. 172 */ 173 void xhelp() {} 174 175 /** Process the option (with arg). Return true if error detected. 176 */ 177 boolean process(String option, String arg) { 178 options.put(option, arg); 179 return false; 180 } 181 182 /** Process the option (without arg). Return true if error detected. 183 */ 184 boolean process(String option) { 185 if (hasSuffix) 186 return process(name, option.substring(name.length())); 187 else 188 return process(option, option); 189 } 190 }; 191 192 private class SharedOption extends Option { 193 SharedOption(String name, String argsNameKey, String descrKey) { 194 super(name, argsNameKey, descrKey); 195 } 196 197 SharedOption(String name, String descrKey) { 198 super(name, descrKey); 199 } 200 201 void help() { 202 String s = " " + helpSynopsis(); 203 out.print(s); 204 for (int j = s.length(); j < 29; j++) out.print(" "); 205 Bark.printLines(out, getLocalizedString(descrKey)); 206 } 207 208 } 209 210 private class AptOption extends Option { 211 AptOption(String name, String argsNameKey, String descrKey) { 212 super(name, argsNameKey, descrKey); 213 aptOnly = true; 214 } 215 216 AptOption(String name, String descrKey) { 217 super(name, descrKey); 218 aptOnly = true; 219 } 220 221 /** Print a line of documentation describing this option, if standard. 222 */ 223 void help() { 224 String s = " " + helpSynopsis(); 225 out.print(s); 226 for (int j = s.length(); j < 29; j++) out.print(" "); 227 Bark.printLines(out, getLocalizedString(descrKey)); 228 } 229 230 } 231 232 /** A nonstandard or extended (-X) option 233 */ 234 private class XOption extends Option { 235 XOption(String name, String argsNameKey, String descrKey) { 236 super(name, argsNameKey, descrKey); 237 } 238 XOption(String name, String descrKey) { 239 this(name, null, descrKey); 240 } 241 void help() {} 242 void xhelp() {} 243 }; 244 245 /** A nonstandard or extended (-X) option 246 */ 247 private class AptXOption extends Option { 248 AptXOption(String name, String argsNameKey, String descrKey) { 249 super(name, argsNameKey, descrKey); 250 aptOnly = true; 251 } 252 AptXOption(String name, String descrKey) { 253 this(name, null, descrKey); 254 } 255 void xhelp() { 256 String s = " " + helpSynopsis(); 257 out.print(s); 258 for (int j = s.length(); j < 29; j++) out.print(" "); 259 Log.printLines(out, getLocalizedString(descrKey)); 260 } 261 }; 262 263 /** A hidden (implementor) option 264 */ 265 private class HiddenOption extends Option { 266 HiddenOption(String name) { 267 super(name, null, null); 268 } 269 HiddenOption(String name, String argsNameKey) { 270 super(name, argsNameKey, null); 271 } 272 void help() {} 273 void xhelp() {} 274 }; 275 276 private class AptHiddenOption extends HiddenOption { 277 AptHiddenOption(String name) { 278 super(name); 279 aptOnly = true; 280 } 281 AptHiddenOption(String name, String argsNameKey) { 282 super(name, argsNameKey); 283 aptOnly = true; 284 } 285 } 286 287 private Option[] recognizedOptions = { 288 new Option("-g", "opt.g"), 289 new Option("-g:none", "opt.g.none") { 290 boolean process(String option) { 291 options.put("-g:", "none"); 292 return false; 293 } 294 }, 295 296 new Option("-g:{lines,vars,source}", "opt.g.lines.vars.source") { 297 boolean matches(String s) { 298 return s.startsWith("-g:"); 299 } 300 boolean process(String option) { 301 String suboptions = option.substring(3); 302 options.put("-g:", suboptions); 303 // enter all the -g suboptions as "-g:suboption" 304 for (StringTokenizer t = new StringTokenizer(suboptions, ","); t.hasMoreTokens(); ) { 305 String tok = t.nextToken(); 306 String opt = "-g:" + tok; 307 options.put(opt, opt); 308 } 309 return false; 310 } 311 }, 312 313 new XOption("-Xlint", "opt.Xlint"), 314 new XOption("-Xlint:{" 315 + "all," 316 + "cast,deprecation,divzero,empty,unchecked,fallthrough,path,serial,finally,overrides," 317 + "-cast,-deprecation,-divzero,-empty,-unchecked,-fallthrough,-path,-serial,-finally,-overrides," 318 + "none}", 319 "opt.Xlint.suboptlist") { 320 boolean matches(String s) { 321 return s.startsWith("-Xlint:"); 322 } 323 boolean process(String option) { 324 String suboptions = option.substring(7); 325 options.put("-Xlint:", suboptions); 326 // enter all the -Xlint suboptions as "-Xlint:suboption" 327 for (StringTokenizer t = new StringTokenizer(suboptions, ","); t.hasMoreTokens(); ) { 328 String tok = t.nextToken(); 329 String opt = "-Xlint:" + tok; 330 options.put(opt, opt); 331 } 332 return false; 333 } 334 }, 335 336 new Option("-nowarn", "opt.nowarn"), 337 new Option("-verbose", "opt.verbose"), 338 339 // -deprecation is retained for command-line backward compatibility 340 new Option("-deprecation", "opt.deprecation") { 341 boolean process(String option) { 342 options.put("-Xlint:deprecation", option); 343 return false; 344 } 345 }, 346 347 new SharedOption("-classpath", "opt.arg.path", "opt.classpath"), 348 new SharedOption("-cp", "opt.arg.path", "opt.classpath") { 349 boolean process(String option, String arg) { 350 return super.process("-classpath", arg); 351 } 352 }, 353 new Option("-sourcepath", "opt.arg.path", "opt.sourcepath"), 354 new Option("-bootclasspath", "opt.arg.path", "opt.bootclasspath") { 355 boolean process(String option, String arg) { 356 options.remove("-Xbootclasspath/p:"); 357 options.remove("-Xbootclasspath/a:"); 358 return super.process(option, arg); 359 } 360 }, 361 new XOption("-Xbootclasspath/p:", "opt.arg.path", "opt.Xbootclasspath.p"), 362 new XOption("-Xbootclasspath/a:", "opt.arg.path", "opt.Xbootclasspath.a"), 363 new XOption("-Xbootclasspath:", "opt.arg.path", "opt.bootclasspath") { 364 boolean process(String option, String arg) { 365 options.remove("-Xbootclasspath/p:"); 366 options.remove("-Xbootclasspath/a:"); 367 return super.process("-bootclasspath", arg); 368 } 369 }, 370 new Option("-extdirs", "opt.arg.dirs", "opt.extdirs"), 371 new XOption("-Djava.ext.dirs=", "opt.arg.dirs", "opt.extdirs") { 372 boolean process(String option, String arg) { 373 return super.process("-extdirs", arg); 374 } 375 }, 376 new Option("-endorseddirs", "opt.arg.dirs", "opt.endorseddirs"), 377 new XOption("-Djava.endorsed.dirs=","opt.arg.dirs", "opt.endorseddirs") { 378 boolean process(String option, String arg) { 379 return super.process("-endorseddirs", arg); 380 } 381 }, 382 new Option("-proc:{none, only}", "opt.proc.none.only") { 383 public boolean matches(String s) { 384 return s.equals("-proc:none") || s.equals("-proc:only"); 385 } 386 }, 387 new Option("-processor", "opt.arg.class", "opt.processor"), 388 new Option("-processorpath", "opt.arg.path", "opt.processorpath"), 389 390 new SharedOption("-d", "opt.arg.path", "opt.d"), 391 new SharedOption("-s", "opt.arg.path", "opt.s"), 392 new Option("-encoding", "opt.arg.encoding", "opt.encoding"), 393 new SharedOption("-source", "opt.arg.release", "opt.source") { 394 boolean process(String option, String operand) { 395 Source source = Source.lookup(operand); 396 if (source == null) { 397 error("err.invalid.source", operand); 398 return true; 399 } else if (source.compareTo(Source.JDK1_5) > 0) { 400 error("err.unsupported.source.version", operand); 401 return true; 402 } 403 return super.process(option, operand); 404 } 405 }, 406 new Option("-target", "opt.arg.release", "opt.target") { 407 boolean process(String option, String operand) { 408 Target target = Target.lookup(operand); 409 if (target == null) { 410 error("err.invalid.target", operand); 411 return true; 412 } else if (target.compareTo(Target.JDK1_5) > 0) { 413 error("err.unsupported.target.version", operand); 414 return true; 415 } 416 return super.process(option, operand); 417 } 418 }, 419 new AptOption("-version", "opt.version") { 420 boolean process(String option) { 421 Bark.printLines(out, ownName + " " + JavaCompiler.version()); 422 return super.process(option); 423 } 424 }, 425 new HiddenOption("-fullversion"), 426 new AptOption("-help", "opt.help") { 427 boolean process(String option) { 428 Main.this.help(); 429 return super.process(option); 430 } 431 }, 432 new SharedOption("-X", "opt.X") { 433 boolean process(String option) { 434 Main.this.xhelp(); 435 return super.process(option); 436 } 437 }, 438 439 // This option exists only for the purpose of documenting itself. 440 // It's actually implemented by the launcher. 441 new AptOption("-J", "opt.arg.flag", "opt.J") { 442 String helpSynopsis() { 443 hasSuffix = true; 444 return super.helpSynopsis(); 445 } 446 boolean process(String option) { 447 throw new AssertionError 448 ("the -J flag should be caught by the launcher."); 449 } 450 }, 451 452 453 new SharedOption("-A", "opt.proc.flag", "opt.A") { 454 String helpSynopsis() { 455 hasSuffix = true; 456 return super.helpSynopsis(); 457 } 458 459 boolean matches(String arg) { 460 return arg.startsWith("-A"); 461 } 462 463 boolean hasArg() { 464 return false; 465 } 466 467 boolean process(String option) { 468 return process(option, option); 469 } 470 }, 471 472 new AptOption("-nocompile", "opt.nocompile"), 473 474 new AptOption("-print", "opt.print"), 475 476 new AptOption("-factorypath", "opt.arg.path", "opt.factorypath"), 477 478 new AptOption("-factory", "opt.arg.class", "opt.factory"), 479 480 new AptXOption("-XListAnnotationTypes", "opt.XListAnnotationTypes"), 481 482 new AptXOption("-XListDeclarations", "opt.XListDeclarations"), 483 484 new AptXOption("-XPrintAptRounds", "opt.XPrintAptRounds"), 485 486 new AptXOption("-XPrintFactoryInfo", "opt.XPrintFactoryInfo"), 487 488 /* 489 * Option to treat both classes and source files as 490 * declarations that can be given on the command line and 491 * processed as the result of an apt round. 492 */ 493 new AptXOption("-XclassesAsDecls", "opt.XClassesAsDecls"), 494 495 // new Option("-moreinfo", "opt.moreinfo") { 496 new HiddenOption("-moreinfo") { 497 boolean process(String option) { 498 Type.moreInfo = true; 499 return super.process(option); 500 } 501 }, 502 503 // treat warnings as errors 504 new HiddenOption("-Werror"), 505 506 // use complex inference from context in the position of a method call argument 507 new HiddenOption("-complexinference"), 508 509 // prompt after each error 510 // new Option("-prompt", "opt.prompt"), 511 new HiddenOption("-prompt"), 512 513 // dump stack on error 514 new HiddenOption("-doe"), 515 516 // display warnings for generic unchecked and unsafe operations 517 new HiddenOption("-warnunchecked") { 518 boolean process(String option) { 519 options.put("-Xlint:unchecked", option); 520 return false; 521 } 522 }, 523 524 new HiddenOption("-Xswitchcheck") { 525 boolean process(String option) { 526 options.put("-Xlint:switchcheck", option); 527 return false; 528 } 529 }, 530 531 // generate trace output for subtyping operations 532 new HiddenOption("-debugsubtyping"), 533 534 new XOption("-Xmaxerrs", "opt.arg.number", "opt.maxerrs"), 535 new XOption("-Xmaxwarns", "opt.arg.number", "opt.maxwarns"), 536 new XOption("-Xstdout", "opt.arg.file", "opt.Xstdout") { 537 boolean process(String option, String arg) { 538 try { 539 out = new PrintWriter(new FileWriter(arg), true); 540 } catch (java.io.IOException e) { 541 error("err.error.writing.file", arg, e); 542 return true; 543 } 544 return super.process(option, arg); 545 } 546 }, 547 548 new XOption("-Xprint", "opt.print"), 549 550 new XOption("-XprintRounds", "opt.printRounds"), 551 552 new XOption("-XprintProcessorInfo", "opt.printProcessorInfo"), 553 554 555 /* -O is a no-op, accepted for backward compatibility. */ 556 new HiddenOption("-O"), 557 558 /* -Xjcov produces tables to support the code coverage tool jcov. */ 559 new HiddenOption("-Xjcov"), 560 561 /* This is a back door to the compiler's option table. 562 * -Dx=y sets the option x to the value y. 563 * -Dx sets the option x to the value x. 564 */ 565 new HiddenOption("-XD") { 566 String s; 567 boolean matches(String s) { 568 this.s = s; 569 return s.startsWith(name); 570 } 571 boolean process(String option) { 572 s = s.substring(name.length()); 573 int eq = s.indexOf('='); 574 String key = (eq < 0) ? s : s.substring(0, eq); 575 String value = (eq < 0) ? s : s.substring(eq+1); 576 options.put(key, value); 577 return false; 578 } 579 }, 580 581 new HiddenOption("sourcefile") { 582 String s; 583 boolean matches(String s) { 584 this.s = s; 585 return s.endsWith(".java") || 586 (options.get("-XclassesAsDecls") != null); 587 } 588 boolean process(String option) { 589 if (s.endsWith(".java")) { 590 if (!sourceFileNames.contains(s)) 591 sourceFileNames.add(s); 592 } else if (options.get("-XclassesAsDecls") != null) { 593 classFileNames.add(s); 594 } 595 return false; 596 } 597 }, 598 }; 599 600 /** 601 * Construct a compiler instance. 602 */ 603 public Main(String name) { 604 this(name, new PrintWriter(System.err, true)); 605 } 606 607 /** 608 * Construct a compiler instance. 609 */ 610 public Main(String name, PrintWriter out) { 611 this.ownName = name; 612 this.out = out; 613 } 614 615 /** A table of all options that's passed to the JavaCompiler constructor. */ 616 private Options options = null; 617 618 /** The list of source files to process 619 */ 620 java.util.List<String> sourceFileNames = new java.util.LinkedList<String>(); 621 622 /** The list of class files to process 623 */ 624 java.util.List<String> classFileNames = new java.util.LinkedList<String>(); 625 626 /** List of top level names of generated source files from most recent apt round. 627 */ 628 java.util.Set<String> genSourceFileNames = new java.util.LinkedHashSet<String>(); 629 630 /** List of names of generated class files from most recent apt round. 631 */ 632 java.util.Set<String> genClassFileNames = new java.util.LinkedHashSet<String>(); 633 634 /** 635 * List of all the generated source file names across all apt rounds. 636 */ 637 java.util.Set<String> aggregateGenSourceFileNames = new java.util.LinkedHashSet<String>(); 638 639 /** 640 * List of all the generated class file names across all apt rounds. 641 */ 642 java.util.Set<String> aggregateGenClassFileNames = new java.util.LinkedHashSet<String>(); 643 644 /** 645 * List of all the generated file names across all apt rounds. 646 */ 647 java.util.Set<java.io.File> aggregateGenFiles = new java.util.LinkedHashSet<java.io.File>(); 648 649 /** 650 * Set of all factories that have provided a processor on some apt round. 651 */ 652 java.util.Set<Class<? extends AnnotationProcessorFactory> > productiveFactories = 653 new java.util.LinkedHashSet<Class<? extends AnnotationProcessorFactory> >(); 654 655 656 657 /** Print a string that explains usage. 658 */ 659 void help() { 660 Bark.printLines(out, getLocalizedString("msg.usage.header", ownName)); 661 for (int i=0; i < recognizedOptions.length; i++) { 662 recognizedOptions[i].help(); 663 } 664 Bark.printLines(out, getLocalizedString("msg.usage.footer")); 665 out.println(); 666 } 667 668 /** Print a string that explains usage for X options. 669 */ 670 void xhelp() { 671 for (int i=0; i<recognizedOptions.length; i++) { 672 recognizedOptions[i].xhelp(); 673 } 674 out.println(); 675 Bark.printLines(out, getLocalizedString("msg.usage.nonstandard.footer")); 676 } 677 678 /** Report a usage error. 679 */ 680 void error(String key, Object... args) { 681 warning(key, args); 682 help(); 683 } 684 685 /** Report a warning. 686 */ 687 void warning(String key, Object... args) { 688 Bark.printLines(out, ownName + ": " 689 + getLocalizedString(key, args)); 690 } 691 692 /** Process command line arguments: store all command line options 693 * in `options' table and return all source filenames. 694 * @param args The array of command line arguments. 695 */ 696 protected java.util.List<String> processArgs(String[] flags) { 697 int ac = 0; 698 while (ac < flags.length) { 699 String flag = flags[ac]; 700 ac++; 701 702 int j; 703 for (j=0; j < recognizedOptions.length; j++) 704 if (recognizedOptions[j].matches(flag)) 705 break; 706 707 if (j == recognizedOptions.length) { 708 error("err.invalid.flag", flag); 709 return null; 710 } 711 712 Option option = recognizedOptions[j]; 713 if (option.hasArg()) { 714 if (ac == flags.length) { 715 error("err.req.arg", flag); 716 return null; 717 } 718 String operand = flags[ac]; 719 ac++; 720 if (option.process(flag, operand)) 721 return null; 722 } else { 723 if (option.process(flag)) 724 return null; 725 } 726 } 727 728 String sourceString = options.get("-source"); 729 Source source = (sourceString != null) 730 ? Source.lookup(sourceString) 731 : Source.JDK1_5; // JDK 5 is the latest supported source version 732 String targetString = options.get("-target"); 733 Target target = (targetString != null) 734 ? Target.lookup(targetString) 735 : Target.JDK1_5; // JDK 5 is the latest supported source version 736 // We don't check source/target consistency for CLDC, as J2ME 737 // profiles are not aligned with J2SE targets; moreover, a 738 // single CLDC target may have many profiles. In addition, 739 // this is needed for the continued functioning of the JSR14 740 // prototype. 741 if (Character.isDigit(target.name.charAt(0)) && 742 target.compareTo(source.requiredTarget()) < 0) { 743 if (targetString != null) { 744 if (sourceString == null) { 745 warning("warn.target.default.source.conflict", 746 targetString, 747 source.requiredTarget().name); 748 } else { 749 warning("warn.source.target.conflict", 750 sourceString, 751 source.requiredTarget().name); 752 } 753 return null; 754 } else { 755 options.put("-target", source.requiredTarget().name); 756 } 757 } 758 return sourceFileNames; 759 } 760 761 /** Programmatic interface for main function. 762 * @param args The command line parameters. 763 */ 764 public int compile(String[] args, AnnotationProcessorFactory factory) { 765 int returnCode = 0; 766 providedFactory = factory; 767 768 Context context = new Context(); 769 JavacFileManager.preRegister(context); 770 options = Options.instance(context); 771 Bark bark; 772 773 /* 774 * Process the command line options to create the intial 775 * options data. This processing is at least partially reused 776 * by any recursive apt calls. 777 */ 778 779 // For testing: assume all arguments in forcedOpts are 780 // prefixed to command line arguments. 781 processArgs(forcedOpts); 782 783 784 /* 785 * A run of apt only gets passed the most recently generated 786 * files; the initial run of apt gets passed the files from 787 * the command line. 788 */ 789 790 java.util.List<String> origFilenames; 791 try { 792 // assign args the result of parse to capture results of 793 // '@file' expansion 794 origFilenames = processArgs((args=CommandLine.parse(args))); 795 if (origFilenames == null) { 796 return EXIT_CMDERR; 797 } else if (origFilenames.size() == 0) { 798 // it is allowed to compile nothing if just asking for help 799 if (options.get("-help") != null || 800 options.get("-X") != null) 801 return EXIT_OK; 802 } 803 } catch (java.io.FileNotFoundException e) { 804 Bark.printLines(out, ownName + ": " + 805 getLocalizedString("err.file.not.found", 806 e.getMessage())); 807 return EXIT_SYSERR; 808 } catch (IOException ex) { 809 ioMessage(ex); 810 return EXIT_SYSERR; 811 } catch (OutOfMemoryError ex) { 812 resourceMessage(ex); 813 return EXIT_SYSERR; 814 } catch (StackOverflowError ex) { 815 resourceMessage(ex); 816 return EXIT_SYSERR; 817 } catch (FatalError ex) { 818 feMessage(ex); 819 return EXIT_SYSERR; 820 } catch (sun.misc.ServiceConfigurationError sce) { 821 sceMessage(sce); 822 return EXIT_ABNORMAL; 823 } catch (Throwable ex) { 824 bugMessage(ex); 825 return EXIT_ABNORMAL; 826 } 827 828 829 boolean firstRound = true; 830 boolean needSourcePath = false; 831 boolean needClassPath = false; 832 boolean classesAsDecls = options.get("-XclassesAsDecls") != null; 833 834 /* 835 * Create augumented classpath and sourcepath values. 836 * 837 * If any of the prior apt rounds generated any new source 838 * files, the n'th apt round (and any javac invocation) has the 839 * source destination path ("-s path") as the last element of 840 * the "-sourcepath" to the n'th call. 841 * 842 * If any of the prior apt rounds generated any new class files, 843 * the n'th apt round (and any javac invocation) has the class 844 * destination path ("-d path") as the last element of the 845 * "-classpath" to the n'th call. 846 */ 847 String augmentedSourcePath = ""; 848 String augmentedClassPath = ""; 849 String baseClassPath = ""; 850 851 try { 852 /* 853 * Record original options for future annotation processor 854 * invocations. 855 */ 856 origOptions = new HashMap<String, String>(options.size()); 857 for(String s: options.keySet()) { 858 String value; 859 if (s.equals(value = options.get(s))) 860 origOptions.put(s, (String)null); 861 else 862 origOptions.put(s, value); 863 } 864 origOptions = Collections.unmodifiableMap(origOptions); 865 866 JavacFileManager fm = (JavacFileManager) context.get(JavaFileManager.class); 867 { 868 // Note: it might be necessary to check for an empty 869 // component ("") of the source path or class path 870 871 String sourceDest = options.get("-s"); 872 if (fm.hasLocation(StandardLocation.SOURCE_PATH)) { 873 for(File f: fm.getLocation(StandardLocation.SOURCE_PATH)) 874 augmentedSourcePath += (f + File.pathSeparator); 875 augmentedSourcePath += (sourceDest == null)?".":sourceDest; 876 } else { 877 augmentedSourcePath = "."; 878 879 if (sourceDest != null) 880 augmentedSourcePath += (File.pathSeparator + sourceDest); 881 } 882 883 String classDest = options.get("-d"); 884 if (fm.hasLocation(StandardLocation.CLASS_PATH)) { 885 for(File f: fm.getLocation(StandardLocation.CLASS_PATH)) 886 baseClassPath += (f + File.pathSeparator); 887 // put baseClassPath into map to handle any 888 // value needed for the classloader 889 options.put("-classpath", baseClassPath); 890 891 augmentedClassPath = baseClassPath + ((classDest == null)?".":classDest); 892 } else { 893 baseClassPath = "."; 894 if (classDest != null) 895 augmentedClassPath = baseClassPath + (File.pathSeparator + classDest); 896 } 897 assert options.get("-classpath") != null; 898 } 899 900 /* 901 * Create base and augmented class loaders 902 */ 903 ClassLoader augmentedAptCL = null; 904 { 905 /* 906 * Use a url class loader to look for classes on the 907 * user-specified class path. Prepend computed bootclass 908 * path, which includes extdirs, to the URLClassLoader apt 909 * uses. 910 */ 911 String aptclasspath = ""; 912 String bcp = ""; 913 Iterable<? extends File> bootclasspath = fm.getLocation(StandardLocation.PLATFORM_CLASS_PATH); 914 915 if (bootclasspath != null) { 916 for(File f: bootclasspath) 917 bcp += (f + File.pathSeparator); 918 } 919 920 // If the factory path is set, use that path 921 if (providedFactory == null) 922 aptclasspath = options.get("-factorypath"); 923 if (aptclasspath == null) 924 aptclasspath = options.get("-classpath"); 925 926 assert aptclasspath != null; 927 aptclasspath = (bcp + aptclasspath); 928 aptCL = new URLClassLoader(pathToURLs(aptclasspath)); 929 930 if (providedFactory == null && 931 options.get("-factorypath") != null) // same CL even if new class files written 932 augmentedAptCL = aptCL; 933 else { 934 // Create class loader in case new class files are 935 // written 936 augmentedAptCL = new URLClassLoader(pathToURLs(augmentedClassPath. 937 substring(baseClassPath.length())), 938 aptCL); 939 } 940 } 941 942 int round = 0; // For -XPrintAptRounds 943 do { 944 round++; 945 946 Context newContext = new Context(); 947 Options newOptions = Options.instance(newContext); // creates a new context 948 newOptions.putAll(options); 949 950 // populate with old options... don't bother reparsing command line, etc. 951 952 // if genSource files, must add destination to source path 953 if (genSourceFileNames.size() > 0 && !firstRound) { 954 newOptions.put("-sourcepath", augmentedSourcePath); 955 needSourcePath = true; 956 } 957 aggregateGenSourceFileNames.addAll(genSourceFileNames); 958 sourceFileNames.addAll(genSourceFileNames); 959 genSourceFileNames.clear(); 960 961 // Don't really need to track this; just have to add -d 962 // "foo" to class path if any class files are generated 963 if (genClassFileNames.size() > 0) { 964 newOptions.put("-classpath", augmentedClassPath); 965 aptCL = augmentedAptCL; 966 needClassPath = true; 967 } 968 aggregateGenClassFileNames.addAll(genClassFileNames); 969 classFileNames.addAll(genClassFileNames); 970 genClassFileNames.clear(); 971 972 options = newOptions; 973 974 if (options.get("-XPrintAptRounds") != null) { 975 out.println("apt Round : " + round); 976 out.println("filenames: " + sourceFileNames); 977 if (classesAsDecls) 978 out.println("classnames: " + classFileNames); 979 out.println("options: " + options); 980 } 981 982 returnCode = compile(args, newContext); 983 firstRound = false; 984 985 // Check for reported errors before continuing 986 bark = Bark.instance(newContext); 987 } while(((genSourceFileNames.size() != 0 ) || 988 (classesAsDecls && genClassFileNames.size() != 0)) && 989 bark.nerrors == 0); 990 } catch (UsageMessageNeededException umne) { 991 help(); 992 return EXIT_CMDERR; // will cause usage message to be printed 993 } 994 995 /* 996 * Do not compile if a processor has reported an error or if 997 * there are no source files to process. A more sophisticated 998 * test would also fail for syntax errors caught by javac. 999 */ 1000 if (options.get("-nocompile") == null && 1001 options.get("-print") == null && 1002 bark.nerrors == 0 && 1003 (origFilenames.size() > 0 || aggregateGenSourceFileNames.size() > 0 )) { 1004 /* 1005 * Need to create new argument string for calling javac: 1006 * 1. apt specific arguments (e.g. -factory) must be stripped out 1007 * 2. proper settings for sourcepath and classpath must be used 1008 * 3. generated class names must be added 1009 * 4. class file names as declarations must be removed 1010 */ 1011 1012 int newArgsLength = args.length + 1013 (needSourcePath?1:0) + 1014 (needClassPath?1:0) + 1015 aggregateGenSourceFileNames.size(); 1016 1017 // Null out apt-specific options and don't copy over into 1018 // newArgs. This loop should be a lot faster; the options 1019 // array should be replaced with a better data structure 1020 // which includes a map from strings to options. 1021 // 1022 // If treating classes as declarations, must strip out 1023 // class names from the javac argument list 1024 argLoop: 1025 for(int i = 0; i < args.length; i++) { 1026 int matchPosition = -1; 1027 1028 // "-A" by itself is recognized by apt but not javac 1029 if (args[i] != null && args[i].equals("-A")) { 1030 newArgsLength--; 1031 args[i] = null; 1032 continue argLoop; 1033 } else { 1034 optionLoop: 1035 for(int j = 0; j < recognizedOptions.length; j++) { 1036 if (args[i] != null && recognizedOptions[j].matches(args[i])) { 1037 matchPosition = j; 1038 break optionLoop; 1039 } 1040 } 1041 1042 if (matchPosition != -1) { 1043 Option op = recognizedOptions[matchPosition]; 1044 if (op.aptOnly) { 1045 newArgsLength--; 1046 args[i] = null; 1047 if (op.hasArg()) { 1048 newArgsLength--; 1049 args[i+1] = null; 1050 } 1051 } else { 1052 if (op.hasArg()) { // skip over next string 1053 i++; 1054 continue argLoop; 1055 } 1056 1057 if ((options.get("-XclassesAsDecls") != null) && 1058 (matchPosition == (recognizedOptions.length-1)) ){ 1059 // Remove class file names from 1060 // consideration by javac. 1061 if (! args[i].endsWith(".java")) { 1062 newArgsLength--; 1063 args[i] = null; 1064 } 1065 } 1066 } 1067 } 1068 } 1069 } 1070 1071 String newArgs[] = new String[newArgsLength]; 1072 1073 int j = 0; 1074 for(int i=0; i < args.length; i++) { 1075 if (args[i] != null) 1076 newArgs[j++] = args[i]; 1077 } 1078 1079 if (needClassPath) 1080 newArgs[j++] = "-XD-classpath=" + augmentedClassPath; 1081 1082 if (needSourcePath) { 1083 newArgs[j++] = "-XD-sourcepath=" + augmentedSourcePath; 1084 1085 for(String s: aggregateGenSourceFileNames) 1086 newArgs[j++] = s; 1087 } 1088 1089 returnCode = com.sun.tools.javac.Main.compile(newArgs); 1090 } 1091 1092 return returnCode; 1093 } 1094 1095 /** Programmatic interface for main function. 1096 * @param args The command line parameters. 1097 */ 1098 int compile(String[] args, Context context) { 1099 boolean assertionsEnabled = false; 1100 assert assertionsEnabled = true; 1101 if (!assertionsEnabled) { 1102 // Bark.printLines(out, "fatal error: assertions must be enabled when running javac"); 1103 // return EXIT_ABNORMAL; 1104 } 1105 int exitCode = EXIT_OK; 1106 1107 JavaCompiler comp = null; 1108 try { 1109 context.put(Bark.outKey, out); 1110 1111 comp = JavaCompiler.instance(context); 1112 if (comp == null) 1113 return EXIT_SYSERR; 1114 1115 java.util.List<String> nameList = new java.util.LinkedList<String>(); 1116 nameList.addAll(sourceFileNames); 1117 if (options.get("-XclassesAsDecls") != null) 1118 nameList.addAll(classFileNames); 1119 1120 List<Symbol.ClassSymbol> cs 1121 = comp.compile(List.from(nameList.toArray(new String[0])), 1122 origOptions, 1123 aptCL, 1124 providedFactory, 1125 productiveFactories, 1126 aggregateGenFiles); 1127 1128 /* 1129 * If there aren't new source files, we shouldn't bother 1130 * running javac if there were errors. 1131 * 1132 * If there are new files, we should try running javac in 1133 * case there were typing errors. 1134 * 1135 */ 1136 1137 if (comp.errorCount() != 0 || 1138 options.get("-Werror") != null && comp.warningCount() != 0) 1139 return EXIT_ERROR; 1140 } catch (IOException ex) { 1141 ioMessage(ex); 1142 return EXIT_SYSERR; 1143 } catch (OutOfMemoryError ex) { 1144 resourceMessage(ex); 1145 return EXIT_SYSERR; 1146 } catch (StackOverflowError ex) { 1147 resourceMessage(ex); 1148 return EXIT_SYSERR; 1149 } catch (FatalError ex) { 1150 feMessage(ex); 1151 return EXIT_SYSERR; 1152 } catch (UsageMessageNeededException umne) { 1153 help(); 1154 return EXIT_CMDERR; // will cause usage message to be printed 1155 } catch (AnnotationProcessingError ex) { 1156 apMessage(ex); 1157 return EXIT_ABNORMAL; 1158 } catch (sun.misc.ServiceConfigurationError sce) { 1159 sceMessage(sce); 1160 return EXIT_ABNORMAL; 1161 } catch (Throwable ex) { 1162 bugMessage(ex); 1163 return EXIT_ABNORMAL; 1164 } finally { 1165 if (comp != null) { 1166 comp.close(); 1167 genSourceFileNames.addAll(comp.getSourceFileNames()); 1168 genClassFileNames.addAll(comp.getClassFileNames()); 1169 } 1170 sourceFileNames = new java.util.LinkedList<String>(); 1171 classFileNames = new java.util.LinkedList<String>(); 1172 } 1173 return exitCode; 1174 } 1175 1176 /** Print a message reporting an internal error. 1177 */ 1178 void bugMessage(Throwable ex) { 1179 Bark.printLines(out, getLocalizedString("msg.bug", 1180 JavaCompiler.version())); 1181 ex.printStackTrace(out); 1182 } 1183 1184 /** Print a message reporting an fatal error. 1185 */ 1186 void apMessage(AnnotationProcessingError ex) { 1187 Bark.printLines(out, getLocalizedString("misc.Problem")); 1188 ex.getCause().printStackTrace(out); 1189 } 1190 1191 /** Print a message about sun.misc.Service problem. 1192 */ 1193 void sceMessage(sun.misc.ServiceConfigurationError ex) { 1194 Bark.printLines(out, getLocalizedString("misc.SunMiscService")); 1195 ex.printStackTrace(out); 1196 } 1197 1198 /** Print a message reporting an fatal error. 1199 */ 1200 void feMessage(Throwable ex) { 1201 Bark.printLines(out, ex.toString()); 1202 } 1203 1204 /** Print a message reporting an input/output error. 1205 */ 1206 void ioMessage(Throwable ex) { 1207 Bark.printLines(out, getLocalizedString("msg.io")); 1208 ex.printStackTrace(out); 1209 } 1210 1211 /** Print a message reporting an out-of-resources error. 1212 */ 1213 void resourceMessage(Throwable ex) { 1214 Bark.printLines(out, getLocalizedString("msg.resource")); 1215 ex.printStackTrace(out); 1216 } 1217 1218 /* ************************************************************************ 1219 * Internationalization 1220 *************************************************************************/ 1221 1222 /** Find a localized string in the resource bundle. 1223 * @param key The key for the localized string. 1224 */ 1225 private static String getLocalizedString(String key, Object... args) { 1226 return getText(key, args); 1227 } 1228 1229 private static final String javacRB = 1230 "com.sun.tools.javac.resources.javac"; 1231 1232 private static final String aptRB = 1233 "com.sun.tools.apt.resources.apt"; 1234 1235 private static ResourceBundle messageRBjavac; 1236 private static ResourceBundle messageRBapt; 1237 1238 /** Initialize ResourceBundle. 1239 */ 1240 private static void initResource() { 1241 try { 1242 messageRBapt = ResourceBundle.getBundle(aptRB); 1243 messageRBjavac = ResourceBundle.getBundle(javacRB); 1244 } catch (MissingResourceException e) { 1245 Error x = new FatalError("Fatal Error: Resource for apt or javac is missing"); 1246 x.initCause(e); 1247 throw x; 1248 } 1249 } 1250 1251 /** Get and format message string from resource. 1252 */ 1253 private static String getText(String key, Object... _args) { 1254 String[] args = new String[_args.length]; 1255 for (int i=0; i<_args.length; i++) { 1256 args[i] = "" + _args[i]; 1257 } 1258 if (messageRBapt == null || messageRBjavac == null ) 1259 initResource(); 1260 try { 1261 return MessageFormat.format(messageRBapt.getString("apt." + key), 1262 (Object[]) args); 1263 } catch (MissingResourceException e) { 1264 try { 1265 return MessageFormat.format(messageRBjavac.getString("javac." + key), 1266 (Object[]) args); 1267 } catch (MissingResourceException f) { 1268 String msg = "apt or javac message file broken: key={0} " 1269 + "arguments={1}, {2}"; 1270 return MessageFormat.format(msg, (Object[]) args); 1271 } 1272 } 1273 } 1274 1275 // Borrowed from DocletInvoker 1276 /** 1277 * Utility method for converting a search path string to an array 1278 * of directory and JAR file URLs. 1279 * 1280 * @param path the search path string 1281 * @return the resulting array of directory and JAR file URLs 1282 */ 1283 static URL[] pathToURLs(String path) { 1284 StringTokenizer st = new StringTokenizer(path, File.pathSeparator); 1285 URL[] urls = new URL[st.countTokens()]; 1286 int count = 0; 1287 while (st.hasMoreTokens()) { 1288 URL url = fileToURL(new File(st.nextToken())); 1289 if (url != null) { 1290 urls[count++] = url; 1291 } 1292 } 1293 if (urls.length != count) { 1294 URL[] tmp = new URL[count]; 1295 System.arraycopy(urls, 0, tmp, 0, count); 1296 urls = tmp; 1297 } 1298 return urls; 1299 } 1300 1301 /** 1302 * Returns the directory or JAR file URL corresponding to the specified 1303 * local file name. 1304 * 1305 * @param file the File object 1306 * @return the resulting directory or JAR file URL, or null if unknown 1307 */ 1308 static URL fileToURL(File file) { 1309 String name; 1310 try { 1311 name = file.getCanonicalPath(); 1312 } catch (IOException e) { 1313 name = file.getAbsolutePath(); 1314 } 1315 name = name.replace(File.separatorChar, '/'); 1316 if (!name.startsWith("/")) { 1317 name = "/" + name; 1318 } 1319 // If the file does not exist, then assume that it's a directory 1320 if (!file.isFile()) { 1321 name = name + "/"; 1322 } 1323 try { 1324 return new URL("file", "", name); 1325 } catch (MalformedURLException e) { 1326 throw new IllegalArgumentException("file"); 1327 } 1328 } 1329 }