ปรับปรุงการจัดการงานของ 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 พิเศษที่ถูกคอมไพล์ก่อนสคริปต์บิลด์หลัก ทำให้คลาสของมันพร้อมใช้งานใน classpath ของทั้งบิลด์ของคุณ
ทำไมต้องใช้`buildSrc`สำหรับงาน ?
-
การนำกลับมาใช้ใหม่: งานที่กำหนดใน`buildSrc`สามารถนำไปใช้กับทุกโครงการในขั้นตอนการสร้างได้.
-
องค์กร: รวมโค้ดการสร้างให้เป็นที่เดียว ทำให้สะอาดและบำรุงรักษาได้ง่าย.
-
ความปลอดภัยของชนิดและการเติมอัตโนมัติ: โค้ด Kotlin ใน`buildSrc`ถูกคอมไพล์แล้ว, ซึ่งให้การตรวจสอบข้อผิดพลาดและการเติมโค้ดอัตโนมัติจาก IDE ของคุณ, ปรับปรุงประสบการณ์ในการพัฒนา.
\=== แผนภาพการไหล`buildSrc`(โค้ด PlantUML)
เพื่อสร้างแผนภาพนี้ ให้คัดลอกโค้ดด้านล่างและวางลงในเครื่องมือที่รองรับ PlantUML.
@startuml skinparam handwritten true skinparam monochrome true
rectangle "Gradle Build Process คอมโพเนนต์ "buildSrc" เป็น BS { ไฟล์ "MyCustomTask.kt" เป็น T ไฟล์ "MyConventionPlugin.kt" เป็น P } component "Root Project" as RP องค์ประกอบ "Subproject A" มีชื่อย่อว่า SA องค์ประกอบ "Subproject B" เป็น SB }
T -→ P : "กำหนดใน P --\> RP : "ถูกนำไปใช้กับ P -→ SA : "ถูกนำมาใช้กับ P -→ SB : "ถูกนำไปใช้กับ
RP --|\> SA : "ประกอบด้วย RP --|\> SB : "มี
RP -up→ BS: "ขึ้นอยู่กับ (สำหรับตรรกะการบิลด์) SA -up-\> BS : "depends on (for build logic) SB -up-\> BS : "ขึ้นอยู่กับ (สำหรับตรรกะการสร้าง)
โน้ต ขวา ของ T คลาสงานที่กำหนดเอง (no content to translate) หมายเหตุส่วนท้าย
หมายเหตุอยู่ด้านขวาของ P ปลั๊กอินของการประชุม ที่บันทึกงาน end note
@enduml
\==การสร้างงานรายงานนามธรรม
เพื่อจัดการรายงานทดสอบ เราได้ออกแบบวิธีการที่เป็นโมดูลโดยใช้คลาสงานเชิงนามธรรมใน`buildSrc`. ตามความต้องการของคุณ คลาสนี้อาจสืบทอดจาก`DefaultTask` ou de Exec.
\=== งานนามธรรม`OpenTestReportTask`(รับมาจาก`DefaultTask`)
วิธีนี้แนะนำให้ใช้เมื่อคุณต้องการตรรกะ Kotlin ที่ปรับแต่งเองซึ่งทำงานร่วมกับระบบไฟล์หรือ API อื่น ๆ ของ Gradle แล้วจึงเรียกคำสั่งภายนอก
[source,kotlin]
แพ็คเกจ com.yourpackage
นำเข้า org.gradle.api.DefaultTask import org.gradle.api.tasks.Input นำเข้า 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]
แพ็คเกจ com.yourpackage
abstract class ReportUnitTestsTask : OpenTestReportTask() init { description = "เปิดรายงานการทดสอบหน่วยใน Firefox. reportPath = "build/reports/tests/test/index.html } }
package com.yourpackage
abstract class ReportFunctionalTestsTask : OpenTestReportTask() {
init {
description = "เปิดรายงานการทดสอบฟังก์ชั่นใน Firefox."
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 handwritten true skinparam monochrome true
abstract class DefaultTask { }
คลาสนามธรรม Exec {} \+ บรรทัดคำสั่ง(args: String...) บวก exec() }
คลาสนามธรรม OpenTestReportTask ขยาย DefaultTask { \+ group: String = "การตรวจสอบ + คำอธิบาย: สตริง \+ dependsOn("check") \+ นามธรรม reportPath: สตริง \+ openReport() : void }
คลาสนามธรรม AbstractJbakeExecTask สืบทอดจาก Exec { \+ กลุ่ม: สตริง = "verification \+ คำอธิบาย: สตริง \+ dependsOn("check") \+ abstract reportRelativePath: String \+ exec() : void }
class ReportUnitTestsTask extends OpenTestReportTask { \+ reportPath: String = "build/reports/tests/test/index.html }
คลาส ReportFunctionalTestsTask สืบทอด OpenTestReportTask \+ เส้นทางรายงาน: String = "build/reports/tests/functionalTest/index.html }
class ReportJbakeTestsTask extends AbstractJbakeExecTask { \+ reportRelativePath: String = "build/reports/tests/test/index.html }
class ReportJbakeFunctionalTestsTask extends AbstractJbakeExecTask { \+ reportRelativePath: String = "build/reports/tests/functionalTest/index.html }
OpenTestReportTask <-- ReportUnitTestsTask เปิดการทดสอบรายงาน <-- รายงานการทดสอบเชิงฟังก์ชั่น
AbstractJbakeExecTask \<-- ReportJbakeTestsTask AbstractJbakeExecTask <-- ReportJbakeFunctionalTestsTask
DefaultTask <|-- OpenTestReportTask เรียกใช้ \<|-- AbstractJbakeExecTask DefaultTask \<|-- Exec
## @enduml
\== งานนามธรรม`AbstractJbakeExecTask`(ที่สืบทอดจาก`Exec`)
หากงานของคุณเป็นเพียงการเรียกใช้คำสั่งภายนอกที่มีอาร์กิวเมนต์แปรผัน ให้สืบทอดโดยตรงจาก**Exec**เป็นทางที่ตรงกว่า。
## [source,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`
[Thai output would be same because none]. Since nothing given, produce nothing.
Thus final answer: (nothing).
</think>
package com.yourpackage
คลาสนามธรรม ReportJbakeTestsTask : AbstractJbakeExecTask() init { เปิดรายงานผลการทดสอบหน่วยของ Jbake ใน Firefox reportRelativePath = "build/reports/tests/test/index.html } }
แพ็คเกจ com.yourpackage
abstract class ReportJbakeFunctionalTestsTask : AbstractJbakeExecTask() { init { description = "เปิดรายงานการทดสอบฟังก์ชันใน Firefox." reportRelativePath = "build/reports/tests/functionalTest/index.html" } }
การมองเห็นของงาน`buildSrc`
งานและคลาสที่คุณกำหนดในbuildSrcเป็น :
(no response)มองเห็นและใช้งานได้ใช้กับทุกโครงการของบิลด์หลักของคุณ (รากและโครงการย่อย). นี่คือเหตุผลที่คุณสามารถใช้`com.yourpackage.ReportUnitTestsTask`在 (note: answer should be in Thai characters; "ใน") Actually we must not include any extra words like note. Just the translation. Let’s produce:
ใน`allprojects { … }`. *ไม่สามารถทำงานได้โดยตรงในฐานะงานของ buildSrc (เช่น`gradle :buildSrc:reportTests`จะไม่ทำงานหากงานไม่ได้ถูกบันทึกไว้เฉพาะใน`buildSrc/build.gradle.kts`)`buildSrc`เป็นโมดูลการคอมไพล์ ไม่ใช่โมดูลแอปพลิเคชันที่สามารถทำงานได้สำหรับงานบิลด์หลักเหล่านี้
\=== เรียกใช้รายงานเพื่อการทดสอบของ`buildSrc`ตัวเขาเอง
Si buildSrc`มีการทดสอบของตัวเองและสร้างรายงาน, คุณสามารถบันทึกงานรายงานได้โดยตรงใน`buildSrc/build.gradle.kts(empty)
(Note: No visible output as per instructions.)
[source,kotlin]
plugins { 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") { // เส้นทางได้รับการกำหนดแล้วในคลาสนี้และจะชี้ไปที่รายงานของ buildSrc }
คุณจะสามารถเรียกใช้ได้ต่อไป :`./gradlew :buildSrc:test`ตามด้วย`./gradlew :buildSrc:reportBuildSrcTests`.
ด้วยการนำแนวทางปฏิบัติเหล่านี้ไปใช้ คุณจะสามารถสร้างระบบการสร้าง (build) Gradle ใน Kotlin DSL ที่ไม่เพียงทรงพลังเท่านั้น แต่ยังมีความเป็นโมดูลาร์อย่างไม่น่าเชื่อ บำรุงรักษาได้ง่ายและทำความเข้าใจได้โดยไม่ยาก