Search code examples
kotlinintellij-ideascripting

In IntelliJ IDEA, what is the proper way to create and execute kotlin scripts (.kts) that depends on code in my project?


I have a simple IntelliJ IDEA kotlin project with standard directory layout, i.e.:

src/
  main/
    kotlin/
      ClassA.kts
      ClassB.kts
    resources/
  test/
    kotlin/
      TestClassA.kts
      TestClassB.kts

In this project I want to use kotlin scripts (.kts files) and I want to use IntelliJ to edit and execute these scripts.

Inside the scripts I need to use classes, functions etc. defined in main, like ClassA (using stuff from test would also be nice). To aid in writing the scripts, I would like to get code completion and other coding assistance from IntelliJ.

Questions:

  • Where should I put the .kts files?
  • How do I specify that the script(s) should use my project's classpath when executing scripts?
  • How do I make IntelliJ provide code completion in the script file?
  • To me it looks like only kotlin scratch files and kotlin worksheet files work properly (see https://kotlinlang.org/docs/run-code-snippets.html).
  • What I want is proper scripts that are stored along the other source code in the project, and a way to run them either from IntelliJ run configurations or from the terminal.

I use IntelliJ IDEA 2022.3.1 / kotlin plugin 223-1.7.21-release-272-IJ8214.52.

See also the IntelliJ issue tracker: https://youtrack.jetbrains.com/issue/KT-55833/Kotlin-scripting-should-have-more-documentation-and-it-should-be-easier-to-execute-scripts-from-the-command-line


Solution

  • The point of .main.kts is that it's simple and doesn't require a build system. So depending on a project which is built with Maven/Gradle/Bazel is only possible through dependencies.

    "IntelliJ IDEA kotlin project" sounds like you might not even have a build system which pretty much rules out running from command line, even if direct linking was possible. In the next sections I'll assume Gradle, because that's what I'm familiar with, but equivalents could work otherwise in other systems.

    Before I go down the rabbit hole, I would like to call XYProblem, just because you might not need a .kts, a simple main.kt with fun main() could work as well. And then running with java -jar on the resulting app or gradlew :module:run --args "..." would just work normally without any special treatment. Oddly enough just today I read this article which describes in detail how to make a small easily-runnable Kotlin app.

    One option is that you use @file:Import, but I haven't gotten it to work, and it seems others neither.

    <rabbithole> So the only workable idea I have for this is that you have your library publish a jar to local repository (Gradle mavenLocal()) or import from a local build folder (flatDir or with maven { url = file://). Note that these are Gradle APIs, which means that Gradle can publish to these in some way.

    At this point we have a .jar file of your library in src/main/kotlin on the local file system. .main.kts files can import JARs from Maven repositories (example). If you're lucky this built-in @file:Repository also supports local file:// URIs, so it "just works".

    Note: location of .main.kts files don't matter, only their file name, which makes a huge difference.

    The developer experience with this solution would be:

    • write code in src/main/kotlin
    • write unit tests in src/test/kotlin
    • build
    • refresh classpath of app.main.kts after build
    • code as normal because code completion works (navigation might not)

    </rabbithole>, notice that the above is pretty much the same as having a main.kt file in your app, with the difference that main.kt is fully supported and you can do things like pre-compilation, minification, packaging.


    You can find some .main.kts examples here: https://github.com/TWiStErRob/TWiStErRob-env/search?q=filename%3Amain.kts, but they're not using local code, all single-file only with mavenCentral() dependencies.