Kotlin 自定义脚本入门——教程

Kotlin scripting is Experimental. It may be dropped or changed at any time. Use it only for evaluation purposes. We appreciate your feedback on it in YouTrack.

Kotlin 自定义脚本入门——教程 - 图1

Kotlin scripting is the technology that enables executing Kotlin code as scripts without prior compilation or packaging into executables.

For an overview of Kotlin scripting with examples, check out the talk Implementing the Gradle Kotlin DSL by Rodrigo Oliveira from KotlinConf’19.

In this tutorial, you’ll create a Kotlin scripting project that executes arbitrary Kotlin code with Maven dependencies. You’ll be able to execute scripts like this:

  1. @file:Repository("https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven")
  2. @file:DependsOn("org.jetbrains.kotlinx:kotlinx-html-jvm:0.7.3")
  3. import kotlinx.html.*
  4. import kotlinx.html.stream.*
  5. import kotlinx.html.attributes.*
  6. val addressee = "World"
  7. print(
  8. createHTML().html {
  9. body {
  10. h1 { +"Hello, $addressee!" }
  11. }
  12. }
  13. )

The specified Maven dependency (kotlinx-html-jvm for this example) will be resolved from the specified Maven repository or local cache during execution and used for the rest of the script.

Project structure

A minimal Kotlin custom scripting project contains two parts:

  • Script definition – a set of parameters and configurations that define how this script type should be recognized, handled, compiled, and executed.
  • Scripting host – an application or component that handles script compilation and execution – actually running scripts of this type.

With all of this in mind, it’s best to split the project into two modules.

Before you start

Download and install the latest version of IntelliJ IDEA.

Create a project

  1. In IntelliJ IDEA, select File | New | Project.
  2. In the panel on the left, select New Project.
  3. Name the new project and change its location if necessary.

    Select the Create Git repository checkbox to place the new project under version control. You will be able to do it later at any time.

    Kotlin 自定义脚本入门——教程 - 图2

  4. From the Language list, select Kotlin.

  5. Select the Gradle build system.

  6. From the JDK list, select the JDK that you want to use in your project.

    • If the JDK is installed on your computer, but not defined in the IDE, select Add JDK and specify the path to the JDK home directory.
    • If you don’t have the necessary JDK on your computer, select Download JDK.
  7. Select the Kotlin or Gradle language for the Gradle DSL.

  8. Click Create.

Create a root project for custom Kotlin scripting

Add scripting modules

Now you have an empty Kotlin/JVM Gradle project. Add the required modules, script definition and scripting host:

  1. In IntelliJ IDEA, select File | New | Module.
  2. In the panel on the left, select New Module. This module will be the script definition.
  3. Name the new module and change its location if necessary.
  4. From the Language list, select Java.
  5. Select the Gradle build system and Kotlin for the Gradle DSL if you want to write the build script in Kotlin.
  6. As a module’s parent, select the root module.
  7. Click Create.

    Create script definition module

  8. In the module’s build.gradle(.kts) file, remove the version of the Kotlin Gradle plugin. It is already in the root project’s build script.

  9. Repeat previous steps one more time to create a module for the scripting host.

The project should have the following structure:

Custom scripting project structure

You can find an example of such a project and more Kotlin scripting examples in the kotlin-script-examples GitHub repository.

Create a script definition

First, define the script type: what developers can write in scripts of this type and how it will be handled. In this tutorial, this includes support for the @Repository and @DependsOn annotations in the scripts.

  1. In the script definition module, add the dependencies on the Kotlin scripting components in the dependencies block of build.gradle(.kts). These dependencies provide the APIs you will need for the script definition:

【Kotlin】

  1. dependencies {
  2. implementation("org.jetbrains.kotlin:kotlin-scripting-common")
  3. implementation("org.jetbrains.kotlin:kotlin-scripting-jvm")
  4. implementation("org.jetbrains.kotlin:kotlin-scripting-dependencies")
  5. implementation("org.jetbrains.kotlin:kotlin-scripting-dependencies-maven")
  6. // coroutines dependency is required for this particular definition
  7. implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
  8. }

【Groovy】

  1. dependencies {
  2. implementation 'org.jetbrains.kotlin:kotlin-scripting-common'
  3. implementation 'org.jetbrains.kotlin:kotlin-scripting-jvm'
  4. implementation 'org.jetbrains.kotlin:kotlin-scripting-dependencies'
  5. implementation 'org.jetbrains.kotlin:kotlin-scripting-dependencies-maven'
  6. // coroutines dependency is required for this particular definition
  7. implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.1'
  8. }
  1. Create the src/main/kotlin/ directory in the module and add a Kotlin source file, for example, scriptDef.kt.

  2. In scriptDef.kt, create a class. It will be a superclass for scripts of this type, so declare it abstract or open.

    1. // abstract (or open) superclass for scripts of this type
    2. abstract class ScriptWithMavenDeps

    This class will also serve as a reference to the script definition later.

  3. To make the class a script definition, mark it with the @KotlinScript annotation. Pass two parameters to the annotation:

    • fileExtension – a string ending with .kts that defines a file extension for scripts of this type.
    • compilationConfiguration – a Kotlin class that extends ScriptCompilationConfiguration and defines the compilation specifics for this script definition. You’ll create it in the next step.
    1. // @KotlinScript annotation marks a script definition class
    2. @KotlinScript(
    3. // File extension for the script type
    4. fileExtension = "scriptwithdeps.kts",
    5. // Compilation configuration for the script type
    6. compilationConfiguration = ScriptWithMavenDepsConfiguration::class
    7. )
    8. abstract class ScriptWithMavenDeps
    9. object ScriptWithMavenDepsConfiguration: ScriptCompilationConfiguration()

    In this tutorial, we provide only the working code without explaining Kotlin scripting API. You can find the same code with a detailed explanation on GitHub.

    Kotlin 自定义脚本入门——教程 - 图6

  4. Define the script compilation configuration as shown below.

    1. object ScriptWithMavenDepsConfiguration : ScriptCompilationConfiguration(
    2. {
    3. // Implicit imports for all scripts of this type
    4. defaultImports(DependsOn::class, Repository::class)
    5. jvm {
    6. // Extract the whole classpath from context classloader and use it as dependencies
    7. dependenciesFromCurrentContext(wholeClasspath = true)
    8. }
    9. // Callbacks
    10. refineConfiguration {
    11. // Process specified annotations with the provided handler
    12. onAnnotations(DependsOn::class, Repository::class, handler = ::configureMavenDepsOnAnnotations)
    13. }
    14. }
    15. )

    The configureMavenDepsOnAnnotations function is as follows:

    1. // Handler that reconfigures the compilation on the fly
    2. fun configureMavenDepsOnAnnotations(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics<ScriptCompilationConfiguration> {
    3. val annotations = context.collectedData?.get(ScriptCollectedData.collectedAnnotations)?.takeIf { it.isNotEmpty() }
    4. ?: return context.compilationConfiguration.asSuccess()
    5. return runBlocking {
    6. resolver.resolveFromScriptSourceAnnotations(annotations)
    7. }.onSuccess {
    8. context.compilationConfiguration.with {
    9. dependencies.append(JvmDependency(it))
    10. }.asSuccess()
    11. }
    12. }
    13. private val resolver = CompoundDependenciesResolver(FileSystemDependenciesResolver(), MavenDependenciesResolver())

    You can find the full code here.

Create a scripting host

The next step is creating the scripting host – the component that handles the script execution.

  1. In the scripting host module, add the dependencies in the dependencies block of build.gradle(.kts):
    • Kotlin scripting components that provide the APIs you need for the scripting host
    • The script definition module you created previously

【Kotlin】

  1. dependencies {
  2. implementation("org.jetbrains.kotlin:kotlin-scripting-common")
  3. implementation("org.jetbrains.kotlin:kotlin-scripting-jvm")
  4. implementation("org.jetbrains.kotlin:kotlin-scripting-jvm-host")
  5. implementation(project(":script-definition")) // the script definition module
  6. }

【Groovy】

  1. dependencies {
  2. implementation 'org.jetbrains.kotlin:kotlin-scripting-common'
  3. implementation 'org.jetbrains.kotlin:kotlin-scripting-jvm'
  4. implementation 'org.jetbrains.kotlin:kotlin-scripting-jvm-host'
  5. implementation project(':script-definition') // the script definition module
  6. }
  1. Create the src/main/kotlin/ directory in the module and add a Kotlin source file, for example, host.kt.

  2. Define the main function for the application. In its body, check that it has one argument – the path to the script file – and execute the script. You’ll define the script execution in a separate function evalFile in the next step. Declare it empty for now.

    main can look like this:

    1. fun main(vararg args: String) {
    2. if (args.size != 1) {
    3. println("usage: <app> <script file>")
    4. } else {
    5. val scriptFile = File(args[0])
    6. println("Executing script $scriptFile")
    7. evalFile(scriptFile)
    8. }
    9. }
  3. Define the script evaluation function. This is where you’ll use the script definition. Obtain it by calling createJvmCompilationConfigurationFromTemplate with the script definition class as a type parameter. Then call BasicJvmScriptingHost().eval, passing it the script code and its compilation configuration. eval returns an instance of ResultWithDiagnostics, so set it as your function’s return type.

    1. fun evalFile(scriptFile: File): ResultWithDiagnostics<EvaluationResult> {
    2. val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<ScriptWithMavenDeps>()
    3. return BasicJvmScriptingHost().eval(scriptFile.toScriptSource(), compilationConfiguration, null)
    4. }
  4. Adjust the main function to print information about the script execution:

    1. fun main(vararg args: String) {
    2. if (args.size != 1) {
    3. println("usage: <app> <script file>")
    4. } else {
    5. val scriptFile = File(args[0])
    6. println("Executing script $scriptFile")
    7. val res = evalFile(scriptFile)
    8. res.reports.forEach {
    9. if (it.severity > ScriptDiagnostic.Severity.DEBUG) {
    10. println(" : ${it.message}" + if (it.exception == null) "" else ": ${it.exception}")
    11. }
    12. }
    13. }
    14. }

You can find the full code here

Run scripts

To check how your scripting host works, prepare a script to execute and a run configuration.

  1. Create the file html.scriptwithdeps.kts with the following content in the project root directory:

    1. @file:Repository("https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven")
    2. @file:DependsOn("org.jetbrains.kotlinx:kotlinx-html-jvm:0.7.3")
    3. import kotlinx.html.*; import kotlinx.html.stream.*; import kotlinx.html.attributes.*
    4. val addressee = "World"
    5. print(
    6. createHTML().html {
    7. body {
    8. h1 { +"Hello, $addressee!" }
    9. }
    10. }
    11. )

    It uses functions from the kotlinx-html-jvm library which is referenced in the @DependsOn annotation argument.

  2. Create a run configuration that starts the scripting host and executes this file:

    1. Open host.kt and navigate to the main function. It has a Run gutter icon on the left.
    2. Right-click the gutter icon and select Modify Run Configuration.
    3. In the Create Run Configuration dialog, add the script file name to Program arguments and click OK.

      Scripting host run configuration

  3. Run the created configuration.

You’ll see how the script is executed, resolving the dependency on kotlinx-html-jvm in the specified repository and printing the results of calling its functions:

  1. <html>
  2. <body>
  3. <h1>Hello, World!</h1>
  4. </body>
  5. </html>

Resolving dependencies may take some time on the first run. Subsequent runs will complete much faster because they use downloaded dependencies from the local Maven repository.

下一步做什么?

Once you’ve created a simple Kotlin scripting project, find more information on this topic: