1 /* 2 * Copyright (c) 1999, 2013, Oracle and/or its affiliates. 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.tools.javac.main; 27 28 import java.io.File; 29 import java.io.IOException; 30 import java.io.PrintWriter; 31 import java.net.URL; 32 import java.security.DigestInputStream; 33 import java.security.MessageDigest; 34 import java.util.Arrays; 35 import java.util.Collection; 36 import java.util.Iterator; 37 import java.util.LinkedHashSet; 38 import java.util.Set; 39 40 import javax.annotation.processing.Processor; 41 import javax.tools.JavaFileManager; 42 import javax.tools.JavaFileObject; 43 44 import com.sun.source.util.JavacTask; 45 import com.sun.source.util.Plugin; 46 import com.sun.tools.doclint.DocLint; 47 import com.sun.tools.javac.api.BasicJavacTask; 48 import com.sun.tools.javac.code.Source; 49 import com.sun.tools.javac.file.CacheFSInfo; 50 import com.sun.tools.javac.file.JavacFileManager; 51 import com.sun.tools.javac.jvm.Profile; 52 import com.sun.tools.javac.jvm.Target; 53 import com.sun.tools.javac.processing.AnnotationProcessingError; 54 import com.sun.tools.javac.processing.JavacProcessingEnvironment; 55 import com.sun.tools.javac.util.*; 56 import com.sun.tools.javac.util.Log.PrefixKind; 57 import com.sun.tools.javac.util.Log.WriterKind; 58 import com.sun.tools.javac.util.ServiceLoader; 59 import static com.sun.tools.javac.main.Option.*; 60 61 /** This class provides a command line interface to the javac compiler. 62 * 63 * <p><b>This is NOT part of any supported API. 64 * If you write code that depends on this, you do so at your own risk. 65 * This code and its internal interfaces are subject to change or 66 * deletion without notice.</b> 67 */ 68 public class Main { 69 70 /** The name of the compiler, for use in diagnostics. 71 */ 72 String ownName; 73 74 /** The writer to use for diagnostic output. 75 */ 76 PrintWriter out; 77 78 /** The log to use for diagnostic output. 79 */ 80 public Log log; 81 82 /** 83 * If true, certain errors will cause an exception, such as command line 84 * arg errors, or exceptions in user provided code. 85 */ 86 boolean apiMode; 87 88 89 /** Result codes. 90 */ 91 public enum Result { 92 OK(0), // Compilation completed with no errors. 93 ERROR(1), // Completed but reported errors. 94 CMDERR(2), // Bad command-line arguments 95 SYSERR(3), // System error or resource exhaustion. 96 ABNORMAL(4); // Compiler terminated abnormally 97 98 Result(int exitCode) { 99 this.exitCode = exitCode; 100 } 101 102 public boolean isOK() { 103 return (exitCode == 0); 104 } 105 106 public final int exitCode; 107 } 108 109 private Option[] recognizedOptions = 110 Option.getJavaCompilerOptions().toArray(new Option[0]); 111 112 private OptionHelper optionHelper = new OptionHelper() { 113 @Override 114 public String get(Option option) { 115 return options.get(option); 116 } 117 118 @Override 119 public void put(String name, String value) { 120 options.put(name, value); 121 } 122 123 @Override 124 public void remove(String name) { 125 options.remove(name); 126 } 127 128 @Override 129 public Log getLog() { 130 return log; 131 } 132 133 @Override 134 public String getOwnName() { 135 return ownName; 136 } 137 138 @Override 139 public void error(String key, Object... args) { 140 Main.this.error(key, args); 141 } 142 143 @Override 144 public void addFile(File f) { 145 filenames.add(f); 146 } 147 148 @Override 149 public void addClassName(String s) { 150 classnames.append(s); 151 } 152 153 }; 154 155 /** 156 * Construct a compiler instance. 157 */ 158 public Main(String name) { 159 this(name, new PrintWriter(System.err, true)); 160 } 161 162 /** 163 * Construct a compiler instance. 164 */ 165 public Main(String name, PrintWriter out) { 166 this.ownName = name; 167 this.out = out; 168 } 169 170 /** A table of all options that's passed to the JavaCompiler constructor. */ 171 private Options options = null; 172 173 /** The list of source files to process 174 */ 175 public Set<File> filenames = null; // XXX sb protected 176 177 /** List of class files names passed on the command line 178 */ 179 public ListBuffer<String> classnames = null; // XXX sb protected 180 181 /** Report a usage error. 182 */ 183 void error(String key, Object... args) { 184 if (apiMode) { 185 String msg = log.localize(PrefixKind.JAVAC, key, args); 186 throw new PropagatedException(new IllegalStateException(msg)); 187 } 188 warning(key, args); 189 log.printLines(PrefixKind.JAVAC, "msg.usage", ownName); 190 } 191 192 /** Report a warning. 193 */ 194 void warning(String key, Object... args) { 195 log.printRawLines(ownName + ": " + log.localize(PrefixKind.JAVAC, key, args)); 196 } 197 198 public Option getOption(String flag) { 199 for (Option option : recognizedOptions) { 200 if (option.matches(flag)) 201 return option; 202 } 203 return null; 204 } 205 206 public void setOptions(Options options) { 207 if (options == null) 208 throw new NullPointerException(); 209 this.options = options; 210 } 211 212 public void setAPIMode(boolean apiMode) { 213 this.apiMode = apiMode; 214 } 215 216 /** Process command line arguments: store all command line options 217 * in `options' table and return all source filenames. 218 * @param flags The array of command line arguments. 219 */ 220 public Collection<File> processArgs(String[] flags) { // XXX sb protected 221 return processArgs(flags, null); 222 } 223 224 public Collection<File> processArgs(String[] flags, String[] classNames) { // XXX sb protected 225 int ac = 0; 226 while (ac < flags.length) { 227 String flag = flags[ac]; 228 ac++; 229 230 Option option = null; 231 232 if (flag.length() > 0) { 233 // quick hack to speed up file processing: 234 // if the option does not begin with '-', there is no need to check 235 // most of the compiler options. 236 int firstOptionToCheck = flag.charAt(0) == '-' ? 0 : recognizedOptions.length-1; 237 for (int j=firstOptionToCheck; j<recognizedOptions.length; j++) { 238 if (recognizedOptions[j].matches(flag)) { 239 option = recognizedOptions[j]; 240 break; 241 } 242 } 243 } 244 245 if (option == null) { 246 error("err.invalid.flag", flag); 247 return null; 248 } 249 250 if (option.hasArg()) { 251 if (ac == flags.length) { 252 error("err.req.arg", flag); 253 return null; 254 } 255 String operand = flags[ac]; 256 ac++; 257 if (option.process(optionHelper, flag, operand)) 258 return null; 259 } else { 260 if (option.process(optionHelper, flag)) 261 return null; 262 } 263 } 264 265 if (this.classnames != null && classNames != null) { 266 this.classnames.addAll(Arrays.asList(classNames)); 267 } 268 269 if (!checkDirectory(D)) 270 return null; 271 if (!checkDirectory(S)) 272 return null; 273 274 String sourceString = options.get(SOURCE); 275 Source source = (sourceString != null) 276 ? Source.lookup(sourceString) 277 : Source.DEFAULT; 278 String targetString = options.get(TARGET); 279 Target target = (targetString != null) 280 ? Target.lookup(targetString) 281 : Target.DEFAULT; 282 // We don't check source/target consistency for CLDC, as J2ME 283 // profiles are not aligned with J2SE targets; moreover, a 284 // single CLDC target may have many profiles. In addition, 285 // this is needed for the continued functioning of the JSR14 286 // prototype. 287 if (Character.isDigit(target.name.charAt(0))) { 288 if (target.compareTo(source.requiredTarget()) < 0) { 289 if (targetString != null) { 290 if (sourceString == null) { 291 warning("warn.target.default.source.conflict", 292 targetString, 293 source.requiredTarget().name); 294 } else { 295 warning("warn.source.target.conflict", 296 sourceString, 297 source.requiredTarget().name); 298 } 299 return null; 300 } else { 301 target = source.requiredTarget(); 302 options.put("-target", target.name); 303 } 304 } else { 305 if (targetString == null && !source.allowGenerics()) { 306 target = Target.JDK1_4; 307 options.put("-target", target.name); 308 } 309 } 310 } 311 312 String profileString = options.get(PROFILE); 313 if (profileString != null) { 314 Profile profile = Profile.lookup(profileString); 315 if (!profile.isValid(target)) { 316 warning("warn.profile.target.conflict", profileString, target.name); 317 return null; 318 } 319 } 320 321 // handle this here so it works even if no other options given 322 String showClass = options.get("showClass"); 323 if (showClass != null) { 324 if (showClass.equals("showClass")) // no value given for option 325 showClass = "com.sun.tools.javac.Main"; 326 showClass(showClass); 327 } 328 329 options.notifyListeners(); 330 331 return filenames; 332 } 333 // where 334 private boolean checkDirectory(Option option) { 335 String value = options.get(option); 336 if (value == null) 337 return true; 338 File file = new File(value); 339 if (!file.exists()) { 340 error("err.dir.not.found", value); 341 return false; 342 } 343 if (!file.isDirectory()) { 344 error("err.file.not.directory", value); 345 return false; 346 } 347 return true; 348 } 349 350 /** Programmatic interface for main function. 351 * @param args The command line parameters. 352 */ 353 public Result compile(String[] args) { 354 Context context = new Context(); 355 JavacFileManager.preRegister(context); // can't create it until Log has been set up 356 Result result = compile(args, context); 357 if (fileManager instanceof JavacFileManager) { 358 // A fresh context was created above, so jfm must be a JavacFileManager 359 ((JavacFileManager)fileManager).close(); 360 } 361 return result; 362 } 363 364 public Result compile(String[] args, Context context) { 365 return compile(args, context, List.<JavaFileObject>nil(), null); 366 } 367 368 /** Programmatic interface for main function. 369 * @param args The command line parameters. 370 */ 371 public Result compile(String[] args, 372 Context context, 373 List<JavaFileObject> fileObjects, 374 Iterable<? extends Processor> processors) 375 { 376 return compile(args, null, context, fileObjects, processors); 377 } 378 379 public Result compile(String[] args, 380 String[] classNames, 381 Context context, 382 List<JavaFileObject> fileObjects, 383 Iterable<? extends Processor> processors) 384 { 385 context.put(Log.outKey, out); 386 log = Log.instance(context); 387 388 if (options == null) 389 options = Options.instance(context); // creates a new one 390 391 filenames = new LinkedHashSet<File>(); 392 classnames = new ListBuffer<String>(); 393 JavaCompiler comp = null; 394 /* 395 * TODO: Logic below about what is an acceptable command line 396 * should be updated to take annotation processing semantics 397 * into account. 398 */ 399 try { 400 if (args.length == 0 401 && (classNames == null || classNames.length == 0) 402 && fileObjects.isEmpty()) { 403 Option.HELP.process(optionHelper, "-help"); 404 return Result.CMDERR; 405 } 406 407 Collection<File> files; 408 try { 409 files = processArgs(CommandLine.parse(args), classNames); 410 if (files == null) { 411 // null signals an error in options, abort 412 return Result.CMDERR; 413 } else if (files.isEmpty() && fileObjects.isEmpty() && classnames.isEmpty()) { 414 // it is allowed to compile nothing if just asking for help or version info 415 if (options.isSet(HELP) 416 || options.isSet(X) 417 || options.isSet(VERSION) 418 || options.isSet(FULLVERSION)) 419 return Result.OK; 420 if (JavaCompiler.explicitAnnotationProcessingRequested(options)) { 421 error("err.no.source.files.classes"); 422 } else { 423 error("err.no.source.files"); 424 } 425 return Result.CMDERR; 426 } 427 } catch (java.io.FileNotFoundException e) { 428 warning("err.file.not.found", e.getMessage()); 429 return Result.SYSERR; 430 } 431 432 boolean forceStdOut = options.isSet("stdout"); 433 if (forceStdOut) { 434 log.flush(); 435 log.setWriters(new PrintWriter(System.out, true)); 436 } 437 438 // allow System property in following line as a Mustang legacy 439 boolean batchMode = (options.isUnset("nonBatchMode") 440 && System.getProperty("nonBatchMode") == null); 441 if (batchMode) 442 CacheFSInfo.preRegister(context); 443 444 // FIXME: this code will not be invoked if using JavacTask.parse/analyze/generate 445 // invoke any available plugins 446 String plugins = options.get(PLUGIN); 447 if (plugins != null) { 448 JavacProcessingEnvironment pEnv = JavacProcessingEnvironment.instance(context); 449 ClassLoader cl = pEnv.getProcessorClassLoader(); 450 ServiceLoader<Plugin> sl = ServiceLoader.load(Plugin.class, cl); 451 Set<List<String>> pluginsToCall = new LinkedHashSet<List<String>>(); 452 for (String plugin: plugins.split("\\x00")) { 453 pluginsToCall.add(List.from(plugin.split("\\s+"))); 454 } 455 JavacTask task = null; 456 Iterator<Plugin> iter = sl.iterator(); 457 while (iter.hasNext()) { 458 Plugin plugin = iter.next(); 459 for (List<String> p: pluginsToCall) { 460 if (plugin.getName().equals(p.head)) { 461 pluginsToCall.remove(p); 462 try { 463 if (task == null) 464 task = JavacTask.instance(pEnv); 465 plugin.init(task, p.tail.toArray(new String[p.tail.size()])); 466 } catch (Throwable ex) { 467 if (apiMode) 468 throw new RuntimeException(ex); 469 pluginMessage(ex); 470 return Result.SYSERR; 471 } 472 } 473 } 474 } 475 for (List<String> p: pluginsToCall) { 476 log.printLines(PrefixKind.JAVAC, "msg.plugin.not.found", p.head); 477 } 478 } 479 480 comp = JavaCompiler.instance(context); 481 482 // FIXME: this code will not be invoked if using JavacTask.parse/analyze/generate 483 String xdoclint = options.get(XDOCLINT); 484 String xdoclintCustom = options.get(XDOCLINT_CUSTOM); 485 if (xdoclint != null || xdoclintCustom != null) { 486 Set<String> doclintOpts = new LinkedHashSet<String>(); 487 if (xdoclint != null) 488 doclintOpts.add(DocLint.XMSGS_OPTION); 489 if (xdoclintCustom != null) { 490 for (String s: xdoclintCustom.split("\\s+")) { 491 if (s.isEmpty()) 492 continue; 493 doclintOpts.add(s.replace(XDOCLINT_CUSTOM.text, DocLint.XMSGS_CUSTOM_PREFIX)); 494 } 495 } 496 if (!(doclintOpts.size() == 1 497 && doclintOpts.iterator().next().equals(DocLint.XMSGS_CUSTOM_PREFIX + "none"))) { 498 JavacTask t = BasicJavacTask.instance(context); 499 // standard doclet normally generates H1, H2 500 doclintOpts.add(DocLint.XIMPLICIT_HEADERS + "2"); 501 new DocLint().init(t, doclintOpts.toArray(new String[doclintOpts.size()])); 502 comp.keepComments = true; 503 } 504 } 505 506 fileManager = context.get(JavaFileManager.class); 507 508 if (!files.isEmpty()) { 509 // add filenames to fileObjects 510 comp = JavaCompiler.instance(context); 511 List<JavaFileObject> otherFiles = List.nil(); 512 JavacFileManager dfm = (JavacFileManager)fileManager; 513 for (JavaFileObject fo : dfm.getJavaFileObjectsFromFiles(files)) 514 otherFiles = otherFiles.prepend(fo); 515 for (JavaFileObject fo : otherFiles) 516 fileObjects = fileObjects.prepend(fo); 517 } 518 comp.compile(fileObjects, 519 classnames.toList(), 520 processors); 521 522 if (log.expectDiagKeys != null) { 523 if (log.expectDiagKeys.isEmpty()) { 524 log.printRawLines("all expected diagnostics found"); 525 return Result.OK; 526 } else { 527 log.printRawLines("expected diagnostic keys not found: " + log.expectDiagKeys); 528 return Result.ERROR; 529 } 530 } 531 532 if (comp.errorCount() != 0) 533 return Result.ERROR; 534 } catch (IOException ex) { 535 ioMessage(ex); 536 return Result.SYSERR; 537 } catch (OutOfMemoryError ex) { 538 resourceMessage(ex); 539 return Result.SYSERR; 540 } catch (StackOverflowError ex) { 541 resourceMessage(ex); 542 return Result.SYSERR; 543 } catch (FatalError ex) { 544 feMessage(ex); 545 return Result.SYSERR; 546 } catch (AnnotationProcessingError ex) { 547 if (apiMode) 548 throw new RuntimeException(ex.getCause()); 549 apMessage(ex); 550 return Result.SYSERR; 551 } catch (ClientCodeException ex) { 552 // as specified by javax.tools.JavaCompiler#getTask 553 // and javax.tools.JavaCompiler.CompilationTask#call 554 throw new RuntimeException(ex.getCause()); 555 } catch (PropagatedException ex) { 556 throw ex.getCause(); 557 } catch (Throwable ex) { 558 // Nasty. If we've already reported an error, compensate 559 // for buggy compiler error recovery by swallowing thrown 560 // exceptions. 561 if (comp == null || comp.errorCount() == 0 || 562 options == null || options.isSet("dev")) 563 bugMessage(ex); 564 return Result.ABNORMAL; 565 } finally { 566 if (comp != null) { 567 try { 568 comp.close(); 569 } catch (ClientCodeException ex) { 570 throw new RuntimeException(ex.getCause()); 571 } 572 } 573 filenames = null; 574 options = null; 575 } 576 return Result.OK; 577 } 578 579 /** Print a message reporting an internal error. 580 */ 581 void bugMessage(Throwable ex) { 582 log.printLines(PrefixKind.JAVAC, "msg.bug", JavaCompiler.version()); 583 ex.printStackTrace(log.getWriter(WriterKind.NOTICE)); 584 } 585 586 /** Print a message reporting a fatal error. 587 */ 588 void feMessage(Throwable ex) { 589 log.printRawLines(ex.getMessage()); 590 if (ex.getCause() != null && options.isSet("dev")) { 591 ex.getCause().printStackTrace(log.getWriter(WriterKind.NOTICE)); 592 } 593 } 594 595 /** Print a message reporting an input/output error. 596 */ 597 void ioMessage(Throwable ex) { 598 log.printLines(PrefixKind.JAVAC, "msg.io"); 599 ex.printStackTrace(log.getWriter(WriterKind.NOTICE)); 600 } 601 602 /** Print a message reporting an out-of-resources error. 603 */ 604 void resourceMessage(Throwable ex) { 605 log.printLines(PrefixKind.JAVAC, "msg.resource"); 606 ex.printStackTrace(log.getWriter(WriterKind.NOTICE)); 607 } 608 609 /** Print a message reporting an uncaught exception from an 610 * annotation processor. 611 */ 612 void apMessage(AnnotationProcessingError ex) { 613 log.printLines(PrefixKind.JAVAC, "msg.proc.annotation.uncaught.exception"); 614 ex.getCause().printStackTrace(log.getWriter(WriterKind.NOTICE)); 615 } 616 617 /** Print a message reporting an uncaught exception from an 618 * annotation processor. 619 */ 620 void pluginMessage(Throwable ex) { 621 log.printLines(PrefixKind.JAVAC, "msg.plugin.uncaught.exception"); 622 ex.printStackTrace(log.getWriter(WriterKind.NOTICE)); 623 } 624 625 /** Display the location and checksum of a class. */ 626 void showClass(String className) { 627 PrintWriter pw = log.getWriter(WriterKind.NOTICE); 628 pw.println("javac: show class: " + className); 629 URL url = getClass().getResource('/' + className.replace('.', '/') + ".class"); 630 if (url == null) 631 pw.println(" class not found"); 632 else { 633 pw.println(" " + url); 634 try { 635 final String algorithm = "MD5"; 636 byte[] digest; 637 MessageDigest md = MessageDigest.getInstance(algorithm); 638 DigestInputStream in = new DigestInputStream(url.openStream(), md); 639 try { 640 byte[] buf = new byte[8192]; 641 int n; 642 do { n = in.read(buf); } while (n > 0); 643 digest = md.digest(); 644 } finally { 645 in.close(); 646 } 647 StringBuilder sb = new StringBuilder(); 648 for (byte b: digest) 649 sb.append(String.format("%02x", b)); 650 pw.println(" " + algorithm + " checksum: " + sb); 651 } catch (Exception e) { 652 pw.println(" cannot compute digest: " + e); 653 } 654 } 655 } 656 657 private JavaFileManager fileManager; 658 659 /* ************************************************************************ 660 * Internationalization 661 *************************************************************************/ 662 663 // /** Find a localized string in the resource bundle. 664 // * @param key The key for the localized string. 665 // */ 666 // public static String getLocalizedString(String key, Object... args) { // FIXME sb private 667 // try { 668 // if (messages == null) 669 // messages = new JavacMessages(javacBundleName); 670 // return messages.getLocalizedString("javac." + key, args); 671 // } 672 // catch (MissingResourceException e) { 673 // throw new Error("Fatal Error: Resource for javac is missing", e); 674 // } 675 // } 676 // 677 // public static void useRawMessages(boolean enable) { 678 // if (enable) { 679 // messages = new JavacMessages(javacBundleName) { 680 // @Override 681 // public String getLocalizedString(String key, Object... args) { 682 // return key; 683 // } 684 // }; 685 // } else { 686 // messages = new JavacMessages(javacBundleName); 687 // } 688 // } 689 690 public static final String javacBundleName = 691 "com.sun.tools.javac.resources.javac"; 692 // 693 // private static JavacMessages messages; 694 }