Gradle
Intro
Система автоматизации сборки. Скрипты — это dsl на Groovy или Kotlin. Не только для Android и jvm, но и для native (C++, swift) и многого другого.
Youtube запись от Redmadrobot: https://www.youtube.com/watch?v=WOBok2u-SL8
Документация от Kotlin по использованию Gradle для сборки kotlin-приложения: https://kotlinlang.org/docs/gradle.html
Мой проект, в котором я собирал kotlin multiplatform библиотеку для веб-сервера и андроид приложения с использованием паттерна buildSrc и синтаксиса Kotlin DSL для gradle: https://github.com/IkeMurami-Examples/KotlinMultiplatformExample
Примеры multiplatform проектов: https://kotlinlang.org/docs/multiplatform-mobile-samples.html
Install
releases: https://gradle.org/releases/
Скачиваем бинари, кладем в папку, и прописываем путь до папки bin в PATH.
Update gradle
Для подключения gradle, надо задать версию для зависимости com.android.tools.build:gradle
.
build.gradle
:
Вот здесь можно посмотреть какие версии доступны в google maven репозитории (google()
): https://mvnrepository.com/artifact/com.android.tools.build/gradle-api?repo=google
Есть два способа обновить версию gradle:
Android Studio: File > Project Structure > установить версию Gradle из выпадающего списка. Нужная версия Gradle сразу подтянется
Terminal:
Basics
Состоит из проектов (они же называются модулями, что может вызвать путаницу)
Проекты могут быть сколь угодно вложенными
Пример структуры
settings.gradle — здесь описаны все проекты:
Tasks и Projects
Это основные объекты в доменной модели Gradle
Таск — основная единица выполнения в Gradle.
Таски могут зависеть друг от друга
Пример таска (на Kotlin DSL):
Task configuration
doFirst() и doLast() — действия, совершаемые в начале и в конце выполенения таска
description — описание таска
group — имя группы тасков
extra properties
Task actions
Методы, помеченные аннотацией @TaskAction, будут запускаться при запуске таска
У таска может быть несколько action'ов
Все action'ы хранятся в списке и будут запускаться по порядку
Методы doFirst и doLast на самом деле добавляют action'ы в начало или конец списка
Incremental Tasks
Некоторые таски достаточно тяжелые, и нет смысла их перезапускать, если произошла ошибка или произошли какие-то изменения в файлах в последующих тасках. Есть специальное API, которое позволяет не перезапускать таски, которые завершились успешно.
Результат выполнения тасков
NO-SOURCE — таск не был запущен, так как не нашлось данных для него
SKIPPED — таск не был запущен
был явно выключен
через командную строку (параметр -x)
через свойство enabled=false
через список excludedTaskNames
произошел StopExecutionException
у таска есть предикат onlyIf {}, который вернул false
FROM-CACHE
Означает, что результат выполнения task'а был взят из кэша
Чтобы gradle мог кэшировать task, надо его явно пометить аннотацией @CacheableTask
Gradle на основе имени класса, входных и выходных данных и других параметров сам вычисляет ключ
Кэш может быть не только локальным, но и удаленным, и использоваться несколькими машинами
UP-TO-DATE
старый механизм в gradle, придуманный до кэша
Task имеет входные и выходные данные, которые не изменились
Task явно указал gradle, что его выходные данные не изменились (через лямбду, переданную в outputs.upToDateWhen{})
У task'а нет action'ов и он зависит от других task'ов, которые UP-TO-DATE, SKIPPED или FROM_CACHE
У task'а нет ни action'ов, ни зависимостей
(no label) or EXECUTED — Task и его зависимости были запущены
Как управлять зависимостями между тасками
1
dependsOn()
— Используется, когда Task не может начать работу, пока не завершится один или несколько других task'ов (можно указать список)
2
finalizedBy()
— Указывает, какой после текущего должен выполнится таск. Finalized task выполнится, даже если тот, от которого он зависит, завершится неудачей.
3
shouldRunAfter()
и mustRunAfter()
— Задают порядок выполнения тасков без явных зависимостей между ними. Главное отличие от dependsOn
в том, что методы никак не влияют на запуск тасков, а ТОЛЬКО на порядок.
shouldRunAfter()
— порядок может быть не выполнен (если получился циклический порядок или при параллельном выполнении)
mustRunAfter()
— порядок тасков должен выполнятся всегда
4
Inputs and Outputs
Обычно, если task зависит от другого, то он ждет на вход данные, которые порождает другой task.
В gradle можно связать входные данные одного таска с выходными данными другого, при этом можно явно не указывать зависимости через
dependsOn
.
В коде это выглядит примерно так
Фазы сборки Gradle
Initialization — Gradle проверяет, какие проекты будут участвовать в билде и создает инстансы Project
Configuration — Запускаются все скрипты build.gradle у каждого проекта и определяются таски проектов, строится граф тасков
Execution — Gradle запускает таски в нужном порядке на основе графа
settings.gradle
Этот файл сапускается во время первой фазы инициализации
Вызовы в этом файле делегируются объекту Settings
Для многомодульных (multi-project) проектов settings.gradle нужен обязательно, чтобы в нем описать дерево проектов
Project evaluation
Во время фазы конфигурации Gradle проходит по каждому проекту, запускает build скрипт и создает таски (Gradle умеет запускать только те build-скрипты, которые нужны для конкретного таска)
В api есть возможность сделать что-то до или после конфигурации: методы beforeEvaluate() и afterEvaluate() у проектов
Эти методы обычно используются из корневого проекта, чтобы сделать какую-то дополнительную конфигурацию у дочерних проектов
gradle.properties
Файл, лежащий в корневом проекте (а еще в
GRADLE_USER_HOME
и в директории gradle для глобальных свойств).можно указывать параметры JVM для запуска gradle (например,
org.gradle.jvmargs=-Xmx4096m
)можно указывать параметры самого Gradle
можно писать свои свойства, которые будут доступны из gradle скриптов
вместо использования gradle.properties, свойства можно передавать через командную строку через параметр
-p
(удобно для ключей, логинов и паролей)
Extra properties
Многие объекты из доменной модели Gradle (в том числе Task и Project) могут содержать дополнительные свойства в виде ключ-значение. Ключ — это строка, а значение — это класс Object.
Репозитории библиотек
Все пакеты публикуются в репозитории, чтобы каждый мог их себе подтянуть. Раньше были популярны JCenter и Google Maven, однако JCenter объявлен как deprecated и сейчас есть три варианта репозиториев:
google()
(Google Maven)mavenCentral()
приватные Maven-репозитории
Ivy
локальная файловая система
Посмотреть, какие версии доступны в публичных репозиториях можно по ссылке:
Например, для пакета org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version
узнать информацию о доступных версиях в открытых репозиториях можно по ссылке: https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-gradle-plugin
Репозитории можно добавлять к build-скрипту или к проектам: первые будут использоваться для плагинов во время фазы конфигурации (они нужны gradle), вторые — во время получения зависимостей проекта (они нужны нашему проекту)
В качестве транспорта можно указывать различные протоколы
Лучше не прописывать все репозитории через allprojects, чтобы не увеличивать время конфигурации
Dependencies
Указывается в проектах и для build-скрипта
Зависимости от внешних модулей:
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.4")
Проектные зависимости:
implementation project(":core")
Файловые зависимости:
implementation fileTree(dir: 'libs', include: ['*.jar'])
Дерево зависимостей можно получить вызовом специально таски dependencies —
./gradlew dependencies [--configuration implementation]
.
Modules
Пример именования:
Configurations
Каждая зависимость должна применяться к определенному скоупу: например, какие-то зависимости должны быть только во время компиляции, только во время тестов, только в runtime и т.д.
Для управления скоупами в gradle используются конфигурации, которые хранятся в ConfigurationContainer
По сути конфигурация — это контейнер для зависимостей
Конфигурации можно создавать и наследоваться от других конфигураций через метод extendsFrom()
Дочерние конфигурации наследуют все зависимости от своих родителей
Обычно конфигурации создаются плагинами
Конфигурации, которые gradle создает по умолчанию (compile и runtime — deprecated) для java gradle plugin:
В java library plugin еще появляется конфигурация api
. Ее отличие от implementation
в том, что она открывает доступ ко всем зависимым модулям (раньше compile
так делал — он открывал доступ ко всем дочерним зависимостям, implementation
так не делает).
compileOnly
— конфигурация подгружает модуль только на этап конфигурации (когда модуль не нужен в runtime). implementation
подгружает модуль в runtime. Грамотное разделение модулей между compileOnly
и implementation
помогает ускорить время сборки проекта.
Resolution strategy
Между зависимостями могут встречаться конфликты
Для каждой конфигурации можно настраивать поведение gradle при конфликтах — через resolution strategy
Поведение по-умолчанию — gradle выбирает последнюю версию библиотеки
Что можно делать через resolution strategy
заменять зависимости (dependencySubstitution)
указать конкретную версию (force)
выбирать проектные зависимости вместо бинарных (preferProjectModules)
падать при конфликтах (failOnVersionConflict)
менять параметры кэширования модулей, которые могут измениться (snapshot)
Transitive dependencies
Транзитивные зависимости — это дочерние зависимости прямой зависимости
У конфигураций есть флаг isTransitive(), если он возвращает true, то будут разрешаться дочерние зависимости
Такой же флаг есть у отдельных модулей
По-умолчанию значение isTransitive()=true, но можно его поменять
Это значит, что мы не подтягиваем зависимости guava к себе в проект, а укажем их вручную.
Dependency constraints
Можно указать ограничения для версий модулей через constraints, включая транзитивные зависимости.
В примере выше httpclient
зависит от commons-codec
, и мы через constraints механизм фиксируем версию транзитивной зависимости.
Excluding transitive dependencies
Можно убрать транзитивные зависимости через exclude (например, у нас две библиотеки тянут одинаковые зависимости; мы можем спокойно убрать из одной копии зависимостей и все будет спокойно собираться):
Because
Вместо комментариев к зависимостям, можно использовать специальное свойство because. При выводе дерева зависимостей, этот комментарий отобразится в консоли :)
Plugins
могут быть написаны скриптом, подключаться как jar (отдельный проект) или быть определены в buildSrc
Добавляют таски, свойства, зависимости, конфигурации
Расширяют DSL и доменную модель
В общем, позволяют делать все, что угодно
Есть встроенные плагины, которые поставляются вместе с gradle. Например, java plugin, java library plugin.
Пример подключения скриптового плагина:
Binary plugins имеют уникальный идентификатор, доступны из репозитория и подключаются через старый синтаксис "apply plugin"
или блок plugins{}
(всегда лучше использовать его).
SourceSets
Java Plugin (или Kotlin Plugin) вносит в доменную модель Gradle такое понятие как source set
Source Sets позволяют группировать ресурсы и исходные файлы в логические группы
Java Plugin создает для каждого Source Set соответствующий таск compileSourceSetJava и несколько конфигураций (для Source Set "main" имя опускается — compileJava)
Аналогично для ресурсов создаются таски processSourceSetResources.
Пример:
Flavors & BuildTypes
Build types — типы сборок, по умолчанию создаются только release и debug, обязательно наличие хотя бы одного типа
Product flavors — разграничивают сборки по фичам (например, платная версия и с урезанной функциональностью)
Build variants — все комбинации между Build types и Product flavors
В доменной модели представлены соответствующими классами: BuildType, ProductFlavor и BaseVariant (содержит BuildType и ProductFlavor)
BuildType и ProductFlavor наследуются от BaseConfigImpl
Для всех них создаются source set'ы и соответствующие таски.
flavor dimension
- под этим можно понимать категорию переменных, некоторый переменный набор. Пример:
productFlavors
- здесь определяются возможные значения для dimension
. Пример:
Здесь: в категории endpoint могут содержатся следующие наборы параметров: prod, staging
Далее, эти значения используется в определении buildTypes (каким образом?)
Kotlin & Groovy DSL
Gradle Kotlin DSL Usage: https://github.com/IkeMurami-Examples/AndroidAppExamples/tree/main/KotlinDSLUsage
buildSrc & Version Catalog
Особая директория Gradle, в которую выносятся:
константы
внешние зависимости и версии
имена проектов и модулей
таски для сборки
плагины для сборки
Пример: https://github.com/IkeMurami-Examples/KotlinMultiplatformExample
Альтернатива этому — использовать Version Catalog (появился в 7-й версии Gradle и работает только с Kotlin DSL) как единое место хранение информации о версиях библиотек. Почитать подробнее: https://habr.com/ru/post/560868/
Для подключения этой фичи, добавляем в settings.gradle.kts
:
и в папке gradle помещаем toml-файл libs.versions.toml
.
Last updated