Инкрементальная обработка
Инкрементальная обработка — это техника обработки, которая максимально избегает повторной обработки исходных данных. Основной целью инкрементальной обработки является сокращение времени цикла «изменение — компиляция — тестирование». Для общей информации см. статью Википедии о инкрементальном вычислении.
Для определения исходных данных, которые являются изменёнными (те, которые необходимо переобработать), KSP нуждается в помощи процессоров для определения того, какие входные источники соответствуют сгенерированным выходным данным. Чтобы помочь с этим часто сложным и подверженным ошибкам процессом, KSP разработан так, чтобы требовать только минимальный набор корневых источников, которые процессоры используют в качестве начальных точек для навигации по структуре кода. Другими словами, процессор должен связать выходной результат со источниками соответствующего KSNode , если KSNode получен из любого из следующих:
Resolver.getAllFilesResolver.getSymbolsWithAnnotationResolver.getClassDeclarationByNameResolver.getDeclarationsFromPackage
Инкрементальная обработка в настоящее время включена по умолчанию. Чтобы отключить её, установите свойство Gradle ksp.incremental=false. Чтобы включить логи, которые выводят набор изменённых файлов в соответствии с зависимостями и выходными данными, используйте ksp.incremental.log=true. Вы можете найти эти лог-файлы в папке вывода build с расширением файла .log.
Агрегирование против изоляции
Аналогично концепциям в обработке аннотаций Gradle, KSP поддерживает режимы агрегирования и изоляции. Обратите внимание, что в отличие от обработки аннотаций Gradle, KSP классифицирует каждый выходной результат как агрегирующий или изолированный, а не весь процессор.
Агрегируемый выходной результат потенциально может быть затронут любыми изменениями входных данных, за исключением удаления файлов, которые не влияют на другие файлы. Это означает, что любое изменение входных данных приводит к пересборке всех агрегируемых выходных данных, что, в свою очередь, означает повторную обработку всех соответствующих зарегистрированных, новых и изменённых исходных файлов.
Например, выходной результат, который собирает все символы с определённой аннотацией, считается агрегируемым выходным результатом.
Изолированный выходной результат зависит только от указанных им исходных данных. Изменения в других исходных данных не влияют на изолированный выходной результат. Обратите внимание, что в отличие от обработки аннотаций Gradle, вы можете определить несколько исходных файлов для данного выходного результата.
Например, сгенерированный класс, посвящённый интерфейсу, который он реализует, считается изолированным.
В заключение, если выходной результат может зависеть от новых или изменённых исходных данных, он считается агрегируемым. В противном случае выходной результат является изолированным.
Вот краткий обзор для читателей, знакомых с обработкой аннотаций Java:
В изолированном процессоре аннотаций Java все выходные данные являются изолированными в KSP.
В агрегируемом процессоре аннотаций Java некоторые выходные данные могут быть изолированными, а некоторые — агрегируемыми в KSP.
Как это реализовано
Зависимости рассчитываются по связи входных и выходных файлов, а не по аннотациям. Это отношение «многие ко многим».
Правила распространения изменённых файлов из-за связи входных и выходных данных:
Если входной файл изменён, он всегда будет переобработан.
Если входной файл изменён и он связан с выходным результатом, то все другие входные файлы, связанные с тем же выходным результатом, также будут переобработаны. Это транзитивное правило, то есть недействительность происходит повторно, пока не появятся новые изменённые файлы.
Все входные файлы, связанные с одним или несколькими агрегируемыми выходными результатами, будут переобработаны. Другими словами, если входной файл не связан ни с какими агрегируемыми выходными результатами, он не будет переобработан (если только он не соответствует пунктам 1 или 2 выше).
Причины:
Если входной файл изменён, может быть введена новая информация, поэтому процессорам необходимо повторно запустить обработку.
Выходной результат состоит из набора входных данных. Процессорам может потребоваться все входные данные для повторной генерации выходного результата.
aggregating=trueозначает, что выходной результат потенциально может зависеть от новой информации, которая может поступать как из новых файлов, так и из изменённых существующих файлов.aggregating=falseозначает, что процессор уверен, что информация поступает только из определённых входных файлов и никогда не поступает из других или новых файлов.
Пример 1
Процессор генерирует outputForA после чтения класса A в A.kt и класса B в B.kt, где A расширяет B. Процессор получил A посредством Resolver.getSymbolsWithAnnotation и затем получил B посредством KSClassDeclaration.superTypes из A. Поскольку включение B обусловлено A, B.kt не нужно указывать в dependencies для outputForA. Вы всё равно можете указать B.kt в этом случае, но это не обязательно.
// 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()
}
// ...
}
Пример 2
Предположим, что процессор генерирует outputA после чтения sourceA и outputB после чтения sourceB.
Когда sourceA изменяется:
Если
outputBявляется агрегируемым, тоsourceAиsourceBбудут переобработаны.Если
outputBявляется изолированным, то толькоsourceAбудет переобработано.
Когда sourceC добавляется:
Если
outputBявляется агрегируемым, тоsourceCиsourceBбудут переобработаны.Если
outputBявляется изолированным, то толькоsourceCбудет переобработано.
Когда sourceA удаляется, ничего не нужно переобрабатывать.
Когда sourceB удаляется, ничего не нужно переобрабатывать.
Как определяется грязь файла
Изменённый файл может быть либо непосредственно изменён пользователем, либо косвенно затронут другими изменёнными файлами. KSP распространяет состояние загрязнения в два этапа:
Распространение посредством прослеживания разрешения: Разрешение ссылки на тип (явное или неявное) — единственный способ перехода от одного файла к другому. Когда процессор разрешает ссылку на тип, изменённый или затронутый файл, содержащий изменение, которое может потенциально повлиять на результат разрешения, повлияет на файл, содержащий эту ссылку.
Распространение по соответствию входных и выходных данных: если исходный файл изменён или затронут, то все другие исходные файлы, имеющие какой-либо общий выходной результат с этим файлом, также будут затронуты.
Обратите внимание, что оба процесса являются транзитивными, а второй формирует классы эквивалентности.
Отчёт об ошибках
Для отчёта об ошибке установите свойства Gradle ksp.incremental=true и ksp.incremental.log=true, и выполните чистую сборку. Эта сборка создаёт два лог-файла:
build/kspCaches/<source set>/logs/kspDirtySet.logbuild/kspCaches/<source set>/logs/kspSourceToOutputs.log
Затем вы можете выполнить последующие инкрементальные сборки, которые сгенерируют два дополнительных лог-файла:
build/kspCaches/<source set>/logs/kspDirtySetByDeps.logbuild/kspCaches/<source set>/logs/kspDirtySetByOutputs.log
Эти лог-файлы содержат имена файлов исходных данных и выходных данных, а также временные метки сборок.
© 2010–2022 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/ksp-incremental.html