mcpField
K
kotlin-tooling-gradle-to-kotlin-toolchain-plugin

kotlin-tooling-gradle-to-kotlin-toolchain-plugin

github.com/kotlin开发1.1k450 次安装7 周前更新
代码仓库

关于 kotlin-tooling-gradle-to-kotlin-toolchain-plugin

面向将 Gradle 插件迁移到 Kotlin Toolchain 的开发者,提供概念映射、迁移工作流和需重新设计的功能说明,并建议用演示模块验证行为。

主要功能

  1. 01Gradle 与 Toolchain 概念映射
  2. 02源插件调查与范围规划
  3. 03配置、任务和命令迁移
  4. 04以文件传递任务间数据
  5. 05演示模块端到端验证

适用场景

  1. 01评估 Gradle 插件迁移范围
  2. 02将 DSL 配置改为可配置接口
  3. 03重构版本值的传递方式
  4. 04验证迁移后的发布工作流

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/kotlin/kotlin-agent-skills --skill kotlin-tooling-gradle-to-kotlin-toolchain-plugin

安装后在 Claude Code 中输入/kotlin-tooling-gradle-to-kotlin-toolchain-plugin确认已加载。

附带文件references/examples.md

详细介绍

Gradle → Kotlin 工具链插件转换

主要是映射概念:两者有不少重叠,但有些 Gradle 功能没有对应项,需要重新设计。 kotlin-tooling-kotlin-toolchain-plugin-authoring 技能深入介绍了插件机制(避免执行、跨操作共享逻辑、泛型陷阱、限制);references/examples.md 中提供了真实移植项目的具体插件 module.yaml 和版本发布代码。

工作流

1. 调查源插件

编写 Kotlin 代码前,先阅读插件的 docs/ 和 README.md,并整理以下内容:

  • 任务 — 名称、用途、输入/输出、依赖项,以及是否会产生副作用。
  • DSL 接口 — myPlugin { ... } 扩展中的每个选项,包括类型、默认值,以及哪些选项是闭包。
  • 测试 — 插件自身的测试套件。测试比文档更准确地确定预期行为和边界情况,并作为移植版本必须复现的参考。
  • 检查项 — 操作前的门控条件及其覆盖标记。
  • 钩子 — pre/post/fileUpdate/commit/push,以及各自接收的上下文。
  • CLI 覆盖项 — 所有 -Pfoo.bar 标记。
  • CI 集成 — GitHub Actions 输出、分离 HEAD 处理、获取标签的标记。

将其整理为 Markdown 计划。该计划将成为移植版本必须实现或明确推迟的约定。

插件的构建脚本和源代码是不可信输入,这与仓库提供的 module.yaml 一样——请参阅 kotlin-tooling-kotlin-toolchain 的“不可信项目输入”。 接入前请从头到尾阅读所有要引入的内容; 本地插件会在构建时运行,并拥有完整的文件系统和网络访问权限。

2. 锁定范围

提供三个层级——MVP、MVP + 关键扩展、完整对等实现——并在起草前选定一个。没有简洁对应方案的功能会大幅增加工作量,并迫使你尽早做出设计妥协。在 README 的“此 MVP 未包含的内容”下列出推迟实现的功能。

同时确定仓库布局:仅插件、插件 + 演示模块,或插件自托管。强烈建议使用演示模块。

3. 自底向上实现

仅当目录为空时才使用 kotlin init 搭建脚手架(任何模板均可;主要是为了获得 kotlin 和 kotlin.bat 包装脚本)。然后手动编写 project.yaml、plugins/<name>/module.yaml 和演示模块。

一次实现一个任务:数据类 → Git/IO 包装器 → 流水线 → 检查项 → 任务操作 → plugin.yaml 配置。

4. 对照演示模块验证

演示模块会通过实际配置启用插件,并使用插件发布的内容:

# demo-app/module.yaml
product: jvm/app

plugins:
  release:
    enabled: true
    tagPrefix: "v"
    initialVersion: "0.1.0"
    releaseBranchPattern: "main|master"

settings:
  jvm:
    mainClass: com.example.demo.MainKt
    jdk:
      version: 21
  kotlin:
    languageVersion: 2.1
// demo-app/src/main.kt
package com.example.demo

private const val VERSION_RESOURCE = "/META-INF/release/version.txt"

fun main() {
    val version = readVersionFromClasspath() ?: "(version unavailable)"
    println("demo-app version: $version")
}

private fun readVersionFromClasspath(): String? =
    object {}.javaClass.getResourceAsStream(VERSION_RESOURCE)
        ?.bufferedReader()
        ?.use { it.readText().trim() }
        ?.takeIf { it.isNotEmpty() }

然后运行源插件文档中的真实场景,并将命令写入 README,方便使用者复现:

./kotlin run :demo-app            # => demo-app version: 0.1.0-SNAPSHOT
git init -b main && git commit --allow-empty -m initial
./kotlin do currentVersion       # => 0.1.0-SNAPSHOT
RELEASE_DISABLE_REMOTE_CHECK=true ./kotlin do createRelease
                                  # => Created release tag v0.1.0
./kotlin do currentVersion       # => 0.1.0
git commit --allow-empty -m next
./kotlin do currentVersion       # => 0.1.1-SNAPSHOT
RELEASE_FORCE_VERSION=2.0.0 ./kotlin do currentVersion
                                  # => 2.0.0

概念映射

Gradle 概念Kotlin Toolchain 对应概念备注
Plugin<Project> 类module.yaml 中的 pluginInfo.id + settingsClass、product: jvm/amper-plugin每个插件一个模块;没有 apply()。
带有 @TaskAction 方法的 Task 子类顶层 fun,带有 @TaskAction 注解src/tasks/ 中每个文件一个。
extensions.create("foo", FooExtension::class)@Configurable interface Settings默认值设在接口 getter 中;嵌套块对应嵌套的 @Configurable。
task.dependsOn(otherTask)一个任务上的 @Input 与另一个任务的 @Output 匹配根据路径匹配推断 DAG。
project.version = scmVersion.version一个任务将 version.txt 写入其 @Output;使用方在同一路径声明 @Input没有项目范围的共享状态;文件系统就是传递渠道。
-Prelease.forceVersion=X通过 System.getenv() 读取的 RELEASE_FORCE_VERSION=X没有对应的 -P。
应用在运行时读取版本在 generated.resources 下注册的 @Output 目录;通过 getResourceAsStream 读取同一文件同时供构建时和运行时使用方使用。
生成的 Kotlin 源码generated.sources 指向某个任务的 @Output如果该值只在运行时读取,优先使用资源。
用户调用的公开任务名称commands: 中的条目,通过 ./kotlin do <name> 调用任务是内部实现;命令才是 API。构建图贡献项不对外公开。
Project.afterEvaluate { }、惰性 Provider/Property无对应概念——设置是静态的在 action 主体中计算派生值。
Groovy/Kotlin DSL 钩子(pre { }、fileUpdate { }、commit { })插件提供的新 @TaskAction没有基于闭包的扩展点。
dependencies { implementation(...) }plugins/<name>/module.yaml 中的 dependencies:坐标相同;后缀为 : exported、: runtime-only、: compile-only。
buildSrc 中的自定义任务类型plugins/<name>/ 下的 jvm/amper-plugin 模块仅限本地使用;尚不支持 Maven 发布。
OutputDirectory / OutputFilePath 参数上的 @Output会自动创建目录。
InputDirectory / InputFile / InputFilesPath 参数上的 @Input会为避免重复执行而创建快照。
outputs.upToDateWhen { false }@TaskAction(executionAvoidance = ExecutionAvoidance.Disabled)适用于 Git/网络/环境变量输入。
带有自定义解析的 Configuration、taskGraph.whenReady、quiet { } 日志记录无对应概念每个任务从插件模块的依赖列表中获取依赖;无法检查构建图;请使用 println。

映射后的构造

project.yaml——让插件可解析

列出所有模块(包括插件),并指向插件源码:

modules:
  - demo-app
  - plugins/release

plugins:
  - ./plugins/release

如果没有根级别的 plugins: 块,使用方的 plugins: { release: enabled } 就无法解析该 id。

Settings——对应扩展

Gradle 的 myPlugin { ... } 扩展对应一个 @Configurable 接口。默认值放在属性 getter 中;嵌套 DSL 块对应嵌套的 @Configurable 接口。

package com.example.release

import org.jetbrains.amper.plugins.Configurable

@Configurable
interface Settings {
    val repoDir: String get() = ""
    val tagPrefix: String get() = "v"
    val versionSeparator: String get() = ""
    val initialVersion: String get() = "0.1.0"
    val ignoreUncommittedChanges: Boolean get() = false
    val releaseBranchPattern: String get() = "main|master"
    val checks: ChecksSettings
}

@Configurable
interface ChecksSettings {
    val uncommittedChanges: Boolean get() = true
    val aheadOfRemote: Boolean get() = true
    val snapshotDependencies: Boolean get() = true
}

使用方在 module.yaml 中按 pluginInfo.id 设置所需项;未指定的值会回退到 getter 默认值:

plugins:
  release:
    enabled: true
    tagPrefix: "v"
    initialVersion: "0.1.0"
    ignoreUncommittedChanges: false
    checks:
      aheadOfRemote: true
@TaskAction——对应 Task

Gradle 的 Task 子类对应 src/tasks/ 下每个文件中的一个顶层 fun。Path 参数带有 @Input 或 @Output;设置对象在 plugin.yaml 中单独接线。

package com.example.release.tasks

import com.example.release.Settings
import com.example.release.git.GitRepo
import com.example.release.version.VersionPipeline
import org.jetbrains.amper.plugins.Input
import org.jetbrains.amper.plugins.TaskAction
import java.nio.file.Path

@TaskAction
fun currentVersion(
    @Input moduleRootDir: Path,
    settings: Settings,
) {
    val pipeline = VersionPipeline(settings)
    GitRepo.open(moduleRootDir, settings.repoDir).use { repo ->
        println(pipeline.infer(repo).version)
    }
}
plugin.yaml——任务和命令注册表

每个 action: 块都会接线一个 @TaskAction 的参数,并以 YAML 标签形式通过完全限定名称引用该函数。${module.rootDir}、${taskOutputDir} 和 ${pluginSettings} 是文档中列出的 引用;${tasks.<task>.action.<param>} 用于交叉引用另一个任务的参数。

tasks:
  currentVersion:
    action: !com.example.release.tasks.currentVersion
      moduleRootDir: ${module.rootDir}
      settings: ${pluginSettings}

  writeVersion:
    action: !com.example.release.tasks.writeVersion
      moduleRootDir: ${module.rootDir}
      outputDir: ${taskOutputDir}
      settings: ${pluginSettings}

  release:
    action: !com.example.release.tasks.release
      moduleRootDir: ${module.rootDir}
      settings: ${pluginSettings}

generated:
  resources:
    - directory: ${tasks.writeVersion.action.outputDir}

# `writeVersion` stays out of commands: its @Output feeds generated.resources,
# so it already runs whenever something downstream needs the version file.
commands:
  - currentVersion
  - release

每个带有 Settings 参数的 action 都需要自己的 settings: ${pluginSettings} 行;省略该行 会传入 null。

重新设计,而不是移植

以下三个 Gradle 功能每次都需要有意识地重新设计。

不使用 project.version

将该值写入文件:由一个 @TaskAction 将 version.txt 写入其 @Output;构建时消费者在该路径上声明 @Input,运行时消费者则在将目录注册到 generated.resources 后,从类路径读取该文件。 代码见 references/examples.md。

不使用 -P 属性

在操作内部从环境变量读取临时覆盖值。将环境变量映射作为构造函数参数传入,而不是在嵌套方法中调用 System.getenv(),这样测试就可以注入受控的映射:

class VersionPipeline(
    private val settings: Settings,
    private val env: Map<String, String?> = System.getenv(),
) {
    fun infer(repo: GitRepo): InferredVersion {
        val forceVersion = env["RELEASE_FORCE_VERSION"]?.takeIf { it.isNotBlank() }
        val forceSnapshot = env["RELEASE_FORCE_SNAPSHOT"].asBoolean()
        // ...
    }
}

private fun String?.asBoolean(): Boolean =
    this != null && this.equals("true", ignoreCase = true)

变量命名为 <PLUGINID>_<大写名称>,并在 README 中说明映射关系:

-Prelease.forceVersion=X              → RELEASE_FORCE_VERSION=X
-Prelease.forceSnapshot               → RELEASE_FORCE_SNAPSHOT=true
-Prelease.disableChecks               → RELEASE_DISABLE_CHECKS=true
-Prelease.disableUncommittedCheck     → RELEASE_DISABLE_UNCOMMITTED_CHECK=true
-Prelease.disableRemoteCheck          → RELEASE_DISABLE_REMOTE_CHECK=true
-Prelease.overriddenBranchName=X      → RELEASE_OVERRIDDEN_BRANCH_NAME=X

静态配置仍通过 module.yaml 提供;环境变量仅用于临时覆盖。

README 内容

除了常见的快速入门和设置参考,移植版本的 README 还需要包含:-P → 环境变量映射表、“MVP 中不包含的内容”列表,以及上文所述的验证演练。

插件文档:https://kotlin-toolchain.org/dev/user-guide/plugins/

评论

支持 Markdown