Первые шаги в разработке пользовательских плагинов Gradle

Не так давно 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

Шаг за шагом

Давайте выполним следующие шаги:

  • Создание плагинов в 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
Войти в полноэкранный режим Выйти из полноэкранного режима

Вот и все! Счастливого кодинга! 💙

Оцените статью
Procodings.ru
Добавить комментарий