Что такое DSL?
DSL (Domain-Specific Language) — это язык программирования, который спроектирован и оптимизирован для решения задач в конкретной области или для определенного класса задач. DSL build.gradle предоставляет разработчикам удобный способ определения настроек проекта и управления ими, используя специфический синтаксис, который Gradle понимает и обрабатывает. Этот DSL позволяет создавать мощные и гибкие сценарии сборки, которые могут быть легко настраиваемы для разных проектов и задач.
С Android Studio Giraffe Kotlin DSL становится новым стандартом для Gradle-скриптов в разработке Android. Когда вы создаете новые проекты, используя встроенные шаблоны IDE, вам будут предоставлены файлы Kotlin DSL вместо файлов Gradle на основе Groovy.
Это предстоящее изменение обеспечило возможность переноса конфигураций Gradle на основе Groovy DSL на Kotlin DSL. Перевод файлов Gradle с Groovy на Kotlin DSL может значительно улучшить рабочий процесс разработки для Android. Особенно если вы уже знакомы с Kotlin. Такой переход на единый знакомый язык не только повышает вашу производительность, но и устраняет необходимость переключаться между двумя языками для выполнения задач разработки и настройки. Надежность и интуитивность Kotlin DSL дает уверенность в создании пользовательских задач Gradle без необходимости прибегать к зачастую нечеткому синтаксису Groovy.
Статья задумана, как руководство, которое поможет вам на этапе перехода на Kotlin DSL. Материал написан с акцентом на проекты Android, но обсуждаемые моменты могут быть применимы и к другим проектам на основе Gradle, таким как приложение Spring Boot.
В первом разделе мы кратко опишем процесс перехода с Groovy на Kotlin DSL в целом. Далее будут перечислены все этапы миграции, через которые прошла наша команда.
Процесс миграции
Обычная структура одномодульных проектов Android в большинстве случаев имеет схожую структуру. Конечно, проект может использовать некоторые дополнительные конфигурационные файлы Gradle, но в целом, процесс миграции одинаков.
То же самое касается многомодульного проекта, где у вас есть отдельный build.gradle
для каждого из модулей.
Исходя из этой структуры, мы можем определить три файла Gradle на основе groovy, которые мы хотим преобразовать в Kotlin DSL:
settings.gradle: Файл
settings.gradle
отвечает за настройку и определение структуры модуля в проекте Android. Он расположен в корневом каталоге проекта и играет решающую роль в организации процесса сборки и управлении им.build.gradle на уровне проекта (Project-level): Файл
build.gradle
на уровне проекта также находится в корневом каталоге и отвечает за настройку конфигураций для процесса сборки всего проекта. Он определяет глобальные настройки, репозитории и зависимости, которые применяются ко всем модулям в проекте.build.gradle на уровне модуля (Module-level): Внутри каждого отдельного модуля в проекте Android (например, модуля приложения) находится файл
build.gradle
. Этот файл отвечает за настройку параметров сборки, зависимостей и отвечает за поведение этого конкретного модуля.
Исходя из этой файловой структуры Gradle, структура миграции была следующей:
Перенести файл
settings.gradle
в файлsettings.gradle.kts
Перенести файл
build.gradle
на уровне проекта в файлbuild.gradle.kts
Перенести файлы
build.gradle
на уровне модуля в файлыbuild.gradle.kts.
Таким образом, у вас будет меньше всего конфликтов. Кроме того, часто помогает закомментировать разделы на каждом этапе миграции, где Kotlin DSL еще не применен, потому что у вас еще не было успешной сборки. Как только проект будет собран с использованием преобразованного файла, подключится поддержка IDE, что позволит быстро устранять ошибки, не пытаясь угадать название свойства Groovy в Kotlin DSL.
Миграционная энциклопедия
Большинство проектных структур очень похожи. Даже при сложных настройках Gradle вы столкнетесь с одинаковыми проблемами в процессе миграции. Ниже приведен список проблем при миграции, на которые мы наткнулись, и способы их решения. В каждом подразделе объясняем, в чем заключается назначение фрагмента Gradle Groovy и как выглядит перенесенный код Kotlin DSL.
Если вы обнаружите какие-либо недостающие части, которые могут быть актуальны для более широкой аудитории, не стесняйтесь оставлять комментарий, и мы добавим их в статью.
Замена одинарных кавычек двойными
Для использования Kotlin DSL нужно убедиться, что используются двойные кавычки (") вместо одинарных кавычек (') для наших строк. Поэтому просмотрите файлы и поищите одинарные кавычки. Вы можете ускорить этот процесс, используя функцию поиска IntelliJ (Mac ⌘ + F; Windows Ctrl + F) или непосредственно функцию замены (Mac ⌘ + R; Windows Ctrl + R).
// build.gradle (Module-level)
implementation 'androidx.core:core-ktx:1.10.1'
При переходе на Kotlin это будет выглядеть так.
// build.gradle.kts (Module-level)
implementation("androidx.core:core-ktx:1.10.1")
Репозиторий плагинов Gradle
Блок repositories используется для определения репозиториев, из которых могут быть взяты зависимости. Для каждого из наших репозиториев мы используем вызовы функций. Однако, объявление репозиториев в Kotlin DSL очень похоже на Groovy. Взгляните на следующее:
// settings.gradle
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
Для конвертации в Kotlin практически не требуется никаких модификаций, потому что команды в этом разделе используют тот же синтаксис, что и Groovy.
// settings.gradle.kts
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
Идентификатор плагина Gradle
Обычно разработчики объявляют идентификатор плагина Gradle, который они хотят использовать на уровне проекта, и заводят его на уровне модуля в виде такого плагина DSL:
// build.gradle (Project-level)
plugins {
id 'com.android.application' version '8.1.1' apply false
id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
id 'com.android.library' version '8.1.1' apply false
}
// build.gradle (Module-level)
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
При переходе на Kotlin это будет выглядеть так:
// build.gradle.kts (Project-level)
plugins {
id("com.android.application") version "8.1.1" apply false
id("org.jetbrains.kotlin.android") version "1.8.10" apply false
}
// build.gradle.kts (Module-level)
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
Плагин Gradle, который все еще использует устаревшее приложение плагина
Если у вас есть плагин Gradle, который использует устаревшее приложение плагина, рекомендуется перейти на плагин DSL. Возьмем, к примеру, плагин Google Play Services Gradle, который существует в проекте уже очень давно:
// build.gradle (Project-level)
buildscript {
dependencies {
classpath "com.google.gms:google-services:4.3.15"
}
}
// build.gradle (Module-level)
apply plugin: "com.google.gms.google-services"
Вместо этого измените его на Plugin DSL:
// build.gradle (Project-level)
plugins {
id 'com.google.gms:google-services' version '4.3.15' apply false
}
// build.gradle (Module-level)
plugins {
id 'com.google.gms.google-services'
}
На Kotlin это будет выглядеть так:
// build.gradle.kts (Project-level)
plugins {
id("com.google.gms:google-services") version "4.3.15" apply false
}
// build.gradle (Module-level)
plugins {
id("com.google.gms.google-services")
}
Сокращенный идентификатор плагина и функция расширения плагина Kotlin
Некоторые плагины gradle могут быть объявлены сокращенно, например, плагин gradle Kotlin:
// Namespaced Plugin ID
id("org.jetbrains.kotlin.android")
// Shorthand Plugin ID
id("kotlin-android")
Сокращенный |
Полностью |
kotlin |
org.jetbrains.kotlin.jvm |
kotlin-android |
org.jetbrains.kotlin.android |
kotlin-kapt |
org.jetbrains.kotlin.kapt |
kotlin-parcelize |
org.jetbrains.kotlin.plugin.parcelize |
А для Kotlin DSL также предусмотрена функция расширения для плагина Gradle.
// build.gradle.kts (Project-level)
plugins {
kotlin("jvm") version "1.9.10" apply false
kotlin("android") version "8.1.1" apply false
kotlin("plugin.parcelize") version "1.9.10" apply false
}
// build.gradle.kts (Module-level)
plugins {
kotlin("jvm")
kotlin("android")
kotlin("plugin.parcelize")
}
Но функции расширения для Android нет.
Миграции extra variables
Extra variables (дополнительные переменные) обычно означают пользовательские переменные, которые могут быть определены и использованы в файле build.gradle
. Эти переменные позволяют передавать пользовательские параметры или значения в ваш сценарий сборки, что делает сценарии более гибкими и настраиваемыми. Например, вы можете определить такие переменные в начале файла build.gradle
:
// build.gradle (Project-level)
buildscript {
ext {
minSdk = 26
targetSdk = 34
}
}
// build.gradle (Module-level)
android {
defaultConfig {
minSdk rootProject.minSdk
targetSdk rootProject.targetSdk
}
}
На Kotlin это будет выглядеть так:
// build.gradle.kts (Project-level)
buildscript {
extra.apply {
set("minSdk", 26)
set("targetSdk", 34)
}
}
// build.gradle.kts (Module-level)
android {
defaultConfig {
minSdk = rootProject.extra["minSdk"] as? Int?
targetSdk = rootProject.extra["targetSdk"] as? Int?
}
}
Репозиторий зависимостей в Gradle
Репозиторий зависимостей в Gradle — это место, где Gradle ищет и загружает зависимости (библиотеки и пакеты), которые необходимы для сборки проекта. Gradle поддерживает различные типы репозиториев, такие как локальные репозитории, удаленные репозитории (например, Maven Central), и пользовательские репозитории.
// settings.gradle
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
Так что его можно сразу переключить на Kotlin без каких-либо дополнительных усилий:
// settings.gradle.kts
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
Включение модулей Gradle
В ваш settings.gradle
включаете свой модуль Gradle. Для проекта с одним модулем — это только модуль приложения. Если вы переносите многомодульный проект, вам следует перенести все включенные в него модули. Для settings.gradle
вы можете преобразовать include
следующим образом:
// settings.gradle
include ':app'
На Kotlin это будет выглядеть так:
// settings.gradle.kts
include(":app")
Назначение и конфигурация Android по умолчанию
При настройке параметров в Groovy у нас был синтаксис parameterName
value
. С помощью Kotlin DSL мы можем просто использовать оператор =
между этими двумя значениями, чтобы присвоить значение параметру, если базовый код предоставляет изменяемую переменную.
Для конфигурации Android и defaultConfig
результирующий скрипт можно найти в следующем примере фрагмента миграции:
// build.gradle (Module-level)
android {
namespace 'com.my.project'
compileSdk 34
defaultConfig {
applicationId "com.my.project"
minSdk 24
targetSdk 34
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
}
/* ... */
}
При переходе в Kotlin это выглядит так:
// build.gradle.kts (Module-level)
android {
namespace = "com.my.project"
compileSdk = 34
defaultConfig {
applicationId = "com.my.project"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
/* ... */
}
Определение типов сборки
В Gradle блок buildTypes
можно использовать для определения различных конфигураций сборки для проекта Android. Каждый тип сборки представляет собой определенный вариант вашего приложения, такой как debug
или release
, со своим собственным набором параметров конфигурации.
Блок buildTypes
позволяет настраивать различные аспекты сборок: включение или отключение отладки, включение сокращения кода и ресурсов, указание правил ProGuard, настройку имен выходных файлов и назначение конфигураций подписи.
Чтобы создать новый тип сборки с помощью Kotlin DSL аналогично созданию вариантов, мы можем использовать create(..)
.
// build.gradle (Module-level)
android {
buildTypes {
debug { /* ... */ }
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
/* ... */
}
googlePlay { /* ... */ }
galaxyStore { /* ... */ }
appGallery { /* ... */ }
}
}
При переходе в Kotlin это выглядит так:
// build.gradle.kts (Module-level)
android {
buildTypes {
debug { /* ... */ }
release {
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
signingConfig = signingConfigs.getByName("release")
/* ... */
}
create("googlePlay") { /* ... */ }
create("galaxyStore") { /* ... */ }
create("appGallery") { /* ... */ }
}
}
Добавление product flavors
Product flavors — это удобная функция для определения конкретных типов вашего приложения, в котором можно устанавливать различные маршруты для внутреннего сервера или других конфигураций, отличающихся от вашей производственной сборки.
Давайте взглянем на пример миграции для определения компонента с именем dev
. Обратите внимание, как мы используем функцию .add(..)
вместо прямого вызова функции flavorDimensions, поскольку базовым значением являетсяMutableList<String>.
Теперь для создания flavors мы должны использовать синтаксис create("ourFlavorName")
вместо прямого объявления имени перед блоком конфигурации.
Кроме того, чтобы добавить базовый URL, поскольку теперь нам приходится использовать двойные кавычки, нужно экранировать внутренние двойные кавычки нашей строки URL с помощью "\
.
// build.gradle (Module-level)
android {
flavorDimensions = ['default', 'type', 'store']
buildTypes {
dev {
dimension = 'type'
applicationId 'dev.com.my.project'
buildConfigField 'String', 'BASE_URL', '"https://api.devserver.com"'
buildConfigField 'Boolean', 'ANALYTICS_ENABLED', 'true'
/* ... */
}
production {
dimension "default"
/* ... */
}
}
}
Для создания flavors
используем синтаксис create("ourFlavorName")
вместо прямого объявления имени перед блоком конфигурации.
// build.gradle.kts (Module-level)
android {
flavorDimensions += listOf("default", "type", "store")
buildTypes {
create("dev") {
dimension = "app"
applictionId = "dev.com.my.project"
buildConfigField("String", "BASE_URL", "\"https://api.devserver.com\"")
/* ... */
}
create("production") {
dimension = "default"
/* ... */
}
}
}
Зависимости модулей
В нашем проекте существует несколько способов объявления зависимостей:
classpath
, используется для объявления зависимостей для самого процесса сборки. Он используется в блокеbuildscript
в файлеbuild.gradle
на уровне проекта.Конфигурация
implementation
отвечает за объявление зависимостей, которые фактически используются и в нашем коде и во время выполнения.Другим распространенным ключевым словом является
platform,
которое используется при объявлении зависимостей через спецификацию, из Firebase или Jetpack Compose.При использовании процессоров аннотаций, таких как KAPT или KSP, мы также объявляем соответствующие зависимости в коде, используя ключевые слова
kapt
иksp.
При включении файлов из каталога, такого как libs, мы можем использовать ключевое слово
fileTree.
Ниже показан фрагмент кода для всех упомянутых ключевых слов:
// build.gradle (Module-level)
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation project(":library")
implementation 'androidx.core:core-ktx:1.10.1'
implementation platform('androidx.compose:compose-bom:2023.03.00')
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
debugImplementation 'androidx.compose.ui:ui-tooling'
betaDebugImplementation 'com.github.chuckerteam.chucker:library:4.0.0'
kapt "androidx.room:room-compiler:2.5.2"
ksp "androidx.room:room-compiler:2.5.2"
}
В этой форме его можно преобразовать в Kotlin:
// build.gradle.kts (Module-level)
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
implementation(project(":library"))
implementation("androidx.core:core-ktx:1.10.1")
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
debugImplementation("androidx.compose.ui:ui-tooling")
"betaDebugImplementation"("com.github.chuckerteam.chucker:library:4.0.0")
kapt("androidx.room:room-compiler:2.5.2")
ksp("androidx.room:room-compiler:2.5.2")
}
Настройки подписи
Конфигурация подписи, которая используется для настройки различных значений хранилища ключей для подписи приложений:
// build.gradle (Module-level)
android {
signingConfigs {
release {
storeFile file("store_file.jks")
keyAlias properties['key_alias']
keyPassword properties['key_password']
storePassword properties['store_password']
}
}
}
При переходе в Kotlin это выглядит так:
// build.gradle.kts (Module-level)
android {
signingConfigs {
create("release") {
storeFile = file(properties.getProperty("store_file..jks"))
storePassword = properties.getProperty("store_password")
keyAlias = properties.getProperty("key_alias")
keyPassword = properties.getProperty("key_password")
}
}
}
Параметры компиляции
Параметры компиляции, которые используются для настройки различных значений во время компиляции:
// build.gradle (Module-level)
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
При переходе в Kotlin это выглядит так
// build.gradle.kts (Module-level)
android {
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}
Блок KotlinOptions
KotlinOptions — это параметр, используемый в файле настроек сборки Gradle для проектов на языке программирования Kotlin. Он позволяет настраивать параметры компиляции и выполнения кода на Kotlin внутри вашего проекта Android.
// build.gradle (Module-level)
android {
kotlinOptions {
jvmTarget = "17"
}
}
Его можно сразу конвертировать в Kotlin, ничего не меняя:
// build.gradle.kts (Module-level)
android {
kotlinOptions {
jvmTarget = "17"
}
}
Features сборки
В функции сборки вы можете включить такие функции, как ViewBinding, RenderScript или Compose.
// build.gradle (Module-level)
android {
buildFeatures {
viewBinding true
dataBinding true
compose true
}
}
При переходе в Kotlin это выглядит так:
// build.gradle.kts (Module-level)
android {
buildFeatures {
viewBinding = true
dataBinding = true
compose = true
}
}
Исключение ресурсов из вариантов упаковки
В некоторых случаях необходимо исключить определенные ресурсы из окончательного пакета приложений. Для этой цели вы можете использовать packagingOptions
, чтобы добавить правила исключения в раздел resources
.
// build.gradle (Module-level)
android {
packaging {
resources.excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
При переходе в Kotlin это выглядит так:
// build.gradle.kts (Module-level)
android {
packaging {
resources {
excludes += setOf("/META-INF/{AL2.0,LGPL2.1}")
}
}
}
Настройка параметров тестирования
Для адаптации конфигурации наших модульных тестов мы можем использовать блок UnitTests, включенный в блок testOptions
. Здесь единственное, что нам нужно изменить— это включить Android Resources в isIncludeAndroidResources
, как вы можете видеть в следующих фрагментах:
// build.gradle (Module-level)
android {
testOptions {
unitTests {
includeAndroidResources true
returnDefaultValues true
}
animationsDisabled true
}
}
При переходе в Kotlin это выглядит так:
// build.gradle.kts (Module-level)
android {
testOptions {
unitTests {
isIncludeAndroidResources = true
isReturnDefaultValues = true
}
animationsDisabled = true
}
}
Вывод
Несмотря на доминирование Groovy в настройке скриптов Gradle и его сходство с Java, с которым должны быть знакомы многие разработчики Android или JVM, очень немногие изучают его полностью. Большую часть времени скрипты Gradle полагаются на фрагменты копирования-вставки из соответствующих документов framework, ответов StackOverflow или других источников, что не всегда может обеспечить наиболее оптимальное решение. Таким образом, переход на Kotlin DSL — это не только практическое изменение, но и шаг к улучшению сопровождаемости и читаемости вашего кода.
Это руководство основано на собственном процессе миграции и направлено на то, чтобы охватить широкий спектр сценариев, с которыми вы можете столкнуться во время вашего процесса миграции. Если вы знакомы с ситуацией, которая здесь не описана, то не стесняйтесь поделиться ею в комментариях, чтобы мы смогли обновить руководство.
Благодарим за внимание!
Rusrst
Не освещен момент того, что в случае если есть кастомные плагины, то их может понадобиться переносить в build-logic module. От AGP scope кажется, но я на память не помню.
PPR Автор
Благодарим за ваш комментарий, это довольно обширная тема, возможно, в будущем подробно рассмотрим данный вопрос в отдельной статье.