In-the-process compiler

This commit is contained in:
Maxim Shafirov
2012-02-07 20:51:41 +04:00
parent d1409116f3
commit 8e6e3f683a
4 changed files with 211 additions and 82 deletions
@@ -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);
@@ -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<Module> 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");
+1 -1
View File
@@ -21,7 +21,7 @@
<orderEntry type="module" module-name="frontend" />
<orderEntry type="module" module-name="frontend.java" />
<orderEntry type="module" module-name="stdlib" />
<orderEntry type="module" module-name="compiler-tests" />
<orderEntry type="module" module-name="compiler-tests" scope="TEST" />
<orderEntry type="module" module-name="jet.as.java.psi" />
<orderEntry type="library" name="idea-full" level="project" />
<orderEntry type="module" module-name="j2k" />
@@ -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<File> 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<File> answer = new ArrayList<File>();
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<URLClassLoader> ourClassloaderRef = new SoftReference<URLClassLoader>(null);
private static URLClassLoader getOrCreateClassloader(File kotlinHome, CompileContext context) {
URLClassLoader answer = ourClassloaderRef.get();
if (answer == null) {
answer = createClassloader(kotlinHome, context);
ourClassloaderRef = new SoftReference<URLClassLoader>(answer);
}
return answer;
}
private static URLClassLoader createClassloader(File kotlinHome, CompileContext context) {
List<File> 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<String, CompilerMessageCategory> categories = new HashMap<String, CompilerMessageCategory>();
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");
}
}
}