Kontext und Ziel

Der Ausgangspunkt

Das Projekt`slider-gradle`Erstellt Reveal.js-Präsentationen aus AsciiDoc-Dateien über das Gradle-Plugin.org.asciidoctor.jvm.revealjs. Die Konfiguration der Hauptaufgabe`asciidoctorRevealJs`lebte direkt im Root-Buildscript`build.gradle.kts`:

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

Das Ziel

Verschieben Sie diese gesamte Konfiguration in`buildSrc/src/main/kotlin/slides/SlidesPlugin.kt`damit das Consumer-Buildscript auf: reduziert wird

apply<slides.SlidesPlugin>()

Das Plugin muss völlig autonom sein: Es wendet seine Plugin-Abhängigkeiten selbst an, konfiguriert seine Repositorys und verwaltet seine Ruby-Gems.

Das erste Hindernis: `ruby { gems() }

Was der Groovy-syntaktische Zucker verbirgt

Die Zeile`repositories { ruby { gems() } }`Es ist eine Groovy-DSL-Erweiterung, die ausschließlich im Buildskript-Ausführungskontext verfügbar ist. Sie existiert nicht als statische Kotlin-API, die von buildSrc aus zugänglich ist.

Beim Versuch, ihn von …​ aus anzurufen`SlidesPlugin.kt`, die Kompilierung schlägt sofort fehl.

Zerlegung des Mechanismus

Nach Analyse des Gradle-Cache (~/.gradle/caches/modules-2/files-2.1/rubygems/), man entdeckt in der Datei`ivy-3.1.0.xml`:

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

`ruby { gems() }`führte tatsächlich durchdrei verschiedene Operationen:

  1. Ein Ivy-Repo speichernd, das auf`https://rubygems.org/gems/`

  2. Gruppe ausschließen`rubygems`Maven-Repositorien zur Vermeidung von Konflikten

  3. Speichere das Gem in der Konfiguration`asciidoctorGems`damit JRuby es zur Laufzeit lädt

Diese drei Verantwortlichkeiten müssen separat in Kotlin reproduziert werden.

Die Lösung in drei Teilen

Teil 1: das Ivy-Repository für 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 Die Erweiterung`.gem`muss hardkodiert sein.[ext]`löst standardmäßig in.jar`, was einen Fehler verursacht`Resource missing`.
Das Ivy-Repo wird direkt darauf erklärt`project.repositories`und nicht in einem Block`repositories { }`weil der Empfänger dieses Blocks nicht das`RepositoryHandler`Gradle-Standard aber eine grolifante API, die mit der Erweiterung inkompatibel ist`ivy`Kotlin DSL.

Teil 2 : die Abhängigkeit asciidoctorGems

Das Plugin`org.asciidoctor.jvm.gems`muss zuerst angewendet werden — es erstellt die Konfiguration`asciidoctorGems`und die Aufgabe`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 Die Anwendungsreihenfolge ist wichtig:`gems`vor`revealjs`. <2> Der Qualifikator`@gem`zwingt die korrekte Erweiterung auf die Abhängigkeit.

Teil 3: settings.gradle.kts

`dependencyResolutionManagement`in`settings.gradle.kts`muss den Projekten erlauben, ihre eigenen Repos zu deklarieren:

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

Ohne diese Zeile ignoriert Gradle die im Plugin deklarierten Repositorys und die Auflösung der Gems schlägt fehl.

Introspection der API durch Bytecodes

Warum javap ?

Das Plugin`asciidoctor-gradle-jvm-slides`ist in Version`4.0.0-alpha.1`. Seine Dokumentation existiert nicht oder ist unvollständig. Die einzige zuverlässige Quelle ist die direkte Inspektion der kompilierten Klassen.

Hierarchie der Aufgabe

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

Ergebnis:

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

Entdeckung von `forkOptions

Bei der Überprüfung`AbstractAsciidoctorTask`:

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

Es gibt:

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;

Die API von JavaForkOptions (grolifant)

`javaLauncher`existiert nicht bei dieser Aufgabe. Die reale API von

(Note: The output preserves the leading space, the space after the period, and the trailing space as in the original.)`org.ysb33r.grolifant.api.v4.JavaForkOptions`Ausstellung :

public void executable(java.lang.Object);  (1)
public void setExecutable(java.lang.Object);

Die Methode`executable(Object)`ersetzt die Zuweisung`executable = …​`das nicht kompiliert (`val`kann nicht neu zugewiesen werden).

RevealJSExtension

`revealjs { }`ist keine Methode der Aufgabe, sondern eineProjektverlängerung:

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

Es wird über: zugegriffen

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

Konfiguration des Java-Toolchains

Das Problem von JavaToolchainService

`JavaToolchainService`Ist keine Projekt-Erweiterung. Der folgende Aufruf schlägt fehl:

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

Die gute API ist`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 Docker-Erkennung

Kontext

Das Plugin Asciidoctor/JRuby erfordert Java 17. Kotlin 2.0.x in buildSrc unterstützt Java 25 nicht (der Versionsparser stürzt bei`"25.0.2"`). Der Gradle-Daemon muss daher auf Java 17 laufen oder Docker muss verwendet werden.

Strategie

  1. Docker verfügbar → Ausführung über Container`eclipse-temurin:17`(Standardverhalten)

  2. Docker fehlt + Java 17 → lokale Ausführung

  3. Docker fehlt + Java > 17 → expliziter Fehler

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

Endresultat

Der Konsument-buildscript

apply<slides.SlidesPlugin>()

Das ist alles. Das Plugin trägt die gesamte Verantwortung.

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"

Zusammenfassung der Fallen und Lösungen

Problem Ursache Lösung

`ruby { gems() }`nicht verfügbar in Kotlin

Nur Groovy-DSL-Erweiterung

Drei getrennte Mechanismen: Ivy-Repository + Maven-Ausschluss +asciidoctorGems

Gradle sucht ein`.jar`statt eines`.gem`

`[ext]`löst in`jar`standardmäßig

Hardcoder`.gem`im Muster Ivy + Qualifikator`@gem`

ivy { }`kompiliert nicht in`repositories { }

Receiver grolifant ist nicht mit dem DSL Kotlin kompatibel

Direktaufruf`project.repositories.ivy { }`

`javaLauncher`nicht gelöst

Nicht vorhandene Eigenschaft auf`AsciidoctorJRevealJSTask`

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

`executable = …​`kompiliert nicht

Eigenschaft`val`in`JavaForkOptions`grolifant

Methode`executable(Object)`stattdessen

JavaToolchainService`nicht auffindbar über`extensions

Es ist ein Gradle-Service, keine Erweiterung

project.serviceOf<JavaToolchainService>()

`revealjs { }`nicht gelöst in der Aufgabe

Erweiterung des Projekts, keine Methode der Aufgabe

project.extensions.getByType<RevealJSExtension>()

Build crasht mit Java 25

Kotlin 2.0.x analysiert keine zweistelligen Java-Versionen

Automatische Docker-Erkennung + fallback Java 17

Untersuchungsmethode: Eine unbekannte API mit javap lesen

Prinzip

Wenn die Dokumentation fehlt oder unvollständig ist, sind die Bytecodes die Quelle der Wahrheit. javap`ist das Standard-Werkzeug des JDK, das die Dateien dekompiliert..class`in lesbaren Java-Signaturen, ohne den Quellcode zu benötigen.

Schritt 1: die JAR im Gradle-Cache lokalisieren

Gradle lädt alle seine Abhängigkeiten herunter in`~/.gradle/caches/modules-2/files-2.1/`. Der erste Schritt besteht darin, die JAR-Datei zu finden, die die zu inspizierende Klasse enthält:

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

Schritt 2: Klassen im JAR auflisten

Bevor eine Klasse inspiziert wird, überprüft man, ob sie tatsächlich im JAR enthalten ist :

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

Schritt 3: eine Klasse überprüfen

Das zeigt alle verfügbaren Klassen : AsciidoctorJRevealJSTask, RevealJSExtension, RevealJSOptions, usw.
javap -p -classpath asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar \
org.asciidoctor.gradle.jvm.slides.AsciidoctorJRevealJSTask

Die Option`-p`zeigt alle Mitglieder einschließlich der privaten Das Ergebnis zeigt sofort die Schlüsselzeile:

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

Schritt 4: die Hierarchie hinaufsteigen

Die Aufgabe erweitert`AbstractAsciidoctorTask`. Es wird nacheinander inspiziert. Indem du zunächst sein JAR lokalisierst :

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"

Das ist, wo man es entdeckt.forkOptions, JAVA_EXEC, et javaForkOptions vom Typ`org.ysb33r.grolifant.api.v4.JavaForkOptions`.

Schritt 5: unbekannte Typen verfolgen

`JavaForkOptions`Es ist eine unbekannte grolifant-Klasse. Wir lokalisieren sein JAR:

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

Dann inspiziert man es:

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

Man findet dort`executable(java.lang.Object)— die richtige Methode aufzurufen, im Gegensatz zu`executable = …​`der nicht kompiliert, weil es eine Eigenschaft ist`val.

Schritt 6: Projekt-Erweiterungen überprüfen

Für`revealjs { }`, die Frage war: ist das eine Methode der Aufgabe? oder eine Projektverlängerung? Die Inspektion von`AsciidoctorJRevealJSTask` zeigt keine Methode`revealjs`. Man überprüft dann`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;

Die Anwesenheit von`NAME`bestätigt, dass es eine auf dem Projekt registrierte Erweiterung ist, zugänglich über`project.extensions.getByType<RevealJSExtension>()`.

Zusammenfassung der Methode

Schritt Aktion

1

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

2

jar tf jar.jar | grep NomClasse— prüfen, dass die Klasse existiert

3

javap -p -classpath jar.jar NomCompletClasse— die Klasse inspizieren

4

identifizieren`extends`und die Hierarchie erklimmen

5

Die unbekannten Typen in ihren eigenen JARs verfolgen

6

Suchen`NAME`Um eine Projekterweiterung zu identifizieren

Diese Methode gilt für jedes Gradle-Plugin, dessen API nicht dokumentiert ist oder dessen Alpha-Version nicht mehr zur vorhandenen Dokumentation passt.

Nächster Schritt

Dieses buildSrc-Plugin wird in ein unabhängiges Projekt extrahiert, das auf dem Gradle Plugin Portal oder Maven Local veröffentlicht wird. Der konsumierende Buildscript wird dann:

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

Et settings.gradle.kts`wird auf das notwendigste Minimum reduziert ohne Bezug auf`foojay-resolver-convention, das JDK-Provisioning wird vom Plugin selbst verwaltet oder als Voraussetzung dokumentiert.

Verwandte Artikel