Привет! Сегодня поделюсь с вами рассказом, почему Scala-макросы могут замедлять CI не только временем компиляции. Выход из такой ситуации мы нашли не сразу, но решение оказалось настолько удачным, что наша команда решила им поделиться со всем сообществом. А дело было так…
У нас есть монорепозиторий на 4 млн LOC Scala-кода, мы собираем его в Bazel с кешированием результатов сборки, чтобы разработчики не ждали компиляцию и тестирование кода, который они не трогали. Долгое время у нас болело, что чужие тесты иногда запускались на CI.
Стали разбираться и выяснили: не весь наш код компилируется идемпотентно. Повторная компиляция одного и того же Scala-кода для многих таргетов создаёт jar-архивы с разной хэш-суммой, но семантически одинаковым содержанием. И весь зависящий от них код собирается заново. В этом виноваты Scala-макросы: при повторной компиляции кода с макросом, генерирующим sealed-иерархию, порядок перечисления наследников в байткоде может отличаться от предыдущей компиляции. Такое поведение мы обнаружили в библиотеках chimney и play-json.
То есть компиляция кода с использованием макросов из этих библиотек работала не идемпотентно и ломала кеширование сборок. Аналогичное поведение мы нашли и в одном макросе для ZLayer.
Мы сделали эти макросы детерминированными:
Если вы используете кеширование сборок и эти библиотеки — обновитесь до последних версий, чтобы применить эти изменения.