Main.java revision 4002:4a4fd9ecca20
1/* 2 * Copyright (c) 1999, 2017, 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 26package com.sun.tools.javac.main; 27 28import java.io.FileNotFoundException; 29import java.io.IOException; 30import java.io.InputStream; 31import java.io.PrintWriter; 32import java.net.URL; 33import java.nio.file.NoSuchFileException; 34import java.security.DigestInputStream; 35import java.security.MessageDigest; 36import java.security.NoSuchAlgorithmException; 37import java.util.Set; 38 39import javax.tools.JavaFileManager; 40 41import com.sun.tools.javac.api.BasicJavacTask; 42import com.sun.tools.javac.file.CacheFSInfo; 43import com.sun.tools.javac.file.BaseFileManager; 44import com.sun.tools.javac.file.JavacFileManager; 45import com.sun.tools.javac.jvm.Target; 46import com.sun.tools.javac.main.CommandLine.UnmatchedQuote; 47import com.sun.tools.javac.platform.PlatformDescription; 48import com.sun.tools.javac.processing.AnnotationProcessingError; 49import com.sun.tools.javac.util.*; 50import com.sun.tools.javac.util.Log.PrefixKind; 51import com.sun.tools.javac.util.Log.WriterKind; 52 53/** This class provides a command line interface to the javac compiler. 54 * 55 * <p><b>This is NOT part of any supported API. 56 * If you write code that depends on this, you do so at your own risk. 57 * This code and its internal interfaces are subject to change or 58 * deletion without notice.</b> 59 */ 60public class Main { 61 62 /** The name of the compiler, for use in diagnostics. 63 */ 64 String ownName; 65 66 /** The writer to use for normal output. 67 */ 68 PrintWriter stdOut; 69 70 /** The writer to use for diagnostic output. 71 */ 72 PrintWriter stdErr; 73 74 /** The log to use for diagnostic output. 75 */ 76 public Log log; 77 78 /** 79 * If true, certain errors will cause an exception, such as command line 80 * arg errors, or exceptions in user provided code. 81 */ 82 boolean apiMode; 83 84 private static final String ENV_OPT_NAME = "JDK_JAVAC_OPTIONS"; 85 86 /** Result codes. 87 */ 88 public enum Result { 89 OK(0), // Compilation completed with no errors. 90 ERROR(1), // Completed but reported errors. 91 CMDERR(2), // Bad command-line arguments 92 SYSERR(3), // System error or resource exhaustion. 93 ABNORMAL(4); // Compiler terminated abnormally 94 95 Result(int exitCode) { 96 this.exitCode = exitCode; 97 } 98 99 public boolean isOK() { 100 return (exitCode == 0); 101 } 102 103 public final int exitCode; 104 } 105 106 /** 107 * Construct a compiler instance. 108 * @param name the name of this tool 109 */ 110 public Main(String name) { 111 this.ownName = name; 112 } 113 114 /** 115 * Construct a compiler instance. 116 * @param name the name of this tool 117 * @param out a stream to which to write messages 118 */ 119 public Main(String name, PrintWriter out) { 120 this.ownName = name; 121 this.stdOut = this.stdErr = out; 122 } 123 124 /** 125 * Construct a compiler instance. 126 * @param name the name of this tool 127 * @param out a stream to which to write expected output 128 * @param err a stream to which to write diagnostic output 129 */ 130 public Main(String name, PrintWriter out, PrintWriter err) { 131 this.ownName = name; 132 this.stdOut = out; 133 this.stdErr = err; 134 } 135 136 /** Report a usage error. 137 */ 138 void error(String key, Object... args) { 139 if (apiMode) { 140 String msg = log.localize(PrefixKind.JAVAC, key, args); 141 throw new PropagatedException(new IllegalStateException(msg)); 142 } 143 warning(key, args); 144 log.printLines(PrefixKind.JAVAC, "msg.usage", ownName); 145 } 146 147 /** Report a warning. 148 */ 149 void warning(String key, Object... args) { 150 log.printRawLines(ownName + ": " + log.localize(PrefixKind.JAVAC, key, args)); 151 } 152 153 154 /** 155 * Programmatic interface for main function. 156 * @param args the command line parameters 157 * @return the result of the compilation 158 */ 159 public Result compile(String[] args) { 160 Context context = new Context(); 161 JavacFileManager.preRegister(context); // can't create it until Log has been set up 162 Result result = compile(args, context); 163 if (fileManager instanceof JavacFileManager) { 164 try { 165 // A fresh context was created above, so jfm must be a JavacFileManager 166 ((JavacFileManager)fileManager).close(); 167 } catch (IOException ex) { 168 bugMessage(ex); 169 } 170 } 171 return result; 172 } 173 174 /** 175 * Internal version of compile, allowing context to be provided. 176 * Note that the context needs to have a file manager set up. 177 * @param argv the command line parameters 178 * @param context the context 179 * @return the result of the compilation 180 */ 181 public Result compile(String[] argv, Context context) { 182 if (stdOut != null) { 183 context.put(Log.outKey, stdOut); 184 } 185 186 if (stdErr != null) { 187 context.put(Log.errKey, stdErr); 188 } 189 190 log = Log.instance(context); 191 192 if (argv.length == 0) { 193 OptionHelper h = new OptionHelper.GrumpyHelper(log) { 194 @Override 195 public String getOwnName() { return ownName; } 196 @Override 197 public void put(String name, String value) { } 198 }; 199 try { 200 Option.HELP.process(h, "-help"); 201 } catch (Option.InvalidValueException ignore) { 202 } 203 return Result.CMDERR; 204 } 205 206 // prefix argv with contents of environment variable and expand @-files 207 try { 208 argv = CommandLine.parse(ENV_OPT_NAME, argv); 209 } catch (UnmatchedQuote ex) { 210 error("err.unmatched.quote", ex.variableName); 211 return Result.CMDERR; 212 } catch (FileNotFoundException | NoSuchFileException e) { 213 warning("err.file.not.found", e.getMessage()); 214 return Result.SYSERR; 215 } catch (IOException ex) { 216 log.printLines(PrefixKind.JAVAC, "msg.io"); 217 ex.printStackTrace(log.getWriter(WriterKind.NOTICE)); 218 return Result.SYSERR; 219 } 220 221 Arguments args = Arguments.instance(context); 222 args.init(ownName, argv); 223 224 if (log.nerrors > 0) 225 return Result.CMDERR; 226 227 Options options = Options.instance(context); 228 229 // init Log 230 boolean forceStdOut = options.isSet("stdout"); 231 if (forceStdOut) { 232 log.flush(); 233 log.setWriters(new PrintWriter(System.out, true)); 234 } 235 236 // init CacheFSInfo 237 // allow System property in following line as a Mustang legacy 238 boolean batchMode = (options.isUnset("nonBatchMode") 239 && System.getProperty("nonBatchMode") == null); 240 if (batchMode) 241 CacheFSInfo.preRegister(context); 242 243 boolean ok = true; 244 245 // init file manager 246 fileManager = context.get(JavaFileManager.class); 247 if (fileManager instanceof BaseFileManager) { 248 ((BaseFileManager) fileManager).setContext(context); // reinit with options 249 ok &= ((BaseFileManager) fileManager).handleOptions(args.getDeferredFileManagerOptions()); 250 } 251 252 // handle this here so it works even if no other options given 253 String showClass = options.get("showClass"); 254 if (showClass != null) { 255 if (showClass.equals("showClass")) // no value given for option 256 showClass = "com.sun.tools.javac.Main"; 257 showClass(showClass); 258 } 259 260 ok &= args.validate(); 261 if (!ok || log.nerrors > 0) 262 return Result.CMDERR; 263 264 if (args.isEmpty()) 265 return Result.OK; 266 267 // init Dependencies 268 if (options.isSet("debug.completionDeps")) { 269 Dependencies.GraphDependencies.preRegister(context); 270 } 271 272 // init plugins 273 Set<List<String>> pluginOpts = args.getPluginOpts(); 274 if (!pluginOpts.isEmpty() || context.get(PlatformDescription.class) != null) { 275 BasicJavacTask t = (BasicJavacTask) BasicJavacTask.instance(context); 276 t.initPlugins(pluginOpts); 277 } 278 279 // init multi-release jar handling 280 if (fileManager.isSupportedOption(Option.MULTIRELEASE.primaryName) == 1) { 281 Target target = Target.instance(context); 282 List<String> list = List.of(target.multiReleaseValue()); 283 fileManager.handleOption(Option.MULTIRELEASE.primaryName, list.iterator()); 284 } 285 286 // init JavaCompiler 287 JavaCompiler comp = JavaCompiler.instance(context); 288 289 // init doclint 290 List<String> docLintOpts = args.getDocLintOpts(); 291 if (!docLintOpts.isEmpty()) { 292 BasicJavacTask t = (BasicJavacTask) BasicJavacTask.instance(context); 293 t.initDocLint(docLintOpts); 294 } 295 296 if (options.get(Option.XSTDOUT) != null) { 297 // Stdout reassigned - ask compiler to close it when it is done 298 comp.closeables = comp.closeables.prepend(log.getWriter(WriterKind.NOTICE)); 299 } 300 301 try { 302 comp.compile(args.getFileObjects(), args.getClassNames(), null, List.nil()); 303 304 if (log.expectDiagKeys != null) { 305 if (log.expectDiagKeys.isEmpty()) { 306 log.printRawLines("all expected diagnostics found"); 307 return Result.OK; 308 } else { 309 log.printRawLines("expected diagnostic keys not found: " + log.expectDiagKeys); 310 return Result.ERROR; 311 } 312 } 313 314 return (comp.errorCount() == 0) ? Result.OK : Result.ERROR; 315 316 } catch (OutOfMemoryError | StackOverflowError ex) { 317 resourceMessage(ex); 318 return Result.SYSERR; 319 } catch (FatalError ex) { 320 feMessage(ex, options); 321 return Result.SYSERR; 322 } catch (AnnotationProcessingError ex) { 323 apMessage(ex); 324 return Result.SYSERR; 325 } catch (PropagatedException ex) { 326 // TODO: what about errors from plugins? should not simply rethrow the error here 327 throw ex.getCause(); 328 } catch (Throwable ex) { 329 // Nasty. If we've already reported an error, compensate 330 // for buggy compiler error recovery by swallowing thrown 331 // exceptions. 332 if (comp == null || comp.errorCount() == 0 || options.isSet("dev")) 333 bugMessage(ex); 334 return Result.ABNORMAL; 335 } finally { 336 if (comp != null) { 337 try { 338 comp.close(); 339 } catch (ClientCodeException ex) { 340 throw new RuntimeException(ex.getCause()); 341 } 342 } 343 } 344 } 345 346 /** Print a message reporting an internal error. 347 */ 348 void bugMessage(Throwable ex) { 349 log.printLines(PrefixKind.JAVAC, "msg.bug", JavaCompiler.version()); 350 ex.printStackTrace(log.getWriter(WriterKind.NOTICE)); 351 } 352 353 /** Print a message reporting a fatal error. 354 */ 355 void feMessage(Throwable ex, Options options) { 356 log.printRawLines(ex.getMessage()); 357 if (ex.getCause() != null && options.isSet("dev")) { 358 ex.getCause().printStackTrace(log.getWriter(WriterKind.NOTICE)); 359 } 360 } 361 362 /** Print a message reporting an input/output error. 363 */ 364 void ioMessage(Throwable ex) { 365 log.printLines(PrefixKind.JAVAC, "msg.io"); 366 ex.printStackTrace(log.getWriter(WriterKind.NOTICE)); 367 } 368 369 /** Print a message reporting an out-of-resources error. 370 */ 371 void resourceMessage(Throwable ex) { 372 log.printLines(PrefixKind.JAVAC, "msg.resource"); 373 ex.printStackTrace(log.getWriter(WriterKind.NOTICE)); 374 } 375 376 /** Print a message reporting an uncaught exception from an 377 * annotation processor. 378 */ 379 void apMessage(AnnotationProcessingError ex) { 380 log.printLines(PrefixKind.JAVAC, "msg.proc.annotation.uncaught.exception"); 381 ex.getCause().printStackTrace(log.getWriter(WriterKind.NOTICE)); 382 } 383 384 /** Print a message reporting an uncaught exception from an 385 * annotation processor. 386 */ 387 void pluginMessage(Throwable ex) { 388 log.printLines(PrefixKind.JAVAC, "msg.plugin.uncaught.exception"); 389 ex.printStackTrace(log.getWriter(WriterKind.NOTICE)); 390 } 391 392 /** Display the location and checksum of a class. */ 393 void showClass(String className) { 394 PrintWriter pw = log.getWriter(WriterKind.NOTICE); 395 pw.println("javac: show class: " + className); 396 397 URL url = getClass().getResource('/' + className.replace('.', '/') + ".class"); 398 if (url != null) { 399 pw.println(" " + url); 400 } 401 402 try (InputStream in = getClass().getResourceAsStream('/' + className.replace('.', '/') + ".class")) { 403 final String algorithm = "MD5"; 404 byte[] digest; 405 MessageDigest md = MessageDigest.getInstance(algorithm); 406 try (DigestInputStream din = new DigestInputStream(in, md)) { 407 byte[] buf = new byte[8192]; 408 int n; 409 do { n = din.read(buf); } while (n > 0); 410 digest = md.digest(); 411 } 412 StringBuilder sb = new StringBuilder(); 413 for (byte b: digest) 414 sb.append(String.format("%02x", b)); 415 pw.println(" " + algorithm + " checksum: " + sb); 416 } catch (NoSuchAlgorithmException | IOException e) { 417 pw.println(" cannot compute digest: " + e); 418 } 419 } 420 421 // TODO: update this to JavacFileManager 422 private JavaFileManager fileManager; 423 424 /* ************************************************************************ 425 * Internationalization 426 *************************************************************************/ 427 428 public static final String javacBundleName = 429 "com.sun.tools.javac.resources.javac"; 430} 431