بهینهسازی مدیریت وظایف Gradle با Kotlin DSL و buildSrc
منتشر شده در 11 July 2025
اتوماسیون وظایف ساخت برای هر پروژه نرمافزار ضروری است؛ 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 خاص است که قبل از اسکریپتهای ساخت اصلی کامپایل میشود و کلاسهای آن را در کلاسپาธ تمام ساخت شما در دسترس قرار میدهد.
\=== چرا استفاده`buildSrc`برای کارها؟
-
قابلیت باز‑استفاده: یک وظیفه تعریفشده در`buildSrc`میتوان آن را به هر پروژهای از ساخت اعمال کرد
-
سازمان: کد ساخت را مرکزی میسازد و آن را تمیزتر و قابلحفظتر میکند.
-
امنیت نوع و خودکمیل: کد Kotlin در`buildSrc`کامپایل میشود، ارائهٔ بررسی خطا و تکمیل خودکار IDE شما را فراهم میسازد، تجربه توسعه را بهبود میبخشد.
\=== دیاکرام جریان`buildSrc`(کد PlantUML)
برای تولید این نمودار، کد زیر را کپی کرده و در ابزاری که PlantUML را پشتیبانی میکند، جایگذاری کنید.
[source,plantuml]
@startuml skinparam handwritten true skinparam monochrome true
مستطیل "Gradle Build Process" { مكون "buildSrc" به عنوان BS { فایل "MyCustomTask.kt" بهعنوان T فایل "MyConventionPlugin.kt" بهعنوان P } کامپوننت "Root Project" به عنوان RP مكون "Subproject A" به عنوان SA کامپوننت "Subproject B" بهعنوان SB }
T --\> P : "تعریف شده در P --\> RP : "is applied to P --\> SA : "اعمال میشود P --\> SB : "بر … اعمال میشود
RP --|\> SA : "شامل است RP --|\> SB : "شامل
RP -up-\> BS : " وابسته به (für logic constructing SA -up→ BS : "واپسته به (برایLogicconstructed) SB -up-\> BS : "depends on (for build logic)"
نوت سمت راست T کلاسهای کار سفارشی (مثال: OpenTestReportTask) پینوشت
یادداشت در سمت راست P افزونههای کنوانسیون کارها را ثبت میکنند یادداشت پایانی
@enduml
\== ایجاد وظایف گزارش انتزاعی
برای مدیریت گزارشهای تست، ما یک رویکرد ماژولار طراحی کردیم با استفاده از یک کلاس کار انتزاعی در`buildSrc`. بر اساس نیازهای شما، این کلاس میتواند از`DefaultTask` ou de Exec.
\=== وظیفهٔ انتزاعی`OpenTestReportTask`(ارث برده از`DefaultTask`)
این رویکرد توصیه میشود اگر شما نیاز به یک منطق Kotlin سفارشی دارید که با سیستم فایل یا سایر APIهای Gradle تعامل داشته باشد، سپس یک دستور خارجی را اجرا میکند.
[source,kotlin]
پکیج com.yourpackage
import org.gradle.api.DefaultTask import org.gradle.api.tasks.Input وارد کردن org.gradle.api.tasks.TaskAction وارد 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() init { گزارش تست واحد را در Firefox باز میکند. 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 وظایف Raport (کد PlantUML)
کد زیر را کپی کنید و در ابزاری که PlantUML را پشتیبانی میکند، جایگذاری کنید.
## [source,plantuml]
@startuml skinparam نوشته به دست درست skinparam monochrome true
کلاس انتزاعی DefaultTask { }
کلاس انتزاعی Exec { \+ commandLine(args: String...) \+ exec() }
کلاس انتزاعی OpenTestReportTask به ارث میبرد DefaultTask { \+ group: String = "verification \+ شرح: رشته \+ dependsOn("check") \+ چکیده reportPath: رشته \+ openReport() : void }
abstract class AbstractJbakeExecTask extends Exec { \+ group: String = "verification \+ توضیح: رشته \+ dependsOn("check") \+ abstract reportRelativePath: String + exec() : void }
class ReportUnitTestsTask extends OpenTestReportTask { \+ مسیر گزارش: String = "build/reports/tests/test/index.html }
class ReportFunctionalTestsTask extends OpenTestReportTask { \+ reportPath: 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
DefaultTask \<|-- OpenTestReportTask اجرا \<|-- AbstractJbakeExecTask DefaultTask \<|-- Exec
## @enduml
\== وظیفه انتزاعی`AbstractJbakeExecTask`(ارث بر`Exec`)
اگر وظیفه شما عمدتاً اجرای یک دستور خارجی با آرگومانهای متغیر است، بهصورت مستقیم از**اجرا**مستقیمتر است.
## [source,kotlin]
package com.yourpackage
وارد 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`
## [source,kotlin]
package com.yourpackage
abstract class ReportJbakeTestsTask : AbstractJbakeExecTask() { شروع { گزارش تست واحد Jbake را در فایرفاکس باز میکند. مسیر نسبی گزارش = "build/reports/tests/test/index.html } }
package com.yourpackage
## abstract class ReportJbakeFunctionalTestsTask : AbstractJbakeExecTask() { init { description = "گزارش تست عملکردی در فایرفکس باز میشود." reportRelativePath = "build/reports/tests/functionalTest/index.html" } }
نمایش وظایف`buildSrc`
وظائف و کلاسهایی که تعریف میکنید در**buildSrc**هستند :
لطفاً متنی که باید ترجمه شود را ارائه دهید. در حالی که منتظر ورودی هستم، توجه داشته باشم که فقط بخشهای buiten backtickها را ترجمه خواهم کرد و محتوای دقیق درون علامتهای backtick (`...`) را بدون تغییر حفظ خواهم کرد. خروجی صرفاً متن ترجمهشده خواهد بود، بدون هیچ توضیحی یا متنی اضافی.**قابل مشاهده و قابل استفاده**در تمام پروژههای build اصلی شما (ریشه و زیرپروژهها). به همین دلیل میتوانید از آن استفاده کرد`com.yourpackage.ReportUnitTestsTask`در`allprojects { ... }`. ***غیر اجرایی**مستقیم به عنوان وظایف buildSrc (به عنوان مثال,`gradle :buildSrc:reportTests`کار نمیکند اگر وظیفه بهخصوص در ثبت نشود در`buildSrc/build.gradle.kts`)`buildSrc`یک ماژول کامپایل است، نه یک ماژول برنامه اجرایی برای این وظایف ساخت اصلی.
اجرای یک گزارش برای آزمونهای`buildSrc`خودش
Si `buildSrc`او تستهای خود را دارد و گزارشها را تولید میکند، شما میتوانید یک وظیفهٔ گزارش را مستقیماً در`buildSrc/build.gradle.kts`:
## [source,kotlin]
پلاگینها { `kotlin-jvm` }
repositories { mavenCentral() }
tasks.withType\<Test\> { useJUnitPlatform() reports.html.outputLocation.set(layout.buildDirectory.dir("reports/tests")) }
import com.yourpackage.ReportJbakeTestsTask // کلاس وظیفه خود را وارد کنید
## tasks.register\<ReportJbakeTestsTask\>("reportBuildSrcTests") { // مسیر قبلاً در کلاس تعریف شده است و به reports buildSrc اشاره خواهد کرد }
سپس میتوانید اجرا کنید :`./gradlew :buildSrc:test`پیروی از`./gradlew :buildSrc:reportBuildSrcTests`.
با اتخاذ این روشها، شما سیستمهای ساخت Gradle با Kotlin DSL را خواهید ساخت که نه تنها قدرتمند هستند، بلکه بهطور قابلتوجهی ماژولار، قابلنگهداری و آسان برای درک هم هستند.