السياق والهدف

نقطة البداية

المشروع`slider-gradle`ينشئ عروض Reveal.js من ملفات AsciiDoc عبر المكوّن Gradle`org.asciidoctor.jvm.revealjs`. إعداد المهمة الرئيسية`asciidoctorRevealJs`كان يعيش مباشرةً في برنامج نصي بناء الجذر`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`حتى يصبح سكريبت البناء للمستهلك مختصرًا :

apply<slides.SlidesPlugin>()

يجب أن يكون البرنامج المساعد مستقلاً تمامًا: هو نفسه يطبق اعتماداته من الإضافات، ويكوّن مستودعاته، ويَدير جواهر Ruby.

العقبة الأولى : `ruby { gems() }

ما يخفيه سكر الصياغة Groovy

السطر`repositories { ruby { gems() } }`هو إضافة DSL Groovy متوفرة فقط في سياق تنفيذ buildscript. لا توجد كواجهة برمجة تطبيقات Kotlin ثابتة يمكن الوصول إليها من buildSrc.

عند محاولة استدعائه من`SlidesPlugin.kt`, يفشل التجميع فورًا.

تحليل الآلية

بعد تحليل ذاكرة التخزين المؤقت لـ Gradle ( ~/.gradle/caches/modules-2/files-2.1/rubygems/), Najd…​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 غير متوافقة مع الإضافة`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)
}

ترتيب التطبيق مهم :`gems`قبل`revealjs`. <2> الوصف`@gem`فرض الامتداد الصحيح على التبعية.

الجزء 3 : settings.gradle.kts

`dependencyResolutionManagement`في`settings.gradle.kts`يجب أن يسمح للمشاريع بالإعلان عن مستودعاتها الخاصة :

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

بدون هذا السطر، يتجاهل Gradle المخازن المعلنة في الإضافة ويفشل حل الجواهر.

تفحص واجهة برمجة التطبيقات عن طريق بايت كود

لماذا 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 (grolifant)

`javaLauncher`لا يوجد على هذه المهمة. الواجهة البرمجية الحقيقية لـ`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 { }`ليست طريقة من طرق المهمة بل هيتوسيع المشروعيرجى إدخال `name و email و password في الحقول المخصصة.

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. دُوكر متوفر → التنفيذ عبر الحاوية`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."
    )
}

النتيجة النهائية

نص البناء المستهلك

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 فقط

ثلاث آليات منفصلة: مستودع Ivy + استثناء Maven +asciidoctorGems

Gradle يبحث عن`.jar`بدلاً من`.gem`

`[ext]`يحل في`jar`افتراضيًا

Hardcoder`.gem`في نمط Ivy + المؤهل`@gem`

ivy { }`لا يُترجم في`repositories { }

المستقبل جروليفان غير متوافق مع DSL Kotlin

مكالمة مباشرة`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 تلقائي + الاحتياطي لجافا 17

طريقة التحقيق: قراءة واجهة برمجة تطبيقات غير معروفة باستخدام javap

مبدأ

عندما تكون الوثائق غائبة أو غير مكتملة، يكون الـ bytecode هو مصدر الحقيقة. javap`هو الأداة القياسية في JDK التي تفكك الملفات.class`في توقيعات جافا قابلة للقراءة, بدون الحاجة إلى الكود المصدر.

الخطوة 1: تحديد موقع ملف jar في ذاكرة التخزين المؤقت Gradle

Gradle تحمّل جميع تبعياتها في`~/.gradle/caches/modules-2/files-2.1/`. الخطوة الأولى هي العثور على الـ jar الذي يحتوي على الفئة التي يجب فحصها :

find ~/.gradle/caches -name "asciidoctor-gradle-jvm-slides*.jar" 2>/dev/null
nتيجة :
/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, etc.

الخطوة 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"— تحديد موقع الجرة

2

jar tf jar.jar | grep NomClasse— التحقق من وجود החל

3

javap -p -classpath jar.jar NomCompletClasse— تفحص الصف

4

حدد`extends`والصعود في التسلسل الهرمي

5

تتبع الأنواع غير المعروفة في ملفات JAR الخاصة بهم

6

يبحث`NAME`لتحديد امتداد مشروع

هذه الطريقة تنطبق على أي مكوّن Gradle لا تكون واجهته البرمجية موثّقة أو التي لم يعد إصدار ألفا الخاص بها يتوافق مع الوثائق الحالية.

الخطوة التالية

سيتم استخراج هذا البرنامج الإضافي buildSrc في مشروع مستقل تم نشره على Gradle Plugin Portal أو Maven Local. سيصبح buildscript المستهلك بعد ذلك :

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

Et settings.gradle.kts`سيتم تقليله إلى الحد الأدنى دون إشارة إلى`foojay-resolver-convention, إعداد JDK يتم إدارته بواسطة المكوّن الإضافي نفسه أو يُوثّق كمتطلب مسبق.

مقالات ذات صلة