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.sampullara.cli.Args; 024 import kotlin.Pair; 025 import kotlin.collections.ArraysKt; 026 import kotlin.collections.CollectionsKt; 027 import kotlin.jvm.functions.Function1; 028 import org.fusesource.jansi.AnsiConsole; 029 import org.jetbrains.annotations.NotNull; 030 import org.jetbrains.annotations.Nullable; 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.CommonConfigurationKeys; 037 import org.jetbrains.kotlin.config.CompilerConfiguration; 038 import org.jetbrains.kotlin.config.LanguageVersion; 039 import org.jetbrains.kotlin.config.Services; 040 import org.jetbrains.kotlin.progress.CompilationCanceledException; 041 import org.jetbrains.kotlin.progress.CompilationCanceledStatus; 042 import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus; 043 import org.jetbrains.kotlin.utils.StringsKt; 044 045 import java.io.PrintStream; 046 import java.util.List; 047 import java.util.Properties; 048 049 import static org.jetbrains.kotlin.cli.common.ExitCode.*; 050 051 public abstract class CLICompiler<A extends CommonCompilerArguments> { 052 static private void setIdeaIoUseFallback() { 053 if (SystemInfo.isWindows) { 054 Properties properties = System.getProperties(); 055 056 properties.setProperty("idea.io.use.nio2", Boolean.TRUE.toString()); 057 058 if (!(SystemInfo.isJavaVersionAtLeast("1.7") && !"1.7.0-ea".equals(SystemInfo.JAVA_VERSION))) { 059 properties.setProperty("idea.io.use.fallback", Boolean.TRUE.toString()); 060 } 061 } 062 } 063 064 @NotNull 065 public ExitCode exec(@NotNull PrintStream errStream, @NotNull String... args) { 066 return exec(errStream, Services.EMPTY, MessageRenderer.PLAIN_RELATIVE_PATHS, args); 067 } 068 069 // Used via reflection in CompilerRunnerUtil#invokeExecMethod and in Eclipse plugin (see KotlinCLICompiler) 070 @SuppressWarnings("UnusedDeclaration") 071 @NotNull 072 public ExitCode execAndOutputXml(@NotNull PrintStream errStream, @NotNull Services services, @NotNull String... args) { 073 return exec(errStream, services, MessageRenderer.XML, args); 074 } 075 076 // Used via reflection in KotlinCompilerBaseTask 077 @SuppressWarnings("UnusedDeclaration") 078 @NotNull 079 public ExitCode execFullPathsInMessages(@NotNull PrintStream errStream, @NotNull String[] args) { 080 return exec(errStream, Services.EMPTY, MessageRenderer.PLAIN_FULL_PATHS, args); 081 } 082 083 @Nullable 084 private A parseArguments(@NotNull PrintStream errStream, @NotNull MessageRenderer messageRenderer, @NotNull String[] args) { 085 try { 086 A arguments = createArguments(); 087 parseArguments(args, arguments); 088 return arguments; 089 } 090 catch (IllegalArgumentException e) { 091 errStream.println(e.getMessage()); 092 Usage.print(errStream, createArguments(), false); 093 } 094 catch (Throwable t) { 095 errStream.println(messageRenderer.render( 096 CompilerMessageSeverity.EXCEPTION, 097 OutputMessageUtil.renderException(t), 098 CompilerMessageLocation.NO_LOCATION) 099 ); 100 } 101 return null; 102 } 103 104 @SuppressWarnings("WeakerAccess") // Used in maven (see KotlinCompileMojoBase.java) 105 public void parseArguments(@NotNull String[] args, @NotNull A arguments) { 106 Pair<List<String>, List<String>> unparsedArgs = 107 CollectionsKt.partition(Args.parse(arguments, args, false), new Function1<String, Boolean>() { 108 @Override 109 public Boolean invoke(String s) { 110 return s.startsWith("-X"); 111 } 112 }); 113 114 arguments.unknownExtraFlags = unparsedArgs.getFirst(); 115 arguments.freeArgs = unparsedArgs.getSecond(); 116 117 for (String argument : arguments.freeArgs) { 118 if (argument.startsWith("-")) { 119 throw new IllegalArgumentException("Invalid argument: " + argument); 120 } 121 } 122 } 123 124 @NotNull 125 protected abstract A createArguments(); 126 127 @NotNull 128 private ExitCode exec( 129 @NotNull PrintStream errStream, 130 @NotNull Services services, 131 @NotNull MessageRenderer messageRenderer, 132 @NotNull String[] args 133 ) { 134 K2JVMCompiler.Companion.resetInitStartTime(); 135 136 A arguments = parseArguments(errStream, messageRenderer, args); 137 if (arguments == null) { 138 return INTERNAL_ERROR; 139 } 140 141 if (arguments.help || arguments.extraHelp) { 142 Usage.print(errStream, createArguments(), arguments.extraHelp); 143 return OK; 144 } 145 146 MessageCollector collector = new PrintingMessageCollector(errStream, messageRenderer, arguments.verbose); 147 148 try { 149 if (PlainTextMessageRenderer.COLOR_ENABLED) { 150 AnsiConsole.systemInstall(); 151 } 152 153 errStream.print(messageRenderer.renderPreamble()); 154 return exec(collector, services, arguments); 155 } 156 finally { 157 errStream.print(messageRenderer.renderConclusion()); 158 159 if (PlainTextMessageRenderer.COLOR_ENABLED) { 160 AnsiConsole.systemUninstall(); 161 } 162 } 163 } 164 165 @SuppressWarnings("WeakerAccess") // Used in maven (see KotlinCompileMojoBase.java) 166 @NotNull 167 public ExitCode exec(@NotNull MessageCollector messageCollector, @NotNull Services services, @NotNull A arguments) { 168 printVersionIfNeeded(messageCollector, arguments); 169 170 if (arguments.suppressWarnings) { 171 messageCollector = new FilteringMessageCollector(messageCollector, Predicates.equalTo(CompilerMessageSeverity.WARNING)); 172 } 173 174 reportUnknownExtraFlags(messageCollector, arguments); 175 176 GroupingMessageCollector groupingCollector = new GroupingMessageCollector(messageCollector); 177 try { 178 ExitCode exitCode = OK; 179 180 int repeatCount = 1; 181 if (arguments.repeat != null) { 182 try { 183 repeatCount = Integer.parseInt(arguments.repeat); 184 } 185 catch (NumberFormatException ignored) { 186 } 187 } 188 189 CompilationCanceledStatus canceledStatus = services.get(CompilationCanceledStatus.class); 190 ProgressIndicatorAndCompilationCanceledStatus.setCompilationCanceledStatus(canceledStatus); 191 192 for (int i = 0; i < repeatCount; i++) { 193 if (i > 0) { 194 K2JVMCompiler.Companion.resetInitStartTime(); 195 } 196 Disposable rootDisposable = Disposer.newDisposable(); 197 try { 198 setIdeaIoUseFallback(); 199 ExitCode code = doExecute(arguments, services, groupingCollector, rootDisposable); 200 exitCode = groupingCollector.hasErrors() ? COMPILATION_ERROR : code; 201 } 202 catch (CompilationCanceledException e) { 203 messageCollector.report(CompilerMessageSeverity.INFO, "Compilation was canceled", CompilerMessageLocation.NO_LOCATION); 204 return ExitCode.OK; 205 } 206 catch (RuntimeException e) { 207 Throwable cause = e.getCause(); 208 if (cause instanceof CompilationCanceledException) { 209 messageCollector 210 .report(CompilerMessageSeverity.INFO, "Compilation was canceled", CompilerMessageLocation.NO_LOCATION); 211 return ExitCode.OK; 212 } 213 else { 214 throw e; 215 } 216 } 217 finally { 218 Disposer.dispose(rootDisposable); 219 } 220 } 221 return exitCode; 222 } 223 catch (Throwable t) { 224 groupingCollector.report(CompilerMessageSeverity.EXCEPTION, OutputMessageUtil.renderException(t), 225 CompilerMessageLocation.NO_LOCATION); 226 return INTERNAL_ERROR; 227 } 228 finally { 229 groupingCollector.flush(); 230 } 231 } 232 233 protected static void setupCommonArgumentsAndServices( 234 @NotNull CompilerConfiguration configuration, @NotNull CommonCompilerArguments arguments, @NotNull Services services 235 ) { 236 CompilerJarLocator locator = services.get(CompilerJarLocator.class); 237 if (locator != null) { 238 configuration.put(CLIConfigurationKeys.COMPILER_JAR_LOCATOR, locator); 239 } 240 241 if (arguments.languageVersion != null) { 242 LanguageVersion languageFeatureSettings = LanguageVersion.fromVersionString(arguments.languageVersion); 243 if (languageFeatureSettings != null) { 244 configuration.put(CommonConfigurationKeys.LANGUAGE_FEATURE_SETTINGS, languageFeatureSettings); 245 } 246 else { 247 List<String> versionStrings = ArraysKt.map(LanguageVersion.values(), new Function1<LanguageVersion, String>() { 248 @Override 249 public String invoke(LanguageVersion version) { 250 return version.getVersionString(); 251 } 252 }); 253 String message = "Unknown language version: " + arguments.languageVersion + "\n" + 254 "Supported language versions: " + StringsKt.join(versionStrings, ", "); 255 configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY).report( 256 CompilerMessageSeverity.ERROR, message, CompilerMessageLocation.NO_LOCATION 257 ); 258 } 259 } 260 } 261 262 private void reportUnknownExtraFlags(@NotNull MessageCollector collector, @NotNull A arguments) { 263 for (String flag : arguments.unknownExtraFlags) { 264 collector.report( 265 CompilerMessageSeverity.WARNING, 266 "Flag is not supported by this version of the compiler: " + flag, 267 CompilerMessageLocation.NO_LOCATION 268 ); 269 } 270 } 271 272 @NotNull 273 protected abstract ExitCode doExecute( 274 @NotNull A arguments, 275 @NotNull Services services, 276 @NotNull MessageCollector messageCollector, 277 @NotNull Disposable rootDisposable 278 ); 279 280 private void printVersionIfNeeded(@NotNull MessageCollector messageCollector, @NotNull A arguments) { 281 if (!arguments.version) return; 282 283 messageCollector.report(CompilerMessageSeverity.INFO, 284 "Kotlin Compiler version " + KotlinVersion.VERSION, 285 CompilerMessageLocation.NO_LOCATION); 286 } 287 288 /** 289 * Useful main for derived command line tools 290 */ 291 public static void doMain(@NotNull CLICompiler compiler, @NotNull String[] args) { 292 // We depend on swing (indirectly through PSI or something), so we want to declare headless mode, 293 // to avoid accidentally starting the UI thread 294 System.setProperty("java.awt.headless", "true"); 295 ExitCode exitCode = doMainNoExit(compiler, args); 296 if (exitCode != OK) { 297 System.exit(exitCode.getCode()); 298 } 299 } 300 301 @SuppressWarnings("UseOfSystemOutOrSystemErr") 302 @NotNull 303 public static ExitCode doMainNoExit(@NotNull CLICompiler compiler, @NotNull String[] args) { 304 try { 305 return compiler.exec(System.err, args); 306 } 307 catch (CompileEnvironmentException e) { 308 System.err.println(e.getMessage()); 309 return INTERNAL_ERROR; 310 } 311 } 312 }