Контекст и циљ

početna tačka

Пројекат`slider-gradle`генерише Reveal.js презентације из AsciiDoc фајлова преко Gradle додатка`org.asciidoctor.jvm.revealjs`. Конфигурација главног задатка`asciidoctorRevealJs`živao je direktno u root buildscriptu`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
    }
}

Циљ

Premesti celu ovu konfiguraciju u`buildSrc/src/main/kotlin/slides/SlidesPlugin.kt`da bi buildscript potrošač se smanji na :

apply<slides.SlidesPlugin>()

Додатак мора да буде потпуно самостал: сам примени swoje plugin-зависности, конфигурише своје репозиторијуме и управи својим Ruby gem-овима.

Prva prepreka : `ruby { gems() }

Šta sakriva Groovin sintaksički šećer

Linija`repositories { ruby { gems() } }`je Groovy DSL ekstenzija dostupna samo u kontekstu izvršavanja buildscript. Ne postoji kao statična Kotlin API dostupna iz buildSrc.

Pokusavajući da ga pozovem od`SlidesPlugin.kt`, kompajliranje ne uspe odmah

Raspadađ mehanizma

После анализе кеша Gradle (~/.gradle/caches/modules-2/files-2.1/rubygems/), откривамо у датотеци`ivy-3.1.0.xml`(empty)

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

`ruby { gems() }`De facto se obavljaloтри одвојене операције:

  1. Sačuvati Ivy repo koji ukazuje na`https://rubygems.org/gems/`

  2. Izuzeti grupu`rubygems`Maven repozitorijumi da se sprečavaju konflikti

  3. Sačuvati gem u konfiguraciji`asciidoctorGems`da JRuby ga učitava tokom izvođenja

Ova tri odgovornosti moraju biti ponovo napravljene posebno u Kotlinu.

Trojedelno rešenje

Deo 1: repozitorijum Ivy za 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 Ekstenzija`.gem`Треба да буде хардкодована.[ext]`решава по умолчанию у.jar`, što uzrokuje grešku`Resource missing`.

OBAVEŠTENJE: Repo Ivy je deklarisano direktno na`project.repositories`i ne u bloku`repositories { }`jer пријемник овог блока није`RepositoryHandler`standard Gradle ali API grolifant nekompatibilan sa ekstenzijom`ivy`Kotlin DSL.

Deo 2: zavisnost asciidoctorGems

Додатак`org.asciidoctor.jvm.gems`треба да се примени прво — то прави конфигурацију`asciidoctorGems`и задатак`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 Redosled primene je važan:`gems`пре`revealjs`. <2> Kvalifikator`@gem`prinuši ispravno proširenje na zavisnost.

Део 3 : settings.gradle.kts

`dependencyResolutionManagement`у`settings.gradle.kts`mora dozvoliti projektima da prijave sopstvena repozitorijuma:

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

Bez ove linije, Gradle ignoriše repozitorijume deklarisane u plugin-u i rešavanje gemova ne uspe.

Introspekcija API-ja kroz bajtkodove

Zašto javap?

Плагин`asciidoctor-gradle-jvm-slides`je u verziji`4.0.0-alpha.1`. Njegova dokumentacija ne postoji ili je nepotpuna. Jedini pouzdani izvor je direktna inspekcija kompajliranih klasa.

Hierarhija zadatka

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

Rezultat :

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

Otkrivanje `forkOptions

Inspektujući`AbstractAsciidoctorTask` :

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

Pronađemo:

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`ne postoji na ovom zadatku. Stvarni API od`org.ysb33r.grolifant.api.v4.JavaForkOptions`изложи :

public void executable(java.lang.Object);  (1)
public void setExecutable(java.lang.Object);
1 Metoda`executable(Object)`Zamjenjuje zadatak`executable = …​`koje ne kompajlira (`val`не може да буде додело).

RevealJSExtension

`revealjs { }`nije jedna metoda zadatka ali jednaпроширење пројекта[space]

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

Pristupa se putem:

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

Konfiguracija Java toolchain

Problem JavaToolchainService

`JavaToolchainService`Није проширење пројекта. Следећи позив није успео :

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

Dobra API je`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
        )
    }
}

Automatska detekcija Docker

Kontekst

Asciidoctor/JRuby plugin zahteva Java 17. Kotlin 2.0.x u buildSrc ne podržava Java 25 (parser verzije se svrće na`"25.0.2"`). Gradle daemon mora da radi na Java 17 ili Docker mora da se koristi.

Стратегија

  1. Docker dostupan → izvršenje putem kontejnera`eclipse-temurin:17`(podrazumevano ponašanje)

  2. Docker nedostupan + Java 17 → lokalno izvršenje

  3. Docker nedostupan + Java > 17 → eksplicitna greška

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

Konačni rezultat

potrošački buildscript

apply<slides.SlidesPlugin>()

To je sve. Plugin preuzima celokupnu odgovornost.

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"

Свод о пажња и решения

Problem Причина Решение

`ruby { gems() }`nije dostupan u Kotlinu

Samo ekstenzija Groovog DSL

Tri odvojenih mehanizma: repo Ivy + isključivanje Maven +asciidoctorGems

Gradle traži jedan`.jar`умсте једног`.gem`

`[ext]`решава у`jar`podrazumevano

Hardcoder`.gem`у узору Ivy + kvalifikator`@gem`

ivy { }`ne kompajlira u`repositories { }

Пријемник grolifant није совместим са Kotlin DSL

Позови директно`project.repositories.ivy { }`

`javaLauncher`нерешено

Nepostojeca svojstvo na`AsciidoctorJRevealJSTask`

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

`executable = …​`ne kompajlira

svojstvo`val`у`JavaForkOptions` grolifant

Metoda`executable(Object)`na mestu

JavaToolchainService`непронађен</tool_call>`extensions

Ovo je Gradle usluga, a ne proširenje.

project.serviceOf<JavaToolchainService>()

`revealjs { }`nerešeno u zadatku

Proširenje projekta, ne metoda zadatka

project.extensions.getByType<RevealJSExtension>()

Grad se ruši sa Javom 25

Kotlin 2.0.x ne parsira dvocifrne verzije Jave

Automatsko otkrivanje Docker + fallback Java 17

Metod istraživanja : čitanje nepoznate API koristeći javap

Принцип

Kada dokumentacija nedostaje ili je nekompletna, bajtkodovi su izvor istine. javap`je standardni alat JDK koji dekompajlira datoteke.class`u čitljivim Java potpisima, bez potrebe od izvornog koda

Korak 1: pronaći JAR u Gradle kešu

Gradle preuzima sve svoje zavisnosti u`~/.gradle/caches/modules-2/files-2.1/`. Први корак је да нађете jar који садржи класу коју треба испитити :

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

Корак 2: исписати класе из jar-а

Пре него што проверавамо класу, проверавамо да ли постоји у jar :

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

Корак 3: проверити класу

Ovo otkriva sve dostupne klase : AsciidoctorJRevealJSTask, RevealJSExtension, RevealJSOptions, itd.
javap -p -classpath asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar \
org.asciidoctor.gradle.jvm.slides.AsciidoctorJRevealJSTask

Opcija`-p`Prikazuje sve članove, uključujući i privatne. Rezultat odmah prikazuje ključnu liniju:

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

Korak 4: povratak na hijerarhiju

Задатак простира`AbstractAsciidoctorTask`. Проверава се наред Prvo lokalizujući njegov/je jar :

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"

Ovo je gde otkrivamo`forkOptions`, JAVA_EXEC, et javaForkOptions tip`org.ysb33r.grolifant.api.v4.JavaForkOptions`.

Етап 5: Следите непознате типове

`JavaForkOptions`Непозната класа гролифант. Локализујемо њену jar :

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

Zatim ga proveravamo:

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

Nalazi se tu`executable(java.lang.Object)— правилна метода за позивање, u suprotnosti sa`executable = …​`što ne kompajlira jer je svojstvo`val.

Корак 6 : проверити проширења пројекта

Za`revealjs { }`, pitanje je bilo : da li je to metoda zadatka ili proširjenje projekta? pregled`AsciidoctorJRevealJSTask` ne prikazuje nikakvu metodu`revealjs`. Затим се проверава`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;

Присутност`NAME`potvrđuje da je to registrovana ekstenzija na projektu, доступан преко`project.extensions.getByType<RevealJSExtension>()`.

Резюме метода

Korak akcija

1

find ~/.gradle/caches -name "*.jar"— pronaći JAR

2

jar tf jar.jar | grep NomClasse— проверити да класа постоји

3

javap -p -classpath jar.jar NomCompletClasse— прегледати класу

4

Identifikovati`extends`i podignuti se po hijerarhiji

5

Праћење непохвативих типова у њиховим сопственим JAR-овима

6

тражити`NAME`da identificiraš proširenje projekta

Ova metoda se primenjuje na bilo koji Gradle plugin čiji API nije dokumentiran. čiji alfa verzija više ne podudara sa postojećom dokumentacijom.

Sledeći korak

Ovaj buildSrc plugin će biti izdvojen u nevisan projekat objavljen na Gradle Plugin Portal ili Maven Lokalno. Potrošački buildscript će tada biti :

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

Et settings.gradle.kts`će se smanjiti na strogo minimum bez reference na`foojay-resolver-convention, provisioning JDK je rukovan od strane samog plugina ili dokumentisan kao preduvjet.

Повезани чланци