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.intellij.openapi.Disposable;
021 import com.intellij.openapi.util.Disposer;
022 import com.intellij.openapi.util.SystemInfo;
023 import com.intellij.util.concurrency.AppExecutorUtil;
024 import com.intellij.util.concurrency.AppScheduledExecutorService;
025 import kotlin.collections.ArraysKt;
026 import kotlin.jvm.functions.Function1;
027 import org.fusesource.jansi.AnsiConsole;
028 import org.jetbrains.annotations.NotNull;
029 import org.jetbrains.annotations.Nullable;
030 import org.jetbrains.kotlin.cli.common.arguments.ArgumentUtilsKt;
031 import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments;
032 import org.jetbrains.kotlin.cli.common.messages.*;
033 import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler;
034 import org.jetbrains.kotlin.cli.jvm.compiler.CompileEnvironmentException;
035 import org.jetbrains.kotlin.cli.jvm.compiler.CompilerJarLocator;
036 import org.jetbrains.kotlin.config.*;
037 import org.jetbrains.kotlin.progress.CompilationCanceledException;
038 import org.jetbrains.kotlin.progress.CompilationCanceledStatus;
039 import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus;
040 import org.jetbrains.kotlin.utils.StringsKt;
041
042 import java.io.PrintStream;
043 import java.util.List;
044 import java.util.Properties;
045
046 import static org.jetbrains.kotlin.cli.common.ExitCode.*;
047
048 public abstract class CLICompiler<A extends CommonCompilerArguments> {
049 static private void setIdeaIoUseFallback() {
050 if (SystemInfo.isWindows) {
051 Properties properties = System.getProperties();
052
053 properties.setProperty("idea.io.use.nio2", Boolean.TRUE.toString());
054
055 if (!(SystemInfo.isJavaVersionAtLeast("1.7") && !"1.7.0-ea".equals(SystemInfo.JAVA_VERSION))) {
056 properties.setProperty("idea.io.use.fallback", Boolean.TRUE.toString());
057 }
058 }
059 }
060
061 @NotNull
062 public ExitCode exec(@NotNull PrintStream errStream, @NotNull String... args) {
063 return exec(errStream, Services.EMPTY, MessageRenderer.PLAIN_RELATIVE_PATHS, args);
064 }
065
066 // Used via reflection in CompilerRunnerUtil#invokeExecMethod and in Eclipse plugin (see KotlinCLICompiler)
067 @SuppressWarnings("UnusedDeclaration")
068 @NotNull
069 public ExitCode execAndOutputXml(@NotNull PrintStream errStream, @NotNull Services services, @NotNull String... args) {
070 return exec(errStream, services, MessageRenderer.XML, args);
071 }
072
073 // Used via reflection in KotlinCompilerBaseTask
074 @SuppressWarnings("UnusedDeclaration")
075 @NotNull
076 public ExitCode execFullPathsInMessages(@NotNull PrintStream errStream, @NotNull String[] args) {
077 return exec(errStream, Services.EMPTY, MessageRenderer.PLAIN_FULL_PATHS, args);
078 }
079
080 @Nullable
081 private A parseArguments(@NotNull PrintStream errStream, @NotNull MessageRenderer messageRenderer, @NotNull String[] args) {
082 try {
083 A arguments = createArguments();
084 parseArguments(args, arguments);
085 return arguments;
086 }
087 catch (IllegalArgumentException e) {
088 errStream.println(e.getMessage());
089 Usage.print(errStream, createArguments(), false);
090 }
091 catch (Throwable t) {
092 errStream.println(messageRenderer.render(
093 CompilerMessageSeverity.EXCEPTION,
094 OutputMessageUtil.renderException(t),
095 CompilerMessageLocation.NO_LOCATION)
096 );
097 }
098 return null;
099 }
100
101 @SuppressWarnings("WeakerAccess") // Used in maven (see KotlinCompileMojoBase.java)
102 public void parseArguments(@NotNull String[] args, @NotNull A arguments) {
103 ArgumentUtilsKt.parseArguments(args, arguments);
104 }
105
106 @NotNull
107 protected abstract A createArguments();
108
109 @NotNull
110 private ExitCode exec(
111 @NotNull PrintStream errStream,
112 @NotNull Services services,
113 @NotNull MessageRenderer messageRenderer,
114 @NotNull String[] args
115 ) {
116 K2JVMCompiler.Companion.resetInitStartTime();
117
118 A arguments = parseArguments(errStream, messageRenderer, args);
119 if (arguments == null) {
120 return INTERNAL_ERROR;
121 }
122
123 if (arguments.help || arguments.extraHelp) {
124 Usage.print(errStream, createArguments(), arguments.extraHelp);
125 return OK;
126 }
127
128 MessageCollector collector = new PrintingMessageCollector(errStream, messageRenderer, arguments.verbose);
129
130 try {
131 if (PlainTextMessageRenderer.COLOR_ENABLED) {
132 AnsiConsole.systemInstall();
133 }
134
135 errStream.print(messageRenderer.renderPreamble());
136 return exec(collector, services, arguments);
137 }
138 finally {
139 errStream.print(messageRenderer.renderConclusion());
140
141 if (PlainTextMessageRenderer.COLOR_ENABLED) {
142 AnsiConsole.systemUninstall();
143 }
144 }
145 }
146
147 @SuppressWarnings("WeakerAccess") // Used in maven (see KotlinCompileMojoBase.java)
148 @NotNull
149 public ExitCode exec(@NotNull MessageCollector messageCollector, @NotNull Services services, @NotNull A arguments) {
150 printVersionIfNeeded(messageCollector, arguments);
151
152 if (arguments.suppressWarnings) {
153 messageCollector = new FilteringMessageCollector(messageCollector, Predicates.equalTo(CompilerMessageSeverity.WARNING));
154 }
155
156 reportUnknownExtraFlags(messageCollector, arguments);
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 setIdeaIoUseFallback();
181 ExitCode code = doExecute(arguments, services, groupingCollector, rootDisposable);
182 exitCode = groupingCollector.hasErrors() ? 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 protected static void setupCommonArgumentsAndServices(
216 @NotNull CompilerConfiguration configuration, @NotNull CommonCompilerArguments arguments, @NotNull Services services
217 ) {
218 CompilerJarLocator locator = services.get(CompilerJarLocator.class);
219 if (locator != null) {
220 configuration.put(CLIConfigurationKeys.COMPILER_JAR_LOCATOR, locator);
221 }
222
223 LanguageVersion languageVersion = parseVersion(configuration, arguments.languageVersion, "language");
224 LanguageVersion apiVersion = parseVersion(configuration, arguments.apiVersion, "API");
225 if (languageVersion != null || apiVersion != null) {
226 if (languageVersion == null) {
227 // If only "-api-version" is specified, language version is assumed to be the latest
228 languageVersion = LanguageVersion.LATEST;
229 }
230 if (apiVersion == null) {
231 // If only "-language-version" is specified, API version is assumed to be equal to the language version
232 // (API version cannot be greater than the language version)
233 apiVersion = languageVersion;
234 }
235
236 if (apiVersion.compareTo(languageVersion) > 0) {
237 configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY).report(
238 CompilerMessageSeverity.ERROR,
239 "-api-version (" + apiVersion.getVersionString() + ") cannot be greater than " +
240 "-language-version (" + languageVersion.getVersionString() + ")",
241 CompilerMessageLocation.NO_LOCATION
242 );
243 }
244
245 configuration.put(
246 CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS,
247 new LanguageVersionSettingsImpl(
248 languageVersion,
249 ApiVersion.createByLanguageVersion(apiVersion),
250 arguments.apiVersion != null
251 )
252 );
253 }
254 }
255
256 private static LanguageVersion parseVersion(
257 @NotNull CompilerConfiguration configuration, @Nullable String value, @NotNull String versionOf
258 ) {
259 if (value == null) return null;
260
261 LanguageVersion version = LanguageVersion.fromVersionString(value);
262 if (version != null) {
263 return version;
264 }
265
266 List<String> versionStrings = ArraysKt.map(LanguageVersion.values(), new Function1<LanguageVersion, String>() {
267 @Override
268 public String invoke(LanguageVersion version) {
269 return version.getVersionString();
270 }
271 });
272 String message = "Unknown " + versionOf + " version: " + value + "\n" +
273 "Supported " + versionOf + " versions: " + StringsKt.join(versionStrings, ", ");
274 configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY).report(
275 CompilerMessageSeverity.ERROR, message, CompilerMessageLocation.NO_LOCATION
276 );
277
278 return null;
279 }
280
281 private void reportUnknownExtraFlags(@NotNull MessageCollector collector, @NotNull A arguments) {
282 for (String flag : arguments.unknownExtraFlags) {
283 collector.report(
284 CompilerMessageSeverity.STRONG_WARNING,
285 "Flag is not supported by this version of the compiler: " + flag,
286 CompilerMessageLocation.NO_LOCATION
287 );
288 }
289 }
290
291 @NotNull
292 protected abstract ExitCode doExecute(
293 @NotNull A arguments,
294 @NotNull Services services,
295 @NotNull MessageCollector messageCollector,
296 @NotNull Disposable rootDisposable
297 );
298
299 private void printVersionIfNeeded(@NotNull MessageCollector messageCollector, @NotNull A arguments) {
300 if (!arguments.version) return;
301
302 messageCollector.report(CompilerMessageSeverity.INFO,
303 "Kotlin Compiler version " + KotlinCompilerVersion.VERSION,
304 CompilerMessageLocation.NO_LOCATION);
305 }
306
307 /**
308 * Useful main for derived command line tools
309 */
310 public static void doMain(@NotNull CLICompiler compiler, @NotNull String[] args) {
311 // We depend on swing (indirectly through PSI or something), so we want to declare headless mode,
312 // to avoid accidentally starting the UI thread
313 System.setProperty("java.awt.headless", "true");
314 try {
315 ExitCode exitCode = doMainNoExit(compiler, args);
316
317 if (exitCode != OK) {
318 System.exit(exitCode.getCode());
319 }
320 }
321 finally {
322 AppScheduledExecutorService service = (AppScheduledExecutorService) AppExecutorUtil.getAppScheduledExecutorService();
323 service.shutdownAppScheduledExecutorService();
324 }
325 }
326
327 @SuppressWarnings("UseOfSystemOutOrSystemErr")
328 @NotNull
329 public static ExitCode doMainNoExit(@NotNull CLICompiler compiler, @NotNull String[] args) {
330 try {
331 return compiler.exec(System.err, args);
332 }
333 catch (CompileEnvironmentException e) {
334 System.err.println(e.getMessage());
335 return INTERNAL_ERROR;
336 }
337 }
338 }