Инкрементальная обработка
KSP поддерживает инкрементальную обработку: KSP обрабатывает файл повторно, только если изменяется одна или несколько его зависимостей. Это позволяет избежать ненужной повторной обработки и, следовательно, сократить время компиляции.
Инкрементальная обработка включена по умолчанию. Вы можете отключить её при устранении неполадок или если нужно принудительно выполнить полную пересборку. Чтобы отключить её, добавьте следующую строку в файл gradle.properties:
ksp.incremental=false
Изменённые файлы
Файл считается изменённым (требующим повторной обработки), если разработчик изменил его напрямую или если на него косвенно повлияли изменения в других изменённых файлах.
Чтобы определить, какие исходные файлы изменены, KSP опирается на процессоры, которые связывают сгенерированные выходные данные с соответствующими входными исходными файлами. KSP использует эти связи, чтобы определить, какие исходные файлы нужно обработать повторно после изменения.
KSP требуется только минимальный набор корневых исходных файлов. Процессоры используют их как точки входа для навигации по структуре кода.
Корневой исходный файл — это файл, символы которого непосредственно получены с помощью любого из следующих методов:
Resolver.getAllFiles()Resolver.getSymbolsWithAnnotation()Resolver.getClassDeclarationByName()Resolver.getDeclarationsFromPackage()
Процессор может получать дополнительные символы из других исходных файлов, разрешая информацию из корневого исходного файла. KSP автоматически отслеживает эти зависимости.
При создании выходного файла процессор должен объявить корневые исходные файлы, участвующие в его создании. KSP использует эти корневые исходные файлы и отслеживаемые зависимости от них, чтобы определить, когда выходной файл нужно создать заново.
Агрегирующие и изолирующие выходные данные
KSP делит сгенерированные выходные данные на два типа: агрегирующие и изолирующие.
- Агрегирующие
-
На агрегирующие выходные данные могут повлиять изменения в любом исходном файле, кроме удалений, не затрагивающих другие файлы.
Любое изменение входных данных приводит к пересборке всех агрегирующих выходных данных и повторной обработке всех соответствующих зарегистрированных, новых или изменённых исходных файлов.
Например, выходные данные, содержащие все символы с определённой аннотацией, являются агрегирующими.
- Изолирующие
-
Изолирующие выходные данные зависят только от указанных для них исходных файлов.
Изменения в других исходных файлах не влияют на эти выходные данные. С одним выходным файлом можно связать несколько исходных файлов.
Например, сгенерированный класс, предназначенный для реализации определённого интерфейса, является изолирующим.
Распространение изменений
KSP распространяет изменения следующими способами:
С помощью отслеживания разрешения типов: разрешение типов — единственный способ перейти из одного файла в другой. Когда процессор разрешает ссылку на тип (явно или неявно), KSP учитывает зависимости между файлом, содержащим ссылку, и любым файлом, определяющим символ, влияющий на это разрешение. В результате изменение разрешённого символа может пометить файл, содержащий ссылку, как изменённый.
С помощью связи входных и выходных данных: если исходный файл изменён или затронут изменениями, все другие исходные файлы, связанные с ним общими выходными данными, также помечаются как затронутые. Так связанные файлы объединяются в классы эквивалентности на основе общих выходных данных.
Реализация
Зависимости определяются отношением «многие ко многим» между входными и выходными файлами.
Вот как KSP определяет, какие файлы нужно обработать повторно:
-
Если входной файл изменён, он всегда будет обработан повторно.
Почему? Если входной файл изменён, в нём может появиться новая информация. Процессоры должны снова запуститься с этим входным файлом.
-
Если входной файл изменён и связан с выходным файлом, все остальные входные файлы, связанные с тем же выходным файлом, также будут обработаны повторно. Это повторяется, пока не останется новых изменённых файлов.
Почему? Выходной файл создаётся на основе набора входных файлов. Процессорам могут понадобиться все входные файлы, чтобы создать выходной файл заново.
-
Если неизменённый входной файл не связан ни с какими агрегирующими выходными данными, он не будет обработан повторно.
Почему? Этот файл не может повлиять на выходные данные, поскольку он не изменён и не связан с агрегирующими выходными данными. Он не будет обработан повторно, если только не применится одно из правил выше.
Например, рассмотрим проект со следующей структурой:
. ├── src │ ├── sourceA.kt │ └── sourceB.kt └── generated ├── outputA └── outputB
Процессор:
читает
sourceA.создаёт
outputA.читает
sourceB.создаёт
outputB.
Когда sourceA изменяется:
Если
outputBявляется агрегирующим, KSP повторно обрабатывает иsourceA, иsourceB.Если
outputBявляется изолирующим, KSP повторно обрабатывает толькоsourceA.
Если добавляется sourceC:
Если
outputBявляется агрегирующим, KSP повторно обрабатываетsourceCиsourceB.Если
outputBявляется изолирующим, KSP повторно обрабатывает толькоsourceC.
Если удаляется sourceA или sourceB, KSP не нужно повторно обрабатывать какие-либо файлы.
Пример процессора
В следующем проекте содержатся классы A и B, где A расширяет B:
// A.kt
@Interesting
class A : B()
// B.kt
open class B
// Example1Processor.kt
class Example1Processor : SymbolProcessor {
override fun process(resolver: Resolver) {
val declA = resolver.getSymbolsWithAnnotation("Interesting").first() as KSClassDeclaration
val declB = declA.superTypes.first().resolve().declaration
// B.kt isn't required, because it can be deduced as a dependency by KSP
val dependencies = Dependencies(aggregating = true, declA.containingFile!!)
// outputForA.kt
val outputName = "outputFor${declA.simpleName.asString()}"
// outputForA depends on A.kt and B.kt
val output = codeGenerator.createNewFile(dependencies, "com.example", outputName, "kt")
output.write("// $declA : $declB\n".toByteArray())
output.close()
}
// ...
}
Чтобы создать outputForA, процессор:
получает A, вызывая
Resolver.getSymbolsWithAnnotation.получает B, вызывая
KSClassDeclaration.superTypesдля A.
KSP отслеживает эту связь с помощью отслеживания разрешения типов и автоматически регистрирует B как зависимость A. Поэтому вам не нужно явно объявлять B.kt зависимостью outputForA.
Сообщение об ошибках
Если вы столкнулись с ошибкой, которая возникает только при включённой инкрементальной обработке, создайте задачу в репозитории GitHub и приложите соответствующие файлы журналов.
-
Включите ведение журналов инкрементальной обработки, добавив следующую строку в
gradle.properties:ksp.incremental.log=true
Выполните чистую сборку, которая завершится успешно.
-
Сохраните созданные файлы журналов, скопировав их в другое место:
build/kspCaches/<source set>/logs/kspDirtySet.logbuild/kspCaches/<source set>/logs/kspSourceToOutputs.log
Измените исходный файл, который вызывает проблему, и снова запустите сборку.
Приложите к задаче на GitHub файлы журналов как успешной сборки, так и сборки, при которой возникает проблема.
Визуализация графа зависимостей символов
Чтобы упростить отладку инкрементальной обработки, KSP может создать файл Graphviz DOT, визуализирующий граф зависимостей символов, начиная с указанного символа.
Включите ведение журнала инкрементальной обработки и укажите полное имя символа, с которого нужно начать визуализацию графа:
ksp.incremental.log=true ksp.incremental.log.graph.origin=<fully-qualified-name>
Если вы используете KSP из командной строки, добавьте следующие параметры:
-incremental-log=true -incremental-log.graph.origin=<fully-qualified-name>
Файл DOT создаётся в каталоге logs.
© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/ksp-incremental.html