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.ArrayList;
044    import java.util.Collections;
045    import java.util.List;
046    import java.util.Properties;
047    
048    import static org.jetbrains.kotlin.cli.common.ExitCode.*;
049    
050    public abstract class CLICompiler<A extends CommonCompilerArguments> {
051        static private void setIdeaIoUseFallback() {
052            if (SystemInfo.isWindows) {
053                Properties properties = System.getProperties();
054    
055                properties.setProperty("idea.io.use.nio2", Boolean.TRUE.toString());
056    
057                if (!(SystemInfo.isJavaVersionAtLeast("1.7") && !"1.7.0-ea".equals(SystemInfo.JAVA_VERSION))) {
058                    properties.setProperty("idea.io.use.fallback", Boolean.TRUE.toString());
059                }
060            }
061        }
062    
063        @NotNull
064        public ExitCode exec(@NotNull PrintStream errStream, @NotNull String... args) {
065            return exec(errStream, Services.EMPTY, MessageRenderer.PLAIN_RELATIVE_PATHS, args);
066        }
067    
068        // Used via reflection in CompilerRunnerUtil#invokeExecMethod and in Eclipse plugin (see KotlinCLICompiler)
069        @SuppressWarnings("UnusedDeclaration")
070        @NotNull
071        public ExitCode execAndOutputXml(@NotNull PrintStream errStream, @NotNull Services services, @NotNull String... args) {
072            return exec(errStream, services, MessageRenderer.XML, args);
073        }
074    
075        // Used via reflection in KotlinCompilerBaseTask
076        @SuppressWarnings("UnusedDeclaration")
077        @NotNull
078        public ExitCode execFullPathsInMessages(@NotNull PrintStream errStream, @NotNull String[] args) {
079            return exec(errStream, Services.EMPTY, MessageRenderer.PLAIN_FULL_PATHS, args);
080        }
081    
082        @Nullable
083        private A parseArguments(@NotNull PrintStream errStream, @NotNull MessageRenderer messageRenderer, @NotNull String[] args) {
084            try {
085                A arguments = createArguments();
086                parseArguments(args, arguments);
087                return arguments;
088            }
089            catch (IllegalArgumentException e) {
090                errStream.println(e.getMessage());
091                Usage.print(errStream, createArguments(), false);
092            }
093            catch (Throwable t) {
094                errStream.println(messageRenderer.render(
095                        CompilerMessageSeverity.EXCEPTION,
096                        OutputMessageUtil.renderException(t),
097                        CompilerMessageLocation.NO_LOCATION)
098                );
099            }
100            return null;
101        }
102    
103        @SuppressWarnings("WeakerAccess") // Used in maven (see KotlinCompileMojoBase.java)
104        public void parseArguments(@NotNull String[] args, @NotNull A arguments) {
105            ArgumentUtilsKt.parseArguments(args, arguments);
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.print(errStream, createArguments(), 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            reportUnknownExtraFlags(messageCollector, arguments);
159            reportUnsupportedJavaVersion(messageCollector, arguments);
160    
161            GroupingMessageCollector groupingCollector = new GroupingMessageCollector(messageCollector);
162    
163            CompilerConfiguration configuration = new CompilerConfiguration();
164            configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, groupingCollector);
165    
166            setupCommonArgumentsAndServices(configuration, arguments, services);
167            setupPlatformSpecificArgumentsAndServices(configuration, arguments, services);
168    
169            try {
170                ExitCode exitCode = OK;
171    
172                int repeatCount = 1;
173                if (arguments.repeat != null) {
174                    try {
175                        repeatCount = Integer.parseInt(arguments.repeat);
176                    }
177                    catch (NumberFormatException ignored) {
178                    }
179                }
180    
181                CompilationCanceledStatus canceledStatus = services.get(CompilationCanceledStatus.class);
182                ProgressIndicatorAndCompilationCanceledStatus.setCompilationCanceledStatus(canceledStatus);
183    
184                for (int i = 0; i < repeatCount; i++) {
185                    if (i > 0) {
186                        K2JVMCompiler.Companion.resetInitStartTime();
187                    }
188                    Disposable rootDisposable = Disposer.newDisposable();
189                    try {
190                        setIdeaIoUseFallback();
191                        ExitCode code = doExecute(arguments, configuration, rootDisposable);
192                        exitCode = groupingCollector.hasErrors() ? COMPILATION_ERROR : code;
193                    }
194                    catch (CompilationCanceledException e) {
195                        messageCollector.report(CompilerMessageSeverity.INFO, "Compilation was canceled", CompilerMessageLocation.NO_LOCATION);
196                        return ExitCode.OK;
197                    }
198                    catch (RuntimeException e) {
199                        Throwable cause = e.getCause();
200                        if (cause instanceof CompilationCanceledException) {
201                            messageCollector
202                                    .report(CompilerMessageSeverity.INFO, "Compilation was canceled", CompilerMessageLocation.NO_LOCATION);
203                            return ExitCode.OK;
204                        }
205                        else {
206                            throw e;
207                        }
208                    }
209                    finally {
210                        Disposer.dispose(rootDisposable);
211                    }
212                }
213                return exitCode;
214            }
215            catch (Throwable t) {
216                groupingCollector.report(CompilerMessageSeverity.EXCEPTION, OutputMessageUtil.renderException(t),
217                                         CompilerMessageLocation.NO_LOCATION);
218                return INTERNAL_ERROR;
219            }
220            finally {
221                groupingCollector.flush();
222            }
223        }
224    
225        private static void setupCommonArgumentsAndServices(
226                @NotNull CompilerConfiguration configuration, @NotNull CommonCompilerArguments arguments, @NotNull Services services
227        ) {
228            if (arguments.noInline) {
229                configuration.put(CommonConfigurationKeys.DISABLE_INLINE, true);
230            }
231    
232            CompilerJarLocator locator = services.get(CompilerJarLocator.class);
233            if (locator != null) {
234                configuration.put(CLIConfigurationKeys.COMPILER_JAR_LOCATOR, locator);
235            }
236    
237            setupLanguageVersionSettings(configuration, arguments);
238        }
239    
240        private static void setupLanguageVersionSettings(
241                @NotNull CompilerConfiguration configuration, @NotNull CommonCompilerArguments arguments
242        ) {
243            LanguageVersion languageVersion = parseVersion(configuration, arguments.languageVersion, "language");
244            LanguageVersion apiVersion = parseVersion(configuration, arguments.apiVersion, "API");
245    
246            if (languageVersion == null) {
247                // If only "-api-version" is specified, language version is assumed to be the latest
248                languageVersion = LanguageVersion.LATEST;
249            }
250            if (apiVersion == null) {
251                // If only "-language-version" is specified, API version is assumed to be equal to the language version
252                // (API version cannot be greater than the language version)
253                apiVersion = languageVersion;
254            }
255    
256            if (apiVersion.compareTo(languageVersion) > 0) {
257                configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY).report(
258                        CompilerMessageSeverity.ERROR,
259                        "-api-version (" + apiVersion.getVersionString() + ") cannot be greater than " +
260                        "-language-version (" + languageVersion.getVersionString() + ")",
261                        CompilerMessageLocation.NO_LOCATION
262                );
263            }
264    
265            List<LanguageFeature> extraLanguageFeatures = new ArrayList<LanguageFeature>(0);
266            if (arguments.multiPlatform) {
267                extraLanguageFeatures.add(LanguageFeature.MultiPlatformProjects);
268            }
269            if (arguments.noCheckImpl) {
270                extraLanguageFeatures.add(LanguageFeature.MultiPlatformDoNotCheckImpl);
271            }
272    
273            LanguageFeature coroutinesApplicabilityLevel = chooseCoroutinesApplicabilityLevel(configuration, arguments);
274            if (coroutinesApplicabilityLevel != null) {
275                extraLanguageFeatures.add(coroutinesApplicabilityLevel);
276            }
277    
278            configuration.put(
279                    CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS,
280                    new LanguageVersionSettingsImpl(
281                            languageVersion,
282                            ApiVersion.createByLanguageVersion(apiVersion),
283                            extraLanguageFeatures,
284                            arguments.apiVersion != null
285                    )
286            );
287        }
288    
289        @Nullable
290        private static LanguageFeature chooseCoroutinesApplicabilityLevel(
291                @NotNull CompilerConfiguration configuration, @NotNull CommonCompilerArguments arguments) {
292            if (!arguments.coroutinesEnable && !arguments.coroutinesError && !arguments.coroutinesWarn) {
293                return LanguageFeature.WarnOnCoroutines;
294            }
295            else if (arguments.coroutinesError && !arguments.coroutinesWarn && !arguments.coroutinesEnable) {
296                return LanguageFeature.ErrorOnCoroutines;
297            }
298            else if (arguments.coroutinesWarn && !arguments.coroutinesError && !arguments.coroutinesEnable) {
299                return LanguageFeature.WarnOnCoroutines;
300            }
301            else if (arguments.coroutinesEnable && !arguments.coroutinesWarn && !arguments.coroutinesError) {
302                return null;
303            } else {
304                String message = "The -Xcoroutines can only have one value";
305                configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY).report(
306                        CompilerMessageSeverity.ERROR, message, CompilerMessageLocation.NO_LOCATION
307                );
308    
309                return null;
310            }
311        }
312    
313        @Nullable
314        private static LanguageVersion parseVersion(
315                @NotNull CompilerConfiguration configuration, @Nullable String value, @NotNull String versionOf
316        ) {
317            if (value == null) return null;
318    
319            LanguageVersion version = LanguageVersion.fromVersionString(value);
320            if (version != null) {
321                return version;
322            }
323    
324            List<String> versionStrings = ArraysKt.map(LanguageVersion.values(), new Function1<LanguageVersion, String>() {
325                @Override
326                public String invoke(LanguageVersion version) {
327                    return version.getVersionString();
328                }
329            });
330            String message = "Unknown " + versionOf + " version: " + value + "\n" +
331                             "Supported " + versionOf + " versions: " + StringsKt.join(versionStrings, ", ");
332            configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY).report(
333                    CompilerMessageSeverity.ERROR, message, CompilerMessageLocation.NO_LOCATION
334            );
335    
336            return null;
337        }
338    
339        protected abstract void setupPlatformSpecificArgumentsAndServices(
340                @NotNull CompilerConfiguration configuration, @NotNull A arguments, @NotNull Services services
341        );
342    
343        private void reportUnknownExtraFlags(@NotNull MessageCollector collector, @NotNull A arguments) {
344            for (String flag : arguments.unknownExtraFlags) {
345                collector.report(
346                        CompilerMessageSeverity.STRONG_WARNING,
347                        "Flag is not supported by this version of the compiler: " + flag,
348                        CompilerMessageLocation.NO_LOCATION
349                );
350            }
351        }
352    
353        private void reportUnsupportedJavaVersion(MessageCollector collector, A arguments) {
354            if (!SystemInfo.isJavaVersionAtLeast("1.8") && !arguments.noJavaVersionWarning) {
355                collector.report(
356                        CompilerMessageSeverity.STRONG_WARNING,
357                        "Running the Kotlin compiler under Java 6 or 7 is unsupported and will no longer be possible in a future update.",
358                        CompilerMessageLocation.NO_LOCATION
359                );
360            }
361        }
362    
363        @NotNull
364        protected abstract ExitCode doExecute(
365                @NotNull A arguments,
366                @NotNull CompilerConfiguration configuration,
367                @NotNull Disposable rootDisposable
368        );
369    
370        private void printVersionIfNeeded(@NotNull MessageCollector messageCollector, @NotNull A arguments) {
371            if (!arguments.version) return;
372    
373            messageCollector.report(CompilerMessageSeverity.INFO,
374                                    "Kotlin Compiler version " + KotlinCompilerVersion.VERSION,
375                                    CompilerMessageLocation.NO_LOCATION);
376        }
377    
378        /**
379         * Useful main for derived command line tools
380         */
381        public static void doMain(@NotNull CLICompiler compiler, @NotNull String[] args) {
382            // We depend on swing (indirectly through PSI or something), so we want to declare headless mode,
383            // to avoid accidentally starting the UI thread
384            System.setProperty("java.awt.headless", "true");
385            try {
386                ExitCode exitCode = doMainNoExit(compiler, args);
387    
388                if (exitCode != OK) {
389                    System.exit(exitCode.getCode());
390                }
391            }
392            finally {
393                AppScheduledExecutorService service = (AppScheduledExecutorService) AppExecutorUtil.getAppScheduledExecutorService();
394                service.shutdownAppScheduledExecutorService();
395            }
396        }
397    
398        @SuppressWarnings("UseOfSystemOutOrSystemErr")
399        @NotNull
400        public static ExitCode doMainNoExit(@NotNull CLICompiler compiler, @NotNull String[] args) {
401            try {
402                return compiler.exec(System.err, args);
403            }
404            catch (CompileEnvironmentException e) {
405                System.err.println(e.getMessage());
406                return INTERNAL_ERROR;
407            }
408        }
409    }