001    /*
002     * Copyright 2010-2013 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.jet.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.intellij.openapi.util.text.StringUtil;
024    import com.sampullara.cli.Args;
025    import com.sampullara.cli.ArgumentUtils;
026    import org.jetbrains.annotations.NotNull;
027    import org.jetbrains.jet.cli.common.arguments.CommonCompilerArguments;
028    import org.jetbrains.jet.cli.common.messages.*;
029    import org.jetbrains.jet.cli.jvm.compiler.CompileEnvironmentException;
030    import org.jetbrains.jet.config.CompilerConfiguration;
031    
032    import java.io.PrintStream;
033    import java.util.List;
034    
035    import static org.jetbrains.jet.cli.common.ExitCode.*;
036    
037    public abstract class CLICompiler<A extends CommonCompilerArguments> {
038    
039        @NotNull
040        private List<CompilerPlugin> compilerPlugins = Lists.newArrayList();
041    
042        @NotNull
043        public List<CompilerPlugin> getCompilerPlugins() {
044            return compilerPlugins;
045        }
046    
047        public void setCompilerPlugins(@NotNull List<CompilerPlugin> compilerPlugins) {
048            this.compilerPlugins = compilerPlugins;
049        }
050    
051        @NotNull
052        public ExitCode exec(@NotNull PrintStream errStream, @NotNull String... args) {
053            A arguments = createArguments();
054            if (!parseArguments(errStream, arguments, args)) {
055                return INTERNAL_ERROR;
056            }
057            return exec(errStream, arguments);
058        }
059    
060        /**
061         * Returns true if the arguments can be parsed correctly
062         */
063        protected boolean parseArguments(@NotNull PrintStream errStream, @NotNull A arguments, @NotNull String[] args) {
064            try {
065                arguments.freeArgs = Args.parse(arguments, args);
066                checkArguments(arguments);
067                return true;
068            }
069            catch (IllegalArgumentException e) {
070                errStream.println(e.getMessage());
071                usage(errStream);
072            }
073            catch (Throwable t) {
074                // Always use tags
075                errStream.println(MessageRenderer.TAGS.renderException(t));
076            }
077            return false;
078        }
079    
080        protected void checkArguments(@NotNull A argument) {
081    
082        }
083    
084        /**
085         * Allow derived classes to add additional command line arguments
086         */
087        protected void usage(@NotNull PrintStream target) {
088            // We should say something like
089            //   Args.usage(target, K2JVMCompilerArguments.class);
090            // but currently cli-parser we are using does not support that
091            // a corresponding patch has been sent to the authors
092            // For now, we are using this:
093            PrintStream oldErr = System.err;
094            System.setErr(target);
095            try {
096                // TODO: use proper argv0
097                Args.usage(createArguments());
098            }
099            finally {
100                System.setErr(oldErr);
101            }
102        }
103    
104        /**
105         * Strategy method to configure the environment, allowing compiler
106         * based tools to customise their own plugins
107         */
108        protected void configureEnvironment(@NotNull CompilerConfiguration configuration, @NotNull A arguments) {
109            configuration.addAll(CLIConfigurationKeys.COMPILER_PLUGINS, compilerPlugins);
110        }
111    
112        @NotNull
113        protected abstract A createArguments();
114    
115        /**
116         * Executes the compiler on the parsed arguments
117         */
118        @NotNull
119        public ExitCode exec(@NotNull PrintStream errStream, @NotNull A arguments) {
120            if (arguments.help) {
121                usage(errStream);
122                return OK;
123            }
124    
125            MessageRenderer messageRenderer = getMessageRenderer(arguments);
126            errStream.print(messageRenderer.renderPreamble());
127    
128            printArgumentsIfNeeded(errStream, arguments, messageRenderer);
129            printVersionIfNeeded(errStream, arguments, messageRenderer);
130    
131            MessageCollector collector = new PrintingMessageCollector(errStream, messageRenderer, arguments.verbose);
132    
133            if (arguments.suppressAllWarnings()) {
134                collector = new FilteringMessageCollector(collector, Predicates.equalTo(CompilerMessageSeverity.WARNING));
135            }
136    
137            try {
138                return exec(collector, arguments);
139            }
140            finally {
141                errStream.print(messageRenderer.renderConclusion());
142            }
143        }
144    
145        @NotNull
146        public ExitCode exec(@NotNull MessageCollector messageCollector, @NotNull A arguments) {
147            GroupingMessageCollector groupingCollector = new GroupingMessageCollector(messageCollector);
148            try {
149                Disposable rootDisposable = Disposer.newDisposable();
150                try {
151                    MessageSeverityCollector severityCollector = new MessageSeverityCollector(groupingCollector);
152                    ExitCode code = doExecute(arguments, severityCollector, rootDisposable);
153                    return severityCollector.anyReported(CompilerMessageSeverity.ERROR) ? COMPILATION_ERROR : code;
154                }
155                finally {
156                    Disposer.dispose(rootDisposable);
157                }
158            }
159            catch (Throwable t) {
160                groupingCollector.report(CompilerMessageSeverity.EXCEPTION, MessageRenderer.PLAIN.renderException(t),
161                                         CompilerMessageLocation.NO_LOCATION);
162                return INTERNAL_ERROR;
163            }
164            finally {
165                groupingCollector.flush();
166            }
167        }
168    
169        @NotNull
170        protected abstract ExitCode doExecute(@NotNull A arguments, @NotNull MessageCollector messageCollector, @NotNull Disposable rootDisposable);
171    
172        //TODO: can we make it private?
173        @NotNull
174        protected MessageRenderer getMessageRenderer(@NotNull A arguments) {
175            return arguments.tags ? MessageRenderer.TAGS : MessageRenderer.PLAIN;
176        }
177    
178        protected void printVersionIfNeeded(
179                @NotNull PrintStream errStream,
180                @NotNull A arguments,
181                @NotNull MessageRenderer messageRenderer
182        ) {
183            if (arguments.version) {
184                String versionMessage = messageRenderer.render(CompilerMessageSeverity.INFO,
185                                                               "Kotlin Compiler version " + KotlinVersion.VERSION,
186                                                               CompilerMessageLocation.NO_LOCATION);
187                errStream.println(versionMessage);
188            }
189        }
190    
191        private void printArgumentsIfNeeded(
192                @NotNull PrintStream errStream,
193                @NotNull A arguments,
194                @NotNull MessageRenderer messageRenderer
195        ) {
196            if (arguments.printArgs) {
197                String freeArgs = !arguments.freeArgs.isEmpty() ? " " + StringUtil.join(arguments.freeArgs, " ") : "";
198    
199                List<String> argumentsAsList = ArgumentUtils.convertArgumentsToStringList(arguments, createArguments());
200                String argumentsAsString = StringUtil.join(argumentsAsList, " ");
201    
202                String printArgsMessage = messageRenderer.render(CompilerMessageSeverity.INFO,
203                                                                 "Invoking compiler " + getClass().getName() +
204                                                                 " with arguments " + argumentsAsString + freeArgs,
205                                                                 CompilerMessageLocation.NO_LOCATION);
206                errStream.println(printArgsMessage);
207            }
208        }
209    
210        /**
211         * Useful main for derived command line tools
212         */
213        public static void doMain(@NotNull CLICompiler compiler, @NotNull String[] args) {
214            // We depend on swing (indirectly through PSI or something), so we want to declare headless mode,
215            // to avoid accidentally starting the UI thread
216            System.setProperty("java.awt.headless", "true");
217            ExitCode exitCode = doMainNoExit(compiler, args);
218            if (exitCode != OK) {
219                System.exit(exitCode.getCode());
220            }
221        }
222    
223        @NotNull
224        public static ExitCode doMainNoExit(@NotNull CLICompiler compiler, @NotNull String[] args) {
225            try {
226                ExitCode rc = compiler.exec(System.out, args);
227                if (rc != OK) {
228                    System.err.println("exec() finished with " + rc + " return code");
229                }
230                return rc;
231            }
232            catch (CompileEnvironmentException e) {
233                System.err.println(e.getMessage());
234                return INTERNAL_ERROR;
235            }
236        }
237    }