How do you make a custom script definition work in IDEA?

I’m experimenting with the Kotlin scripting support. I have it working with a custom script host from the command line, and now I’d like it to work in an IDE too.

I have:

  1. Two Gradle modules under the same root project, one that defines the scripting classes and one that is empty.
  2. The second depends on the first in both implementation and kotlinScriptDef configurations.
  3. The first module contains the src/main/resources/META-INF/kotlin/script/templates directory with an empty file with the same fully qualified name as the script definition.
  4. The second module contains a test script but not in any source directory, just alongside the build.gradle.kts file.

Yet, no dice. If I create a file with the right registered extension IDEA knows it’s a Kotlin script, but it doesn’t understand my custom script definition. No code completion is offered for anything defined in it. I have built my project, and refreshed Gradle a bunch of times.

Any idea what I’m doing wrong?

OK, finally figured this out and got it working - the issues were not at all obvious, so I’ll write down all the things I was doing wrong here.

Firstly, the file name for discovery is not just the classname, it literally has to end in .classname
Secondly, I’m using Java 17, but IDEA uses Java 11. At this point it tried to load my script definition and took an exception because it couldn’t load the classes. I fixed this by switching IntelliJ to using JBR 17.

Next it couldn’t configure the classpath. I was using the following from the samples, which doesn’t work inside the IDE:

dependenciesFromCurrentContext(
    "etc", "etc",    
    "kotlin-scripting-dependencies",
    "tinylog-api-kotlin"
)

After several rounds of experiments (grep the IDEA logs for KOTLIN_SCRIPTING!) I got this piece of code which works:

val libraries = setOf(
    "etc",
    "etc",
    "kotlin-scripting-dependencies",
    "kotlin-stdlib",
    "kotlin-reflect",
    "tinylog-api-kotlin"
)
val cp: List<File>? = classpathFromClassloader(CompilationConfiguration::class.java.classLoader)
checkNotNull(cp) { "Could not read classpath" }
val filteredCP = cp.filter { element -> libraries.any { it in element.toString() } }
updateClasspath(filteredCP)

This is very similar logic to what the defaults do, so I’m not quite sure why mine works and the regular code doesn’t, I haven’t debugged it. I suspect there’s a problem with naming. I just switched to any substring instead of prefixes, and it is OK now.

NB: After making a change to try and fix this, I had to restart the IDE each time. It seems to remember that it failed to load the scripting provider.

After making these changes, I now have working code completion in scripts (albeit, it’s kinda slow). I still can’t actually run the script from the IDE - but it’ll do for now.

1 Like