Контекст и цель

Начальная точка

Проект`slider-gradle`генерирует презентации Reveal.js из файлов AsciiDoc с помощью плагина Gradle`org.asciidoctor.jvm.revealjs`. Конфигурация основной задачи`asciidoctorRevealJs`жил непосредственно в корневом 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
    }
}

Цель

Переместить всю эту конфигурацию в`buildSrc/src/main/kotlin/slides/SlidesPlugin.kt`Чтобы потребительский buildscript сократился до :

apply<slides.SlidesPlugin>()

Плагин должен быть полностью автономным: он сам применяет свои зависимости от плагинов, настраивает свои репозитории и управляет своими гемами Ruby.

Первое препятствие: `ruby { gems() }

Что скрывает синтаксический сахар Groovy

Линия`repositories { ruby { gems() } }`является расширением DSL Groovy, доступным только в контексте выполнения buildscript. Она не существует как статический API Kotlin, доступный из buildSrc.

Пытаясь позвонить ему из`SlidesPlugin.kt`, компиляция не удаётся сразу

Разложение механизма

После анализа кэша Gradle (~/.gradle/caches/modules-2/files-2.1/rubygems/), в файле обнаруживается`ivy-3.1.0.xml`Пожалуйста, предоставьте текст для перевода.

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

`ruby { gems() }`осуществлял на самом делетри отдельные операции:

  1. Сохранить репозиторий Ivy, указывающий на`https://rubygems.org/gems/`

  2. Исключить группу`rubygems`депозитории Maven для избежания конфликтов

  3. Сохранить гем в конфигурации`asciidoctorGems`чтобы JRuby загрузил её во время выполнения

Эти три обязанности должны быть воспроизведены отдельно в Kotlin.

Решение в трёх частях

Часть 1: репозиторий Ivy для 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 Расширение`.gem`должна быть жестко закодирована.[ext]`Решает по умолчанию в.jar`, что приводит к ошибке`Resource missing`.

ПРИМЕЧАНИЕ: репозиторий Ivy объявлен непосредственно на`project.repositories`и не в блоке`repositories { }`потому что приемник этого блока не является`RepositoryHandler`стандарт Gradle но grolifant несовместимая API с расширением`ivy`Kotlin DSL.

Часть 2: зависимость 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 Порядок применения важен:`gems`перед`revealjs`. <2> Квалификатор`@gem`принудительно задать правильное расширение для зависимости.

Часть 3 : settings.gradle.kts

`dependencyResolutionManagement`в`settings.gradle.kts`должен разрешить проектам объявлять свои собственные репозитории :

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

Без этой строки Gradle игнорирует репозитории, объявленные в плагине, и разрешение гемов не удаётся.

Интроспекция API посредством байткода

Почему javap ?

Плагин`asciidoctor-gradle-jvm-slides`является в версии`4.0.0-alpha.1`. Её документация отсутствует или неполная. Единственным надежным источником является непосредственный осмотр скомпилированных классов.

Иерархия задачи

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

Результат:

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

Обнаружение `forkOptions

Проверяя`AbstractAsciidoctorTask`:

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

Находим:

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`не существует на этой задаче. Реальная API de`org.ysb33r.grolifant.api.v4.JavaForkOptions`Экспозиция :

public void executable(java.lang.Object);  (1)
public void setExecutable(java.lang.Object);
1 Метод`executable(Object)`заменяет присвоение`executable = …​`который не компилируется (`val`нельзя переназначить).

Расширение RevealJS

`revealjs { }`не является методом задачи, норасширение проекта:

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

Доступ к нему через :

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

Настройка набора инструментов Java

Проблема JavaToolchainService

`JavaToolchainService`не является расширением проекта. Следующий вызов завершится ошибкой :

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

Правильный API — это`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
        )
    }
}

Автоматическое обнаружение Docker

Контекст

Плагин Asciidoctor/JRuby требует Java 17. Kotlin 2.0.x в buildSrc не поддерживает Java 25 (анализатор версии падает на`"25.0.2"`). демон Gradle должен работать на Java 17 или Docker должен использоваться.

Стратегия

  1. Docker доступен → выполнение через контейнер`eclipse-temurin:17`(поведение по умолчанию)

  2. Docker отсутствует + Java 17 → локальное выполнение

  3. Docker отсутствует + Java > 17 → явная ошибка

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

Итоговый результат

Потребительский buildscript

apply<slides.SlidesPlugin>()

Это всё. Плагин несёт полную ответственность.

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"

Сводка ловушек и решений

Проблема Причина Решение

`ruby { gems() }`недоступно в Kotlin

Расширение DSL только для Groovy

Три отдельных механизма: repo Ivy + исключение Maven +asciidoctorGems

Gradle ищет один`.jar`вместо одного`.gem`

`[ext]`решает в`jar`по умолчанию

Hardcoder`.gem`в паттерне Ivy + квалификатор`@gem`

ivy { }`не компилируется в`repositories { }

Ресивер grolifant несовместим с DSL Kotlin

Прямой вызов`project.repositories.ivy { }`

`javaLauncher`не решено

Свойство отсутствует в`AsciidoctorJRevealJSTask`

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

`executable = …​`не компилируется

Свойство`val`в`JavaForkOptions`grolifant

Метод`executable(Object)`вместо этого

JavaToolchainService`не найдено через`extensions

Это сервис Gradle, а не расширение

project.serviceOf<JavaToolchainService>()

`revealjs { }`не решено в задаче

Расширение проекта, а не метод задачи

project.extensions.getByType<RevealJSExtension>()

Сборка падает с Java 25

Kotlin 2.0.x не парсит двухзначные версии Java

Автоматическое обнаружение Docker + fallback на Java 17

Метод исследования : читать неизвестный API с javap

Принцип

Когда документация отсутствует или неполна, байткоды являются источником истины. javap`это стандартный инструмент JDK, который декомпилирует файлы.class`в читаемых подписях Java, без необходимости исходного кода.

Шаг 1: найти jar в кэше Gradle

Gradle загружает все свои зависимости в`~/.gradle/caches/modules-2/files-2.1/`. Первый шаг — найти jar, содержащий класс, который нужно проинспектировать :

find ~/.gradle/caches -name "asciidoctor-gradle-jvm-slides*.jar" 2>/dev/null
Результат :
/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"

Это раскрывает все доступные классы : AsciidoctorJRevealJSTask, RevealJSExtension, RevealJSOptions, и т.д.

Шаг 3: осмотреть класс

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

Опция`-p`Показывает всех членов, включая приватные. Результат сразу показывает ключевую строку:

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

Шаг 4: подняться по иерархии

Задача расширяет`AbstractAsciidoctorTask`. Его проверяют в свою очередь при первой локализации его 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"

Вот где мы обнаруживаем`forkOptions`, JAVA_EXEC, et javaForkOptions типа`org.ysb33r.grolifant.api.v4.JavaForkOptions`.

Шаг 5: отслеживать неизвестные типы

`JavaForkOptions`Это неизвестный класс grolifant. Мы находим его jar :

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

Затем его проверяют:

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

Там есть`executable(java.lang.Object)— правильный метод для вызова, в отличие от`executable = …​`который не компилируется потому что это свойство`val.

Шаг 6 : проверить расширения проекта

Для`revealjs { }`, вопрос был : это метод задачи или расширение проекта ? Инспекция`AsciidoctorJRevealJSTask` не показывает ни одного метода`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`подтверждает, что это зарегистрированное расширение в проекте, доступно через`project.extensions.getByType<RevealJSExtension>()`.

Сводка метода

Этап действие

1

find ~/.gradle/caches -name "*.jar"— найти jar

2

jar tf jar.jar | grep NomClasse— проверить, что класс существует

3

javap -p -classpath jar.jar NomCompletClasse— осмотреть класс

4

Идентификатор`extends`и подняться по иерархии

5

Следить за неизвестными типами в их собственных банках

6

Искать`NAME`pour идентификации расширения проекта

Этот метод применим к любому плагину Gradle, API которого не документирована. или чья альфа-версия больше не соответствует существующей документации.

Следующий шаг

Этот плагин buildSrc будет извлечён в отдельный проект, опубликованный на Gradle Plugin Portal или в Maven Local. Потребительский buildscript будет выглядеть так:

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

Et settings.gradle.kts`будет сокращён до своего строгого минимума без ссылки на`foojay-resolver-convention, подготовка JDK управляется самим плагином или документируется как предварительное условие.

Похожие статьи