001 /*
002 * Copyright 2010-2015 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.kotlin.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.sampullara.cli.Args;
024 import org.fusesource.jansi.AnsiConsole;
025 import org.jetbrains.annotations.NotNull;
026 import org.jetbrains.annotations.Nullable;
027 import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments;
028 import org.jetbrains.kotlin.cli.common.messages.*;
029 import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler;
030 import org.jetbrains.kotlin.cli.jvm.compiler.CompileEnvironmentException;
031 import org.jetbrains.kotlin.config.CompilerConfiguration;
032 import org.jetbrains.kotlin.config.Services;
033 import org.jetbrains.kotlin.progress.CompilationCanceledException;
034 import org.jetbrains.kotlin.progress.CompilationCanceledStatus;
035 import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus;
036
037 import java.io.PrintStream;
038 import java.util.List;
039
040 import static org.jetbrains.kotlin.cli.common.ExitCode.*;
041
042 public abstract class CLICompiler<A extends CommonCompilerArguments> {
043 @NotNull
044 private List<CompilerPlugin> compilerPlugins = Lists.newArrayList();
045
046 @NotNull
047 public List<CompilerPlugin> getCompilerPlugins() {
048 return compilerPlugins;
049 }
050
051 public void setCompilerPlugins(@NotNull List<CompilerPlugin> compilerPlugins) {
052 this.compilerPlugins = compilerPlugins;
053 }
054
055 @NotNull
056 public ExitCode exec(@NotNull PrintStream errStream, @NotNull String... args) {
057 return exec(errStream, Services.EMPTY, MessageRenderer.PLAIN_RELATIVE_PATHS, args);
058 }
059
060 @SuppressWarnings("UnusedDeclaration") // Used via reflection in CompilerRunnerUtil#invokeExecMethod
061 @NotNull
062 public ExitCode execAndOutputXml(@NotNull PrintStream errStream, @NotNull Services services, @NotNull String... args) {
063 return exec(errStream, services, MessageRenderer.XML, args);
064 }
065
066 @SuppressWarnings("UnusedDeclaration") // Used via reflection in KotlinCompilerBaseTask
067 @NotNull
068 public ExitCode execFullPathsInMessages(@NotNull PrintStream errStream, @NotNull String[] args) {
069 return exec(errStream, Services.EMPTY, MessageRenderer.PLAIN_FULL_PATHS, args);
070 }
071
072 @Nullable
073 private A parseArguments(@NotNull PrintStream errStream, @NotNull MessageRenderer messageRenderer, @NotNull String[] args) {
074 try {
075 A arguments = createArguments();
076 arguments.freeArgs = Args.parse(arguments, args);
077 return arguments;
078 }
079 catch (IllegalArgumentException e) {
080 errStream.println(e.getMessage());
081 usage(errStream, false);
082 }
083 catch (Throwable t) {
084 errStream.println(messageRenderer.render(
085 CompilerMessageSeverity.EXCEPTION,
086 OutputMessageUtil.renderException(t),
087 CompilerMessageLocation.NO_LOCATION)
088 );
089 }
090 return null;
091 }
092
093 /**
094 * Allow derived classes to add additional command line arguments
095 */
096 protected void usage(@NotNull PrintStream target, boolean extraHelp) {
097 Usage.print(target, createArguments(), extraHelp);
098 }
099
100 /**
101 * Strategy method to configure the environment, allowing compiler
102 * based tools to customise their own plugins
103 */
104 protected void configureEnvironment(@NotNull CompilerConfiguration configuration, @NotNull A arguments) {
105 configuration.addAll(CLIConfigurationKeys.COMPILER_PLUGINS, compilerPlugins);
106 }
107
108 @NotNull
109 protected abstract A createArguments();
110
111 @NotNull
112 private ExitCode exec(
113 @NotNull PrintStream errStream,
114 @NotNull Services services,
115 @NotNull MessageRenderer messageRenderer,
116 @NotNull String[] args
117 ) {
118 K2JVMCompiler.Companion.resetInitStartTime();
119
120 A arguments = parseArguments(errStream, messageRenderer, args);
121 if (arguments == null) {
122 return INTERNAL_ERROR;
123 }
124
125 if (arguments.help || arguments.extraHelp) {
126 usage(errStream, arguments.extraHelp);
127 return OK;
128 }
129
130 MessageCollector collector = new PrintingMessageCollector(errStream, messageRenderer, arguments.verbose);
131
132 try {
133 AnsiConsole.systemInstall();
134 errStream.print(messageRenderer.renderPreamble());
135 return exec(collector, services, arguments);
136 }
137 finally {
138 errStream.print(messageRenderer.renderConclusion());
139 AnsiConsole.systemUninstall();
140 }
141 }
142
143 @NotNull
144 public ExitCode exec(@NotNull MessageCollector messageCollector, @NotNull Services services, @NotNull A arguments) {
145 printVersionIfNeeded(messageCollector, arguments);
146
147 if (arguments.suppressWarnings) {
148 messageCollector = new FilteringMessageCollector(messageCollector, Predicates.equalTo(CompilerMessageSeverity.WARNING));
149 }
150
151 GroupingMessageCollector groupingCollector = new GroupingMessageCollector(messageCollector);
152 try {
153 ExitCode exitCode = OK;
154
155 int repeatCount = 1;
156 if (arguments.repeat != null) {
157 try {
158 repeatCount = Integer.parseInt(arguments.repeat);
159 }
160 catch (NumberFormatException ignored) {
161 }
162 }
163
164 CompilationCanceledStatus canceledStatus = services.get(CompilationCanceledStatus.class);
165 ProgressIndicatorAndCompilationCanceledStatus.setCompilationCanceledStatus(canceledStatus);
166
167 for (int i = 0; i < repeatCount; i++) {
168 if (i > 0) {
169 K2JVMCompiler.Companion.resetInitStartTime();
170 }
171 Disposable rootDisposable = Disposer.newDisposable();
172 try {
173 MessageSeverityCollector severityCollector = new MessageSeverityCollector(groupingCollector);
174 ExitCode code = doExecute(arguments, services, severityCollector, rootDisposable);
175 exitCode = severityCollector.anyReported(CompilerMessageSeverity.ERROR) ? COMPILATION_ERROR : code;
176 }
177 catch(CompilationCanceledException e) {
178 messageCollector.report(CompilerMessageSeverity.INFO, "Compilation was canceled", CompilerMessageLocation.NO_LOCATION);
179 return ExitCode.OK;
180 }
181 catch(RuntimeException e) {
182 Throwable cause = e.getCause();
183 if (cause instanceof CompilationCanceledException) {
184 messageCollector
185 .report(CompilerMessageSeverity.INFO, "Compilation was canceled", CompilerMessageLocation.NO_LOCATION);
186 return ExitCode.OK;
187 }
188 else {
189 throw e;
190 }
191 }
192 finally {
193 Disposer.dispose(rootDisposable);
194 }
195 }
196 return exitCode;
197 }
198 catch (Throwable t) {
199 groupingCollector.report(CompilerMessageSeverity.EXCEPTION, OutputMessageUtil.renderException(t),
200 CompilerMessageLocation.NO_LOCATION);
201 return INTERNAL_ERROR;
202 }
203 finally {
204 groupingCollector.flush();
205 }
206 }
207
208 @NotNull
209 protected abstract ExitCode doExecute(
210 @NotNull A arguments,
211 @NotNull Services services,
212 @NotNull MessageCollector messageCollector,
213 @NotNull Disposable rootDisposable
214 );
215
216 protected void printVersionIfNeeded(@NotNull MessageCollector messageCollector, @NotNull A arguments) {
217 if (!arguments.version) return;
218
219 messageCollector.report(CompilerMessageSeverity.INFO,
220 "Kotlin Compiler version " + KotlinVersion.VERSION,
221 CompilerMessageLocation.NO_LOCATION);
222 }
223
224 /**
225 * Useful main for derived command line tools
226 */
227 public static void doMain(@NotNull CLICompiler compiler, @NotNull String[] args) {
228 // We depend on swing (indirectly through PSI or something), so we want to declare headless mode,
229 // to avoid accidentally starting the UI thread
230 System.setProperty("java.awt.headless", "true");
231 ExitCode exitCode = doMainNoExit(compiler, args);
232 if (exitCode != OK) {
233 System.exit(exitCode.getCode());
234 }
235 }
236
237 @NotNull
238 public static ExitCode doMainNoExit(@NotNull CLICompiler compiler, @NotNull String[] args) {
239 try {
240 return compiler.exec(System.err, args);
241 }
242 catch (CompileEnvironmentException e) {
243 System.err.println(e.getMessage());
244 return INTERNAL_ERROR;
245 }
246 }
247 }