jbang
jbang copied to clipboard
Idea about Kotlin kts support
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.ktsand 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.ktsto run kts file.
shouldn't we be able to treat .kst like we treat .jsh ? skips compile and pass it to jshell/kotlin directly ?
@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.