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