001    /*
002     * Copyright 2010-2015 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.sampullara.cli.Args;
024    import org.jetbrains.annotations.NotNull;
025    import org.jetbrains.annotations.Nullable;
026    import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments;
027    import org.jetbrains.kotlin.cli.common.messages.*;
028    import org.jetbrains.kotlin.cli.jvm.compiler.CompileEnvironmentException;
029    import org.jetbrains.kotlin.config.CompilerConfiguration;
030    import org.jetbrains.kotlin.config.Services;
031    
032    import java.io.PrintStream;
033    import java.util.List;
034    
035    import static org.jetbrains.kotlin.cli.common.ExitCode.*;
036    
037    public abstract class CLICompiler<A extends CommonCompilerArguments> {
038        @NotNull
039        private List<CompilerPlugin> compilerPlugins = Lists.newArrayList();
040    
041        @NotNull
042        public List<CompilerPlugin> getCompilerPlugins() {
043            return compilerPlugins;
044        }
045    
046        public void setCompilerPlugins(@NotNull List<CompilerPlugin> compilerPlugins) {
047            this.compilerPlugins = compilerPlugins;
048        }
049    
050        @NotNull
051        public ExitCode exec(@NotNull PrintStream errStream, @NotNull String... args) {
052            return exec(errStream, Services.EMPTY, MessageRenderer.PLAIN_RELATIVE_PATHS, args);
053        }
054    
055        @SuppressWarnings("UnusedDeclaration") // Used via reflection in CompilerRunnerUtil#invokeExecMethod
056        @NotNull
057        public ExitCode execAndOutputXml(@NotNull PrintStream errStream, @NotNull Services services, @NotNull String... args) {
058            return exec(errStream, services, MessageRenderer.XML, args);
059        }
060    
061        @SuppressWarnings("UnusedDeclaration") // Used via reflection in KotlinCompilerBaseTask
062        @NotNull
063        public ExitCode execFullPathsInMessages(@NotNull PrintStream errStream, @NotNull String[] args) {
064            return exec(errStream, Services.EMPTY, MessageRenderer.PLAIN_FULL_PATHS, args);
065        }
066    
067        @Nullable
068        private A parseArguments(@NotNull PrintStream errStream, @NotNull MessageRenderer messageRenderer, @NotNull String[] args) {
069            try {
070                A arguments = createArguments();
071                arguments.freeArgs = Args.parse(arguments, args);
072                return arguments;
073            }
074            catch (IllegalArgumentException e) {
075                errStream.println(e.getMessage());
076                usage(errStream, false);
077            }
078            catch (Throwable t) {
079                errStream.println(messageRenderer.render(
080                        CompilerMessageSeverity.EXCEPTION,
081                        OutputMessageUtil.renderException(t),
082                        CompilerMessageLocation.NO_LOCATION)
083                );
084            }
085            return null;
086        }
087    
088        /**
089         * Allow derived classes to add additional command line arguments
090         */
091        protected void usage(@NotNull PrintStream target, boolean extraHelp) {
092            Usage.print(target, createArguments(), extraHelp);
093        }
094    
095        /**
096         * Strategy method to configure the environment, allowing compiler
097         * based tools to customise their own plugins
098         */
099        protected void configureEnvironment(@NotNull CompilerConfiguration configuration, @NotNull A arguments) {
100            configuration.addAll(CLIConfigurationKeys.COMPILER_PLUGINS, compilerPlugins);
101        }
102    
103        @NotNull
104        protected abstract A createArguments();
105    
106        @NotNull
107        private ExitCode exec(
108                @NotNull PrintStream errStream,
109                @NotNull Services services,
110                @NotNull MessageRenderer messageRenderer,
111                @NotNull String[] args
112        ) {
113            A arguments = parseArguments(errStream, messageRenderer, args);
114            if (arguments == null) {
115                return INTERNAL_ERROR;
116            }
117    
118            if (arguments.help || arguments.extraHelp) {
119                usage(errStream, arguments.extraHelp);
120                return OK;
121            }
122    
123            errStream.print(messageRenderer.renderPreamble());
124    
125            MessageCollector collector = new PrintingMessageCollector(errStream, messageRenderer, arguments.verbose);
126    
127            try {
128                return exec(collector, services, arguments);
129            }
130            finally {
131                errStream.print(messageRenderer.renderConclusion());
132            }
133        }
134    
135        @NotNull
136        public ExitCode exec(@NotNull MessageCollector messageCollector, @NotNull Services services, @NotNull A arguments) {
137            printVersionIfNeeded(messageCollector, arguments);
138    
139            if (arguments.suppressWarnings) {
140                messageCollector = new FilteringMessageCollector(messageCollector, Predicates.equalTo(CompilerMessageSeverity.WARNING));
141            }
142    
143            GroupingMessageCollector groupingCollector = new GroupingMessageCollector(messageCollector);
144            try {
145                Disposable rootDisposable = Disposer.newDisposable();
146                try {
147                    MessageSeverityCollector severityCollector = new MessageSeverityCollector(groupingCollector);
148                    ExitCode code = doExecute(arguments, services, severityCollector, rootDisposable);
149                    return severityCollector.anyReported(CompilerMessageSeverity.ERROR) ? COMPILATION_ERROR : code;
150                }
151                finally {
152                    Disposer.dispose(rootDisposable);
153                }
154            }
155            catch (Throwable t) {
156                groupingCollector.report(CompilerMessageSeverity.EXCEPTION, OutputMessageUtil.renderException(t),
157                                         CompilerMessageLocation.NO_LOCATION);
158                return INTERNAL_ERROR;
159            }
160            finally {
161                groupingCollector.flush();
162            }
163        }
164    
165        @NotNull
166        protected abstract ExitCode doExecute(
167                @NotNull A arguments,
168                @NotNull Services services,
169                @NotNull MessageCollector messageCollector,
170                @NotNull Disposable rootDisposable
171        );
172    
173        protected void printVersionIfNeeded(@NotNull MessageCollector messageCollector, @NotNull A arguments) {
174            if (!arguments.version) return;
175    
176            messageCollector.report(CompilerMessageSeverity.INFO,
177                                    "Kotlin Compiler version " + KotlinVersion.VERSION,
178                                    CompilerMessageLocation.NO_LOCATION);
179        }
180    
181        /**
182         * Useful main for derived command line tools
183         */
184        public static void doMain(@NotNull CLICompiler compiler, @NotNull String[] args) {
185            // We depend on swing (indirectly through PSI or something), so we want to declare headless mode,
186            // to avoid accidentally starting the UI thread
187            System.setProperty("java.awt.headless", "true");
188            ExitCode exitCode = doMainNoExit(compiler, args);
189            if (exitCode != OK) {
190                System.exit(exitCode.getCode());
191            }
192        }
193    
194        @NotNull
195        public static ExitCode doMainNoExit(@NotNull CLICompiler compiler, @NotNull String[] args) {
196            try {
197                return compiler.exec(System.err, args);
198            }
199            catch (CompileEnvironmentException e) {
200                System.err.println(e.getMessage());
201                return INTERNAL_ERROR;
202            }
203        }
204    }