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.intellij.openapi.Disposable;
020 import com.intellij.openapi.util.Disposer;
021 import com.sampullara.cli.Args;
022 import org.jetbrains.annotations.NotNull;
023 import org.jetbrains.jet.cli.common.messages.*;
024 import org.jetbrains.jet.cli.jvm.compiler.CompileEnvironmentException;
025 import org.jetbrains.jet.cli.jvm.compiler.CompileEnvironmentUtil;
026 import org.jetbrains.jet.config.CompilerConfiguration;
027
028 import java.io.PrintStream;
029
030 import static org.jetbrains.jet.cli.common.ExitCode.COMPILATION_ERROR;
031 import static org.jetbrains.jet.cli.common.ExitCode.INTERNAL_ERROR;
032 import static org.jetbrains.jet.cli.common.ExitCode.OK;
033
034 public 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 " + KotlinVersion.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 }