001    /*
002     * Copyright 2010-2016 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.kotlin.cli.common;
018    
019    import com.google.common.base.Predicates;
020    import com.google.common.collect.Lists;
021    import com.intellij.openapi.Disposable;
022    import com.intellij.openapi.util.Disposer;
023    import com.sampullara.cli.Args;
024    import org.fusesource.jansi.AnsiConsole;
025    import org.jetbrains.annotations.NotNull;
026    import org.jetbrains.annotations.Nullable;
027    import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments;
028    import org.jetbrains.kotlin.cli.common.messages.*;
029    import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler;
030    import org.jetbrains.kotlin.cli.jvm.compiler.CompileEnvironmentException;
031    import org.jetbrains.kotlin.config.CompilerConfiguration;
032    import org.jetbrains.kotlin.config.Services;
033    import org.jetbrains.kotlin.progress.CompilationCanceledException;
034    import org.jetbrains.kotlin.progress.CompilationCanceledStatus;
035    import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus;
036    
037    import java.io.PrintStream;
038    import java.util.List;
039    
040    import static org.jetbrains.kotlin.cli.common.ExitCode.*;
041    
042    public abstract class CLICompiler<A extends CommonCompilerArguments> {
043        @NotNull
044        private List<CompilerPlugin> compilerPlugins = Lists.newArrayList();
045    
046        @NotNull
047        public List<CompilerPlugin> getCompilerPlugins() {
048            return compilerPlugins;
049        }
050    
051        public void setCompilerPlugins(@NotNull List<CompilerPlugin> compilerPlugins) {
052            this.compilerPlugins = compilerPlugins;
053        }
054    
055        @NotNull
056        public ExitCode exec(@NotNull PrintStream errStream, @NotNull String... args) {
057            return exec(errStream, Services.EMPTY, MessageRenderer.PLAIN_RELATIVE_PATHS, args);
058        }
059    
060        @SuppressWarnings("UnusedDeclaration") // Used via reflection in CompilerRunnerUtil#invokeExecMethod
061        @NotNull
062        public ExitCode execAndOutputXml(@NotNull PrintStream errStream, @NotNull Services services, @NotNull String... args) {
063            return exec(errStream, services, MessageRenderer.XML, args);
064        }
065    
066        @SuppressWarnings("UnusedDeclaration") // Used via reflection in KotlinCompilerBaseTask
067        @NotNull
068        public ExitCode execFullPathsInMessages(@NotNull PrintStream errStream, @NotNull String[] args) {
069            return exec(errStream, Services.EMPTY, MessageRenderer.PLAIN_FULL_PATHS, args);
070        }
071    
072        @Nullable
073        private A parseArguments(@NotNull PrintStream errStream, @NotNull MessageRenderer messageRenderer, @NotNull String[] args) {
074            try {
075                A arguments = createArguments();
076                arguments.freeArgs = Args.parse(arguments, args);
077                return arguments;
078            }
079            catch (IllegalArgumentException e) {
080                errStream.println(e.getMessage());
081                usage(errStream, false);
082            }
083            catch (Throwable t) {
084                errStream.println(messageRenderer.render(
085                        CompilerMessageSeverity.EXCEPTION,
086                        OutputMessageUtil.renderException(t),
087                        CompilerMessageLocation.NO_LOCATION)
088                );
089            }
090            return null;
091        }
092    
093        /**
094         * Allow derived classes to add additional command line arguments
095         */
096        protected void usage(@NotNull PrintStream target, boolean extraHelp) {
097            Usage.print(target, createArguments(), extraHelp);
098        }
099    
100        /**
101         * Strategy method to configure the environment, allowing compiler
102         * based tools to customise their own plugins
103         */
104        protected void configureEnvironment(@NotNull CompilerConfiguration configuration, @NotNull A arguments) {
105            configuration.addAll(CLIConfigurationKeys.COMPILER_PLUGINS, compilerPlugins);
106        }
107    
108        @NotNull
109        protected abstract A createArguments();
110    
111        @NotNull
112        private ExitCode exec(
113                @NotNull PrintStream errStream,
114                @NotNull Services services,
115                @NotNull MessageRenderer messageRenderer,
116                @NotNull String[] args
117        ) {
118            K2JVMCompiler.Companion.resetInitStartTime();
119    
120            A arguments = parseArguments(errStream, messageRenderer, args);
121            if (arguments == null) {
122                return INTERNAL_ERROR;
123            }
124    
125            if (arguments.help || arguments.extraHelp) {
126                usage(errStream, arguments.extraHelp);
127                return OK;
128            }
129    
130            MessageCollector collector = new PrintingMessageCollector(errStream, messageRenderer, arguments.verbose);
131    
132            try {
133                if (PlainTextMessageRenderer.COLOR_ENABLED) {
134                    AnsiConsole.systemInstall();
135                }
136    
137                errStream.print(messageRenderer.renderPreamble());
138                return exec(collector, services, arguments);
139            }
140            finally {
141                errStream.print(messageRenderer.renderConclusion());
142    
143                if (PlainTextMessageRenderer.COLOR_ENABLED) {
144                    AnsiConsole.systemUninstall();
145                }
146            }
147        }
148    
149        @SuppressWarnings("WeakerAccess") // Used in maven (see KotlinCompileMojoBase.java)
150        @NotNull
151        public ExitCode exec(@NotNull MessageCollector messageCollector, @NotNull Services services, @NotNull A arguments) {
152            printVersionIfNeeded(messageCollector, arguments);
153    
154            if (arguments.suppressWarnings) {
155                messageCollector = new FilteringMessageCollector(messageCollector, Predicates.equalTo(CompilerMessageSeverity.WARNING));
156            }
157    
158            GroupingMessageCollector groupingCollector = new GroupingMessageCollector(messageCollector);
159            try {
160                ExitCode exitCode = OK;
161    
162                int repeatCount = 1;
163                if (arguments.repeat != null) {
164                    try {
165                        repeatCount = Integer.parseInt(arguments.repeat);
166                    }
167                    catch (NumberFormatException ignored) {
168                    }
169                }
170    
171                CompilationCanceledStatus canceledStatus = services.get(CompilationCanceledStatus.class);
172                ProgressIndicatorAndCompilationCanceledStatus.setCompilationCanceledStatus(canceledStatus);
173    
174                for (int i = 0; i < repeatCount; i++) {
175                    if (i > 0) {
176                        K2JVMCompiler.Companion.resetInitStartTime();
177                    }
178                    Disposable rootDisposable = Disposer.newDisposable();
179                    try {
180                        MessageSeverityCollector severityCollector = new MessageSeverityCollector(groupingCollector);
181                        ExitCode code = doExecute(arguments, services, severityCollector, rootDisposable);
182                        exitCode = severityCollector.anyReported(CompilerMessageSeverity.ERROR) ? COMPILATION_ERROR : code;
183                    }
184                    catch(CompilationCanceledException e) {
185                        messageCollector.report(CompilerMessageSeverity.INFO, "Compilation was canceled", CompilerMessageLocation.NO_LOCATION);
186                        return ExitCode.OK;
187                    }
188                    catch(RuntimeException e) {
189                        Throwable cause = e.getCause();
190                        if (cause instanceof CompilationCanceledException) {
191                            messageCollector
192                                    .report(CompilerMessageSeverity.INFO, "Compilation was canceled", CompilerMessageLocation.NO_LOCATION);
193                            return ExitCode.OK;
194                        }
195                        else {
196                            throw e;
197                        }
198                    }
199                    finally {
200                        Disposer.dispose(rootDisposable);
201                    }
202                }
203                return exitCode;
204            }
205            catch (Throwable t) {
206                groupingCollector.report(CompilerMessageSeverity.EXCEPTION, OutputMessageUtil.renderException(t),
207                                         CompilerMessageLocation.NO_LOCATION);
208                return INTERNAL_ERROR;
209            }
210            finally {
211                groupingCollector.flush();
212            }
213        }
214    
215        @NotNull
216        protected abstract ExitCode doExecute(
217                @NotNull A arguments,
218                @NotNull Services services,
219                @NotNull MessageCollector messageCollector,
220                @NotNull Disposable rootDisposable
221        );
222    
223        private void printVersionIfNeeded(@NotNull MessageCollector messageCollector, @NotNull A arguments) {
224            if (!arguments.version) return;
225    
226            messageCollector.report(CompilerMessageSeverity.INFO,
227                                    "Kotlin Compiler version " + KotlinVersion.VERSION,
228                                    CompilerMessageLocation.NO_LOCATION);
229        }
230    
231        /**
232         * Useful main for derived command line tools
233         */
234        public static void doMain(@NotNull CLICompiler compiler, @NotNull String[] args) {
235            // We depend on swing (indirectly through PSI or something), so we want to declare headless mode,
236            // to avoid accidentally starting the UI thread
237            System.setProperty("java.awt.headless", "true");
238            ExitCode exitCode = doMainNoExit(compiler, args);
239            if (exitCode != OK) {
240                System.exit(exitCode.getCode());
241            }
242        }
243    
244        @SuppressWarnings("UseOfSystemOutOrSystemErr")
245        @NotNull
246        public static ExitCode doMainNoExit(@NotNull CLICompiler compiler, @NotNull String[] args) {
247            try {
248                return compiler.exec(System.err, args);
249            }
250            catch (CompileEnvironmentException e) {
251                System.err.println(e.getMessage());
252                return INTERNAL_ERROR;
253            }
254        }
255    }