Bir Asciidoctor RevealJS eklentisinin buildSrc Kotlin'e taşınması: bir Groovy API'sinin tersine mühendisliği
Yayınlandı: 16 April 2026
Bağlam ve amaç
Başlangıç noktası
Proje`slider-gradle`Reveal.js sunumlarını AsciiDoc dosyalarından Gradle eklentisi aracılığıyla oluşturur`org.asciidoctor.jvm.revealjs`. Ana görevin yapılandırması`asciidoctorRevealJs`doğrudan kök buildscript içinde bulunuyordu`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
}
}
Amaç
Tüm bu yapılandırmayı içine taşı`buildSrc/src/main/kotlin/slides/SlidesPlugin.kt`tüketici buildscript’inin şuna indirgenmesi için:
apply<slides.SlidesPlugin>()
Eklenti tamamen özerk olmalıdır: eklenti bağımlılıklarını kendisi uygular, depolarını yapılandırır ve Ruby gem’lerini yönetir.
İlk engel: `ruby { gems() }
Groovy sözdizimsel şekerinin gizlediği şey
Çizgi`repositories { ruby { gems() } }`Yalnızca buildscript yürütme bağlamında kullanılabilen bir Groovy DSL uzantısıdır. buildSrc’den erişilebilen statik bir Kotlin API’si olarak mevcut değildir.
Onu aramaya çalışırken`SlidesPlugin.kt`, derleme hemen başarısız olur.
Mekanizmanın ayrıştırılması
Gradle önbelleği analiz edildikten sonra (~/.gradle/caches/modules-2/files-2.1/rubygems/), dosyada görülür`ivy-3.1.0.xml`:
<artifact type='gem' url='https://rubygems.org/gems/asciidoctor-revealjs-3.1.0.gem' />
`ruby { gems() }`aslında gerçekleştiriyorduüç ayrı işlem :
-
Şuraya işaret eden bir Ivy deposunu kaydet`https://rubygems.org/gems/`
-
Grubu hariç tut`rubygems`çakışmaları önlemek için Maven depoları
-
gem’i yapılandırmaya kaydet`asciidoctorGems`JRuby’nin onu çalışma zamanında yüklemesi için
Bu üç sorumluluk Kotlin’de ayrı ayrı yeniden üretilmelidir.
Üç bölümlü çözüm
Bölüm 1: rubygems için Ivy deposu
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 | Uzantı`.gem`hardcode edilmelidir.[ext]`varsayılan olarak şuna çözümler.jar`, bu da bir hataya neden olur`Resource missing`. |
NOT: Ivy deposu doğrudan üzerinde bildirilir`project.repositories`ve bir blok içinde değil`repositories { }`çünkü bu bloğun alıcısı değil`RepositoryHandler`Gradle standardı ancak uzantıyla uyumsuz bir grolifant API’si`ivy`Kotlin DSL.
Bölüm 2: asciidoctorGems bağımlılığı
Eklenti`org.asciidoctor.jvm.gems`önce uygulanmalıdır — yapılandırmayı oluşturur`asciidoctorGems`ve görev`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 | Uygulama sırası önemlidir:`gems`önce`revealjs`. <2> Niteleyici`@gem`bağımlılık üzerinde doğru uzantıyı zorlar. |
Bölüm 3 : settings.gradle.kts
`dependencyResolutionManagement`içinde`settings.gradle.kts`projelerin kendi depolarını bildirmesine izin vermelidir:
@Suppress("UnstableApiUsage")
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_PROJECT)
}
Bu satır olmadan Gradle, eklentide tanımlanan depoları yok sayar ve gem’lerin çözümlenmesi başarısız olur.
Bytecode’lar aracılığıyla API iç gözlemi
Neden javap?
Eklenti`asciidoctor-gradle-jvm-slides`sürümündedir`4.0.0-alpha.1`. Belgeleri mevcut değil veya eksik. Tek güvenilir kaynak, derlenmiş sınıfların doğrudan incelenmesidir.
Görev hiyerarşisi
javap -p -classpath asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar \
org.asciidoctor.gradle.jvm.slides.AsciidoctorJRevealJSTask
Sonuç:
public class AsciidoctorJRevealJSTask
extends org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask
implements org.asciidoctor.gradle.base.slides.SlidesToExportAware
forkOptions` keşfi
İncelerken`AbstractAsciidoctorTask` :
javap -p -classpath asciidoctor-gradle-jvm-4.0.0-alpha.1.jar \
org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask | grep -i "fork\|exec\|jvm"
Bulunur:
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 (grolifant) API’si
`javaLauncher`bu görevde mevcut değil. Gerçek API’si`org.ysb33r.grolifant.api.v4.JavaForkOptions`açığa çıkarır:
public void executable(java.lang.Object); (1)
public void setExecutable(java.lang.Object);
| 1 | Yöntem`executable(Object)`atamayı değiştir`executable = …`derlenmeyen (`val`yeniden atanamaz). |
RevealJSExtension
`revealjs { }`görevin bir yöntemi değil, ancak birproje uzantısı:
javap -p -classpath asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar \
org.asciidoctor.gradle.jvm.slides.RevealJSExtension
Şu yolla erişilir:
project.extensions.getByType<RevealJSExtension>().apply {
version = "3.1.0"
templateGitHub {
setOrganisation("hakimel")
setRepository("reveal.js")
setTag("3.9.1")
}
}
Java toolchain yapılandırması
JavaToolchainService sorunu
`JavaToolchainService`bir proje uzantısı değildir. Aşağıdaki çağrı başarısız oluyor:
// ERREUR : Extension of type 'JavaToolchainService' does not exist
project.extensions.getByType<JavaToolchainService>()
Doğru API şudur`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 otomatik algılama
Bağlam
Asciidoctor/JRuby eklentisi Java 17 gerektirir. buildSrc içindeki Kotlin 2.0.x, Java 25’i desteklemez (sürüm ayrıştırıcısı şurada çöküyor:`"25.0.2"`). Bu nedenle Gradle daemon’u Java 17 üzerinde çalışmalı ya da Docker kullanılmalıdır.
Strateji
-
Docker mevcut → konteyner aracılığıyla çalıştırma`eclipse-temurin:17`(varsayılan davranış)
-
Docker yok + Java 17 → yerel çalıştırma
-
Docker yok + Java > 17 → açık hata
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."
)
}
Nihai sonuç
Tüketici derleme betiği
apply<slides.SlidesPlugin>()
Hepsi bu. Tüm sorumluluk eklentiye aittir.
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"
Tuzaklar ve çözümler özeti
| Sorun | Neden | Çözüm |
|---|---|---|
`ruby { gems() }`Kotlin’de mevcut değil |
Yalnızca Groovy DSL uzantısı |
Üç ayrı mekanizma: Ivy deposu + Maven hariç tutma + |
Gradle bir`.jar`instead of a`.gem` |
`[ext]`çözülür`jar`varsayılan olarak |
Sabit kodlamak`.gem`Ivy + niteleyici deseninde`@gem` |
|
grolifant alıcısı Kotlin DSL ile uyumsuz |
Doğrudan arama`project.repositories.ivy { }` |
`javaLauncher`çözülmemiş |
Üzerinde bulunmayan özellik`AsciidoctorJRevealJSTask` |
|
`executable = …`derlenmiyor |
Mülkiyet`val`içinde`JavaForkOptions`obur |
Yöntem`executable(Object)`yerine |
|
Bu bir Gradle servisi, uzantı değil. |
|
`revealjs { }`görevde çözülmemiş |
Proje uzantısı, görev yöntemi değil |
|
Build, Java 25 ile çöküyor |
Kotlin 2.0.x, iki basamaklı Java sürümlerini ayrıştırmıyor |
Otomatik Docker algılama + Java 17’ye geri dönüş |
İnceleme yöntemi: javap ile bilinmeyen bir API’yi okumak
İlke
Belge yoksa veya eksikse, bytecode’lar gerçeğin kaynağıdır. javap`JDK’nın dosyaları decompile eden standart aracıdır.class`okunabilir Java imzaları olarak, kaynak kodunu gerektirmeden.
Adım 1: jar dosyasını Gradle önbelleğinde bulun
Gradle tüm bağımlılıklarını içine indirir`~/.gradle/caches/modules-2/files-2.1/`. İlk adım, incelenecek sınıfı içeren jar dosyasını bulmaktır:
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
Adım 2: jar dosyasındaki sınıfları listele
Bir sınıfı incelemeden önce, jar dosyasında gerçekten var olup olmadığını kontrol ederiz:
jar tf asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar | grep -i "RevealJS\|revealjs"
Adım 3: bir sınıfı inceleyin
AsciidoctorJRevealJSTask, RevealJSExtension, RevealJSOptions, vb.javap -p -classpath asciidoctor-gradle-jvm-slides-4.0.0-alpha.1.jar \
org.asciidoctor.gradle.jvm.slides.AsciidoctorJRevealJSTask
Seçenek`-p`özel olanlar da dahil olmak üzere tüm üyeleri görüntüle. Sonuç, kilit satırı hemen gösterir:
public class AsciidoctorJRevealJSTask
extends org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask
Adım 4: hiyerarşide yukarı çık
Görev uzatır`AbstractAsciidoctorTask`. O da sırası geldiğinde denetlenir. önce onun jar dosyasını bularak:
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"
İşte orada keşfediyoruz`forkOptions`, JAVA_EXEC, et javaForkOptions tipinde`org.ysb33r.grolifant.api.v4.JavaForkOptions`.
Adım 5: bilinmeyen türleri izle
`JavaForkOptions`bilinmeyen bir grolifant sınıfıdır. Jar’ının yerini tespit ediyoruz:
find ~/.gradle/caches -name "grolifant*.jar" 2>/dev/null
Sonra onu inceleriz:
javap -p -classpath grolifant40-legacy-api-2.0.0-alpha.6.jar \
org.ysb33r.grolifant.api.v4.JavaForkOptions
Orada bulunur`executable(java.lang.Object)— çağrılacak doğru yöntem, karşıt olarak`executable = …`bir özellik olduğu için derlenmeyen`val.
Adım 6: proje uzantılarını kontrol edin
için`revealjs { }`, soru şuydu: bu görevin bir yöntemi mi yoksa bir proje uzantısı mı? Denetimi`AsciidoctorJRevealJSTask` hiçbir yöntem göstermiyor`revealjs`. Ardından incelenir`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;
varlığı`NAME`bunun projede kayıtlı bir uzantı olduğunu onaylar, aracılığıyla erişilebilir`project.extensions.getByType<RevealJSExtension>()`.
Yöntemin özeti
| Adım | Eylem |
|---|---|
1 |
|
2 |
|
3 |
|
4 |
Tanımlamak`extends`ve hiyerarşide yukarı çıkmak |
5 |
Bilinmeyen türleri kendi jar’larında takip et |
6 |
Aramak`NAME`bir proje uzantısını tanımlamak için |
Bu yöntem, API’si belgelenmemiş herhangi bir Gradle eklentisi için geçerlidir. veya alfa sürümü artık mevcut belgelerle eşleşmeyen.
Sonraki adım
Bu buildSrc eklentisi, Gradle Plugin Portal veya Maven Local üzerinde yayımlanan bağımsız bir projeye çıkarılacak. Tüketici buildscript’i o zaman şöyle olacak:
plugins { id("slides") version "1.0.0" }
Et settings.gradle.kts`en aza indirilecek, referans verilmeksizin`foojay-resolver-convention, JDK sağlaması eklentinin kendisi tarafından yönetildiğinden veya önkoşul olarak belgelendiğinden.