在Gradle KTS中是否有一种方法可以实例化KTS脚本引擎?

4

我想在我的项目构建过程中使用第三方库。 该库的方法需要ScriptEngine。当我尝试实例化它时,我遇到了一个错误:

java.lang.IllegalArgumentException: Unable to construct script definition: Unable to load base class kotlin.script.experimental.api.KotlinType@42842bb8
    at kotlin.script.experimental.host.ConfigurationFromTemplateKt.getTemplateClass(configurationFromTemplate.kt:189)
    at kotlin.script.experimental.host.ConfigurationFromTemplateKt.createScriptDefinitionFromTemplate(configurationFromTemplate.kt:36)
    at kotlin.script.experimental.jsr223.KotlinJsr223DefaultScriptEngineFactory.<init>(KotlinJsr223DefaultScriptEngineFactory.kt:74)
    at ce.domain.usecase.load.LoadMetaFilesForTargetUseCase.invoke(LoadMetaFilesUseCase.kt:17)
    at ce.domain.usecase.entry.BuildProjectUseCase.invoke(BuildProjectUseCase.kt:24)
    at ce.domain.usecase.entry.BuildProjectUseCase.invoke$default(BuildProjectUseCase.kt:18)
    at Build_gradle$$$result$1.invoke(build.gradle.kts:68)
    at Build_gradle$$$result$1.invoke(build.gradle.kts:60)
    at org.gradle.kotlin.dsl.ProjectExtensionsKt$sam$org_gradle_api_Action$0.execute(ProjectExtensions.kt)
    at org.gradle.api.internal.tasks.DefaultTaskContainer.create(DefaultTaskContainer.java:368)
    at org.gradle.kotlin.dsl.ProjectExtensionsKt.task(ProjectExtensions.kt:147)
    at Build_gradle.<init>(build.gradle.kts:60)
    ...

我已经复制了一个简单的gradle项目的问题: 示例项目gradle:
import kotlin.script.experimental.jsr223.KotlinJsr223DefaultScriptEngineFactory

plugins {
    kotlin("jvm") version "1.8.21"
    application
}

repositories {
    mavenCentral()
}

buildscript {
    repositories {
        mavenCentral()
        google()
    }
    dependencies {
        classpath("org.jetbrains.kotlin:kotlin-scripting-jsr223:1.8.10")
        classpath("org.jetbrains.kotlin:kotlin-scripting-common:1.8.10")
        classpath("org.jetbrains.kotlin:kotlin-compiler-embeddable:1.8.10")
        classpath("org.jetbrains.kotlin:kotlin-reflect")
    }
}

dependencies {
    testImplementation(kotlin("test"))
}

abstract class TestProjectTask : DefaultTask() {
    @get: InputFile
    abstract val projectFile: RegularFileProperty

    @TaskAction
    fun execute() {
        try {
            val eng2 = KotlinJsr223DefaultScriptEngineFactory().getScriptEngine()
            println("Project file = ${projectFile.get()} $eng2")
            val worker = Worker()
            worker.doWork(eng2, projectFile.asFile.get().absolutePath)
        } catch (err: Throwable) {
            err.printStackTrace()
        }
    }
}

task("hello2", TestProjectTask::class) {
    projectFile.set(File("./project.kts"))
}


KotlinJsr223DefaultScriptEngineFactory()。getScriptEngine()始终会引发相同的异常。
1个回答

4
感谢这个问题相关评论线程中贡献者的工作,答案相对简单,尽管发现如何做到并不容易!我已经整理了提供的解决方法。
总结如下:
- 创建一个Gradle任务来运行Kotlin脚本 - 获取所需的编译和运行时依赖项 - 运行K2JVMCompiler生成源代码
我建议使用buildSrc约定插件来设置必要的逻辑。它有助于保持构建脚本更加清晰和声明性,并且设置逻辑包含在buildSrc中。

Kotlin依赖

首先,请确保K2JVMCompiler类可用。
如果您正在使用单个build.gradle.kts文件进行工作,那么可以通过应用Kotlin插件来实现此目标。
// build.gradle.kts

plugins {
  kotlin("jvm") version "1.8.22"
}

或者,如果编写一个插件/预编译脚本插件,在项目的`build.gradle.kts`中添加对Kotlin Gradle插件的依赖。
访问`K2JVMCompiler`类需要在编译时依赖于`kotlin-compiler-embeddable`。
// buildSrc/build.gradle.kts
plugins {
  `kotlin-dsl`
}

repositories {
  mavenCentral()
}

dependencies {
  implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.22")

  // required for K2JVMCompiler::class - will be provided at runtime by Gradle
  compileOnly("org.jetbrains.kotlin:kotlin-compiler-embeddable:1.8.22")
}

⚠️ Note that adding a dependency on KGP in buildSrc/build.gradle.kts means that all other KGP versions must be removed.

// build.gradle.kts
plugins {
  kotlin("jvm") // no version needed - it's set in buildSrc/build.gradle.kts
}

运行任务

接下来,让我们创建一个用于运行 .main.kts 文件的任务。

为了运行 Kotlin 脚本,我们需要几样东西:

  • Kotlin 脚本的位置(显而易见!)
  • 用于编译 Kotlin 脚本的类路径
  • 用于运行 Kotlin 脚本的类路径

为了遵循 Gradle 的最佳实践,跟踪 任务的输入和输出文件 也很重要(但不是严格要求的)。

// buildSrc/src/main/kotlin/RunKotlinScript.kt

import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.*
import org.gradle.process.ExecOperations
import javax.inject.Inject

/** Task for running Kotlin Scripts */
abstract class RunKotlinScript @Inject constructor(
    private val executor: ExecOperations
) : DefaultTask() {
    /** Location of the `.kts` file (required) */
    @get:InputFile
    abstract val script: RegularFileProperty

    /** (optional) Files that the script uses as an input */
    @get:InputFiles
    @get:Optional
    abstract val scriptInputs: ConfigurableFileCollection

    /** (optional) Files that the script produces as output */
    @get:OutputFiles
    abstract val scriptOutputs: ConfigurableFileCollection

    @get:Classpath
    abstract val compileClasspath: ConfigurableFileCollection

    @get:Classpath
    abstract val runtimeClasspath: ConfigurableFileCollection

    init {
        group = "run kts"
        description = "Runs a Kotlin script"
    }

    @TaskAction
    fun run() {
        val scriptPath = script.get().asFile.invariantSeparatorsPath
        val runtimeClasspath = runtimeClasspath.asPath

        executor.javaexec {
            classpath(compileClasspath)
            mainClass.set(org.jetbrains.kotlin.cli.jvm.K2JVMCompiler::class.qualifiedName)
            args(
                "-no-stdlib",
                "-no-reflect",
                "-classpath", runtimeClasspath,
                "-script", scriptPath,
            )
        }
    }
}

(如前所述,最好在buildSrc目录中进行此操作,但您也可以将此任务粘贴到常规的build.gradle.kts中。)

编译和运行时依赖

让我们使用一个预编译的约定插件来定义如何获取编译和运行Kotlin脚本所需的依赖项。

// buildSrc/src/main/kotlin/kotlin-script-runner.gradle.kts

plugins {
  kotlin("jvm") // no version needed - it's set in buildSrc/build.gradle.kts
}

// Fetch dependencies necessary to compile and run kts scripts inside Gradle,
// so installing the kotlin CLI is not required (e.g. on CI/CD, or Heroku)
val ktsCompileClasspath by configurations.creating<Configuration> {
    description = "Dependencies used to compile Kotlin scripts"
    isCanBeConsumed = false
}

val ktsRuntimeClasspath by configurations.creating<Configuration> {
    description = "Dependencies used to run Kotlin scripts"
    isCanBeConsumed = false
    // only fetch direct dependencies - the scripting context will pull in other dependencies
    isTransitive = false
}

dependencies {
    // add compile-time dependencies on the regular and scripting Kotlin compilers
    ktsCompileClasspath(kotlin("compiler"))
    ktsCompileClasspath(kotlin("scripting-compiler"))
    // only depend on Kotlin main-kts for runtime
    ktsRuntimeClasspath(kotlin("main-kts"))
}

我们现在有两个包含所需依赖项的配置。在同一个约定插件中,让我们将这些依赖项添加到所有 RunKotlinScript 任务中。
// buildSrc/src/main/kotlin/kotlin-script-runner.gradle.kts

// ...

tasks.withType<RunKotlinScript>().configureEach {
    runtimeClasspath.from(ktsRuntimeClasspath)
    compileClasspath.from(ktsCompileClasspath)
}

创建运行任务

这个约定插件可以应用于项目中的任何脚本:

// my-subproject/build.gradle.kts

plugins {
  `kotlin-script-runner`
}

然后您可以创建一个任务,该任务将被正确配置。
// my-subproject/build.gradle.kts


tasks.register<RunKotlinScript>("runDoSomethingKts") {
    script.set(file("scripts/do-something.main.kts"))
    scriptOutputs.from("scripts/out.txt")    
    scriptInputs.from("script/input.txt")
}

并且可以使用Gradle运行

./gradlew runDoSomethingKts

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接