Context en doel

Het startpunt

Het project`slider-gradle`genereert Reveal.js-presentaties van AsciiDoc-bestanden via de Gradle-plugin`org.asciidoctor.jvm.revealjs`. De configuratie van de hoofdtaak`asciidoctorRevealJs`leefde direct in het hoofd buildscript`build.gradle.kts`(Empty output)

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
    }
}

Het doel

Verplaats deze hele configuratie naar`buildSrc/src/main/kotlin/slides/SlidesPlugin.kt`zodat het buildscript van de consument wordt teruggebracht tot:

apply<slides.SlidesPlugin>()

De plug‑in moet volledig autonoom zijn: hij past zijn plug‑in‑afhankelijkheden zelf toe, configureert zijn repositories, en beheert zijn Ruby‑gems.

Het eerste obstakel : `ruby { gems() }

Wat de Groovy syntactische suiker verbirgt

De lijn`repositories { ruby { gems() } }`is een Groovy DSL-extensie die alleen beschikbaar is in de uitvoeringscontext van het buildscript. Het bestaat niet als een statische Kotlin-API die toegankelijk is vanaf buildSrc.

Bij het proberen om hem/haar te bellen vanaf`SlidesPlugin.kt`, de compilatie mislukt onmiddellijk.

Ontbinding van het mechanisme

Na analyse van de Gradle-cache (~/.gradle/caches/modules-2/files-2.1/rubygems/), ontdekken we in het bestand`ivy-3.1.0.xml`:

<artifact type='gem' url='https://rubygems.org/gems/asciidoctor-revealjs-3.1.0.gem' />

`ruby { gems() }`voerde eigenlijk uitdrie afzonderlijke operaties:

  1. Een Ivy-repo opslaan dat naar …​ verwijst`https://rubygems.org/gems/`

  2. Groep uitsluiten`rubygems`Maven-repositories om conflicten te voorkomen

  3. Sla de gem op in de configuratie`asciidoctorGems`zodaat JRuby het tijdens runtime laadt

Deze drie verantwoordelijkheden moeten apart in Kotlin gereproduceerd worden.

De oplossing in drie delen

Deel 1: de Ivy-repo voor 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") }
}
1 De extensie`.gem`moet hard gecodeerd zijn.[ext]`lost standaard in.jar`, wat een fout veroorzaakt`Resource missing`.
De Ivy-repo wordt direct aangekondigd op`project.repositories`en niet in een blok`repositories { }`omdat de receiver van dit blok niet de`RepositoryHandler`standaard van Gradle maar een API grolifant onverenigbaar met de extensie`ivy`Kotlin DSL

Deel 2: de afhankelijkheid asciidoctorGems

De plugin`org.asciidoctor.jvm.gems`moet als eerste worden toegepast — het creëert de configuratie`asciidoctorGems`en de taak`asciidoctorGemsPrepare`Please provide the text you would like me to translate.

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 De volgorde van toepassing is belangrijk:`gems`voor`revealjs`. <2> De kwalificator`@gem`Forceer de juiste extensie op de afhankelijkheid.

Deel 3 : `settings.gradle.kts

`dependencyResolutionManagement`in`settings.gradle.kts`moet de projecten toestaan hun eigen repositories te verklaren :

@Suppress("UnstableApiUsage")
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.PREFER_PROJECT)
}

Zonder deze regel, Gradle negeert de in de plugin gedeclareerde repositories en mislukt de oplosser van de gems.

De API-introspectie via bytecodes

Waarom javap?

De plugin`asciidoctor-gradle-jvm-slides`is in versie`4.0.0-alpha.1`. De documentatie is niet bestaand of onvolledig. De enige betrouwbare bron is de directe inspectie van de gecompileerde klassen.

Hiërarchie van de taak

javap -p -classpath asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar \
  org.asciidoctor.gradle.jvm.slides.AsciidoctorJRevealJSTask

Resultaat :

public class AsciidoctorJRevealJSTask
  extends org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask
  implements org.asciidoctor.gradle.base.slides.SlidesToExportAware

Ontdekking van `forkOptions

Bij het inspecteren`AbstractAsciidoctorTask`[Translation of empty string: nothing]

javap -p -classpath asciidoctor-gradle-jvm-4.0.0-alpha.1.jar \
  org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask | grep -i "fork\|exec\|jvm"

Men vindt:

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;

De API van JavaForkOptions (grolifant)

`javaLauncher`bestaat niet op deze taak. De echte API van`org.ysb33r.grolifant.api.v4.JavaForkOptions`tentoonstelling :

public void executable(java.lang.Object);  (1)
public void setExecutable(java.lang.Object);
1 De methode`executable(Object)`vervang de toewijzing`executable = …​`die niet compileert (`val`kan niet opnieuw worden toegewezen).

RevealJSExtension

`revealjs { }`is geen methode van de taak maar eenuitbreiding van het project:

javap -p -classpath asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar \
  org.asciidoctor.gradle.jvm.slides.RevealJSExtension

Ze wordt via :

project.extensions.getByType<RevealJSExtension>().apply {
    version = "3.1.0"
    templateGitHub {
        setOrganisation("hakimel")
        setRepository("reveal.js")
        setTag("3.9.1")
    }
}

Configuratie van de Java toolchain

Het probleem van JavaToolchainService

`JavaToolchainService`Het is geen projectuitbreiding. De volgende aanroep mislukt :

// ERREUR : Extension of type 'JavaToolchainService' does not exist
project.extensions.getByType<JavaToolchainService>()

De goede API is`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
        )
    }
}

Automatische detectie Docker

Context

De Asciidoctor/JRuby-plugin vereist Java 17. Kotlin 2.0.x in buildSrc ondersteunt Java 25 niet (de versieparser crasht op`"25.0.2"`). De Gradle-daemon moet dus draaien op Java 17 of Docker moet worden gebruikt.

Strategie

  1. Docker beschikbaar → uitvoering via container`eclipse-temurin:17`(standaardgedrag)

  2. Docker afwezig + Java 17 → lokale uitvoering

  3. Docker ontbreekt + Java > 17 → expliciete fout

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."
    )
}

Eindresultaat

De consumenten buildscript

apply<slides.SlidesPlugin>()

Dat is alles. De plugin draagt de volledige verantwoordelijkheid.

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"

Samenvatting van de valkuilen en oplossingen

probleem Oorzaak Oplossing

`ruby { gems() }`niet beschikbaar in Kotlin

Alleen Groovy DSL-extensie

Drie gescheiden mechanismen : repo Ivy + Maven-uitsluiting +asciidoctorGems

Gradle zoekt een`.jar`in plaats van een`.gem`

`[ext]`lost op en`jar`standaard

Hardcoder`.gem`in het patroon Ivy + kwalificatie`@gem`

ivy { }`compileert niet in`repositories { }

Receiver grolifant onverenigbaar met de Kotlin DSL

Directe oproep`project.repositories.ivy { }`

`javaLauncher`onopgelost

Niet-bestaande eigenschap op`AsciidoctorJRevealJSTask`

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

`executable = …​`compileert niet

Eigendom`val`in`JavaForkOptions`grolifant

Methode`executable(Object)`in de plaats

JavaToolchainService`niet gevonden via`extensions

Dit is een Gradle-service, geen extensie

project.serviceOf<JavaToolchainService>()

`revealjs { }`niet opgelost in de taak

Projectuitbreiding, geen taakmethode

project.extensions.getByType<RevealJSExtension>()

Build crasht met Java 25

Kotlin 2.0.x parseert geen Java-versies met twee cijfers

Automatische Docker-detectie + fallback Java 17

Onderzoeksmethode : een onbekende API lezen met javap

Principe

Als de documentatie ontbreekt of incompleet is, zijn de bytecodes de bron van waarheid. javap`is het standaardhulpmiddel van de JDK die bestanden decompileert.class`in leesbare Java-handtekeningen, zonder de broncode nodig te hebben

Stap 1: zoek de jar in de Gradle-cache

Gradle downloadt al zijn afhankelijkheden in`~/.gradle/caches/modules-2/files-2.1/`. De eerste stap is om de jar te vinden die de te inspecteren klasse bevat :

find ~/.gradle/caches -name "asciidoctor-gradle-jvm-slides*.jar" 2>/dev/null
Resultaat :
/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

Stap 2: de klassen van de jar opsommen

Voordat we een klasse inspecteren, controleren we of deze bestaat in de jar :

jar tf asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar | grep -i "RevealJS\|revealjs"

Stap 3 : een klasse inspecteren

Dit onthult alle beschikbare klassen : AsciidoctorJRevealJSTask, RevealJSExtension, RevealJSOptions, etc.
javap -p -classpath asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar \
org.asciidoctor.gradle.jvm.slides.AsciidoctorJRevealJSTask

De optie`-p`toont alle leden inclusief de privé-leden. Het resultaat toont onmiddellijk de sleutelregel:

public class AsciidoctorJRevealJSTask
extends org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask

Stap 4: de hiërarchie beklimmen

De taak strekt uit`AbstractAsciidoctorTask`. We inspecteren het op zijn beurt door eerst zijn jar te vinden :

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"

Daar ontdekken we`forkOptions`, JAVA_EXEC, et javaForkOptions van het type`org.ysb33r.grolifant.api.v4.JavaForkOptions`.

Stap 5: onbekende typen volgen

`JavaForkOptions`is een grolifant onbekende klasse. Zijn jar wordt gelokaliseerd:

find ~/.gradle/caches -name "grolifant*.jar" 2>/dev/null

Daarna inspecteren we het:

javap -p -classpath grolifant40-legacy-api-2.0.0-alpha.6.jar \
org.ysb33r.grolifant.api.v4.JavaForkOptions

Daar vind je`executable(java.lang.Object)— de juiste methode om aan te roepen, in tegenstelling tot`executable = …​`die niet compileert omdat het een eigenschap is`val.

Stap 6: controleer de projectextensies

Voor`revealjs { }`, de vraag was: is dit een methode van de taak of een projectuitbreiding? De inspectie van`AsciidoctorJRevealJSTask` toont geen methode`revealjs`. Men inspecteert dan`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;

De aanwezigheid van`NAME`bevestigt dat het een geregistreerde extensie is op het project, toegankelijk via`project.extensions.getByType<RevealJSExtension>()`.

Samenvatting van de methode

Stap Actie

1

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

2

jar tf jar.jar | grep NomClasse— controleer of de klasse bestaat

3

javap -p -classpath jar.jar NomCompletClasse— de klasse inspecteren

4

Identificeren`extends`en de hiërarchie beklimmen

5

Volg onbekende types in hun eigen jars

6

zoeken`NAME`om een projectextensie te identificeren

Deze methode is van toepassing op elke Gradle-plugin waarvan de API niet is gedocumenteerd. of waarvan de alpha-versie niet meer overeenkomt met de bestaande documentatie.

Volgende stap

Dit buildSrc-plugin wordt geëxtraheerd naar een apart project dat wordt gepubliceerd op de Gradle Plugin Portal of Maven Local. Het consumer-buildscript wordt dan:

plugins { id("slides") version "1.0.0" }

Et settings.gradle.kts`zal worden teruggebracht tot het strenge minimum zonder verwijzing naar`foojay-resolver-convention, het JDK-provisioning wordt beheerd door de plugin zelf of wordt gedocumenteerd als vereiste.

Gerelateerde artikelen