Встреча с призраком: что такое "unrecognized Attribute name MODULE"?
Вы когда-нибудь видели ошибку, от которой веет мистикой? Представьте: вы пишете на современном Kotlin, всё летает, Gradle жужжит, и вдруг — бам! — в логах появляется что-то вроде:
unrecognized Attribute name MODULE (class com.sun.tools.javac.util.SharedNameTable$NameImpl)
Вы гуглите, находите Stack Overflow с тысячами просмотров, и понимаете: это не просто баг. Это — ошибка совместимости между мирами. Между Java и Kotlin. Между старой системой сборки и новым модульным миром.
В этой статье мы не просто дадим решение. Мы заглянем под капот Java-компилятора, разберём, что такое SharedNameTable, почему JVM ругается на слово MODULE, и как это связано с вашим Kotlin-проектом. Спойлер: виноват не Kotlin, а старый Gradle и Java 9+.
Коротко: как исправить (для нетерпеливых)
Если вам нужно срочно починить сборку — вот три рабочих способа. Выбирайте по ситуации:
- Способ 1 (рекомендуемый): Обновите Gradle до версии 7.0+ и Kotlin Gradle Plugin до 1.5+. Это решит проблему навсегда.
- Способ 2 (быстрое решение): Явно укажите JVM target в
build.gradle.kts:
kotlin {
jvmToolchain(11) // или 8, если хотите старую совместимость
}
- Способ 3 (костыль): Добавьте флаг компилятору:
tasks.withType<KotlinCompile> {
kotlinOptions {
jvmTarget = "1.8"
}
}
Но если вы хотите понять, почему это работает — читайте дальше. Мы нырнём в глубины Java-компилятора.
Под капотом: что такое com.sun.tools.javac.util.SharedNameTable$NameImpl?
Давайте разберём эту страшную строку по частям. Это не просто случайный набор слов — это указание на конкретный класс внутри Java Development Kit (JDK).
com.sun.tools.javac— пакет внутреннего Java-компилятора (javac). Да, того самого, который превращает.javaв.class.util.SharedNameTable— служебный класс, который хранит имена (переменных, методов, классов) в пуле строк для оптимизации. Это кэш, чтобы не создавать миллион одинаковых строк.$NameImpl— внутренний класс, представляющий конкретное имя. Когда компилятор встречает словоmodule, он создаёт объектNameImpl.
А теперь самое интересное: ошибка возникает, когда javac натыкается на атрибут MODULE в class-файле, но не знает, что с ним делать. Это происходит, если вы используете старый компилятор (< Java 9) или, наоборот, новый компилятор, но старый формат class-файлов.
Почему MODULE — особое слово?
Начиная с Java 9, в языке появилась система модулей (Project Jigsaw). Ключевое слово module стало зарезервированным. В class-файлах появился новый атрибут Module, ModulePackages, ModuleMainClass и т.д.
Когда Kotlin-компилятор генерирует байт-код, он может случайно (или намеренно) добавить этот атрибут, если ваш JVM target установлен в 9+. А старый Gradle или Maven, используя устаревшую версию javac, не понимает этих атрибутов. Отсюда и ошибка.
Реальный пример: Kotlin + Gradle + Java 11
Представьте простой Kotlin-проект на Gradle. Вот файл build.gradle.kts:
plugins {
kotlin("jvm") version "1.4.32"
application
}
dependencies {
implementation(kotlin("stdlib"))
}
application {
mainClass.set("MainKt")
}
И простой файл Main.kt:
fun main() {
println("Hello, World!")
}
Если вы запустите сборку с JDK 11, но с Gradle 6.8 — вы с вероятностью 99% получите ту самую ошибку. Почему? Потому что Kotlin 1.4.32 по умолчанию генерирует байт-код для JVM 1.8, но Gradle 6.8 использует внутренний javac, который путается в атрибутах.
Решение — обновить Kotlin Gradle Plugin до 1.5+ и Gradle до 7.0+. Или явно указать jvmTarget = "1.8".
Глубже: как Kotlin взаимодействует с Java-компилятором?
Многие думают, что Kotlin — это отдельный язык со своим компилятором. Так и есть: kotlinc компилирует .kt файлы в байт-код. Но есть нюанс: Kotlin-компилятор использует Java-компилятор (javac) для генерации некоторых метаданных, особенно при работе с Java-библиотеками и аннотациями.
Когда вы пишете:
@JvmStatic
fun myFunction() { ... }
Kotlin генерирует class-файл, который содержит атрибуты, понятные JVM. Если ваша JVM (или инструмент, который читает class-файлы) слишком старая, она может не распознать новый атрибут. Ошибка unrecognized Attribute name MODULE — это именно такой случай.
Анатомия class-файла: что говорит спецификация
Каждый class-файл начинается с магического числа 0xCAFEBABE, затем идёт версия, пул констант, флаги доступа, и — самое важное — атрибуты. Атрибуты