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:

  1. Menyimpan repo Ivy yang menunjuk ke`https://rubygems.org/gems/`

  2. Mengecualikan kelompok`rubygems`des repositori Maven untuk menghindari konflik

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

  1. Docker tersedia → eksekusi melalui kontainer`eclipse-temurin:17`(perilaku default)

  2. Docker tidak ada + Java 17 → eksekusi lokal

  3. 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 +asciidoctorGems

Gradle sedang mencari satu`.jar`alih-alih sebuah`.gem`

`[ext]`menyelesaikan dalam`jar`secara default

Hardcoder`.gem`dalam pola Ivy + kualifikasi`@gem`

ivy { }`tidak kompiliasi di`repositories { }

Receiver grolifant tidak kompatibel dengan DSL Kotlin

Panggilan langsung`project.repositories.ivy { }`

`javaLauncher`belum diselesaikan

Properti tidak ada pada`AsciidoctorJRevealJSTask`

setInProcess("JAVA_EXEC")+forkOptions { executable(…​) }

`executable = …​`tidak bisa dikompilasi

sifat`val`dalam`JavaForkOptions`grolifant

Metode`executable(Object)`alih-alih

JavaToolchainService`tidak dapat ditemukan via`extensions

Ini adalah layanan Gradle, bukan ekstensi

project.serviceOf<JavaToolchainService>()

`revealjs { }`tidak terselesaikan dalam tugas

Ekstensi proyek, bukan metode tugas

project.extensions.getByType<RevealJSExtension>()

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
Hasil :
/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

Ini menampilkan semua kelas yang tersedia : 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

find ~/.gradle/caches -name "*.jar"— menemukan jar

2

jar tf jar.jar | grep NomClasse— memastikan bahwa kelas ada

3

javap -p -classpath jar.jar NomCompletClasse— memeriksa kelas

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.

Artikel terkait