
NoSuchMethodError в Kotlin Script
В предыдущей статье мы рассмотрели некоторые WTF-ы, с которыми может столкнуться каждый, решившийся пойти и начать писать DSL на языке Kotlin. С ними можно успешно бороться и побеждать, что мы и будем делать снова.
Итак, вы написали движок и обмазались котлин-классами для того, чтобы эффективно писать котлин-скрипты. Подключили всё по феншую в build.gradle, как мы разбирали в прошлой статье:
compile("org.jetbrains.kotlin:kotlin-script-util:$kotlin_version")
и не забыли вкорячить "волшебный" скриптовый движок в ресурсы: org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory.
А потом вам вдруг стало нужно заиспользовать официальную библиотеку Jira клиента, и понеслась. При попытке её использования из котлин-скрипта мы получим ошибку вроде такой:
java.lang.NoSuchMethodError: com.google.common.base.Preconditions.checkState(...)
Первым делом смотришь свой classpath, в котором находишь google-guava и класс такой тоже там можно найти. И даже метод. WTF?
Чудес на свете не бывает, поэтому ищем что же у нас вообще попало в classpath и видим, что таких классов у нас на самом деле два. Один, понятное дело пришёл транзитивно для клиента к джире, как и полагается. Второй, оказался в пришедшем транзитивно kotlin-compiler-е. Эээ, стоп. В том же пакете? С таким же именем?
Если вас когда-нибудь посетит идея положить в свой jar-ник скопированные классы из широкоиспользуемого пакета (как та же guava), и с теми же именами, то гоните такую идею от себя прочь!
Стало понятно, что абсолютно непонятно, как подружить библиотеку для дёргания джиры, и скриптовый движок котлина.
Вариант для слабаков
Не использовать оригинальную библиотеку от производителя, а самому написать свои собственные вызовы к апишечке. Ну или взять первое более-менее котируемое полусамопальное решение с гитхаба. Это первое, что я сделал, так как решённая задача важнее поиска истины. Да, я слабак!
Правда у такого решения остаётся очевидный минус. Если вы хотите использовать ту же гуаву, или кого-нибудь, кто использует её, то альтернатив может не оказаться от слова совсем, поэтому нам нужен другой вариант.
Вариант с кровью из глаз
В одном из тредов по обсуждению проблемы, я вычитал прекрасное решение в виде собственной пересборки kotlin-script-util с подменой зависимостей на kotlin-compiler-embeddable. Сдуру я пошел это делать. О том как я пытался по модному через gradle, а потом по не очень модному через Maven, собрать одну единственную библиотеку из репозитория на гитхабе, я потом напишу отдельную статью, но в какой-то момент я так устал это делать, что пошел правильным путем и решил подменить зависимость с помощью Gradle:
compile "org.jetbrains.kotlin:kotlin-compiler-embeddable:$kotlin_version"
compile("org.jetbrains.kotlin:kotlin-script-util:$kotlin_version") {
exclude module: 'kotlin-compiler'
}
Но тут из засады на меня посмотрело другое исключение:
java.lang.NoClassDefFoundError: com/intellij/openapi/util/Disposer
Ммм, intellij. Какое оно вообще имеет отношение к фабрике для проигрывания скриптов? Сам хотел бы знать.
Ну ладно, "не беда", подумал я и пошёл искать где же тот самый джарник с заветным классом. И нашёл. Но лучше бы этого не делал. Последняя версия, которую мне удалось найти, оказалась десятилетней давности: https://mvnrepository.com/artifact/com.intellij/openapi/7.0.3. Ну, думаю не надо говорить, что при её использовании ко мне снова пришёл NoSuchMethodError.
Ок, попробуем поискать по имени класса. И находим, что класс теперь сменил пакет на org.jetbrains.kotlin.com.intellij.openapi.util
. А в kotlin-script-util пакет оригинальный (ну и зависимость тоже не на embeddable), и собранного (если бы он конечно ещё существовал), kotlin-script-util-embeddable нет.
Какие варианты?
Собрать таки kotlin-script-util, и собирать его каждый раз при обновлениях, куда-то выкладывать,... Вариант не слишком хороший, и к тому же долгий.
Альтернативный вариант чуть проще, хотя своих недостатков у него ещё больше, зато быстрый. Берём оригинальные классы KotlinJsr223JvmLocalScriptEngineFactory
и KotlinJsr223JvmLocalScriptEngine
и копируем их реализацию к себе в проект, не забыв поменять пакет. Внутри них переписываем импорт для Disposer
на "правильный" и прописываем новоиспечённый engine в javax.script.ScriptEngineFactory
.
В итоге всё запускается, но только после того, как мы покрутим ручку для завода автомобиля.
Try it
Попробовать писать скрипты на Kotlin-е с гуавой, джирой и докером можно тут: https://github.com/aatarasoff/kotlin-script-starter