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 }