001 /*
002 * Copyright 2010-2016 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 if (PlainTextMessageRenderer.COLOR_ENABLED) {
134 AnsiConsole.systemInstall();
135 }
136
137 errStream.print(messageRenderer.renderPreamble());
138 return exec(collector, services, arguments);
139 }
140 finally {
141 errStream.print(messageRenderer.renderConclusion());
142
143 if (PlainTextMessageRenderer.COLOR_ENABLED) {
144 AnsiConsole.systemUninstall();
145 }
146 }
147 }
148
149 @SuppressWarnings("WeakerAccess") // Used in maven (see KotlinCompileMojoBase.java)
150 @NotNull
151 public ExitCode exec(@NotNull MessageCollector messageCollector, @NotNull Services services, @NotNull A arguments) {
152 printVersionIfNeeded(messageCollector, arguments);
153
154 if (arguments.suppressWarnings) {
155 messageCollector = new FilteringMessageCollector(messageCollector, Predicates.equalTo(CompilerMessageSeverity.WARNING));
156 }
157
158 GroupingMessageCollector groupingCollector = new GroupingMessageCollector(messageCollector);
159 try {
160 ExitCode exitCode = OK;
161
162 int repeatCount = 1;
163 if (arguments.repeat != null) {
164 try {
165 repeatCount = Integer.parseInt(arguments.repeat);
166 }
167 catch (NumberFormatException ignored) {
168 }
169 }
170
171 CompilationCanceledStatus canceledStatus = services.get(CompilationCanceledStatus.class);
172 ProgressIndicatorAndCompilationCanceledStatus.setCompilationCanceledStatus(canceledStatus);
173
174 for (int i = 0; i < repeatCount; i++) {
175 if (i > 0) {
176 K2JVMCompiler.Companion.resetInitStartTime();
177 }
178 Disposable rootDisposable = Disposer.newDisposable();
179 try {
180 MessageSeverityCollector severityCollector = new MessageSeverityCollector(groupingCollector);
181 ExitCode code = doExecute(arguments, services, severityCollector, rootDisposable);
182 exitCode = severityCollector.anyReported(CompilerMessageSeverity.ERROR) ? COMPILATION_ERROR : code;
183 }
184 catch(CompilationCanceledException e) {
185 messageCollector.report(CompilerMessageSeverity.INFO, "Compilation was canceled", CompilerMessageLocation.NO_LOCATION);
186 return ExitCode.OK;
187 }
188 catch(RuntimeException e) {
189 Throwable cause = e.getCause();
190 if (cause instanceof CompilationCanceledException) {
191 messageCollector
192 .report(CompilerMessageSeverity.INFO, "Compilation was canceled", CompilerMessageLocation.NO_LOCATION);
193 return ExitCode.OK;
194 }
195 else {
196 throw e;
197 }
198 }
199 finally {
200 Disposer.dispose(rootDisposable);
201 }
202 }
203 return exitCode;
204 }
205 catch (Throwable t) {
206 groupingCollector.report(CompilerMessageSeverity.EXCEPTION, OutputMessageUtil.renderException(t),
207 CompilerMessageLocation.NO_LOCATION);
208 return INTERNAL_ERROR;
209 }
210 finally {
211 groupingCollector.flush();
212 }
213 }
214
215 @NotNull
216 protected abstract ExitCode doExecute(
217 @NotNull A arguments,
218 @NotNull Services services,
219 @NotNull MessageCollector messageCollector,
220 @NotNull Disposable rootDisposable
221 );
222
223 private void printVersionIfNeeded(@NotNull MessageCollector messageCollector, @NotNull A arguments) {
224 if (!arguments.version) return;
225
226 messageCollector.report(CompilerMessageSeverity.INFO,
227 "Kotlin Compiler version " + KotlinVersion.VERSION,
228 CompilerMessageLocation.NO_LOCATION);
229 }
230
231 /**
232 * Useful main for derived command line tools
233 */
234 public static void doMain(@NotNull CLICompiler compiler, @NotNull String[] args) {
235 // We depend on swing (indirectly through PSI or something), so we want to declare headless mode,
236 // to avoid accidentally starting the UI thread
237 System.setProperty("java.awt.headless", "true");
238 ExitCode exitCode = doMainNoExit(compiler, args);
239 if (exitCode != OK) {
240 System.exit(exitCode.getCode());
241 }
242 }
243
244 @SuppressWarnings("UseOfSystemOutOrSystemErr")
245 @NotNull
246 public static ExitCode doMainNoExit(@NotNull CLICompiler compiler, @NotNull String[] args) {
247 try {
248 return compiler.exec(System.err, args);
249 }
250 catch (CompileEnvironmentException e) {
251 System.err.println(e.getMessage());
252 return INTERNAL_ERROR;
253 }
254 }
255 }