Поймал сегодня забавный баг в gradle-скриптах на Groovy. Спешу поделиться, чтобы вы тоже посмеялись (или поплакали) 😅
Суть в чем: объявляем переменную
enableInstabug в build.gradle, используем её в разных местах. Смотрим код:// Объявляем переменную
boolean enableInstabug = Boolean.valueOf(project.properties["enableInstabug"])
// В android блоке всё работает нормально
android {
defaultConfig {
manifestPlaceholders.readExtStoragePermMaxSdkVersion = enableInstabug ? "32" : "28"
}
}
// А в методе - сюрприз! 🎉
def applyInstabugIfNeeded() {
if (enableInstabug) { // Читает "false" как строку из gradle.properties
apply plugin: 'instabug-apm'
// ...
}
}
В чем прикол? Оказывается, в Groovy методы не захватывают переменные из внешнего скопа, в отличие от лямбд.
А почему никто не заметил? Потому что:
1. В скоупе метода переменная отсутствует - внутри Gradle неизвестные переменные "магически" резолвятся из
gradle.properties если там есть свойство с таким же названием как переменная.2. Метод читает свойство заново из gradle.properties - получает строку "false"
3. В условии if эта строка превращается в boolean по правилам Groovy - любая непустая строка считается true (привет JavaScript 😭)
4. Profit! 🎉 Код "работает", но совсем не так, как мы думали
Решения два:
1️⃣ Передавать переменную в метод как параметр:
def applyInstabugIfNeeded(boolean shouldEnableInstabug) { ... }2️⃣ Использовать closure вместо метода:
def applyInstabugIfNeeded = {
if (enableInstabug) { ... }
}Мораль: Gradle-скрипты на Groovy - как коробка шоколадных конфет: никогда не знаешь, что внутри. И да, они тоже могут испортить тебе день 🍫