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