From b8bfde3102323a01c2c47f1701347383a18b6f10 Mon Sep 17 00:00:00 2001 From: Alex Tkachman Date: Sat, 28 Jul 2012 20:32:18 +0300 Subject: [PATCH] in-memory compilation of standalone script --- .../jetbrains/jet/cli/jvm/K2JVMCompiler.java | 10 +- .../jvm/compiler/CompileEnvironmentUtil.java | 4 +- .../K2JVMCompileEnvironmentConfiguration.java | 14 +- .../compiler/KotlinToJVMBytecodeCompiler.java | 138 +++++++++++++++--- compiler/testData/cli/fib.ktscript | 10 ++ .../org/jetbrains/jet/cli/jvm/CliTest.java | 23 +++ .../jetbrains/jet/codegen/TestlibTest.java | 4 +- 7 files changed, 169 insertions(+), 34 deletions(-) create mode 100644 compiler/testData/cli/fib.ktscript diff --git a/compiler/cli/src/org/jetbrains/jet/cli/jvm/K2JVMCompiler.java b/compiler/cli/src/org/jetbrains/jet/cli/jvm/K2JVMCompiler.java index a3a1a8c6797..316e88357da 100644 --- a/compiler/cli/src/org/jetbrains/jet/cli/jvm/K2JVMCompiler.java +++ b/compiler/cli/src/org/jetbrains/jet/cli/jvm/K2JVMCompiler.java @@ -25,20 +25,20 @@ import org.jetbrains.jet.cli.common.CLICompiler; import org.jetbrains.jet.cli.common.ExitCode; import org.jetbrains.jet.cli.common.messages.*; import org.jetbrains.jet.cli.common.util.CompilerPathUtil; -import org.jetbrains.jet.cli.jvm.compiler.CompileEnvironmentUtil; -import org.jetbrains.jet.cli.jvm.compiler.JetCoreEnvironment; -import org.jetbrains.jet.cli.jvm.compiler.K2JVMCompileEnvironmentConfiguration; -import org.jetbrains.jet.cli.jvm.compiler.KotlinToJVMBytecodeCompiler; +import org.jetbrains.jet.cli.jvm.compiler.*; import org.jetbrains.jet.cli.jvm.repl.ReplFromTerminal; import org.jetbrains.jet.codegen.BuiltinToJavaTypesMapping; import org.jetbrains.jet.codegen.CompilationException; import org.jetbrains.jet.config.CommonConfigurationKeys; import org.jetbrains.jet.config.CompilerConfiguration; import org.jetbrains.jet.lang.BuiltinsScopeExtensionMode; +import org.jetbrains.jet.lang.resolve.AnalyzerScriptParameter; import org.jetbrains.jet.utils.PathUtil; import java.io.File; import java.io.PrintStream; +import java.util.Collection; +import java.util.Collections; import java.util.List; import static org.jetbrains.jet.cli.common.ExitCode.*; @@ -95,7 +95,7 @@ public class K2JVMCompiler extends CLICompileremptyList(), builtins ? BuiltinsScopeExtensionMode.ONLY_STANDARD_CLASSES : BuiltinsScopeExtensionMode.ALL, builtins, builtins ? BuiltinToJavaTypesMapping.DISABLED : BuiltinToJavaTypesMapping.ENABLED); diff --git a/compiler/cli/src/org/jetbrains/jet/cli/jvm/compiler/CompileEnvironmentUtil.java b/compiler/cli/src/org/jetbrains/jet/cli/jvm/compiler/CompileEnvironmentUtil.java index 12d36096e52..5ae08b8c6af 100644 --- a/compiler/cli/src/org/jetbrains/jet/cli/jvm/compiler/CompileEnvironmentUtil.java +++ b/compiler/cli/src/org/jetbrains/jet/cli/jvm/compiler/CompileEnvironmentUtil.java @@ -34,6 +34,7 @@ import org.jetbrains.jet.codegen.GenerationState; import org.jetbrains.jet.config.CommonConfigurationKeys; import org.jetbrains.jet.config.CompilerConfiguration; import org.jetbrains.jet.lang.BuiltinsScopeExtensionMode; +import org.jetbrains.jet.lang.resolve.AnalyzerScriptParameter; import org.jetbrains.jet.lang.resolve.java.JvmAbi; import org.jetbrains.jet.lang.resolve.name.FqName; import org.jetbrains.jet.utils.PathUtil; @@ -47,6 +48,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.jar.*; @@ -103,7 +105,7 @@ public class CompileEnvironmentUtil { JetCoreEnvironment scriptEnvironment = JetCoreEnvironment.createCoreEnvironmentForJVM(disposable, configuration); GenerationState generationState = KotlinToJVMBytecodeCompiler - .analyzeAndGenerate(new K2JVMCompileEnvironmentConfiguration(scriptEnvironment, messageCollector, false, + .analyzeAndGenerate(new K2JVMCompileEnvironmentConfiguration(scriptEnvironment, messageCollector, Collections.emptyList(), BuiltinsScopeExtensionMode.ALL, false, BuiltinToJavaTypesMapping.ENABLED), false); if (generationState == null) { diff --git a/compiler/cli/src/org/jetbrains/jet/cli/jvm/compiler/K2JVMCompileEnvironmentConfiguration.java b/compiler/cli/src/org/jetbrains/jet/cli/jvm/compiler/K2JVMCompileEnvironmentConfiguration.java index 79f257af83b..29dda4baccd 100644 --- a/compiler/cli/src/org/jetbrains/jet/cli/jvm/compiler/K2JVMCompileEnvironmentConfiguration.java +++ b/compiler/cli/src/org/jetbrains/jet/cli/jvm/compiler/K2JVMCompileEnvironmentConfiguration.java @@ -22,13 +22,16 @@ import org.jetbrains.jet.cli.common.CompileEnvironmentConfiguration; import org.jetbrains.jet.cli.common.messages.MessageCollector; import org.jetbrains.jet.codegen.BuiltinToJavaTypesMapping; import org.jetbrains.jet.lang.BuiltinsScopeExtensionMode; +import org.jetbrains.jet.lang.resolve.AnalyzerScriptParameter; + +import java.util.List; /** * @author abreslav */ public class K2JVMCompileEnvironmentConfiguration extends CompileEnvironmentConfiguration { private final JetCoreEnvironment environment; - private final boolean script; + private final List scriptParameters; private final BuiltinsScopeExtensionMode builtinsScopeExtensionMode; private final boolean stubs; private final BuiltinToJavaTypesMapping builtinToJavaTypesMapping; @@ -39,10 +42,10 @@ public class K2JVMCompileEnvironmentConfiguration extends CompileEnvironmentConf * @see Disposer */ public K2JVMCompileEnvironmentConfiguration(@NotNull JetCoreEnvironment environment, @NotNull MessageCollector messageCollector, - boolean script, BuiltinsScopeExtensionMode builtinsScopeExtensionMode, boolean stubs, BuiltinToJavaTypesMapping builtinToJavaTypesMapping) { + List scriptParameters, BuiltinsScopeExtensionMode builtinsScopeExtensionMode, boolean stubs, BuiltinToJavaTypesMapping builtinToJavaTypesMapping) { super(messageCollector); this.environment = environment; - this.script = script; + this.scriptParameters = scriptParameters; this.builtinsScopeExtensionMode = builtinsScopeExtensionMode; this.stubs = stubs; this.builtinToJavaTypesMapping = builtinToJavaTypesMapping; @@ -53,9 +56,12 @@ public class K2JVMCompileEnvironmentConfiguration extends CompileEnvironmentConf } public boolean isScript() { - return script; + return scriptParameters != null; } + public List getScript() { + return scriptParameters; + } public BuiltinsScopeExtensionMode getBuiltinsScopeExtensionMode() { return builtinsScopeExtensionMode; diff --git a/compiler/cli/src/org/jetbrains/jet/cli/jvm/compiler/KotlinToJVMBytecodeCompiler.java b/compiler/cli/src/org/jetbrains/jet/cli/jvm/compiler/KotlinToJVMBytecodeCompiler.java index afb431db609..754a8c726b6 100644 --- a/compiler/cli/src/org/jetbrains/jet/cli/jvm/compiler/KotlinToJVMBytecodeCompiler.java +++ b/compiler/cli/src/org/jetbrains/jet/cli/jvm/compiler/KotlinToJVMBytecodeCompiler.java @@ -30,14 +30,15 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.analyzer.AnalyzeExhaust; import org.jetbrains.jet.cli.common.CompilerPlugin; import org.jetbrains.jet.cli.common.CompilerPluginContext; -import org.jetbrains.jet.cli.common.messages.AnalyzerWithCompilerReport; -import org.jetbrains.jet.cli.common.messages.CompilerMessageLocation; -import org.jetbrains.jet.cli.common.messages.CompilerMessageSeverity; +import org.jetbrains.jet.cli.common.ExitCode; +import org.jetbrains.jet.cli.common.messages.*; import org.jetbrains.jet.cli.common.util.CompilerPathUtil; import org.jetbrains.jet.cli.jvm.JVMConfigurationKeys; +import org.jetbrains.jet.cli.jvm.repl.ReplFromTerminal; import org.jetbrains.jet.codegen.*; import org.jetbrains.jet.config.CommonConfigurationKeys; import org.jetbrains.jet.config.CompilerConfiguration; +import org.jetbrains.jet.lang.BuiltinsScopeExtensionMode; import org.jetbrains.jet.lang.psi.JetFile; import org.jetbrains.jet.lang.psi.JetPsiUtil; import org.jetbrains.jet.lang.resolve.AnalyzerScriptParameter; @@ -50,18 +51,23 @@ import org.jetbrains.jet.utils.ExceptionUtils; import org.jetbrains.jet.utils.PathUtil; import org.jetbrains.jet.utils.Progress; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; +import java.io.*; +import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.net.URLClassLoader; +import java.util.Collection; import java.util.Collections; +import java.util.LinkedList; import java.util.List; +import static org.jetbrains.jet.cli.common.ExitCode.COMPILATION_ERROR; +import static org.jetbrains.jet.cli.common.ExitCode.INTERNAL_ERROR; +import static org.jetbrains.jet.cli.common.ExitCode.OK; + /** * @author yole * @author abreslav + * @author alex.tkachman */ public class KotlinToJVMBytecodeCompiler { @@ -113,7 +119,7 @@ public class KotlinToJVMBytecodeCompiler { K2JVMCompileEnvironmentConfiguration currentK2JVMConfiguration = new K2JVMCompileEnvironmentConfiguration( - environment, configuration.getMessageCollector(), configuration.isScript(), + environment, configuration.getMessageCollector(), Collections.emptyList(), configuration.getBuiltinsScopeExtensionMode(), configuration.isStubs(), configuration.getBuiltinToJavaTypesMapping()); GenerationState generationState = analyzeAndGenerate(currentK2JVMConfiguration); @@ -231,10 +237,28 @@ public class KotlinToJVMBytecodeCompiler { public static boolean compileAndExecuteScript( @NotNull K2JVMCompileEnvironmentConfiguration configuration, @NotNull List scriptArgs) { + Class scriptClass = compileScript(configuration, null); + if(scriptClass == null) + return false; + + try { + scriptClass.getConstructor(String[].class).newInstance(new Object[]{scriptArgs.toArray(new String[0])}); + } + catch (RuntimeException e) { + throw e; + } + catch (Exception e) { + throw new RuntimeException("Failed to evaluate script: " + e, e); + } + return true; + } + + public static Class compileScript( + @NotNull K2JVMCompileEnvironmentConfiguration configuration, ClassLoader parentLoader) { GenerationState generationState = analyzeAndGenerate(configuration); if (generationState == null) { - return false; + return null; } try { @@ -244,14 +268,12 @@ public class KotlinToJVMBytecodeCompiler { // TODO: add all classpath CompilerPathUtil.getRuntimePath().toURI().toURL() }, - AllModules.class.getClassLoader())); - Class scriptClass = classLoader.loadClass(ScriptCodegen.SCRIPT_DEFAULT_CLASS_NAME.getFqName().getFqName()); - scriptClass.getConstructor(String[].class).newInstance(new Object[]{scriptArgs.toArray(new String[0])}); + parentLoader == null ? AllModules.class.getClassLoader() : parentLoader)); + return classLoader.loadClass(ScriptCodegen.SCRIPT_DEFAULT_CLASS_NAME.getFqName().getFqName()); } catch (Exception e) { throw new RuntimeException("Failed to evaluate script: " + e, e); } - return true; } finally { generationState.destroy(); @@ -260,7 +282,7 @@ public class KotlinToJVMBytecodeCompiler { @Nullable public static GenerationState analyzeAndGenerate(K2JVMCompileEnvironmentConfiguration configuration) { - return analyzeAndGenerate(configuration, configuration.isStubs()); + return analyzeAndGenerate(configuration, configuration.isStubs(), configuration.getScript()); } @Nullable @@ -268,7 +290,18 @@ public class KotlinToJVMBytecodeCompiler { K2JVMCompileEnvironmentConfiguration configuration, boolean stubs ) { - AnalyzeExhaust exhaust = analyze(configuration, configuration.isScript(), stubs); + return analyzeAndGenerate(configuration, stubs, configuration.isScript() + ? CommandLineScriptUtils.scriptParameters() + : Collections.emptyList()); + } + + @Nullable + public static GenerationState analyzeAndGenerate( + K2JVMCompileEnvironmentConfiguration configuration, + boolean stubs, + List scriptParameters + ) { + AnalyzeExhaust exhaust = analyze(configuration, scriptParameters, stubs); if (exhaust == null) { return null; @@ -282,13 +315,12 @@ public class KotlinToJVMBytecodeCompiler { @Nullable private static AnalyzeExhaust analyze( final K2JVMCompileEnvironmentConfiguration configuration, - boolean script, boolean stubs) { + final List scriptParameters, + boolean stubs) { final JetCoreEnvironment environment = configuration.getEnvironment(); AnalyzerWithCompilerReport analyzerWithCompilerReport = new AnalyzerWithCompilerReport(configuration.getMessageCollector()); final Predicate filesToAnalyzeCompletely = stubs ? Predicates.alwaysFalse() : Predicates.alwaysTrue(); - final List scriptParameters = - script ? CommandLineScriptUtils.scriptParameters() : Collections.emptyList(); analyzerWithCompilerReport.analyzeAndReport( new Function0() { @NotNull @@ -326,12 +358,72 @@ public class KotlinToJVMBytecodeCompiler { generationState.compileCorrectFiles(CompilationErrorHandler.THROW_EXCEPTION); List plugins = configuration.getCompilerPlugins(); - if (plugins != null) { - CompilerPluginContext context = new CompilerPluginContext(project, exhaust.getBindingContext(), environment.getSourceFiles()); - for (CompilerPlugin plugin : plugins) { - plugin.processFiles(context); - } + CompilerPluginContext context = new CompilerPluginContext(project, exhaust.getBindingContext(), environment.getSourceFiles()); + for (CompilerPlugin plugin : plugins) { + plugin.processFiles(context); } return generationState; } + + public static Class compileScript( + ClassLoader parentLoader, + String scriptPath, + List scriptParameters) { + final MessageRenderer messageRenderer = MessageRenderer.PLAIN; + PrintingMessageCollector messageCollector = new PrintingMessageCollector(System.err, messageRenderer, false); + Disposable rootDisposable = CompileEnvironmentUtil.createMockDisposable(); + try { + CompilerConfiguration compilerConfiguration = new CompilerConfiguration(); + compilerConfiguration.addAll(JVMConfigurationKeys.CLASSPATH_KEY, getClasspath(parentLoader)); + compilerConfiguration.addAll(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY, Collections.singletonList(CompilerPathUtil.getJdkAnnotationsPath())); + compilerConfiguration.add(CommonConfigurationKeys.SOURCE_ROOTS_KEY, scriptPath); + + JetCoreEnvironment environment = JetCoreEnvironment.createCoreEnvironmentForJVM(rootDisposable, compilerConfiguration); + K2JVMCompileEnvironmentConfiguration configuration = new K2JVMCompileEnvironmentConfiguration( + environment, messageCollector, scriptParameters, + BuiltinsScopeExtensionMode.ALL, + false, + BuiltinToJavaTypesMapping.ENABLED); + + try { + return compileScript(configuration, parentLoader); + } + catch (CompilationException e) { + messageCollector.report(CompilerMessageSeverity.EXCEPTION, MessageRenderer.PLAIN.renderException(e), + MessageUtil.psiElementToMessageLocation(e.getElement())); + return null; + } + catch (Throwable t) { + messageCollector.report(CompilerMessageSeverity.EXCEPTION, MessageRenderer.PLAIN.renderException(t), + CompilerMessageLocation.NO_LOCATION); + return null; + } + + } + finally { + messageCollector.printToErrStream(); + Disposer.dispose(rootDisposable); + } + } + + private static Collection getClasspath(ClassLoader loader) { + return getClasspath(loader, new LinkedList()); + } + + private static Collection getClasspath(ClassLoader loader, LinkedList files) { + ClassLoader parent = loader.getParent(); + if(parent != null) + getClasspath(parent, files); + + if(loader instanceof URLClassLoader) { + for (URL url : ((URLClassLoader) loader).getURLs()) { + String urlFile = url.getFile(); + File file = new File(urlFile); + if(file.exists() && (file.isDirectory() || file.getName().endsWith(".jar"))) { + files.add(file); + } + } + } + return files; + } } diff --git a/compiler/testData/cli/fib.ktscript b/compiler/testData/cli/fib.ktscript new file mode 100644 index 00000000000..5c714e673d1 --- /dev/null +++ b/compiler/testData/cli/fib.ktscript @@ -0,0 +1,10 @@ +// this script expected parameter num : Int + +fun fib(n: Int): Int { + val v = if(n < 2) 1 else fib(n-1) + fib(n-2) + System.out.println("fib($n)=$v") + return v +} + +System.out.println("num: $num") +val result = fib(num) diff --git a/compiler/tests/org/jetbrains/jet/cli/jvm/CliTest.java b/compiler/tests/org/jetbrains/jet/cli/jvm/CliTest.java index 83c5724fa99..2817a9a69db 100644 --- a/compiler/tests/org/jetbrains/jet/cli/jvm/CliTest.java +++ b/compiler/tests/org/jetbrains/jet/cli/jvm/CliTest.java @@ -21,6 +21,10 @@ import com.intellij.openapi.vfs.LocalFileSystem; import junit.framework.Assert; import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.cli.common.ExitCode; +import org.jetbrains.jet.cli.jvm.compiler.KotlinToJVMBytecodeCompiler; +import org.jetbrains.jet.lang.resolve.AnalyzerScriptParameter; +import org.jetbrains.jet.lang.resolve.name.Name; +import org.jetbrains.jet.lang.types.ref.JetTypeName; import org.jetbrains.jet.test.Tmpdir; import org.jetbrains.jet.utils.ExceptionUtils; import org.junit.Rule; @@ -32,6 +36,9 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.PrintStream; import java.io.StringReader; +import java.lang.reflect.InvocationTargetException; +import java.util.LinkedList; +import java.util.List; /** * @author Stepan Koltsov @@ -119,4 +126,20 @@ public class CliTest { executeCompilerCompareOutput(new String[]{ "-src", "compiler/testData/cli/ideTemplates.kt", "-output", tmpdir.getTmpDir().getPath()}); } + @Test + public void testScript() { + LinkedList scriptParameters = new LinkedList(); + AnalyzerScriptParameter parameter = new AnalyzerScriptParameter(Name.identifier("num"), JetTypeName.parse("jet.Int")); + scriptParameters.add(parameter); + Class aClass = KotlinToJVMBytecodeCompiler + .compileScript(getClass().getClassLoader(), "compiler/testData/cli/fib.ktscript", scriptParameters); + Assert.assertNotNull(aClass); + + try { + aClass.getConstructor(int.class).newInstance(4); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/compiler/tests/org/jetbrains/jet/codegen/TestlibTest.java b/compiler/tests/org/jetbrains/jet/codegen/TestlibTest.java index 89b5e77c4d4..304eee26aba 100644 --- a/compiler/tests/org/jetbrains/jet/codegen/TestlibTest.java +++ b/compiler/tests/org/jetbrains/jet/codegen/TestlibTest.java @@ -37,6 +37,7 @@ import org.jetbrains.jet.lang.descriptors.ClassDescriptor; import org.jetbrains.jet.lang.psi.JetClass; import org.jetbrains.jet.lang.psi.JetDeclaration; import org.jetbrains.jet.lang.psi.JetFile; +import org.jetbrains.jet.lang.resolve.AnalyzerScriptParameter; import org.jetbrains.jet.lang.resolve.BindingContext; import org.jetbrains.jet.lang.resolve.DescriptorUtils; import org.jetbrains.jet.lang.types.JetType; @@ -48,6 +49,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; import java.net.URL; import java.net.URLClassLoader; +import java.util.Collections; import java.util.Set; /** @@ -81,7 +83,7 @@ public class TestlibTest extends CodegenTestCase { try { GenerationState generationState = KotlinToJVMBytecodeCompiler .analyzeAndGenerate(new K2JVMCompileEnvironmentConfiguration(myEnvironment, MessageCollector.PLAIN_TEXT_TO_SYSTEM_ERR, - false, BuiltinsScopeExtensionMode.ALL, false, + Collections.emptyList(), BuiltinsScopeExtensionMode.ALL, false, BuiltinToJavaTypesMapping.ENABLED), false); if (generationState == null) {