mcpField
K
kotlin-tooling-kotlin-toolchain-plugin-authoring

kotlin-tooling-kotlin-toolchain-plugin-authoring

github.com/kotlin開発1.1k451 回インストール7 週間前に更新
リポジトリ

kotlin-tooling-kotlin-toolchain-plugin-authoring について

Kotlin Toolchain のプラグイン作者向けガイドです。ローカルプラグインの構成、タスクや設定の配線、生成物、利用モジュールでの検証方法を説明します。

主な機能

  1. 01ローカルプラグインの構成
  2. 02設定インターフェースとタスクアクション
  3. 03タスク入出力と実行回避
  4. 04生成ソースとリソースの登録
  5. 05コマンドとファイルベースのタスク連携

活用シーン

  1. 01ビルドにコード生成を追加
  2. 02独自のビルド検証を組み込む
  3. 03ビルド時の値を JAR に公開
  4. 04繰り返し使う CLI コマンドを定義

インストール

コマンドをコピーしてターミナルで実行します。

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

インストール後、Claude Code で/kotlin-tooling-kotlin-toolchain-plugin-authoringを入力して読み込まれたか確認します。

同梱ファイルreferences/examples.md

詳細

Kotlin Toolchain プラグインの作成

ローカルプラグインは、宣言的な YAML の正式な拡張手段です。jvm/amper-plugin モジュールで、プロジェクトと併せてタスクアクション、設定、生成ソース/リソースを提供します。応用できるコードパターンはreferences/examples.mdにあります。

プラグインを作成するタイミング

次のような場合に作成します。

  • ライブラリのワークフローが必要とするビルド時のステップ(コード生成、スキーマコンパイル、リソース変換、バージョンの埋め込み)。
  • ビルドに組み込むカスタム検証(リリース前チェック、スキーマ検証、コントラクトテスト)。
  • JAR のクラスパスまたは後続タスクに公開するビルド時の値。Gradle の project.version に最も近いものです。
  • 繰り返し行うワークフロー用の名前付き CLI コマンド(./kotlin do release)。

module.yaml で既に対応できる場合(依存関係、JDK のプロビジョニング、ソースレイアウト、基本的なパッケージング)は作成しないでください。また、Gradle プラグインを再利用する目的では決して作成しないでください。Kotlin Toolchain は Gradle プラグインを利用できません。

構成

repo-root/
├── kotlin, kotlin.bat            # wrappers (from `kotlin init`)
├── project.yaml                  # registers the plugin
├── plugins/<name>/
│   ├── module.yaml               # product: jvm/amper-plugin
│   ├── plugin.yaml               # tasks: + commands: + generated:
│   └── src/
│       ├── Settings.kt           # @Configurable interface
│       ├── tasks/                # one @TaskAction per file
│       │   ├── Foo.kt
│       │   └── FooSteps.kt       # internal shared helpers (not @TaskAction)
│       └── <domain logic>/
└── <consumer-module>/
    └── module.yaml               # plugins: { <name>: enabled: true, ... }

リポジトリには、少なくとも1つのコンシューマーモジュールを含めてください。これがプラグインをエンドツーエンドで動作確認する唯一の方法であり、プラグインはまだ公開レジストリに公開できません。

project.yaml

modules:
  - consumer-app
  - plugins/<name>

plugins:
  - ./plugins/<name>

トップレベルの plugins: ブロックがないと、どのコンシューマーからもプラグイン ID を解決できません。

module.yaml — プラグインモジュール

product: jvm/amper-plugin          # marks the module as a plugin

dependencies:
  - <coordinate>:<version>
  - <coordinate>:<version>: runtime-only   # required only at runtime
  - <coordinate>:<version>: compile-only

pluginInfo:
  id: <plugin-id>                                  # what consumers write under `plugins:`
  settingsClass: <fully.qualified.Settings>        # the @Configurable interface

settings:
  jvm:
    jdk:
      version: 21
  kotlin:
    languageVersion: 2.1

@Configurable interface Settings

デフォルト値はインターフェースのプロパティゲッターに記述します。ネストしたブロックは、ネストした @Configurable インターフェースになります。

@Configurable
interface Settings {
    val someValue: String get() = "default"
    val checks: ChecksSettings
}

@Configurable
interface ChecksSettings {
    val strict: Boolean get() = true
}

コンシューマーは module.yaml で必要な値を上書きします。省略した値にはゲッターのデフォルト値が使われます。

plugins:
  <plugin-id>:
    enabled: true
    someValue: "override"
    checks:
      strict: false

@TaskAction

タスクアクションはトップレベルの fun で、対応する plugin.yaml エントリーが実行されると呼び出されます。

@TaskAction
fun foo(
    @Input moduleRootDir: Path,
    @Output outputDir: Path,
    settings: Settings,
) {
    // body
}
  • @Input path: Path — 宣言された入力です。Kotlin Toolchain は実行を省略できるよう、内容のスナップショットを作成します。
  • @Output path: Path — 宣言された出力ディレクトリです。Kotlin Toolchain が作成し、そのパスを渡します。受け取ったものと完全に同じ Path に書き込んでください。そうしないと、後続の参照で結果を見つけられません。
  • settings: Settings(または任意の @Configurable) — plugin.yaml で接続する型付き設定です。
  • 通常の Path/プリミティブ型 — plugin.yaml からそのまま渡されます。
  • println(...) は出力チャネルです。Kotlin Toolchain が標準出力を取得します。
実行の省略

宣言された入力が変更されていない場合、@TaskAction はスキップされます。実際の入力が Git の履歴、ネットワーク、環境変数であるタスクはフィンガープリントを作成できないため、実行省略を無効にしてください。

@TaskAction(executionAvoidance = ExecutionAvoidance.Disabled)
fun foo(@Output outputDir: Path, settings: Settings) { /* ... */ }

@Output のないタスクはキャッシュされず、常に再実行されます。リリース、デプロイ、プッシュなど、副作用のみを伴うタスクではこれが適切です。

plugin.yaml

tasks:
  foo:
    action: !<fully.qualified.foo>
      moduleRootDir: ${module.rootDir}
      outputDir: ${taskOutputDir}
      settings: ${pluginSettings}

  bar:
    action: !<fully.qualified.bar>
      input: ${tasks.foo.action.outputDir}/result.txt
      settings: ${pluginSettings}

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

commands:
  - foo
参照解決先
${module.rootDir}コンシューマーの module.yaml があるディレクトリです。コンシューマーのツリーを調べるため、@Input として渡します。
${taskOutputDir}ツールチェーンが管理するタスクごとの出力ディレクトリです。@Output として渡します。
${pluginSettings}コンシューマーの module.yaml から構築された @Configurable オブジェクトです。
${tasks.<task>.action.<param>}別のタスクのパラメーターです。generated.* で使用し、あるタスクの @Input を別のタスクの @Output に接続します。
generated.resources/generated.sources

どちらもディレクトリ(通常はタスクの @Output)をコンシューマーのビルドへの追加要素として登録し、生成元のタスクが先に実行されるよう自動的に接続します。

  • generated.resources — JAR のクラスパスに追加され、getResourceAsStream("/path/in/jar") でアクセスできます。
  • generated.sources — Kotlin ソースルートとして追加され、コンシューマーの src/ と一緒にコンパイルされます。
タスクとコマンド

タスクは実装であり、./kotlin task :<module>:<task>@<plugin-id> として指定します。ただし、ドキュメントではこの加工された名前に依存しないよう勧めています。コマンドは公開 API です。./kotlin do <command-name> で実行でき、./kotlin show commands で一覧を表示できます(対象を絞るには -m <module> を指定します)。

  • @Output が generated.resources/generated.sources に渡されるタスクは、ビルドグラフに寄与します。自動実行されるため、commands: には含めないでください。公開すると、ユーザーが手動で実行してしまうおそれがあります。
  • ユーザーが直接実行するタスクは、commands: に含める必要があります。

ファイルベースのタスク間通信

共有された可変のビルド状態はありません。project.version も、拡張プロパティのマップもありません。タスク間では、パスを対応させてやり取りします。

  1. 生成側で @Output outputDir: Path を受け取り、その中にファイルを書き込みます。
  2. plugin.yaml で、利用側タスクの @Input を ${tasks.<producer>.action.outputDir}/<file> に設定します。
  3. Toolchain がパスの一致から依存関係を推論するため、dependsOn API は必要ありません。

同じ @Output ディレクトリを、ビルド時の利用側(@Input 経由)と実行時の利用側(generated.resources に登録し、getResourceAsStream で読み込む)の両方に同時に使用できます。

環境変数による実行時の上書き

-Pkey=value はありません。一時的な上書きには、アクション内で環境変数を読み取ります。

val forced = System.getenv("MYPLUGIN_FORCE_VALUE")?.takeIf { it.isNotBlank() }
val skipChecks = System.getenv("MYPLUGIN_SKIP_CHECKS")?.equals("true", ignoreCase = true) == true

呼び出しスタックの深いところで System.getenv() を呼び出すのではなく、環境変数のマップをコンストラクターパラメーターとして渡してください。これにより、ロジックを単体テストできる状態に保てます。認識する環境変数はすべて、プラグインの README に記載してください。環境変数による上書きは一時的なものであり、信頼境界ではありません。ファイルパスやプロセス引数に使用する前に、値を検証してください。

タスクアクション間でのロジックの共有

タスクで手順(検証 → 作成 → プッシュ)を共有することはよくあります。ビルドグラフ上のタスクを連鎖させて、ユーザー向けの単一のタスクを構成しないでください。呼び出しを分けると、共有リソースが再び開かれ、別のプロセスが中間状態を観測する余地が生まれます。

設計時に考慮すべき制限事項

  • プラグインはローカルのみで利用できます。公開レジストリへの公開には、まだ対応していません。
  • プラグインはモジュール単位です。プロジェクト全体に適用されるプラグインはありません。利用する各モジュールで plugins: に指定し、モジュール間の影響はファイルを介して受け渡します。
  • module.yaml では ${...} による補間はできません(0.11 時点)。利用側の設定にはリテラル値を指定します。
  • Project.afterEvaluate や、遅延評価される Provider/Property グラフはありません。派生値はアクション本体で計算してください。
  • -h/--help ではプラグインのコマンドは一覧表示されません。./kotlin show commands を使用してください。

利用側で検証する

  1. 現実的な設定でプラグインを有効にする、小規模な利用側モジュール(demo-app/、sample/)を追加します。
  2. その main.kt またはテストで、プラグインが公開する内容を読み取ります。
  3. 新たにクローンした人がコマンドを貼り付けて結果を比較できるよう、正確なコマンドと期待される出力をプラグインの README に記載します。

プラグインのドキュメント:https://kotlin-toolchain.org/dev/user-guide/plugins/

コメント