배경 및 목적

시작점

프로젝트`slider-gradle`AsciiDoc 파일로부터 Gradle 플러그인을 통해 Reveal.js 발표를 생성합니다`org.asciidoctor.jvm.revealjs`. 주요 작업 설정`asciidoctorRevealJs`루트 빌드스크립트에 직접 있었다`build.gradle.kts`[No output]

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 { gems() }

그루비 구문 설탕이 숨기는 것

라인`repositories { ruby { gems() } }`이는 빌드스크립트 실행 컨텍스트에서만 사용할 수 있는 Groovy DSL 확장입니다. 이는 buildSrc에서 접근 가능한 정적 Kotlin API가 아닙니다.

그에게 전화를 걸려고 시도하면서`SlidesPlugin.kt`, 컴파일이 즉시 실패합니다.

메커니즘의 분해

그레이들 캐시 분석 후 (~/.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. 구성에 gem을 저장`asciidoctorGems`JRuby가 런타임에 이를 로드하도록

이 세 가지 책임은 Kotlin에서 별도로 재현해야 합니다

세 부분으로 구성된 해결책

1부: rubygems를 위한 Ivy 리포지터리

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은 플러그인에 선언된 저장소를 무시하고, gem 의존성 해결이 실패합니다.

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;

JavaForkOptions의 API (grolifant)

`javaLauncher`이 작업에는 존재하지 않습니다. 실제 API의`org.ysb33r.grolifant.api.v4.JavaForkOptions`노출 :

public void executable(java.lang.Object);  (1)
public void setExecutable(java.lang.Object);
1 방법`executable(Object)`할당을 대체한다`executable = …​`컴파일되지 않는다 (`val`재할당할 수 없습니다).

RevealJSExtension

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

자바 툴체인 구성

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이 필요합니다. buildSrc의 Kotlin 2.0.x는 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에서 사용할 수 없음

Groovy DSL 확장만

세 가지 별도의 메커니즘 : Ivy 저장소 + Maven 제외 +asciidoctorGems

Gradle가 찾는`.jar`대신에`.gem`

`[ext]`해결하는`jar`기본적으로

하드코더`.gem`Ivy 패턴 + 한정자 안에`@gem`

ivy { }`컴파일이 되지 않음`repositories { }

수신자 grolifant는 Kotlin DSL과 호환되지 않습니다.

직접 통화`project.repositories.ivy { }`

`javaLauncher`해결되지 않은

존재하지 않는 속성`AsciidoctorJRevealJSTask`

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

`executable = …​`컴파일되지 않습니다

속성`val`안에`JavaForkOptions`그롤리팡

방법`executable(Object)`대신에

JavaToolchainService`를 통해 찾을 수 없음`extensions

이것은 Gradle 서비스이지, 확장이 아닙니다.

project.serviceOf<JavaToolchainService>()

`revealjs { }`작업에서 해결되지 않음

프로젝트 확장, 작업 방법이 아님

project.extensions.getByType<RevealJSExtension>()

Java 25에서 빌드가 충돌합니다

Kotlin 2.0.x는 두 자리 숫자 버전의 Java를 파싱하지 않습니다.

자동 Docker 감지 + Java 17 폴백

조사 방법: javap을 사용해 알 수 없는 API 읽기

원칙

문서가 없거나 불완전할 때, 바이트코드가 진실의 원천입니다. javap`는 JDK의 표준 도구로 파일을 디컴파일하는 도구입니다..class`읽을 수 있는 Java 서명, 소스 코드가 필요 없이.

1단계: Gradle 캐시에서 JAR 파일 찾기

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"

단계 3: 클래스 검사

이것은 사용 가능한 모든 클래스를 보여줍니다 : AsciidoctorJRevealJSTask, RevealJSExtension, RevealJSOptions, 등
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

자신들의 jars에서 알려지지 않은 유형을 추적하다

6

검색하다`NAME`프로젝트 확장을 식별하기 위해

이 방법은 문서화되지 않은 API를 가진 모든 Gradle 플러그인에 적용됩니다. 또는 알파 버전이 기존 문서와 더 이상 일치하지 않는.

다음 단계

이 buildSrc 플러그인은 Gradle Plugin Portal 또는 Maven Local에 게시된 독립된 프로젝트로 추출됩니다. 소비자 buildscript는 다음과 같이 될 것입니다:

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

Et settings.gradle.kts`최소한으로 축소될 것이며 참조를 갖지 않을`foojay-resolver-convention, 플러그인 자체에서 관리되거나 사전 요구 사항으로 문서화된 JDK 프로비저닝.

관련 기사