Move module scripting out of jvm backend

This commit is contained in:
Maxim Shafirov
2012-01-13 14:23:48 +04:00
parent 90fb61f63c
commit 59e688f9ff
14 changed files with 17 additions and 41 deletions
@@ -0,0 +1,385 @@
package org.jetbrains.jet.compiler;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.Function;
import com.intellij.util.Processor;
import jet.ExtensionFunction0;
import jet.modules.IModuleBuilder;
import jet.modules.IModuleSetBuilder;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.codegen.ClassFileFactory;
import org.jetbrains.jet.codegen.GeneratedClassLoader;
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 java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;
import java.util.jar.*;
/**
* The environment for compiling a bunch of source files or
*
* @author yole
*/
public class CompileEnvironment {
private JetCoreEnvironment myEnvironment;
private final Disposable myRootDisposable;
private PrintStream myErrorStream = System.out;
public CompileEnvironment() {
myRootDisposable = new Disposable() {
@Override
public void dispose() {
}
};
myEnvironment = new JetCoreEnvironment(myRootDisposable);
}
public void setErrorStream(PrintStream errorStream) {
myErrorStream = errorStream;
}
public void dispose() {
Disposer.dispose(myRootDisposable);
}
public boolean initializeKotlinRuntime() {
return initializeKotlinRuntime(myEnvironment);
}
public static boolean initializeKotlinRuntime(JetCoreEnvironment environment) {
final File unpackedRuntimePath = getUnpackedRuntimePath();
if (unpackedRuntimePath != null) {
environment.addToClasspath(unpackedRuntimePath);
}
else {
final File runtimeJarPath = getRuntimeJarPath();
if (runtimeJarPath != null && runtimeJarPath.exists()) {
environment.addToClasspath(runtimeJarPath);
}
else {
return false;
}
}
return true;
}
public static File getUnpackedRuntimePath() {
URL url = CompileEnvironment.class.getClassLoader().getResource("jet/JetObject.class");
if (url != null && url.getProtocol().equals("file")) {
return new File(url.getPath()).getParentFile().getParentFile();
}
return null;
}
public static File getRuntimeJarPath() {
URL url = CompileEnvironment.class.getClassLoader().getResource("jet/JetObject.class");
if (url != null && url.getProtocol().equals("jar")) {
String path = url.getPath();
return new File(path.substring(path.indexOf(":") + 1, path.indexOf("!/")));
}
return null;
}
public void setJavaRuntime(File rtJarPath) {
myEnvironment.addToClasspath(rtJarPath);
}
public static File findRtJar(boolean failOnError) {
String javaHome = System.getenv("JAVA_HOME");
if (javaHome == null) {
javaHome = System.getProperty("java.home");
if ("jre".equals(new File(javaHome).getName())) {
javaHome = new File(javaHome).getParent();
}
}
File rtJar;
if (javaHome == null) {
rtJar = findActiveRtJar(failOnError);
if(rtJar == null && failOnError) {
throw new CompileEnvironmentException("JAVA_HOME environment variable needs to be defined");
}
}
else {
rtJar = findRtJar(javaHome);
}
if ((rtJar == null || !rtJar.exists()) && failOnError) {
rtJar = findActiveRtJar(failOnError);
if ((rtJar == null || !rtJar.exists())) {
throw new CompileEnvironmentException("No JDK rt.jar found under JAVA_HOME=" + javaHome);
}
}
return rtJar;
}
private static File findRtJar(String javaHome) {
File rtJar = new File(javaHome, "jre/lib/rt.jar");
if (rtJar.exists()) {
return rtJar;
}
File classesJar = new File(new File(javaHome).getParentFile().getAbsolutePath(), "Classes/classes.jar");
if (classesJar.exists()) {
return classesJar;
}
return null;
}
public static File findActiveRtJar(boolean failOnError) {
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
if (systemClassLoader instanceof URLClassLoader) {
URLClassLoader loader = (URLClassLoader) systemClassLoader;
for (URL url: loader.getURLs()) {
if("file".equals(url.getProtocol())) {
if(url.getFile().endsWith("/lib/rt.jar")) {
return new File(url.getFile());
}
if(url.getFile().endsWith("/Classes/classes.jar")) {
return new File(url.getFile()).getAbsoluteFile();
}
}
}
if (failOnError) {
throw new CompileEnvironmentException("Could not find rt.jar in system class loader: " + StringUtil.join(loader.getURLs(), new Function<URL, String>() {
@Override
public String fun(URL url) {
return url.toString() + "\n";
}
}, ", "));
}
}
else if (failOnError) {
throw new CompileEnvironmentException("System class loader is not an URLClassLoader: " + systemClassLoader);
}
return null;
}
public void compileModuleScript(String moduleFile, String jarPath, boolean jarRuntime) {
try {
System.out.println("module file text: " + FileUtil.loadFile(new File(moduleFile)));
} catch (Exception e) {
e.printStackTrace();
}
final IModuleSetBuilder moduleSetBuilder = loadModuleScript(moduleFile);
if (moduleSetBuilder == null) {
return;
}
final String directory = new File(moduleFile).getParent();
for (IModuleBuilder moduleBuilder : moduleSetBuilder.getModules()) {
ClassFileFactory moduleFactory = compileModule(moduleBuilder, directory);
final String path = jarPath != null ? jarPath : new File(directory, moduleBuilder.getModuleName() + ".jar").getPath();
try {
writeToJar(moduleFactory, new FileOutputStream(path), null, jarRuntime);
} catch (FileNotFoundException e) {
throw new CompileEnvironmentException("Invalid jar path " + path, e);
}
}
}
public IModuleSetBuilder loadModuleScript(String moduleFile) {
CompileSession scriptCompileSession = new CompileSession(myEnvironment);
scriptCompileSession.addSources(moduleFile);
scriptCompileSession.addStdLibSources(true);
if (!scriptCompileSession.analyze(myErrorStream)) {
return null;
}
final ClassFileFactory factory = scriptCompileSession.generate();
return runDefineModules(moduleFile, factory);
}
private static IModuleSetBuilder runDefineModules(String moduleFile, ClassFileFactory factory) {
GeneratedClassLoader loader = new GeneratedClassLoader(factory);
try {
Class moduleSetBuilderClass = loader.loadClass("kotlin.modules.ModuleSetBuilder");
final IModuleSetBuilder moduleSetBuilder = (IModuleSetBuilder) moduleSetBuilderClass.newInstance();
Class namespaceClass = loader.loadClass(JvmAbi.PACKAGE_CLASS);
final Field[] fields = namespaceClass.getDeclaredFields();
boolean modulesDefined = false;
for (Field field : fields) {
if (field.getName().equals("modules")) {
field.setAccessible(true);
ExtensionFunction0 defineMudules = (ExtensionFunction0) field.get(null);
defineMudules.invoke(moduleSetBuilder);
modulesDefined = true;
break;
}
}
if (!modulesDefined) {
throw new CompileEnvironmentException("Module script " + moduleFile + " must define a modules() property");
}
return moduleSetBuilder;
} catch (Exception e) {
throw new CompileEnvironmentException(e);
}
}
public ClassFileFactory compileModule(IModuleBuilder moduleBuilder, String directory) {
CompileSession moduleCompileSession = new CompileSession(myEnvironment);
if (!"stdlib".equals(moduleBuilder.getModuleName())) {
moduleCompileSession.addStdLibSources(false);
}
for (String sourceFile : moduleBuilder.getSourceFiles()) {
File source = new File(sourceFile);
if (!source.isAbsolute()) {
source = new File(directory, sourceFile);
}
moduleCompileSession.addSources(source.getPath());
}
for (String classpathRoot : moduleBuilder.getClasspathRoots()) {
if (classpathRoot.contains("/stdlib/") || classpathRoot.contains("\\stdlib\\")) continue; // TODO: We have source-level dependency on stdlib for now
myEnvironment.addToClasspath(new File(classpathRoot));
}
if (!moduleCompileSession.analyze(myErrorStream)) {
return null;
}
return moduleCompileSession.generate();
}
private static String getHomeDirectory() {
return new File(PathManager.getResourceRoot(CompileEnvironment.class, "/org/jetbrains/jet/compiler/CompileEnvironment.class")).getParentFile().getParentFile().getParent();
}
public static void writeToJar(ClassFileFactory factory, final OutputStream fos, @Nullable String mainClass, boolean includeRuntime) {
try {
Manifest manifest = new Manifest();
final Attributes mainAttributes = manifest.getMainAttributes();
mainAttributes.putValue("Manifest-Version", "1.0");
mainAttributes.putValue("Created-By", "JetBrains Kotlin");
if (mainClass != null) {
mainAttributes.putValue("Main-Class", mainClass);
}
JarOutputStream stream = new JarOutputStream(fos, manifest);
try {
for (String file : factory.files()) {
stream.putNextEntry(new JarEntry(file));
stream.write(factory.asBytes(file));
}
if (includeRuntime) {
writeRuntimeToJar(stream);
}
}
finally {
stream.close();
fos.close();
}
} catch (IOException e) {
throw new CompileEnvironmentException("Failed to generate jar file", e);
}
}
private static void writeRuntimeToJar(final JarOutputStream stream) throws IOException {
final File unpackedRuntimePath = getUnpackedRuntimePath();
if (unpackedRuntimePath != null) {
FileUtil.processFilesRecursively(unpackedRuntimePath, new Processor<File>() {
@Override
public boolean process(File file) {
if (file.isDirectory()) return true;
final String relativePath = FileUtil.getRelativePath(unpackedRuntimePath, file);
try {
stream.putNextEntry(new JarEntry(FileUtil.toSystemIndependentName(relativePath)));
FileInputStream fis = new FileInputStream(file);
try {
FileUtil.copy(fis, stream);
} finally {
fis.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return true;
}
});
}
else {
File runtimeJarPath = getRuntimeJarPath();
if (runtimeJarPath != null) {
JarInputStream jis = new JarInputStream(new FileInputStream(runtimeJarPath));
try {
while (true) {
JarEntry e = jis.getNextJarEntry();
if (e == null) {
break;
}
if (FileUtil.getExtension(e.getName()).equals("class")) {
stream.putNextEntry(e);
FileUtil.copy(jis, stream);
}
}
} finally {
jis.close();
}
}
else {
throw new CompileEnvironmentException("Couldn't find runtime library");
}
}
}
public boolean compileBunchOfSources(String sourceFileOrDir, String jar, String outputDir, boolean includeRuntime, boolean includeSources) {
CompileSession session = new CompileSession(myEnvironment);
session.addSources(sourceFileOrDir);
if (includeSources) {
session.addStdLibSources(false);
}
String mainClass = null;
for (JetFile file : session.getSourceFileNamespaces()) {
if (JetMainDetector.hasMain(file.getDeclarations())) {
mainClass = JetPsiUtil.getFQName(file) + "." + JvmAbi.PACKAGE_CLASS;
break;
}
}
if (!session.analyze(myErrorStream)) {
return false;
}
ClassFileFactory factory = session.generate();
if (jar != null) {
try {
writeToJar(factory, new FileOutputStream(jar), mainClass, includeRuntime);
} catch (FileNotFoundException e) {
throw new CompileEnvironmentException("Invalid jar path " + jar, e);
}
}
else if (outputDir != null) {
writeToOutputDirectory(factory, outputDir);
}
else {
throw new CompileEnvironmentException("Output directory or jar file is not specified - no files will be saved to the disk");
}
return true;
}
public static void writeToOutputDirectory(ClassFileFactory factory, final String outputDir) {
List<String> files = factory.files();
for (String file : files) {
File target = new File(outputDir, file);
try {
FileUtil.writeToFile(target, factory.asBytes(file));
} catch (IOException e) {
throw new CompileEnvironmentException(e);
}
}
}
}
@@ -0,0 +1,18 @@
package org.jetbrains.jet.compiler;
/**
* @author yole
*/
public class CompileEnvironmentException extends RuntimeException {
public CompileEnvironmentException(String message) {
super(message);
}
public CompileEnvironmentException(Throwable cause) {
super(cause);
}
public CompileEnvironmentException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,146 @@
package org.jetbrains.jet.compiler;
import com.google.common.base.Predicates;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.codegen.ClassBuilderFactory;
import org.jetbrains.jet.codegen.ClassFileFactory;
import org.jetbrains.jet.codegen.GenerationState;
import org.jetbrains.jet.lang.cfg.pseudocode.JetControlFlowDataTraceFactory;
import org.jetbrains.jet.lang.psi.JetFile;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.java.AnalyzerFacade;
import org.jetbrains.jet.plugin.JetFileType;
import java.io.File;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
/**
* The session which handles analyzing and compiling a single module.
*
* @author yole
*/
public class CompileSession {
private final JetCoreEnvironment myEnvironment;
private final List<JetFile> mySourceFiles = new ArrayList<JetFile>();
private final List<JetFile> myLibrarySourceFiles = new ArrayList<JetFile>();
private List<String> myErrors = new ArrayList<String>();
public BindingContext getMyBindingContext() {
return myBindingContext;
}
private BindingContext myBindingContext;
public CompileSession(JetCoreEnvironment environment) {
myEnvironment = environment;
}
public void addSources(String path) {
if(path == null)
return;
VirtualFile vFile = myEnvironment.getLocalFileSystem().findFileByPath(path);
if (vFile == null) {
myErrors.add("File/directory not found: " + path);
return;
}
if (!vFile.isDirectory() && vFile.getFileType() != JetFileType.INSTANCE) {
myErrors.add("Not a Kotlin file: " + path);
return;
}
addSources(new File(path), false);
}
private void addSources(File file, boolean library) {
if(file.isDirectory()) {
for (File child : file.listFiles()) {
addSources(child, library);
}
}
else {
VirtualFile fileByPath = myEnvironment.getLocalFileSystem().findFileByPath(file.getAbsolutePath());
PsiFile psiFile = PsiManager.getInstance(myEnvironment.getProject()).findFile(fileByPath);
if(psiFile instanceof JetFile) {
(library ? myLibrarySourceFiles : mySourceFiles).add((JetFile) psiFile);
}
}
}
public void addSources(VirtualFile vFile) {
addSources(vFile, false);
}
private void addSources(VirtualFile vFile, boolean library) {
if (vFile.isDirectory()) {
for (VirtualFile virtualFile : vFile.getChildren()) {
addSources(virtualFile, library);
}
}
else {
if (vFile.getFileType() == JetFileType.INSTANCE) {
PsiFile psiFile = PsiManager.getInstance(myEnvironment.getProject()).findFile(vFile);
if (psiFile instanceof JetFile) {
(library ? myLibrarySourceFiles : mySourceFiles).add((JetFile) psiFile);
}
}
}
}
public List<JetFile> getSourceFileNamespaces() {
return mySourceFiles;
}
public boolean analyze(final PrintStream out) {
if (!myErrors.isEmpty()) {
for (String error : myErrors) {
out.println(error);
}
return false;
}
List<JetFile> allNamespaces = new ArrayList<JetFile>(mySourceFiles);
allNamespaces.addAll(myLibrarySourceFiles);
myBindingContext = AnalyzerFacade.analyzeFilesWithJavaIntegration(
myEnvironment.getProject(), allNamespaces, Predicates.<PsiFile>alwaysTrue(), JetControlFlowDataTraceFactory.EMPTY);
ErrorCollector errorCollector = new ErrorCollector(myBindingContext);
errorCollector.report(out);
return !errorCollector.hasErrors;
}
@NotNull
public ClassFileFactory generate() {
GenerationState generationState = new GenerationState(myEnvironment.getProject(), ClassBuilderFactory.BINARIES);
generationState.compileCorrectFiles(myBindingContext, mySourceFiles);
return generationState.getFactory();
}
public String generateText() {
GenerationState generationState = new GenerationState(myEnvironment.getProject(), ClassBuilderFactory.TEXT);
generationState.compileCorrectFiles(myBindingContext, mySourceFiles);
return generationState.createText();
}
public boolean addStdLibSources(boolean toModuleSources) {
final File unpackedRuntimePath = CompileEnvironment.getUnpackedRuntimePath();
if (unpackedRuntimePath != null) {
addSources(new File(unpackedRuntimePath, "../../../stdlib/ktSrc").getAbsoluteFile(), !toModuleSources);
}
else {
final File runtimeJarPath = CompileEnvironment.getRuntimeJarPath();
if (runtimeJarPath != null && runtimeJarPath.exists()) {
VirtualFile runtimeJar = myEnvironment.getLocalFileSystem().findFileByPath(runtimeJarPath.getAbsolutePath());
VirtualFile jarRoot = myEnvironment.getJarFileSystem().findFileByPath(runtimeJar.getPath() + "!/stdlib/ktSrc");
addSources(jarRoot, !toModuleSources);
}
else {
return false;
}
}
return true;
}
}
@@ -0,0 +1,53 @@
package org.jetbrains.jet.compiler;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.intellij.psi.PsiFile;
import org.jetbrains.jet.lang.diagnostics.Diagnostic;
import org.jetbrains.jet.lang.diagnostics.DiagnosticUtils;
import org.jetbrains.jet.lang.diagnostics.DiagnosticWithTextRange;
import org.jetbrains.jet.lang.diagnostics.Severity;
import org.jetbrains.jet.lang.resolve.BindingContext;
import java.io.PrintStream;
import java.util.Collection;
/**
* @author alex.tkachman
*/
class ErrorCollector {
Multimap<PsiFile,DiagnosticWithTextRange> maps = LinkedHashMultimap.<PsiFile, DiagnosticWithTextRange>create();
boolean hasErrors;
public ErrorCollector(BindingContext bindingContext) {
for (Diagnostic diagnostic : bindingContext.getDiagnostics()) {
report(diagnostic);
}
}
private void report(Diagnostic diagnostic) {
hasErrors |= diagnostic.getSeverity() == Severity.ERROR;
if(diagnostic instanceof DiagnosticWithTextRange) {
DiagnosticWithTextRange diagnosticWithTextRange = (DiagnosticWithTextRange) diagnostic;
maps.put(diagnosticWithTextRange.getPsiFile(), diagnosticWithTextRange);
}
else {
System.out.println(diagnostic.getSeverity().toString() + ": " + diagnostic.getMessage());
}
}
void report(final PrintStream out) {
if(!maps.isEmpty()) {
for (PsiFile psiFile : maps.keySet()) {
out.println(psiFile.getVirtualFile().getPath());
Collection<DiagnosticWithTextRange> diagnosticWithTextRanges = maps.get(psiFile);
for (DiagnosticWithTextRange diagnosticWithTextRange : diagnosticWithTextRanges) {
String position = DiagnosticUtils.formatPosition(diagnosticWithTextRange);
out.println("\t" + diagnosticWithTextRange.getSeverity().toString() + ": " + position + " " + diagnosticWithTextRange.getMessage());
}
}
}
}
}
@@ -0,0 +1,35 @@
package org.jetbrains.jet.compiler;
import com.intellij.core.JavaCoreEnvironment;
import com.intellij.lang.java.JavaParserDefinition;
import com.intellij.mock.MockApplication;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.psi.PsiElementFinder;
import org.jetbrains.jet.asJava.JavaElementFinder;
import org.jetbrains.jet.lang.parsing.JetParserDefinition;
import org.jetbrains.jet.plugin.JetFileType;
/**
* @author yole
*/
public class JetCoreEnvironment extends JavaCoreEnvironment {
public JetCoreEnvironment(Disposable parentDisposable) {
super(parentDisposable);
registerFileType(JetFileType.INSTANCE, "kt");
registerFileType(JetFileType.INSTANCE, "kts");
registerFileType(JetFileType.INSTANCE, "ktm");
registerFileType(JetFileType.INSTANCE, "jet");
registerParserDefinition(new JavaParserDefinition());
registerParserDefinition(new JetParserDefinition());
Extensions.getArea(myProject)
.getExtensionPoint(PsiElementFinder.EP_NAME)
.registerExtension(new JavaElementFinder(myProject));
}
public MockApplication getApplication() {
return myApplication;
}
}