Предупреждения Kotlin очень полезны для разработчиков. Они помогают очистить код и даже исправить возможные ошибки.
Вот как они выглядят на выходе Gradle:
w: /Users/dipien/android-sample/app/src/main/java/com/dipien/sample/MainActivity.kt: (16, 7): Variable ‘unused’ is never used
Очень важно исправлять их, чтобы улучшить качество вашего кода.
По умолчанию сборки Gradle не дают сбоев при обнаружении предупреждений Kotlin. Это плохо, потому что разработчики могут просто игнорировать эти предупреждения.
Существует способ настроить компилятор Kotlin таким образом, чтобы он выдавал ошибку при каждом предупреждении. Просто добавьте следующую конфигурацию в файл build.gradle
:
// For Android Project android { kotlinOptions { allWarningsAsErrors = true } } // For non Android Project compileKotlin { kotlinOptions { allWarningsAsErrors = true } }
Проблема
Включение флага allWarningsAsErrors
выглядит нормально, но есть проблема. Использование устаревшего кода также считается предупреждением в Kotlin. Например:
w: /Users/dipien/android-sample/app/src/main/java/com/dipien/sample/MainActivity.kt: (18, 3): ‘deprecatedFunction(): Unit’ is deprecated.
Итак, после включения флага allWarningsAsErrors
компилятор Kotlin будет давать сбой после каждого использования устаревшего кода. А это нежелательно для большинства проектов.
К сожалению, Kotlin не поддерживает игнорирование предупреждений is deprecated
или предотвращение ошибок при их появлении.
Решение
Следующий скрипт Gradle предлагает решение этой проблемы.
import org.gradle.api.Project import org.gradle.api.internal.GradleInternal import org.gradle.configurationcache.extensions.serviceOf import org.gradle.internal.logging.events.operations.LogEventBuildOperationProgressDetails import org.gradle.internal.operations.BuildOperationDescriptor import org.gradle.internal.operations.BuildOperationListener import org.gradle.internal.operations.BuildOperationListenerManager import org.gradle.internal.operations.OperationFinishEvent import org.gradle.internal.operations.OperationIdentifier import org.gradle.internal.operations.OperationProgressEvent import org.gradle.internal.operations.OperationStartEvent val kotlinWarnings = mutableListOf<String>() val nonKotlinWarnings = mutableListOf<String>() val errors = mutableListOf<String>() val buildOperationListener = object : BuildOperationListener { override fun started(buildOperation: BuildOperationDescriptor, startEvent: OperationStartEvent) { } override fun progress(operationIdentifier: OperationIdentifier, progressEvent: OperationProgressEvent) { val log = progressEvent.details if (log is LogEventBuildOperationProgressDetails) { if (log.logLevel == LogLevel.WARN) { if (log.message.contains("w:") && log.message.contains(".kt")) { if (!log.message.contains("is deprecated") && !kotlinWarnings.contains(log.message)) { kotlinWarnings.add(log.message) } } else { if (!nonKotlinWarnings.contains(log.message)) { nonKotlinWarnings.add(log.message) } } } else if (log.logLevel == LogLevel.ERROR) { if (!errors.contains(log.message)) { errors.add(log.message) } } } } override fun finished(buildOperation: BuildOperationDescriptor, finishEvent: OperationFinishEvent) { } } val gradleInternal = project.gradle as GradleInternal val buildOperationListenerManager = gradleInternal.serviceOf() as BuildOperationListenerManager? buildOperationListenerManager?.addListener(buildOperationListener) project.gradle.buildFinished { buildResult -> buildOperationListenerManager?.removeListener(buildOperationListener) if (buildResult.failure == null && nonKotlinWarnings.isNotEmpty()) { project.logger.warn("") project.logger.warn("================================= Project Warnings Summary ================================================================================") nonKotlinWarnings.forEach { project.logger.warn(it) } project.logger.warn("===========================================================================================================================================") } if (kotlinWarnings.isNotEmpty()) { project.logger.warn("") project.logger.warn("====================================== Kotlin Warnings ====================================================================================") kotlinWarnings.forEach { project.logger.warn(it) } project.logger.warn("===========================================================================================================================================") if (buildResult.failure == null) { throw RuntimeException("Kotlin warning found") } } if (buildResult.failure != null && errors.isNotEmpty()) { project.logger.warn("") project.logger.warn("================================= Project Errors Summary ==================================================================================") errors.forEach { project.logger.error(it) } project.logger.warn("===========================================================================================================================================") } }
По сути, в сборку Gradle добавляется BuildOperationListener
, чтобы мы могли отслеживать каждый консольный журнал. Мы проверяем каждый журнал в поисках предупреждений Kotlin, но игнорируем предупреждения об устаревшем использовании. Вы можете настроить скрипт так, чтобы он игнорировал любые другие предупреждения. Наконец, мы выбрасываем исключение, если в проекте есть предупреждения Kotlin.
Учтите, что для того, чтобы этот скрипт работал, необходимо, чтобы флаги allWarningsAsErrors
иsuppressWarnings
в kotlinOptions
были отключены (значения по умолчанию).
Как вы видите, это решение использует множество классов org.gradle.internal
, которые не предназначены для использования, потому что они не являются частью Gradle API. Поэтому скрипт может перестать работать в будущем, если Gradle решит изменить эти классы. Он был протестирован на Gradle 6.8 и 7.1 и работает корректно.
У вас есть 4 различных варианта включения этого скрипта в ваш проект:
- Создайте файл
fail_on_kotlin_warning.gradle.kts
с логикой и включите его в сборку:
// From a .gradle script apply from: "kotlin_warnings.gradle.kts" // From a .gradle.kts script apply(from = "kotlin_warnings.gradle.kts")
2. Скопируйте логику и включите ее непосредственно в файл build.gradle.kts
3. Поместите логику в каталог buildSrc
4. Поместите логику в собственный плагин Gradle
Недостатки:
- Сценарий зависит от некоторых внутренних классов Gradle
- Учитывая, что вы не можете включить флаг
suppressWarnings
, вы увидите всеis deprecated
предупреждения на консоли