Загрузка
Все статьи

Kapt vs KSP — почему компиляция Kotlin-проекта тормозит и как починить

Время сборки Kotlin-проекта выросло с 30 секунд до 4 минут. Причина — kapt и annotation processing. KSP (Kotlin Symbol Processing) призван это исправить, но миграция не тривиальна. Рассказываю, как мы ускорили сборку в 5 раз, заменив kapt на KSP в реальном проекте с Room, Moshi и Dagger.

Помните, когда сборка проекта занимала 30 секунд и вы даже не замечали? Потом добавили Room для кэширования, Moshi для сериализации, Dagger Hilt для DI — и вдруг ./gradlew build занимает 4 минуты. Каждую итерацию. На каждом push в feature-ветку.

Я точно помню момент, когда понял, что это ненормально: наша CI pipeline бежала 12 минут, из которых 7 — сборка. Команда из 8 разработчиков теряла в сумме больше часа каждый день только на ожидание сборки. Это боль, о которой редко говорят, потому что «сборка тормозит — это нормально для Android».

Но это не нормально. И причина в 90% случаев — kapt.

Что такое kapt и почему он медленный

kapt (Kotlin Annotation Processing Tool) — это bridge между Kotlin и Java annotation processing. Проблема в том, что annotation processing в Java-мире работает с Java AST (Abstract Syntax Tree). Kotlin-код нужно сначала скомпилировать в Java-stub'ы, потом прогнать через Java annotation processor, потом результат подмешать обратно в Kotlin-компиляцию.

Это значит, что каждый annotation processor запускает отдельный проход компиляции. Три processor'а — три дополнительных прохода. Плюс генерация stub'ов. Плюс тип-resolution. Всё это суммируется.

Вот что у нас было в build.gradle.kts:

plugins {
    id("com.android.application")
    kotlin("android")
    id("com.google.dagger.hilt.android")
    kotlin("kapt")
}

dependencies {
    // Hilt
    implementation("com.google.dagger:hilt-android:2.50")
    kapt("com.google.dagger:hilt-android-compiler:2.50")

    // Room
    implementation("androidx.room:room-runtime:2.6.1")
    implementation("androidx.room:room-ktx:2.6.1")
    kapt("androidx.room:room-compiler:2.6.1")

    // Moshi
    implementation("com.squareup.moshi:moshi-kotlin:1.15.1")
    kapt("com.squareup.moshi:moshi-kotlin-codegen:1.15.1")
}

kapt {
    correctErrorTypes = true
    useBuildCache = true
    mapDiagnosticLocations = true
}

Три kapt-зависимости. Три отдельных прохода компиляции. Я замерил с помощью --profile:

./gradlew assembleDebug --profile

Результат: kapt-проходы занимали 2 минуты 48 секунд из 4 минут общей сборки. Почти 70% времени уходило на annotation processing.

KSP — что это и как работает

KSP (Kotlin Symbol Processing) — это annotation processing, написанный специально для Kotlin. Он работает напрямую с Kotlin PSI (Program Structure Interface), без промежуточных Java stub'ов. Нет двойной компиляции, нет stub generation — процессор читает Kotlin-код, анализирует символы и генерирует код.

Архитектурно KSP встроен в Kotlin compiler plugin API. Это значит, что он работает в том же процессе, что и компилятор, и может использовать типы напрямую. Нет overhead на конвертацию между Java и Kotlin типами.

Миграция: шаг за шагом

Шаг 1 — добавляем KSP-плагин

// build.gradle.kts (project level)
plugins {
    id("com.google.devtools.ksp") version "2.0.0-1.0.21" apply false
}

// build.gradle.kts (app level)
plugins {
    id("com.android.application")
    kotlin("android")
    id("com.google.dagger.hilt.android")
    id("com.google.devtools.ksp") // вместо kotlin("kapt")
}

Шаг 2 — заменяем kapt на ksp для Room

dependencies {
    // Room — уже поддерживает KSP
    implementation("androidx.room:room-runtime:2.6.1")
    implementation("androidx.room:room-ktx:2.6.1")
    ksp("androidx.room:room-compiler:2.6.1") // было kapt
}

Room поддерживает KSP с версии 2.6.0 — это самая безболезненная миграция. Просто заменяете kapt на ksp и всё работает. Мы проверили все 47 Dao-интерфейса — ни одного breaking change.

Шаг 3 — Moshi

dependencies {
    // Moshi — KSP-поддержка с версии 1.15.0
    implementation("com.squareup.moshi:moshi-kotlin:1.15.1")
    ksp("com.squareup.moshi:moshi-kotlin-codegen:1.15.1") // было kapt
}

Moshi тоже без проблем. Единственный нюанс — если вы использовали JsonClass(generateAdapter = true) с kapt, с KSP это работает точно так же. Миграция прозрачная.

Шаг 4 — Dagger Hilt (самый сложный)

Dagger Hilt начал поддерживать KSP начиная с версии 2.50. Но есть нюанс:

dependencies {
    implementation("com.google.dagger:hilt-android:2.51")
    ksp("com.google.dagger:hilt-android-compiler:2.51")
}

// Если используете Hilt Testing
dependencies {
    testImplementation("com.google.dagger:hilt-android-testing:2.51")
    kspTest("com.google.dagger:hilt-android-compiler:2.51")
    androidTestImplementation("com.google.dagger:hilt-android-testing:2.51")
    kspAndroidTest("com.google.dagger:hilt-android-compiler:2.51")
}

У нас были проблемы с кастомными @Module, которые генерировали типы, зависящие от сгенерированных Room-DAO. Порядок процессоров важен — KSP обрабатывает их в порядке объявления в dependencies. Если Room-процессор идёт после Hilt — может быть проблема с unresolved types. Решение: Room сначала, потом Hilt.

Удаляем kapt полностью

Когда все три процессора мигрированы на KSP, удаляем kapt-плагин:

// УДАЛЯЕМ:
// kotlin("kapt")
// kapt { ... }

// Добавляем для чистоты:
ksp {
    // Room-specific: аргументы для schema export
    arg("room.schemaLocation", "$projectDir/schemas")
}

Результаты

После миграции всех трёх процессоров на KSP, вот замеры на том же --profile:

До: 4 минуты 02 секунды (clean build), из них kapt — 2:48.
После: 1 минута 12 секунд (clean build), из них KSP — 0:34.
Ускорение: 3.4x на clean build, 5x на incremental.

Инкрементальная сборка (изменён один файл) заняла 8 секунд вместо 45. Это самое важное для разработчика — теперь ./gradlew :app:assembleDebug при изменении одного класса занимает меньше 10 секунд, а не почти минуту.

Подводные камни

Не все библиотеки поддерживают KSP. Если у вас есть annotation processor, который не портирован на KSP — придётся оставить kapt для него. Да, можно использовать и kapt и KSP одновременно, но вы не получите полного ускорения — каждый kapt-проход всё ещё запускает полный цикл.

Ещё одна проблема: kapt.correctErrorTypes = true в KSP не нужен (и не существует). Это настройка, которая скрывает ошибки типизации в kapt-генерируемом коде. Если ваш код завязан на это поведение — при миграции могут всплыть ошибки.

Итог

Если в вашем проекте есть kapt — начните планировать миграцию на KSP. Это не сложная миграция для популярных библиотек (Room, Moshi, Dagger Hilt, Epoxy, Parcelize). Ускорение сборки в 3-5x — это не маркетинг, это реальность. А для команды из 8 человек это экономия 5+ часов каждый день. Однозначно стоит того.

Поделиться статьёй

Комментарии

0
0 / 2000

Пока нет комментариев

Будьте первым, кто оставит комментарий к этой статье

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

Micronaut vs Spring Boot — лёгкий фреймворк для Kotlin-микросервисов

Сравниваю Micronaut и Spring Boot для Kotlin-микросервисов: стартовое время, потребление памяти, холодный старт и реальный опыт миграции одного из сервисов.

12 июня 2026 11 мин

Kotlin: sealed interfaces и when-выражения — паттерн-матчинг как в Rust

Как sealed interfaces из Kotlin 1.5+ вместе с when-выражениями дают мощный паттерн-матчинг, похожий на Rust enum+match, и почему мы перешли на ADT для доменных событий.

11 июня 2026 9 мин

Kotlinx Serialization + Protobuf — быстрая сериализация для микросервисов

Разбираюсь с накладными расходами JSON-сериализации при высоких нагрузках и показываю, как интегрировать Protobuf с kotlinx-serialization в микросервисной архитектуре на Kotlin.

10 июня 2026 10 мин