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