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