Démarrer vos projets Java plus rapidement avec Jbang

jbang est une commande ligne permettant de programmer et d’exécuter du code Java très simplement comme on peut le faire avec du Shell.

On n’a alors plus besoin de la sacro-saint arborescence src/main/java ou même d’un fichier pom.xml puisque toute les informations du projet Java se trouvent contenues dans un ou plusieurs fichiers Java.

J’ai découvert jbang en Mai 2020 et nous verrons dans ce billet les apports principaux de jbang pour le développeur Java ou l’ingénieur qualité, que celui-ci soit novice ou débutant.

1. Installer jbang

1.1 Avec liar

Personnellement j’installe jbang avec liar, une commande ligne que j’ai écrite pour installer des archives:

$ liar -l install jbang https://github.com/jbangdev/jbang/releases/download/v0.68.0/jbang-0.68.0.zip

Le flag -l permet de créer un lien symbolique dans $HOME/bin afin de disposer de la commande jbang dans le PATH.

Vous pouvez aussi utiliser la complétion Bash de liar pour avoir la dernière version de jbang:

$ liar -l install jbang <TAB>

1.2 Avec SDKMan

Si vous utilisez SDKMan, vous pouvez installer jbang comme suit:

$ sdk install jbang

1.3 Configurer la complétion Bash

$ jbang completion | sed 's/+o default/-o default/' > jbang
$ sudo cp jbang /etc/bash_completion.d

La modification du script de complétion faites ici avec sed permet d’éviter un problème dans ce script.

Une fois votre terminal redémarré, vous disposez de jbang et de la complétion pour Bash:

$ jbang 
jbang is a tool for building and running .java/.jsh scripts and jar packages.
Usage: jbang [-hV] [--verbose | --quiet] [-o | [--fresh]] [COMMAND]

  jbang init hello.java [args...]
        (to initialize a script)
...

2. Un premier script

Comme évoqué en introduction, jbang permet de faire des « scripts » en Java comme on le fait en Shell ou en Python.

Voyons comment on commence avec un classique HelloWorld:

$ jbang init --template=hello Hello.java
[jbang] File initialized. You can now run it with 'jbang Hello.java' or edit it using 'jbang edit --open=[editor] Hello.java' where [editor] is your editor or IDE, e.g. 'idea'

Voyons ce que jbang nous a généré:

$ cat Hello.java
///usr/bin/env jbang "$0" "$@" ; exit $?
// //DEPS <dependency1> <dependency2>

import static java.lang.System.*;

public class Hello {

    public static void main(String... args) {
        out.println("Hello World");
    }
}

La première ligne doit vous rappeler les scripts Shell ou Python. C’est un Shebang qui permet de dire au Shell quel programme est en charge de ce fichier.

Vous voyez ensuite une ligne d’exemple pour déclarer les dépendances du programme (pas de fichier pom.xml).

Finalement vient le code de la classe elle-même, sans nom de package qui ne nous apporterait rien dans un « script ».

2.1 Exécuter le script

Pour exécuter le script, rien de plus simple:

$ ./Hello.java 
[jbang] Building jar...
Hello World

On bien:

$ jbang Hello.java
Hello World

Le script était déjà compilé par jbang, c’est pourquoi la ligne « Building jar » n’est pas apparue la deuxième fois.

On peut aussi utiliser:

$ jbang run Hello.java

La commande run est facultative mais il est intéressant de savoir que c’est elle qui se lance, en particulier pour connaître les paramètres qu’elle accepte:

$ jbang run --help
Usage: jbang run -o [-hnV] [--[no-]cds] [--ea] [--esa] [--fresh] [--insecure]
                 [--interactive] [--jsh] --quiet --verbose [-r
                 [=<flightRecorderString>]] [-d=<debugString>]
                 [-j=<javaVersion>] [-m=<main>] [--cp=<classpaths>]...
                 [-D=<String=String>]... [--deps=<dependencies>]...
                 [--javaagent=<String=Optional>]... <scriptOrFile>
                 [<userParams>...]

On peut par exemple spécifier la version de Java à utiliser, des propriétés systèmes, le fait d’utiliser le Java Flight Recorder ou encore activer le mode verbeux de jbang.

2.2 Ajouter des paramètres

Si vous avez regardé attentivement le Shebang généré par jbang ou les paramètres possibles de la commande run, vous aurez compris qu’on peut passer des paramètres à notre script.

Changeons dans notre fichier source la ligne de code du main:

out.println("Hello World: " + Arrays.deepToString(args));

Et exécutons le avec des paramètres:

$ ./Hello.java i use jbang
[jbang] Building jar...
Hello World: [i, use, jbang]

2.3 Editer le script avec IDEA

Pour modifier le script, on ne va pas le faire avec vi. Je n’ai rien contre vi mais pour du Java on peut faire autrement !

On va dire à jbang que l’on souhaiter éditer notre script avec IDEA:

$ jbang edit --open=idea Hello.java
[jbang] Running `sh -c idea /home/pyfourmond/.jbang/cache/projects/Hello.java_jbang_3ffb56a92041a06bde360de31f0251136fcb1e61a9e3fd412a3fd1b8ff833715/Hello`
/home/pyfourmond/.jbang/cache/projects/Hello.java_jbang_3ffb56a92041a06bde360de31f0251136fcb1e61a9e3fd412a3fd1b8ff833715/Hello

jbang va alors générer un projet IDEA, Gradle ou Maven, permettant d’éditer notre fichier avec toutes les dépendances résolues (dans cette exemple simple, nous n’en avons pas) et nous pourrons le modifier en bénéficiant de la coloration syntaxique, la complétion, la compilation automatique, etc …

Si vous n’utilisez pas IDEA, sachez que jbang supporte aussi Visual Studio Code, Eclipse, Netbeans

3. Une autre version de Java

Par défaut, jbang utilise la version de Java disponible dans votre environnement, i.e. votre PATH.

Mais il permet aussi d’utiliser une version précise de Java ou une version minimale, par exemple en ajoutant en début de script:

//JAVA 14+

jbang va alors s’occuper de télécharger le JDK requis pour vous et l’utiliser pour l’exécution de ce script:

$ ./Hello.java i learn jbang
[jbang] Downloading JDK 14. Be patient, this can take several minutes...
[jbang] Installing JDK 14...

De la même façon on peut préciser les options de java ou de javac comme suit:

//JAVAC_OPTIONS --enable-preview -source 14 (1)
//JAVA_OPTIONS --enable-preview // (2)

Ce qui permet notamment d’utiliser les « features » Java qui sont encore en « preview » dans le JDK utilisé.

PS: Attention à ne pas mettre d’espace après les deux slashs, sinon jbang prendra la ligne pour un commentaire.

4. Déclarer les dépendances

Pour déclarer les dépendances dans un script jbang, on utilise la syntaxe suivante:

//DEPS groupId:artifactId:version

par exemple, pour Picocli:

//DEPS info.picocli:picocli:4.5.0

puisque jbang se passe de fichier pom.xml.

4.1 Chercher les dépendances

Pour trouver le groupId, artifactId et la version d’une dépendance pour jbang, il est possible d’utiliser le script gavsearch disponible dans le catalogue jbangdev:

$ jbang gavsearch@jbangdev -q -f jbang picocli
Searching for `picocli` on search.maven.org...
[jbang]
//DEPS info.picocli:picocli:4.6.1
...

5. Utiliser plusieurs fichiers source

jbang a été initialement écrit pour exécuter un fichier Java, que son extension soit .java ou pas.

Comme un seul fichier contenant plusieurs classes internes peut rapidement devenir important et difficile à gérer, on peut maintenant utiliser plusieurs fichiers sources Java dans un même script jbang:

Par exemple le script suivant GitGet hérite d’une classe abstraite AbstractGit et on le précise à jbang avec la syntaxe //SOURCES:

$ more GitGet.java 
///usr/bin/env jbang "$0" "$@" ; exit $?
...
//DEPS info.picocli:picocli:4.5.0
...
//SOURCES AbstractGit.java
...

@Command(name = "GitGet", mixinStandardHelpOptions = true, version = "GitGet 0.1", description = "A command to get one o
r more files (or directories) from a Git repository")
class GitGet extends AbstractGit {

Le script GitGet s’exécute alors comme un script jbang normal et jbang va se charger de résoudre les fichiers référencés par la directive //SOURCES.

6. Utiliser les alias et les catalogues

Dans l’exemple du paragraphe 4.1, on a utilisé sans le savoir les notions d’alias et de catalogue !

Dans la commande:

$ jbang gavsearch@jbangdev

On a utilisé l’alias gavsearch du catalogue jbangdev.

Un catalogue est un ensemble de scripts jbang.

Pour en voir le contenu:

$ jbang catalog list jbangdev
Aliases:
--------
bouncinglogo@jbangdev = https://github.com/jbangdev/jbang-catalog/blob/HEAD/bouncinglogo.java
env@jbangdev = Dump table of Environment Variables
gavsearch@jbangdev = Search search.maven.org for maven artifacts.
...

Les scripts contenus dans le catalogue sont décrits dans le fichier https://github.com/jbangdev/jbang-catalog/blob/master/jbang-catalog.json.

Dans cet exemple, le catalogue est distant mais vous pouvez aussi définir des catalogues locaux qui référencent des scripts présents sur votre machine.

On peut faire beaucoup de choses avec les alias et les catalogues, n’hésitez pas à lire la documentation.

7. Conclusion

jbang est un outil formidable pour le développeur Java, très orienté pour les projets de petite taille, d’où la notion de « script » souvent utilisée.

Le projet est très actif comme son créateur Max Rydahl Andersen, les releases sont régulières et de nouvelles fonctionnalités sont ajoutées chaque mois.

La possibilité de générer très simplement une image native d’une application avec GraalVM, non décrite dans ce billet, est particulièrement intéressante.

Néanmoins, certaines choses peuvent manquer comme par exemple la possibilité d’éditer un catalogue, celle d’éditer les scripts comportant plusieurs sources (//SOURCES) ou encore d’accéder aux catalogues privés via SSH.

En outre, l’édition des fichiers dans un répertoire autre que celui d’origine interdit l’utilisation de Git dans le répertoire d’édition.

Cela dit, le développeur Java en 2021 doit absolument tester et utiliser jbang, ne serait-ce que pour appréhender les nouveautées du JDK ou tester très rapidement les possibilités d’une API qu’il envisage d’utiliser prochainement dans un projet ou un produit.