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