waktu membaca : 11 minutes

Anda mengklon repositori plugin Gradle. Anda membuka terminal. Apa yang Anda ketik? Selanjutnya?./gradlew tasks, tentunya. Namun dalam folder mana? Akar? Sub-modul? Apakah harus membaca README terlebih dahulu untuk mengetahui cara membangun? Jika Anda ragu, bukankah dalam sekejap, arsitektur proyeknya rusak

Ini adalah bagaimana saya sudah menyelesaikan masalah ini sekali untuk selamanya — dan mengapa ini pattern saat ini adalah tanda tangan semua plugin Gradle saya di`foundry/public/`.

Masalah: Tiga Arsitektur yang Menyita Waktu Saya

Sebelum mencapai pola saat ini, saya mencoba-coba antara tiga pendekatan Klasik untuk mengatur plugin Gradle. Masing-masing memiliki kelemahan yang tidak dapat diterima.

Opsi 1: Plugin Terisolasi (tidak ada dogfooding)

Proyek hanya berisi plugin. Tidak ada contoh konsumsi. Tidak ada proyek yang mengerjakannya. Untuk mengujinya, harus membuat proyek eksternal, y mereferensikan plugin melalui`mavenLocal`atau satu build komposit, dan hanya periksa bahwa itu berfungsi

$ git clone mon-plugin
$ cd mon-plugin
$ ./gradlew build          # le plugin compile
$ # ... et maintenant ? comment je l'essaie ?

Plugin Gradle tanpa contoh konsumsi adalah sebuah pustaka tanpa tes integrasi. Anda tidak pernah tahu jika perubahan terakhir a merusak pengalaman pengguna

Opsi 2 : Monorepo Klasik (include(":plugin"))

Gradle`init`menghasilkan`settings.gradle.kts`dengan`include("plugin")`. Root dan sub-modul membagikan daemon yang sama, konfigurasi yang sama, katalog yang sama. Praktis, tetapi terhubung.

.
├── settings.gradle.kts   → include("plugin")
├── build.gradle.kts      → plugins { id("mon-plugin") }
├── plugin/
│   └── build.gradle.kts  → java-gradle-plugin
└── gradle/
    └── libs.versions.toml

Yang menyangkut:

  • akarharusmemiliki versi Gradle yang sama dengan sub-modul

  • `libs.versions.toml`dibagikan — katalog versi umum, dependensi

yang melarikan diri dari satu modul ke modul lain

  • Tidak dapat membangun plugin secara independen dari root.

  • CI harus membangun kedua modul, meski hanya plugin yang berubah.

Opsi 3 : Build Komposit (includeBuild())

Kami memisahkan keduanya menjadi build Gradle terpisah dan kami menghubungkannya melalui includeBuild("mon-plugin")`dalam`settings.gradle.kts.

Ini sudah lebih baik. Build plugin terisolasi. Tapi konsumen harus secara eksplisit merujuk pada build eksternal — dan output dari `./gradlew tasks`pada akar bergantung pada konfigurasi yang benar dari komposit. Kloning bukan zero-config : Anda harus mengetahui bahwa plugin berada di folder terpisah, yang direferensi oleh root, etc.

.
├── settings.gradle.kts   → includeBuild("plugin-build/")
├── build.gradle.kts      → plugins { id("mon-plugin") }
└── plugin-build/
    ├── settings.gradle.kts
    └── build.gradle.kts

Saya mencari yang lebih baik. Lebih baik lagi.

Solusi: Dua Build Independen, Akar Konsumen

Berikut adalah pola yang akhirnya saya mengadopsi:

.
├── 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

Kunci :`{name}-plugin/`adalah sebuah proyek Gradle yang lengkap dan mandiri Dia memiliki wrapper sendiri, settings sendiri, katalog de versi. Ia diklon, dibuild, ditest, dan dipublikasikan tanpa akar tahu akan keberadaannya

Root, hanya satu hal: menerapkan plugin.

plugins {
    alias(libs.plugins.bakery)
}

repositories {
    mavenLocal()
    mavenCentral()
}

bakery { configPath = file("site.yml").absolutePath }

Tiga baris dalam kasus`bakery-gradle`. Tidak ada lagi. Nol`include(), nol`includeBuild(), nol sub-proyek. Sebuah build Gradle klasik yang menerapkan plugin seperti apa pun Konsumen mana yang akan melakukannya?

Alur Kerja

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontSize 12

left to right direction

package "RACINE (Konsumen)" #CCFFCC {
    usecase "Klona repositori" 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/ (Build Mandiri)" #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 : "bergantung pada plugin
diterbitkan secara lokal"
BuildPlugin -up-> MavenLocal
TestPlugin -up-> BuildPlugin

@enduml

Workflow konkret

# 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.

Mengapa arsitektur ini menaklukkan saya

Tiga manfaat yang, ketika digabungkan, setara dengan biaya « duplication» :

Dogfooding Asli, Umpan Balik Instan

Uji terbaik sebuah plugin Gradle adalah menggunakannya. Bukan sebuah unit test yang dimock. Bukan sebuah`GradleRunner`dengan sebuah proyek uji. Sebuah build nyata yang menerapkan plugin pada file yang nyata.

$ git clone bakery-gradle
$ cd bakery-gradle
$ ./gradlew bake    # ← le plugin est exercé immédiatement

Jika plugin rusak, build root memberitahu. Tidak perlu pergi Mencari proyek pengujian eksternal. Dogfooding adalah pertama. Tugas yang diluncurkan oleh kontributor baru. Ini adalah smoke test muktamad.

Aturan ini sederhana: jika akar tersebut dikompilasi dan`./gradlew tasks` Menampilkan tugas plugin Anda, plugin berfungsi. Tidak ada kejutan. dalam produksi.

2. Kloning Tanpa Konfigurasi

Un `git clone && ./gradlew tasks`dan orang baru melihat semuanya berjalan tanpa mengonfigurasi apa pun. Yang`build.gradle.kts`akar adalah dokumentasi hidup penggunaan plugin. Yang`site.yml`di samping menunjukkan konfigurasi yang diharapkan.

Bandingkan dengan alternatif: sebuah README dengan tiga paragraf yang jelaskan bagaimana builder plugin dan bagaimana builder proyek dari uji. Seorang kontributor baru membaca README secara diagonal, salah, membuka sebuah isu — padahal informasi tersebut mungkin adalah dapat dieksekusi.

Documentation yang paling kuat bukan yang dibaca. Itu adalah yang kitaMenjalankan. Build akar adalah dokumentasi file eksekusi plugin.

3. Builds terisolasi, CI mandiri

Plugin memiliki wrapper Gradle sendiri, siklus hidupnya sendiri, Tes-tes pribadinya. Anda dapat:

  • Mengupgrade Gradle di plugin tanpa menyentuh root

  • Menambahkan dependensi di dalam plugin tanpa bocor ke root

  • Menghancurkan plugin tanpa memengaruhi build dasar (selama Anda tidak

jangan publikasikan versi yang rusak)

  • Memiliki sebuah CI yang membangun dan menguji plugin, dan yang lain yang menjalankan

root — secara independen

.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

Anatomi dari Subdirektori `{name}-plugin/

Mari kita lihat lebih dekat apa yang ada di folder 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

Semua ada di sini. Tidak ada dispersi antara akar dan sub-direktori. Pengembang yang bekerja pada plugin tidak pernah membutuhkan meninggalkan`codebase-plugin/`. Pengembang yang menggunakan plugin hanya melihat akar — dan`build.gradle.kts`dari 3 baris Dia memberitahunya semua yang perlu diketahui.

Katalog Versi: Dua File Terpisah

gradle/libs.versions.toml(akar)

{name}-plugin/gradle/libs.versions.toml

Jumlah dependensi

2-3 (plugin + readme opsional)

30+ (langchain4j, pgvector, cucumber…​)

peran

Mengonsumi plugin

Bangun plugin

Siapa yang membacanya?

Pengguna plugin

Pengembang plugin

root memiliki katalog yang secara sengaja minimal. plugin memiliki katalog Lengkap. Kebingungan tidak mungkin: setiap build memiliki ruang lingkupnya sendiri. ketergantungan.

Jika Anda telah menghabiskan satu jam untuk men-debug tabrakan dependensi antara plugin Anda dan proyek pengujian Anda, Anda memahami nilai dari ini pemisahan. Katalog independen menghilangkan masalah ini dari konstruksi.

Apa yang tidak kita lakukan

Pola ini bukan ajaib. Ia mengimpos sebuah batas yang saya mengakibatkan saya dengan sukarela:

Root tidak membangun plugin. Anda harus`publishToMavenLocal` ou men-deploy pada repositori sebelum root dapat menggunakannya

Ini adalah biaya kecil, dan ini adalah kontrain yang baik. Root Menggunakan plugin sebagai klien eksternal — melalui Maven. Tepat seperti yang dilakukan oleh proyek pihak ketiga. Jika plugin tidak dapat dipublikasikan, root memberitahu Anda itu segera.

# La seule "friction" du pattern
$ cd codebase-plugin && ./gradlew publishToMavenLocal && cd ..
$ ./gradlew tasks --group=codebase

Perbandingan: Tiga Arsitektur Terhadap Dogfooding

Plugin terisolasi

repositori tunggal`include()`

komposit`includeBuild()`

Akar + plugin independen

`git clone && gradlew tasks`memberikan tugas plugin

❌

✅

✅

✅

Bangun plugin independen dari root

✅

❌

✅

✅

Katalog versi terpisah

✅

❌

✅

✅

Tidak`include()` ni includeBuild()

✅

❌

❌

✅

Dogfood native tanpa konfigurasi

❌

✅

Terjemahkan teks berikut dari Prancis ke Indonesia, dengan menjaga semua rentang kode yang dikutip dengan backtick (…​) tanpa perubahan apa pun — isi, spasi, dan posisinya harus tetap utuh. Terjemahkan hanya fragmen yang diberikan tanpa menambah konteks. Keluarkan hanya hasil terjemahan, tanpa penjelasan, komentar, atau teks tambahan.

✅

CI plugin independen

✅

❌

✅

(Empty)

Kolom kanan mencentang semua kotak. Itu sebabnya saya tidak Saya akan kembali lebih ke belakang.

Kontrak DAG: Build akar tidak pernah berasal dari subfolder

Arsitektur ini terintegrasi secara alami dalam DAG N0→N3 dari workspace saya. Pola adalah: * plugin N2 adalah hub dari ketergantungannya sendiri, akar N3 adalah sebuah terminal yang menerapkan hubs*

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontSize 11

title Kontrak DAG — Konsumen Root vs Plugin Independen
package "N3 — AKAR (setoran)" #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}-plugin/" #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 : "versi plugin"
ROOT -down-> PLUGIN : "Publikasikan ke repositori Maven lokal"
@enduml

Le codebase-gradle/build.gradle.kts`buat 6 baris. Tidak ada`src/, tidak ada`buildSrc/, tidak ada`gradle/rag-bench.gradle.kts. hanya plugins { alias(libs.plugins.codebase) }`dan repositori. Semua kompleksitas berada di`codebase-plugin/.

Conclusion : Duplikasi Tampak yang Menghemat Waktu

Saat saya menunjukkan struktur ini kepada seseorang, reaksi pertama sering : « Tapi kamu memiliki dua`gradlew`, dua`settings.gradle.kts`, dua`libs.versions.toml`— ini adalah duplikasi!

Ya. Dan tidak.

Duplikasi » berarti mereproduksi informasi yang sama di dua tempat. Di sini, ini adalah dua file distincts yang melayani dua kegunaan distincts : katalog plugin (30+ dependensi untuk builder) dan katalog dari akar (2-3 dependensi untuk dikonsumsi). wrapper plugin (versi terkunci untuk pengembangan) dan wrapper dari akar (versi yang berpotensi berbeda, untuk latihan plugin).

Bukan duplikasi. Ini pemisahan tanggung jawab diterapkan ke sistem build. Setiap build Melakukan satu hal, dan hanya satu. Akar mengkonsumsi. Plugin membangun.

Biaya? Satu pesanan`publishToMavenLocal`antara build plugin dan pembangunan akar. Keuntungan? Kejelasan arsitektur yang menghemat jam-jam waktu debugging di hilir

Sejak saya men-deploy pola ini di`bakery-gradle`, plantuml-gradle, codebase-gradle, dan plugins lain`foundry/public/, saya tidak punya Tidak pernah lagi ragu ketika membuka terminal di salah satu repositori saya. Yang refleks pertama —./gradlew tasks`— selalu berjalan, selalu memberi tugas yang baik, dan memberitahu saya secara instan jika semuanya sehat.

Ini dia, arsitektur yang baik. Dia tidak dapat dibaca dalam README. Dia diuji dalam terminal, dalam kurang dari sepuluh detik.

Referensi

Artikel terkait