3

I created simple utility for runtime compilation kotlin code:

package com.example

import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoot
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.com.intellij.openapi.Disposable
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.JvmTarget
import java.io.File
import kotlin.script.experimental.jvm.util.KotlinJars

class KotlinDynamicCompiler {
    fun compileScript(moduleName: String,
                      sourcePath: String,
                      saveClassesDir: File
    ): GenerationState {
        val stubDisposable = StubDisposable();
        val configuration = CompilerConfiguration()
        configuration.put(CommonConfigurationKeys.MODULE_NAME, moduleName)
        configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, PrintingMessageCollector(System.out, MessageRenderer.PLAIN_FULL_PATHS, true))
        configuration.put(JVMConfigurationKeys.OUTPUT_DIRECTORY, saveClassesDir)
        configuration.put(JVMConfigurationKeys.JVM_TARGET, JvmTarget.JVM_1_8)
        configuration.addKotlinSourceRoot(sourcePath)
        configuration.addJvmClasspathRoots(listOf(KotlinJars.stdlib))
        val env = KotlinCoreEnvironment.createForProduction(stubDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
        return KotlinToJVMBytecodeCompiler.analyzeAndGenerate(env)!!;
    }

    inner class StubDisposable : Disposable {
        @Volatile
        var isDisposed: Boolean = false
            private set

        override fun dispose() {
            isDisposed = true
        }

    };

}

And it works for code as

package com.example.kt

class SimpleClass(val str:String){
    fun test(){
    }
}
class UsedSimpleClass(val simpleClass: SimpleClass, val file: java.io.File) {
}

But it not works if I want to use no-base package classes as:

package com.example.kt

import com.example.pojo.TestPojo //class have in project that call runtime compilation 

class SimpleClass(val str:TestPojo){

}

or:

package com.example.kt

import com.fasterxml.jackson.databind.ObjectMapper //class have in project classpath where called runtime compilation 

class SimpleClass(val str:ObjectMapper){

}

How to pass current ClassLoader to KotlinToJVMBytecodeCompiler for dynamic (runtime) compilation kotlin code programmatically?


More details:

Test project on github with crashed test: https://github.com/nekkiy/dynamic-kotlin

Cause: We need use codegeneration and would like to test generated code. But I don't understand how to pass current classes environment.

Thanks for attention.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459

1 Answers1

2

Solution:

I have used method fun classpathFromClassloader(currentClassLoader: ClassLoader, unpackJarCollections: Boolean = false): List<File>? from kotlin.script.experimental.jvm.util.jvmClasspathUtil.kt and it works.

Result dynamic compiller:

package com.example

import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoots
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.com.intellij.openapi.Disposable
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.JvmTarget
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.PrintStream
import kotlin.script.experimental.jvm.util.KotlinJars
import kotlin.script.experimental.jvm.util.classpathFromClassloader

class KotlinDynamicCompiler {
    fun compileModule(moduleName: String,
                      sourcePath: List<String>,
                      saveClassesDir: File,
                      classLoader: ClassLoader? = null,
                      forcedAddKotlinStd: Boolean = true

    ): GenerationState {
        val stubDisposable = StubDisposable();
        val configuration = CompilerConfiguration()
        configuration.put(CommonConfigurationKeys.MODULE_NAME, moduleName)
        val baos = ByteArrayOutputStream()
        val ps: PrintStream = PrintStream(baos)
        configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, PrintingMessageCollector(ps, MessageRenderer.PLAIN_FULL_PATHS, true))
        configuration.put(JVMConfigurationKeys.OUTPUT_DIRECTORY, saveClassesDir)
//        configuration.put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true)
        configuration.put(JVMConfigurationKeys.JVM_TARGET, JvmTarget.JVM_1_8)
        val classPath = mutableSetOf<File>()
        if (classLoader != null) {
            classPath.addAll(classpathFromClassloader(classLoader)!!);
        }
        if (forcedAddKotlinStd) {
            classPath.add(KotlinJars.stdlib)
        }
        configuration.addJvmClasspathRoots(classPath.toList())
        configuration.addKotlinSourceRoots(sourcePath)
        val env = KotlinCoreEnvironment.createForProduction(stubDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
        val result = KotlinToJVMBytecodeCompiler.analyzeAndGenerate(env);
        ps.flush();
        if (result != null) {
            return result
        } else {
            throw IllegalStateException("Compilation error. Details:\n$baos")
        }

    }

    inner class StubDisposable : Disposable {
        @Volatile
        var isDisposed: Boolean = false
            private set

        override fun dispose() {
            isDisposed = true
        }

    };

}

Note: This function is contained in experimental package.

P.S. I also updated github-project.