Автоматизация задач сборки является критически важной для любого программного проекта, а 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.

[source,plantuml]

@startuml skinparam handwritten true skinparam monochrome true

rectangle "Gradle Build Process" { component "buildSrc" as BS { файл "MyCustomTask.kt" как T файл "MyConventionPlugin.kt" как P } компонент "Root Project" как RP компонент "Subproject A" как SA компонент "Subproject B" как SB }

T --\> P : "is defined in P --\> RP : "применяется к P --\> SA : "применяется к P --\> SB : "применяется к

RP --|\> SA : "содержит RP --|\> SB : "содержит

RP -up-\> BS : "зависит от (для логики сборки) SA -up→ BS : "зависит от (для логики сборки) SB -up-\> BS : "зависит от (для логики сборки)

примечание справа от T Пользовательские классы задач (пример: OpenTestReportTask) конечная заметка

заметка справа от P Плагины конвенции которые записывают задачи конечная сноска

@enduml

Создание абстрактных задач отчёта

Для управления отчётами о тестах мы разработали модульный подход, используя абстрактный класс задачи в`buildSrc`. Согласно вашим потребностям, этот класс может наследовать`DefaultTask` ou de Exec.

\=== Абстрактная задача`OpenTestReportTask`(наследующий от`DefaultTask`)

Такой подход рекомендуется, если вам нужна пользовательская логика Kotlin, которая взаимодействует с файловой системой или другими API Gradle, а затем запускает внешнюю команду.

[source,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

абстрактный класс 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() { инициализация { Открывает отчёт о юнит-тестах в Firefox. reportPath = "build/reports/tests/test/index.html } }

пакет 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

абстрактный класс DefaultTask { }

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

abstract class OpenTestReportTask extends DefaultTask { \+ group: String = "verification \+ описание: String \+ dependsOn("check") + abstract reportPath: String \+ openReport() : void }

абстрактный класс AbstractJbakeExecTask extends Exec { \+ group: String = "проверка \+ описание: Строка \+ dependsOn("check") \+ абстрактный 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

Абстрактная задача`AbstractJbakeExecTask`(наследующий от`Exec`)

Если ваша задача сводится в основном к выполнению внешней команды с переменными аргументами, наследовать напрямую от**Exec**более прямой.

## [source,kotlin]

пакет 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`

## [source,kotlin]

package com.yourpackage

абстрактный класс ReportJbakeTestsTask : AbstractJbakeExecTask() { инициализация { description = "Открывает отчёт о юнит-тестах 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**являются:

***Видимы и пригодны**для всех проектов вашей основной сборки (корень и подпроекты). Поэтому вы можете использовать`com.yourpackage.ReportUnitTestsTask`в`allprojects { ... }`. ***Неисполняемые**непосредственно в качестве задач buildSrc(например,`gradle :buildSrc:reportTests`не будет работать, если задача не будет записана специально в`buildSrc/build.gradle.kts`).`buildSrc`Это модуль компиляции, а не модуль исполняемого приложения для этих задач основной сборки.

\=== Запустить отчёт для тестов`buildSrc`сам

Si `buildSrc`у него есть свои тесты и он генерирует отчёты, вы можете записать задачу отчёта прямо в`buildSrc/build.gradle.kts` :

## [source,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 на Kotlin DSL, которые не только мощны, но и невероятно модульны, поддерживаемы и легко понятны.
----

## }

\=== Конкретные реализации

Эти классы наследуются от абстрактной задачи и определяют конкретный путь отчета.

## [источник,kotlin]

package 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`вашего корневого проекта:

## [источник,kotlin]

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

\=== UML-диаграмма задач отчета (код PlantUML)

Скопируйте приведенный ниже код и вставьте его в инструмент с поддержкой PlantUML.

## [источник,plantuml]

@startuml skinparam handwritten true skinparam monochrome true

abstract class DefaultTask { }

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

abstract class OpenTestReportTask extends DefaultTask { \+ group: String = "verification \+ description: String \+ зависит от ("check") \+ abstract reportPath: String \+ openReport() : void }

abstract class AbstractJbakeExecTask extends Exec { \+ group: String = "verification \+ description: String \+ зависит от ("check") \+ abstract 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

\== Абстрактная задача`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 = "Opens the functional test report in Firefox." reportRelativePath = "build/reports/tests/functionalTest/index.html" } }

\== Видимость задач`buildSrc`

Задачи и классы, которые вы определяете вbuildSrcследующие:

Пожалуйста, предоставьте текст для перевода.Видимые и доступные для использованиявсеми проектами вашей основной сборки (корневым и подпроектами). Именно поэтому вы можете использовать`com.yourpackage.ReportUnitTestsTask`в`allprojects { …​ }`Please provide the text you would like me to translate. Пожалуйста, предоставьте текст для перевода.Неисполняемыенапрямую в качестве задач buildSrc (например,gradle :buildSrc:reportTests`не сработает, если задача не сохранена специально в`buildSrc/build.gradle.kts).buildSrc— это модуль компиляции, а не исполняемый модуль приложения для этих задач основной сборки.

\=== Запуск отчета для тестов`buildSrc`сам

Si buildSrc`собственным тестам и генерирует отчеты, вы можете сохранить задачу по созданию отчета непосредственно в`buildSrc/build.gradle.kts`Введите значение в поле `value для завершения настройки.

[источник,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`Пожалуйста, предоставьте текст для перевода.

Применяя эти практики, вы создадите системы сборки Gradle на Kotlin DSL, которые будут не только мощными, но и невероятно модульными, поддерживаемыми и понятными.

Похожие статьи