Add support for custom script compilation and execution to maven plugin, add simple test, relevant refactorings on the compiler side

This commit is contained in:
Ilya Chernikov
2016-09-19 14:15:04 +02:00
parent 86ece30330
commit 1a137357e5
10 changed files with 269 additions and 142 deletions
@@ -237,7 +237,7 @@ class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
): KotlinCoreEnvironment? {
val scriptResolverEnv = hashMapOf<String, Any?>()
configureScriptDefinitions(arguments, configuration, messageCollector, scriptResolverEnv)
configureScriptDefinitions(arguments.scriptTemplates, configuration, messageCollector, scriptResolverEnv)
if (!messageCollector.hasErrors()) {
val environment = createCoreEnvironment(rootDisposable, configuration)
if (!messageCollector.hasErrors()) {
@@ -248,50 +248,6 @@ class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
return null
}
private fun configureScriptDefinitions(arguments: K2JVMCompilerArguments,
configuration: CompilerConfiguration,
messageCollector: MessageCollector,
scriptResolverEnv: HashMap<String, Any?>) {
val classpath = configuration.getList(JVMConfigurationKeys.CONTENT_ROOTS).filterIsInstance(JvmClasspathRoot::class.java).mapNotNull { it.file }
// TODO: consider using escaping to allow kotlin escaped names in class names
if (arguments.scriptTemplates != null && arguments.scriptTemplates.isNotEmpty()) {
val classloader = URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray(), this.javaClass.classLoader)
var hasErrors = false
for (template in arguments.scriptTemplates) {
try {
val cls = classloader.loadClass(template)
val def = KotlinScriptDefinitionFromTemplate(cls.kotlin, null, null, scriptResolverEnv)
configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, def)
messageCollector.report(
CompilerMessageSeverity.INFO,
"Added script definition $template to configuration: files pattern = \"${def.scriptFilePattern}\", resolver = ${def.resolver?.javaClass?.name}",
CompilerMessageLocation.NO_LOCATION
)
}
catch (ex: ClassNotFoundException) {
messageCollector.report(
CompilerMessageSeverity.ERROR, "Cannot find script definition template class $template", CompilerMessageLocation.NO_LOCATION
)
hasErrors = true
}
catch (ex: Exception) {
messageCollector.report(
CompilerMessageSeverity.ERROR, "Error processing script definition template $template: ${ex.message}", CompilerMessageLocation.NO_LOCATION
)
hasErrors = true
break
}
}
if (hasErrors) {
messageCollector.report(
CompilerMessageSeverity.LOGGING, "(Classpath used for templates loading: $classpath)", CompilerMessageLocation.NO_LOCATION
)
return
}
}
configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, StandardScriptDefinition)
}
private fun createCoreEnvironment(rootDisposable: Disposable, configuration: CompilerConfiguration): KotlinCoreEnvironment {
val result = KotlinCoreEnvironment.createForProduction(rootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
@@ -431,6 +387,50 @@ class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
}
return OK
}
fun configureScriptDefinitions(scriptTemplates: Array<String>?,
configuration: CompilerConfiguration,
messageCollector: MessageCollector,
scriptResolverEnv: HashMap<String, Any?>) {
val classpath = configuration.getList(JVMConfigurationKeys.CONTENT_ROOTS).filterIsInstance(JvmClasspathRoot::class.java).mapNotNull { it.file }
// TODO: consider using escaping to allow kotlin escaped names in class names
if (scriptTemplates != null && scriptTemplates.isNotEmpty()) {
val classloader = URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray(), Thread.currentThread().contextClassLoader)
var hasErrors = false
for (template in scriptTemplates) {
try {
val cls = classloader.loadClass(template)
val def = KotlinScriptDefinitionFromTemplate(cls.kotlin, null, null, scriptResolverEnv)
configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, def)
messageCollector.report(
CompilerMessageSeverity.INFO,
"Added script definition $template to configuration: files pattern = \"${def.scriptFilePattern}\", resolver = ${def.resolver?.javaClass?.name}",
CompilerMessageLocation.NO_LOCATION
)
}
catch (ex: ClassNotFoundException) {
messageCollector.report(
CompilerMessageSeverity.ERROR, "Cannot find script definition template class $template", CompilerMessageLocation.NO_LOCATION
)
hasErrors = true
}
catch (ex: Exception) {
messageCollector.report(
CompilerMessageSeverity.ERROR, "Error processing script definition template $template: ${ex.message}", CompilerMessageLocation.NO_LOCATION
)
hasErrors = true
break
}
}
if (hasErrors) {
messageCollector.report(
CompilerMessageSeverity.LOGGING, "(Classpath used for templates loading: $classpath)", CompilerMessageLocation.NO_LOCATION
)
return
}
}
configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, StandardScriptDefinition)
}
}
}
@@ -34,6 +34,7 @@ import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.cli.common.messages.*
import org.jetbrains.kotlin.cli.common.output.outputUtils.writeAll
import org.jetbrains.kotlin.cli.common.tryConstructScriptClass
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
import org.jetbrains.kotlin.cli.jvm.config.*
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
@@ -66,10 +67,6 @@ import java.lang.reflect.InvocationTargetException
import java.net.URLClassLoader
import java.util.concurrent.TimeUnit
import java.util.jar.Attributes
import kotlin.reflect.KParameter
import kotlin.reflect.KType
import kotlin.reflect.defaultType
import kotlin.reflect.jvm.javaType
object KotlinToJVMBytecodeCompiler {
@@ -230,7 +227,7 @@ object KotlinToJVMBytecodeCompiler {
try {
try {
tryConstructClass(scriptClass, scriptArgs)
tryConstructScriptClass(scriptClass, scriptArgs)
?: throw RuntimeException("unable to find appropriate constructor for class ${scriptClass.name} accepting arguments $scriptArgs\n")
}
finally {
@@ -295,78 +292,6 @@ object KotlinToJVMBytecodeCompiler {
}
}
@TestOnly
fun tryConstructClassPub(scriptClass: Class<*>, scriptArgs: List<String>): Any? = tryConstructClass(scriptClass, scriptArgs)
private fun tryConstructClass(scriptClass: Class<*>, scriptArgs: List<String>): Any? {
fun convertPrimitive(type: KType?, arg: String): Any? =
when (type) {
String::class.defaultType -> arg
Int::class.defaultType -> arg.toInt()
Long::class.defaultType -> arg.toLong()
Short::class.defaultType -> arg.toShort()
Byte::class.defaultType -> arg.toByte()
Char::class.defaultType -> arg[0]
Float::class.defaultType -> arg.toFloat()
Double::class.defaultType -> arg.toDouble()
Boolean::class.defaultType -> arg.toBoolean()
else -> null
}
fun convertArray(type: KType?, args: List<String>): Any? =
when (type) {
String::class.defaultType -> args.toTypedArray()
Int::class.defaultType -> args.map { it.toInt() }.toTypedArray()
Long::class.defaultType -> args.map { it.toLong() }.toTypedArray()
Short::class.defaultType -> args.map { it.toShort() }.toTypedArray()
Byte::class.defaultType -> args.map { it.toByte() }.toTypedArray()
Char::class.defaultType -> args.map { it[0] }.toTypedArray()
Float::class.defaultType -> args.map { it.toFloat() }.toTypedArray()
Double::class.defaultType -> args.map { it.toDouble() }.toTypedArray()
Boolean::class.defaultType -> args.map { it.toBoolean() }.toTypedArray()
else -> null
}
fun foldingFunc(state: Pair<List<Any>, List<String>?>, par: KParameter): Pair<List<Any>, List<String>?> {
state.second?.let { scriptArgsLeft ->
try {
if (scriptArgsLeft.isNotEmpty()) {
val primArgCandidate = convertPrimitive(par.type, scriptArgsLeft.first())
if (primArgCandidate != null)
return@foldingFunc Pair(state.first + primArgCandidate, scriptArgsLeft.drop(1))
}
val arrCompType = (par.type.javaType as? Class<*>)?.componentType?.kotlin?.defaultType
val arrayArgCandidate = convertArray(arrCompType, scriptArgsLeft)
if (arrayArgCandidate != null)
return@foldingFunc Pair(state.first + arrayArgCandidate, null)
}
catch (e: NumberFormatException) {
} // just skips to return below
}
return state
}
try {
return scriptClass.getConstructor(Array<String>::class.java).newInstance(*arrayOf<Any>(scriptArgs.toTypedArray()))
}
catch (e: java.lang.NoSuchMethodException) {
for (ctor in scriptClass.kotlin.constructors) {
val (ctorArgs, scriptArgsLeft) = ctor.parameters.fold(Pair(emptyList<Any>(), scriptArgs), ::foldingFunc)
if (ctorArgs.size <= ctor.parameters.size && (scriptArgsLeft == null || scriptArgsLeft.isEmpty())) {
val argsMap = ctorArgs.zip(ctor.parameters) { a, p -> Pair(p, a) }.toMap()
try {
return ctor.callBy(argsMap)
}
catch (e: Exception) { // TODO: find the exact exception type thrown then callBy fails
}
}
}
}
return null
}
fun compileScript(environment: KotlinCoreEnvironment, paths: KotlinPaths): Class<*>? =
compileScript(environment,
{
@@ -481,7 +406,7 @@ object KotlinToJVMBytecodeCompiler {
configuration,
GenerationState.GenerateClassFilter.GENERATE_ALL,
module?.let(::TargetId),
module?.let { it.getModuleName() },
module?.let(Module::getModuleName),
module?.let { File(it.getOutputDirectory()) },
createOutputFilesFlushingCallbackIfPossible(configuration)
)