14 minute read

CLI image Gabriel Heinzer


Il y a quelques temps, j’ai Ă©crit un article qui explique comment rapidement faire une CLI en Go. Mais voilĂ , chassez le naturel et il revient au galop : je prĂ©fĂšre le Java ☕.
Je vous propose donc, durant cet article, de dĂ©couvrir comment Ă©crire rapidement et efficacement une CLI đŸ§‘â€đŸ’» en Java.

🧐 C’est quoi une CLI ?

Question mark image Emily Morter

CLI, pour Command Line Interface, ou comment interagir avec une utilisatrice ou un utilisateur au sein d’un terminal et non d’une interface graphique.
J’ai l’avantage (ou pas đŸ€Ł) d’avoir connu le monde du dĂ©veloppement et de l’informatique au siĂšcle dernier. A cette Ă©poque les interactions dans le terminal Ă©taient lĂ©gions et les interfaces graphiques pas si rĂ©pandues. Je vous parle d’un temps oĂč les terminaux passifs Ă©taient la norme (ainsi que le systĂšme d’exploitation Solaris 😅).
On peut donc, sans trop se tromper, se dire que la CLI est l’ancĂȘtre des interactions Homme-Machine depuis que l’on est en capacitĂ© de saisir de la donnĂ©e (aux alentours de 1960 avec l’apparition des teletypes).

En rĂ©sumĂ©, une CLI doit permettre d’enchaĂźner une ou plusieurs actions Ă  la suite d’une ligne de commande composĂ©e d’options et ou de paramĂštres ou d’un simple prompt. L’informatique Ă©tant cyclique, les CLI sont redevenues Ă  la mode notamment avec Kubernetes ou Docker qui proposent des CLI trĂšs avancĂ©es pour simplifier l’utilisation de leurs produits. L’autre avantage est qu’elle permet de scripter simplement des actions sans passer par des appels API parfois un peu rĂ©barbatifs.
En parlant API, souvent, les CLI ne sont que de simples encapsulations des appels à ces API ou du SDK (Software Development Kit ) utilisé par les API.

Il existe plein de moyens et de technos pour crĂ©er une CLI : de la plus minimaliste (des scripts bash ou Windows par ex) jusqu’à l’utilisation de langages dits haut niveau comme Go ou Java par exemple. Comme indiquĂ© en introduction, nous allons utiliser Java, mais pas que. En effet, nous sommes loin des annĂ©es 90 et des terminaux passifs, il existe maintenant bon nombre d’outils et de Frameworks facilitant la vie des dĂ©veloppeuses et des dĂ©veloppeurs.

đŸ§© Frameworks et librairies utilisĂ©es

En 2023, je pense, que l’on ne code plus tout de A Ă  Z, et avant de demander Ă  ChatGPT on va passer par la case intermĂ©diaire : utiliser des Frameworks et ou librairies.
Dans mon cas je vais donc utiliser :

  • un JDK Java, la derniĂšre version LTS, Ă  ce jour la 17.
  • un Framework qu’il est cool pour Ă©crire des applications : Quarkus.
  • une librairie qu’elle est cool pour faire des CLI en Java : Picocli.
  • GraalVM histoire de gĂ©nĂ©rer un “vrai” exĂ©cutable

Mais faisons connaissance avec les trois briques principales de notre CLI.

đŸ§© Picocli

Picocli est un Framework permettant de, simplement, crĂ©er des CLI. L’idĂ©e principale de ce Framework Ă©tant de minimiser le code technique pour Ă©crire la CLI en se concentrant sur le code fonctionnel.
On le verra plus tard dans le code, mais tout cela est permis par l’utilisation massive d’annotations Java. Il sera alors simple de faire une jolie aide en ligne, avoir de la coloration dans celle-ci, faire de la validation de paramùtres, 


Les premiĂšres releases sous GitHub datent de 2017 et la derniĂšre (au moment de l’écriture de cet article) date de juin 2023.

đŸ§© Quarkus

Quarkus, comme SpringBoot, fait partie de la famille des Frameworks qui sont arrivĂ©s aprĂšs l’ùre du JEE (J2E pour les plus anciens comme moi đŸ„Č) qui nĂ©cessitait un serveur d’application. L’objectif est simple : vous faciliter la vie dans la conception d’une application et de la distribution de celle-ci. A noter que Quarkus a Ă©tĂ© pensĂ© (par RedHat) pour ĂȘtre pleinement optimisĂ© pour les applications Cloud Natives.

â„č Note :
Bien entendu, Picocli peut trĂšs bien ĂȘtre utilisĂ© sans Quarkus, c’est juste que j’adore ce Framework đŸ€©.

đŸ§© GraalVM

Lorsque je donne des confĂ©rences et que je parle de GraalVM, j’aime souvent indiquer qu’enfin en tant que dĂ©veloppeuses et dĂ©veloppeurs Java on ne doit plus baisser la tĂȘte lorsque l’on parle de performances d’exĂ©cution ! Projet créé par Oracle, il a bouleversĂ© la vision que l’on pouvait avoir d’une application Cloud Native, rĂ©siliente, et Ă©crite en Java. Pour notre besoin, c’est essentiellement la partie gĂ©nĂ©ration d’un exĂ©cutable de GraalVM qui va nous intĂ©resser : gĂ©nĂ©rer un exĂ©cutable rapide, pas (trop) gros et facilement distribuable.

🎉 Initialisation du projet

loading image Mike van den Bos

La premiĂšre chose est d’initialiser le projet, et lĂ  tout de suite Quarkus va aider đŸ€©.
Devinez quoi ?
Et oui : Quarkus propose sa propre CLI et avec celle-ci, il est possible d’initialiser un projet pour crĂ©er une CLI qui est basĂ©e sur l’extension Picocli disponible dans l’écosystĂšme Quarkus.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ quarkus create cli fr.wilda.article:discover-picocli-article:0.0.1-SNAPSHOT
Looking for the newly published extensions in registry.quarkus.io
-----------
selected extensions: 
- io.quarkus:quarkus-picocli


applying codestarts...
📚  java
🔹  maven
📩  quarkus
📝  config-properties
🔧  dockerfiles
🔧  maven-wrapper
🚀  picocli-codestart

-----------
[SUCCESS] ✅  quarkus project has been successfully generated in:
--> /Users/stef/discover-picocli-article
-----------
Navigate into this directory and get started: quarkus dev

Cela gĂ©nĂšre une arborescence prĂȘte Ă  l’emploi :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ exa -T   
.
├── LICENSE
├── mvnw
├── mvnw.cmd
├── pom.xml
├── README.md
└── src
   └── main
      ├── docker
      │  ├── Dockerfile.jvm
      │  ├── Dockerfile.legacy-jar
      │  ├── Dockerfile.native
      │  └── Dockerfile.native-micro
      ├── java
      │  └── fr
      │     └── wilda
      │        └── article
      │           └── GreetingCommand.java
      └── resources
         └── application.properties

Regardons avec un peu plus d’attention ce que l’on a comme fichiers :

  • tout ce qui est nĂ©cessaire pour compiler et builder le projet avec Maven (mĂȘme si on verra plus tard que l’on utilisera directement la CLI de Quarkus pour faire ces actions) dans le pom.xml,
  • tout ce qui est nĂ©cessaire pour construire une image Docker dans src/main/docker,
  • un exemple de code dans src/main/java,
  • un fichier de configuration dans src/main/resources

✹ CrĂ©ation de sa premiĂšre CLI

Java code image Markus Spiske

Intéressons-nous à la classe GreetingCommand.java qui a été générée:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package fr.wilda.article;

import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;

@Command(name = "greeting", mixinStandardHelpOptions = true)
public class GreetingCommand implements Runnable {

    @Parameters(paramLabel = "<name>", defaultValue = "picocli",
        description = "Your name.")
    String name;

    @Override
    public void run() {
        System.out.printf("Hello %s, go go commando!\n", name);
    }
}

Un petit zoom sur les points clefs :

  • @Command : permet de dĂ©clarer le point d’entrĂ©e de la CLI lorsque l’on a qu’une seule commande,
  • @Parameter : permet de spĂ©cifier les paramĂštres que l’on passe Ă  la CLI (ce qui est diffĂ©rent des options mais nous y reviendrons plus tard). Le paramĂštre est automatiquement stockĂ© dans la variable correspondante et il est possible de dĂ©clarer une valeur par dĂ©faut (option defaultValue). A noter aussi, qu’il est possible et mĂȘme conseillĂ© de positionner l’index du paramĂštre avec l’option index lorsqu’il y en a plusieurs. En l’absence d’index, l’ordre est calculĂ© par rĂ©flexion, ce qui peut poser des problĂšmes ou donner des rĂ©sultats diffĂ©rents selon les compilateurs. Enfin, dans l’exemple fourni, l’option paramLabel permet de spĂ©cifier quel label sera utilisĂ© dans le message d’aide pour identifier la valeur Ă  positionner pour le paramĂštre.
  • ensuite, une mĂ©thode run sans argument et sans paramĂštre : c’est cette mĂ©thode qui est le point d’entrĂ©e principal de notre CLI.

Vous pouvez donc en déduire que le travail de notre CLI est de :

  • rĂ©cupĂ©rer le premier paramĂštre pour le stocker dans le champ name
  • si il n’y a pas d’attribut : de stocker la valeur picocli
  • puis d’afficher Hello <valeur du paramĂštre>, go go commando! ou Hello picocli, go go commando!

On devrait donc avoir une utilisation de la forme :

1
2
$ discover-picocli-article Stef
Hello Stef, go go commando!

Allons vérifier cela tout de suite.

âšĄïž ExĂ©cution de la CLI

Laptop image Simon Hattinga Verschure

Ce que j’aime avec Quarkus c’est le developer mode qui permet de tester au fur et à mesure ce que l’on code sans avoir à relancer explicitement la compilation et le run.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ quarkus dev                                                                

[INFO] Scanning for projects...
[INFO] 
[INFO] -------------< fr.wilda.article:discover-picocli-article >--------------
[INFO] Building discover-picocli-article 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- quarkus-maven-plugin:3.2.3.Final:dev (default-cli) @ discover-picocli-article ---

__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2023-08-03 16:50:55,297 INFO  [io.quarkus] (Quarkus Main Thread) discover-picocli-article 0.0.1-SNAPSHOT on JVM (powered by Quarkus 3.2.3.Final) started in 0.523s. 

2023-08-03 16:50:55,307 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2023-08-03 16:50:55,307 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, picocli]
Hello picocli, go go commando!

2023-08-03 16:50:55,346 INFO  [io.quarkus] (Quarkus Main Thread) discover-picocli-article stopped in 0.002s

--
Tests paused
Press [space] to restart, [e] to edit command line args (currently ''), [r] to resume testing, [o] Toggle test output, [:] for the terminal, [h] for more options>

On le voit sur la sortie de Quarkus ci-dessus, la CLI est appelĂ©e sans paramĂštre au lancement, d’oĂč le message Hello picocli, go go commando!. Mais cela ne s’arrĂȘte pas lĂ , en effet le mode developer de Quarkus va nous permettre d’interagir avec les paramĂštres (et options) que l’on passerait Ă  notre CLI sans quitter le mode developer.
Notamment avec la touche e comme mentionné en bas de la sortie de Quarkus : [e] to edit command line args (currently '')

Essayons de passer un paramÚtre, et voyons le résultat :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2023-08-03 18:10:34,954 INFO  [io.quarkus] (Quarkus Main Thread) discover-picocli-article stopped in 0.002s


--
Tests paused
Stef

2023-08-03 18:11:11,211 INFO  [io.qua.dep.dev.RuntimeUpdatesProcessor] (Aesh InputStream Reader) Live reload total time: 0.144s 
Hello Stef, go go commando!

2023-08-03 18:11:11,213 INFO  [io.quarkus] (Quarkus Main Thread) discover-picocli-article stopped in 0.000s

--
Tests paused
Press [space] to restart, [e] to edit command line args (currently ''), [r] to resume testing, [o] Toggle test output, [:] for the terminal, [h] for more options>

Pas mal mais au final une CLI qui doit ĂȘtre lancĂ©e par une CLI ce n’est pas vraiment ce que l’on veut je pense đŸ€”.
On va voir ensemble comment on pourrait transformer notre projet en une vraie CLI đŸ€˜.

🚀 Une CLI dans mon terminal

Commençons certainement par le plus Ă©vident, l’utilisation de la commande java. Avec cette commande on peut lancer une application packagĂ©e en fichier JAR avec la commande : java -jar <nom de mon jar> <options> <paramĂštres>. Et encore une fois, Quarkus va nous aider Ă  packager l’application pour ne pas avoir Ă  crĂ©er les diffĂ©rents manifests nĂ©cessaires. Pour cela, il faudra lancer la commande quarkus build.
Cela a pour effet de gĂ©nĂ©rer le livrable quarkus-run.jar dans ./target/quarkus-app. C’est ce livrable que nous allons pouvoir utiliser, c’est aussi simple que cela 😎.
Il ne reste plus qu’à tester :

1
2
3
4
5
6
7
8
9
10
11
12
$ java -jar ./target/quarkus-app/quarkus-run.jar Stef

__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2023-08-03 18:24:24,404 INFO  [io.quarkus] (main) discover-picocli-article 0.0.1-SNAPSHOT on JVM (powered by Quarkus 3.2.3.Final) started in 0.399s. 
2023-08-03 18:24:24,420 INFO  [io.quarkus] (main) Profile prod activated. 
2023-08-03 18:24:24,420 INFO  [io.quarkus] (main) Installed features: [cdi, picocli]
Hello Stef, go go commando!
2023-08-03 18:24:24,465 INFO  [io.quarkus] (main) discover-picocli-article stopped in 0.005s

Et voilà : on a une belle CLI opérationnelle !

A-Team image NBC

Vous ne m’avez pas l’air convaincu‱e‱s 😇 ?

Oui il y a encore deux ou trois trucs Ă  amĂ©liorer, dĂ©jĂ  le cĂŽtĂ© utilisation : pas trĂšs simple et intuitif ! Ensuite, il y a beaucoup d’informations qui, au final, ne m’intĂ©ressent pas pour le fonctionnel de ma CLI.

🔡 Suppression des logs

Attaquons-nous Ă  ce problĂšme en premier, les logs. Avant de tout dĂ©sactiver, j’aimerai quand mĂȘme les avoir en mode developer mais plus en mode production. Vous commencez Ă  vous en douter, Quarkus va, encore une fois, nous aider. Pour cela on va positionner le paramĂ©trage des logs Ă  OFF mais uniquement pour le mode production dans le fichier application.properties dans l’arborescence ./src/main/resources :

1
2
%prod.quarkus.log.level=OFF
%prod.quarkus.banner.enabled=false

Testons de nouveau notre CLI :

1
2
3
$ java -jar ./target/quarkus-app/quarkus-run.jar Stef

Hello Stef, go go commando!

đŸ–„ïž Une vrai CLI dans le terminal

On l’a vu, la commande java est un peu verbeuse et on se perd un peu sur ce qui est propre Ă  la CLI que l’on dĂ©veloppe et ce qui est propre Ă  la commande java. Pour cela, la premiĂšre amĂ©lioration que l’on peut faire c’est encapsuler la commande, par exemple dans un script bash my-cli dans le rĂ©pertoire ./src/main/bin :

1
2
3
#!/bin/bash

java -jar $PATH_TO_CLI/quarkus-run.jar $1

â„č Note : l’utilisation de la variable d’environnement ici me permet de ne pas mettre de chemin en dur, il faudrait bien sĂ»r le spĂ©cifier dans la belle documentation d’installation de notre CLI 😉.

On le rend exécutable : chmod +x my-cli. Puis on le rajoute dans le PATH de la machine (ou on le copie dans un répertoire qui est déjà le PATH de la machine).
Testons notre CLI finale :

1
2
3
$ my-cli Stef

Hello Stef, go go commando!

Bon, cette fois ça y est, j’ai ma CLI et je l’utilise directement en ligne de commande dans un terminal 🎉.

Alors pari gagnĂ© ? đŸ€”

Non, effectivement et pour différentes raisons :

  • l’utilisation d’un script bash n’est qu’un artifice pour masquer l’utilisation de la commande java
  • cela nĂ©cessite toujours d’avoir une JVM d’installĂ©e
  • cela ajoute donc cette empreinte supplĂ©mentaire en termes d’exĂ©cution et de consommation mĂ©moire et processeur
  • ce n’est pas un vrai exĂ©cutable comme on peut l’imaginer pour une CLI

Voyons comment remĂ©dier Ă  cela đŸ’„.

🚀 GraalVM, le dernier chaünon manquant

Graal Kamelott image M6

Pour moi une CLI c’est un exĂ©cutable et c’est tout ! On a vu prĂ©cĂ©demment, que pour avoir un seul exĂ©cutable, on doit tricher et que l’installation n’est pas fluide (installer un JRE, 
). Et je ne reviens pas sur les coĂ»ts de performances. Heureusement, depuis quelques annĂ©es, il est maintenant possible de gĂ©nĂ©rer des exĂ©cutables Ă  partir d’un dĂ©veloppement Java.

Comment ?

GrĂące Ă  GraalVM !

⚒ Installation

Pour cela, rien de plus simple, il faut suivre le tutoriel de la documentation.
Ensuite, pour que la compilation avec Quarkus se dĂ©roule bien, il faut ajouter la variable d’environnement GRAALVM_HOME avec le chemin d’installation de GraalVM, par exemple export GRAALVM_HOME=/Library/Java/JavaVirtualMachines/graalvm-jdk-17.0.8+9.1/Contents/Home dans mon cas.
Et voilĂ  GraalVM est installĂ© et prĂȘt Ă  ĂȘtre utilisĂ© avec Quarkus.

⚙ GĂ©nĂ©ration de l’exĂ©cutable

Je pense que, maintenant, vous en avez l’habitude, mais encore un fois Quarkus va grandement nous faciliter la vie pour gĂ©nĂ©rer notre exĂ©cutable. Il suffit d’ajouter l’option --native Ă  la commande quarkus build, et le tour est jouĂ© !

quarkus build --native

Quarkus (avec l’aide de GraalVM) gĂ©nĂšre un exĂ©cutable dans le rĂ©pertoire ./target de la forme artifactId-version-runner. Dans notre cas cela donne discover-picocli-0.0.1-SNAPSHOT-runner. Un petit renommage en my-cli et le tour est jouĂ© mv discover-picocli-article-0.0.1-SNAPSHOT-runner my-cli.

Il ne reste plus qu’à tester tout ça :

1
2
3
$ my-cli Stef

Hello Stef, go go commando!

⚠ Quelques points d’attention ⚠

  • Pour juste ce hello, world!, le binaire a une taille de 35 Mo sur un MacBook M1. C’est certes assez gros mais il ne faut pas oublier que cela embarque aussi les libs nĂ©cessaires Ă  l’exĂ©cution, Ă  voir comment cela Ă©volue avec le code mĂ©tier de la CLI 😉.
  • Sauf si je suis passĂ© Ă  cĂŽtĂ©, mais la gĂ©nĂ©ration du binaire dĂ©pend de la machine sur laquelle vous gĂ©nĂ©rez ce binaire, c’est pour moi un gros manque par rapport Ă  ce qu’il est possible de faire en Go avec COBRA.

đŸ‘šâ€đŸ’» Pour aller plus loin

Buzz image Brian McGowan

Cet article n’est que le dĂ©but de la crĂ©ation d’une CLI avec Picocli, Quarkus et GraalVM. Nous n’avons pas du tout explorĂ© toute la puissance de Picocli pour crĂ©er notre CLI. Il nous reste Ă  jouer avec les options, les paramĂštres, l’aide, 

Ce sera pour la partie 2 de cet article que je trouve dĂ©jĂ  bien assez long 😅.

🔎 En conclusion

Si vous ĂȘtes arrivĂ©s jusque lĂ  merci de m’avoir lu et si il y a des coquilles n’hĂ©sitez pas Ă  me faire une issue ou PR 😊.

Merci à ma relectrice, Fanny, qui vous permet de lire cet article sans avoir trop les yeux qui saignent 😘.

Vous trouverez l’ensemble du code source dans le repository discover-picocli-article.

Comments