Поддержка крупного Android- или Kotlin-проекта с десятками или даже сотнями модулей может быстро превратиться в настоящую головную боль. Разные команды отвечают за различные функции, у каждой свои сроки, ограничения и стили создания кода. Со временем логика сборки начинает рассогласовываться: модули по-разному настраивают зависимости, плагины применяются непоследовательно, а обновление версий библиотек или слоев Android SDK напоминает восхождение на Эверест.

Проблема усугубляется при управлении несколькими Android-проектами, которые должны соответствовать одним стандартам. Вышла новая версия Android? Обновился Kotlin? Произошло критическое изменение в библиотеке навигации? Требуется переход от MVVM к MVI? Внезапно приходится обновлять каждый проект и каждый модуль, часто с дублирующейся или скопированной логикой сборки, разбросанной повсюду.

Другое дело, когда конфигурация сборки является централизованной, стандартизированной и многократно используемой. Тогда каждый модуль — будь то Feature-, Core- или App-модуль — может применить один плагин и автоматически следовать конвенциям проекта.

Именно это преимущество и предоставляет инструмент Gradle Convention Plugins.

Примечание: хотя данная статья посвящена Android-разработке, описанный здесь  подход применим к любому проекту Kotlin на основе Gradle, включая Kotlin Multiplatform.

Но разве не для этого нужен buildSrc?

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

Плагины, входящие в Gradle Convention Plugins, решают эту проблему, позволяя компилировать и кэшировать логику один раз, как обычные плагины. Они не вызывают ненужных пересборок, их проще версионировать и тестировать, а также они делают файлы build.gradle.kts чище и модульнее. Короче говоря, Convention Plugins предоставляют все преимущества централизованной логики сборки — без потери производительности, как в случае с buildSrc.

Приступим

Сначала создадим новый каталог с именем build-logic и добавим в него файл settings.gradle.kts:

pluginManagement {
    repositories {
        gradlePluginPortal()
        google()
    }
}

dependencyResolutionManagement {
    repositories {
        google {
            content {
                includeGroupByRegex("com\\.android.*")
                includeGroupByRegex("com\\.google.*")
                includeGroupByRegex("androidx.*")
            }
        }
        mavenCentral()
    }
    versionCatalogs {
        create("libs") {
            from(files("../gradle/libs.versions.toml"))
        }
    }
}

rootProject.name = "build-logic"
include(":convention")  

В этом файле регистрируем модуль convention. Теперь создадим этот модуль и добавим следующее в его файл build.gradle.kts

import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
`kotlin-dsl`
}

group = "com.example.template.buildlogic"

java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

kotlin {
compilerOptions {
jvmTarget = JvmTarget.JVM_17
}
}

dependencies {
compileOnly(libs.android.gradleApiPlugin)
compileOnly(libs.kotlin.gradlePlugin)
compileOnly(libs.compose.gradlePlugin)
// Добавьте любые другие необходимые внешние плагины, например: KSP, Hilt и т. д.
implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) // Чтобы разрешить получение ссылок на версионный каталог в Convention Plugins
}

gradlePlugin {
plugins { // Здесь регистрируем пользовательские плагины
// Android
register("androidApplication") {
id = libs.plugins.template.application.get().pluginId
implementationClass = "ApplicationConventionPlugin"
}
register("androidLibrary") {
id = libs.plugins.template.library.get().pluginId
implementationClass = "LibraryConventionPlugin"
}
// JVM
register("jvm") {
id = libs.plugins.template.jvm.get().pluginId
implementationClass = "JvmConventionPlugin"
}
// Compose
register("androidComposeApplication") {
id = libs.plugins.template.compose.application.get().pluginId
implementationClass = "ComposeApplicationConventionPlugin"
}
register("androidComposeLibrary") {
id = libs.plugins.template.compose.library.get().pluginId
implementationClass = "ComposeLibraryConventionPlugin"
}
// Koin
register("koinCore") {
id = libs.plugins.template.koin.core.get().pluginId
implementationClass = "KoinCoreConventionPlugin"
}
register("koinAndroid") {
id = libs.plugins.template.koin.android.get().pluginId
implementationClass = "KoinAndroidConventionPlugin"
}
// Флейворы
register("FlavorApplication") {
id = libs.plugins.template.flavor.application.get().pluginId
implementationClass = "FlavorApplicationConventionPlugin"
}
register("FlavorLibrary") {
id = libs.plugins.template.flavor.library.get().pluginId
implementationClass = "FlavorLibraryConventionPlugin"
}
// Типы сборки
register("BuildTypeApplication") {
id = libs.plugins.template.buildtype.application.get().pluginId
implementationClass = "BuildTypeApplicationConventionPlugin"
}
register("BuildTypeLibrary") {
id = libs.plugins.template.buildtype.library.get().pluginId
implementationClass = "BuildTypeLibraryConventionPlugin"
}
// Слои архитектуры
register("ArchitectureLayerUIArchitectureLayer") {
id = libs.plugins.template.ui.architecture.layer.get().pluginId
implementationClass = "ArchitectureLayerUIConventionPlugin"
}
register("ArchitectureLayerDomainArchitectureLayer") {
id = libs.plugins.template.domain.architecture.layer.get().pluginId
implementationClass = "ArchitectureLayerDomainConventionPlugin"
}
register("ArchitectureLayerDataArchitectureLayer") {
id = libs.plugins.template.data.architecture.layer.get().pluginId
implementationClass = "ArchitectureLayerDataConventionPlugin"
}
}
}

В блоке dependencies объявим все внешние плагины, от которых будут зависеть Convention Plugins. В данном случае включим плагины для Android-приложений и библиотек, а также плагин для Compose. Если ваш проект использует другие плагины — такие как KSP, Hilt или Firebase — их также следует добавить здесь. Их необходимо объявить в файле build.gradle.kts проекта:

plugins {
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.android.application) apply false
alias(libs.plugins.android.library) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose) apply false
}

В блоке plugins регистрируем все пользовательские Convention Plugins, которые будем определять и повторно использовать в модулях.

Важно: если Convention Plugin указан здесь, но фактически не создан, Gradle завершит сборку с ошибкой. Чтобы каждый Convention Plugin был разрешен и применен, он должен иметь:

  1. Собственную реализацию плагина внутри модуля build-logic.
  1. Соответствующую запись в каталоге версий (libs.versions.toml). 

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

Наконец, не забудьте включить модуль build-logic в корневой файл settings.gradle.kts:

pluginManagement {
includeBuild("build-logic") <- Add here
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}

rootProject.name = "Template"

// Приложение
include(":app")
// Ядро
include(":core:designsystem")
// Функциональные модули
/// Аутентификация
include(":feature:auth:data")
include(":feature:auth:domain")
include(":feature:auth:ui")

Ускорение сборки с помощью настроек производительности Gradle

Чтобы повысить производительность сборки во всем проекте, добавим следующие параметры конфигурации в корневой файл gradle.properties:

# Запуск независимых задач параллельно для ускорения сборки многомодульных проектов
org.gradle.parallel=true
# Повторное использование выходных данных предыдущих сборок, чтобы избежать ненужного повторного выполнения задач
org.gradle.caching=true
# Настройка только необходимых модулей для текущей сборки, сокращение времени конфигурации
org.gradle.configureondemand=true
# Кэширование результата фазы конфигурации, чтобы будущие сборки могли полностью пропустить этот шаг
org.gradle.configuration-cache=true
# Разрешение параллельного выполнения работы кэширования конфигурации для ещё более быстрой настройки
org.gradle.configuration-cache.parallel=true

Создание Convention Plugins для Application- и Library-модулей

Прежде всего нам понадобятся плагины, которые настраивают Application- и Library-модули.

В большинстве проектов есть только один Application-модуль, но использование Convention Plugin подготавливает проект к сценариям, где может потребоваться добавить второй Application-модуль с общими функциями.

Library-модули используются по всему проекту — для основных слоев (дизайн-система, сетевое взаимодействие, база данных и т.д.), а также для функциональных модулей.

Application Convention Plugin

import com.android.build.api.dsl.ApplicationExtension
import com.example.template.configureAndroid
import org.gradle.accessors.dm.LibrariesForLibs
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.the

class ApplicationConventionPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        with(target) {
            val libs = the<LibrariesForLibs>()
            apply(plugin = libs.plugins.android.application.get().pluginId)
            apply(plugin = libs.plugins.kotlin.android.get().pluginId)

            val extension = extensions.getByType<ApplicationExtension>()
            configureAndroid(extension)
        }
    }
}

Library Convention Plugin

import com.android.build.api.dsl.LibraryExtension
import com.example.template.configureAndroid
import org.gradle.accessors.dm.LibrariesForLibs
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.the

class LibraryConventionPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        with(target) {
            val libs = the<LibrariesForLibs>()
            apply(plugin = libs.plugins.android.library.get().pluginId)
            apply(plugin = libs.plugins.kotlin.android.get().pluginId)

            val extension = extensions.getByType<LibraryExtension>()
            configureAndroid(extension)
        }
    }
}

Оба плагина делегируют общую конфигурацию функции configureAndroid, определенной в ConfigureAndroid.kt. Эта функция получает соответствующий метод расширения commonExtension (ApplicationExtension для приложений и LibraryExtension для библиотек).

Внутри централизуем ключевую конфигурацию Android: compileSdkminSdk и (только для Application-модулей) targetSdk.

Также вызываем две дополнительные функции конфигурации: configureJavaAndroid (из ConfigureJava.kt) и configureKotlin (из ConfigureKotlin.kt).

package com.example.template

import com.android.build.api.dsl.ApplicationExtension
import com.android.build.api.dsl.CommonExtension
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension

internal fun Project.configureAndroid(
    commonExtension: CommonExtension<*, *, *, *, *, *>,
) {
    commonExtension.apply {
        compileSdk = 36
        defaultConfig.minSdk = 24

        if (this is ApplicationExtension) {
            defaultConfig.targetSdk = 36
        }
    }

    configureJavaAndroid(commonExtension)
    configureKotlin<KotlinAndroidProjectExtension>()
}

Файл ConfigureJava.kt содержит две функции: configureJavaAndroid, которую  используем в Android-модулях, и configureJavaJvm, предназначенную для чистых Kotlin-модулей без Android-зависимостей — например, модулей Domain-слоя, содержащих варианты использования, интерфейсы репозиториев и модели.

package com.example.template

import com.android.build.api.dsl.CommonExtension
import org.gradle.api.JavaVersion
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.kotlin.dsl.configure

private val javaVersion = JavaVersion.VERSION_11

internal fun Project.configureJavaAndroid(commonExtension: CommonExtension<*, *, *, *, *, *>) {
    commonExtension.compileOptions {
        sourceCompatibility = javaVersion
        targetCompatibility = javaVersion
    }
}

internal fun Project.configureJavaJvm() {
    extensions.configure<JavaPluginExtension> {
        sourceCompatibility = javaVersion
        targetCompatibility = javaVersion
    }
}

Файл ConfigureKotlin.kt содержит единственную функцию, отвечающую за применение конфигурации компилятора Kotlin:

package com.example.template

import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinBaseExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension

internal inline fun <reified T : KotlinBaseExtension> Project.configureKotlin() = configure<T> {
    when (this) {
        is KotlinAndroidProjectExtension -> compilerOptions
        is KotlinJvmProjectExtension -> compilerOptions
        else -> throw Exception("Unsupported Kotlin project type")
    }.apply {
        jvmTarget.set(JvmTarget.JVM_11)
    }
}

Теперь объявим Convention Plugins в каталоге версий, чтобы Gradle мог их разрешать:

template-application = { id = "template.application" }
template-library = { id = "template.library" }

После добавления Convention Plugins в каталог регистрируем их в блоке gradlePlugin файла build-logic модуля build.gradle.kts:

gradlePlugin {
plugins {
// Android
register("androidApplication") {
id = libs.plugins.template.application.get().pluginId
implementationClass = "ApplicationConventionPlugin"
}
register("androidLibrary") {
id = libs.plugins.template.library.get().pluginId
implementationClass = "LibraryConventionPlugin"
}

.....
}
}

Создание Convention Plugins для чистого Kotlin

Как упоминалось ранее, некоторые модули в данном проекте являются чисто Kotlin-модулями — например, Domain-модули, содержащие варианты использования, интерфейсы репозиториев и модели.

Для работы с ними создадим JvmConventionPlugin:

import com.example.template.configureJavaJvm
import com.example.template.configureKotlin
import org.gradle.accessors.dm.LibrariesForLibs
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.the
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension

class JvmConventionPlugin : Plugin<Project> {
    override fun apply(target: Project) {

        with(target) {
            val libs = the<LibrariesForLibs>()
            apply(plugin = libs.plugins.kotlin.jvm.get().pluginId)

            configureJavaJvm()
            configureKotlin<KotlinJvmProjectExtension>()
        }
    }
}

Типы сборки

Наш проект требует настройки типов сборки, поэтому создадим Convention Plugins для управления ими. Нам понадобятся два отдельных плагина: один для Application-модулей и один для Library-модулей. Оба плагина будут использовать общую функцию конфигурации, которая получает commonExtension для централизации общих настроек.

Convention Plugin для сборки App-модулей

import com.android.build.api.dsl.ApplicationExtension
import com.example.template.configureBuildType
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.getByType

class BuildTypeApplicationConventionPlugin : Plugin<Project> {
    override fun apply(target: Project) {

        with(target) {
            val extension = extensions.getByType<ApplicationExtension>()
            configureBuildType(extension)
        }
    }
}

Convention Plugin для сборки Library-модулей

import com.android.build.api.dsl.LibraryExtension
import com.example.template.configureBuildType
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.getByType

class BuildTypeLibraryConventionPlugin : Plugin<Project> {
    override fun apply(target: Project) {

        with(target) {
            val extension = extensions.getByType<LibraryExtension>()
            configureBuildType(extension)
        }
    }
}

Общая конфигурация для типов сборки

При настройке существующих типов сборки, таких как debug и release, важно использовать функцию named:

package com.example.template

import com.android.build.api.dsl.CommonExtension
import org.gradle.api.Project

internal fun Project.configureBuildType(
    commonExtension: CommonExtension<*, *, *, *, *, *>,
) {
    commonExtension.apply {
        buildTypes {
            named("debug") {
                isMinifyEnabled = false
            }
            named("release") {
                isMinifyEnabled = true
                proguardFiles(
                    getDefaultProguardFile("proguard-android-optimize.txt"),
                    "proguard-rules.pro"
                )
            }
        }
    }
}

Функция named безопасно ссылается на существующие типы сборки без их переопределения. Если потребуется добавить новый пользовательский тип сборки, необходимо использовать функцию register:

buildTypes {
register("staging") {
isMinifyEnabled = false
}
}

Флейворы сборки

Наш проект также требует настройки продуктовых флейворов, поэтому создадим Convention Plugins для управления ими. Как и в случае с типами сборки, создадим два плагина: один для Application-модулей и один для Library-модулей, оба использующие общую функцию конфигурации через commonExtension.

Convention Plugin для флейворов Application

import com.android.build.api.dsl.ApplicationExtension
import com.example.template.configureFlavor
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.getByType

class FlavorApplicationConventionPlugin : Plugin<Project> {
    override fun apply(target: Project) {

        with(target) {
            val extension = extensions.getByType<ApplicationExtension>()
            configureFlavor(extension)
        }
    }
}

Convention Plugin для флейворов Library

import com.android.build.api.dsl.LibraryExtension
import com.example.template.configureFlavor
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.getByType

class FlavorLibraryConventionPlugin : Plugin<Project> {
    override fun apply(target: Project) {

        with(target) {
            val extension = extensions.getByType<LibraryExtension>()
            configureFlavor(extension)
        }
    }
}

Общая конфигурация для флейворов

package com.example.template

import com.android.build.api.dsl.CommonExtension
import org.gradle.api.Project

private data class Dimension(val name: String, val values: List<String>)

private val environmentDimension = Dimension(
    name = "environment",
    values = listOf("dev", "prod"),
)

internal fun Project.configureFlavor(
    commonExtension: CommonExtension<*, *, *, *, *, *>,
) {
    commonExtension.apply {
        flavorDimensions += environmentDimension.name

        productFlavors {
            environmentDimension.values.forEach { value ->
                register(value) {
                    dimension = environmentDimension.name
                }
            }
        }
    }
}

Compose

Теперь создадим Compose Convention Plugins. Как и в предыдущих конфигурациях, понадобятся два плагина — один для Application-модулей и один для Library-модулей — плюс общая функция конфигурации.

Compose Convention Plugin для Application

В этом плагине добавляем зависимости, необходимые для действий Compose (Compose Activities). Следуя принципу Single-Activity, не включаем их в плагин для модулей Library.

Примечание: если приложению требуются Activities внутри Library-модулей, можно перенести эти зависимости в общую конфигурацию.

import com.android.build.api.dsl.ApplicationExtension
import com.example.template.configureCompose
import org.gradle.accessors.dm.LibrariesForLibs
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.the

class ComposeApplicationConventionPlugin : Plugin<Project> {
    override fun apply(target: Project) {

        with(target) {
            val libs = the<LibrariesForLibs>()
            apply(plugin = libs.plugins.kotlin.compose.get().pluginId)

            val extension = extensions.getByType<ApplicationExtension>()
            configureCompose(extension)

            dependencies {
                "implementation"(libs.androidx.activity.compose)
            }
        }
    }
}

Compose Convention Plugin для Library

import com.android.build.api.dsl.LibraryExtension
import com.example.template.configureCompose
import org.gradle.accessors.dm.LibrariesForLibs
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.the

class ComposeLibraryConventionPlugin : Plugin<Project> {
    override fun apply(target: Project) {

        with(target) {
            val libs = the<LibrariesForLibs>()
            apply(plugin = libs.plugins.kotlin.compose.get().pluginId)

            val extension = extensions.getByType<LibraryExtension>()
            configureCompose(extension)
        }
    }
}

Общая конфигурация для Compose

Стандартная конфигурация обрабатывает общую настройку Compose, включая Compose BOM и другие зависимости, загружаемые из каталога версий:

package com.example.template

import com.android.build.api.dsl.CommonExtension
import org.gradle.accessors.dm.LibrariesForLibs
import org.gradle.api.Project
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.the

internal fun Project.configureCompose(
    commonExtension: CommonExtension<*, *, *, *, *, *>,
) {
    commonExtension.apply {
        val libs = the<LibrariesForLibs>()

        buildFeatures {
            compose = true
        }

        dependencies {
            "implementation"(platform(libs.androidx.compose.bom))
            "implementation"(libs.androidx.compose.ui)
            "implementation"(libs.androidx.compose.ui.graphics)
            "implementation"(libs.androidx.compose.ui.tooling.preview)
            "implementation"(libs.androidx.compose.material3)
            "androidTestImplementation"(platform(libs.androidx.compose.bom))
            "androidTestImplementation"(libs.androidx.compose.ui.test.junit4)
            "debugImplementation"(libs.androidx.compose.ui.tooling)
            "debugImplementation"(libs.androidx.compose.ui.test.manifest)
        }
    }
}

Koin

Далее настроим внедрение зависимостей с помощью Koin. В этом случае создадим два Convention Plugins, разделенных не по типу Application-модули / Library-модули, а по типу Android-модули / Core-модули (чистый Kotlin).

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

Koin Android Convention Plugin

import org.gradle.accessors.dm.LibrariesForLibs
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.the

class KoinAndroidConventionPlugin : Plugin<Project> {
    override fun apply(target: Project) {

        with(target) {
            val libs = the<LibrariesForLibs>()

            dependencies {
                "implementation"(project.dependencies.platform(libs.koin.bom))
                "implementation"(libs.koin.android)
                "testImplementation"(libs.koin.android.test)
                "testImplementation"(libs.koin.junit4)
            }
        }
    }
}

Koin Core Convention Plugin

import org.gradle.accessors.dm.LibrariesForLibs
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.the

class KoinCoreConventionPlugin : Plugin<Project> {
    override fun apply(target: Project) {

        with(target) {
            val libs = the<LibrariesForLibs>()

            dependencies {
                "implementation"(project.dependencies.platform(libs.koin.bom))
                "implementation"(libs.koin.core)
                "testImplementation"(libs.koin.test)
                "testImplementation"(libs.koin.junit4)
            }
        }
    }
}

Каталог версий

Не забудьте объявить все необходимые зависимости, используемые вашими Convention Plugins, в каталоге версий (libs.versions.toml).

[versions]
agp = "8.13.0"
kotlin = "2.2.21"
kotlin-serialization-json = "1.9.0"
coreKtx = "1.17.0"
junit = "4.13.2"
junitVersion = "1.3.0"
espressoCore = "3.7.0"
lifecycleRuntimeKtx = "2.10.0"
composeActivity = "1.12.0"
composeBom = "2025.11.01"
ksp = "2.2.20-2.0.4"
koin-bom = "4.1.1"
ktor-bom = "3.3.2"
navigation = "2.9.6"

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }

# Kotlin
kotlin-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlin-serialization-json" }

# Compose
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "composeActivity" }
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }

# Koin
koin-bom = { module = "io.insert-koin:koin-bom", version.ref = "koin-bom" }
koin-core = { module = "io.insert-koin:koin-core" }
koin-android = { module = "io.insert-koin:koin-android" }
koin-android-compose = { module = "io.insert-koin:koin-androidx-compose" }
koin-android-compose-navigation = { module = "io.insert-koin:koin-androidx-compose-navigation" }
koin-test = { module = "io.insert-koin:koin-test" }
koin-android-test = { module = "io.insert-koin:koin-android-test" }
koin-junit4 = { module = "io.insert-koin:koin-test-junit4" }

# Ktor
ktor-bom = { module = "io.ktor:ktor-bom", version.ref = "ktor-bom" }
ktor-core = { module = "io.ktor:ktor-client-core" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio" }
ktor-client-logging = { module = "io.ktor:ktor-client-logging" }
ktor-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation" }
ktor-serialization-json = { module = "io.ktor:ktor-serialization-kotlinx-json" }

# Зависимости включенного build-logic
android-gradleApiPlugin = { group = "com.android.tools.build", name = "gradle-api", version.ref = "agp" }
compose-gradlePlugin = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin" }
kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" }
ksp-gradlePlugin = { group = "com.google.devtools.ksp", name = "com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }

# Плагины включенного build-logic
template-application = { id = "template.application" }
template-library = { id = "template.library" }
template-jvm = { id = "template.jvm" }
template-kotlin-serialization = { id = "template.kotlin.serialization" }
template-compose-application = { id = "template.compose.application" }
template-compose-library = { id = "template.compose.library" }
template-koin-core = { id = "template.koin.core" }
template-koin-android = { id = "template.koin.android" }
template-flavor-application = { id = "template.flavor.application" }
template-flavor-library = { id = "template.flavor.library" }
template-buildtype-application = { id = "template.buildtype.application" }
template-buildtype-library = { id = "template.buildtype.library" }
template-ui-architecture-layer = { id = "template.ui.architecture.layer" }
template-data-architecture-layer = { id = "template.data.architecture.layer" }
template-domain-architecture-layer = { id = "template.domain.architecture.layer" }

Применение плагинов к модулям

Теперь, когда все Convention Plugins определены, можно применить их к Application-модулю.

Например, можно перенести существующую настройку из:

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
}

android {
namespace = "com.example.template"
compileSdk = 36

defaultConfig {
applicationId = "com.example.template"
minSdk = 24
targetSdk = 36
versionCode = 1
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
buildFeatures {
compose = true
}
}

dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.compose.material3)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.ui.test.manifest)
}

(Примечание: в данном примере конфигурация для вариантов флейворов и настроек Kotlin опущена для краткости, поэтому реальный файл build.gradle.kts будет еще больше)

в новую настройку Convention Plugin:

plugins {
    alias(libs.plugins.template.application)
    alias(libs.plugins.template.compose.application)
    alias(libs.plugins.template.flavor.application)
    alias(libs.plugins.template.buildtype.application)
    alias(libs.plugins.template.koin.android)
}

android {
    namespace = "com.example.template"

    defaultConfig {
        applicationId = "com.example.template"
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }
}

Использование Convention Plugins централизует логику сборки, сокращает дублирование и значительно упрощает будущие обновления между модулями.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Oliver Vicente: Stop Copy-Pasting Build Logic: Use Gradle Convention Plugins Instead I

Предыдущая статьяПочему каждый разработчик должен потерпеть неудачу (хотя бы раз)