What is android gradle plugin

Using Gradle Plugins

Gradle at its core intentionally provides very little for real world automation. All of the useful features, like the ability to compile Java code, are added by plugins. Plugins add new tasks (e.g. JavaCompile), domain objects (e.g. SourceSet), conventions (e.g. Java source is located at src/main/java ) as well as extending core objects and objects from other plugins.

In this chapter we discuss how to use plugins and the terminology and concepts surrounding plugins.

What plugins do

Applying a plugin to a project allows the plugin to extend the project’s capabilities. It can do things such as:

Extend the Gradle model (e.g. add new DSL elements that can be configured)

Configure the project according to conventions (e.g. add new tasks or configure sensible defaults)

Apply specific configuration (e.g. add organizational repositories or enforce standards)

By applying plugins, rather than adding logic to the project build script, we can reap a number of benefits. Applying plugins:

Promotes reuse and reduces the overhead of maintaining similar logic across multiple projects

Allows a higher degree of modularization, enhancing comprehensibility and organization

Encapsulates imperative logic and allows build scripts to be as declarative as possible

Types of plugins

There are two general types of plugins in Gradle, binary plugins and script plugins. Binary plugins are written either programmatically by implementing Plugin interface or declaratively using one of Gradle’s DSL languages. Binary plugins can reside within a build script, within the project hierarchy or externally in a plugin jar. Script plugins are additional build scripts that further configure the build and usually implement a declarative approach to manipulating the build. They are typically used within a build although they can be externalized and accessed from a remote location.

A plugin often starts out as a script plugin (because they are easy to write) and then, as the code becomes more valuable, it’s migrated to a binary plugin that can be easily tested and shared between multiple projects or organizations.

Using plugins

To use the build logic encapsulated in a plugin, Gradle needs to perform two steps. First, it needs to resolve the plugin, and then it needs to apply the plugin to the target, usually a Project.

Resolving a plugin means finding the correct version of the jar which contains a given plugin and adding it to the script classpath. Once a plugin is resolved, its API can be used in a build script. Script plugins are self-resolving in that they are resolved from the specific file path or URL provided when applying them. Core binary plugins provided as part of the Gradle distribution are automatically resolved.

Applying a plugin means actually executing the plugin’s Plugin.apply(T) on the Project you want to enhance with the plugin. Applying plugins is idempotent. That is, you can safely apply any plugin multiple times without side effects.

The most common use case for using a plugin is to both resolve the plugin and apply it to the current project. Since this is such a common use case, it’s recommended that build authors use the plugins DSL to both resolve and apply plugins in one step.

Binary plugins

You apply plugins by their plugin id, which is a globally unique identifier, or name, for plugins. Core Gradle plugins are special in that they provide short names, such as ‘java’ for the core JavaPlugin. All other binary plugins must use the fully qualified form of the plugin id (e.g. com.github.foo.bar ), although some legacy plugins may still utilize a short, unqualified form. Where you put the plugin id depends on whether you are using the plugins DSL or the buildscript block.

Locations of binary plugins

A plugin is simply any class that implements the Plugin interface. Gradle provides the core plugins (e.g. JavaPlugin ) as part of its distribution which means they are automatically resolved. However, non-core binary plugins need to be resolved before they can be applied. This can be achieved in a number of ways:

Including the plugin from the plugin portal or a custom repository using the plugins DSL (see Applying plugins using the plugins DSL).

Читайте также:  You are using insecure hash algorithm in ca signature openvpn android

Including the plugin from an external jar defined as a buildscript dependency (see Applying plugins using the buildscript block).

Defining the plugin as a source file under the buildSrc directory in the project (see Using buildSrc to extract functional logic).

Defining the plugin as an inline class declaration inside a build script.

For more on defining your own plugins, see Custom Plugins.

Applying plugins with the plugins DSL

The plugins DSL provides a succinct and convenient way to declare plugin dependencies. It works with the Gradle plugin portal to provide easy access to both core and community plugins. The plugins DSL block configures an instance of PluginDependenciesSpec.

To apply a core plugin, the short name can be used:

Источник

Gradle Plugin: Что, зачем и как?

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

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

В первой части статьи проведём небольшой теоретический экскурс и рассмотрим способы реализации плагинов, а во второй части двинемся к практической задаче и проникнемся разработкой плагина для Kotlin-кодогенерации. Для реализации Gradle-задумок по ходу статьи я буду использовать Kotlin. Заваривайте ваш любимый напиток и поехали.

Gradle Plugin: Что, зачем и как?

Краткое введение в Gradle-плагины

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

Плагины полностью автономны и позволяют хранить логику для её повторного использования в нескольких проектах или Gradle-модулях. Они замечательно подходят для любых задач, требующих работы с исходным кодом. Такими задачами могут быть кодогенерация / генерация документации / проверка кода / запуск задач на CI / деплой и многое другое.

С точки зрения кода, плагин представляет собой реализацию примитивного интерфейса с единственным методом apply:

Project-ом является Gradle-проект (или Gradle-модуль), куда подключается плагин. Сам по себе интерфейс Project больше похож на God Object, поскольку в нём доступен сразу весь Gradle API, что, в принципе, достаточно удобно.

Gradle предлагает разработчикам несколько способов реализации плагинов (как и практически всего в Gradle). Дальше рассмотрим каждый способ по-отдельности и определим его плюсы и минусы.

Реализация плагина в build.gradle(.kts)

Самый простой вариант – реализовать плагин прямо внутри файла конфигурации. Да, просто берём и пишем класс по примеру выше в build.gradle(.kts). Если честно, трудно понять, какой пользой обладает такое решение, ведь Gradle уже предоставляет возможность обратиться project-у из файла конфигурации и накрутить его нужной логикой. А вот недостатков получаем сразу несколько.

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

Реализация плагина в buildSrc

Модуль buildSrc компилируется и поставляется на этап конфигурации проекта в виде jar, поэтому реализовав плагин в buildSrc, получаем возможность его использования в Gradle-модулях проекта. Однако из-за известных проблем c инвалидацией кеша, реализация плагинов в buildSrc имеет право на жизнь в том случае, если для вашего проекта buildSrc представляет собой около-константный модуль, в котором редко происходят изменения.

Также такой вариант также является более компромиссным в сравнении со standalone-плагинами с точки зрения затрат на реализацию и вполне может подойти для небольших проектов.

Допустим, такой вариант нам подходит. Выполним подготовительный шаг и подключим в buildSrc плагин Kotlin DSL. Он сделает всю грязную работу по подключению Kotlin, подключит Gradle API и ещё м.

P.S. Поскольку для .kts скриптов используется embedded-версия Kotlin, которая лежит вместе с Gradle, то самостоятельно подключать свежую версию Kotlin для реализации плагинов не рекомендуется во избежание проблем с обратной совместимостью. На практике проблем не встречал, но потенциально может выстрелить.

Кладём реализацию плагина в buildSrc/src/main/kotlin. Охапка дров, плагин готов. Теперь можно подключать плагин в проект с помощью apply :

Нагляднее будет подключать плагин c помощью id, поэтому давайте его зададим. Плагин Kotlin DSL транзитивно подключает Java Gradle Plugin Development Plugin, который предоставляет такую возможность:

В результате будет создан следующий файл:

src/main/resources/META-INF/gradle-plugins/ru.myorg.demo.my-plugin.properties

, который Gradle бережно положит в jar и дальше сможет соотносить id плагина с его реализацией. Теперь подключить плагин к проекту можно так:

Script-плагины

В Gradle присутствует интересная возможность реализовать плагин в виде скрипта. Скриптовый плагин по структуре идентичен скриптам конфигурации build.gradle(.kts) и может быть реализован как на Groovy, так и на Kotlin. При реализации на Kotlin существует достаточно весомое ограничение – скрипты должны находиться либо в buildSrc, либо в standalone Gradle-проекте, чтобы быть скомпилированными.

Читайте также:  Все для андроид офис

В случае с Groovy такого ограничения нет, и плагин можно реализовать, например, в рутовой папке проекта, а затем подключить в нужные модули с помощью apply from .

Приятным бонусом script-плагинов является автоматическая генерация id. Это сделает за нас Gradle Plugin Development Plugin, исходя из названия скрипта.

Важно: При реализации и использовании script-плагинов необходимо учесть следующие ограничения, связанные с версиями Gradle:

При реализации script-плагина в отдельном проекте и его подключении через композитную сборку, необходимо использовать Gradle версии 7.1 и выше из-за бага, при котором падала сборка после подключения плагина Kotlin DSL. Во всех проектах, куда подключается плагин с помощью композитной сборки, также необходимо использовать Gradle 7.1 и выше.

Script-плагины, написанные на Kotlin, можно подключать в проекты, использующие Gradle 6 и выше.

Script-плагины, написанные на Groovy, можно подключать в проекты, использующие Gradle 5 и выше.

Script-плагины можно писать на Kotlin, начиная с Gradle 6.0, и на Groovу, начиная с Gradle 6.4.

Лучше всегда держать версию Gradle в актуальном состоянии и не забивать голову лишними вопросами.

Попробуем script-плагин в деле

Давайте проведём небольшой эксперимент. Попробуем вынести в script-плагин на Kotlin общую конфигурацию Android-модулей и сделаем это в buildSrc.

Подключаем Android Gradle Plugin:

Теперь напишем сам плагин:

buildSrc/src/main/kotlin/android-common.gradle.kts:

Жмём Gradle Sync и получаем следующее время конфигурации: BUILD SUCCESSFUL IN 2m 6s

Почему так много? Я начал разбираться и первым делом зашёл в папку buildSrc/build, немного покопался и увидел там следующее:

Оказалось, что для плагинов, которые были объявлены в блоке plugins <> также производится кодогенерация Kotlin-аксессоров для использования в .kts-скриптах. На выходе получаем несколько сотен сгенерированных файлов для плагина com.android.library.

Кстати, именно это происходит при первом подключении Kotlin DSL в Gradle-проект, но лишь за тем отличием, что кодогенерация осуществляется внутри .gradle директории на вашем рабочем устройстве.

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

В коде выше android <> будет самописным Kotlin-екстеншном:

Получаем следующее время конфигурации: BUILD SUCCESSFUL in 48s

и никакого лишнего кодгена. Not bad, not terrible. Замеры проводил на рабочем проекте, поэтому ваши результаты могут отличаться. А чтобы плагин было удобно подключать, генерируем ему id и в buildSrc самостоятельно напишем Kotlin-екстеншн:

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

Небольшой, но важный оффтоп про Kotlin-екстеншны для подключения плагинов

В ходе статьи можно заметить, что некоторые плагины удобным образом подключаются с помощью Kotlin-екстеншнов. Дело в том, что они хитро вшиты в сам Gradle, и написать собственный екстеншн получится только при реализации плагина в buildSrc. Если же плагин реализован в standalone-проекте, то при попытке использования самописного екстеншна получаем ошибку компиляции. Проблема актуальна вплоть до Gradle 7.1. Очень ждём, что это будет исправлено будущих релизах.

Теперь давайте двинемся дальше и рассмотрим полноценные standalone-плагины.

Standalone-плагин на примере кодогенерации

Standalone-плагин подразумевает реализацию в отдельном Gradle-проекте. Такой способ является наиболее предпочтительным, поскольку позволяет избежать всех недостатков buildSrc и script-плагинов. Если вы не хотите создавать отдельный git-репозиторий и планируете использовать плагин внутри одного проекта, то можно воспользоваться возможностью композитной сборки.

Планомерно приступим к небольшой практической задаче. Создадим новый проект и подключим всё, что нужно:

В зависимости добавим Kotlin Poet, с помощью которого будет реализована кодогенерация и Kotlin Gradle Plugin, который поможет интегрировать сгенерированный код в компиляцию JVM-проекта.

Теперь давайте определимся с параметрами плагина и с тем, как их передавать.

Конфигурация плагина

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

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

Контейнер должен быть abstract или open классом, поскольку Gradle будет хитро создавать его инстанс с помощью рефлексии.

Для хранения параметров вместо стандартных переменных следует использовать специальные Gradle properties. Они обеспечивают оптимизацию потребления памяти благодаря ленивой инициализации и дают возможность сконфигурировать плагин в случае, если входные параметры заранее неизвестны и будут установлены позже (например, после выполнения другой Gradle-таски). Для проперти также можно указать дефолтное значение на случай, если к моменту обращения в ней не лежит никакого значения.

Читайте также:  Secret neighbor для андроид

Создать и зарегистрировать extension-контейнер можно так:

Последним параметром передается аргумент (или аргументы) для конструктора. Идеально, если единственным аргументом будет project, но спроектировать можно как угодно. Созданный extension можно использовать в скриптах конфигурации build.gradle(.kts) следующим образом:

Теперь самое время создать саму Gradle-таску, которая будет выполнять кодогенерацию:

Аннотацией @TaskAction помечена функция, с которой таска начинает своё выполнение. Геттеры и сеттеры для Kotlin-пропертей помечены специальными Gradle-аннотациями, чтобы таска имела возможность выполняться инкрементально, то есть не выполняться, когда на то нет необходимости. Кому любопытно, полный исходный код доступен на моём Github.

Теперь давайте сделаем так, чтобы сгенерированный файл успешно компилировался. Директорию, в которой осуществляется кодогенерация, добавим в основной sourceSet. Таким образом явно объявим, что в директории хранится исходный код, который должен быть скомпилирован. С помощью afterEvaluate дождёмся окончания конфигурации, чтобы убедиться, что sourceSets уже были созданы.

Регистрируем кодген-таску, а саму компиляцию вежливо просим её подождать:

Всё готово к использованию. Получившийся плагин можно изучить под спойлером:

Воспользуемся композитной сборкой, чтобы подключить плагин в проект:

settings.gradle.kts:

Прежде чем нажать на Build, положим в корень проекта файл с сообщением. Собираем проект и видим созданный в build/src-gen созданный класс.

Ура! Теперь сгенерированный класс можно использовать в проекте. Самое время протестировать реализованное.

Тестирование плагина

В первую очередь, ребята из Gradle советуют тестировать плагин ручками. То есть, подключаем плагин с помощью композитной сборки к проекту и пользуемся всеми радостями дебаггинга.

Например, чтобы отдебажить конфигурацию плагина, выбираем интересующую нас таску из раздела Run Configurations и жмём Debug. Аналогично можно дебажить любые Gradle-таски.

Для автоматического тестирования Gradle-плагина предусмотрено три варианта: функциональные, интеграционные и модульные тесты.

С модульными тестами всё стандартно – обкладываемся моками, подключив для этого нужные библиотеки, и проверяем работу реализованных компонентов. Модульный тест отвязан от знания Gradle и проверяет корректность работы какого-либо компонента в изоляции. Для модульных тестов по умолчанию создаётся sourceSet test , поэтому никаких подготовительных шагов не требуется.

Для функциональных и интеграционных тестов выполним подготовительный шаг и создадим кастомные sourceSets. Никто не запрещает делать всё в одном сете с модульными тестами, однако подход с раздельными sourceSet позволяет подключать только необходимые для конкретного вида тестов зависимости и в целом изолировать тесты друг от друга.

Чтобы запускать тесты из кастомных sourceSet, самостоятельно создадим соответствующие Gradle-таски. Для удобства запуска всех тестов сразу, свяжем их в стандартную таску check. В итоге конфигурация для интеграционных и функциональных тестов будет выглядеть следующим образом:

Конфигурация для интеграционных и функциональных тестов

build.gradle.kts (Gradle-проект с плагином):

Интеграционное тестирование плагина

Теперь приступим к разработке интеграционного теста, в котором проверим взаимодействие плагина с внешней средой, а именно с файловой системой. Здесь мы всё ещё ничего не знаем о Gradle и проверяем работу бизнес-логики.

Создаём одноимённую для соответствующего sourceSet директорию в /src и реализуем тест.

src/integrationTest/kotlin/:

Функциональное тестирование плагина

Теперь перейдём к самому интересному – функциональным тестам. Они позволяют проверить работу плагина целиком совместно с Gradle и его жизненным циклом. Для этого будем запускать настоящий Gradle Daemon. Конфигурация функциональных тестов практически ничем не отличается от конфигурации интеграционных тестов, за тем исключением, что больше не нужна зависимость на модуль с Gradle-плагином.

Чтобы запустить Gradle Daemon, необходимо создать и сконфигурировать Gradle Runner. Для этого добавляем Gradle Runner API в classpath для функциональных тестов следующим образом:

В тесте эмулируем структуру проекта, конфигурируем Gradle Runner, запускаем и смотрим что получилось. Сам тест лежит под спойлером:

src/functionalTest/kotlin/:

Отлично! Плагин протестировали, можно похвалить себя чем-то приятным. Осталось дело за малым – задеплоить плагин.

Деплой плагина во внешний репозиторий

Для этого воспользуемся плагином Maven Publish. Формируем публикацию и объявляем список репозиториев, в которые она сможет публиковаться:

Итоги

Gradle-плагины представляют собой действительно мощный инструмент для дополнения логики сборки необходимыми для вас задачами. К сожалению, в Gradle API по-прежнему много загадок и непонятных проблем, в том числе не до конца раскрытых в документации. Это создаёт препятствия на пути к удобству использования, однако команда Gradle над этим активно работает.

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

Всё изложенное в статье доступно на Github.

Ниже представлю небольшой список opensource-плагинов, в исходниках которых можно подчерпнуть идеи для реализации:

Источник

Оцените статью