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.intellij.util.concurrency.AppExecutorUtil;
024    import com.intellij.util.concurrency.AppScheduledExecutorService;
025    import kotlin.collections.ArraysKt;
026    import kotlin.jvm.functions.Function1;
027    import org.fusesource.jansi.AnsiConsole;
028    import org.jetbrains.annotations.NotNull;
029    import org.jetbrains.annotations.Nullable;
030    import org.jetbrains.kotlin.cli.common.arguments.ArgumentUtilsKt;
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.*;
037    import org.jetbrains.kotlin.progress.CompilationCanceledException;
038    import org.jetbrains.kotlin.progress.CompilationCanceledStatus;
039    import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus;
040    import org.jetbrains.kotlin.utils.StringsKt;
041    
042    import java.io.PrintStream;
043    import java.util.List;
044    import java.util.Properties;
045    
046    import static org.jetbrains.kotlin.cli.common.ExitCode.*;
047    
048    public abstract class CLICompiler<A extends CommonCompilerArguments> {
049        static private void setIdeaIoUseFallback() {
050            if (SystemInfo.isWindows) {
051                Properties properties = System.getProperties();
052    
053                properties.setProperty("idea.io.use.nio2", Boolean.TRUE.toString());
054    
055                if (!(SystemInfo.isJavaVersionAtLeast("1.7") && !"1.7.0-ea".equals(SystemInfo.JAVA_VERSION))) {
056                    properties.setProperty("idea.io.use.fallback", Boolean.TRUE.toString());
057                }
058            }
059        }
060    
061        @NotNull
062        public ExitCode exec(@NotNull PrintStream errStream, @NotNull String... args) {
063            return exec(errStream, Services.EMPTY, MessageRenderer.PLAIN_RELATIVE_PATHS, args);
064        }
065    
066        // Used via reflection in CompilerRunnerUtil#invokeExecMethod and in Eclipse plugin (see KotlinCLICompiler)
067        @SuppressWarnings("UnusedDeclaration")
068        @NotNull
069        public ExitCode execAndOutputXml(@NotNull PrintStream errStream, @NotNull Services services, @NotNull String... args) {
070            return exec(errStream, services, MessageRenderer.XML, args);
071        }
072    
073        // Used via reflection in KotlinCompilerBaseTask
074        @SuppressWarnings("UnusedDeclaration")
075        @NotNull
076        public ExitCode execFullPathsInMessages(@NotNull PrintStream errStream, @NotNull String[] args) {
077            return exec(errStream, Services.EMPTY, MessageRenderer.PLAIN_FULL_PATHS, args);
078        }
079    
080        @Nullable
081        private A parseArguments(@NotNull PrintStream errStream, @NotNull MessageRenderer messageRenderer, @NotNull String[] args) {
082            try {
083                A arguments = createArguments();
084                parseArguments(args, arguments);
085                return arguments;
086            }
087            catch (IllegalArgumentException e) {
088                errStream.println(e.getMessage());
089                Usage.print(errStream, createArguments(), false);
090            }
091            catch (Throwable t) {
092                errStream.println(messageRenderer.render(
093                        CompilerMessageSeverity.EXCEPTION,
094                        OutputMessageUtil.renderException(t),
095                        CompilerMessageLocation.NO_LOCATION)
096                );
097            }
098            return null;
099        }
100    
101        @SuppressWarnings("WeakerAccess") // Used in maven (see KotlinCompileMojoBase.java)
102        public void parseArguments(@NotNull String[] args, @NotNull A arguments) {
103            ArgumentUtilsKt.parseArguments(args, arguments);
104        }
105    
106        @NotNull
107        protected abstract A createArguments();
108    
109        @NotNull
110        private ExitCode exec(
111                @NotNull PrintStream errStream,
112                @NotNull Services services,
113                @NotNull MessageRenderer messageRenderer,
114                @NotNull String[] args
115        ) {
116            K2JVMCompiler.Companion.resetInitStartTime();
117    
118            A arguments = parseArguments(errStream, messageRenderer, args);
119            if (arguments == null) {
120                return INTERNAL_ERROR;
121            }
122    
123            if (arguments.help || arguments.extraHelp) {
124                Usage.print(errStream, createArguments(), arguments.extraHelp);
125                return OK;
126            }
127    
128            MessageCollector collector = new PrintingMessageCollector(errStream, messageRenderer, arguments.verbose);
129    
130            try {
131                if (PlainTextMessageRenderer.COLOR_ENABLED) {
132                    AnsiConsole.systemInstall();
133                }
134    
135                errStream.print(messageRenderer.renderPreamble());
136                return exec(collector, services, arguments);
137            }
138            finally {
139                errStream.print(messageRenderer.renderConclusion());
140    
141                if (PlainTextMessageRenderer.COLOR_ENABLED) {
142                    AnsiConsole.systemUninstall();
143                }
144            }
145        }
146    
147        @SuppressWarnings("WeakerAccess") // Used in maven (see KotlinCompileMojoBase.java)
148        @NotNull
149        public ExitCode exec(@NotNull MessageCollector messageCollector, @NotNull Services services, @NotNull A arguments) {
150            printVersionIfNeeded(messageCollector, arguments);
151    
152            if (arguments.suppressWarnings) {
153                messageCollector = new FilteringMessageCollector(messageCollector, Predicates.equalTo(CompilerMessageSeverity.WARNING));
154            }
155    
156            reportUnknownExtraFlags(messageCollector, arguments);
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                        setIdeaIoUseFallback();
181                        ExitCode code = doExecute(arguments, services, groupingCollector, rootDisposable);
182                        exitCode = groupingCollector.hasErrors() ? 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        protected static void setupCommonArgumentsAndServices(
216                @NotNull CompilerConfiguration configuration, @NotNull CommonCompilerArguments arguments, @NotNull Services services
217        ) {
218            CompilerJarLocator locator = services.get(CompilerJarLocator.class);
219            if (locator != null) {
220                configuration.put(CLIConfigurationKeys.COMPILER_JAR_LOCATOR, locator);
221            }
222    
223            LanguageVersion languageVersion = parseVersion(configuration, arguments.languageVersion, "language");
224            LanguageVersion apiVersion = parseVersion(configuration, arguments.apiVersion, "API");
225            if (languageVersion != null || apiVersion != null) {
226                if (languageVersion == null) {
227                    // If only "-api-version" is specified, language version is assumed to be the latest
228                    languageVersion = LanguageVersion.LATEST;
229                }
230                if (apiVersion == null) {
231                    // If only "-language-version" is specified, API version is assumed to be equal to the language version
232                    // (API version cannot be greater than the language version)
233                    apiVersion = languageVersion;
234                }
235    
236                if (apiVersion.compareTo(languageVersion) > 0) {
237                    configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY).report(
238                            CompilerMessageSeverity.ERROR,
239                            "-api-version (" + apiVersion.getVersionString() + ") cannot be greater than " +
240                            "-language-version (" + languageVersion.getVersionString() + ")",
241                            CompilerMessageLocation.NO_LOCATION
242                    );
243                }
244    
245                configuration.put(
246                        CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS,
247                        new LanguageVersionSettingsImpl(
248                                languageVersion,
249                                ApiVersion.createByLanguageVersion(apiVersion),
250                                arguments.apiVersion != null
251                        )
252                );
253            }
254        }
255    
256        private static LanguageVersion parseVersion(
257                @NotNull CompilerConfiguration configuration, @Nullable String value, @NotNull String versionOf
258        ) {
259            if (value == null) return null;
260    
261            LanguageVersion version = LanguageVersion.fromVersionString(value);
262            if (version != null) {
263                return version;
264            }
265    
266            List<String> versionStrings = ArraysKt.map(LanguageVersion.values(), new Function1<LanguageVersion, String>() {
267                @Override
268                public String invoke(LanguageVersion version) {
269                    return version.getVersionString();
270                }
271            });
272            String message = "Unknown " + versionOf + " version: " + value + "\n" +
273                             "Supported " + versionOf + " versions: " + StringsKt.join(versionStrings, ", ");
274            configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY).report(
275                    CompilerMessageSeverity.ERROR, message, CompilerMessageLocation.NO_LOCATION
276            );
277    
278            return null;
279        }
280    
281        private void reportUnknownExtraFlags(@NotNull MessageCollector collector, @NotNull A arguments) {
282            for (String flag : arguments.unknownExtraFlags) {
283                collector.report(
284                        CompilerMessageSeverity.STRONG_WARNING,
285                        "Flag is not supported by this version of the compiler: " + flag,
286                        CompilerMessageLocation.NO_LOCATION
287                );
288            }
289        }
290    
291        @NotNull
292        protected abstract ExitCode doExecute(
293                @NotNull A arguments,
294                @NotNull Services services,
295                @NotNull MessageCollector messageCollector,
296                @NotNull Disposable rootDisposable
297        );
298    
299        private void printVersionIfNeeded(@NotNull MessageCollector messageCollector, @NotNull A arguments) {
300            if (!arguments.version) return;
301    
302            messageCollector.report(CompilerMessageSeverity.INFO,
303                                    "Kotlin Compiler version " + KotlinCompilerVersion.VERSION,
304                                    CompilerMessageLocation.NO_LOCATION);
305        }
306    
307        /**
308         * Useful main for derived command line tools
309         */
310        public static void doMain(@NotNull CLICompiler compiler, @NotNull String[] args) {
311            // We depend on swing (indirectly through PSI or something), so we want to declare headless mode,
312            // to avoid accidentally starting the UI thread
313            System.setProperty("java.awt.headless", "true");
314            try {
315                ExitCode exitCode = doMainNoExit(compiler, args);
316    
317                if (exitCode != OK) {
318                    System.exit(exitCode.getCode());
319                }
320            }
321            finally {
322                AppScheduledExecutorService service = (AppScheduledExecutorService) AppExecutorUtil.getAppScheduledExecutorService();
323                service.shutdownAppScheduledExecutorService();
324            }
325        }
326    
327        @SuppressWarnings("UseOfSystemOutOrSystemErr")
328        @NotNull
329        public static ExitCode doMainNoExit(@NotNull CLICompiler compiler, @NotNull String[] args) {
330            try {
331                return compiler.exec(System.err, args);
332            }
333            catch (CompileEnvironmentException e) {
334                System.err.println(e.getMessage());
335                return INTERNAL_ERROR;
336            }
337        }
338    }