زمینه و هدف

نقطه Anfangi

پروژه`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>()

پلاگین باید به‌طور کامل خودکفا باشد: خود به خود وابستگی‌های پلاگین خود را اعمال می‌کند، مخازنش را تنظیم می‌کند و gems Ruby خود را مدیریت می‌کند.

اولین مانع : `ruby { gems() }

چه چیزی شکر سینتکس گروویی پنهان دارد

خط`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. ذخیره gem در تنظیمات`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]`حل mi-conduct به towardpishfarad در.jar`, که باعث خطا می‌شود`Resource missing`.
مخزن Ivy مستقیم بر اعلام شده است`project.repositories`و نه در یک بلوک`repositories { }`چون گیرنده این بلوک نیست`RepositoryHandler`معيار Gradle اما یک API grolifant ناسازگار با افزونه`ivy`Kotlin DSL.

بخش ۲: وابستگی asciidoctorGems

پلاگین`org.asciidoctor.jvm.gems`باید به‌عنوان اولین عمل انجام شود — آن پیکربندی را ایجاد می‌کند`asciidoctorGems`و وظیفه`asciidoctorGemsPrepare`(Empty)

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`. الواصف`@gem`افزونه صحیح را روی وابستگی اعمال کنید.

بخش 3 : settings.gradle.kts

`dependencyResolutionManagement`در`settings.gradle.kts`باید به پروژه‌ها اجازه دهد مخازن خود را اعلام کنند :

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

بدون این خط، Gradle مخازن اعلام‌شده در پلاگین را نادیده می‌گیرد و حل gems با شکست مواجه می‌شود.

انتروسپکشن 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 واقعی`org.ysb33r.grolifant.api.v4.JavaForkOptions`توضیح :

public void executable(java.lang.Object);  (1)
public void setExecutable(java.lang.Object);
1 روش`executable(Object)`جایگزین می‌شود`executable = …​`که کامپایل نمی‌شود (`val`نمی‌توانست reassigned شود).

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

پیکربندی زنجیر ابزار 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 را پشتیبانی نمی‌کند (پارسر نسخه crash بر`"25.0.2"`). دیمون Gradle بنابراین باید در Java 17 اجرا شود یا Docker باید به کار گرفته شود.

استراتژی

  1. Docker در دسترس → اجرا از طریق کانتینر`eclipse-temurin:17`(رفتار پیش‌فرض)

  2. داکر غایب + جاوا 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 فقط

سه مکانیزم جداگانه: مخزن 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")(Empty output)forkOptions { executable(…​) }

`executable = …​`کامپایل نمی‌شود

ملک`val`در`JavaForkOptions`فیل بزرگ

روش`executable(Object)`به جای

JavaToolchainService`یافت نشد از طریق`extensions

این یک سرویس Gradle است، نه یک افزونه

project.serviceOf<JavaToolchainService>()

`revealjs { }`حل‌نشده در وظیفه

تمدید پروژه، نه روش کار

project.extensions.getByType<RevealJSExtension>()

Build crashe با Java 25

Kotlin 2.0.x نسخ‌های دو رقمی جاوا را پارس نمی‌کند

تشخیص خودکار داکر + بازگشت به Java 17

روش تحقیق: خواندن یک API نامشخص با javap

مبدأ

زمانی که اسناد غایب یا ناقص هستند، بایت‌کد منبع حقیقت است. javap`ابزار استاندارد JDK است که فایل‌ها را دی‌کامپایل می‌کند.class`در امضاهای Java قابل‌خواندن, بدون نیاز به کد منبع.

مرحله ۱: یافتن جار را در کش Gradle

Gradle تمام وابستگی‌های خود را در`~/.gradle/caches/modules-2/files-2.1/`. مرحله اول این است که جار حاوی کلاس مورد بررسی را پیدا کنید :

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

مرحله ۲: لیست کردن کلاس‌های جار

قبل از بررسی یک کلاس، مطمئن می‌شویم که در فایل JAR موجود است :

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

مرحله ۳: بررسی یک کلاس

این تمام کلاس‌های موجود را نشان می‌دهد : 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

مرحلة ۴: صعود در مراتب

وظیفه گسترش می‌دهد`AbstractAsciidoctorTask`. او نیز به نوبت آن مورد بررسی قرار می‌گیرد در ابتدا به وسیله locating his 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

انواع ناشناخته را در جارهای خود دنبال کنید

6

جستجو`NAME`برای شناسایی یک گسترش پروژه

این روش به تمام پلاگین‌های Gradle‌ای که API آن‌ها مستند نشده است، اعمال می‌شود. یا که نسخه آلفا بیشتر با اسناد موجود مطابقت ندارد.

مرحله بعدی

این پلاگین buildSrc در یک پروژه مستقل منتشر شده در Gradle Plugin Portal یا Maven Local استخراج خواهد شد. سپس buildscript مصرف‌کننده به این صورت خواهد شود:

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

Et settings.gradle.kts`به حداقل ضروری کاهش داده خواهد شد بدون اشاره به`foojay-resolver-convention, تأمين JDK توسط خود پلاگین مدیریت می‌شود یا به عنوان پیش‌نیاز مستند شده است.

مقالات مرتبط