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 com.sampullara.cli.Args;
026    import kotlin.Pair;
027    import kotlin.collections.ArraysKt;
028    import kotlin.collections.CollectionsKt;
029    import kotlin.jvm.functions.Function1;
030    import org.fusesource.jansi.AnsiConsole;
031    import org.jetbrains.annotations.NotNull;
032    import org.jetbrains.annotations.Nullable;
033    import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments;
034    import org.jetbrains.kotlin.cli.common.messages.*;
035    import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler;
036    import org.jetbrains.kotlin.cli.jvm.compiler.CompileEnvironmentException;
037    import org.jetbrains.kotlin.cli.jvm.compiler.CompilerJarLocator;
038    import org.jetbrains.kotlin.config.*;
039    import org.jetbrains.kotlin.progress.CompilationCanceledException;
040    import org.jetbrains.kotlin.progress.CompilationCanceledStatus;
041    import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus;
042    import org.jetbrains.kotlin.utils.StringsKt;
043    
044    import java.io.PrintStream;
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            Pair<List<String>, List<String>> unparsedArgs =
106                    CollectionsKt.partition(Args.parse(arguments, args, false), new Function1<String, Boolean>() {
107                        @Override
108                        public Boolean invoke(String s) {
109                            return s.startsWith("-X");
110                        }
111                    });
112    
113            arguments.unknownExtraFlags = unparsedArgs.getFirst();
114            arguments.freeArgs = unparsedArgs.getSecond();
115    
116            for (String argument : arguments.freeArgs) {
117                if (argument.startsWith("-")) {
118                    throw new IllegalArgumentException("Invalid argument: " + argument);
119                }
120            }
121        }
122    
123        @NotNull
124        protected abstract A createArguments();
125    
126        @NotNull
127        private ExitCode exec(
128                @NotNull PrintStream errStream,
129                @NotNull Services services,
130                @NotNull MessageRenderer messageRenderer,
131                @NotNull String[] args
132        ) {
133            K2JVMCompiler.Companion.resetInitStartTime();
134    
135            A arguments = parseArguments(errStream, messageRenderer, args);
136            if (arguments == null) {
137                return INTERNAL_ERROR;
138            }
139    
140            if (arguments.help || arguments.extraHelp) {
141                Usage.print(errStream, createArguments(), arguments.extraHelp);
142                return OK;
143            }
144    
145            MessageCollector collector = new PrintingMessageCollector(errStream, messageRenderer, arguments.verbose);
146    
147            try {
148                if (PlainTextMessageRenderer.COLOR_ENABLED) {
149                    AnsiConsole.systemInstall();
150                }
151    
152                errStream.print(messageRenderer.renderPreamble());
153                return exec(collector, services, arguments);
154            }
155            finally {
156                errStream.print(messageRenderer.renderConclusion());
157    
158                if (PlainTextMessageRenderer.COLOR_ENABLED) {
159                    AnsiConsole.systemUninstall();
160                }
161            }
162        }
163    
164        @SuppressWarnings("WeakerAccess") // Used in maven (see KotlinCompileMojoBase.java)
165        @NotNull
166        public ExitCode exec(@NotNull MessageCollector messageCollector, @NotNull Services services, @NotNull A arguments) {
167            printVersionIfNeeded(messageCollector, arguments);
168    
169            if (arguments.suppressWarnings) {
170                messageCollector = new FilteringMessageCollector(messageCollector, Predicates.equalTo(CompilerMessageSeverity.WARNING));
171            }
172    
173            reportUnknownExtraFlags(messageCollector, arguments);
174    
175            GroupingMessageCollector groupingCollector = new GroupingMessageCollector(messageCollector);
176            try {
177                ExitCode exitCode = OK;
178    
179                int repeatCount = 1;
180                if (arguments.repeat != null) {
181                    try {
182                        repeatCount = Integer.parseInt(arguments.repeat);
183                    }
184                    catch (NumberFormatException ignored) {
185                    }
186                }
187    
188                CompilationCanceledStatus canceledStatus = services.get(CompilationCanceledStatus.class);
189                ProgressIndicatorAndCompilationCanceledStatus.setCompilationCanceledStatus(canceledStatus);
190    
191                for (int i = 0; i < repeatCount; i++) {
192                    if (i > 0) {
193                        K2JVMCompiler.Companion.resetInitStartTime();
194                    }
195                    Disposable rootDisposable = Disposer.newDisposable();
196                    try {
197                        setIdeaIoUseFallback();
198                        ExitCode code = doExecute(arguments, services, groupingCollector, rootDisposable);
199                        exitCode = groupingCollector.hasErrors() ? COMPILATION_ERROR : code;
200                    }
201                    catch (CompilationCanceledException e) {
202                        messageCollector.report(CompilerMessageSeverity.INFO, "Compilation was canceled", CompilerMessageLocation.NO_LOCATION);
203                        return ExitCode.OK;
204                    }
205                    catch (RuntimeException e) {
206                        Throwable cause = e.getCause();
207                        if (cause instanceof CompilationCanceledException) {
208                            messageCollector
209                                    .report(CompilerMessageSeverity.INFO, "Compilation was canceled", CompilerMessageLocation.NO_LOCATION);
210                            return ExitCode.OK;
211                        }
212                        else {
213                            throw e;
214                        }
215                    }
216                    finally {
217                        Disposer.dispose(rootDisposable);
218                    }
219                }
220                return exitCode;
221            }
222            catch (Throwable t) {
223                groupingCollector.report(CompilerMessageSeverity.EXCEPTION, OutputMessageUtil.renderException(t),
224                                         CompilerMessageLocation.NO_LOCATION);
225                return INTERNAL_ERROR;
226            }
227            finally {
228                groupingCollector.flush();
229            }
230        }
231    
232        protected static void setupCommonArgumentsAndServices(
233                @NotNull CompilerConfiguration configuration, @NotNull CommonCompilerArguments arguments, @NotNull Services services
234        ) {
235            CompilerJarLocator locator = services.get(CompilerJarLocator.class);
236            if (locator != null) {
237                configuration.put(CLIConfigurationKeys.COMPILER_JAR_LOCATOR, locator);
238            }
239    
240            LanguageVersion languageVersion = parseVersion(configuration, arguments.languageVersion, "language");
241            LanguageVersion apiVersion = parseVersion(configuration, arguments.apiVersion, "API");
242            if (languageVersion != null || apiVersion != null) {
243                if (languageVersion == null) {
244                    // If only "-api-version" is specified, language version is assumed to be the latest
245                    languageVersion = LanguageVersion.LATEST;
246                }
247                if (apiVersion == null) {
248                    // If only "-language-version" is specified, API version is assumed to be equal to the language version
249                    // (API version cannot be greater than the language version)
250                    apiVersion = languageVersion;
251                }
252    
253                if (apiVersion.compareTo(languageVersion) > 0) {
254                    configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY).report(
255                            CompilerMessageSeverity.ERROR,
256                            "-api-version (" + apiVersion.getVersionString() + ") cannot be greater than " +
257                            "-language-version (" + languageVersion.getVersionString() + ")",
258                            CompilerMessageLocation.NO_LOCATION
259                    );
260                }
261    
262                configuration.put(CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS,
263                                  new LanguageVersionSettingsImpl(languageVersion, ApiVersion.createByLanguageVersion(apiVersion)));
264            }
265        }
266    
267        private static LanguageVersion parseVersion(
268                @NotNull CompilerConfiguration configuration, @Nullable String value, @NotNull String versionOf
269        ) {
270            if (value == null) return null;
271    
272            LanguageVersion version = LanguageVersion.fromVersionString(value);
273            if (version != null) {
274                return version;
275            }
276    
277            List<String> versionStrings = ArraysKt.map(LanguageVersion.values(), new Function1<LanguageVersion, String>() {
278                @Override
279                public String invoke(LanguageVersion version) {
280                    return version.getVersionString();
281                }
282            });
283            String message = "Unknown " + versionOf + " version: " + value + "\n" +
284                             "Supported " + versionOf + " versions: " + StringsKt.join(versionStrings, ", ");
285            configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY).report(
286                    CompilerMessageSeverity.ERROR, message, CompilerMessageLocation.NO_LOCATION
287            );
288    
289            return null;
290        }
291    
292        private void reportUnknownExtraFlags(@NotNull MessageCollector collector, @NotNull A arguments) {
293            for (String flag : arguments.unknownExtraFlags) {
294                collector.report(
295                        CompilerMessageSeverity.WARNING,
296                        "Flag is not supported by this version of the compiler: " + flag,
297                        CompilerMessageLocation.NO_LOCATION
298                );
299            }
300        }
301    
302        @NotNull
303        protected abstract ExitCode doExecute(
304                @NotNull A arguments,
305                @NotNull Services services,
306                @NotNull MessageCollector messageCollector,
307                @NotNull Disposable rootDisposable
308        );
309    
310        private void printVersionIfNeeded(@NotNull MessageCollector messageCollector, @NotNull A arguments) {
311            if (!arguments.version) return;
312    
313            messageCollector.report(CompilerMessageSeverity.INFO,
314                                    "Kotlin Compiler version " + KotlinVersion.VERSION,
315                                    CompilerMessageLocation.NO_LOCATION);
316        }
317    
318        /**
319         * Useful main for derived command line tools
320         */
321        public static void doMain(@NotNull CLICompiler compiler, @NotNull String[] args) {
322            // We depend on swing (indirectly through PSI or something), so we want to declare headless mode,
323            // to avoid accidentally starting the UI thread
324            System.setProperty("java.awt.headless", "true");
325            try {
326                ExitCode exitCode = doMainNoExit(compiler, args);
327    
328                if (exitCode != OK) {
329                    System.exit(exitCode.getCode());
330                }
331            }
332            finally {
333                AppScheduledExecutorService service = (AppScheduledExecutorService) AppExecutorUtil.getAppScheduledExecutorService();
334                service.shutdownAppScheduledExecutorService();
335            }
336        }
337    
338        @SuppressWarnings("UseOfSystemOutOrSystemErr")
339        @NotNull
340        public static ExitCode doMainNoExit(@NotNull CLICompiler compiler, @NotNull String[] args) {
341            try {
342                return compiler.exec(System.err, args);
343            }
344            catch (CompileEnvironmentException e) {
345                System.err.println(e.getMessage());
346                return INTERNAL_ERROR;
347            }
348        }
349    }