Error when compiling with K2JVMCompiler dynamically in Java

Hi,

I am new to Kotlin. We are planning on using kotlin to run pluggable scripts, so I am trying to compile kotlin scripts in java and add to the available classes.

My java code puts the script text to a file and runs K2JVMCompiler.

For testing I am trying to compile this script:

class HelloWorld {
fun sayHello(inp : String) : String { return "Hello from dynamic Kotlin!" + inp; }
}

in java I run this:

private static final K2JVMCompiler KOTLIN_COMPILER = new K2JVMCompiler();
File kotlinSourceFile = new File(className + “.kt”);kotlinSourceFile.delete();Files.write(kotlinSourceFile.toPath(), code.getBytes());
ExitCode exitCode = KOTLIN_COMPILER.exec(System.out, kotlinSourceFile.getAbsolutePath(), “-d”, className + “.jar”);

which gives me this warning/error:

warning: unable to find kotlin-stdlib.jar in the Kotlin home directory. Pass either ‘-no-stdlib’ to prevent adding it to the classpath, or the correct ‘-kotlin-home’
warning: unable to find kotlin-script-runtime.jar in the Kotlin home directory. Pass either ‘-no-stdlib’ to prevent adding it to the classpath, or the correct ‘-kotlin-home’
warning: unable to find kotlin-reflect.jar in the Kotlin home directory. Pass either ‘-no-reflect’ or ‘-no-stdlib’ to prevent adding it to the classpath, or the correct ‘-kotlin-home’

HelloWorld.kt:1:1: error: cannot access built-in declaration ‘kotlin.Any’. Ensure that you have a dependency on the Kotlin standard library.
class HelloWorld
^^^^^^^^^^^^^^^^
HelloWorld.kt:3:79: error: cannot access built-in declaration ‘kotlin.String’. Ensure that you have a dependency on the Kotlin standard library.
fun sayhello(inp : String) : String { return “Hello from dynamic Kotlin!” + inp; }
^
HelloWorld.kt:3:81: error: cannot access built-in declaration ‘kotlin.String’. Ensure that you have a dependency on the Kotlin standard library.
fun sayhello(inp : String) : String { return “Hello from dynamic Kotlin!” + inp; }
^^^

My pom.xml has

<!-- https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-stdlib -->
<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-stdlib</artifactId>
    <version>2.2.10</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-script-runtime -->
<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-script-runtime</artifactId>
    <version>2.2.10</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-scripting-common -->
<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-scripting-common</artifactId>
    <version>2.2.10</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-scripting-jvm -->
<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-scripting-jvm</artifactId>
    <version>2.2.10</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-scripting-dependencies -->
<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-scripting-dependencies</artifactId>
    <version>2.2.10</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-compiler-embeddable -->
<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-compiler-embeddable</artifactId>
    <version>2.2.10</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-scripting-compiler-embeddable -->
<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-scripting-compiler-embeddable</artifactId>
    <version>2.2.10</version>
    <!-->runtime</scope-->
</dependency>

<!-- https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-scripting-jsr223 -->
<dependency>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-scripting-jsr223</artifactId>
    <version>2.2.10</version>
    <!-->runtime</scope-->
</dependency>
<!-- https://mvnrepository.com/artifact/org.jetbrains.kotlin.jvm/org.jetbrains.kotlin.jvm.gradle.plugin -->
<dependency>
    <groupId>org.jetbrains.kotlin.jvm</groupId>
    <artifactId>org.jetbrains.kotlin.jvm.gradle.plugin</artifactId>
    <version>2.2.10</version>
    <type>pom</type>
    <!--scope>runtime</scope-->
</dependency>

Any idea on what I need to do to get the compiler to see the appropriate kotlin library/libraries?

Do I need to install kotlin locally and set the kotlin home?
I would have thought the point of embedding the compiler is that it would not require that.

Apparently it seems I do need to install the kotlin jars on the filesystem and point K2JVMCompiler.exec to them, unless there’s a way to hook the project dependencies up to the execution.

How are you running your code?

If you run it through Maven, Maven should include all the libraries on the classpath. But if you’re using Maven to compile, and then run the compiled code directly using Java, then Java doesn’t know about all the dependencies. In the latter case, you would need to create a shaded Jar (or fat Jar, or uber Jar) that includes all the dependencies.

I am compiling in java using the K2JVMCompiler class.
To be clear - I want to compile and execute ad-hoc kotlin scripts as pluggables within a java app.

So I am compiling those scripts in java using the K2JVMCompiler class.

The only way I can get this to work is by also having the installation of kotlinc locally on my server, and referencing that installation during the compilation in the java code, ie

ExitCode exitCode = (new K2JVMCompiler()).exec(System.out, kotlinSourceFile.getAbsolutePath(), "-kotlin-home", KOTLIN_HOME, "-d", jarFile);

where KOTLIN_HOME points to the install on the filesystem.

I was hoping this was not needed and that K2JVMCompiler could pick those jars up from the dependencies given to the java app.

If I understand what you want, you want to compile Kotlin code AT RUNTIME. So something like this pseudo-code.

public class RuntimeCompiler {
    public static void main(String[] args) {
        var filename = args[0];
        var file = new File(filename);
        try {
            file.open();
        } catch (FileNotFoundException e) {
            System.out.println("File " + filename + " was not found.");
            return;
        }

        var kotlinCompiler = new KotlinCompiler();
        var compiledCode = kotlinCompiler.compile(file);
    }
}
java RuntimeCompiler.class KotlinCode.kt

Is that correct? So you COMPILE the Java code, then you RUN the Java code, and the running Java code then COMPILES the Kotlin code.

The reason it’s important to make this distinction is to understand what libraries are in the class path for each action. When you compile via Maven, Maven will provide all of the libraries to the compiler, so it can compile the code. But usually by default, Maven will compile your code to individual class files, and a jar file that contains all of YOUR code. So when you run your jar file or your compiled code, you have to provide all of the other libraries to the class path.

I apologise if I’m going over things you already know, but I want to make sure we’re both on the same page about what we’re talking about.

Does what I’ve said so far make sense? That you’re compiling Kotlin code in your RUNNING Java code?

Yes, that’s correct.

The kotlin code is pluggable - supplied to a running java app as text, compiled, put into the JVM and executed.

So the running java code includes the appropriate kotlin jars to execute the compiled kotlin classes, but the compilation of the kotlin code inside java still requires me to include a link to the filesystem for it to have the same kotlin jars as are included for execution, but just for compilation and if I don’t do that, I can’t compile the kotlin inside the running java code.

So how are you running your Java code, is the ultimate question? Cause I would think that the compiler needs the Kotlin stdlib to be on the classpath at runtime, which if you run the code through Maven, should be the case.

How do you know the running Java code includes the appropriate Kotlin Jars for execution?

This is a spring boot app.
Having compiled kotlin at runtime by referencing external jars in the file system, the compiled kotlin class is executed inside the java app. The app has the appropriate maven inclusions for kotlin.

So it works, except I do not want to have to both reference the kotlin jars as part of the spring boot project, as well as have a local file directory reference to them to compile the kotlin.