From eadb98af53e5e57e1aacc4fc37c7abf803b59948 Mon Sep 17 00:00:00 2001 From: pTalanov Date: Sat, 28 Apr 2012 16:05:09 +0400 Subject: [PATCH] Extract some of the JetCompiler functionality to CompilerUtils and CompilerEnvironment classes. --- .../plugin/compiler/CompilerEnvironment.java | 79 ++++ .../jet/plugin/compiler/CompilerUtils.java | 339 ++++++++++++++ .../jet/plugin/compiler/JetCompiler.java | 428 ++++-------------- 3 files changed, 511 insertions(+), 335 deletions(-) create mode 100644 idea/src/org/jetbrains/jet/plugin/compiler/CompilerEnvironment.java create mode 100644 idea/src/org/jetbrains/jet/plugin/compiler/CompilerUtils.java diff --git a/idea/src/org/jetbrains/jet/plugin/compiler/CompilerEnvironment.java b/idea/src/org/jetbrains/jet/plugin/compiler/CompilerEnvironment.java new file mode 100644 index 00000000000..a8b00bf467b --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/compiler/CompilerEnvironment.java @@ -0,0 +1,79 @@ +/* + * Copyright 2010-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jet.plugin.compiler; + +import com.intellij.openapi.compiler.CompileContext; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.vfs.VirtualFile; +import jet.Function1; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.utils.PathUtil; + +import java.io.*; + +import static com.intellij.openapi.compiler.CompilerMessageCategory.ERROR; + +/** + * @author Pavel Talanov + */ +public final class CompilerEnvironment { + + @NotNull + public static CompilerEnvironment getEnvironmentFor(@NotNull CompileContext compileContext, @NotNull Module module, boolean tests) { + VirtualFile mainOutput = compileContext.getModuleOutputDirectory(module); + final VirtualFile outputDir = tests ? compileContext.getModuleOutputDirectoryForTests(module) : mainOutput; + File kotlinHome = PathUtil.getDefaultCompilerPath(); + return new CompilerEnvironment(kotlinHome, outputDir); + } + + @Nullable + private final File kotlinHome; + @Nullable + private final VirtualFile output; + + public CompilerEnvironment(@Nullable File home, @Nullable VirtualFile output) { + this.kotlinHome = home; + this.output = output; + } + + public boolean success() { + return kotlinHome != null && output != null; + } + + @NotNull + public File getKotlinHome() { + assert kotlinHome != null; + return kotlinHome; + } + + @NotNull + public VirtualFile getOutput() { + assert output != null; + return output; + } + + public void reportErrorsTo(@NotNull CompileContext compileContext) { + if (output == null) { + compileContext.addMessage(ERROR, "[Internal Error] No output directory", "", -1, -1); + } + if (kotlinHome == null) { + compileContext.addMessage(ERROR, "Cannot find kotlinc home. Make sure plugin is properly installed", "", -1, -1); + } + } + +} diff --git a/idea/src/org/jetbrains/jet/plugin/compiler/CompilerUtils.java b/idea/src/org/jetbrains/jet/plugin/compiler/CompilerUtils.java new file mode 100644 index 00000000000..b8a2d62b782 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/compiler/CompilerUtils.java @@ -0,0 +1,339 @@ +/* + * Copyright 2010-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jet.plugin.compiler; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.intellij.compiler.impl.javaCompiler.OutputItemImpl; +import com.intellij.openapi.compiler.CompileContext; +import com.intellij.openapi.compiler.CompilerMessageCategory; +import com.intellij.openapi.compiler.TranslatingCompiler; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import jet.Function1; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import java.io.*; +import java.lang.ref.SoftReference; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.*; + +import static com.intellij.openapi.compiler.CompilerMessageCategory.*; + +/** + * @author Pavel Talanov + */ +public final class CompilerUtils { + private static SoftReference ourClassLoaderRef = new SoftReference(null); + static final Logger LOG = Logger.getInstance("#org.jetbrains.jet.plugin.compiler.CompilerUtils"); + + private CompilerUtils() { + } + + private static List getJarsInDirectory(File dir) { + List r = Lists.newArrayList(); + + File[] files = dir.listFiles(); + if (files != null) { + for (File jar : files) { + if (jar.isFile() && jar.getName().endsWith(".jar")) { + r.add(jar); + } + } + } + + return r; + } + + public static List kompilerClasspath(File kotlinHome, CompileContext context) { + File libs = new File(kotlinHome, "lib"); + + if (!libs.exists() || libs.isFile()) { + context.addMessage(ERROR, "Broken compiler at '" + libs.getAbsolutePath() + "'. Make sure plugin is properly installed", "", -1, + -1); + return Collections.emptyList(); + } + + + ArrayList answer = new ArrayList(); + answer.addAll(getJarsInDirectory(libs)); + answer.addAll(getJarsInDirectory(new File(libs, "guice"))); // TODO: flatten at artifact build + return answer; + } + + public static URLClassLoader getOrCreateClassLoader(File kotlinHome, CompileContext context) { + URLClassLoader answer = ourClassLoaderRef.get(); + if (answer == null) { + answer = createClassloader(kotlinHome, context); + ourClassLoaderRef = new SoftReference(answer); + } + return answer; + } + + private static URLClassLoader createClassloader(File kotlinHome, CompileContext context) { + List jars = kompilerClasspath(kotlinHome, context); + URL[] urls = new URL[jars.size()]; + for (int i = 0; i < urls.length; i++) { + try { + urls[i] = jars.get(i).toURI().toURL(); + } + catch (MalformedURLException e) { + throw new RuntimeException(e); // Checked exceptions are great! I love them, and I love brilliant library designers too! + } + } + return new URLClassLoader(urls, null); + } + + static void handleProcessTermination(int exitCode, CompileContext compileContext) { + if (exitCode != 0 && exitCode != 1) { + compileContext.addMessage(ERROR, "Compiler terminated with exit code: " + exitCode, "", -1, -1); + } + } + + public static void parseCompilerMessagesFromReader(CompileContext compileContext, final Reader reader, OutputItemsCollector collector) { + // Sometimes the compiler can't output valid XML + // Example: error in command line arguments passed to the compiler + // having no -tags key (arguments are not parsed), the compiler doesn't know + // if it should put any tags in the output, so it will simply print the usage + // and the SAX parser will break. + // In this case, we want to read everything from this stream + // and report it as an IDE error. + final StringBuilder stringBuilder = new StringBuilder(); + //noinspection IOResourceOpenedButNotSafelyClosed + Reader wrappingReader = new Reader() { + + @Override + public int read(char[] cbuf, int off, int len) throws IOException { + int read = reader.read(cbuf, off, len); + stringBuilder.append(cbuf, off, len); + return read; + } + + @Override + public void close() throws IOException { + // Do nothing: + // If the SAX parser sees a syntax error, it throws an exception + // and calls close() on the reader. + // We prevent hte reader from being closed here, and close it later, + // when all the text is read from it + } + }; + try { + SAXParserFactory factory = SAXParserFactory.newInstance(); + SAXParser parser = factory.newSAXParser(); + parser.parse(new InputSource(wrappingReader), new CompilerOutputSAXHandler(compileContext, collector)); + } + catch (Throwable e) { + + // Load all the text into the stringBuilder + try { + // This will not close the reader (see the wrapper above) + FileUtil.loadTextAndClose(wrappingReader); + } + catch (IOException ioException) { + LOG.error(ioException); + } + String message = stringBuilder.toString(); + LOG.error(message); + LOG.error(e); + compileContext.addMessage(ERROR, message, null, -1, -1); + } + finally { + try { + reader.close(); + } + catch (IOException e) { + LOG.error(e); + } + } + } + + public static int getReturnCodeFromObject(Object rc) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { + if ("org.jetbrains.jet.cli.common.ExitCode".equals(rc.getClass().getCanonicalName())) { + return (Integer)rc.getClass().getMethod("getCode").invoke(rc); + } + else { + throw new IllegalStateException("Unexpected return: " + rc); + } + } + + public static Object invokeExecMethod(CompilerEnvironment environment, + PrintStream out, + CompileContext context, String[] arguments, String name) throws Exception { + URLClassLoader loader = getOrCreateClassLoader(environment.getKotlinHome(), context); + Class kompiler = Class.forName(name, true, loader); + Method exec = kompiler.getMethod("exec", PrintStream.class, String[].class); + return exec.invoke(kompiler.newInstance(), out, arguments); + } + + private static class CompilerOutputSAXHandler extends DefaultHandler { + private static final Map CATEGORIES = ImmutableMap.builder() + .put("error", CompilerMessageCategory.ERROR) + .put("warning", CompilerMessageCategory.WARNING) + .put("logging", CompilerMessageCategory.STATISTICS) + .put("exception", CompilerMessageCategory.ERROR) + .put("info", CompilerMessageCategory.INFORMATION) + .put("messages", CompilerMessageCategory.INFORMATION) // Root XML element + .build(); + + private final CompileContext compileContext; + private final OutputItemsCollector collector; + + private final StringBuilder message = new StringBuilder(); + private Stack tags = new Stack(); + private String path; + private int line; + private int column; + + public CompilerOutputSAXHandler(CompileContext compileContext, OutputItemsCollector collector) { + this.compileContext = compileContext; + this.collector = collector; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + tags.push(qName); + + message.setLength(0); + + String rawPath = attributes.getValue("path"); + path = rawPath == null ? null : "file://" + rawPath; + line = safeParseInt(attributes.getValue("line"), -1); + column = safeParseInt(attributes.getValue("column"), -1); + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + if (tags.size() == 1) { + // We're directly inside the root tag: + String message = new String(ch, start, length); + if (!message.trim().isEmpty()) { + compileContext.addMessage(ERROR, "Unhandled compiler output: " + message, null, -1, -1); + } + } + else { + message.append(ch, start, length); + } + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + if (tags.size() == 1) { + // We're directly inside the root tag: + return; + } + String qNameLowerCase = qName.toLowerCase(); + CompilerMessageCategory category = CATEGORIES.get(qNameLowerCase); + if (category == null) { + compileContext.addMessage(ERROR, "Unknown compiler message tag: " + qName, null, -1, -1); + category = INFORMATION; + } + String text = message.toString(); + + if ("exception".equals(qNameLowerCase)) { + LOG.error(text); + } + + if (category == STATISTICS) { + compileContext.getProgressIndicator().setText(text); + collector.learn(text); + } + else { + compileContext.addMessage(category, text, path, line, column); + } + tags.pop(); + } + + private static int safeParseInt(@Nullable String value, int defaultValue) { + if (value == null) { + return defaultValue; + } + try { + return Integer.parseInt(value.trim()); + } + catch (NumberFormatException e) { + return defaultValue; + } + } + } + + public static class OutputItemsCollectorImpl implements OutputItemsCollector { + private static final String FOR_SOURCE_PREFIX = "For source: "; + private static final String EMITTING_PREFIX = "Emitting: "; + private final String outputPath; + private VirtualFile currentSource; + private List answer = new ArrayList(); + private List sources = new ArrayList(); + + public OutputItemsCollectorImpl(String outputPath) { + this.outputPath = outputPath; + } + + @Override + public void learn(String message) { + message = message.trim(); + if (message.startsWith(FOR_SOURCE_PREFIX)) { + currentSource = LocalFileSystem.getInstance().findFileByPath(message.substring(FOR_SOURCE_PREFIX.length())); + if (currentSource != null) { + sources.add(currentSource); + } + } + else if (message.startsWith(EMITTING_PREFIX)) { + if (currentSource != null) { + OutputItemImpl item = new OutputItemImpl(outputPath + "/" + message.substring(EMITTING_PREFIX.length()), currentSource); + LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(item.getOutputPath())); + answer.add(item); + } + } + } + + public List getOutputs() { + return answer; + } + + public List getSources() { + return sources; + } + } + + public static void outputCompilerMessagesAndHandleExitCode(@NotNull CompileContext context, + @NotNull OutputItemsCollector collector, + @NotNull Function1 compilerRun) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(outputStream); + + int exitCode = compilerRun.invoke(out); + + BufferedReader reader = new BufferedReader(new StringReader(outputStream.toString())); + parseCompilerMessagesFromReader(context, reader, collector); + handleProcessTermination(exitCode, context); + } +} + diff --git a/idea/src/org/jetbrains/jet/plugin/compiler/JetCompiler.java b/idea/src/org/jetbrains/jet/plugin/compiler/JetCompiler.java index a037ec1f356..72cb9695e43 100644 --- a/idea/src/org/jetbrains/jet/plugin/compiler/JetCompiler.java +++ b/idea/src/org/jetbrains/jet/plugin/compiler/JetCompiler.java @@ -16,20 +16,15 @@ package org.jetbrains.jet.plugin.compiler; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.intellij.compiler.impl.javaCompiler.ModuleChunk; -import com.intellij.compiler.impl.javaCompiler.OutputItemImpl; import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.configurations.SimpleJavaParameters; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.compiler.CompileContext; import com.intellij.openapi.compiler.CompileScope; -import com.intellij.openapi.compiler.CompilerMessageCategory; import com.intellij.openapi.compiler.TranslatingCompiler; import com.intellij.openapi.compiler.ex.CompileContextEx; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.JavaSdkType; @@ -37,37 +32,27 @@ import com.intellij.openapi.projectRoots.JdkUtil; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.projectRoots.SimpleJavaSdkType; import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Chunk; import com.intellij.util.SystemProperties; +import jet.Function1; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.plugin.JetFileType; import org.jetbrains.jet.plugin.project.JsModuleDetector; -import org.jetbrains.jet.utils.PathUtil; -import org.xml.sax.Attributes; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; -import java.io.*; -import java.lang.ref.SoftReference; -import java.lang.reflect.Method; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; import java.util.*; -import static com.intellij.openapi.compiler.CompilerMessageCategory.*; +import static com.intellij.openapi.compiler.CompilerMessageCategory.ERROR; +import static com.intellij.openapi.compiler.CompilerMessageCategory.INFORMATION; /** * @author yole */ public class JetCompiler implements TranslatingCompiler { - private static final Logger LOG = Logger.getInstance("#org.jetbrains.jet.plugin.compiler.JetCompiler"); private static final boolean RUN_OUT_OF_PROCESS = false; @@ -95,7 +80,10 @@ public class JetCompiler implements TranslatingCompiler { } @Override - public void compile(final CompileContext compileContext, Chunk moduleChunk, final VirtualFile[] virtualFiles, OutputSink outputSink) { + public void compile(final CompileContext compileContext, + Chunk moduleChunk, + final VirtualFile[] virtualFiles, + OutputSink outputSink) { if (virtualFiles.length == 0) return; List productionFiles = new ArrayList(); @@ -116,23 +104,50 @@ public class JetCompiler implements TranslatingCompiler { doCompile(compileContext, moduleChunk, testFiles, module, outputSink, true); } - private void doCompile(CompileContext compileContext, Chunk moduleChunk, List files, Module module, OutputSink outputSink, boolean tests) { + private void doCompile(CompileContext compileContext, + Chunk moduleChunk, + List files, + Module module, + OutputSink outputSink, + boolean tests) { if (files.isEmpty()) return; - VirtualFile mainOutput = compileContext.getModuleOutputDirectory(module); - final VirtualFile outputDir = tests ? compileContext.getModuleOutputDirectoryForTests(module) : mainOutput; - if (outputDir == null) { - compileContext.addMessage(ERROR, "[Internal Error] No output directory", "", -1, -1); + CompilerEnvironment environment = CompilerEnvironment.getEnvironmentFor(compileContext, module, tests); + if (!environment.success()) { + environment.reportErrorsTo(compileContext); return; } - File kotlinHome = PathUtil.getDefaultCompilerPath(); - if (kotlinHome == null) { - compileContext.addMessage(ERROR, "Cannot find kotlinc home. Make sure plugin is properly installed", "", -1, -1); - return; - } + File scriptFile = + tryToWriteScriptFile(compileContext, moduleChunk, files, module, tests, compileContext.getModuleOutputDirectory(module), + environment.getOutput()); - ModuleChunk chunk = new ModuleChunk((CompileContextEx) compileContext, moduleChunk, Collections.>emptyMap()); + if (scriptFile == null) return; + + CompilerUtils.OutputItemsCollectorImpl collector = new CompilerUtils.OutputItemsCollectorImpl(environment.getOutput().getPath()); + runCompiler(compileContext, environment, scriptFile, collector); + outputSink.add(environment.getOutput().getPath(), collector.getOutputs(), collector.getSources().toArray(VirtualFile.EMPTY_ARRAY)); + } + + private void runCompiler(CompileContext compileContext, + CompilerEnvironment environment, + File scriptFile, + CompilerUtils.OutputItemsCollectorImpl collector) { + if (RUN_OUT_OF_PROCESS) { + runOutOfProcess(compileContext, collector, environment, scriptFile); + } + else { + runInProcess(compileContext, collector, environment, scriptFile); + } + } + + private static File tryToWriteScriptFile(CompileContext compileContext, + Chunk moduleChunk, + List files, + Module module, + boolean tests, VirtualFile mainOutput, VirtualFile outputDir) { + ModuleChunk + chunk = new ModuleChunk((CompileContextEx)compileContext, moduleChunk, Collections.>emptyMap()); String moduleName = moduleChunk.getNodes().iterator().next().getName(); // Filter the output we are writing to @@ -145,26 +160,20 @@ public class JetCompiler implements TranslatingCompiler { File scriptFile = new File(path(outputDir), "script.kts"); try { FileUtil.writeToFile(scriptFile, script.toString()); - } catch (IOException e) { + } + catch (IOException e) { compileContext.addMessage(ERROR, "[Internal Error] Cannot write script to " + scriptFile.getAbsolutePath(), "", -1, -1); - return; + return null; } - - OutputItemsCollectorImpl collector = new OutputItemsCollectorImpl(outputDir.getPath()); - - if (RUN_OUT_OF_PROCESS) { - runOutOfProcess(compileContext, collector, outputDir, kotlinHome, scriptFile); - } - else { - runInProcess(compileContext, collector, outputDir, kotlinHome, scriptFile); - } - - outputSink.add(outputDir.getPath(), collector.getOutputs(), collector.getSources().toArray(VirtualFile.EMPTY_ARRAY)); -// System.out.println("Generated module script:\n" + script.toString()); -// compileContext.addMessage(INFORMATION, "Generated module script:\n" + script.toString(), "file://" + path(mainOutput), 0, 1); + return scriptFile; } - private static CharSequence generateModuleScript(String moduleName, ModuleChunk chunk, List files, boolean tests, VirtualFile mainOutput, Set directoriesToFilterOut) { + private static CharSequence generateModuleScript(String moduleName, + ModuleChunk chunk, + List files, + boolean tests, + VirtualFile mainOutput, + Set directoriesToFilterOut) { StringBuilder script = new StringBuilder(); if (tests) { @@ -217,197 +226,76 @@ public class JetCompiler implements TranslatingCompiler { return script; } - private static List getJarsInDirectory(File dir) { - List r = Lists.newArrayList(); - - File[] files = dir.listFiles(); - if (files != null) { - for (File jar : files) { - if (jar.isFile() && jar.getName().endsWith(".jar")) { - r.add(jar); - } - } - } - - return r; - } - - private static List kompilerClasspath(File kotlinHome, CompileContext context) { - File libs = new File(kotlinHome, "lib"); - - if (!libs.exists() || libs.isFile()) { - context.addMessage(ERROR, "Broken compiler at '" + libs.getAbsolutePath() + "'. Make sure plugin is properly installed", "", -1, -1); - return Collections.emptyList(); - } - - - ArrayList answer = new ArrayList(); - answer.addAll(getJarsInDirectory(libs)); - answer.addAll(getJarsInDirectory(new File(libs, "guice"))); // TODO: flatten at artifact build - return answer; - } - - private void runInProcess(final CompileContext compileContext, OutputItemsCollector collector, VirtualFile outputDir, File kotlinHome, File scriptFile) { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - PrintStream out = new PrintStream(outputStream); - - int exitCode = execInProcess(kotlinHome, outputDir, scriptFile, out, compileContext); - - BufferedReader reader = new BufferedReader(new StringReader(outputStream.toString())); - parseCompilerMessagesFromReader(compileContext, reader, collector); - handleProcessTermination(exitCode, compileContext); - } - - private static void parseCompilerMessagesFromReader(CompileContext compileContext, final Reader reader, OutputItemsCollector collector) { - // Sometimes the compiler can't output valid XML - // Example: error in command line arguments passed to the compiler - // having no -tags key (arguments are not parsed), the compiler doesn't know - // if it should put any tags in the output, so it will simply print the usage - // and the SAX parser will break. - // In this case, we want to read everything from this stream - // and report it as an IDE error. - final StringBuilder stringBuilder = new StringBuilder(); - //noinspection IOResourceOpenedButNotSafelyClosed - Reader wrappingReader = new Reader() { - + private static void runInProcess(final CompileContext compileContext, + OutputItemsCollector collector, + final CompilerEnvironment environment, + final File scriptFile) { + CompilerUtils.outputCompilerMessagesAndHandleExitCode(compileContext, collector, new Function1() { @Override - public int read(char[] cbuf, int off, int len) throws IOException { - int read = reader.read(cbuf, off, len); - stringBuilder.append(cbuf, off, len); - return read; + public Integer invoke(PrintStream stream) { + return execInProcess(environment, scriptFile, stream, compileContext); } - - @Override - public void close() throws IOException { - // Do nothing: - // If the SAX parser sees a syntax error, it throws an expcetion - // and calls close() on the reader. - // We prevent hte reader from being closed here, and close it later, - // when all the text is read from it - } - }; - try { - SAXParserFactory factory = SAXParserFactory.newInstance(); - SAXParser parser = factory.newSAXParser(); - parser.parse(new InputSource(wrappingReader), new CompilerOutputSAXHandler(compileContext, collector)); - } - catch (Throwable e) { - - // Load all the text into the stringBuilder - try { - // This will not close the reader (see the wrapper above) - FileUtil.loadTextAndClose(wrappingReader); - } - catch (IOException ioException) { - LOG.error(ioException); - } - String message = stringBuilder.toString(); - LOG.error(message); - LOG.error(e); - compileContext.addMessage(ERROR, message, null, -1, -1); - } - finally { - try { - reader.close(); - } - catch (IOException e) { - LOG.error(e); - } - } + }); } - private static void handleProcessTermination(int exitCode, CompileContext compileContext) { - if (exitCode != 0 && exitCode != 1) { - compileContext.addMessage(ERROR, "Compiler terminated with exit code: " + exitCode, "", -1, -1); - } - } - - private static int execInProcess(File kotlinHome, VirtualFile outputDir, File scriptFile, PrintStream out, CompileContext context) { - URLClassLoader loader = getOrCreateClassLoader(kotlinHome, context); + private static int execInProcess(CompilerEnvironment environment, File scriptFile, PrintStream out, CompileContext context) { try { String compilerClassName = "org.jetbrains.jet.cli.jvm.K2JVMCompiler"; - Class kompiler = Class.forName(compilerClassName, true, loader); - Method exec = kompiler.getMethod("exec", PrintStream.class, String[].class); - - String[] arguments = commandLineArguments(outputDir, scriptFile); - - context.addMessage(INFORMATION, "Using kotlinHome=" + kotlinHome, "", -1, -1); - context.addMessage(INFORMATION, "Invoking in-process compiler " + compilerClassName + " with arguments " + Arrays.asList(arguments), "", -1, -1); - - Object rc = exec.invoke(kompiler.newInstance(), out, arguments); + String[] arguments = commandLineArguments(environment.getOutput(), scriptFile); + context.addMessage(INFORMATION, "Using kotlinHome=" + environment.getKotlinHome(), "", -1, -1); + context.addMessage(INFORMATION, + "Invoking in-process compiler " + compilerClassName + " with arguments " + Arrays.asList(arguments), "", -1, + -1); + Object rc = CompilerUtils.invokeExecMethod(environment, out, context, arguments, compilerClassName); // exec() returns a K2JVMCompiler.ExitCode object, that class is not accessible here, // so we take it's contents through reflection - if ("org.jetbrains.jet.cli.common.ExitCode".equals(rc.getClass().getCanonicalName())) { - return (Integer) rc.getClass().getMethod("getCode").invoke(rc); - } - else { - throw new IllegalStateException("Unexpected return: " + rc); - } - } catch (Throwable e) { - LOG.error(e); + return CompilerUtils.getReturnCodeFromObject(rc); + } + catch (Throwable e) { + CompilerUtils.LOG.error(e); return -1; } } private static String[] commandLineArguments(VirtualFile outputDir, File scriptFile) { - return new String[]{ "-module", scriptFile.getAbsolutePath(), "-output", path(outputDir), "-tags", "-verbose", "-version", "-mode", "stdlib" }; + return new String[]{"-module", scriptFile.getAbsolutePath(), "-output", path(outputDir), "-tags", "-verbose", "-version", "-mode", + "stdlib"}; } - private static SoftReference ourClassLoaderRef = new SoftReference(null); - - private static URLClassLoader getOrCreateClassLoader(File kotlinHome, CompileContext context) { - URLClassLoader answer = ourClassLoaderRef.get(); - if (answer == null) { - answer = createClassloader(kotlinHome, context); - ourClassLoaderRef = new SoftReference(answer); - } - return answer; - } - - private static URLClassLoader createClassloader(File kotlinHome, CompileContext context) { - List jars = kompilerClasspath(kotlinHome, context); - URL[] urls = new URL[jars.size()]; - for (int i = 0; i < urls.length; i++) { - try { - urls[i] = jars.get(i).toURI().toURL(); - } catch (MalformedURLException e) { - throw new RuntimeException(e); // Checked exceptions are great! I love them, and I love brilliant library designers too! - } - } - - return new URLClassLoader(urls, null); - } - - private static void runOutOfProcess(final CompileContext compileContext, final OutputItemsCollector collector, VirtualFile outputDir, File kotlinHome, File scriptFile) { + private static void runOutOfProcess(final CompileContext compileContext, + final OutputItemsCollector collector, + CompilerEnvironment environment, + File scriptFile) { final SimpleJavaParameters params = new SimpleJavaParameters(); params.setJdk(new SimpleJavaSdkType().createJdk("tmp", SystemProperties.getJavaHome())); params.setMainClass("org.jetbrains.jet.cli.jvm.K2JVMCompiler"); - for (String arg : commandLineArguments(outputDir, scriptFile)) { + for (String arg : commandLineArguments(environment.getOutput(), scriptFile)) { params.getProgramParametersList().add(arg); } - for (File jar : kompilerClasspath(kotlinHome, compileContext)) { + for (File jar : CompilerUtils.kompilerClasspath(environment.getKotlinHome(), compileContext)) { params.getClassPath().add(jar); } params.getVMParametersList().addParametersString("-Djava.awt.headless=true -Xmx512m"); -// params.getVMParametersList().addParametersString("-agentlib:yjpagent=sampling"); + // params.getVMParametersList().addParametersString("-agentlib:yjpagent=sampling"); Sdk sdk = params.getJdk(); final GeneralCommandLine commandLine = JdkUtil.setupJVMCommandLine( - ((JavaSdkType) sdk.getSdkType()).getVMExecutablePath(sdk), params, false); - + ((JavaSdkType)sdk.getSdkType()).getVMExecutablePath(sdk), params, false); + compileContext.addMessage(INFORMATION, "Invoking out-of-process compiler with arguments: " + commandLine, "", -1, -1); - + try { final Process process = commandLine.createProcess(); ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { @Override public void run() { - parseCompilerMessagesFromReader(compileContext, new InputStreamReader(process.getInputStream()), collector); + CompilerUtils + .parseCompilerMessagesFromReader(compileContext, new InputStreamReader(process.getInputStream()), collector); } }); @@ -424,7 +312,7 @@ public class JetCompiler implements TranslatingCompiler { }); int exitCode = process.waitFor(); - handleProcessTermination(exitCode, compileContext); + CompilerUtils.handleProcessTermination(exitCode, compileContext); } catch (Exception e) { compileContext.addMessage(ERROR, "[Internal Error] " + e.getLocalizedMessage(), "", -1, -1); @@ -432,45 +320,6 @@ public class JetCompiler implements TranslatingCompiler { } } - public static class OutputItemsCollectorImpl implements OutputItemsCollector { - private static final String FOR_SOURCE_PREFIX = "For source: "; - private static final String EMITTING_PREFIX = "Emitting: "; - private final String outputPath; - private VirtualFile currentSource; - private List answer = new ArrayList(); - private List sources = new ArrayList(); - - public OutputItemsCollectorImpl(String outputPath) { - this.outputPath = outputPath; - } - - @Override - public void learn(String message) { - message = message.trim(); - if (message.startsWith(FOR_SOURCE_PREFIX)) { - currentSource = LocalFileSystem.getInstance().findFileByPath(message.substring(FOR_SOURCE_PREFIX.length())); - if (currentSource != null) { - sources.add(currentSource); - } - } - else if (message.startsWith(EMITTING_PREFIX)) { - if (currentSource != null) { - OutputItemImpl item = new OutputItemImpl(outputPath + "/" + message.substring(EMITTING_PREFIX.length()), currentSource); - LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(item.getOutputPath())); - answer.add(item); - } - } - } - - public List getOutputs() { - return answer; - } - - public List getSources() { - return sources; - } - } - private static String path(VirtualFile root) { String path = root.getPath(); if (path.endsWith("!/")) { @@ -479,95 +328,4 @@ public class JetCompiler implements TranslatingCompiler { return path; } - - private static class CompilerOutputSAXHandler extends DefaultHandler { - private static final Map CATEGORIES = ImmutableMap.builder() - .put("error", CompilerMessageCategory.ERROR) - .put("warning", CompilerMessageCategory.WARNING) - .put("logging", CompilerMessageCategory.STATISTICS) - .put("exception", CompilerMessageCategory.ERROR) - .put("info", CompilerMessageCategory.INFORMATION) - .put("messages", CompilerMessageCategory.INFORMATION) // Root XML element - .build(); - - private final CompileContext compileContext; - private final OutputItemsCollector collector; - - private final StringBuilder message = new StringBuilder(); - private Stack tags = new Stack(); - private String path; - private int line; - private int column; - - public CompilerOutputSAXHandler(CompileContext compileContext, OutputItemsCollector collector) { - this.compileContext = compileContext; - this.collector = collector; - } - - @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - tags.push(qName); - - message.setLength(0); - - String rawPath = attributes.getValue("path"); - path = rawPath == null ? null : "file://" + rawPath; - line = safeParseInt(attributes.getValue("line"), -1); - column = safeParseInt(attributes.getValue("column"), -1); - } - - @Override - public void characters(char[] ch, int start, int length) throws SAXException { - if (tags.size() == 1) { - // We're directly inside the root tag: - String message = new String(ch, start, length); - if (!message.trim().isEmpty()) { - compileContext.addMessage(ERROR, "Unhandled compiler output: " + message, null, -1, -1); - } - } - else { - message.append(ch, start, length); - } - } - - @Override - public void endElement(String uri, String localName, String qName) throws SAXException { - if (tags.size() == 1) { - // We're directly inside the root tag: - return; - } - String qNameLowerCase = qName.toLowerCase(); - CompilerMessageCategory category = CATEGORIES.get(qNameLowerCase); - if (category == null) { - compileContext.addMessage(ERROR, "Unknown compiler message tag: " + qName, null, -1, -1); - category = INFORMATION; - } - String text = message.toString(); - - if ("exception".equals(qNameLowerCase)) { - LOG.error(text); - } - - if (category == STATISTICS) { - compileContext.getProgressIndicator().setText(text); - collector.learn(text); - } - else { - compileContext.addMessage(category, text, path, line, column); - } - tags.pop(); - } - - private static int safeParseInt(@Nullable String value, int defaultValue) { - if (value == null) { - return defaultValue; - } - try { - return Integer.parseInt(value.trim()); - } - catch (NumberFormatException e) { - return defaultValue; - } - } - } }