Оптимизация управления задачами Gradle с помощью Kotlin DSL и buildSrc
Опубликовано 11 July 2025
- Централизовать Конфигурации с
allprojectsи `subprojects - [source,plantuml]
- @enduml
- [source,kotlin]
- }
- [источник,kotlin]
- abstract class ReportJbakeFunctionalTestsTask : AbstractJbakeExecTask() { init { description = "Opens the functional test report in Firefox." reportRelativePath = "build/reports/tests/functionalTest/index.html" } }
- [источник,kotlin]
- tasks.register\<ReportJbakeTestsTask\>("reportBuildSrcTests") { // Путь уже определен в классе, он будет указывать на отчеты buildSrc }
Автоматизация задач сборки является критически важной для любого программного проекта, а 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, которые будут не только мощными, но и невероятно модульными, поддерживаемыми и понятными.