Введение

Целевая аудитория: Разработчики Gradle, уже сталкивавшиеся с "волшебным" или неожиданным поведением сборки.

В нашем приключении по созданию плагина`site-baker`, мы следовали строгому подходу TDD. Каждая функция тестировалась, проверялась, и мы продвигались с уверенностью. И тогда, однажды, непредвиденное произошло. Сборки начали вести себя непредсказуемо. Изменения в логике плагина или в файлах конфигурации, казалось, игнорировались, и наши функциональные тесты, ранее надёжные, терпели неудачу без видимой причины.

Такая проблема может быть невероятно раздражающим. Он ставит под сомнение надежность инструмента и корректность нашего кода. После интенсивной сессии отладки виновный был идентифицирован: этоткеш конфигурации Gradle.

Симптом: Призрачные сборки

Проблема проявлялась несколькими способами :

  1. Я изменял строку символов в задаче`println`, но старая строка продолжала отображаться во время выполнения.

  2. Я менял значение в моём файле`managed-jbake-context.yml`, но плагин вёл себя так, как будто файл не был изменён.

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

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

Расследование: что такое кэш конфигурации?

Кеш конфигурации — это относительно новая и чрезвычайно мощная функция Gradle, включённая по умолчанию в новых версиях. Её цель — ускорить сборки.

Принцип прост :
  1. При первом запуске, Gradle выполняет фазуКонфигурация(чтение`build.gradle.kts`, создание задач, разрешение зависимостей) и строит граф задач.

  2. В конце этой фазы, Gradleсериализуй этот граф задачи сохраняет его в кэше.

  3. При последующих запусках, если ничего не изменилось (скрипты сборки,gradle.properties, и т.д.), Gradleполностью пропускает этап настройкии повторно использует кэшированный граф задач

Экономия времени поразительна в больших проектах. Однако эта производительность имеет свою цену: она накладывает строгие правила на то, как должны писаться плагины.

Жизненный цикл кэша конфигурации
@startuml
start
:Première exécution;
:Phase de Configuration;
:Création du graphe de tâches;
:Mise en cache du graphe;
:Phase d'Exécution;
end

start
:Exécution suivante;
if (Cache valide ?) then (oui)
  :Restauration du graphe depuis le cache;
  note right
    La phase de configuration
    est sautée !
  end note
else (non)
  :Phase de Configuration;
  :Mise à jour du cache;
endif
:Phase d'Exécution;
stop
@enduml

Причина проблемы: Несоответствующий плагин

Наш плагин`site-baker`Нарушал, не зная об этом, несколько правил кэша конфигурации. Чтобы граф задач был сериализуемым, задачи не должны содержать ссылок на сложные объекты, подобные этому объекту.`Project`или читать файлы произвольно во время выполнения.

Наша основная ошибка заключалась в чтении содержимого YAML-файла прямо внутри логики выполнения задачи, используя ссылку на путь, хранящийся в нашем расширении. Этот подход несовместим с кэшированием, поскольку Gradle не может определить, изменилось ли содержимое файла, если это чтение не смоделировано какзапись задачи(empty)

Временное решение: отключить кэш

Чтобы разблокировать нас и восстановить предсказуемое поведение сборки, самое быстрое решение было отключить кэш конфигурации. Достаточно добавить следующую строку в файл`gradle.properties`du проекта, который использует плагин (или в нашем случае, тестовый проект)site-baker).

# site-baker/gradle.properties
org.gradle.configuration-cache=false

Мгновенно сборки вернули своё нормальное поведение. Каждый запуск перезапускал этап настройки, и наши изменения учитывались.

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

Истинное решение : Сделать плагин совместимым

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

Ключ в том, чтобы использоватьAPI поставщикаGradle. Вместо передачи прямых значений (как`String` ou un File) на наши задачи, мы должны потратить`Property<T>`или некоторых`Provider<T>`.

Вот план рефакторинга :
  1. Объявлять записи задач :Задача, которая парсит файл YAML, должна объявить этот файл как вход. Для этого используется аннотация`@InputFile`.

[источник,kotlin] Please provide the text you would like me to translate. @get:InputFile abstract val configFile: RegularFileProperty Please provide the text you would like me to translate.

  1. Использование Property и Provider:Значение`configFile`будет подключена к объекту собственности`configPath`нашего расширения DSL. Таким образом, Gradle способен отслеживать происхождение данных.

[источник,kotlin] Please provide the text you would like me to translate. // В плагине tasks.register<MyTask>("myTask") { configFile.set(extension.configPath.flatMap { project.layout.projectDirectory.file(it) }) } Please provide the text you would like me to translate.

  1. Чтение содержимого в подходящий момент:Чтение файла должно выполняться внутри действия задачи (@TaskAction), используя`Provider`входа.

[источник,kotlin] Please provide the text you would like me to translate. @TaskAction fun execute() { val content = configFile.get().asFile.readText() // …​ парсить содержимое } Please provide the text you would like me to translate.

Следуя этой модели, Gradle понимает, что если содержимое`configFile`изменении, кэш конфигурации становится недействительным, и фаза конфигурации должна быть выполнена повторно. Кроме того, он знает, что результат задачи зависит от содержимого этого файла, что также позволяет оптимизировать кэш выполнения (UP-TO-DATE).

Заключение

Это приключение с отладкой стало ценным уроком. «Магическое» поведение кэша конфигурации заставило нас лучше понять жизненный цикл Gradle, а также принципы декларативного и ленивого программирования (lazy).

Отключение кэша конфигурации является полезным инструментом диагностики, но истинным решением является разработка надежных и современных плагинов. Правильное моделирование входных и выходных данных наших задач с помощью API`Provider`, мы не просто исправляем баг, мы повышаем производительность, надежность и удобство сопровождения нашего плагина.

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

Похожие статьи