英文:
Is there a way to instantinate KTS script engine in the Gradle KTS?
问题
我想在我的项目构建过程中使用第三方库。
库方法需要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()
}
}
}
tasks.register("hello2", TestProjectTask::class) {
projectFile.set(file("./project.kts"))
}
KotlinJsr223DefaultScriptEngineFactory().getScriptEngine() 总是抛出相同的异常。
英文:
I want to use 3d party library in my project build process.
Library methods requires ScriptEngine. When I'm trying to instantiate it i got an error:
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)
...
I've reproduced issue with simple grdale project:
Sample project 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() always throws same exception.
答案1
得分: 3
感谢贡献者在此问题和链接的评论线程中的工作,答案相对简单,尽管发现如何做到这一点并不容易!我已经整理了提供的解决方法。
摘要:
- 创建一个用于运行Kotlin脚本的Gradle任务
- 获取所需的编译和运行依赖项
- 运行
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")
}
⚠️ 请注意,在
buildSrc/build.gradle.kts
中添加对KGP的依赖意味着必须删除_所有_其他的KGP版本。
// 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"))
}
现在,我们有两个包含所需依赖项的[配置](https://docs.gradle.org/8.1.1/userguide/dependency_management_terminology.html#sub:
英文:
Thanks to work done by contributors in this issue and the linked comment thread, the answer is relative simple, even if discovering how to do it was not! I've cleaned up the provided workaround.
Summary:
- Create a Gradle task for running the Kotlin script
- fetch the required compilation and runtime dependencies
- Run
K2JVMCompiler
to generate the sources
I recommend using a buildSrc convention plugin to set up the requisite logic. It helps keep the build scripts cleaner and more declarative, and setup-logic is contained within buildSrc.
Kotlin dependencies
First, make sure that the K2JVMCompiler
class is available.
If you're working in a single build.gradle.kts
, then this can be achieved by applying the Kotlin plugin:
// build.gradle.kts
plugins {
kotlin("jvm") version "1.8.22"
}
Or if writing a plugin/pre-compiled script plugin, add a dependency on the Kotlin Gradle Plugin in the project's build.gradle.kts
.
A compile-time dependency on kotlin-compiler-embeddable
is required for accessing the K2JVMCompiler
class.
// 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.
>
> kotlin
> // build.gradle.kts
> plugins {
> kotlin("jvm") // no version needed - it's set in buildSrc/build.gradle.kts
> }
>
Run task
Next, let's create the task that will be used to run the .main.kts
files.
In order to run a Kotlin script, we need a few things:
- the location of the Kotlin script (obviously!)
- the classpath used to compile the Kotlin script
- the classpath used to run the Kotlin script
In order to follow Gradle best practices, tracking task's input and output files is also important (but is not strictly required).
// 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,
)
}
}
}
(As previously mentioned, it's best to do this in a buildSrc directory, but you can paste this task into a regular build.gradle.kts
too.)
Compile and Runtime dependencies
Let's use a pre-compiled convention plugin to define how to fetch the dependencies needed to compile and run a Kotlin script.
// 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"))
}
We now have two Configurations that contain the requisite dependencies. In the same convention plugin, let's add those dependencies to all RunKotlinScript tasks.
// buildSrc/src/main/kotlin/kotlin-script-runner.gradle.kts
// ...
tasks.withType<RunKotlinScript>().configureEach {
runtimeClasspath.from(ktsRuntimeClasspath)
compileClasspath.from(ktsCompileClasspath)
}
Creating run tasks
This convention plugin can be applied to any script in the project:
// my-subproject/build.gradle.kts
plugins {
`kotlin-script-runner`
}
and then you can create a task, which will be correctly configured
// 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")
}
and can be run, using Gradle
./gradlew runDoSomethingKts
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论