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 017package org.jetbrains.jet.cli.common; 018 019import com.intellij.openapi.Disposable; 020import com.intellij.openapi.util.Disposer; 021import com.sampullara.cli.Args; 022import org.jetbrains.annotations.NotNull; 023import org.jetbrains.jet.cli.common.messages.*; 024import org.jetbrains.jet.cli.jvm.compiler.CompileEnvironmentException; 025import org.jetbrains.jet.cli.jvm.compiler.CompileEnvironmentUtil; 026import org.jetbrains.jet.config.CompilerConfiguration; 027 028import java.io.PrintStream; 029 030import static org.jetbrains.jet.cli.common.ExitCode.COMPILATION_ERROR; 031import static org.jetbrains.jet.cli.common.ExitCode.INTERNAL_ERROR; 032import static org.jetbrains.jet.cli.common.ExitCode.OK; 033 034public abstract class CLICompiler<A extends CompilerArguments> { 035 036 @NotNull 037 public ExitCode exec(@NotNull PrintStream errStream, @NotNull String... args) { 038 A arguments = createArguments(); 039 if (!parseArguments(errStream, arguments, args)) { 040 return INTERNAL_ERROR; 041 } 042 return exec(errStream, arguments); 043 } 044 045 /** 046 * Returns true if the arguments can be parsed correctly 047 */ 048 protected boolean parseArguments(@NotNull PrintStream errStream, @NotNull A arguments, @NotNull String[] args) { 049 try { 050 arguments.freeArgs = Args.parse(arguments, args); 051 return true; 052 } 053 catch (IllegalArgumentException e) { 054 errStream.println(e.getMessage()); 055 usage(errStream); 056 } 057 catch (Throwable t) { 058 // Always use tags 059 errStream.println(MessageRenderer.TAGS.renderException(t)); 060 } 061 return false; 062 } 063 064 /** 065 * Allow derived classes to add additional command line arguments 066 */ 067 protected void usage(@NotNull PrintStream target) { 068 // We should say something like 069 // Args.usage(target, K2JVMCompilerArguments.class); 070 // but currently cli-parser we are using does not support that 071 // a corresponding patch has been sent to the authors 072 // For now, we are using this: 073 PrintStream oldErr = System.err; 074 System.setErr(target); 075 try { 076 // TODO: use proper argv0 077 Args.usage(createArguments()); 078 } 079 finally { 080 System.setErr(oldErr); 081 } 082 } 083 084 /** 085 * Strategy method to configure the environment, allowing compiler 086 * based tools to customise their own plugins 087 */ 088 //TODO: add parameter annotations when KT-1863 is resolved 089 protected void configureEnvironment(@NotNull CompilerConfiguration configuration, @NotNull A arguments) { 090 configuration.addAll(CLIConfigurationKeys.COMPILER_PLUGINS, arguments.getCompilerPlugins()); 091 } 092 093 @NotNull 094 protected abstract A createArguments(); 095 096 /** 097 * Executes the compiler on the parsed arguments 098 */ 099 @NotNull 100 public ExitCode exec(@NotNull PrintStream errStream, @NotNull A arguments) { 101 if (arguments.isHelp()) { 102 usage(errStream); 103 return OK; 104 } 105 106 MessageRenderer messageRenderer = getMessageRenderer(arguments); 107 errStream.print(messageRenderer.renderPreamble()); 108 109 printVersionIfNeeded(errStream, arguments, messageRenderer); 110 111 PrintingMessageCollector printingCollector = new PrintingMessageCollector(errStream, messageRenderer, arguments.isVerbose()); 112 113 try { 114 return exec(printingCollector, arguments); 115 } 116 finally { 117 errStream.print(messageRenderer.renderConclusion()); 118 } 119 } 120 121 @NotNull 122 public ExitCode exec(@NotNull MessageCollector messageCollector, @NotNull A arguments) { 123 GroupingMessageCollector groupingCollector = new GroupingMessageCollector(messageCollector); 124 try { 125 Disposable rootDisposable = CompileEnvironmentUtil.createMockDisposable(); 126 try { 127 MessageSeverityCollector severityCollector = new MessageSeverityCollector(groupingCollector); 128 ExitCode code = doExecute(arguments, severityCollector, rootDisposable); 129 return severityCollector.anyReported(CompilerMessageSeverity.ERROR) ? COMPILATION_ERROR : code; 130 } 131 finally { 132 Disposer.dispose(rootDisposable); 133 } 134 } 135 catch (Throwable t) { 136 groupingCollector.report(CompilerMessageSeverity.EXCEPTION, MessageRenderer.PLAIN.renderException(t), 137 CompilerMessageLocation.NO_LOCATION); 138 return INTERNAL_ERROR; 139 } 140 finally { 141 groupingCollector.flush(); 142 } 143 } 144 145 //TODO: can't declare parameters as not null due to KT-1863 146 @NotNull 147 protected abstract ExitCode doExecute(A arguments, MessageCollector messageCollector, Disposable rootDisposable); 148 149 //TODO: can we make it private? 150 @NotNull 151 protected MessageRenderer getMessageRenderer(@NotNull A arguments) { 152 return arguments.isTags() ? MessageRenderer.TAGS : MessageRenderer.PLAIN; 153 } 154 155 protected void printVersionIfNeeded(@NotNull PrintStream errStream, 156 @NotNull A arguments, 157 @NotNull MessageRenderer messageRenderer) { 158 if (arguments.isVersion()) { 159 String versionMessage = messageRenderer.render(CompilerMessageSeverity.INFO, 160 "Kotlin Compiler version " + CompilerVersion.VERSION, 161 CompilerMessageLocation.NO_LOCATION); 162 errStream.println(versionMessage); 163 } 164 } 165 166 /** 167 * Useful main for derived command line tools 168 */ 169 public static void doMain(@NotNull CLICompiler compiler, @NotNull String[] args) { 170 // We depend on swing (indirectly through PSI or something), so we want to declare headless mode, 171 // to avoid accidentally starting the UI thread 172 System.setProperty("java.awt.headless", "true"); 173 ExitCode exitCode = doMainNoExit(compiler, args); 174 if (exitCode != OK) { 175 System.exit(exitCode.getCode()); 176 } 177 } 178 179 @NotNull 180 public static ExitCode doMainNoExit(@NotNull CLICompiler compiler, @NotNull String[] args) { 181 try { 182 ExitCode rc = compiler.exec(System.out, args); 183 if (rc != OK) { 184 System.err.println("exec() finished with " + rc + " return code"); 185 } 186 if (Boolean.parseBoolean(System.getProperty("kotlin.print.cmd.args"))) { 187 System.out.println("Command line arguments:"); 188 for (String arg : args) { 189 System.out.println(arg); 190 } 191 } 192 return rc; 193 } 194 catch (CompileEnvironmentException e) { 195 System.err.println(e.getMessage()); 196 return INTERNAL_ERROR; 197 } 198 } 199}