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