in kotlin dsl ~ read.
NoSuchMethodError в Kotlin Script

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

comments powered by Disqus