أتمتة مهام البناء أمر حاسم لأي مشروع برمجي، و Gradle، مع DSL Kotlin الخاص به، يوفر مرونة استثنائية. خلال محادثتنا، استكشفنا كيفية تركيز وإعادة استخدام منطق البناء، خاصة لتقارير الاختبارات، بالاستفادة منجميع المشاريع, buildSrc, ومهام مخصصة في Kotlin.

مركزية التكوينات باستخدام allprojects و `subprojects

الكتل`allprojects` et `subprojects`فيك`build.gradle.kts`الجذر أساسي لتطبيق التكوينات المشتركة عبر مشروعك متعدد الوحدات.

*allprojects { …​ }`تطبيق التكوين علىمشروع الجذر وكافة مشاريعه الفرعية. مثالي لتحديد`group, واحده`version`, أو بعض`repositories`مشتاركة. *subprojects { …​ }: طبق التكوينفقط إلى المشاريع الفرعية, باستثناء المشروع الجذري. مثالي لتطبيق الإضافات الخاصة بالوحدات (مثل`java` ou kotlin-jvm) أو اعتماديات مشتركة لمكتباتك.

إليك مثال توضيحي :

// build.gradle.kts (projet racine)
plugins {
    base // Appliqué au projet racine
}

allprojects {
    group = "com.example"
    version = "1.0.0"

    repositories {
        mavenCentral()
    }

    tasks.withType<org.gradle.api.tasks.testing.Test> {
        useJUnitPlatform() // Configuration commune des tests pour tous les projets
    }
}

subprojects {
    apply(plugin = "java")
    apply(plugin = "org.jetbrains.kotlin.jvm")

    dependencies {
        implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    }

}

\==buildSrc: سكين الجيش السويسري لمنطق البناء

عندما يصبح منطق مهامك معقدًا أو يجب إعادة استخدامه,buildSrcهو الحل المفضل. إنه وحدة Gradle خاصة يتم تجميعها قبل سكريبتات البناء الرئيسية، مما يجعل فئاتها متاحة على مسار الفئة (classpath) لكل البناء.

\=== لماذا تستخدم`buildSrc`للمهام؟

  • قابلية إعادة الاستخدام: مهمة محددة في`buildSrc`peut تطبيقها على أي مشروع من البناء.

  • تنظيم: يركّز كود البناء، مما يجعله أنظف وأكثر قابلية للصيانة.

  • سلامة النوع والإكمال التلقائي: كود Kotlin في`buildSrc`يتم تجميعه، مما يوفر فحص الأخطاء وإكمال التلقائي في بيئة التطوير المتكاملة (IDE) الخاصة بك، مما يحسن تجربة التطوير.

\=== مخطط التدفق`buildSrc`(كود PlantUML)

ولإنشاء هذا المخطط، انسخ الكود أدناه ثم الصقه في أداة تدعم PlantUML.

[source,plantuml]

@startuml skinparam handwritten true skinparam monochrome true

مستطيل "Gradle Build Process" { component "buildSrc" as BS { ملف "MyCustomTask.kt" كـ T ملف "MyConventionPlugin.kt" كـ P } مكون "Root Project" كـ RP المكوّن "Subproject A" كـ SA المكون "المشروع الفرعي B" كـ SB }

T --\> P : "معرف في P --\> RP : "يُطبق على P --\> SA : "يتم تطبيقه على P --\> SB : "يتم تطبيقه على

RP --|\> SA : "contains RP --|\> SB : "يحتوي

RP -up-\> BS : "يعتمد على (لمنطق البناء) SA -up-\> BS : "يعتمد على (لمنطق البناء) SB -up→ BS : "يعتمد على (منطق البناء)

ملاحظة إلى يمين T فئات مهام مخصصة (مثال: OpenTestReportTask) ملاحظة نهائية

ملاحظة إلى يمين P إضافات الاتفاقية الذين يسجلون المهام ملاحظة النهاية

@enduml

\== إنشاء مهام تقارير مجردة

لإدارة تقارير الاختبار، صممنا نهجًا معياريًا باستخدام فئة مهمة مجردة في`buildSrc`. وفقًا لاحتياجاتك، يمكن لهذه الفئة أن ترث من`DefaultTask` ou de Exec.

\=== مهمة مجردة`OpenTestReportTask`(وارث من`DefaultTask`)

هذا النهج موصى به إذا كنت بحاجة إلى منطق Kotlin مخصص يتفاعل مع نظام الملفات أو مع APIs أخرى لـ Gradle، ثم يطلق أمرًا خارجيًا.

[source,kotlin]

package com.yourpackage

استورد org.gradle.api.DefaultTask import org.gradle.api.tasks.Input import org.gradle.api.tasks.TaskAction import java.io.File

فئة مجردة OpenTestReportTask : DefaultTask() {

----
----
----
init {
    group = "verification"
    description = "Opens a test report in Firefox."
    dependsOn("check") // Assure que les rapports sont générés
}

@get:Input
abstract var reportPath: String

@TaskAction
fun openReport() {
    val separator = File.separator
    val reportFile = project.layout.projectDirectory.asFile.toPath()
        .resolve(reportPath.replace("/", separator))
        .toAbsolutePath()
        .toFile()

    if (!reportFile.exists()) {
        logger.warn("Report file does not exist: $reportFile. Ensure 'check' ran.")
        return
    }

    project.exec {
        commandLine("firefox", "--new-tab", reportFile.absolutePath)
    }
    logger.lifecycle("Opened test report: ${reportFile.absolutePath}")
}
----

## }

التنفيذات الملموسة

ترث هذه الفئات من المهمة المجردة وتعرف المسار المحدد للتقرير.

## [source,kotlin]

package com.yourpackage

فئة مجردة ReportUnitTestsTask : OpenTestReportTask() تهيئة { يفتح تقرير اختبار الوحدة في فايرفوكس. reportPath = "build/reports/tests/test/index.html } }

حزمة com.yourpackage

## abstract class ReportFunctionalTestsTask : OpenTestReportTask() { init { description = "يفتح تقرير الاختبار الوظيفي في فايرفوكس." reportPath = "build/reports/tests/functionalTest/index.html" } }

تسجيل المهام

في`build.gradle.kts`من مشروعك الجذر:

## [source,kotlin]

tasks.register\<com.yourpackage.ReportUnitTestsTask\>("reportTests") {} tasks.register\<com.yourpackage.ReportFunctionalTestsTask\>("reportFunctionalTests") {}

\=== مخطط UML لمهام التقرير (كود PlantUML)

انسخ الكود أدناه والصقه في أداة تدعم PlantUML.

## [source,plantuml]

@startuml skinparam مكتوب بخط اليد true skinparam monochrome true

فئة مجردة DefaultTask { }

فئة مجردة Exec { \+ commandLine(args: String...) + exec() }

فئة مجردة OpenTestReportTask تمتد من DefaultTask { \+ group: String = "التحقق \+ description: String \+ dependsOn("check") \+ abstract مسار التقرير: سلسلة \+ openReport() : void }

فئة مجردة AbstractJbakeExecTask تمدّ Exec { \+ group: String = "verification \+ وصف: سلسلة \+ dependsOn("check") \+ abstract reportRelativePath: String + exec() : void }

class ReportUnitTestsTask extends OpenTestReportTask { \+ reportPath: String = "build/reports/tests/test/index.html }

class ReportFunctionalTestsTask extends OpenTestReportTask + مسار التقرير: String = "build/reports/tests/functionalTest/index.html }

class ReportJbakeTestsTask extends AbstractJbakeExecTask { \+ reportRelativePath: String = "build/reports/tests/test/index.html }

فئة ReportJbakeFunctionalTestsTask تمتد AbstractJbakeExecTask { \+ reportRelativePath: String = "build/reports/tests/functionalTest/index.html }

OpenTestReportTask <-- ReportUnitTestsTask OpenTestReportTask \<-- ReportFunctionalTestsTask

AbstractJbakeExecTask \<-- ReportJbakeTestsTask AbstractJbakeExecTask \<-- ReportJbakeFunctionalTestsTask

المهمة الافتراضية \<|-- مهمة فتح تقرير الاختبار Exec \<|-- AbstractJbakeExecTask المهمة الافتراضية \<|-- Exec

## @enduml

\== مهمة مجردة`AbstractJbakeExecTask`(وارث من`Exec`)

إذا كانت مهمتك تتمحور بشكل أساسي حول تنفيذ أمر خارجي مع متغيّرات، ارث مباشرة من**تنفيذ**هو أكثر مباشرة.

## [source,kotlin]

حزمة com.yourpackage

import org.gradle.api.tasks.Exec import org.gradle.api.tasks.Input import java.io.File

فئة مجردة AbstractJbakeExecTask : Exec() {

----

init { group = "verification" description = "Opens a Jbake project report in Firefox." dependsOn("check") }

@get:Input abstract var reportRelativePath: String

override fun exec() { val separator = File.separator val reportFile = project.layout.projectDirectory.asFile.toPath() .resolve(reportRelativePath.replace("/", separator)) .toAbsolutePath() .toFile()

if (!reportFile.exists()) { logger.warn("Report file does not exist: $reportFile. Ensure 'check' ran.") return }

commandLine("firefox", "--new-tab", reportFile.absolutePath) logger.lifecycle("Attempting to open report: ${reportFile.absolutePath}") super.exec() // Appelle la méthode exec() de la super-classe Exec }

## }

\=== تنفيذات ملموسة لـ`Exec`

## [source,kotlin]

حزمة com.yourpackage

abstract class ReportJbakeTestsTask : AbstractJbakeExecTask() { تهيئة { description = "يفتح تقرير اختبار الوحدة Jbake في فايرفوكس. reportRelativePath = "build/reports/tests/test/index.html } }

حزمة com.yourpackage

## abstract class ReportJbakeFunctionalTestsTask : AbstractJbakeExecTask() { init { description = "Opens the functional test report in Firefox." reportRelativePath = "build/reports/tests/functionalTest/index.html" } }

\== رؤية المهام`buildSrc`

المهام والفئات التي تحددها في**buildSrc**:

***مرئية وقابلة للاستخدام**من خلال جميع مشاريع البناء الرئيسي (الجذر والمشاريع الفرعية). لهذا السبب يمكنك استخدام`com.yourpackage.ReportUnitTestsTask`في`allprojects { ... }`. ***غير قابل للتنفيذ**بشكل مباشر كمهمات buildSrc (على سبيل المثال,`gradle :buildSrc:reportTests`لن يعمل إذا لم يتم تسجيل المهمة بشكل محدد في`buildSrc/build.gradle.kts`).`buildSrc`هو وحدة ترجمة، ليست وحدة تطبيق قابل للتنفيذ لهذه المهام من عملية البناء الأساسية.

\=== تشغيل تقرير للاختبارات من`buildSrc`نفسه

Si `buildSrc`له اختباراته الخاصة ويولد تقارير، يمكنك تسجيل مهمة تقرير مباشرة في`buildSrc/build.gradle.kts` :

## [source,kotlin]

الإضافات { `kotlin-jvm` }

المستودعات { mavenCentral() }

tasks.withType\<Test\> { useJUnitPlatform() reports.html.outputLocation.set(layout.buildDirectory.dir("reports/tests")) }

import com.yourpackage.ReportJbakeTestsTask // استورد فئة المهمة

tasks.register\<ReportJbakeTestsTask\>("reportBuildSrcTests") { // المسار مُعرّف مسبقًا في الفئة، سيشير إلى تقارير buildSrc }

ستتمكنون بعد ذلك من تنفيذ :`./gradlew :buildSrc:test`مُتبَع بـ`./gradlew :buildSrc:reportBuildSrcTests`.

من خلال اعتماد هذه الممارسات، ستقوم بإنشاء أنظمة بناء Gradle باستخدام Kotlin DSL التي ليست قوية فحسب، بل هي أيضًا وحدات بشكل لا يصدق، قابلة للصيانة وسهلة الفهم.

AbstractJbakeExecTask \<-- ReportJbakeTestsTask AbstractJbakeExecTask \<-- ReportJbakeFunctionalTestsTask

DefaultTask \<|-- OpenTestReportTask Exec \<|-- AbstractJbakeExecTask DefaultTask \<|-- Exec

## @enduml

\== مهمة مجردة`AbstractJbakeExecTask`(يرث من`Exec`)

إذا كانت مهمتك تتلخص بشكل أساسي في تنفيذ أمر خارجي مع وسائط متغيرة، فقم بالوراثة مباشرة من**تنفيذ**أكثر مباشرة.

## [المصدر,kotlin]

package com.yourpackage

import org.gradle.api.tasks.Exec import org.gradle.api.tasks.Input import java.io.File

abstract class AbstractJbakeExecTask : Exec() {

----

init { group = "verification" description = "Opens a Jbake project report in Firefox." dependsOn("check") }

@get:Input abstract var reportRelativePath: String

override fun exec() { val separator = File.separator val reportFile = project.layout.projectDirectory.asFile.toPath() .resolve(reportRelativePath.replace("/", separator)) .toAbsolutePath() .toFile()

if (!reportFile.exists()) { logger.warn("Report file does not exist: $reportFile. Ensure 'check' ran.") return }

commandLine("firefox", "--new-tab", reportFile.absolutePath) logger.lifecycle("Attempting to open report: ${reportFile.absolutePath}") super.exec() // Appelle la méthode exec() de la super-classe Exec }

## }

\=== تنفيذات ملموسة لـ`Exec`

## [المصدر,kotlin]

package com.yourpackage

abstract class ReportJbakeTestsTask : AbstractJbakeExecTask() { init { description = "يفتح تقرير اختبارات الوحدة لـ Jbake في متصفح Firefox. reportRelativePath = "build/reports/tests/test/index.html } }

package com.yourpackage

## abstract class ReportJbakeFunctionalTestsTask : AbstractJbakeExecTask() { init { description = "يفتح تقرير الاختبارات الوظيفية في متصفح Firefox." reportRelativePath = "build/reports/tests/functionalTest/index.html" } }

\== رؤية المهام`buildSrc`

المهام والفئات التي تحددها في**buildSrc**هي:

يرجى إدخال النص المراد ترجمته.**مرئية وقابلة للاستخدام**من قبل جميع مشاريع بناءك الرئيسي (الجذر والمشاريع الفرعية). ولهذا السبب يمكنك استخدام`com.yourpackage.ReportUnitTestsTask`في`allprojects { ... }`يرجى تقديم النص الذي ترغب في ترجمته. يرجى إدخال النص الذي ترغب في ترجمته.**غير قابلة للتنفيذ**مباشرةً كمهام `buildSrc` (على سبيل المثال،`gradle :buildSrc:reportTests`لن يعمل إذا لم يتم تسجيل المهمة تحديداً في`buildSrc/build.gradle.kts`).`buildSrc`هو وحدة تجميع (compilation module)، وليس وحدة تطبيق قابلة للتنفيذ لمهام البناء الرئيسية هذه.

تشغيل تقرير لاختبارات`buildSrc`نفسه

Si `buildSrc`إلى اختباراته الخاصة ويقوم بإنشاء تقارير، يمكنك حفظ مهمة تقرير مباشرة في`buildSrc/build.gradle.kts`يرجى تقديم النص الذي ترغب في ترجمته.

## [المصدر,kotlin]

plugins { `kotlin-jvm` }

المستودعات { mavenCentral() }

tasks.withType\<Test\> { useJUnitPlatform() reports.html.outputLocation.set(layout.buildDirectory.dir("reports/tests")) }

import com.yourpackage.ReportJbakeTestsTask // استورد فئة المهمة الخاصة بك

## tasks.register\<ReportJbakeTestsTask\>("reportBuildSrcTests") { // المسار محدد بالفعل في الفئة، وسيشير إلى تقارير buildSrc }

يمكنك بعد ذلك تنفيذ:`./gradlew :buildSrc:test`متبوعاً بـ`./gradlew :buildSrc:reportBuildSrcTests`يرجى تزويدي بالنص الذي ترغب في ترجمته.

من خلال اعتماد هذه الممارسات، ستتمكن من بناء أنظمة Gradle build باستخدام Kotlin DSL ليست قوية فحسب، بل وأيضاً معيارية بشكل مذهل، وقابلة للصيانة وسهلة الفهم.

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