001 /* 002 * Copyright 2010-2013 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.jet.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.text.StringUtil; 024 import com.sampullara.cli.Args; 025 import com.sampullara.cli.ArgumentUtils; 026 import org.jetbrains.annotations.NotNull; 027 import org.jetbrains.jet.cli.common.arguments.CommonCompilerArguments; 028 import org.jetbrains.jet.cli.common.messages.*; 029 import org.jetbrains.jet.cli.jvm.compiler.CompileEnvironmentException; 030 import org.jetbrains.jet.config.CompilerConfiguration; 031 032 import java.io.PrintStream; 033 import java.util.List; 034 035 import static org.jetbrains.jet.cli.common.ExitCode.*; 036 037 public abstract class CLICompiler<A extends CommonCompilerArguments> { 038 039 @NotNull 040 private List<CompilerPlugin> compilerPlugins = Lists.newArrayList(); 041 042 @NotNull 043 public List<CompilerPlugin> getCompilerPlugins() { 044 return compilerPlugins; 045 } 046 047 public void setCompilerPlugins(@NotNull List<CompilerPlugin> compilerPlugins) { 048 this.compilerPlugins = compilerPlugins; 049 } 050 051 @NotNull 052 public ExitCode exec(@NotNull PrintStream errStream, @NotNull String... args) { 053 A arguments = createArguments(); 054 if (!parseArguments(errStream, arguments, args)) { 055 return INTERNAL_ERROR; 056 } 057 return exec(errStream, arguments); 058 } 059 060 /** 061 * Returns true if the arguments can be parsed correctly 062 */ 063 protected boolean parseArguments(@NotNull PrintStream errStream, @NotNull A arguments, @NotNull String[] args) { 064 try { 065 arguments.freeArgs = Args.parse(arguments, args); 066 checkArguments(arguments); 067 return true; 068 } 069 catch (IllegalArgumentException e) { 070 errStream.println(e.getMessage()); 071 usage(errStream); 072 } 073 catch (Throwable t) { 074 // Always use tags 075 errStream.println(MessageRenderer.TAGS.renderException(t)); 076 } 077 return false; 078 } 079 080 protected void checkArguments(@NotNull A argument) { 081 082 } 083 084 /** 085 * Allow derived classes to add additional command line arguments 086 */ 087 protected void usage(@NotNull PrintStream target) { 088 // We should say something like 089 // Args.usage(target, K2JVMCompilerArguments.class); 090 // but currently cli-parser we are using does not support that 091 // a corresponding patch has been sent to the authors 092 // For now, we are using this: 093 PrintStream oldErr = System.err; 094 System.setErr(target); 095 try { 096 // TODO: use proper argv0 097 Args.usage(createArguments()); 098 } 099 finally { 100 System.setErr(oldErr); 101 } 102 } 103 104 /** 105 * Strategy method to configure the environment, allowing compiler 106 * based tools to customise their own plugins 107 */ 108 protected void configureEnvironment(@NotNull CompilerConfiguration configuration, @NotNull A arguments) { 109 configuration.addAll(CLIConfigurationKeys.COMPILER_PLUGINS, compilerPlugins); 110 } 111 112 @NotNull 113 protected abstract A createArguments(); 114 115 /** 116 * Executes the compiler on the parsed arguments 117 */ 118 @NotNull 119 public ExitCode exec(@NotNull PrintStream errStream, @NotNull A arguments) { 120 if (arguments.help) { 121 usage(errStream); 122 return OK; 123 } 124 125 MessageRenderer messageRenderer = getMessageRenderer(arguments); 126 errStream.print(messageRenderer.renderPreamble()); 127 128 printArgumentsIfNeeded(errStream, arguments, messageRenderer); 129 printVersionIfNeeded(errStream, arguments, messageRenderer); 130 131 MessageCollector collector = new PrintingMessageCollector(errStream, messageRenderer, arguments.verbose); 132 133 if (arguments.suppressAllWarnings()) { 134 collector = new FilteringMessageCollector(collector, Predicates.equalTo(CompilerMessageSeverity.WARNING)); 135 } 136 137 try { 138 return exec(collector, arguments); 139 } 140 finally { 141 errStream.print(messageRenderer.renderConclusion()); 142 } 143 } 144 145 @NotNull 146 public ExitCode exec(@NotNull MessageCollector messageCollector, @NotNull A arguments) { 147 GroupingMessageCollector groupingCollector = new GroupingMessageCollector(messageCollector); 148 try { 149 Disposable rootDisposable = Disposer.newDisposable(); 150 try { 151 MessageSeverityCollector severityCollector = new MessageSeverityCollector(groupingCollector); 152 ExitCode code = doExecute(arguments, severityCollector, rootDisposable); 153 return severityCollector.anyReported(CompilerMessageSeverity.ERROR) ? COMPILATION_ERROR : code; 154 } 155 finally { 156 Disposer.dispose(rootDisposable); 157 } 158 } 159 catch (Throwable t) { 160 groupingCollector.report(CompilerMessageSeverity.EXCEPTION, MessageRenderer.PLAIN.renderException(t), 161 CompilerMessageLocation.NO_LOCATION); 162 return INTERNAL_ERROR; 163 } 164 finally { 165 groupingCollector.flush(); 166 } 167 } 168 169 @NotNull 170 protected abstract ExitCode doExecute(@NotNull A arguments, @NotNull MessageCollector messageCollector, @NotNull Disposable rootDisposable); 171 172 //TODO: can we make it private? 173 @NotNull 174 protected MessageRenderer getMessageRenderer(@NotNull A arguments) { 175 return arguments.tags ? MessageRenderer.TAGS : MessageRenderer.PLAIN; 176 } 177 178 protected void printVersionIfNeeded( 179 @NotNull PrintStream errStream, 180 @NotNull A arguments, 181 @NotNull MessageRenderer messageRenderer 182 ) { 183 if (arguments.version) { 184 String versionMessage = messageRenderer.render(CompilerMessageSeverity.INFO, 185 "Kotlin Compiler version " + KotlinVersion.VERSION, 186 CompilerMessageLocation.NO_LOCATION); 187 errStream.println(versionMessage); 188 } 189 } 190 191 private void printArgumentsIfNeeded( 192 @NotNull PrintStream errStream, 193 @NotNull A arguments, 194 @NotNull MessageRenderer messageRenderer 195 ) { 196 if (arguments.printArgs) { 197 String freeArgs = !arguments.freeArgs.isEmpty() ? " " + StringUtil.join(arguments.freeArgs, " ") : ""; 198 199 List<String> argumentsAsList = ArgumentUtils.convertArgumentsToStringList(arguments, createArguments()); 200 String argumentsAsString = StringUtil.join(argumentsAsList, " "); 201 202 String printArgsMessage = messageRenderer.render(CompilerMessageSeverity.INFO, 203 "Invoking compiler " + getClass().getName() + 204 " with arguments " + argumentsAsString + freeArgs, 205 CompilerMessageLocation.NO_LOCATION); 206 errStream.println(printArgsMessage); 207 } 208 } 209 210 /** 211 * Useful main for derived command line tools 212 */ 213 public static void doMain(@NotNull CLICompiler compiler, @NotNull String[] args) { 214 // We depend on swing (indirectly through PSI or something), so we want to declare headless mode, 215 // to avoid accidentally starting the UI thread 216 System.setProperty("java.awt.headless", "true"); 217 ExitCode exitCode = doMainNoExit(compiler, args); 218 if (exitCode != OK) { 219 System.exit(exitCode.getCode()); 220 } 221 } 222 223 @NotNull 224 public static ExitCode doMainNoExit(@NotNull CLICompiler compiler, @NotNull String[] args) { 225 try { 226 ExitCode rc = compiler.exec(System.out, args); 227 if (rc != OK) { 228 System.err.println("exec() finished with " + rc + " return code"); 229 } 230 return rc; 231 } 232 catch (CompileEnvironmentException e) { 233 System.err.println(e.getMessage()); 234 return INTERNAL_ERROR; 235 } 236 } 237 }