001    /*
002     * Copyright 2010-2013 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.jet.cli.common;
018    
019    import com.google.common.base.Predicates;
020    import com.intellij.openapi.Disposable;
021    import com.intellij.openapi.util.Disposer;
022    import com.sampullara.cli.Args;
023    import org.jetbrains.annotations.NotNull;
024    import org.jetbrains.jet.cli.common.messages.*;
025    import org.jetbrains.jet.cli.jvm.compiler.CompileEnvironmentException;
026    import org.jetbrains.jet.cli.jvm.compiler.CompileEnvironmentUtil;
027    import org.jetbrains.jet.config.CompilerConfiguration;
028    
029    import java.io.PrintStream;
030    
031    import static org.jetbrains.jet.cli.common.ExitCode.*;
032    
033    public abstract class CLICompiler<A extends CompilerArguments> {
034    
035        @NotNull
036        public ExitCode exec(@NotNull PrintStream errStream, @NotNull String... args) {
037            A arguments = createArguments();
038            if (!parseArguments(errStream, arguments, args)) {
039                return INTERNAL_ERROR;
040            }
041            return exec(errStream, arguments);
042        }
043    
044        /**
045         * Returns true if the arguments can be parsed correctly
046         */
047        protected boolean parseArguments(@NotNull PrintStream errStream, @NotNull A arguments, @NotNull String[] args) {
048            try {
049                arguments.freeArgs = Args.parse(arguments, args);
050                return true;
051            }
052            catch (IllegalArgumentException e) {
053                errStream.println(e.getMessage());
054                usage(errStream);
055            }
056            catch (Throwable t) {
057                // Always use tags
058                errStream.println(MessageRenderer.TAGS.renderException(t));
059            }
060            return false;
061        }
062    
063        /**
064         * Allow derived classes to add additional command line arguments
065         */
066        protected void usage(@NotNull PrintStream target) {
067            // We should say something like
068            //   Args.usage(target, K2JVMCompilerArguments.class);
069            // but currently cli-parser we are using does not support that
070            // a corresponding patch has been sent to the authors
071            // For now, we are using this:
072            PrintStream oldErr = System.err;
073            System.setErr(target);
074            try {
075                // TODO: use proper argv0
076                Args.usage(createArguments());
077            }
078            finally {
079                System.setErr(oldErr);
080            }
081        }
082    
083        /**
084         * Strategy method to configure the environment, allowing compiler
085         * based tools to customise their own plugins
086         */
087        //TODO: add parameter annotations when KT-1863 is resolved
088        protected void configureEnvironment(@NotNull CompilerConfiguration configuration, @NotNull A arguments) {
089            configuration.addAll(CLIConfigurationKeys.COMPILER_PLUGINS, arguments.getCompilerPlugins());
090        }
091    
092        @NotNull
093        protected abstract A createArguments();
094    
095        /**
096         * Executes the compiler on the parsed arguments
097         */
098        @NotNull
099        public ExitCode exec(@NotNull PrintStream errStream, @NotNull A arguments) {
100            if (arguments.isHelp()) {
101                usage(errStream);
102                return OK;
103            }
104    
105            MessageRenderer messageRenderer = getMessageRenderer(arguments);
106            errStream.print(messageRenderer.renderPreamble());
107    
108            printVersionIfNeeded(errStream, arguments, messageRenderer);
109    
110            MessageCollector collector = new PrintingMessageCollector(errStream, messageRenderer, arguments.isVerbose());
111    
112            if (arguments.suppressAllWarnings()) {
113                collector = new FilteringMessageCollector(collector, Predicates.equalTo(CompilerMessageSeverity.WARNING));
114            }
115    
116            try {
117                return exec(collector, arguments);
118            }
119            finally {
120                errStream.print(messageRenderer.renderConclusion());
121            }
122        }
123    
124        @NotNull
125        public ExitCode exec(@NotNull MessageCollector messageCollector, @NotNull A arguments) {
126            GroupingMessageCollector groupingCollector = new GroupingMessageCollector(messageCollector);
127            try {
128                Disposable rootDisposable = CompileEnvironmentUtil.createMockDisposable();
129                try {
130                    MessageSeverityCollector severityCollector = new MessageSeverityCollector(groupingCollector);
131                    ExitCode code = doExecute(arguments, severityCollector, rootDisposable);
132                    return severityCollector.anyReported(CompilerMessageSeverity.ERROR) ? COMPILATION_ERROR : code;
133                }
134                finally {
135                    Disposer.dispose(rootDisposable);
136                }
137            }
138            catch (Throwable t) {
139                groupingCollector.report(CompilerMessageSeverity.EXCEPTION, MessageRenderer.PLAIN.renderException(t),
140                                         CompilerMessageLocation.NO_LOCATION);
141                return INTERNAL_ERROR;
142            }
143            finally {
144                groupingCollector.flush();
145            }
146        }
147    
148        //TODO: can't declare parameters as not null due to KT-1863
149        @NotNull
150        protected abstract ExitCode doExecute(A arguments, MessageCollector messageCollector, Disposable rootDisposable);
151    
152        //TODO: can we make it private?
153        @NotNull
154        protected MessageRenderer getMessageRenderer(@NotNull A arguments) {
155            return arguments.isTags() ? MessageRenderer.TAGS : MessageRenderer.PLAIN;
156        }
157    
158        protected void printVersionIfNeeded(@NotNull PrintStream errStream,
159                @NotNull A arguments,
160                @NotNull MessageRenderer messageRenderer) {
161            if (arguments.isVersion()) {
162                String versionMessage = messageRenderer.render(CompilerMessageSeverity.INFO,
163                                                               "Kotlin Compiler version " + KotlinVersion.VERSION,
164                                                               CompilerMessageLocation.NO_LOCATION);
165                errStream.println(versionMessage);
166            }
167        }
168    
169        /**
170         * Useful main for derived command line tools
171         */
172        public static void doMain(@NotNull CLICompiler compiler, @NotNull String[] args) {
173            // We depend on swing (indirectly through PSI or something), so we want to declare headless mode,
174            // to avoid accidentally starting the UI thread
175            System.setProperty("java.awt.headless", "true");
176            ExitCode exitCode = doMainNoExit(compiler, args);
177            if (exitCode != OK) {
178                System.exit(exitCode.getCode());
179            }
180        }
181    
182        @NotNull
183        public static ExitCode doMainNoExit(@NotNull CLICompiler compiler, @NotNull String[] args) {
184            try {
185                ExitCode rc = compiler.exec(System.out, args);
186                if (rc != OK) {
187                    System.err.println("exec() finished with " + rc + " return code");
188                }
189                if (Boolean.parseBoolean(System.getProperty("kotlin.print.cmd.args"))) {
190                    System.out.println("Command line arguments:");
191                    for (String arg : args) {
192                        System.out.println(arg);
193                    }
194                }
195                return rc;
196            }
197            catch (CompileEnvironmentException e) {
198                System.err.println(e.getMessage());
199                return INTERNAL_ERROR;
200            }
201        }
202    }