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 }