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.intellij.util.concurrency.AppExecutorUtil;
024 import com.intellij.util.concurrency.AppScheduledExecutorService;
025 import com.sampullara.cli.Args;
026 import kotlin.Pair;
027 import kotlin.collections.ArraysKt;
028 import kotlin.collections.CollectionsKt;
029 import kotlin.jvm.functions.Function1;
030 import org.fusesource.jansi.AnsiConsole;
031 import org.jetbrains.annotations.NotNull;
032 import org.jetbrains.annotations.Nullable;
033 import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments;
034 import org.jetbrains.kotlin.cli.common.messages.*;
035 import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler;
036 import org.jetbrains.kotlin.cli.jvm.compiler.CompileEnvironmentException;
037 import org.jetbrains.kotlin.cli.jvm.compiler.CompilerJarLocator;
038 import org.jetbrains.kotlin.config.*;
039 import org.jetbrains.kotlin.progress.CompilationCanceledException;
040 import org.jetbrains.kotlin.progress.CompilationCanceledStatus;
041 import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus;
042 import org.jetbrains.kotlin.utils.StringsKt;
043
044 import java.io.PrintStream;
045 import java.util.List;
046 import java.util.Properties;
047
048 import static org.jetbrains.kotlin.cli.common.ExitCode.*;
049
050 public abstract class CLICompiler<A extends CommonCompilerArguments> {
051 static private void setIdeaIoUseFallback() {
052 if (SystemInfo.isWindows) {
053 Properties properties = System.getProperties();
054
055 properties.setProperty("idea.io.use.nio2", Boolean.TRUE.toString());
056
057 if (!(SystemInfo.isJavaVersionAtLeast("1.7") && !"1.7.0-ea".equals(SystemInfo.JAVA_VERSION))) {
058 properties.setProperty("idea.io.use.fallback", Boolean.TRUE.toString());
059 }
060 }
061 }
062
063 @NotNull
064 public ExitCode exec(@NotNull PrintStream errStream, @NotNull String... args) {
065 return exec(errStream, Services.EMPTY, MessageRenderer.PLAIN_RELATIVE_PATHS, args);
066 }
067
068 // Used via reflection in CompilerRunnerUtil#invokeExecMethod and in Eclipse plugin (see KotlinCLICompiler)
069 @SuppressWarnings("UnusedDeclaration")
070 @NotNull
071 public ExitCode execAndOutputXml(@NotNull PrintStream errStream, @NotNull Services services, @NotNull String... args) {
072 return exec(errStream, services, MessageRenderer.XML, args);
073 }
074
075 // Used via reflection in KotlinCompilerBaseTask
076 @SuppressWarnings("UnusedDeclaration")
077 @NotNull
078 public ExitCode execFullPathsInMessages(@NotNull PrintStream errStream, @NotNull String[] args) {
079 return exec(errStream, Services.EMPTY, MessageRenderer.PLAIN_FULL_PATHS, args);
080 }
081
082 @Nullable
083 private A parseArguments(@NotNull PrintStream errStream, @NotNull MessageRenderer messageRenderer, @NotNull String[] args) {
084 try {
085 A arguments = createArguments();
086 parseArguments(args, arguments);
087 return arguments;
088 }
089 catch (IllegalArgumentException e) {
090 errStream.println(e.getMessage());
091 Usage.print(errStream, createArguments(), false);
092 }
093 catch (Throwable t) {
094 errStream.println(messageRenderer.render(
095 CompilerMessageSeverity.EXCEPTION,
096 OutputMessageUtil.renderException(t),
097 CompilerMessageLocation.NO_LOCATION)
098 );
099 }
100 return null;
101 }
102
103 @SuppressWarnings("WeakerAccess") // Used in maven (see KotlinCompileMojoBase.java)
104 public void parseArguments(@NotNull String[] args, @NotNull A arguments) {
105 Pair<List<String>, List<String>> unparsedArgs =
106 CollectionsKt.partition(Args.parse(arguments, args, false), new Function1<String, Boolean>() {
107 @Override
108 public Boolean invoke(String s) {
109 return s.startsWith("-X");
110 }
111 });
112
113 arguments.unknownExtraFlags = unparsedArgs.getFirst();
114 arguments.freeArgs = unparsedArgs.getSecond();
115
116 for (String argument : arguments.freeArgs) {
117 if (argument.startsWith("-")) {
118 throw new IllegalArgumentException("Invalid argument: " + argument);
119 }
120 }
121 }
122
123 @NotNull
124 protected abstract A createArguments();
125
126 @NotNull
127 private ExitCode exec(
128 @NotNull PrintStream errStream,
129 @NotNull Services services,
130 @NotNull MessageRenderer messageRenderer,
131 @NotNull String[] args
132 ) {
133 K2JVMCompiler.Companion.resetInitStartTime();
134
135 A arguments = parseArguments(errStream, messageRenderer, args);
136 if (arguments == null) {
137 return INTERNAL_ERROR;
138 }
139
140 if (arguments.help || arguments.extraHelp) {
141 Usage.print(errStream, createArguments(), arguments.extraHelp);
142 return OK;
143 }
144
145 MessageCollector collector = new PrintingMessageCollector(errStream, messageRenderer, arguments.verbose);
146
147 try {
148 if (PlainTextMessageRenderer.COLOR_ENABLED) {
149 AnsiConsole.systemInstall();
150 }
151
152 errStream.print(messageRenderer.renderPreamble());
153 return exec(collector, services, arguments);
154 }
155 finally {
156 errStream.print(messageRenderer.renderConclusion());
157
158 if (PlainTextMessageRenderer.COLOR_ENABLED) {
159 AnsiConsole.systemUninstall();
160 }
161 }
162 }
163
164 @SuppressWarnings("WeakerAccess") // Used in maven (see KotlinCompileMojoBase.java)
165 @NotNull
166 public ExitCode exec(@NotNull MessageCollector messageCollector, @NotNull Services services, @NotNull A arguments) {
167 printVersionIfNeeded(messageCollector, arguments);
168
169 if (arguments.suppressWarnings) {
170 messageCollector = new FilteringMessageCollector(messageCollector, Predicates.equalTo(CompilerMessageSeverity.WARNING));
171 }
172
173 reportUnknownExtraFlags(messageCollector, arguments);
174
175 GroupingMessageCollector groupingCollector = new GroupingMessageCollector(messageCollector);
176 try {
177 ExitCode exitCode = OK;
178
179 int repeatCount = 1;
180 if (arguments.repeat != null) {
181 try {
182 repeatCount = Integer.parseInt(arguments.repeat);
183 }
184 catch (NumberFormatException ignored) {
185 }
186 }
187
188 CompilationCanceledStatus canceledStatus = services.get(CompilationCanceledStatus.class);
189 ProgressIndicatorAndCompilationCanceledStatus.setCompilationCanceledStatus(canceledStatus);
190
191 for (int i = 0; i < repeatCount; i++) {
192 if (i > 0) {
193 K2JVMCompiler.Companion.resetInitStartTime();
194 }
195 Disposable rootDisposable = Disposer.newDisposable();
196 try {
197 setIdeaIoUseFallback();
198 ExitCode code = doExecute(arguments, services, groupingCollector, rootDisposable);
199 exitCode = groupingCollector.hasErrors() ? COMPILATION_ERROR : code;
200 }
201 catch (CompilationCanceledException e) {
202 messageCollector.report(CompilerMessageSeverity.INFO, "Compilation was canceled", CompilerMessageLocation.NO_LOCATION);
203 return ExitCode.OK;
204 }
205 catch (RuntimeException e) {
206 Throwable cause = e.getCause();
207 if (cause instanceof CompilationCanceledException) {
208 messageCollector
209 .report(CompilerMessageSeverity.INFO, "Compilation was canceled", CompilerMessageLocation.NO_LOCATION);
210 return ExitCode.OK;
211 }
212 else {
213 throw e;
214 }
215 }
216 finally {
217 Disposer.dispose(rootDisposable);
218 }
219 }
220 return exitCode;
221 }
222 catch (Throwable t) {
223 groupingCollector.report(CompilerMessageSeverity.EXCEPTION, OutputMessageUtil.renderException(t),
224 CompilerMessageLocation.NO_LOCATION);
225 return INTERNAL_ERROR;
226 }
227 finally {
228 groupingCollector.flush();
229 }
230 }
231
232 protected static void setupCommonArgumentsAndServices(
233 @NotNull CompilerConfiguration configuration, @NotNull CommonCompilerArguments arguments, @NotNull Services services
234 ) {
235 CompilerJarLocator locator = services.get(CompilerJarLocator.class);
236 if (locator != null) {
237 configuration.put(CLIConfigurationKeys.COMPILER_JAR_LOCATOR, locator);
238 }
239
240 LanguageVersion languageVersion = parseVersion(configuration, arguments.languageVersion, "language");
241 LanguageVersion apiVersion = parseVersion(configuration, arguments.apiVersion, "API");
242 if (languageVersion != null || apiVersion != null) {
243 if (languageVersion == null) {
244 // If only "-api-version" is specified, language version is assumed to be the latest
245 languageVersion = LanguageVersion.LATEST;
246 }
247 if (apiVersion == null) {
248 // If only "-language-version" is specified, API version is assumed to be equal to the language version
249 // (API version cannot be greater than the language version)
250 apiVersion = languageVersion;
251 }
252
253 if (apiVersion.compareTo(languageVersion) > 0) {
254 configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY).report(
255 CompilerMessageSeverity.ERROR,
256 "-api-version (" + apiVersion.getVersionString() + ") cannot be greater than " +
257 "-language-version (" + languageVersion.getVersionString() + ")",
258 CompilerMessageLocation.NO_LOCATION
259 );
260 }
261
262 configuration.put(CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS,
263 new LanguageVersionSettingsImpl(languageVersion, ApiVersion.createByLanguageVersion(apiVersion)));
264 }
265 }
266
267 private static LanguageVersion parseVersion(
268 @NotNull CompilerConfiguration configuration, @Nullable String value, @NotNull String versionOf
269 ) {
270 if (value == null) return null;
271
272 LanguageVersion version = LanguageVersion.fromVersionString(value);
273 if (version != null) {
274 return version;
275 }
276
277 List<String> versionStrings = ArraysKt.map(LanguageVersion.values(), new Function1<LanguageVersion, String>() {
278 @Override
279 public String invoke(LanguageVersion version) {
280 return version.getVersionString();
281 }
282 });
283 String message = "Unknown " + versionOf + " version: " + value + "\n" +
284 "Supported " + versionOf + " versions: " + StringsKt.join(versionStrings, ", ");
285 configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY).report(
286 CompilerMessageSeverity.ERROR, message, CompilerMessageLocation.NO_LOCATION
287 );
288
289 return null;
290 }
291
292 private void reportUnknownExtraFlags(@NotNull MessageCollector collector, @NotNull A arguments) {
293 for (String flag : arguments.unknownExtraFlags) {
294 collector.report(
295 CompilerMessageSeverity.WARNING,
296 "Flag is not supported by this version of the compiler: " + flag,
297 CompilerMessageLocation.NO_LOCATION
298 );
299 }
300 }
301
302 @NotNull
303 protected abstract ExitCode doExecute(
304 @NotNull A arguments,
305 @NotNull Services services,
306 @NotNull MessageCollector messageCollector,
307 @NotNull Disposable rootDisposable
308 );
309
310 private void printVersionIfNeeded(@NotNull MessageCollector messageCollector, @NotNull A arguments) {
311 if (!arguments.version) return;
312
313 messageCollector.report(CompilerMessageSeverity.INFO,
314 "Kotlin Compiler version " + KotlinCompilerVersion.VERSION,
315 CompilerMessageLocation.NO_LOCATION);
316 }
317
318 /**
319 * Useful main for derived command line tools
320 */
321 public static void doMain(@NotNull CLICompiler compiler, @NotNull String[] args) {
322 // We depend on swing (indirectly through PSI or something), so we want to declare headless mode,
323 // to avoid accidentally starting the UI thread
324 System.setProperty("java.awt.headless", "true");
325 try {
326 ExitCode exitCode = doMainNoExit(compiler, args);
327
328 if (exitCode != OK) {
329 System.exit(exitCode.getCode());
330 }
331 }
332 finally {
333 AppScheduledExecutorService service = (AppScheduledExecutorService) AppExecutorUtil.getAppScheduledExecutorService();
334 service.shutdownAppScheduledExecutorService();
335 }
336 }
337
338 @SuppressWarnings("UseOfSystemOutOrSystemErr")
339 @NotNull
340 public static ExitCode doMainNoExit(@NotNull CLICompiler compiler, @NotNull String[] args) {
341 try {
342 return compiler.exec(System.err, args);
343 }
344 catch (CompileEnvironmentException e) {
345 System.err.println(e.getMessage());
346 return INTERNAL_ERROR;
347 }
348 }
349 }