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
017package org.jetbrains.jet.cli.common;
018
019import com.intellij.openapi.Disposable;
020import com.intellij.openapi.util.Disposer;
021import com.sampullara.cli.Args;
022import org.jetbrains.annotations.NotNull;
023import org.jetbrains.jet.cli.common.messages.*;
024import org.jetbrains.jet.cli.jvm.compiler.CompileEnvironmentException;
025import org.jetbrains.jet.cli.jvm.compiler.CompileEnvironmentUtil;
026import org.jetbrains.jet.config.CompilerConfiguration;
027
028import java.io.PrintStream;
029
030import static org.jetbrains.jet.cli.common.ExitCode.COMPILATION_ERROR;
031import static org.jetbrains.jet.cli.common.ExitCode.INTERNAL_ERROR;
032import static org.jetbrains.jet.cli.common.ExitCode.OK;
033
034public abstract class CLICompiler<A extends CompilerArguments> {
035
036    @NotNull
037    public ExitCode exec(@NotNull PrintStream errStream, @NotNull String... args) {
038        A arguments = createArguments();
039        if (!parseArguments(errStream, arguments, args)) {
040            return INTERNAL_ERROR;
041        }
042        return exec(errStream, arguments);
043    }
044
045    /**
046     * Returns true if the arguments can be parsed correctly
047     */
048    protected boolean parseArguments(@NotNull PrintStream errStream, @NotNull A arguments, @NotNull String[] args) {
049        try {
050            arguments.freeArgs = Args.parse(arguments, args);
051            return true;
052        }
053        catch (IllegalArgumentException e) {
054            errStream.println(e.getMessage());
055            usage(errStream);
056        }
057        catch (Throwable t) {
058            // Always use tags
059            errStream.println(MessageRenderer.TAGS.renderException(t));
060        }
061        return false;
062    }
063
064    /**
065     * Allow derived classes to add additional command line arguments
066     */
067    protected void usage(@NotNull PrintStream target) {
068        // We should say something like
069        //   Args.usage(target, K2JVMCompilerArguments.class);
070        // but currently cli-parser we are using does not support that
071        // a corresponding patch has been sent to the authors
072        // For now, we are using this:
073        PrintStream oldErr = System.err;
074        System.setErr(target);
075        try {
076            // TODO: use proper argv0
077            Args.usage(createArguments());
078        }
079        finally {
080            System.setErr(oldErr);
081        }
082    }
083
084    /**
085     * Strategy method to configure the environment, allowing compiler
086     * based tools to customise their own plugins
087     */
088    //TODO: add parameter annotations when KT-1863 is resolved
089    protected void configureEnvironment(@NotNull CompilerConfiguration configuration, @NotNull A arguments) {
090        configuration.addAll(CLIConfigurationKeys.COMPILER_PLUGINS, arguments.getCompilerPlugins());
091    }
092
093    @NotNull
094    protected abstract A createArguments();
095
096    /**
097     * Executes the compiler on the parsed arguments
098     */
099    @NotNull
100    public ExitCode exec(@NotNull PrintStream errStream, @NotNull A arguments) {
101        if (arguments.isHelp()) {
102            usage(errStream);
103            return OK;
104        }
105
106        MessageRenderer messageRenderer = getMessageRenderer(arguments);
107        errStream.print(messageRenderer.renderPreamble());
108
109        printVersionIfNeeded(errStream, arguments, messageRenderer);
110
111        PrintingMessageCollector printingCollector = new PrintingMessageCollector(errStream, messageRenderer, arguments.isVerbose());
112
113        try {
114            return exec(printingCollector, arguments);
115        }
116        finally {
117            errStream.print(messageRenderer.renderConclusion());
118        }
119    }
120
121    @NotNull
122    public ExitCode exec(@NotNull MessageCollector messageCollector, @NotNull A arguments) {
123        GroupingMessageCollector groupingCollector = new GroupingMessageCollector(messageCollector);
124        try {
125            Disposable rootDisposable = CompileEnvironmentUtil.createMockDisposable();
126            try {
127                MessageSeverityCollector severityCollector = new MessageSeverityCollector(groupingCollector);
128                ExitCode code = doExecute(arguments, severityCollector, rootDisposable);
129                return severityCollector.anyReported(CompilerMessageSeverity.ERROR) ? COMPILATION_ERROR : code;
130            }
131            finally {
132                Disposer.dispose(rootDisposable);
133            }
134        }
135        catch (Throwable t) {
136            groupingCollector.report(CompilerMessageSeverity.EXCEPTION, MessageRenderer.PLAIN.renderException(t),
137                                     CompilerMessageLocation.NO_LOCATION);
138            return INTERNAL_ERROR;
139        }
140        finally {
141            groupingCollector.flush();
142        }
143    }
144
145    //TODO: can't declare parameters as not null due to KT-1863
146    @NotNull
147    protected abstract ExitCode doExecute(A arguments, MessageCollector messageCollector, Disposable rootDisposable);
148
149    //TODO: can we make it private?
150    @NotNull
151    protected MessageRenderer getMessageRenderer(@NotNull A arguments) {
152        return arguments.isTags() ? MessageRenderer.TAGS : MessageRenderer.PLAIN;
153    }
154
155    protected void printVersionIfNeeded(@NotNull PrintStream errStream,
156            @NotNull A arguments,
157            @NotNull MessageRenderer messageRenderer) {
158        if (arguments.isVersion()) {
159            String versionMessage = messageRenderer.render(CompilerMessageSeverity.INFO,
160                                                           "Kotlin Compiler version " + CompilerVersion.VERSION,
161                                                           CompilerMessageLocation.NO_LOCATION);
162            errStream.println(versionMessage);
163        }
164    }
165
166    /**
167     * Useful main for derived command line tools
168     */
169    public static void doMain(@NotNull CLICompiler compiler, @NotNull String[] args) {
170        // We depend on swing (indirectly through PSI or something), so we want to declare headless mode,
171        // to avoid accidentally starting the UI thread
172        System.setProperty("java.awt.headless", "true");
173        ExitCode exitCode = doMainNoExit(compiler, args);
174        if (exitCode != OK) {
175            System.exit(exitCode.getCode());
176        }
177    }
178
179    @NotNull
180    public static ExitCode doMainNoExit(@NotNull CLICompiler compiler, @NotNull String[] args) {
181        try {
182            ExitCode rc = compiler.exec(System.out, args);
183            if (rc != OK) {
184                System.err.println("exec() finished with " + rc + " return code");
185            }
186            if (Boolean.parseBoolean(System.getProperty("kotlin.print.cmd.args"))) {
187                System.out.println("Command line arguments:");
188                for (String arg : args) {
189                    System.out.println(arg);
190                }
191            }
192            return rc;
193        }
194        catch (CompileEnvironmentException e) {
195            System.err.println(e.getMessage());
196            return INTERNAL_ERROR;
197        }
198    }
199}