anyone interested in being able to run any Kotlin ...
# github-workflows-kt
p
anyone interested in being able to run any Kotlin code within a GH Actions step? Like:
Copy code
runKotlin {
            println("Hello from action logic!")
            File("output-first.txt").writeText("Written from first Kotlin logic")
        }
I'm curious if you want me to implement such feature 🙂 I'd find it useful because it would allow to stay in the Kotlin world instead of running bash commands or using actions even for simplest tasks
the implementation looks like this: start a server which will host pieces of logic passed in workflow definition, and then as GitHub runs the steps, call the appropriate server endpoint
it's possible even now with the library, but with a lot of boilerplate. I want to make it a first-class feature
b
I think it could be implemented without the server, but with script preparse step. Something along the lines of how gradle plugins block is handled.
Alternatively, just use kotlin string template with IDE language injection.
Just trying to avoid overengineering where possible 😀
Another option, inject compiler plugin to capture runKotlin blocks in code and convert them to strings.
In any case, main concern will probably be leaking scope where it should not leak.
p
I don't quite get the "preparsing" idea, how would you implement it?
Kotlin as string - would it provide proper IDE support?
Compiler plugin: it's challenging to call compiler plugins with Kotlin scripting. Do you know if it's possible? It was my initial idea
b
From what I know how it's done in gradle, it basically evaluates your buildscript only capturing logic in the plugins block to do some work before it goes to evaluate your buildscript again to capture the rest of it
p
Leaking scope: yes, I'm aware this solution is not perfect, but it's just the first iteration and still gives some interesting power :) would require some knowledge what not to do
b

https://youtu.be/OEFwnWxoazI

This was quite an eye opener to me for kotlin scripting
p
I'll have a look, thanks! Great feedback :)
b
But overall I think it's best to keep it simple and expose kotlin actions via string templates with language injection. This would allow adding dependencies to those scripts as well as provide some safeguards for leaking scopes
Maybe expose some dedicated property for dependencies to make it cleaner.
p
Do you have a moment to prepare a PoC using the library? Doesn't even have to compile, just to get a grasp on the API you propose. I don't have experience with language injections
b
I don't have any experience with it either, but I'll see what I can piece together.
💪 1
For string templates option, the API could look something like this:
Copy code
kotlinScript(
  someActionProp = "xxx",
  jvmVersion = "1.1"
  dependencies = arrayOf("some.group:artefact:1.1.1") // <- vararg
) {
  // language=kotlin
  """
  println("Hello world")
  """
}
Which would then generate the script by prepending dependency annotations to returned string.
Copy code
@file:Dependency("some.group:artefact:1.1.1")
println("Hello world")
Alternatively, optional properties could be moved into the lambda
Copy code
kotlinScript {
  someActionProp = "xxx",
  jvmVersion = "1.1"
  dependencies {
    +"some.group:artefact:1.1.1"
  }
  repositories {
    mavenCentral()
    maven("<https://url.com>") {
      username="xxx"
      password="${{ GH_SECRET }}"
    }
  }

  // language=kotlin
  """
  println("Hello world")
  """
}
Just found this @Language annotation that you could potentially apply to lambda return type to get the injection on the consumer side automatically
p
Will it provide proper compile-time validation, or just IDE support?
b
Just IDE support.
😔 1
Here's a PoC though
Copy code
class Sandbox {
  private fun kotlinScript(script: () -> /* language=kotlin */ String) {
  }

  @Test
  fun main() {
    kotlinScript {
      """
      fun main() {
      
      }
      """.trimIndent()
    }
  }
}
🙏 1
One problem, though. There's no language injection support for kotlinScript, only kotlin. This would mean that top-level invocations will be highlighted as errors.
🙏 1
p
Thanks, I'll prepare a summary of proposed approaches, seems like there's no perfect one and each has pros and cons (as usual)
n
i would probably prefer to keep my gh-action generating and code to execute seperated anyways.. and then i can just use
run(name= "execute kts", command = "./$pathToScript")
and this lets me use executable script files i can test by hand and such
what might be generally easier to do is a gradle plugin that can search your project for files and generate some boilerplate code eg. ready to use actions for each
.main.kts
file in a specific defined location then you just have to include the actions and add them to the workflow/job
p
i would probably prefer to keep my gh-action generating and code to execute seperated anyways.. and this lets me use executable script files i can test by hand and such
interesting point of view 🙂 well, it's already supported and the boilerplate is minimal. However, this whole idea started with a different mindset. Best if you don't have to know that there's some YAML under the hood, and just write your workflow instead. Then this separation - action generating code and logic code - would be artificial, hence my attempts to have them in a single place in a type-safe way
the similar approach is used e.g. in Gradle or multiple other build systems - both the tasks layout and tasks' logic is in a single place