How do I run `main.kts` files now that `kotlin` is...
# scripting
m
How do I run
main.kts
files now that
kotlin
is kotlin toolchain? I get this
Copy code
$ kotlin foo.main.kts
Usage: kotlin [<options>] <command> [<args>]...

Error: no such subcommand foo.main.kts
j
We're going to address this very soon. The most likely plan is that the existing Kotlin distribution will get a copy of the
kotlin
executable named
kotlinr
(TBD) and we'll also add support to run scripts in the Kotlin Toolchain
👍 1
v
In the meantime you can use
kotlinc -script foo.main.kts
instead
👍 1
m
Silly question but
kotlinc
is not part of the kotlin toolchain, right? I have to install it separately?
v
Isn't Kotlin toolchain a rebranded Amper, so an alternative build tool / Gradle alternative? It is probably not so much the right thing to run a Kotlin script, but somewhere it should still have a Kotlin distribution around where
kotlinc
and maybe even the old
kotlin
are around.
m
Yes, also what I think
j
The Kotlin Toolchain is actually meant to serve all user-level needs, not only build-tool-specific things. This is why it's not really just a rebranding of Amper (although it does start off as a rename of the codebase). So running scripts is definitely planned there, and ideally users shouldn't have to download/install anything else manually.
The current situation where you still have to install
kotlinc
for scripts is not ideal. Hence we why we're trying to address this.
🙏 1
m
BTW, why
kotlinr
?
v
`kotlinr`unner The current
kotlin
is mainly a wrapper around
kotlinc
with supplying proper arguments afair.
💯 1
@Joffrey I hope it will then still work to just call the
*.main.kts
file. For this the
kotlin
call should not require further arguments to run the script, because a Shebang with (multiple) arguments is not portable.
j
@Vampire thank you! We are aware of this shebang case, and are trying to think about solutions for it. At the moment if we go with
kotlin run script.main.kts
it would indeed have the portability problem you mention.
m
FWIW, and for an additional anecdotal data point, I'm also running all my scripts with shebang
v
Actually, as far as I remember
kotlin run script.main.kts
would work. One argument between the executable and the file should be portable. As long as the shebang does not use
env
like
#!/usr/bin/env kotlin
which is a security issue anyway. In that case the
run
would be a second parameter which is then not portable. With
#!/what/ever/kotlin run
it should be portable, but only works if you know the path to the
kotlin
executable.
thank you color 1
m
Looks like
kotlinr
it is, right? https://github.com/JetBrains/kotlin/pull/6289
j
Yes, the
kotlinr
executable will be present in the Kotlin compiler distribution 2.4.10. It's better to replace your shebangs to this if you want to use both scripting and the Kotlin Toolchain together for now.
👍 1
I have added some support for scripting with
kotlin run --script foo.main.kts
(which can be simplified to
kotlin run foo.main.kts
if you run it outside any project) in the most recent dev versions of the Kotlin Toolchain, but I'm not sure this will be a long term solution. We're contemplating the idea of focusing on running single
.kt
files (real Kotlin files with a main function) instead of scripts, which are a bit of an oddity in the Kotlin language.
@Vampire @mbonnin could you please share your use cases for scripting? Things like: 1. Why do you use scripts for? If it's for management tasks inside a project, would a local plugin with
kotlin do X
cover what you need? Do you use scripts for some other types of tasks, outside Kotlin projects? 2. Do you usually have a single file? Or does it import others? (Do you ever use
@file:Import
?) 3. Do you often need Maven dependencies? If yes, are they always the same (e.g. coroutines/serialization/ktor client)? Or do you often use random dependencies, like a specific SDK or something?
m
I use them for all kinds of things: release scripts, data mining, handling of my email filters rules, running stats on my repos, modifying JSON, etc... Basically everything that Claude is doing by writing python, I want to do in kotlin
I really want them outside the build process because I don't want to pay configuration time but also more philosophically, I think of them as something different, they could even modify the build itself
I always used single file
thank you color 1
j
because I don't want to pay configuration time
Would reading the project model (< 0.5s) count as configuration time in your mind? Or are you referring specifically to Gradle's configuration phase?
m
Sub second is probably ok but regardless, I don't think I want them in my build process
I don't really want it to contribute remote build cache, etc... Often times I don't even have a build tool
Maybe my release scripts could become part of the build if startup time is faster but my email filters scripts don't need a build tool
j
but my email filters scripts don't need a build tool
But those wouldn't live inside a project anyway, right?
m
Just something that run in a GitHub action
No, there's no project
Just a file
j
I see, yeah I was talking about
kotlin do X
with the plugin approach (hopefully streamlined a bit better) only for project-management things that already live in a project. The case for "a single file that lives on its own" is still relevant, and this is the case for which I was asking 2) and 3).
m
Yup. This is why I don't care much about
file:Import
. As soon as I need 2 files, I create a proper project
But the single file use case is super important to me
For dependencies, I use
@file:DependsOn
all the time (JSON parsers, io, CSV, etc...)
j
> As soon as I need 2 files, I create a proper project This is the assumption I would like to try and verify, because it's my feeling as well. For Gradle projects, it was debatable because the overhead of creating a project is a bit higher, but for Kotlin Toolchain, you technically just need a
module.yaml
file, and to move your file under
src
, and you are good to go. So I'm assuming it might be ok to require a "real project" (a
module.yaml
) when you have 2+ files. The remaining question being dependencies.
m
In some projects, I have a bunch of
JavaExec
task for one-of things (in Gradle land) and TBH, that works quite well. I don't need specific plugin support I think? "Just" an easy way to start a process locally with a given classpath
v
could you please share your use cases for scripting? Things like:
While it is also nice to have Kotlin for writing - well - scripts instead of using Bash or similar, what I mainly use it for is github.com/typesafegithub/github-workflows-kt to write typesafe convenient GitHub Actions Workflow files in Kotlin instead of bloody YAML files. Especially for more complex ones like github.com/Vampire/setup-wsl/blob/…/test.main.kts. cc @Piotr Krzemiński
Do you usually have a single file? Or does it import others? (Do you ever use
@file:Import
?)
Yes, definitely, to be able to follow DRY.
Do you often need Maven dependencies?
Yes, always
If yes, are they always the same (e.g. coroutines/serialization/ktor client)?
No, always different.
👀 1
j
I don't need specific plugin support I think? "Just" an easy way to start a process locally with a given classpath
Yeah the ergonomics question of how to declare a custom command will most likely come later. If this became the recommended way to do management tasks, we will probably want to work on reducing boilerplate for them.
m
I haven't looked at Amper too much in that area but Gradle is pretty much straightforward: make a module with
JavaExec
task. The task takes classpath and java version as input.
j
@Vampire this case seems to be a perfect example of something that could be replaced by a very simple Kotlin Toolchain single-module project:
Copy code
src/
  main.kt
  other.kt
module.yaml # 'product: jvm/app' + dependencies, maybe more if you need
And run with
kotlin run
👍 1
v
Might be, didn't really look at that. But it seems to be much more hassle than just having one or two script files. Especially as different scripts need different dependencies. So for 5 Scripts with different dependencies that use one common file I would then need 6 projects? 1 for the common and one for each of the 5 others that then somehow depend on the common one etc.? Sounds very cumbersome compared to what we have now.
j
Actually let me backtrack, this might also fit the other approach of having someone write a proper scripting host (here the
github-workflow-kt
author could provide some executable to serve as runtime to simplify the generation), which is able to load and run scripts with its own rules, without the need for the Kotlin Toolchain to be able to run it in a simple command.
v
github-workflow-kt
is not an action. It is just a Kotlin library that allows to write GHA workflow files as Kotlin scripts conveniently. You can currently simple execute that scirpt and get the necessary YAML file produced.
j
Yes, sorry, I edited my message right before your answer to account for this 😅
v
I sent my message after your edit, so maybe I did not get you right?
j
So, in short, you could really just have an independent module with all you need, and use
kotlin run
to do the generation. At that point you're not really leveraging the fact that those workflows are scripts
The current flow: • you write a script with a dependency on the
github-workflow-kt
library, so you can use a nice DSL to express your workflow • you run the script (in some way), which generates real Github workflow YAML files • you commit these YAML files to make Github process them
v
Well, being able to run the script as is as executable file is quite handy. Also for Bash-script replacements. If you take this away, maybe one could use polyglot files that are bash scripts and Kotlin files or something like that. But it will definitely be a step back imho.
j
The
github-workflow-kt
case seems to be about managing workflows that are for a project. If this project is already a Kotlin Toolchain project, then this workflow generation could just be a local plugin module exposing a
generateWorkflows
custom command. And then you could do
kotlin do generateWorkflows
. It's a very mild downgrade in convenience (you have to mode your script file to some dir, but you can run it really easily too. You can also manage the location of the generated YAML, and even make sure they are in sync as part of your build by registering a
check
for this. I guess you could even do this for Gradle projects in general, by writing a Gradle plugin. If this is not a Kotlin Toolchain nor Gradle project (I guess not a Kotlin project at all), you could use the single-module project approach I mentioned above, and just run
kotlin run
instead of
./my-workflow.main.kts
(or just click the play button in IJ, which is completely equivalent). The
github-workflow-kt
library author could add any scaffolding they need to make it easy to write a
main()
function (they're kinda already there).
being able to run the script as is as executable file is quite handy
Do you see drawbacks in the above approaches regarding this? Would some of these make it less handy (e.g.
kotlin do generateWorkflows
or
kotlin run
?)
Also for Bash-script replacements.
yeah I was mostly considering the Github workflows case right now
v
The
github-workflow-kt
case seems to be about managing workflows that are for a project.
Yes, but they are independent of any project setup. They can be for a Kotlin project with Gradle build, but they can also be for literally anything you run a GHA for. And it must not be run through any build tool, because before even running the actual build, the consistency of the YAML with the Workflow script must be ensured as GitHub does not yet have a built-in support for this.
Do you see drawbacks in the above approaches regarding this? Would some of these make it less handy (e
Yes, definitely. I have in
~/bin
a file
foo.main.kts
and then I can run anywhere on my computer
foo bla
to run the script just like any other command or bash script on the path. With those I would have to make it a polyglot script if possible so that it is a bash script that runs itself with running
ktlin run
or have second "starter script" that runs the actual script. And also providing such scripts as commands through package managers will have the same drawbacks etc.
yeah I was mostly considering the Github workflows case right now
For the GHA case it is still handy that you can as fast as possible run the command, especially as it runs before each workflow execution to ensure consistency of the YAML. I indeed usually also have a
proprocessWorkflows
Gradle task if it is in a Gradle project to be able to run the scripts as Gradle task and as part of the
check
task. But as I said, the GHA workflow YAML generation is absolutely independent of the type of project or build tool and should even run without any build tool being available.
p
github-workflows-kt's owner here - busy family time so just in read-only mode of this thread for now, but happy to build some PoC on top of some new API to test-run things in practice
❤️ 1
j
And it must not be run through any build tool, because before even running the actual build, the consistency of the YAML with the Workflow script must be ensured as GitHub does not yet have a built-in support for this
How does it work for you right now? You later said it actually went through the Gradle build tool, integrated with the
check
task, so I'm not sure I follow
v
I said in my own Gradle based projects I also have additionally such a task. The GHA workflow that is generated has as first step a run step that executes the script and checks whether the generated YAML changed.
p
The consistency check job looks like this: https://github.com/typesafegithub/github-actions-typing/blob/9caadf311fac3349c053cf0644a5aba277d7a6b7/.github/workflows/build.yaml#L12. It's not even aware of Kotlin (apart from the script's file extension) because the library instructs the users the use shebangs