プラグイン独立+コンシューマー ルート アーキテクチャ:なぜ私のGradleビルドが重複するのか
公開日: 14 May 2026
Gradleプラグインのリポジトリをクローンしています。ターミナルを開いています。何を入力していますか? その後?./gradlew tasks, 当然です。 しかし、どのフォルダーですか? ルートですか? サブモジュールですか? まずREADMEを読んでビルド方法を確認すべきでしょうか?もし躊躇しているなら 一瞬で、プロジェクトのアーキテクチャーが壊れています。
これが私がこの問題を一度限りで解決した方法 — そしてなぜこれが パターンは今日、私のすべてのGradleプラグインの署名の中`foundry/public/`.
問題:私の時間を奪った3つのアーキテクチャ
現在のパターンに収束する前に、私は3つのアプローチの間で試行錯誤した Gradle プラグインを構成するための伝統的な方法。それぞれに致命的な欠陥があった。
オプション 1 : 分離されたプラグイン (ドッグフーディングなし)
プロジェクトにはプラグインのみが含まれます。 消費の例はありません。 なし プロジェクトがそれを行う。それをテストするには、外部のプロジェクトを作成する必要があります、y プラグインを介して参照する`mavenLocal`または複合ビルド、そしてのみ そこでそれが動くことを確認する。
$ git clone mon-plugin
$ cd mon-plugin
$ ./gradlew build # le plugin compile
$ # ... et maintenant ? comment je l'essaie ?
|
消費例のない Gradle プラグインは、ライブラリではない 統合テスト。 あなたは最後の変更が ユーザー体験を壊した |
Option 2 : クラシックなモノリポ (include(":plugin"))
グレードル`init`生成する`settings.gradle.kts`と`include("plugin")`. ルートとサブモジュールは同じデーモンと同じ設定を共有します、 同じカタログです。便利ですが、結合されています。
.
├── settings.gradle.kts → include("plugin")
├── build.gradle.kts → plugins { id("mon-plugin") }
├── plugin/
│ └── build.gradle.kts → java-gradle-plugin
└── gradle/
└── libs.versions.toml
ネックは:
-
ルートしなければならないサブモジュールと同じGradleのバージョンを持つ。
-
`libs.versions.toml`共有されている — 共通のバージョンカタログ、依存関係
モジュールから別のモジュールへ逃げている
-
ルートなしではプラグインをビルドすることはできません。
-
CIはプラグインのみが変更された場合でも、2つのモジュールをビルドしなければならない。
オプション 3 : コンポジット ビルド (includeBuild())
2つをそれぞれ異なるGradleビルドに分け、それらを経由でつなげる includeBuild("mon-plugin")`中に`settings.gradle.kts。
既に改善されました。プラグインのビルドは分離されています。しかし、消費者 明示的に外部ビルドを参照しなければならない — 出力は `./gradlew tasks`根本では、正しい設定に依存する 複合体。クローン作成はzero-configではありません:それが…であることを知っておく必要があります プラグインは別のフォルダーにあり、ルートがそれを参照しているなど。
.
├── settings.gradle.kts → includeBuild("plugin-build/")
├── build.gradle.kts → plugins { id("mon-plugin") }
└── plugin-build/
├── settings.gradle.kts
└── build.gradle.kts
もっと良いものを探していた。ずっと良いもの。
ソリューション:独立した2つのビルド、1つの消費者ルート
こちらが最終的に採用したパターンです:
.
├── settings.gradle.kts ← racine consommateur
├── build.gradle.kts ← 3 lignes : apply plugin + dogfood
├── gradle/
│ └── libs.versions.toml ← catalogue du consommateur
├── {name}-plugin/ ← BUILD INDÉPENDANT
│ ├── gradlew ← son propre wrapper
│ ├── settings.gradle.kts ← rootProject.name = "{name}-plugin"
│ ├── build.gradle.kts ← java-gradle-plugin, signing, publish
│ ├── gradle/
│ │ ├── libs.versions.toml ← catalogue du plugin
│ │ └── wrapper/
│ ├── src/ ← sources du plugin
│ ├── .agents/ ← gouvernance agent
│ └── *.adoc ← AGENT, PROMPT_REPRISE, snapshot, etc.
└── site.yml / slides-context.yml / ... ← configs dogfood
|
�鍵:`{name}-plugin/`完全かつ独立したGradleプロジェクト。 彼には独自の wrapper、独自の settings、独自の catalogue の バージョン。自身をクローンし、ビルドし、テストし、そして root なしで公開します。 その存在を知っているかどうか |
ルートは、*一つ*のことだけを行う:プラグインを適用する。
plugins {
alias(libs.plugins.bakery)
}
repositories {
mavenLocal()
mavenCentral()
}
bakery { configPath = file("site.yml").absolutePath }
ケースでは3行`bakery-gradle`. それ以上はありません。 ゼロ`include(), ゼロ`includeBuild(), ゼロのサブプロジェクト。 通常のGradleビルドは、任意のプラグインを適用します どの消費者がそれをするでしょうか。
ワークフロー
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontSize 12
left to right direction
package "根 (消費者)" #CCFFCC {
usecase "リポジトリをクローン" as Clone
usecase "./gradlew tasks" as Tasks
usecase "./gradlew bake" as Dogfood
note bottom of Dogfood
Exerce le plugin
Feedback immédiat
Zéro config
end note
}
package "{name}-plugin/ (独立したビルド)" #CCE5FF {
usecase "./gradlew build" as BuildPlugin
usecase "./gradlew publishToMavenLocal" as MavenLocal
usecase "./gradlew test" as TestPlugin
note bottom of BuildPlugin
Cycle de vie isolé
Tests unitaires + Cucumber
CI dédiée possible
end note
}
Clone -down-> Tasks
Tasks -down-> Dogfood
Dogfood ..> MavenLocal : "プラグインに依存します
ローカルで公開"
BuildPlugin -up-> MavenLocal
TestPlugin -up-> BuildPlugin
@enduml
具体的なワークフロー
# 1. Builder le plugin
$ cd codebase-plugin
$ ./gradlew publishToMavenLocal
# 2. L'exercer depuis la racine
$ cd ..
$ ./gradlew indexCodebase queryCodebase snapshot
# La boucle est fermée. Le plugin est testé dans des conditions
# réelles de consommation, par le projet même qui l'héberge.
なぜこのアーキテクチャが私を魅了した
組み合わせるとコストに見合う3つの利点:
1. ネイティブドッグフーディング、即時フィードバック
Gradleプラグインの最良のテストは、それを使うことです。 モックされたユニットテストではない。でもない`GradleRunner`プロジェクトと共に テストの。プラグインを本当のファイルに適用する*本当の*ビルド
$ git clone bakery-gradle
$ cd bakery-gradle
$ ./gradlew bake # ← le plugin est exercé immédiatement
プラグインが壊れているとき、ルートビルドはそれを教えてくれます。わざわざ確認しに行く必要はありません。 外部のテストプロジェクトを探す。ドッグフーディングは*最初的*だ 新しい貢献者が開始するタスク。それが最終的なスモークテストです。
|
ルールはシンプルです : もしルートがコンパイルされ、そして`./gradlew tasks` あなたのプラグインのタスクを表示し、プラグインは機能しています。驚きはありません 本番で。 |
2. ゼロコンフィグ クローニング
Un `git clone && ./gradlew tasks`そして新参者はすべてを見る 設定せずに歩く。 その`build.gradle.kts`根は プラグインの使用に関する生きているドキュメント。 Le`site.yml`横に 期待される構成を示します。
代替案と比較して:3段落のREADMEが プラグインをビルドする方法とプロジェクトをビルドする方法を説明します のテスト。 新しい貢献者はREADMEを斜めに読む, 間違って、issue を開ける — しかし情報は である 実行可能。
|
最も堅牢なドキュメントは読むものではない。 それは実行. ルートビルドはドキュメントです プラグインの実行可能ファイル |
3. ビルドの分離, CI の独立
プラグインは独自のGradleラッパーと、独自のライフサイクルを持ち、 独自のテスト。できること:
-
プラグインでGradleをアップグレードし、rootを触らない
-
ルートに漏れないようにプラグインに依存関係を追加する
-
プラグインを壊して、ルート ビルドに影響を与えずに (ただし、あなたが
壊れたバージョンを公開するな)
-
プラグインをビルド/テストするCIと、それを行使するもう一方のCI
root — 独立して
.github/workflows/
├── test-plugin.yml → codebase-plugin/.gradlew build
├── test-root.yml → .gradlew tasks (vérifie que le plugin est consommable)
└── publish.yml → codebase-plugin/.gradlew publish
サブフォルダー {name}-plugin/ の構造
プラグインのフォルダに存在するものを詳しく見てみましょう:
codebase-plugin/
├── gradlew ← wrapper indépendant
├── settings.gradle.kts ← @Suppress("UnstableApiUsage")
│ + foobar-resolver-convention
├── build.gradle.kts ← java-gradle-plugin + signing + publish
├── gradle/
│ ├── libs.versions.toml ← catalogue complet (langchain4j, pgvector...)
│ └── wrapper/
├── buildSrc/ ← classes utilitaires buildSrc
│ ├── build.gradle.kts
│ └── src/main/kotlin/
│ ├── codebase/ ← CodebaseYmlAnonymizer, CodebaseConfiguration
│ ├── benchmark/ ← BenchmarkConfig, BenchmarkProtocol
│ ├── readme/ ← ReadmeYmlAnonymizer
│ ├── site/ ← SiteYmlAnonymizer
│ ├── slider/ ← SliderYmlAnonymizer
│ └── snapshot/ ← SnapshotManager
├── src/
│ ├── main/kotlin/codebase/
│ │ ├── CodebasePlugin.kt ← class Plugin<Project>
│ │ ├── rag/ ← pgvector, embedding, anonymization...
│ │ ├── benchmark/ ← BenchmarkRunner, export, comparison...
│ │ └── walker/ ← WorkspaceWalker
│ └── test/
│ ├── kotlin/codebase/scenarios/ ← steps Cucumber
│ ├── features/ ← .feature files
│ └── resources/datasets/ ← fixtures .adoc, .yml, .json
├── .agents/ ← gouvernance agent (INDEX, SESSIONS, etc.)
├── AGENT.adoc ← règles agent
├── PROMPT_REPRISE.adoc ← mission session
├── BACKLOG.adoc ← backlog produit
├── snapshot.adoc ← snapshot auto-généré du projet
└── embeds.yml ← config RAG embeds
すべてそこにある。ルートとサブフォルダーの間に散らばりはない。 プラグインで作業する開発者は決して…する必要がない 去る`codebase-plugin/`. プラグインを使用する開発者 根だけを見 — そして`build.gradle.kts`3行の 彼は彼に必要なことをすべて伝える。
バージョンカタログ : 2つの異なるファイル
|
|
|
依存関係の数 |
2-3 (プラグイン + README オプション) |
30+ (langchain4j, pgvector, cucumber…) |
役割 |
プラグインを消費する |
プラグインをビルド |
それを読む人 |
プラグインの利用者 |
プラグインの開発者 |
ルートは意図的に最小限のカタログを持っています。プラグインはカタログを持っています。 完了。 混同は不可能です:各ビルドはそれぞれ固有のスコープを持ちます。 依存関係の。
|
依存関係の衝突をデバッグするのに既に1時間以上費やしたことがある場合 あなたのプラグインとテストプロジェクトの間で、その価値がわかる この分離。独立したカタログはこの問題を解消します 構成により。 |
私たちがやらないこと
このパターンは魔法的ではない。私が課す制約がある 喜んで私に課す:
ルートはプラグインをビルドしません。 あなたは`publishToMavenLocal` ou root がそれを消費する前に、リポジトリにデプロイする。
これは最小限のコストです。そしてそれが*適切な*制約です。ルート プラグインを外部クライアントのように消費する — Mavenを通じて。確かに サードパーティプロジェクトが行うように。プラグインが公開できない場合、 root がすぐに教えてくれます。
# La seule "friction" du pattern
$ cd codebase-plugin && ./gradlew publishToMavenLocal && cd ..
$ ./gradlew tasks --group=codebase
比較:ドッグフーディングに直面する3つのアーキテクチャ
隔離されたプラグイン |
モノレポ`include()` |
複合`includeBuild()` |
ルート + 独立プラグイン |
|
`git clone && gradlew tasks`プラグインのタスクを与える |
❌ |
�✅ |
✅ |
(No output, as there is no French text provided to translate) |
ルートに依存しないビルドプラグイン |
�✅ |
�❌ |
�✅ |
�✅ |
バージョン別カタログ |
�✅ |
�❌ |
�✅ |
✅ |
ない`include()` ni |
�✅ |
�❌ |
❌ |
�✅ |
設定なしのネイティブドッグフード |
❌ |
✅ |
⚠️ |
</think> |
独立したCIプラグイン |
�✅ |
❌ |
(empty string) |
✅ |
ルートは実際の消費の例 |
(Empty) |
⚠️ |
�✅ |
</think> (No output as there is no French text provided to translate) |
右�側の列はすべてのチェックボックスにチェックを入れます。それが理由で私は さらに後ろに戻ります。
DAG契約 : サブディレクトリからはルートビルドが決して行われない
このアーキテクチャは自然にDAG N0→N3に組み込まれます。 私のワークスペースから。パターンは:*プラグインN2はハブ 自身の依存関係において、N3のルートは終端です。 ハブ*を適用する
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontSize 11
title DAG契約 — コンシューマールート vs 独立プラグイン
package "N3 — 根 (預金)" #FFCCCC {
[build.gradle.kts (3 lignes)] as ROOT
[libs.versions.toml (minimal)] as ROOT_TOML
note right of ROOT
plugins { bakery }
bakery { configPath = ... }
Aucune logique métier
end note
}
package "N2 — {name}-プラグイン/" #CCFFCC {
[build.gradle.kts (complet)] as PLUGIN
[src/main/kotlin/] as SRC
[gradle/libs.versions.toml] as PLUGIN_TOML
[buildSrc/] as BUILDSRC
note bottom of PLUGIN
java-gradle-plugin
signing + publish
Tests unitaires + Cucumber
CI dédiée
end note
}
ROOT_TOML --> ROOT : "バージョン プラグイン"
ROOT -down-> PLUGIN : "publishToMavenLocal"
@enduml
Le codebase-gradle/build.gradle.kts`6行です。 なし`src/, ない`buildSrc/, なし`gradle/rag-bench.gradle.kts. 正しい plugins { alias(libs.plugins.codebase) }`そしてリポジトリ。 すべての複雑さはそこにある`codebase-plugin/。
結論:時間を節約する見かけの重複
誰かにこの構造を示すとき、最初の反応 よくある:「でも、あなたは二つ`gradlew`, 二`settings.gradle.kts`, 二`libs.versions.toml`— それはただの複製です!
はい。そしていいえ。
「複製」は、同じ情報を2か所に再現すること。 ここで、これらは2つの*別々の*用途に使われる2つの*別々の*ファイルです: プラグインカタログ(30+ ビルダー用依存関係)およびカタログ ルートから(2-3の依存関係を消費する)。 プラグインのラッパー (開発用ロックされたバージョン) と ルートのラッパー (プラグインの演習用として、異なるバージョンである可能性があります).
これは複製ではありません。これは分離の �責任 ビルドシステムに適用された。 各ビルド 一つのことだけを行う。 ルートが消費する。 プラグインがビルドされる。
コスト? 一つの注文`publishToMavenLocal`プラグインのビルドの間 そしてルートのビルド。利点とは?建築的な明確さをもたらす 下流のデバッグに費やす時間を削減します。
このパターンをデプロイして以来`bakery-gradle`, plantuml-gradle, codebase-gradle, 他のプラグインの`foundry/public/, 持っていません ターミナルを私のリポジトリの一つで開けたとき、二度とためらった。 最初の反射 —./gradlew tasks`— 歩み続け、常に与える 良いタスクを教えてくれ、すぐにすべてが健全かどうかを教えてくれる。
それが良いアーキテクチャだ。それはREADMEには書かれていない。 端末でテストされ、10秒以内に完了します。
参照
-
記事 0123 — Knowledge Graph をレコメンデーション エンジンとして
関連記事
31 May 2026