Spec-Zone.ru › Kotlin 1.7

Инкрементальная обработка

Инкрементальная обработка — это техника обработки, которая по возможности избегает повторной обработки исходных данных. Основная цель инкрементальной обработки — сократить время цикла изменение-компиляция-тестирование. Для общей информации см. статью Википедии о инкрементальном вычислении.

Для определения, какие исходные данные являются грязными (те, которые требуют повторной обработки), KSP нуждается в помощи процессоров для определения соответствия входных источников сгенерированным выходным данным. Для облегчения этой часто сложной и подверженной ошибкам задачи KSP разработан таким образом, чтобы требовать только минимальный набор корневых источников, которые процессоры используют в качестве начальных точек для навигации по структуре кода. Другими словами, процессор должен связать выходной результат с источниками соответствующего KSNode если KSNode получен из любого из следующих:

  • Resolver.getAllFiles

  • Resolver.getSymbolsWithAnnotation

  • Resolver.getClassDeclarationByName

  • Resolver.getDeclarationsFromPackage

В настоящее время отслеживаются только изменения в Kotlin и Java источниках. Изменения в пути к классам, а именно в других модулях или библиотеках, по умолчанию вызывают полную повторную обработку всех источников. Чтобы отслеживать изменения в пути к классам, установите свойство Gradle ksp.incremental.intermodule=true для экспериментальной реализации на JVM.

Инкрементальная обработка в настоящее время включена по умолчанию. Чтобы отключить её, установите свойство Gradle ksp.incremental=false. Чтобы включить логи, которые выводят набор грязных данных в соответствии с зависимостями и выходными данными, используйте ksp.incremental.log=true. Вы можете найти эти файлы логов в папке вывода build с расширением файла .log.

Агрегирование против изоляции

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

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

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

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

В качестве примера, сгенерированный класс, посвящённый интерфейсу, который он реализует, считается изолированным.

Подводя итог, если выходной результат может зависеть от новых или изменённых источников, он считается агрегирующим. В противном случае выходной результат является изолированным.

Вот сводка для читателей, знакомых с обработкой аннотаций Java:

  • В изолированном Java процессоре обработки аннотаций все выходные данные изолированы в KSP.

  • В агрегированном Java процессоре обработки аннотаций некоторые выходные данные могут быть изолированными, а некоторые — агрегированными в KSP.

Как это реализовано

Зависимости вычисляются путём сопоставления входных и выходных файлов, а не аннотаций. Это отношение «многие ко многим».

Правила распространения грязности из-за соответствия входных и выходных данных:

  1. Если входной файл изменён, он всегда будет переобработан.

  2. Если входной файл изменён и он связан с выходным результатом, все другие входные файлы, связанные с тем же выходным результатом, также будут переобработаны. Это транзитивно, то есть недействительность происходит повторно до тех пор, пока не появится новый грязный файл.

  3. Все входные файлы, связанные с одним или несколькими агрегированными выходными результатами, будут переобработаны. Другими словами, если входной файл не связан ни с какими агрегированными выходными результатами, он не будет переобработан (если он не соответствует условиям 1 или 2 выше).

Причин несколько:

  1. Если входной файл изменён, может быть введена новая информация, и поэтому процессорам необходимо снова запустить обработку с этим входным файлом.

  2. Выходной результат состоит из набора входных данных. Процессорам может потребоваться все входные данные для повторной генерации выходного результата.

  3. 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.log

  • build/kspCaches/<source set>/logs/kspSourceToOutputs.log

Затем вы можете выполнить последовательные инкрементальные сборки, которые сгенерируют два дополнительных файла логов:

  • build/kspCaches/<source set>/logs/kspDirtySetByDeps.log

  • build/kspCaches/<source set>/logs/kspDirtySetByOutputs.log

Эти логи содержат имена файлов источников и выходных данных, а также метки времени сборок.

Последнее изменение: 11 января 2022
Справочник по переходу от Java обработки аннотаций к KSP Обработка в несколько раундов

© 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API