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 = !arguments.freeArgs.isEmpty() ? " " + StringUtil.join(arguments.freeArgs, " ") : "";
193
194 List<String> argumentsAsList = ArgumentUtils.convertArgumentsToStringList(arguments, createArguments());
195 String argumentsAsString = StringUtil.join(argumentsAsList, " ");
196
197 String printArgsMessage = messageRenderer.render(CompilerMessageSeverity.INFO,
198 "Invoking compiler " + getClass().getName() +
199 " with arguments " + argumentsAsString + freeArgs,
200 CompilerMessageLocation.NO_LOCATION);
201 errStream.println(printArgsMessage);
202 }
203 }
204
205 /**
206 * Useful main for derived command line tools
207 */
208 public static void doMain(@NotNull CLICompiler compiler, @NotNull String[] args) {
209 // We depend on swing (indirectly through PSI or something), so we want to declare headless mode,
210 // to avoid accidentally starting the UI thread
211 System.setProperty("java.awt.headless", "true");
212 ExitCode exitCode = doMainNoExit(compiler, args);
213 if (exitCode != OK) {
214 System.exit(exitCode.getCode());
215 }
216 }
217
218 @NotNull
219 public static ExitCode doMainNoExit(@NotNull CLICompiler compiler, @NotNull String[] args) {
220 try {
221 ExitCode rc = compiler.exec(System.out, args);
222 if (rc != OK) {
223 System.err.println("exec() finished with " + rc + " return code");
224 }
225 return rc;
226 }
227 catch (CompileEnvironmentException e) {
228 System.err.println(e.getMessage());
229 return INTERNAL_ERROR;
230 }
231 }
232 }