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 }