kotlin-tooling-gradle-to-kotlin-toolchain-plugin について
Gradle プラグインを Kotlin Toolchain に移行する開発者向けのガイドです。概念の対応関係や移行手順、再設計が必要な機能を示し、デモモジュールでの検証を勧めます。
主な機能
- 01Gradle と Toolchain の概念対応
- 02元プラグインの調査と範囲設定
- 03設定・タスク・コマンドの移行
- 04ファイルによるタスク間連携
- 05デモモジュールでの一連の検証
活用シーン
- 01Gradle プラグイン移植の範囲を評価
- 02DSL 設定を設定可能インターフェースに変換
- 03バージョン値の受け渡しを再設計
- 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 の出力、detached HEAD の処理、fetch-tags フラグ。
これらを Markdown の計画書として保存します。これは、移植版が実装するか、明示的に後回しにする機能の基準になります。
プラグインのビルドスクリプトとソースは、リポジトリから提供された module.yaml と同様に、信頼できない入力です。詳しくは kotlin-tooling-kotlin-toolchain の「信頼できないプロジェクト入力」を参照してください。組み込む前に、ベンダーとして取り込むものは最初から最後まで読みます。
ローカルプラグインは、ビルド時にファイルシステムとネットワークへフルアクセスできます。
2. スコープを確定する
3 つの段階(MVP、MVP +主要な追加機能、完全な機能互換)を提示し、作成に着手する前にどれか 1 つに決めます。対応関係が明確でない機能は作業量を増やし、早い段階で設計上の妥協を迫ることになります。README の「この MVP では対応しないこと」に、後回しにする機能を記載します。
また、リポジトリの構成(プラグインのみ、プラグイン + デモモジュール、プラグインのセルフホスティング)も決めます。デモモジュールを用意することを強く推奨します。
3. 下位レイヤーから実装する
ディレクトリが空の場合に限り、kotlin init でひな形を作成します(テンプレートはどれでも構いません。主に必要なのは kotlin と kotlin.bat のラッパーです)。次に、project.yaml、plugins/<name>/module.yaml、デモモジュールを手書きします。
タスクを 1 つずつ実装します。データクラス → 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 | プラグインごとに 1 つのモジュールを使用します。apply() はありません。 |
@TaskAction メソッドを持つ Task サブクラス | トップレベルの @TaskAction 付き fun | src/tasks/ 内のファイルごとに 1 つです。 |
extensions.create("foo", FooExtension::class) | @Configurable interface Settings | デフォルト値はインターフェースの getter に記述します。ネストしたブロックはネストした @Configurable にします。 |
task.dependsOn(otherTask) | あるタスクの @Output と一致する、別のタスクの @Input | パスの一致から DAG が推論されます。 |
project.version = scmVersion.version | タスクが @Output に version.txt を書き込み、利用側が同じパスを @Input として宣言する | プロジェクト全体で共有される状態はなく、ファイルシステムが受け渡し経路になります。 |
-Prelease.forceVersion=X | System.getenv() で読み取る RELEASE_FORCE_VERSION=X | -P に相当するものはありません。 |
| 実行時にバージョンを読み取るアプリ | generated.resources に登録された @Output ディレクトリ。getResourceAsStream で読み取ります | 同じファイルをビルド時と実行時の両方で利用できます。 |
| 生成された Kotlin ソース | タスクの @Output を指す generated.sources | 値を実行時にのみ読み取る場合は、リソースを優先してください。 |
| ユーザーが実行する公開タスク名 | commands: のエントリー。./kotlin do <name> で実行します | タスクは内部用です。コマンドが API です。ビルドグラフに関わる要素は公開しません。 |
Project.afterEvaluate { }、遅延評価される Provider/Property | 対応するものはありません。設定は静的です | 派生値はアクション本体で計算してください。 |
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/OutputFile | Path パラメーターに付ける @Output | ディレクトリは自動的に作成されます。 |
InputDirectory/InputFile/InputFiles | Path パラメーターに付ける @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/ 配下のファイルごとに 1 つのトップレベル 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: ブロックは 1 つの @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 パラメーターを受け取るすべてのアクションには、それぞれ独自の settings: ${pluginSettings} 行が必要です。省略すると null が渡されます。
移植ではなく再設計
Gradle の 3 つの機能は、移植のたびに意識して再設計する必要があります。
project.version は使用しない
値をファイルにします。1 つの @TaskAction で @Output に version.txt を書き込み、ビルド時のコンシューマーはそのパスに @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>_<UPPERCASE> とし、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/