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