Contesto e obiettivo

Il punto di partenza

Il progetto`slider-gradle`genera presentazioni Reveal.js da file AsciiDoc tramite il plugin Gradle`org.asciidoctor.jvm.revealjs`. La configurazione dell’attività principale`asciidoctorRevealJs`viveva direttamente nel buildscript radice`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
    }
}

L’obiettivo

Sposta tutta questa configurazione in`buildSrc/src/main/kotlin/slides/SlidesPlugin.kt`affinché lo script di build del consumatore si riduca a :

apply<slides.SlidesPlugin>()

Il plug-in deve essere totalmente autonomo: applica da sé le sue dipendenze di plug-in, configura i suoi repository e gestisce i suoi gem Ruby.

Il primo ostacolo : `ruby { gems() }

Ciò che nasconde lo zucchero sintattico Groovy

La linea`repositories { ruby { gems() } }`è un’estensione DSL Groovy disponibile solo nel contesto di esecuzione del buildscript. Non esiste come API Kotlin statica accessibile da buildSrc.

Tentando di chiamarlo da`SlidesPlugin.kt`, la compilazione fallisce immediatamente.

Scomposizione del meccanismo

Dopo l’analisi della cache Gradle (~/.gradle/caches/modules-2/files-2.1/rubygems/), si scopre nel file`ivy-3.1.0.xml`:

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

`ruby { gems() }`effettuava realmentetre operazioni distinte :

  1. Registrare un repo Ivy che punta a`https://rubygems.org/gems/`

  2. Escludere il gruppo`rubygems`dei repository Maven per evitare i conflitti

  3. Salvare la gem nella configurazione`asciidoctorGems`affinché JRuby lo carichi in fase di esecuzione

Queste tre responsabilità devono essere riprodotte separatamente in Kotlin.

La soluzione in tre parti

Parte 1 : il repo Ivy per 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 L’estensione`.gem`deve essere hardcodata.[ext]`risolve per impostazione predefinita in.jar`, il che provoca un errore`Resource missing`.
Le repo Ivy est déclaré directement sur project.repositories`e non in un blocco`repositories { }`perché il ricevitore di questo blocco non è il`RepositoryHandler`standard di Gradle ma una API grolifant incompatibile con l’estensione.`ivy# Integrazione Gradle JBake può essere integrato nelle build Gradle utilizzando il plugin JBake per Gradle o chiamando direttamente la CLI JBake:
----
tasks.register<JavaExec>("bake") {
    mainClass.set("org.jbake.launcher.Main")
    classpath = configurations["jbake"]
    args = listOf(projectDir.absolutePath, "$buildDir/jbake")
}
----

DSL Kotlin.

=== Parte 2: la dipendenza asciidoctorGems

Il plugin`org.asciidoctor.jvm.gems`deve essere applicato per primo — crea la configurazione`asciidoctorGems`e il compito`asciidoctorGemsPrepare`</think> :

[source,kotlin]
----
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> L'ordine di applicazione è importante :`gems`prima`revealjs`. <2> Il qualificatore`@gem`Forza l'estensione corretta sulla dipendenza.

=== Parte 3 : settings.gradle.kts

`dependencyResolutionManagement`in`settings.gradle.kts`deve consentire ai progetti di dichiarare i propri repo:

[source,kotlin]
----
@Suppress("UnstableApiUsage")
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.PREFER_PROJECT)
}
----

Senza questa linea, Gradle ignora i repo dichiarati nel plugin e la risoluzione delle gemme fallisce.

== Introspezione dell'API tramite bytecode

=== Perché `javap`?

Il plugin`asciidoctor-gradle-jvm-slides`è nella versione`4.0.0-alpha.1`. La sua documentazione è inesistente o incompleta. L'unica fonte affidabile è l'ispezione diretta delle classi compilate.

=== Gerarchia del compito

[source,bash]
----
javap -p -classpath asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar \
  org.asciidoctor.gradle.jvm.slides.AsciidoctorJRevealJSTask
----

Risultato :

[source]
----
public class AsciidoctorJRevealJSTask
  extends org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask
  implements org.asciidoctor.gradle.base.slides.SlidesToExportAware
----

=== Scoperta di `forkOptions

Ispezionando`AbstractAsciidoctorTask`:

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

Si trova :

[source]
----
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;
----

=== L'API di JavaForkOptions (grolifant)

`javaLauncher`non esiste per questa attivita. L'API reale di`org.ysb33r.grolifant.api.v4.JavaForkOptions`esposizione:

[source]
----
public void executable(java.lang.Object);  (1)
public void setExecutable(java.lang.Object);
----

Il metodo`executable(Object)`sostituisce l'assegnazione`executable = ...`che non compila (`val`non può essere riassegnato).

=== RevealJSExtension

`revealjs { }`non è un metodo del compito ma una**estensione del progetto**:

[source,bash]
----
javap -p -classpath asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar \
  org.asciidoctor.gradle.jvm.slides.RevealJSExtension
----

Si accede via:

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

== Configurazione del toolchain Java

=== Il problema di JavaToolchainService

`JavaToolchainService`non è un'estensione di progetto. La chiamata successiva fallisce :

[source,kotlin]
----
// ERREUR : Extension of type 'JavaToolchainService' does not exist
project.extensions.getByType<JavaToolchainService>()
----

L'API giusta è`serviceOf`:

[source,kotlin]
----
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
        )
    }
}
----

== Rilevamento automatico Docker

=== Contesto

Il plugin Asciidoctor/JRuby richiede Java 17. Kotlin 2.0.x in buildSrc non supporta Java 25 (il parser di versione si arresta su`"25.0.2"`Il daemon Gradle deve quindi essere eseguito su Java 17 o Docker deve essere utilizzato.

=== Strategia

1. Docker disponibile → esecuzione tramite container`eclipse-temurin:17`(comportamento predefinito)
2. Docker assente + Java 17 → esecuzione locale
3. Docker assente + Java > 17 → errore esplicito

[source,kotlin]
----
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."
    )
}
----

== Risultato finale

=== Il buildscript consumatore

[source,kotlin]
----
apply<slides.SlidesPlugin>()
----

È tutto. Il plugin si assume l'intera responsabilità.

=== settings.gradle.kts

[source,kotlin]
----
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"
----

== Riepilogo delle trappole e delle soluzioni

[cols="2,3,3"]
|===
|problema |Causa |soluzione

|`ruby { gems() }`### Integrazione Gradle JBake può essere integrato nelle build Gradle utilizzando il plugin JBake Gradle o chiamando direttamente la CLI di JBake:
|Gradle cerca un`.jar`al posto di un`.gem` |`[ext]`ne risolve`jar`per impostazione predefinita |Hardcoder`.gem`nel pattern Ivy + qualificatore`@gem` |`ivy { }`python print(" non compila in", end='')`repositories { }` |Ricevitore grolifant incompatibile con il DSL Kotlin |Chiamata diretta`project.repositories.ivy { }` |`javaLauncher`non risolto |Proprietà inesistente su`AsciidoctorJRevealJSTask` |`setInProcess("JAVA_EXEC")`+`forkOptions { executable(...) }` |`executable = ...`non compila |Proprietà`val`in`JavaForkOptions`grolifant |Metodo`executable(Object)` à la place |`JavaToolchainService`introvabile via`extensions` |È un servizio Gradle, non un'estensione |`project.serviceOf<JavaToolchainService>()` |`revealjs { }`non risolto nel compito |Estensione del progetto, non metodo di attività |`project.extensions.getByType<RevealJSExtension>()` |Build si blocca con Java 25 |Kotlin 2.0.x non analizza le versioni Java a due cifre |Rilevamento Docker automatico + fallback Java 17
|===

== Metodo di indagine: leggere un'API sconosciuta con javap

=== principio

Quando la documentazione è assente o incompleta, i bytecode sono la fonte della verità. `javap`è lo strumento standard del JDK che decompila i file`.class`nelle firme Java leggibili, senza richiedere il codice sorgente.

=== Passo 1: individuare il jar nella cache Gradle

Gradle scarica tutte le sue dipendenze in`~/.gradle/caches/modules-2/files-2.1/`. Il primo passo è trovare il jar che contiene la classe da ispezionare :

[source,bash]
----
find ~/.gradle/caches -name "asciidoctor-gradle-jvm-slides*.jar" 2>/dev/null
----

.Risultato :

[source]
----
/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
----

=== Passo 2: elencare le classi del jar

Prima di ispezionare una classe, si verifica che essa esista effettivamente nel jar:

[source,bash]
----
jar tf asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar | grep -i "RevealJS\|revealjs"
----

.Questo rivela tutte le classi disponibili: `AsciidoctorJRevealJSTask`, `RevealJSExtension`, `RevealJSOptions`, etc.

=== Passo 3: ispezionare una classe

[source,bash]
----
javap -p -classpath asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar \
org.asciidoctor.gradle.jvm.slides.AsciidoctorJRevealJSTask
----

L'opzione`-p`mostra tutti i membri inclusi quelli privati. Il risultato mostra immediatamente la chiave :

[source]
----
public class AsciidoctorJRevealJSTask
extends org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask
----

=== Passo 4: risalire la gerarchia

Il compito si estende`AbstractAsciidoctorTask`Lo si ispeziona a turno localizzando innanzitutto il suo jar :

[source,bash]
----
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"
----

È lì che si scopre`forkOptions`, `JAVA_EXEC`, et `javaForkOptions` di tipo`org.ysb33r.grolifant.api.v4.JavaForkOptions`.

=== Passo 5: seguire i tipi sconosciuti

`JavaForkOptions`è una classe grolifant sconosciuta. Si individua il suo jar :

[source,bash]
----
find ~/.gradle/caches -name "grolifant*.jar" 2>/dev/null
----

Poi lo si ispeziona:

[source,bash]
----
javap -p -classpath grolifant40-legacy-api-2.0.0-alpha.6.jar \
org.ysb33r.grolifant.api.v4.JavaForkOptions
----

Si trova`executable(java.lang.Object)`— il metodo corretto da chiamare, in opposizione a`executable = ...`che non compila perché è una proprietà`val`.

=== Passo 6: controllare le estensioni del progetto

Per`revealjs { }`, la domanda era: è un metodo del compito o un'estensione di progetto? L'ispezione di`AsciidoctorJRevealJSTask` non mostra nessun metodo`revealjs`. Si ispeziona poi`RevealJSExtension`:

[source,bash]
----
javap -p -classpath asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar \
org.asciidoctor.gradle.jvm.slides.RevealJSExtension | head -5
----

[source]
----
public class RevealJSExtension implements groovy.lang.GroovyObject {
public static final java.lang.String NAME;
----

La presenza di`NAME`conferma che è un'estensione registrata sul progetto, accessibile tramite`project.extensions.getByType<RevealJSExtension>()`.

=== Riepilogo del metodo

[cols="1,3"]
|===
|Passo |azione

|1 |`find ~/.gradle/caches -name "*.jar"`— localizzare il jar |2 |`jar tf jar.jar \| grep NomClasse`— verificare che la classe esista |3 |`javap -p -classpath jar.jar NomCompletClasse`— ispezionare la classe |4 |Identificatore`extends`risalire la gerarchia |5 |Seguire i tipi sconosciuti nei loro jar |6 |cercare`NAME`per identificare un'estensione di progetto
|===

Questo metodo si applica a qualsiasi plugin Gradle la cui API non è documentata o di cui la versione alfa non corrisponde più alla documentazione esistente.

== Prossimo passo

Il plugin buildSrc sarà estratto in un progetto indipendente pubblicato su Gradle Plugin Portal o Maven Local. Il buildscript del consumatore diventerà quindi :

[source,kotlin]
----
plugins { id("slides") version "1.0.0" }
----

Et `settings.gradle.kts`sarà ridotto al minimo indispensabile senza riferimento a`foojay-resolver-convention`, il provisioning JDK gestito dal plugin stesso o documentato come prerequisito.

Articoli correlati