diff --git a/compiler/cli/src/org/jetbrains/jet/cli/KotlinCompiler.java b/compiler/cli/src/org/jetbrains/jet/cli/KotlinCompiler.java index 91a931ea40d..8f2de696738 100644 --- a/compiler/cli/src/org/jetbrains/jet/cli/KotlinCompiler.java +++ b/compiler/cli/src/org/jetbrains/jet/cli/KotlinCompiler.java @@ -73,6 +73,10 @@ public class KotlinCompiler { } public static int exec(String... args) { + return exec(System.out, args); + } + + public static int exec(PrintStream errStream, String... args) { System.setProperty("java.awt.headless", "true"); Arguments arguments = new Arguments(); try { @@ -88,12 +92,13 @@ public class KotlinCompiler { } if (arguments.help) { - usage(System.out); + usage(errStream); return 0; } CompileEnvironment environment = new CompileEnvironment(arguments.transformNamesToJava ? ANY_EXTENSION_TO_JAVA : FileNameTransformer.IDENTITY); environment.setIgnoreErrors(arguments.ignoreErrors); + environment.setErrorStream(errStream); if (arguments.stdlib != null) { environment.setStdlib(arguments.stdlib); diff --git a/compiler/cli/src/org/jetbrains/jet/compiler/CompileEnvironment.java b/compiler/cli/src/org/jetbrains/jet/compiler/CompileEnvironment.java index 25a5427aaec..b296d4cfb01 100644 --- a/compiler/cli/src/org/jetbrains/jet/compiler/CompileEnvironment.java +++ b/compiler/cli/src/org/jetbrains/jet/compiler/CompileEnvironment.java @@ -16,6 +16,7 @@ import org.jetbrains.jet.lang.psi.JetFile; import org.jetbrains.jet.lang.psi.JetPsiUtil; import org.jetbrains.jet.lang.resolve.java.JvmAbi; import org.jetbrains.jet.plugin.JetMainDetector; +import org.jetbrains.jet.plugin.compiler.PathUtil; import java.io.*; import java.lang.reflect.Method; @@ -89,15 +90,18 @@ public class CompileEnvironment { public static void ensureRuntime(JetCoreEnvironment env) { Project project = env.getProject(); - if (JavaPsiFacade.getInstance(project).findClass("java.lang.Obect", GlobalSearchScope.allScope(project)) == null) { + if (JavaPsiFacade.getInstance(project).findClass("java.lang.Object", GlobalSearchScope.allScope(project)) == null) { // TODO: prepend env.addToClasspath(findRtJar()); } if (JavaPsiFacade.getInstance(project).findClass("jet.JetObject", GlobalSearchScope.allScope(project)) == null) { // TODO: prepend - File kotlin = getUnpackedRuntimePath(); - if (kotlin == null) kotlin = getRuntimeJarPath(); + File kotlin = PathUtil.getDefaultRuntimePath(); + if (kotlin == null || !kotlin.exists()) { + kotlin = getUnpackedRuntimePath(); + if (kotlin == null) kotlin = getRuntimeJarPath(); + } env.addToClasspath(kotlin); } } @@ -174,7 +178,8 @@ public class CompileEnvironment { } private List runDefineModules(String moduleFile, ClassFileFactory factory) { - GeneratedClassLoader loader = myStdlib != null ? new GeneratedClassLoader(factory, new URLClassLoader(new URL[] {myStdlib}, AllModules.class.getClassLoader())) : new GeneratedClassLoader(factory); + GeneratedClassLoader loader = myStdlib != null ? new GeneratedClassLoader(factory, new URLClassLoader(new URL[] {myStdlib}, AllModules.class.getClassLoader())) + : new GeneratedClassLoader(factory, CompileEnvironment.class.getClassLoader()); try { Class namespaceClass = loader.loadClass(JvmAbi.PACKAGE_CLASS); final Method method = namespaceClass.getDeclaredMethod("project"); diff --git a/idea/idea.iml b/idea/idea.iml index d646586b05d..18f0c4867e6 100644 --- a/idea/idea.iml +++ b/idea/idea.iml @@ -21,7 +21,7 @@ - + diff --git a/idea/src/org/jetbrains/jet/plugin/compiler/JetCompiler.java b/idea/src/org/jetbrains/jet/plugin/compiler/JetCompiler.java index 4fb977aca57..c4efd41a5a4 100644 --- a/idea/src/org/jetbrains/jet/plugin/compiler/JetCompiler.java +++ b/idea/src/org/jetbrains/jet/plugin/compiler/JetCompiler.java @@ -3,14 +3,13 @@ package org.jetbrains.jet.plugin.compiler; import com.intellij.compiler.impl.javaCompiler.ModuleChunk; import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.configurations.SimpleJavaParameters; -import com.intellij.execution.process.OSProcessHandler; -import com.intellij.execution.process.ProcessAdapter; -import com.intellij.execution.process.ProcessEvent; +import com.intellij.execution.process.*; 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.projectRoots.JavaSdkType; import com.intellij.openapi.projectRoots.JdkUtil; @@ -24,13 +23,14 @@ import com.intellij.util.SystemProperties; import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.plugin.JetFileType; -import java.io.File; -import java.io.IOException; +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.nio.charset.Charset; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -40,6 +40,10 @@ import static com.intellij.openapi.compiler.CompilerMessageCategory.*; * @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; + @Override public boolean isCompilableFile(VirtualFile virtualFile, CompileContext compileContext) { return virtualFile.getFileType() instanceof JetFileType; @@ -106,29 +110,115 @@ public class JetCompiler implements TranslatingCompiler { return; } + if (RUN_OUT_OF_PROCESS) { + runOutOfProcess(compileContext, outputDir, kotlinHome, scriptFile); + } + else { + runInProcess(compileContext, outputDir, kotlinHome, scriptFile); + } + } + 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(); + File[] jars = libs.listFiles(); + if (jars != null) { + for (File jar : jars) { + if (jar.isFile() && jar.getName().endsWith(".jar")) { + answer.add(jar); + } + } + } + + return answer; + } + + private void runInProcess(CompileContext compileContext, VirtualFile outputDir, File kotlinHome, File scriptFile) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(outputStream); + + int rc = execInProcess(kotlinHome, outputDir, scriptFile, out, compileContext); + + ProcessAdapter listener = createProcessListener(compileContext); + + BufferedReader reader = new BufferedReader(new StringReader(outputStream.toString())); + while (true) { + try { + String line = reader.readLine(); + if (line == null) break; + listener.onTextAvailable(new ProcessEvent(NullProcessHandler.INSTANCE, line), ProcessOutputTypes.STDOUT); + } catch (IOException e) { + // Can't be + throw new RuntimeException(e); + } + } + + ProcessEvent termintationEvent = new ProcessEvent(NullProcessHandler.INSTANCE, rc); + listener.processWillTerminate(termintationEvent, false); + listener.processTerminated(termintationEvent); + } + + private static int execInProcess(File kotlinHome, VirtualFile outputDir, File scriptFile, PrintStream out, CompileContext context) { + URLClassLoader loader = getOrCreateClassloader(kotlinHome, context); + try { + Class kompiler = Class.forName("org.jetbrains.jet.cli.KotlinCompiler", true, loader); + Method exec = kompiler.getDeclaredMethod("exec", PrintStream.class, String[].class); + Object rc = exec.invoke(null, out, new String[]{"-module", scriptFile.getAbsolutePath(), "-output", path(outputDir)}); + if (rc instanceof Integer) { + return ((Integer) rc).intValue(); + } + else { + throw new RuntimeException("Unexpected return: " + rc); + } + } catch (Throwable e) { + LOG.error(e); + return -1; + } + } + + 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); + } + + private static void runOutOfProcess(CompileContext compileContext, VirtualFile outputDir, File kotlinHome, File scriptFile) { final SimpleJavaParameters params = new SimpleJavaParameters(); params.setJdk(new SimpleJavaSdkType().createJdk("tmp", SystemProperties.getJavaHome())); params.setMainClass("org.jetbrains.jet.cli.KotlinCompiler"); params.getProgramParametersList().add("-module", scriptFile.getAbsolutePath()); params.getProgramParametersList().add("-output", path(outputDir)); - File libs = new File(kotlinHome, "lib"); - - if (!libs.exists() || libs.isFile()) { - compileContext.addMessage(ERROR, "Broken compiler at '" + libs.getAbsolutePath() + "'. Make sure plugin is properly installed", "", -1, -1); - return; + for (File jar : kompilerClasspath(kotlinHome, compileContext)) { + params.getClassPath().add(jar); } - File[] jars = libs.listFiles(); - if (jars != null) { - for (File jar : jars) { - if (jar.isFile() && jar.getName().endsWith(".jar")) { - params.getClassPath().add(jar); - } - } - } - params.getVMParametersList().addParametersString("-Djava.awt.headless=true -Xmx512m"); // params.getVMParametersList().addParametersString("-agentlib:yjpagent=sampling"); @@ -144,59 +234,8 @@ public class JetCompiler implements TranslatingCompiler { } }; - processHandler.addProcessListener(new ProcessAdapter() { - StringBuilder stderr = null; - - @Override - public void onTextAvailable(ProcessEvent event, Key outputType) { - String text = event.getText(); - String levelCode = parsePrefix(text); - if(outputType.toString().equals("stderr")) { - if(stderr == null) - stderr = new StringBuilder(); - stderr.append(text); - return; - } - if (levelCode != null) { - CompilerMessageCategory category = categories.get(levelCode); - text = text.substring(levelCode.length()); - - String path = ""; - int line = -1; - int column = -1; - int colonIndex = text.indexOf(':'); - if (colonIndex > 0) { - path = "file://" + text.substring(0, colonIndex).trim(); - text = text.substring(colonIndex + 1); - - Pattern position = Pattern.compile("\\((\\d+),\\s*(\\d+)\\)"); - - Matcher matcher = position.matcher(text); - if (matcher.find()) { - line = Integer.parseInt(matcher.group(1)); - column = Integer.parseInt(matcher.group(2)); - text = text.substring(matcher.group(0).length()); - } - } - - compileContext.addMessage(category, text, path, line, column); - } - else { - compileContext.addMessage(INFORMATION, text, "", -1, -1); - } - } - - @Override - public void processTerminated(ProcessEvent event) { - if (event.getExitCode() != 0) { - compileContext.addMessage(ERROR, "Compiler terminated with exit code: " + event.getExitCode(), "", -1, -1); - } - // By alex.tkachman: - if(stderr != null) { - compileContext.addMessage(ERROR, "stderr output:\r\n" + stderr.toString(), "", -1, -1); - } - } - }); + ProcessAdapter processListener = createProcessListener(compileContext); + processHandler.addProcessListener(processListener); processHandler.startNotify(); processHandler.waitFor(); @@ -206,6 +245,63 @@ public class JetCompiler implements TranslatingCompiler { } } + private static ProcessAdapter createProcessListener(final CompileContext compileContext) { + return new ProcessAdapter() { + StringBuilder stderr = null; + + @Override + public void onTextAvailable(ProcessEvent event, Key outputType) { + String text = event.getText(); + String levelCode = parsePrefix(text); + if (outputType == ProcessOutputTypes.STDERR) { + if (stderr == null) { + stderr = new StringBuilder(); + } + stderr.append(text); + return; + } + if (levelCode != null) { + CompilerMessageCategory category = categories.get(levelCode); + text = text.substring(levelCode.length()); + + String path = ""; + int line = -1; + int column = -1; + int colonIndex = text.indexOf(':'); + if (colonIndex > 0) { + path = "file://" + text.substring(0, colonIndex).trim(); + text = text.substring(colonIndex + 1); + + Pattern position = Pattern.compile("\\((\\d+),\\s*(\\d+)\\)"); + + Matcher matcher = position.matcher(text); + if (matcher.find()) { + line = Integer.parseInt(matcher.group(1)); + column = Integer.parseInt(matcher.group(2)); + text = text.substring(matcher.group(0).length()); + } + } + + compileContext.addMessage(category, text, path, line, column); + } + else { + compileContext.addMessage(INFORMATION, text, "", -1, -1); + } + } + + @Override + public void processTerminated(ProcessEvent event) { + if (event.getExitCode() != 0) { + compileContext.addMessage(ERROR, "Compiler terminated with exit code: " + event.getExitCode(), "", -1, -1); + } + // By alex.tkachman: + if (stderr != null) { + compileContext.addMessage(ERROR, "stderr output:\r\n" + stderr.toString(), "", -1, -1); + } + } + }; + } + private static String[] messagePrefixes = new String[] {"ERROR:", "WARNING:", "INFO:"} ; private static Map categories = new HashMap(); static { @@ -229,4 +325,27 @@ public class JetCompiler implements TranslatingCompiler { return path; } + + private static class NullProcessHandler extends ProcessHandler { + public static NullProcessHandler INSTANCE = new NullProcessHandler(); + @Override + protected void destroyProcessImpl() { + throw new UnsupportedOperationException("destroyProcessImpl is not implemented"); + } + + @Override + protected void detachProcessImpl() { + throw new UnsupportedOperationException("detachProcessImpl is not implemented"); // TODO + } + + @Override + public boolean detachIsDefault() { + throw new UnsupportedOperationException("detachIsDefault is not implemented"); + } + + @Override + public OutputStream getProcessInput() { + throw new UnsupportedOperationException("getProcessInput is not implemented"); + } + } }