Sobre

Kotlin Script Engine e seus classpaths - "Como assim? Unresolved Reference? Como?" ~ Fazendo os dois funcionar corretamente!

Hoje eu decidi usar o Script Engine do Kotlin na Loritta, afinal, usar o Nashorn estava meio chato e eu ūüíĖ Kotlin, ent√£o eu decidi tentar usar.

Criei uma pequena classe de testes no meu projeto, rodei ele dentro do IntelliJ IDEA, nenhum erro! Agora vamos tentar rodar em produção... huh? unresolved reference? mas... como? Se essa classe não existivesse você nem poderia ter sido executado!

javax.script.ScriptException: error: unresolved reference: mrpowergamerbr
fun loritta(context: com.mrpowergamerbr.loritta.commands.CommandContext) {
                         ^

√Č, por algum motivo o Script Engine do Kotlin n√£o consegue encontrar as minhas classes, ou qualquer outra classe na verdade, mas... porque? Elas existem durante o runtime porque, se n√£o existissem, o aplicativo nem iria rodar!

Após pensar e procurar no Google, eu encontrei esta issue no YouTrack mas ela é relacionada a fat JARs, e o meu aplicativo em produção não usa fat JARs, mas decidi investigar...

E após várias tentativas, eu consegui fazer funcionar! Mas porque não estava funcionando? Bem...

Hoje eu decidi parar de compilar fat JARs, antes a minha JAR era um good boye, pesando apenas ~10MBs... mas já que o meu aplicativo estava crescendo com mais funcionalidades, o tamanho da JAR continuou a crescer devido a novas dependências e novos códigos adicionados... aí a JAR estava pesando ~70MBs e o good boye já não era mais um good boye, agora era um fatty boye

fatty boye

Então eu decidi tirar isso, parar de compilar o fat JAR diminuiu o tempo de compilação em 10s (e ainda diminuiu uns 20s na hora de copiar a JAR para o meu dedicado!, já que agora eu só copio as dependências quando preciso atualizar elas!), fiz que todas as dependências fossem exportadas para a pasta libs/ e fiz que o Maven adicionasse o Class-Path para o MANIFEST.MF e tudo funcionou bem! ...exceto o Script Engine do Kotlin.

O motivo de não funcionar é porque, por algum motivo, o Script Engine do Kotlin não estava nem pegando as minhas próprias classes da JAR! Mas espere, tem um jeito de arrumar... e a correção surgiu naquela issue do YouTrack que eu passei antes, mesmo se você não está usando fat JARs!

Você pode adicionar todas as dependências manualmente utilizando o -Dkotlin.script.classpath=jar1:jar2:jar3:jar4 no seu script de inicialização...

java -Dkotlin.script.classpath=libs/dependency.jar:libs/dependency2.jar:your-app.jar -jar your-app.jar

Mas aí você tem que atualizar o script de inicialização toda hora que você atualizar/remover/adicionar dependências, e as vezes você pode até esquecer de atualizar aquela property, causando problemas em seus scripts... então vamos atualizar a property pelo código!

val path = this::class.java.protectionDomain.codeSource.location.path
val jar = JarFile(path)
val mf = jar.manifest
val mattr = mf.mainAttributes
// Yes, you SHOULD USE Attributes.Name.CLASS_PATH! Don't try using "Class-Path", it won't work!
val manifestClassPath = mattr[Attributes.Name.CLASS_PATH] as String

// The format within the Class-Path attribute is different than the one expected by the property, so let's fix it!
// By the way, don't forget to append your original JAR at the end of the string!
val propClassPath = manifestClassPath.replace(" ", ":") + ":Loritta-0.0.1-SNAPSHOT.jar"

// Now we set it to our own classpath
System.setProperty("kotlin.script.classpath", propClassPath)

Sim, eu sei, você também poderia atualizar aquela property usando bash scripts ou qualquer outra coisa, mas este é o jeito que eu resolvi fazer (e funciona, e agora que você sabe como arrumar, você pode fazer a sua própria solução (que talvez até seja melhor que a minha!)).

E depois de fazer isto, você agora pode fazer evaluation do seu código (mesmo se ele dependa de classes externas) sem ter nenhum problema!

javaScript = "fun test() { println(\"Hello World!\") }"

val engine = ScriptEngineManager().getEngineByName("kotlin")
engine.eval(javaScript)
val invocable = engine as Invocable
invocable.invokeFunction("test")

Antes da mudança

https://mrpowergamerbr.com/uploads/bash_2018-06-11_18-39-50.png

Depois da mudança

https://mrpowergamerbr.com/uploads/DiscordCanary_2018-06-11_18-40-55.png

Isto √© provavelmente um problema que quase ningu√©m ir√° ter? Sim, mas mesmo assim, talvez isto ser√° √ļtil para outra pessoa. ūüėĄ

E é isto! Resolvi postar isto para caso alguém tenha o mesmo problema. (E para evitar isto)

Post original no /r/kotlin: https://www.reddit.com/r/Kotlin/comments/8qdd4x/kotlin_script_engine_and_your_classpaths_what/
Resposta no StackOverflow: https://stackoverflow.com/questions/50820869/kotlin-script-engine-throws-unresolved-reference-even-if-the-package-and-clas


Total de Visualiza√ß√Ķes

7645

Posts Populares

A história do sistema de sincronização de vídeos da Loritta
Todos nós sabemos que o YouTube demora para enviar novos vídeos para a "box" (e as vezes nem envia o vídeo!), ou seja, muitas pessoas qu...

"Brincando" de Gato e Rato com o SimSimi
Você acreditaria que esse bichinho amarelinho iria te ameaçar de atividade ilegal e que ele está coletando evidências contra você? Se vo...

A minha verdadeira opini√£o sobre o Skript
Pessoas que me conhecem faz algum tempinho sabem que eu usava Skript para quase tudo no meu servidor, realmente tentava passar do limite...

PowerMochilas - Mochilas "reais"!
<i>Originalmente publicado em 21/09/14</i> Você queria ter Mochilas no seu Servidor? Você queria um plugin de Mochila que fosse mais "...

Gabriela, o meu clone do SimSimi
Lembra da história que o SimSimi estava me ameaçando de atividade ilegal e que eu deveria parar de fazer engenheria reversa da API deles...

Coisas interessantes para sair da bad‚ĄĘ (ou n√£o)