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. 01Gradle プラグイン移植の範囲を評価
  2. 02DSL 設定を設定可能インターフェースに変換
  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 の出力、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 付き funsrc/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=XSystem.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/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/ 配下のファイルごとに 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/

コメント