No trabalho tivemos um problema com a GraalVM, e devido a isso essa issue foi criada.
Eu falei com o autor e pensei "será que não seria melhor criar um projeto maven com isso bonitinho? Subir um repo, MCVE, que você controla através de profiles as dependências?"
E cá está o repo! github.com/jeffque/graalvm-regression
Pois bem, aproveitar que vou fazer isso e compartilhar um pouco o processo.
Iniciando um projeto maven
Para começar, eu gosto de iniciar o projeto maven colocar o mvnw
(maven wrapper). Eu normalmente começo copiando o mvnw
de um outro repositório meu conhecido, mas posso fazer isso de modo mais canônico. Como por exemplo, seguindo esse tutorial do Baeldung:
$ mvn -N wrapper:wrapper
Ficou assim o diretório:
. ├── .mvn │ └── wrapper │ └── maven-wrapper.properties ├── mvnw └── mvnw.cmd
Qual a versão do maven que ele usa? Podemos perguntar diretamente ao mvwn
ou então consultar em .mvn/wrapper/maven-wrapper.properties
. No caso, o conteúdo do arquivo é:
# Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. distributionType=only-script distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
Muito bem, maven na versão 3.9.9, uma delícia.
Criando o projeto base
Existem diversas maneiras de iniciar um projeto maven. Inclusive a de você
o pom.xml
totalmente do zero. Mas... um modo mais canônico seria pedir um
arquétipo, que nem eu fiz em "Hello, World!" em GWT usando arquétipo do TBroyer.
Então, vamos pedir o arquétipo vazio? Normalmente eu peço o maven-archetype-quickstart
. Tem uma lista não exaustiva de arquétipos aqui.
./mvnw archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.5
Após baixar as dependências necessárias para rodar o arquétipo ele me pergunta:
- Define value for property 'groupId'
- com.jeffque
- Define value for property 'artifactId'
- graalvm-24-1-2-regression
- Define value for property 'version' 1.0-SNPASHOT
- <enter> (o que é equivalente a usar o default, que no caso é
1.0-SNAPSHOT
)
- <enter> (o que é equivalente a usar o default, que no caso é
- Define value for property 'package' com.jeffque:
- <enter> (o que é equivalente a usar o default, que no caso é
com.jeffque
)
- <enter> (o que é equivalente a usar o default, que no caso é
Após responder, ele pede para confirmar:
Define value for property 'package' com.jeffque: Confirm properties configuration: javaCompilerVersion: 17 junitVersion: 5.11.0 groupId: com.jeffque artifactId: graalvm-24-1-2-regression version: 1.0-SNAPSHOT package: com.jeffque Y:
Confimei e... puff!
. ├── .mvn │ └── wrapper │ └── maven-wrapper.properties ├── graalvm-24-1-2-regression │ ├── .mvn │ │ ├── jvm.config │ │ └── maven.config │ ├── pom.xml │ └── src │ ├── main │ │ └── java │ │ └── com │ │ └── jeffque │ │ └── App.java │ └── test │ └── java │ └── com │ └── jeffque │ └── AppTest.java ├── mvnw └── mvnw.cmd
Gerei no canto errado? Ok, só mover uma pasta pra lá
.
cd graalvm-24-1-2-regression mv pom.xml src ../ mv .mvn/* ../.mvn rmdir .mvn cd .. rmdir graalvm-24-1-2-regression
Ok, ajeitado:
. ├── .mvn │ ├── jvm.config │ ├── maven.config │ └── wrapper │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main │ └── java │ └── com │ └── jeffque │ └── App.java └── test └── java └── com └── jeffque └── AppTest.java
Adicionando comando de execução
Eu quero facilitar a vida de quem vai testar usando Maven. Para tal, vou configurar o plugin exec
. O plugin é esse: exec-maven-plugin
.
Na linha de comando:
> ./mvnw exec:java -Dexec.mainClass="com.jeffque.App" [INFO] Scanning for projects... [INFO] [INFO] ---------------< com.jeffque:graalvm-24-1-2-regression >---------------- [INFO] Building graalvm-24-1-2-regression 1.0-SNAPSHOT [INFO] from pom.xml [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- exec:3.5.0:java (default-cli) @ graalvm-24-1-2-regression --- Hello World! [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 0.383 s [INFO] Finished at: 2025-01-31T08:02:13-03:00 [INFO] ------------------------------------------------------------------------
Beleza, vamos por no pom? Em pluginManagement
a gente indica commo o plugin vai ser executado ao ser chamado, não necessariamente ele será chamado. Então, se eu adicionar o exec-maven-plugin
lá, o maven não tentará fazer o fetch desse plugin a priori. Já em build.plugins
... aí a gente está indicando que o plugin será de fato executado.
Aí vem a questão: quando usar algo em build.plugins
? Quando precisamos que o plugin seja executado e não tem implícito em algum lugar isso. Como por exemplo, chamar o retrolambda
para permitir usar a sintaxe de lambdas e conseguir dar um target para java 7.
Ok, vamos adicionar as configurações do plugin em pluginManagement
? Primeiramente, vamos colocar o plugin lá:
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>3.5.0</version> </plugin>
Ok, prendemos a versão. Agora, vamos para o como vai ser chamado. No caso, quando for chamado o mojo exec:java
: exec
identifica esse plugin e java
o goal específico:
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>3.5.0</version> <executions> <execution> <goals> <goal>java</goal> </goals> </execution> </executions> </plugin>
Ok, agora, vamos por a configuração da classe principal:
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>3.5.0</version> <executions>...</executions> <configuration> <mainClass>com.jeffque.App</mainClass> </configuration> </plugin>
Plugin configurado! Será?
> ./mvnw exec:java [INFO] Scanning for projects... [INFO] [INFO] ---------------< com.jeffque:graalvm-24-1-2-regression >---------------- [INFO] Building graalvm-24-1-2-regression 1.0-SNAPSHOT [INFO] from pom.xml [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- exec:3.5.0:java (default-cli) @ graalvm-24-1-2-regression --- Hello World! [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 0.341 s [INFO] Finished at: 2025-01-31T08:28:48-03:00 [INFO] ------------------------------------------------------------------------
Adaptando ao problema da issue
Vamos colocar as coisas da issue do GitHub. Primeiramente, para a versão problemática do GraalVM. Vamos alterar a main
para refletir como está na issue.
Adicionar as dependências e alterar a main
foi uma non-issue, bem direto ao ponto. Para testar, coloquei as dependências para a versão do GraalVM com problema e mandei executar:
> ./mvnw compile exec:java ... org.graalvm.polyglot.PolyglotException: TypeError: k.equals is not a function at <js>.:=> (Unnamed:6) at com.oracle.truffle.polyglot.PolyglotFunctionProxyHandler.invoke (PolyglotFunctionProxyHandler.java:151) ...
Perfeito! Tal qual aparece na issue!
Profiles
Vou criar perfis distintos. E neles vou colocar as dependências. Você pode ler mais na documentação oficial.
Vou criar dois perfis:
- graalvm-24, apresenta o problema
- graalvm-20, não apresenta o problema
<profiles> <profile> <id>graalvm-24</id> </profile> <profile> <id>graalvm-20</id> </profile> </profiles>
Ok. Agora, vou deixar o perfil graalvm-24
ativo por default. Se ninguém falar nada, ele que será invocado:
<profile> <id>graalvm-24</id> <activation> <activeByDefault>true</activeByDefault> </activation> </profile>
Como invocar esse perfil? Passando a opção -P
na linha de comando!
Por exemplo:
./mvnw exec:java -Pgraalvm-24
Estou explicitando que quero o perfil graalvm-24
. De modo semelhante:
./mvnw exec:java -Pgraalvm-20
Para o perfil graalvm-20
. Eu poderia passar múltiplos perfis também:
./mvnw exec:java -Pgraalvm-20,jeff,marmota
Isso ativa os perfis graalvm-20
, jeff
e marmota
. E no caso o activateByDefault
, funciona como? Bem, se você não falar nada...
./mvnw exec:java # note que não tem -P
Aí só ativa o que tá cadastrado pra rodar por padrão.
Muito bem, agora eu coloco as dependências:
<profiles> <profile> <id>graalvm-24</id> <activation> <activeByDefault>true</activeByDefault> </activation> <dependencies> <dependency> <groupId>org.graalvm.polyglot</groupId> <artifactId>polyglot</artifactId> <version>24.1.2</version> </dependency> <dependency> <groupId>org.graalvm.polyglot</groupId> <artifactId>js-community</artifactId> <version>24.1.2</version> <type>pom</type> <scope>runtime</scope> </dependency> </dependencies> </profile> <profile> <id>graalvm-20</id> <dependencies> <dependency> <groupId>org.graalvm.sdk</groupId> <artifactId>graal-sdk</artifactId> <version>20.1.0</version> </dependency> <dependency> <groupId>org.graalvm.js</groupId> <artifactId>js</artifactId> <version>20.1.0</version> </dependency> </dependencies> </profile> </profiles>
E pronto, agora tenho dois perfis distintos! Note que o graalvm
foi removido do project.dependencies
, pois essas dependências sem perfil afetam a todos.
Para testar:
> ./mvnw exec:java ... org.graalvm.polyglot.PolyglotException: TypeError: k.equals is not a function at <js>.:=> (Unnamed:6) ... > ./mvnw exec:java -P graalvm-20 ... [INFO] --- exec:3.5.0:java (default-cli) @ graalvm-24-1-2-regression --- Optional[true] [INFO] ------------------------------------------------------------------------ ...
Top comments (3)
Nossa, bem interessante esse case que resolveram! É bem normal eu ter problemas de ter que me virar com versões depreciadas de bibliotecas por compatibilidade, curti demais!
É ótimo ver novos textos escritos sobre Maven. O pessoal usa hoje em dia sem nem pensar direito como funciona.
Muito bom!
Eu criei pq na minha época de criar coisa maven eu não tive acesso a nenhum material sobre profiles.
Espero que esse artigo encontre mais novatos no mundo maven e que eles entendam que tipos de problema o profile pode resolver