Migrasi plugin Asciidoctor RevealJS ke buildSrc Kotlin: reverse engineering dari API Groovy
Diterbitkan 16 April 2026
Konteks dan tujuan
titik awal
Proyek`slider-gradle`Menghasilkan presentasi Reveal.js dari file AsciiDoc menggunakan plugin Gradle`org.asciidoctor.jvm.revealjs`. Konfigurasi tugas utama`asciidoctorRevealJs`berada langsung dalam buildscript root`build.gradle.kts`(blank)
plugins { id("org.asciidoctor.jvm.revealjs") }
apply<slides.SlidesPlugin>()
project.tasks.getByName<AsciidoctorJRevealJSTask>(TASK_ASCIIDOCTOR_REVEALJS) {
repositories { ruby { gems() } }
revealjs {
version = "3.1.0"
templateGitHub {
setOrganisation("hakimel")
setRepository("reveal.js")
setTag("3.9.1")
}
}
revealjsOptions {
// ... configuration complète
}
}
Tujuan
Memindahkan seluruh konfigurasi ini ke`buildSrc/src/main/kotlin/slides/SlidesPlugin.kt`supaya buildscript konsumen berkurang menjadi :
apply<slides.SlidesPlugin>()
Plugin harus sepenuhnya mandiri: ia mengaplikasikan sendiri dependensi plugin-nya, mengonfigurasi repositori-nya, dan mengelola gem Ruby-nya.
Halangan pertama : `ruby { gems() }
Apa yang disembunyikan oleh gula sintaksis Groovy
Baris`repositories { ruby { gems() } }`adalah ekstensi DSL Groovy yang hanya tersedia dalam konteks eksekusi buildscript. Ia tidak ada sebagai API Kotlin statis yang dapat diakses dari buildSrc.
Saat mencoba memanggilnya dari`SlidesPlugin.kt`, kompilasi gagal segera.
Dekomposisi mekanisme
Setelah menganalisis cache Gradle (~/.gradle/caches/modules-2/files-2.1/rubygems/), kami menemukan di file`ivy-3.1.0.xml`:
<artifact type='gem' url='https://rubygems.org/gems/asciidoctor-revealjs-3.1.0.gem' />
`ruby { gems() }`sebenarnya melakukantiga operasi berbeda:
-
Menyimpan repo Ivy yang menunjuk ke`https://rubygems.org/gems/`
-
Mengecualikan kelompok`rubygems`des repositori Maven untuk menghindari konflik
-
Simpan gem dalam konfigurasi`asciidoctorGems`agar JRuby memuatnya saat runtime
Ketiga tanggung jawab ini harus direproduksi secara terpisah dalam Kotlin.
Solusi dalam tiga bagian
Bagian 1: repositori Ivy untuk rubygems
project.repositories.mavenCentral() {
content { excludeGroup("rubygems") }
}
project.repositories.ivy {
url = project.uri("https://rubygems.org/gems/")
patternLayout { artifact("[module]-[revision].gem") } (1)
metadataSources { artifact() }
content { includeGroup("rubygems") }
}
Ekstensi`.gem`harus dihardcode.[ext]`menyelesaikan secara default dalam.jar`, yang menyebabkan kesalahan`Resource missing`.
| Repo Ivy dideklarasikan langsung di`project.repositories`dan tidak dalam sebuah blok`repositories { }`karena receiver dari blok ini bukan`RepositoryHandler`standar Gradle namun sebuah API grolifant tidak kompatibel dengan ekstensi`ivy`Kotlin DSL. |
Bagian 2: ketergantungan asciidoctorGems
Plugin`org.asciidoctor.jvm.gems`harus diterapkan terlebih dahulu — ia membuat konfigurasi`asciidoctorGems`dan tugas`asciidoctorGemsPrepare`:
project.plugins.apply("org.asciidoctor.jvm.gems")
project.plugins.apply("org.asciidoctor.jvm.revealjs") (1)
project.dependencies {
add("asciidoctorGems", "rubygems:asciidoctor-revealjs:3.1.0@gem") (2)
}
| 1 | Urutan penerapan penting:`gems`sebelum`revealjs`. <2> Pemenuh syarat`@gem`Paksa ekstensi yang benar pada dependensi. |
Bagian 3 : settings.gradle.kts
`dependencyResolutionManagement`dalam`settings.gradle.kts`harus mengizinkan proyek-proyek untuk mendeklarasikan repositori mereka sendiri :
@Suppress("UnstableApiUsage")
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_PROJECT)
}
Tanpa baris ini, Gradle mengabaikan repos yang dideklarasikan dalam plugin dan resolusi gems gagal.
Introspeksi API dengan bytecode
Mengapa javap ?
Plugin`asciidoctor-gradle-jvm-slides`adalah dalam versi`4.0.0-alpha.1`. Dokumentasinya tidak ada atau tidak lengkap. Sumber terpercaya yang satu-satunya adalah inspeksi langsung dari kelas yang dikompilasi.
Hierarki tugas
javap -p -classpath asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar \
org.asciidoctor.gradle.jvm.slides.AsciidoctorJRevealJSTask
Hasil :
public class AsciidoctorJRevealJSTask
extends org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask
implements org.asciidoctor.gradle.base.slides.SlidesToExportAware
Penemuan `forkOptions
Saat memeriksa`AbstractAsciidoctorTask`:
javap -p -classpath asciidoctor-gradle-jvm-4.0.0-alpha.1.jar \
org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask | grep -i "fork\|exec\|jvm"
Ada :
final org.ysb33r.grolifant.api.v4.JavaForkOptions javaForkOptions;
public void forkOptions(org.gradle.api.Action<org.ysb33r.grolifant.api.v4.JavaForkOptions>);
public static final org.asciidoctor.gradle.base.process.ProcessMode JAVA_EXEC;
API JavaForkOptions (grolifant)
`javaLauncher`tidak ada pada tugas ini. API nyata dari`org.ysb33r.grolifant.api.v4.JavaForkOptions`penjelasan :
public void executable(java.lang.Object); (1)
public void setExecutable(java.lang.Object);
| 1 | Metode`executable(Object)`mengganti penugasan`executable = …`yang tidak dapat dikompilasi (`val`tidak dapat ditetapkan ulang). |
RevealJSExtension
`revealjs { }`bukan metode dari tugas ini tetapi sebuahperpanjangan proyek:
javap -p -classpath asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar \
org.asciidoctor.gradle.jvm.slides.RevealJSExtension
Diakses melalui:
project.extensions.getByType<RevealJSExtension>().apply {
version = "3.1.0"
templateGitHub {
setOrganisation("hakimel")
setRepository("reveal.js")
setTag("3.9.1")
}
}
Konfigurasi toolchain Java
Masalah dari JavaToolchainService
`JavaToolchainService`bukan merupakan ekstensi proyek. Pemanggilan berikutnya gagal :
// ERREUR : Extension of type 'JavaToolchainService' does not exist
project.extensions.getByType<JavaToolchainService>()
API yang baik adalah`serviceOf` :
import org.gradle.kotlin.dsl.support.serviceOf
project.tasks.getByName<AsciidoctorJRevealJSTask>(TASK_ASCIIDOCTOR_REVEALJS) {
setInProcess("JAVA_EXEC")
forkOptions {
executable(
project.serviceOf<JavaToolchainService>()
.launcherFor {
languageVersion.set(JavaLanguageVersion.of(17))
vendor.set(JvmVendorSpec.ADOPTIUM)
}
.get()
.executablePath
.asFile
.absolutePath
)
}
}
Deteksi otomatis Docker
konteks
Plugin Asciidoctor/JRuby membutuhkan Java 17. Kotlin 2.0.x di buildSrc tidak mendukung Java 25 (parser versi crash pada`"25.0.2"`). Daemon Gradle harus berjalan di Java 17 atau Docker harus digunakan.
Strategi
-
Docker tersedia → eksekusi melalui kontainer`eclipse-temurin:17`(perilaku default)
-
Docker tidak ada + Java 17 → eksekusi lokal
-
Docker tidak ada + Java > 17 → error eksplisit
val isDockerAvailable = try {
Runtime.getRuntime().exec(arrayOf("docker", "info")).waitFor() == 0
} catch (e: Exception) {
false
}
val javaVersion = JavaVersion.current().majorVersion.toInt()
when {
isDockerAvailable -> project.tasks.register<Exec>(TASK_ASCIIDOCTOR_REVEALJS) {
group = GROUP_TASK_SLIDER
description = "Slider settings and generation (via Docker)"
dependsOn(TASK_CLEAN_SLIDES_BUILD)
finalizedBy(TASK_DASHBOARD_SLIDES_BUILD)
commandLine(
"docker", "run", "--rm",
"-v", "${project.rootDir.absolutePath}:/workspace",
"-v", "${System.getProperty("user.home")}/.gradle:/root/.gradle",
"-w", "/workspace",
"eclipse-temurin:17",
"./gradlew", TASK_ASCIIDOCTOR_REVEALJS
)
workingDir = project.rootDir
}
javaVersion == 17 -> {
project.repositories.mavenCentral() {
content { excludeGroup("rubygems") }
}
project.repositories.ivy {
url = project.uri("https://rubygems.org/gems/")
patternLayout { artifact("[module]-[revision].gem") }
metadataSources { artifact() }
content { includeGroup("rubygems") }
}
project.extensions.getByType<RevealJSExtension>().apply {
version = "3.1.0"
templateGitHub {
setOrganisation("hakimel")
setRepository("reveal.js")
setTag("3.9.1")
}
}
project.tasks.getByName<AsciidoctorJRevealJSTask>(TASK_ASCIIDOCTOR_REVEALJS) {
setInProcess("JAVA_EXEC")
forkOptions {
executable(
project.serviceOf<JavaToolchainService>()
.launcherFor {
languageVersion.set(JavaLanguageVersion.of(17))
vendor.set(JvmVendorSpec.ADOPTIUM)
}
.get()
.executablePath
.asFile
.absolutePath
)
}
// ... reste de la configuration
}
}
else -> error(
"Docker est requis pour exécuter $TASK_ASCIIDOCTOR_REVEALJS " +
"avec Java $javaVersion. Installez Docker ou utilisez Java 17."
)
}
Hasil akhir
buildscript konsumen
apply<slides.SlidesPlugin>()
Itu saja. Plugin menanggung seluruh tanggung jawab.
settings.gradle.kts
pluginManagement {
repositories {
mavenLocal()
gradlePluginPortal()
}
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
}
@Suppress("UnstableApiUsage")
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_PROJECT)
}
rootProject.name = "slider-gradle"
Ringkasan jebakan dan solusi
| Masalah | Penyebab | Solusi |
|---|---|---|
`ruby { gems() }`tidak tersedia di Kotlin |
Ekstensi DSL Groovy hanya |
Tiga mekanisme terpisah : repo Ivy + pengecualian Maven + |
Gradle sedang mencari satu`.jar`alih-alih sebuah`.gem` |
`[ext]`menyelesaikan dalam`jar`secara default |
Hardcoder`.gem`dalam pola Ivy + kualifikasi`@gem` |
|
Receiver grolifant tidak kompatibel dengan DSL Kotlin |
Panggilan langsung`project.repositories.ivy { }` |
`javaLauncher`belum diselesaikan |
Properti tidak ada pada`AsciidoctorJRevealJSTask` |
|
`executable = …`tidak bisa dikompilasi |
sifat`val`dalam`JavaForkOptions`grolifant |
Metode`executable(Object)`alih-alih |
|
Ini adalah layanan Gradle, bukan ekstensi |
|
`revealjs { }`tidak terselesaikan dalam tugas |
Ekstensi proyek, bukan metode tugas |
|
Build crash dengan Java 25 |
Kotlin 2.0.x tidak memparse versi Java dua digit |
Deteksi Docker otomatis + fallback Java 17 |
Metode penyelidikan : membaca API yang tidak diketahui dengan javap
prinsip
Ketika dokumentasi tidak ada atau tidak lengkap, bytecode adalah sumber kebenaran. javap`adalah alat standar JDK yang mendekompilasi file.class`dalam tanda tangan Java yang dapat dibaca, tanpa memerlukan kode sumber.
Langkah 1: mencari jar di cache Gradle
Gradle mengunduh semua dependensinya di`~/.gradle/caches/modules-2/files-2.1/`. Langkah pertama adalah menemukan jar yang berisi kelas yang akan diperiksa :
find ~/.gradle/caches -name "asciidoctor-gradle-jvm-slides*.jar" 2>/dev/null
/home/user/.gradle/caches/modules-2/files-2.1/org.asciidoctor/
asciidoctor-gradle-jvm-slides/4.0.0-alpha.1/.../
asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar
Langkah 2: Mencantumkan kelas dari jar
Sebelum memeriksa sebuah kelas, kita memastikan bahwa kelas tersebut memang ada di dalam jar :
jar tf asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar | grep -i "RevealJS\|revealjs"
Langkah 3: memeriksa sebuah kelas
AsciidoctorJRevealJSTask, RevealJSExtension, RevealJSOptions, dst.javap -p -classpath asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar \
org.asciidoctor.gradle.jvm.slides.AsciidoctorJRevealJSTask
opsi`-p`Menampilkan semua anggota termasuk yang pribadi Hasilnya langsung menunjukkan baris kunci:
public class AsciidoctorJRevealJSTask
extends org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask
Langkah 4 : naik ke atas dalam hierarki
Tugas meluas`AbstractAsciidoctorTask`. Kita memeriksanya bergantian dengan mencari jar-nya terlebih dahulu :
find ~/.gradle/caches -name "asciidoctor-gradle-jvm-[0-9]*.jar" 2>/dev/null
javap -p -classpath asciidoctor-gradle-jvm-4.0.0-alpha.1.jar \
org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask | grep -i "fork\|exec\|jvm\|java"
Di sinilah kita menemukan`forkOptions`, JAVA_EXEC, et javaForkOptions jenis`org.ysb33r.grolifant.api.v4.JavaForkOptions`.
Langkah 5 : mengikuti tipe tidak dikenal
`JavaForkOptions`adalah kelas grolifant yang tidak diketahui. Kami menemukan jar-nya :
find ~/.gradle/caches -name "grolifant*.jar" 2>/dev/null
Kemudian kita memeriksanya :
javap -p -classpath grolifant40-legacy-api-2.0.0-alpha.6.jar \
org.ysb33r.grolifant.api.v4.JavaForkOptions
Di sini ada`executable(java.lang.Object)— metode yang benar untuk dipanggil, berlawanan dengan`executable = …`yang tidak dapat dikompilasi karena itu adalah sebuah properti`val.
Langkah 6: memeriksa ekstensi proyek
untuk`revealjs { }`, pertanyaannya adalah: apakah ini adalah metode dari tugas atau ekstensi proyek ? Pemeriksaan`AsciidoctorJRevealJSTask` tidak menunjukkan metode`revealjs`. Kemudian diperiksa`RevealJSExtension`:
javap -p -classpath asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar \
org.asciidoctor.gradle.jvm.slides.RevealJSExtension | head -5
public class RevealJSExtension implements groovy.lang.GroovyObject {
public static final java.lang.String NAME;
Keberadaan`NAME`mengonfirmasi bahwa ini adalah ekstensi yang terdaftar di proyek, dapat diakses melalui`project.extensions.getByType<RevealJSExtension>()`.
Ringkasan metode
| Langkah | Aksi |
|---|---|
1 |
|
2 |
|
3 |
|
4 |
pengenal`extends`dan naik dalam hierarki |
5 |
Melacak jenis yang tidak dikenal dalam jars mereka sendiri |
6 |
Mencari`NAME`untuk mengidentifikasi ekstensi proyek |
Metode ini berlaku untuk setiap plugin Gradle yang APInya tidak didokumentasikan atau yang versi alpha-nya tidak lagi sesuai dengan dokumentasi yang ada.
Langkah selanjutnya
Plugin buildSrc ini akan diekstrak ke dalam proyek mandiri yang dipublikasikan di Gradle Plugin Portal atau Maven Local. Buildscript yang dikonsumsi kemudian akan menjadi :
plugins { id("slides") version "1.0.0" }
Et settings.gradle.kts`akan diperkecil menjadi minimum ketat tanpa referensi pada`foojay-resolver-convention, provisioning JDK dikelola oleh plugin itu sendiri atau didokumentasikan sebagai prasyarat.