Tự động hóa các tác vụ build là điều thiết yếu đối với mọi dự án phần mềm, và Gradle, với DSL Kotlin của nó, mang lại sự linh hoạt đặc biệt. Trong cuộc trò chuyện của chúng ta, chúng ta đã khám phá cách tập trung hóa và tái sử dụng logic build, đặc biệt là đối với các báo cáo kiểm thử, bằng cách tận dụngtất cả các dự án, buildSrc, và các tác vụ tùy chỉnh bằng Kotlin.

Tập trung hóa các Cấu hình với allprojects và `subprojects

Các khối`allprojects` et `subprojects`trong …​ của bạn`build.gradle.kts`gốc là những yếu tố thiết yếu để áp dụng các cấu hình chung trên toàn bộ dự án đa mô-đun của bạn.

*allprojects { …​ }: Áp dụng cấu hình chodự án gốc và tất cả các dự án con của nó. Lý tưởng để định nghĩa một`group`, một`version`, hoặc những`repositories`chung. *subprojects { …​ }: Áp dụng cấu hìnhchỉ cho các tiểu dự án, loại trừ dự án gốc. Hoàn hảo để áp dụng các plugin cụ thể cho các mô-đun (như`java` ou kotlin-jvm) hoặc các phụ thuộc dùng chung cho các thư viện của bạn.

Đây là một ví dụ minh họa:

// 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: Con dao đa năng Thụy Sĩ của Logic Build

Khi logic trong các tác vụ của bạn trở nên phức tạp hoặc cần được tái sử dụng,buildSrclà giải pháp được ưu tiên. Đây là một module Gradle đặc biệt được biên dịch trước các script build chính, giúp các lớp của nó có sẵn trên classpath của toàn bộ build của bạn.

\=== Tại sao sử dụng`buildSrc`cho các Nhiệm vụ ?

  • Khả năng tái sử dụng: Một tác vụ được định nghĩa trong`buildSrc`có thể được áp dụng cho bất kỳ dự án nào của bản build.

  • Tổ chức: Tập trung mã build, làm cho nó gọn gàng và dễ bảo trì hơn.

  • An toàn kiểu và Tự động hoàn thành: Mã Kotlin trong`buildSrc`được biên dịch, cung cấp tính năng kiểm tra lỗi và tự động hoàn thành của IDE, cải thiện trải nghiệm phát triển.

\=== Sơ đồ luồng`buildSrc`(Mã PlantUML)

Để tạo sơ đồ này, hãy sao chép mã bên dưới và dán vào một công cụ hỗ trợ PlantUML.

[source,plantuml]

@startuml skinparam handwritten true skinparam monochrome true

rectangle "Quy trình xây dựng Gradle" { component "buildSrc" as BS { tệp "MyCustomTask.kt" dưới dạng T hàng đợi "MyConventionPlugin.kt" as P } thành phần "Root Project" là RP thành phần "Subproject A" với bí danh SA thành phần "Subproject B" là SB }

T -→ P : "được định nghĩa trong P --\> RP : "được áp dụng cho P --\> SA : "được áp dụng cho P -→ SB : "được áp dụng cho

RP --|\> SA : "chứa RP --|\> SB : "chứa

RP -up-\> BS : "phụ thuộc vào (cho logic build) SA -up→ BS : "phụ thuộc vào (cho logic build) SB -up→ BS : "phụ thuộc vào (cho logic xây dựng)

ghi chú bên phải T Các lớp tác vụ tùy chỉnh (ví dụ: OpenTestReportTask) ghi chú cuối

ghi chú bên phải P Các plugin quy ước mà ghi lại các nhiệm vụ ghi chú cuối

@enduml

\== Tạo các Tác vụ Báo cáo Trừu tượng

Để quản lý báo cáo kiểm thử, chúng tôi đã thiết kế một cách tiếp cận mô-đun bằng cách sử dụng một lớp tác vụ trừu tượng trong`buildSrc`. Tùy theo nhu cầu của bạn, lớp này có thể kế thừa từ`DefaultTask` ou de Exec.

Nhiệm vụ trừu tượng`OpenTestReportTask`(kế thừa từ`DefaultTask`)

Cách tiếp cận này được khuyến nghị nếu bạn cần logic Kotlin tùy chỉnh tương tác với hệ thống tệp hoặc các API Gradle khác, rồi chạy một lệnh bên ngoài.

[nguồn,kotlin]

package com.yourpackage

import org.gradle.api.DefaultTask import org.gradle.api.tasks.Input import org.gradle.api.tasks.TaskAction import java.io.File

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

}

=== Các triển khai cụ thể

Các lớp này kế thừa từ tác vụ trừu tượng và xác định đường dẫn cụ thể của báo cáo.

## [nguồn,kotlin]

package com.yourpackage

abstract class ReportUnitTestsTask : OpenTestReportTask() { khởi tạo { description = "Mở báo cáo kiểm thử đơn vị trong Firefox. reportPath = "build/reports/tests/test/index.html } }

gói com.yourpackage

abstract class ReportFunctionalTestsTask : OpenTestReportTask() { init { description = "Mở báo cáo kiểm thử chức năng trong Firefox." reportPath = "build/reports/tests/functionalTest/index.html" } }

\=== Đăng ký Tác vụ

Trong`build.gradle.kts`của dự án gốc của bạn:

## [nguồn,kotlin]

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

=== Sơ đồ UML các Tác vụ Báo cáo (Mã PlantUML)

Sao chép mã bên dưới và dán vào công cụ hỗ trợ PlantUML.

[source,plantuml]

@startuml skinparam handwritten true skinparam monochrome true

abstract class DefaultTask { }

abstract class Exec { \+ commandLine(args: String...) \+ exec() }

abstract class OpenTestReportTask extends DefaultTask { \+ nhóm: String = "xác minh \+ mô tả: String \+ dependsOn("check") \+ abstract reportPath: String \+ openReport() : void }

abstract class AbstractJbakeExecTask extends Exec { \+ group: String = "verification \+ mô tả: String \+ dependsOn("check") \+ trừu tượng reportRelativePath: String \+ exec() : void }

class ReportUnitTestsTask extends OpenTestReportTask { \+ reportPath: 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 }

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

OpenTestReportTask \<-- ReportUnitTestsTask OpenTestReportTask \<-- ReportFunctionalTestsTask

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

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

## @enduml

\== Tác vụ trừu tượng`AbstractJbakeExecTask`(kế thừa từ`Exec`)

Nếu nhiệm vụ của bạn chủ yếu chỉ là thực thi một lệnh bên ngoài với các đối số thay đổi, hãy kế thừa trực tiếp từ**Exec**trực tiếp hơn.

## [nguồn,kotlin]

gói 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
}

}

\=== Triển khai cụ thể cho`Exec`

[nguồn,kotlin]

package com.yourpackage

abstract class ReportJbakeTestsTask : AbstractJbakeExecTask() { init { Mở báo cáo kiểm thử đơn vị Jbake trong Firefox. reportRelativePath = "build/reports/tests/test/index.html } }

package com.yourpackage

abstract class ReportJbakeFunctionalTestsTask : AbstractJbakeExecTask() { init { description = "Mở báo cáo kiểm thử chức năng trong Firefox." reportRelativePath = "build/reports/tests/functionalTest/index.html" } }

\== Khả năng hiển thị của Nhiệm vụ`buildSrc`

Các tác vụ và lớp mà bạn định nghĩa trongbuildSrclà:

*Có thể nhìn thấy và sử dụng đượcbởi tất cả các dự án thuộc bản build chính của bạn (gốc và các dự án con). Đó là lý do tại sao bạn có thể sử dụng`com.yourpackage.ReportUnitTestsTask`trong`allprojects { …​ }`. *Không thể thực thitrực tiếp dưới dạng các tác vụ của buildSrc (ví dụ,gradle :buildSrc:reportTests`sẽ không hoạt động nếu tác vụ không được lưu cụ thể trong`buildSrc/build.gradle.kts).`buildSrc`là một mô-đun biên dịch, không phải mô-đun ứng dụng có thể thực thi cho các tác vụ này của bản dựng chính.

Chạy báo cáo cho các bài kiểm tra của`buildSrc`chính anh ta

Si buildSrc`có các bài kiểm tra riêng và tạo báo cáo, bạn có thể lưu một tác vụ báo cáo trực tiếp trong`buildSrc/build.gradle.kts:

[nguồn,kotlin]

plugins { kotlin-jvm }

repositories { mavenCentral() }

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

import com.yourpackage.ReportJbakeTestsTask // Import lớp tác vụ của bạn

tasks.register<ReportJbakeTestsTask>("reportBuildSrcTests") { // Đường dẫn đã được định nghĩa trong lớp, nó sẽ trỏ đến các báo cáo của buildSrc }

Sau đó, bạn có thể chạy:`./gradlew :buildSrc:test`tiếp theo là`./gradlew :buildSrc:reportBuildSrcTests`.

Bằng cách áp dụng những thực tiễn này, bạn sẽ xây dựng các hệ thống build Gradle bằng Kotlin DSL không chỉ mạnh mẽ mà còn cực kỳ mô-đun, dễ bảo trì và dễ hiểu.

Bài viết liên quan