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