Contexto e objetivo

O ponto de partida

O projeto`slider-gradle`gera apresentações Reveal.js a partir de arquivos AsciiDoc via o plugin Gradle`org.asciidoctor.jvm.revealjs`. A configuração da tarefa principal`asciidoctorRevealJs`vivia diretamente no script de construção raiz`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
    }
}

O objetivo

Mover toda essa configuração para`buildSrc/src/main/kotlin/slides/SlidesPlugin.kt`para que o buildscript do consumidor se reduza à:

apply<slides.SlidesPlugin>()

O plugin deve ser totalmente autônomo: ele aplica por si mesmo as dependências de plugins, configura seus repositórios, e gerencia suas gems Ruby.

O primeiro obstáculo: `ruby { gems() }

O que o açúcar sintático do Groovy esconde

A linha`repositories { ruby { gems() } }`é uma extensão DSL Groovy disponível apenas no contexto de execução do buildscript. Ela não existe como uma API Kotlin estática acessível a partir de buildSrc.

Ao tentar chamá-lo desde`SlidesPlugin.kt`, a compilação falha imediatamente.

Decomposição do mecanismo

Após análise do cache Gradle (~/.gradle/caches/modules-2/files-2.1/rubygems/), descobre-se no arquivo`ivy-3.1.0.xml`:

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

`ruby { gems() }`efetuava na realidadetrês operações distintas :

  1. Salvar um repositório Ivy apontando para`https://rubygems.org/gems/`

  2. Excluir o grupo`rubygems`repositórios Maven para evitar conflitos

  3. Salvar a gema na configuração`asciidoctorGems`para que o JRuby a carregue em tempo de execução

Essas três responsabilidades devem ser reproduzidas separadamente em Kotlin.

A solução em três partes

Parte 1: o repositório Ivy para 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 A extensão`.gem`deve estar hardcodada`[ext]resolve por padrão em.jar`, que causa um erro`Resource missing`.
O repo Ivy é declarado diretamente em`project.repositories`e não em um bloco`repositories { }`porque o receiver deste bloco não é o`RepositoryHandler`padrão do Gradle mas uma API grolifant incompatível com a extensão`ivy`Kotlin DSL.

Parte 2 : a dependência asciidoctorGems

O plugin`org.asciidoctor.jvm.gems`deve ser aplicado primeiro — ele cria a configuração`asciidoctorGems`e a tarefa`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 A ordem de aplicação é importante:`gems`antes`revealjs`. <2> O qualificador`@gem`força a extensão correta na dependência.

Parte 3 : settings.gradle.kts

`dependencyResolutionManagement`em`settings.gradle.kts`deve permitir que os projetos declarem seus próprios repositórios:

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

Sem essa linha, o Gradle ignora os repositórios declarados no plugin e a resolução das gems falha.

Introspecção da API por bytecodes

Por que javap?

O plugin`asciidoctor-gradle-jvm-slides`está em versão`4.0.0-alpha.1`. Sua documentação é inexistente ou incompleta. A única fonte confiável é a inspeção direta das classes compiladas.

Hierarquia da tarefa

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

Resultado :

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

Descoberta de `forkOptions

Ao inspecionar`AbstractAsciidoctorTask`:

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

Encontra-se:

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;

A API de JavaForkOptions (grolifant)

`javaLauncher`não existe nesta tarefa. A API real de`org.ysb33r.grolifant.api.v4.JavaForkOptions`exposição :

public void executable(java.lang.Object);  (1)
public void setExecutable(java.lang.Object);
1 O método`executable(Object)`substitui a atribuição`executable = …​`que não compila (`val`não pode ser reatribuído).

Extensão RevealJS

`revealjs { }`não é um método da tarefa mas umaextensão de projeto :

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

É acessada via :

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

Configuração do toolchain Java

O problema de JavaToolchainService

`JavaToolchainService`não é uma extensão de projeto. A chamada seguinte falha :

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

A boa API é`serviceOf`(There is no text to translate, so output is empty.)

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

Detecção automática Docker

Contexto

O plugin Asciidoctor/JRuby requer Java 17. Kotlin 2.0.x em buildSrc não suporta o Java 25 (o analisador de versão falha em`"25.0.2"`). O daemon Gradle deve portanto rodar no Java 17 ou o Docker deve ser usado.

Estratégia

  1. Docker disponível → execução via contêiner`eclipse-temurin:17`(comportamento padrão)

  2. Docker ausente + Java 17 → execução local

  3. Docker ausente + Java > 17 → erro explícito

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

Resultado final

O buildscript consumidor

apply<slides.SlidesPlugin>()

Isso é tudo. O plugin assume toda a responsabilidade.

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"

Resumo das armadilhas e soluções

Problema causa Solução

`ruby { gems() }`não disponível em Kotlin

Extensão DSL Groovy apenas

Três mecanismos separados: repositório Ivy + exclusão Maven +asciidoctorGems

Gradle procura um`.jar`em vez de um`.gem`

`[ext]`resolve em`jar`por padrão

Hardcoder`.gem`no padrão Ivy + qualificador`@gem`

ivy { }`não compila em`repositories { }

Receiver grolifant incompatível com o DSL Kotlin

Chamada direta`project.repositories.ivy { }`

`javaLauncher`não resolvido

Propriedade inexistente em`AsciidoctorJRevealJSTask`

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

`executable = …​`não compila

propriedade`val`dentro`JavaForkOptions`elefante grande

Método`executable(Object)`no lugar de

JavaToolchainService`não encontrado via`extensions

Este é um serviço Gradle, não uma extensão

project.serviceOf<JavaToolchainService>()

`revealjs { }`não resolvido na tarefa

Extensão do projeto, não método de tarefa

project.extensions.getByType<RevealJSExtension>()

Build falha com o Java 25

Kotlin 2.0.x não analisa as versões Java de dois dígitos

Detecção Docker automática + fallback Java 17

Método de investigação : ler uma API desconhecida com javap

Princípio

Quando a documentação está ausente ou incompleta, os bytecodes são a fonte da verdade. javap`é a ferramenta padrão do JDK que descompila os arquivos.class`em assinaturas Java legíveis, sem precisar do código-fonte

Passo 1: localizar o jar no cache do Gradle

Gradle baixa todas as suas dependências em`~/.gradle/caches/modules-2/files-2.1/`. A primeira etapa é encontrar o JAR que contém a classe a ser inspecionada :

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

Etapa 2 : listar as classes do jar

Antes de inspecionar uma classe, verifica-se que ela realmente existe no jar:

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

Isso revela todas as classes disponíveis: AsciidoctorJRevealJSTask, RevealJSExtension, RevealJSOptions, etc.

Etapa 3: inspecionar uma classe

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

A opção`-p`mostra todos os membros, inclusive os privados. O resultado mostra imediatamente a linha chave :

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

Passo 4: subir na hierarquia

A tarefa estende`AbstractAsciidoctorTask`. Inspeciona-se à sua vez localizando primeiro o seu 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"

É aí que se descobre`forkOptions`, JAVA_EXEC, et javaForkOptions de tipo`org.ysb33r.grolifant.api.v4.JavaForkOptions`.

Passo 5: seguir os tipos desconhecidos

`JavaForkOptions`est é uma classe grolifant desconhecida. Localizamos o seu jar :

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

Então, inspeciona-se:

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

Lá se encontra`executable(java.lang.Object)— o método correto a ser chamado, ao contrário de`executable = …​`que não compila porque é uma propriedade`val.

Etapa 6 : verificar as extensões do projeto

para`revealjs { }`, a pergunta era : é um método da tarefa ou uma extensão de projeto? A inspeção de`AsciidoctorJRevealJSTask` não mostra nenhum método`revealjs`. Inspeciona-se então`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;

A presença de`NAME`confirma que é uma extensão registrada no projeto, acessível via`project.extensions.getByType<RevealJSExtension>()`.

Resumo do método

Etapa Ação

1

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

2

jar tf jar.jar | grep NomClasse— verificar que a classe existe

3

javap -p -classpath jar.jar NomCompletClasse— inspecionar a classe

4

Identificar`extends`e subir na hierarquia

5

Seguir os tipos desconhecidos nos seus próprios jars

6

Procurar`NAME`para identificar uma extensão de projeto

Este método aplica-se a qualquer plugin Gradle cuja API não está documentada ou cuja versão alfa não corresponde mais à documentação existente.

próxima etapa

Este plugin buildSrc será extraído para um projeto independente publicado no Gradle Plugin Portal ou no Maven Local. O buildscript do consumidor passará a ser:

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

Et settings.gradle.kts`será reduzido ao seu mínimo estrito sem referência a`foojay-resolver-convention, o provisionamento do JDK sendo gerenciado pelo próprio plugin ou documentado como pré-requisito.

Articles connexes