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.ArrayList;
044 import java.util.Collections;
045 import java.util.List;
046 import java.util.Properties;
047
048 import static org.jetbrains.kotlin.cli.common.ExitCode.*;
049
050 public abstract class CLICompiler<A extends CommonCompilerArguments> {
051 static private void setIdeaIoUseFallback() {
052 if (SystemInfo.isWindows) {
053 Properties properties = System.getProperties();
054
055 properties.setProperty("idea.io.use.nio2", Boolean.TRUE.toString());
056
057 if (!(SystemInfo.isJavaVersionAtLeast("1.7") && !"1.7.0-ea".equals(SystemInfo.JAVA_VERSION))) {
058 properties.setProperty("idea.io.use.fallback", Boolean.TRUE.toString());
059 }
060 }
061 }
062
063 @NotNull
064 public ExitCode exec(@NotNull PrintStream errStream, @NotNull String... args) {
065 return exec(errStream, Services.EMPTY, MessageRenderer.PLAIN_RELATIVE_PATHS, args);
066 }
067
068 // Used via reflection in CompilerRunnerUtil#invokeExecMethod and in Eclipse plugin (see KotlinCLICompiler)
069 @SuppressWarnings("UnusedDeclaration")
070 @NotNull
071 public ExitCode execAndOutputXml(@NotNull PrintStream errStream, @NotNull Services services, @NotNull String... args) {
072 return exec(errStream, services, MessageRenderer.XML, args);
073 }
074
075 // Used via reflection in KotlinCompilerBaseTask
076 @SuppressWarnings("UnusedDeclaration")
077 @NotNull
078 public ExitCode execFullPathsInMessages(@NotNull PrintStream errStream, @NotNull String[] args) {
079 return exec(errStream, Services.EMPTY, MessageRenderer.PLAIN_FULL_PATHS, args);
080 }
081
082 @Nullable
083 private A parseArguments(@NotNull PrintStream errStream, @NotNull MessageRenderer messageRenderer, @NotNull String[] args) {
084 try {
085 A arguments = createArguments();
086 parseArguments(args, arguments);
087 return arguments;
088 }
089 catch (IllegalArgumentException e) {
090 errStream.println(e.getMessage());
091 Usage.print(errStream, createArguments(), false);
092 }
093 catch (Throwable t) {
094 errStream.println(messageRenderer.render(
095 CompilerMessageSeverity.EXCEPTION,
096 OutputMessageUtil.renderException(t),
097 CompilerMessageLocation.NO_LOCATION)
098 );
099 }
100 return null;
101 }
102
103 @SuppressWarnings("WeakerAccess") // Used in maven (see KotlinCompileMojoBase.java)
104 public void parseArguments(@NotNull String[] args, @NotNull A arguments) {
105 ArgumentUtilsKt.parseArguments(args, arguments);
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.print(errStream, createArguments(), 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 reportUnknownExtraFlags(messageCollector, arguments);
159 reportUnsupportedJavaVersion(messageCollector, arguments);
160
161 GroupingMessageCollector groupingCollector = new GroupingMessageCollector(messageCollector);
162
163 CompilerConfiguration configuration = new CompilerConfiguration();
164 configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, groupingCollector);
165
166 setupCommonArgumentsAndServices(configuration, arguments, services);
167 setupPlatformSpecificArgumentsAndServices(configuration, arguments, services);
168
169 try {
170 ExitCode exitCode = OK;
171
172 int repeatCount = 1;
173 if (arguments.repeat != null) {
174 try {
175 repeatCount = Integer.parseInt(arguments.repeat);
176 }
177 catch (NumberFormatException ignored) {
178 }
179 }
180
181 CompilationCanceledStatus canceledStatus = services.get(CompilationCanceledStatus.class);
182 ProgressIndicatorAndCompilationCanceledStatus.setCompilationCanceledStatus(canceledStatus);
183
184 for (int i = 0; i < repeatCount; i++) {
185 if (i > 0) {
186 K2JVMCompiler.Companion.resetInitStartTime();
187 }
188 Disposable rootDisposable = Disposer.newDisposable();
189 try {
190 setIdeaIoUseFallback();
191 ExitCode code = doExecute(arguments, configuration, rootDisposable);
192 exitCode = groupingCollector.hasErrors() ? COMPILATION_ERROR : code;
193 }
194 catch (CompilationCanceledException e) {
195 messageCollector.report(CompilerMessageSeverity.INFO, "Compilation was canceled", CompilerMessageLocation.NO_LOCATION);
196 return ExitCode.OK;
197 }
198 catch (RuntimeException e) {
199 Throwable cause = e.getCause();
200 if (cause instanceof CompilationCanceledException) {
201 messageCollector
202 .report(CompilerMessageSeverity.INFO, "Compilation was canceled", CompilerMessageLocation.NO_LOCATION);
203 return ExitCode.OK;
204 }
205 else {
206 throw e;
207 }
208 }
209 finally {
210 Disposer.dispose(rootDisposable);
211 }
212 }
213 return exitCode;
214 }
215 catch (Throwable t) {
216 groupingCollector.report(CompilerMessageSeverity.EXCEPTION, OutputMessageUtil.renderException(t),
217 CompilerMessageLocation.NO_LOCATION);
218 return INTERNAL_ERROR;
219 }
220 finally {
221 groupingCollector.flush();
222 }
223 }
224
225 private static void setupCommonArgumentsAndServices(
226 @NotNull CompilerConfiguration configuration, @NotNull CommonCompilerArguments arguments, @NotNull Services services
227 ) {
228 if (arguments.noInline) {
229 configuration.put(CommonConfigurationKeys.DISABLE_INLINE, true);
230 }
231
232 CompilerJarLocator locator = services.get(CompilerJarLocator.class);
233 if (locator != null) {
234 configuration.put(CLIConfigurationKeys.COMPILER_JAR_LOCATOR, locator);
235 }
236
237 setupLanguageVersionSettings(configuration, arguments);
238 }
239
240 private static void setupLanguageVersionSettings(
241 @NotNull CompilerConfiguration configuration, @NotNull CommonCompilerArguments arguments
242 ) {
243 LanguageVersion languageVersion = parseVersion(configuration, arguments.languageVersion, "language");
244 LanguageVersion apiVersion = parseVersion(configuration, arguments.apiVersion, "API");
245
246 if (languageVersion == null) {
247 // If only "-api-version" is specified, language version is assumed to be the latest
248 languageVersion = LanguageVersion.LATEST;
249 }
250 if (apiVersion == null) {
251 // If only "-language-version" is specified, API version is assumed to be equal to the language version
252 // (API version cannot be greater than the language version)
253 apiVersion = languageVersion;
254 }
255
256 if (apiVersion.compareTo(languageVersion) > 0) {
257 configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY).report(
258 CompilerMessageSeverity.ERROR,
259 "-api-version (" + apiVersion.getVersionString() + ") cannot be greater than " +
260 "-language-version (" + languageVersion.getVersionString() + ")",
261 CompilerMessageLocation.NO_LOCATION
262 );
263 }
264
265 List<LanguageFeature> extraLanguageFeatures = new ArrayList<LanguageFeature>(0);
266 if (arguments.multiPlatform) {
267 extraLanguageFeatures.add(LanguageFeature.MultiPlatformProjects);
268 }
269 if (arguments.noCheckImpl) {
270 extraLanguageFeatures.add(LanguageFeature.MultiPlatformDoNotCheckImpl);
271 }
272
273 LanguageFeature coroutinesApplicabilityLevel = chooseCoroutinesApplicabilityLevel(configuration, arguments);
274 if (coroutinesApplicabilityLevel != null) {
275 extraLanguageFeatures.add(coroutinesApplicabilityLevel);
276 }
277
278 configuration.put(
279 CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS,
280 new LanguageVersionSettingsImpl(
281 languageVersion,
282 ApiVersion.createByLanguageVersion(apiVersion),
283 extraLanguageFeatures,
284 arguments.apiVersion != null
285 )
286 );
287 }
288
289 @Nullable
290 private static LanguageFeature chooseCoroutinesApplicabilityLevel(
291 @NotNull CompilerConfiguration configuration, @NotNull CommonCompilerArguments arguments) {
292 if (!arguments.coroutinesEnable && !arguments.coroutinesError && !arguments.coroutinesWarn) {
293 return LanguageFeature.WarnOnCoroutines;
294 }
295 else if (arguments.coroutinesError && !arguments.coroutinesWarn && !arguments.coroutinesEnable) {
296 return LanguageFeature.ErrorOnCoroutines;
297 }
298 else if (arguments.coroutinesWarn && !arguments.coroutinesError && !arguments.coroutinesEnable) {
299 return LanguageFeature.WarnOnCoroutines;
300 }
301 else if (arguments.coroutinesEnable && !arguments.coroutinesWarn && !arguments.coroutinesError) {
302 return null;
303 } else {
304 String message = "The -Xcoroutines can only have one value";
305 configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY).report(
306 CompilerMessageSeverity.ERROR, message, CompilerMessageLocation.NO_LOCATION
307 );
308
309 return null;
310 }
311 }
312
313 @Nullable
314 private static LanguageVersion parseVersion(
315 @NotNull CompilerConfiguration configuration, @Nullable String value, @NotNull String versionOf
316 ) {
317 if (value == null) return null;
318
319 LanguageVersion version = LanguageVersion.fromVersionString(value);
320 if (version != null) {
321 return version;
322 }
323
324 List<String> versionStrings = ArraysKt.map(LanguageVersion.values(), new Function1<LanguageVersion, String>() {
325 @Override
326 public String invoke(LanguageVersion version) {
327 return version.getVersionString();
328 }
329 });
330 String message = "Unknown " + versionOf + " version: " + value + "\n" +
331 "Supported " + versionOf + " versions: " + StringsKt.join(versionStrings, ", ");
332 configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY).report(
333 CompilerMessageSeverity.ERROR, message, CompilerMessageLocation.NO_LOCATION
334 );
335
336 return null;
337 }
338
339 protected abstract void setupPlatformSpecificArgumentsAndServices(
340 @NotNull CompilerConfiguration configuration, @NotNull A arguments, @NotNull Services services
341 );
342
343 private void reportUnknownExtraFlags(@NotNull MessageCollector collector, @NotNull A arguments) {
344 for (String flag : arguments.unknownExtraFlags) {
345 collector.report(
346 CompilerMessageSeverity.STRONG_WARNING,
347 "Flag is not supported by this version of the compiler: " + flag,
348 CompilerMessageLocation.NO_LOCATION
349 );
350 }
351 }
352
353 private void reportUnsupportedJavaVersion(MessageCollector collector, A arguments) {
354 if (!SystemInfo.isJavaVersionAtLeast("1.8") && !arguments.noJavaVersionWarning) {
355 collector.report(
356 CompilerMessageSeverity.STRONG_WARNING,
357 "Running the Kotlin compiler under Java 6 or 7 is unsupported and will no longer be possible in a future update.",
358 CompilerMessageLocation.NO_LOCATION
359 );
360 }
361 }
362
363 @NotNull
364 protected abstract ExitCode doExecute(
365 @NotNull A arguments,
366 @NotNull CompilerConfiguration configuration,
367 @NotNull Disposable rootDisposable
368 );
369
370 private void printVersionIfNeeded(@NotNull MessageCollector messageCollector, @NotNull A arguments) {
371 if (!arguments.version) return;
372
373 messageCollector.report(CompilerMessageSeverity.INFO,
374 "Kotlin Compiler version " + KotlinCompilerVersion.VERSION,
375 CompilerMessageLocation.NO_LOCATION);
376 }
377
378 /**
379 * Useful main for derived command line tools
380 */
381 public static void doMain(@NotNull CLICompiler compiler, @NotNull String[] args) {
382 // We depend on swing (indirectly through PSI or something), so we want to declare headless mode,
383 // to avoid accidentally starting the UI thread
384 System.setProperty("java.awt.headless", "true");
385 try {
386 ExitCode exitCode = doMainNoExit(compiler, args);
387
388 if (exitCode != OK) {
389 System.exit(exitCode.getCode());
390 }
391 }
392 finally {
393 AppScheduledExecutorService service = (AppScheduledExecutorService) AppExecutorUtil.getAppScheduledExecutorService();
394 service.shutdownAppScheduledExecutorService();
395 }
396 }
397
398 @SuppressWarnings("UseOfSystemOutOrSystemErr")
399 @NotNull
400 public static ExitCode doMainNoExit(@NotNull CLICompiler compiler, @NotNull String[] args) {
401 try {
402 return compiler.exec(System.err, args);
403 }
404 catch (CompileEnvironmentException e) {
405 System.err.println(e.getMessage());
406 return INTERNAL_ERROR;
407 }
408 }
409 }