Не так давно Gradle меня сильно пугал 👻 … может быть, это было из-за Groovy? 😱
Но сегодня я в полном ❤️ Gradle! Пожалуйста, не говорите Maven 😜.
Плагины Gradle позволяют нам повторно использовать логику сборки в разных проектах, и мы можем реализовать их на любом языке, совместимом с JVM: Java, Kotlin, Groovy, ….
В этой демонстрации мы реализуем базовые плагины Gradle, следуя документации Developing Custom Gradle Plugins. Если у вас есть время, загляните в раздел Designing Gradle plugins и среди прочих разделов Convention over configuration и Capabilities vs. conventions.
Будет весело, обещаю!
- rogervinas / gradle-plugins-first-steps
- 🧞♂️ Первые шаги разработки пользовательских плагинов Gradle
- Шаг за шагом
- Создание плагинов в сценарии сборки
- Плагин настроек сценария сборки
- Создание плагина проекта Script
- Создание плагинов в модуле buildSrc
- Создание плагинов в автономном проекте
- Автономный плагин настроек
- Плагин для отдельного проекта
- Использование автономных плагинов
- Запустите эту демонстрацию
- Запуск с использованием includeBuild
- Запуск с помощью mavenLocal
rogervinas / gradle-plugins-first-steps
🧞♂️ Первые шаги разработки пользовательских плагинов Gradle
Шаг за шагом
Давайте выполним следующие шаги:
- Создание плагинов в Build Script
- Плагин настроек сценария сборки
- Плагин проекта Build Script
- Создание плагинов в модуле buildSrc
- Создание плагинов в отдельном проекте
- Плагин настроек автономного проекта
- Плагин автономного проекта
- Использование автономных плагинов
- Запустите эту демонстрацию
- Запуск с использованием includeBuild
- Запуск с использованием mavenLocal
В этом демо мы будем использовать пример многомодульного Gradle-проекта под названием my-gradle-project с двумя модулями и пользовательской задачей hello, определенной как:
tasks.create("hello") {
doLast {
println("Hello from ${project.name}!")
}
}
Таким образом, мы можем просто выполнить ./gradlew hello
и проверить все примененные плагины.
Создание плагинов в сценарии сборки
В качестве первого шага мы можем определить плагины непосредственно в нашем скрипте сборки. Этого достаточно, если нам не нужно повторно использовать их за пределами скрипта сборки, в котором они определены.
Плагин настроек сценария сборки
Чтобы создать плагин настроек и применить его в my-gradle-project > settings.gradle.kts:
class MyBuildSettingsPlugin : Plugin<Settings> {
override fun apply(settings: Settings) {
println("Plugin ${this.javaClass.simpleName} applied on ${settings.rootProject.name}")
// TODO configure `settings`
}
}
apply<MyBuildSettingsPlugin>()
Для простоты этот пример плагина печатает только строку при каждом применении. Настоящий плагин должен что-то делать с объектом Settings
, который передается в качестве параметра.
Попробуйте запустить ./gradlew hello
и он должен вывести эту строку:
Plugin MyBuildSettingsPlugin applied on my-gradle-project
Создание плагина проекта Script
Чтобы создать плагин проекта в my-gradle-project > build.gradle.kts:
class MyBuildProjectPlugin : Plugin<Project> {
override fun apply(project: Project) {
println("Plugin ${this.javaClass.simpleName} applied on ${project.name}")
// TODO configure `project`
}
}
Затем, например, мы можем применить плагин ко всем проектам:
allprojects {
apply<MyBuildProjectPlugin>()
}
Опять же, этот пример плагина выводит только строку при каждом применении. Настоящий плагин должен что-то делать с объектом Project
, который передается в качестве параметра.
Попробуйте запустить ./gradlew hello
и он должен вывести эти строки:
> Configure project :
Plugin MyBuildProjectPlugin applied on my-gradle-project
Plugin MyBuildProjectPlugin applied on my-module-1
Plugin MyBuildProjectPlugin applied on my-module-2
Создание плагинов в модуле buildSrc
В качестве второго шага мы можем определить плагины проекта в специальном модуле buildSrc. Все плагины, определенные в нем, будут видны каждому скрипту сборки в проекте.
Но самое главное, мы можем добавлять тесты! 🤩
Сначала мы создаем модуль buildSrc под my-gradle-project с помощью Gradle init и шаблона kotlin-gradle-plugin.
Мы реализуем плагин в MyBuildSrcProjectPlugin.kt:
class MyBuildSrcProjectPlugin : Plugin<Project> {
override fun apply(project: Project) {
println("Plugin ${this.javaClass.simpleName} applied on ${project.name}")
project.tasks.register("my-buildsrc-project-task") { task ->
task.doLast {
println("Task ${task.name} executed on ${project.name}")
}
}
}
}
Регистрируем его в buildSrc > build.gradle.kts, присваивая ему id
:
gradlePlugin {
plugins {
create("my-buildsrc-project-plugin") {
id = "com.rogervinas.my-buildsrc-project-plugin"
implementationClass = "com.rogervinas.MyBuildSrcProjectPlugin"
}
}
}
И мы тестируем его в файле MyBuildSrcProjectPluginTest.kt:
@Test
fun `should add new task to project`() {
val project = ProjectBuilder.builder().build()
project.plugins.apply("com.rogervinas.my-buildsrc-project-plugin")
assertThat(project.tasks.findByName("my-buildsrc-project-task")).isNotNull()
}
Как вы можете видеть в этом примере, плагин регистрирует новую задачу с именем my-buildsrc-project-task
.
Теперь мы можем использовать ее в любом скрипте сборки, например, в корневом my-gradle-project > build.gradle.kts, примененном к allprojects
:
plugins {
id("com.rogervinas.my-buildsrc-project-plugin")
}
allprojects {
apply(plugin = "com.rogervinas.my-buildsrc-project-plugin")
}
Затем мы можем попробовать выполнить ./gradlew my-buildsrc-project-task
и он должен вывести эти строки:
> Configure project :
Plugin MyBuildSrcProjectPlugin applied on my-gradle-project
Plugin MyBuildSrcProjectPlugin applied on my-module-1
Plugin MyBuildSrcProjectPlugin applied on my-module-2
> Task :my-buildsrc-project-task
Task my-buildsrc-project-task executed on my-gradle-project
> Task :my-module-1:my-buildsrc-project-task
Task my-buildsrc-project-task executed on my-module-1
> Task :my-module-2:my-buildsrc-project-task
Task my-buildsrc-project-task executed on my-module-2
Примечания:
- Помимо модульных тестов мы можем добавить функциональные тесты в модуль buildSrc. Я опустил их здесь для простоты (пример можно посмотреть в разделе Standalone Project).
- Начиная с Gradle 5.x мы не можем определять плагины настроек на buildSrc, потому что классы из buildSrc больше не видны скриптам настроек.
Создание плагинов в автономном проекте
В качестве последнего шага, если мы хотим повторно использовать плагины во всех наших проектах и даже поделиться ими с остальным миром, мы можем создать их в отдельном проекте.
Для этого примера я создал проект my-gradle-plugins с двумя независимыми модулями, каждый из которых использует Gradle init и шаблон kotlin-gradle-plugin. Можно использовать и другие шаблоны: java-gradle-plugin или groovy-gradle-plugin.
Я решил создать один плагин на модуль, но вы можете определить много плагинов в одном модуле.
Автономный плагин настроек
Мы реализуем плагин в файле MySettingsPlugin.kt:
class MySettingsPlugin : Plugin<Settings> {
override fun apply(settings: Settings) {
println("Plugin ${this.javaClass.simpleName} applied on ${settings.rootProject.name}")
settings.gradle.allprojects { project ->
project.tasks.register("my-settings-task") { task ->
task.doLast {
println("Task ${task.name} executed on ${project.name}")
}
}
}
}
}
Мы регистрируем его в build.gradle.kts, присваивая ему id
:
gradlePlugin {
plugins {
create("my-settings-plugin") {
id = "com.rogervinas.my-settings-plugin"
implementationClass = "com.rogervinas.MySettingsPlugin"
}
}
}
И мы тестируем его в функциональном тесте в MySettingsPluginFunctionalTest.kt, с реальными проектами gradle, сохраненными в src/functionalTest/resources:
@Test
fun `should add new task to single-project`() {
val runner = GradleRunner.create()
runner.forwardOutput()
runner.withPluginClasspath()
runner.withArguments("my-settings-task")
runner.withProjectDir(File("src/functionalTest/resources/single-project"))
val result = runner.build()
assertThat(result.output).all {
contains("Plugin MySettingsPlugin applied on single-project")
contains("Task my-settings-task executed on single-project")
}
}
Примечания:
- Если вы проверите MySettingsPluginFunctionalTest.kt, вы увидите два теста: один для одного одномодульного проекта и один для одного многомодульного проекта.
- Я не нашел никакого способа юнит-тестирования плагина настроек. Для плагинов настроек нет вспомогательного класса, как
org.gradle.testfixtures.ProjectBuilder
для плагинов проектов. Если вы знаете способ, пожалуйста, дайте мне знать! 🙏 - Мы используем статические gradle-проекты, сохраненные в src/functionalTest/resources, но мы также можем генерировать gradle-проекты программно, сохраняя их во временных папках (посмотрите этот пример).
Плагин для отдельного проекта
Мы реализуем плагин в файле MyProjectPlugin.kt:
class MyProjectPlugin : Plugin<Project> {
override fun apply(project: Project) {
println("Plugin ${this.javaClass.simpleName} applied on ${project.name}")
project.tasks.register("my-project-task") { task ->
task.doLast {
println("Task ${task.name} executed on ${project.name}")
}
}
}
}
Регистрируем его в build.gradle.kts, присваивая ему id
:
gradlePlugin {
plugins {
create("my-project-plugin") {
id = "com.rogervinas.my-project-plugin"
implementationClass = "com.rogervinas.MyProjectPlugin"
}
}
}
Мы тестируем его в модульном тесте в файле MyProjectPluginTest.kt:
@Test
fun `should add new task to project`() {
val project = ProjectBuilder.builder().build()
project.plugins.apply("com.rogervinas.my-project-plugin")
assertThat(project.tasks.findByName("my-project-task")).isNotNull()
}
Мы тестируем его в функциональном тесте в MyProjectPluginFunctionalTest.kt с реальными gradle-проектами, сохраненными в src/functionalTest/resources:
@Test
fun `should add new task to single-project`() {
val runner = GradleRunner.create()
runner.forwardOutput()
runner.withPluginClasspath()
runner.withArguments("my-project-task")
runner.withProjectDir(File("src/functionalTest/resources/single-project"))
val result = runner.build()
assertThat(result.output).all {
contains("Plugin MyProjectPlugin applied on single-project")
contains("Task my-project-task executed on single-project")
}
}
Примечания:
- Если вы проверите MyProjectPluginFunctionalTest.kt, вы увидите два теста: один для одного одномодульного проекта и один для одного многомодульного проекта.
- Мы используем статические gradle-проекты, сохраненные в src/functionalTest/resources, но мы также можем генерировать gradle-проекты программно, сохраняя их во временных папках (посмотрите этот пример).
Использование автономных плагинов
Чтобы использовать автономные плагины локально во время разработки, у нас есть две альтернативы:
- Использование
includeBuild
: см. раздел Запуск с использованием includeBuild. - Публикация плагинов локально: см. Запуск с помощью mavenLocal.
Затем мы объявляем, какую версию мы хотим использовать всего один раз в my-gradle-project > settings.gradle.kts:
pluginManagement {
plugins {
id("com.rogervinas.my-settings-plugin") version "1.0"
id("com.rogervinas.my-project-plugin") version "1.0"
}
}
Применяем плагин настроек в my-gradle-project > settings.gradle.kts:
plugins {
id("com.rogervinas.my-settings-plugin")
}
Применяем плагин проекта в любом скрипте сборки, например, в my-gradle-project > build.gradle.kts, примененном к allprojects
:
plugins {
id("com.rogervinas.my-project-plugin")
}
allprojects {
apply(plugin="com.rogervinas.my-project-plugin")
}
И, наконец, мы можем опубликовать их в любом частном или публичном репозитории или на Gradle Plugin Portal 🎉.
Запустите эту демонстрацию
Запуск с использованием includeBuild
- Отредактируйте my-gradle-project > settings.gradle.kts и:
- Удалите или закомментируйте строку
mavenLocal()
в pluginManagement > репозитория. - Добавить или раскомментировать строку
includeBuild("../my-gradle-plugins")
.
- Удалите или закомментируйте строку
- Выполнить:
cd my-gradle-project
./gradlew hello
Если вы хотите узнать больше о includeBuild
, вы можете прочитать о составлении сборок
Запуск с помощью mavenLocal
- Соберите и опубликуйте my-gradle-plugins локально:
cd my-gradle-plugins
./gradlew publishToMavenLocal
- Отредактируйте my-gradle-project > settings.gradle.kts и:
- Добавьте или откомментируйте строку
mavenLocal()
в pluginManagement > repositories. - Удалить или закомментировать строку
includeBuild("../my-gradle-plugins")
.
- Добавьте или откомментируйте строку
- Выполнить:
cd my-gradle-project
./gradlew hello
Вот и все! Счастливого кодинга! 💙