In-the-process compiler
This commit is contained in:
@@ -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
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user