jbang icon indicating copy to clipboard operation
jbang copied to clipboard

Idea about Kotlin kts support

Open linux-china opened this issue 3 years ago • 2 comments

Add Kotlin script(kts) support for jbang because logic to run kts is different with kt source file.

  • create Kotlin script file: hello.kts
//KOTLIN 1.5.32

println("Hello, Kotlin Script!")
  • Use kotlinc to compile hello.kts file kotlinc hello.kts and get Hello.class
  • It's hard to run Hello.class by main method, and we should create a wrapper class to run Hello.class from the constructor method. Decompiled code as following from Hello.class. For more https://github.com/holgerbrandl/kscript/blob/5a2ffdc05f1a03afeb719ba09f3ca81b2f8c191e/src/main/kotlin/kscript/app/Kscript.kt#L244
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import kotlin.script.experimental.jvm.RunnerKt;
import kotlin.script.templates.standard.ScriptTemplateWithArgs;
import org.jetbrains.annotations.NotNull;

@Metadata(mv = {1, 6, 0}, k = 1, xi = 52, d1 = {"\000\022\n\002\030\002\n\002\030\002\n\000\n\002\020\021\n\002\020\016\030\0002\0020\001B\016\022\f\020\002\032\b\022\004\022\0020\0040\003"}, d2 = {"LHello;", "Lkotlin/script/templates/standard/ScriptTemplateWithArgs;", "args", "", ""})
public final class Hello extends ScriptTemplateWithArgs {
  public Hello(@NotNull String[] args) {
    super(args);
    System.out.println("good");
  }
  
  public static final void main(@NotNull String[] args) {
    Intrinsics.checkNotNullParameter(args, "args");
    RunnerKt.runCompiledScript(Hello.class, args);
  }
}
  • create a Wrapper class to run Kotlin script class and Kotin script class name is from runtime options. The command line to run kts almost like java -Dkts=Hello -classpath hello.kts.f2adcb7.jar:kts-runner-1.0.0.jar com.example.jbang.CompiledKtsRunner
public class CompiledKtsRunner {
    public static void main(String[] args) throws Exception {
        String ktsName = System.getProperty("kts");
        Class<?> clazz = CompiledKtsRunner.class.getClassLoader().loadClass(ktsName);
        clazz.getDeclaredConstructor(String[].class).newInstance(new Object[]{args});
    }
}
  • KtsSource.java in jbang. com.example.jbang:kts-runner:1.0.0" is Maven artifact for kts wrapper class.
public class KtsSource extends ScriptSource { 
    ....
    @Override
    public List<String> getRuntimeOptions() {
        String fileName = getResourceRef().getFile().getName();
        fileName = fileName.substring(0, fileName.lastIndexOf('.'));
        String ktClass = fileName.substring(0, 1).toUpperCase() + fileName.substring(1);
        return Arrays.asList("-Dkts=" + ktClass);
    }

    @Override
    public List<String> getAllDependencies() {
        final List<String> allDependencies = super.getAllDependencies();
        allDependencies.add("com.example.jbang:kts-runner:1.0.0");
        return allDependencies;
    }

    @Override
    public String getMainClass() {
        return "com.example.jbang.CompiledKtsRunner";
    }

     @Override
    public Predicate<ClassInfo> getMainFinder() {
        return pubClass -> false;
    }

}
  • Finally, use jbang hello.kts to run kts file.

linux-china avatar Dec 09 '21 16:12 linux-china

shouldn't we be able to treat .kst like we treat .jsh ? skips compile and pass it to jshell/kotlin directly ?

maxandersen avatar Dec 09 '21 20:12 maxandersen

@maxandersen I think main reason is about performance. It's very slow to execute Kotlin script by kotlin CLI, and compare as following:

kotlin hello.kts  6.50s user 0.47s system 198% cpu 3.522 total
java -Dkts=Hello -classpath  com.example.jbang.CompiledKtsRunner  0.11s user 0.04s system 33% cpu 0.436 total

This is the main reason that kscript adopts compile/jar solution, and not to use kotlin cli to execute script.

linux-china avatar Dec 09 '21 21:12 linux-china