Глава 41. Разработка пользовательских плагинов.

Плагин Gradle содержит повторно используемые кусочки логики сборки, которые можно использовать во многих различных проектах и сборках. Gradle позволяет вам реализовывать свои собственные плагины, чтобы вы могли повторно использовать вашу логику сборки и делиться ей с другими.

Вы можете создать свой собственный плагин на любом языке, который вам нравится и поддерживает компиляцию в байт-код. В примерах в этой главе мы будет использовать Groovy. Вы можете использовать Java или Scala, если хотите.

41.1. Упаковка плагина.

Есть несколько мест, где вы можете разместить исходные коды плагина.

Сборочный скрипт

Вы можете включить исходные коды плагина прямо в сборочный скрипт. Преимущество этого способа состоит в том, что плагин автоматически компилируется и включается в путь к классам сборочного скрипта. Однако, он не виден за пределами сборочного скрипта и в другом месте вы его не сможете использовать.

Проект buildSrc

Вы можете разместить исходные коды плагина в папке rootProjectDir/buildSrc/src/main/groovy. Gradle позаботится о компиляции и тестировании плагина и сделает его доступным в пути к классам сборочного скрипта. Плагин виден любому скрипту в сборке. Однако, он не виден за пределами сборки и в другом месте вы его не сможете использовать.

Чтобы узнать больше, смотрите Главу 43 Организация логики сборки.

Автономный проект

Вы можете создать отдельный проект для плагина. Это проект выдаст и опубликует jar-файл, который можно использовать в нескольких сборках и поделиться им с другими. В общем случае, jar-файл может включать несколько пользовательских плагинов или нескольких связанных классов задачи упакованных в единую библиотеку. Или их комбинацию.

В наших примерах, мы начнем с плагина в сборочном скрипте, чтобы было проще. Потом посмотрим на создание автономного проекта.

41.2. Написание простого плагина.

Чтобы создать пользовательский плагин, вам необходимо реализовать Plugin. Gradle создаст экземпляр плагина и вызовет его метод Plugin.apply(T), когда он будет использован в проекте. Объект проекта передается в качестве параметра, который плагин может использовать, чтобы настроить проект, если это необходимо. Следующий пример содержит плагин приветствия, который добавляет задачу hello к проекту.

Пример 41.1. Пользовательский плагин

build.gradle

apply plugin: GreetingPlugin

class GreetingPlugin implements Plugin {
    void apply(Project project) {
        project.task('hello') {
            doLast {
                println "Hello from the GreetingPlugin"
            }
        }
    }
}
	  

Вывод команды gradle -q hello

> gradle -q hello
Hello from the GreetingPlugin
	  

Обратите внимание на то, что новый экземпляр данного плагина создается для каждого проекта к которому он применяется. Также обратите внимание, что класс Plugin является базовым типом. В этом примере он получает тип Project в качестве параметра типа. Возможно написать необычные пользовательские плагины, которые принимают различные типы параметров, но это будет неперспективно (пока кто-нибудь не придумает как это можно использовать).

41.3. Получение ввода из сборки.

Большинству плагинов требуется получать некоторую конфигурацию из сборочного скрипта. Один способ - использовать объекты расширения. У Project есть связанный объект ExtensionContainer, который помогает отследить все настройки и свойства, переданные плагинам. Вы можете захватить пользовательский ввод, сказав контейнеру расширения о вашем плагине. Чтобы захватить ввод, просто добавьте совместимый с Java Bean класс в список расширений контейнера. Groovy - хороший выбор для написания плагина, потому что простые старые объекты Groovy содержат все методы установки (сеттеры) и получения (геттеры), которые требуются Java Bean.

Давайте добавим простой объект расширения в проект. Мы добавим объект расширения greeting в проект, который позволит вам настроить приветствие.

Пример 41.2. Расширение пользовательского плагина

build.gradle

apply plugin: GreetingPlugin

greeting.message = 'Hi from Gradle'

class GreetingPlugin implements Plugin {
    void apply(Project project) {
        // Add the 'greeting' extension object
        project.extensions.create("greeting", GreetingPluginExtension)
        // Add a task that uses the configuration
        project.task('hello') {
            doLast {
                println project.greeting.message
            }
        }
    }
}

class GreetingPluginExtension {
    def String message = 'Hello from GreetingPlugin'
}
	  

Вывод команды gradle -q hello

> gradle -q hello
Hi from Gradle
	  

В этом примере, GreetingPluginExtension - просто старый объект Groovy с полем message. Объект расширения добавлен в список плагинов под именем greeting. Потом этот объект стал доступен в виде свойства проекта с тем же именем, что и у объекта расширения.

Часто у вас есть несколько связанных свойст, которые надо указать у одного плагина. Gradle добавяет блок замыкания настройки для каждого объекта расширения, так что вы можете сгруппировать настройки. В следующем примере показано как это работает.

Пример 41.3. Пользовательский плагин с замыканием настройки

build.gradle

apply plugin: GreetingPlugin

greeting {
    message = 'Hi'
    greeter = 'Gradle'
}

class GreetingPlugin implements Plugin {
    void apply(Project project) {
        project.extensions.create("greeting", GreetingPluginExtension)
        project.task('hello') {
            doLast {
                println "${project.greeting.message} from ${project.greeting.greeter}"
            }
        }
    }
}

class GreetingPluginExtension {
    String message
    String greeter
}
	  

Вывод команды gradle -q hello

> gradle -q hello
Hi from Gradle
	  

В этом примере, несколько настроек могут быть сгруппированы внутри замыкания greeting. Имя блока замыкания в сборочном скрипте (greeting) должно совпадать с именем объекта расширения. Затем, когда замыкание выполняется, поля объекта расширения будут отображены на переменны внутри блока, основываясь на стандартной функции передачи замыкания Groovy.

41.4. Работа с файлами в пользовательских задачах и плагинах.

Когда разрабатываешь пользовательские задачи и плагины, хорошая идея быть гибким при приеме входной конфигурации местоположений файлов. Для этого вы можете воспользоваться методом Project.file(java.lang.Object), чтобы разрешить значения в файлы как можно позже.

Пример 41.4. Ленивое вычисление файловых свойств

build.gradle

class GreetingToFileTask extends DefaultTask {

    def destination

    File getDestination() {
        project.file(destination)
    }

    @TaskAction
    def greet() {
        def file = getDestination()
        file.parentFile.mkdirs()
        file.write "Hello!"
    }
}

task greet(type: GreetingToFileTask) {
    destination = { project.greetingFile }
}

task sayGreeting(dependsOn: greet) {
    doLast {
        println file(greetingFile).text
    }
}

ext.greetingFile = "$buildDir/hello.txt"
	  

Вывод команды gradle -q sayGreeting

> gradle -q sayGreeting
Hello!
	  

В этом примере, мы настроили свойство destination задачи greet как замыкание, возвращаемое значение которого вычисляется методом Project.file(java.lang.Object) в объект файла в последнюю минуту. Обратите внимание, что в примере выше мы указали значение свойства greetingFile после настойки его для использования задачей. Ключевое преимущество такого типа ленивого вычисления - принятие значения при установке файлового свойства, затем разрешение этого значения при чтении свойства.

41.5. Автономный проект.

Теперь мы переместим наш плагин в автономный проект, чтобы его можно было опубликовать и поделиться с другими. Это просто проект Groovy, который выдает jar-файл, содержащий классы плагина. Ниже приведен сборочный скрипт для проекта. Он применяет плагин Groovy и добавляет Gradle API в качестве зависимости времени компиляции.

Пример 41.5. Сборка пользовательского плагина

build.gradle

apply plugin: 'groovy'

dependencies {
    compile gradleApi()
    compile localGroovy()
}
	  

Примечание: код этого проекта можно найти в папке samples/customPlugin/plugin дистрибутива Gradle '-all'.

Как же Gradle найдет реализацию Plugin? Вам необходимо предоставить файл свойств в папке META-INF/gradle-plugins в архиве jar, который совпадает с идентификатором вашего плагина.

Пример 41.6. Написание пользовательского плагина

src/main/resources/META-INF/gradle-plugins/org.samples.greeting.properties

implementation-class=org.gradle.GreetingPlugin
	  

Обратите внимание, что имя файла свойств совпадает с идентификатором плагина и расположено в папке с ресурсами и свойство implementation-class указывает класс реализации Plugin.

41.5.1. Создание идентификатора плагина.

Идентификаторы плагинов полностью квалифицированы и в этом похожи на пакеты Java (то есть обратные имена доменов). Это помогает избежать коллизий и предоставляет способ группировать плагины с одинаковым владельцем.

Идентификатор вашего плагина должен быть комбинацией компонентов, которые отражают пространство имен (подходящее указание на вас или вашу организацию) и имя плагина. Например, если у вас есть аккаунт на Github с именем 'foo' и ваш плагин назван 'bar', подходящим идентификатором может быть com.github.foo.bar. Точно так же, если плагин был разработан организацией bar, идентификатором плагина может быть org.baz.bar.

Идентификаторы должны удовлетворять следующему:

  • Могут содержать любой буквенно-цифровой символ, '.' и '-'.
  • Должны содержать как минимум один символ '.', отделяющий пространство имен от имени плагина.
  • Для пространства имен использовать обратное доменное именя в нижнем регистре.
  • В имени использовать только символы в нижнем регистре.
  • Могут не использовать пространства имен org.gradle и com.gradleware.
  • Не могут начинаться или заканчиваться символом '.'.
  • Не могут содержать идущие друг за другом символы '.' (то есть '..').

Хотя есть схожести в соглашениях об идентификаторах плагинов и именах пакетов, в общем, имена пакетов более детальны, чем это нужно для идентификатора плагина. Например, может казаться обоснованным добавить 'gradle' как компонент вашего идентификатора плагина, но так как идентификаторы плагинов используются только для плагинов Gradle, это будет лишним. Как правило, пространство имен, которое идентифицирует владельца и имя - это все, что нужно для хорошего идентификатора плагина.

41.5.2. Публикация вашего плагина.

Если вы публикуете ваш плагин для внутреннего использования в рамках вашей организации, то можете публиковать его как любой другой артефакт. Смотрите главы про публикацию артефактов в ivy и maven.

Если вы заинтересованы в том, чтобы ваш опубликованный плагин использовался более широким сообществом Gradle, вы можете опубликовать его на портале плагинов Gradle. На этом сайте есть возможнсоть искать и собирать информацию о плагинах, созданных сообществом Gradle. Инструкцию о том, как сделать ваш плагин доступным на сайте, смотрите здесь.

41.5.3. Использование вашего плагина в другом проекте.

Для использования плагина с сборочном скрипте, вам необходимо добавить классы плагина в путь к классам. Для этого используйте блок 'buildscript { }', как было описано в секции Применение плагинов с использованием блока buildscript. В следующем примере показано как вы можете это сделать, когда jar-файл, содержащий плагин, был опубликован в локальное хранилище:

Пример 41.7. Использование пользовательского плагина в другом проекте

build.gradle

buildscript {
    repositories {
        maven {
            url uri('../repo')
        }
    }
    dependencies {
        classpath group: 'org.gradle', name: 'customPlugin',
                  version: '1.0-SNAPSHOT'
    }
}
apply plugin: 'org.samples.greeting'
	  

Если ваш плагин опубликован на портале плагинов, вы можете использовать инкубационный DSL плагинов (смотрите Секцию 27.5.2 Применение плагинов с помощью DSL) для его применения:

Пример 41.8. Применение плагина сообщества с помощью DSL плагинов

build.gradle

plugins {
    id "com.jfrog.bintray" version "0.4.1"
}
	  

41.5.4. Написание тестов для плагина.

Вы можете использовать классс ProjectBuilder для создания экземпляров Project, используемых при тестировании реализации вашего плагина.

Пример 41.9. Тестирование пользовательского плагина

src/test/groovy/org/gradle/GreetingPluginTest.groovy

class GreetingPluginTest {
    @Test
    public void greeterPluginAddsGreetingTaskToProject() {
        Project project = ProjectBuilder.builder().build()
        project.pluginManager.apply 'org.samples.greeting'

        assertTrue(project.tasks.hello instanceof GreetingTask)
    }
}
	  

41.5.5. Использование плагина разработки Java Gradle.

Вы можете использовать инкубационный плагин разработки Java Gradle, чтобы убрать несколько избыточных объявлений в вашем сборочном скрипте и предоставить несколько базовых проверок метаданных. Этот плагин автоматически применяет плагин Java, добавляет зависимость gradleApi() к конфигурации компиляции и выполняет проверку метаданных плагина, как часть выполнения задачи jar.

Пример 41.10. Использование плагина разработки Java Gradle

build.gradle

plugins {
    id "java-gradle-plugin"
}
	  

При публикации плагинов в пользовательские хранилища с использованием публикации ivy или maven, плагин разработки Java Gradle также сгенерирует артефакты маркировки с именем, основанным на идентификаторе плагина, который зависит от артефакта реализации плагина.

41.6. Поддержка несколько объектов домена.

Gradle предоставляет несколько вспомогательных классов для поддержки коллекций объектов, которые хорошо работают с языком сборки.

Пример 41.11. Управление доменными объектами

build.gradle

apply plugin: DocumentationPlugin

books {
    quickStart {
        sourceFile = file('src/docs/quick-start')
    }
    userGuide {

    }
    developerGuide {

    }
}

task books {
    doLast {
        books.each { book ->
            println "$book.name -> $book.sourceFile"
        }
    }
}

class DocumentationPlugin implements Plugin {
    void apply(Project project) {
        def books = project.container(Book)
        books.all {
            sourceFile = project.file("src/docs/$name")
        }
        project.extensions.books = books
    }
}

class Book {
    final String name
    File sourceFile

    Book(String name) {
        this.name = name
    }
}
	  

Вывод команды gradle -q books

> gradle -q books
developerGuide -> /home/user/gradle/samples/userguide/organizeBuildLogic/customPluginWithDomainObjectContainer/src/docs/developerGuide
quickStart -> /home/user/gradle/samples/userguide/organizeBuildLogic/customPluginWithDomainObjectContainer/src/docs/quick-start
userGuide -> /home/user/gradle/samples/userguide/organizeBuildLogic/customPluginWithDomainObjectContainer/src/docs/userGuide
	  

Методы Project.container(java.lang.Class) создают экземпляры NamedDomainObjectContainer, у которых есть много полезных методов для управления и настройки объектов. Для того, чтобы использовать тип с любым методом project.container, у него должно быть свойство с именем 'name' с неизменным и уникальным именем объекта. Вариант метода контейнера project.container(Class) создает новые экземпляры, пытаясь выполнить конструктор класса, который принимает одиночный строковый аргумент, являющийся желаемым именем объекта. Смотрите ссылку выше, где есть варианты метода project.container, которые дают различные стратегии создания экземпляра.