Le problĂšme

Je suis le genre de dĂ©veloppeur qui travaille toujours avec un terminal ouvert sur le cĂŽtĂ©, en plus de mon IDE. Je lance souvent des commandes mvn pour m’assurer que mon projet compile et que mes tests s’exĂ©cutent correctement. C’est un vieux rĂ©flexe qui date de l’Ă©poque oĂč les IDE n’avaient qu’un support limitĂ© de Maven. Lancer ces commandes hors-IDE m’aide souvent Ă  valider que tout fonctionnera bien dans un environnement de CI par exemple. J’ai donc parfois besoin de changer de version de Java en fonction du projet dans lequel je me trouve. Maven utilise la variable d’environnement JAVA_HOME pour localiser l’installation de Java Ă  utiliser. Donc ĂȘtre capable de charger des variables d’environnement diffĂ©rentes en fonction d’un projet peut s’avĂ©rer pratique. Un autre usage courant consiste Ă  venir charger des clĂ© d’API ou des secrets d’accĂšs cloud comme des variables AWS_ACCESS_KEY ou autres en fonction de mes diffĂ©rents projets.

direnv

direnv (lien) est un outil Ă©crit en go qui permet de charger des variables d’environnement dans la session courante du terminal, lorsqu’on change de rĂ©pertoire en effectuant un cd .

Installation

direnv est disponible dans les dĂ©pots de nombreuses distributions Linux, l’installation sur Ubuntu se fait avec les commandes habituelles, Ă  savoir apt install direnv. L’installation pour d’autres distributions se fait Ă  partir des dĂ©pĂŽts, ou bien directement Ă  partir de binaires prĂ©-compilĂ©s Ă  rĂ©cupĂ©rer sur Github dans les releases de direnv.

Configuration

Une fois installĂ©, il faut indiquer au shell d’appeler le binaire direnv Ă  chaque changement de rĂ©pertoire. Le shell que j’utilise au quotidien est zsh. Pour zsh, la configuration de direnv consiste Ă  venir modifier mon fichier ~/.zshrc pour y ajouter la ligne suivante:

eval "$(direnv hook zsh)"

Les procĂ©dures de configuration pour les autres shells sont dĂ©taillĂ©es sur le site de direnv et sont du mĂȘme ordre que la procĂ©dure ci-dessus.

Utilisation basique

L’utilisation de direnv se fait au travers d’un fichier .envrc Ă  positionner dans le rĂ©pertoire souhaitĂ©. Ce fichier peut contenir :

  • des commandes export pour dĂ©clarer des variables d’environnement
  • des appels de fonction de la stdlib direnv
  • des appels de fonction customisĂ©s
  • du code shell (bash)

Un exemple de fichier .envrc déposé dans un répertoire ~/workspaces/demo-direnv:

export JAVA_HOME=/opt/jdk-17.0.1+12
export MAVEN_HOME=/opt/apache-maven-3.8.4

Ce fichier dĂ©clare deux variables d’environnement qui me sont utiles dans un projet Java. À l’entrĂ©e dans le rĂ©pertoire contenant ce fichier, direnv va tenter de charger le fichier. Les variables d’environnement exportĂ©es par le fichier seront alors chargĂ©es dans la session shell courante. Au premier chargement d’un fichier, ou aprĂšs une modification, direnv demandera une validation explicite pour autoriser le fichier.

~/workspaces > cd demo-direnv 
direnv: error /home/jwittouck/workspaces/demo-direnv/.envrc is blocked. Run `direnv allow` to approve its content             

J’exĂ©cute donc direnv allow pour autoriser le chargement de mon fichier .envrc:

~/workspaces/demo-direnv > direnv allow
direnv: loading ~/workspaces/demo-direnv/.envrc
direnv: export +JAVA_HOME +MAVEN_HOME
~/workspaces/demo-direnv > 

direnv nous indique qu’il a chargĂ© notre fichier, ainsi que nos deux variables d’environnement. Nous pouvons maintenant les utiliser:

~/workspaces/demo-direnv > echo $JAVA_HOME
/opt/jdk-17.0.1+12
~/workspaces/demo-direnv > echo $MAVEN_HOME
/opt/apache-maven-3.8.4

Si on quitte le répertoire, les variables sont déchargées:

~/workspaces/demo-direnv > cd ..
direnv: unloading
~/workspaces > echo $JAVA_HOME
# rien ici !

Modification du PATH

Pour nous simplifier la vie, direnv propose des fonctions qui permettent de manipuler le PATH facilement. La fonction PATH_add permet d’ajouter simplement une nouvelle valeur au PATH. En voici un exemple dans mon fichier .envrc prĂ©cĂ©dent:

export JAVA_HOME=/opt/jdk-17.0.1+12
export MAVEN_HOME=/opt/apache-maven-3.8.4

PATH_add $JAVA_HOME/bin
PATH_add $MAVEN_HOME/bin

Avec cette nouvelle version de mon fichier, j’ai donc ajoutĂ© mes versions de java et maven au PATH. Ces ajouts seront retirĂ©s Ă  la sortie du rĂ©pertoire:

# je rentre dans mon répertoire demo-direnv
~/workspaces > cd demo-direnv
direnv: loading ~/workspaces/demo-direnv/.envrc                                                                                                                                                                                                               
direnv: export +JAVA_HOME +MAVEN_HOME ~PATH

# le $PATH a été modifié
~/workspaces/demo-direnv > java --version
openjdk 17.0.1 2021-10-19
OpenJDK Runtime Environment Temurin-17.0.1+12 (build 17.0.1+12)
OpenJDK 64-Bit Server VM Temurin-17.0.1+12 (build 17.0.1+12, mixed mode, sharing)

# je sors du répertoire 
~/workspaces/demo-direnv > cd ..
direnv: unloading

# le $PATH ne contient plus la commande java
~/workspaces > java --version
zsh: command not found: java

Avec direnv, je peux donc changer de version de java dans mon PATH, simplement en changeant de répertoire.

Utilisation avancée

Pour aller plus loin, direnv propose une librairie de fonctions qu’ils nomment stdlib. Ces fonctions sont listĂ©es et documentĂ©es dans la documentation de direnv man/direnv-stdlib.1. Voici quelques unes de ces commandes que j’utilise rĂ©guliĂšrement.

source_up

Cette commande permet d’aller chercher et exĂ©cuter le premier fichier .envrc dans l’arborescence remontante des fichiers, ce qui permet de factoriser un peu le contenu de mes fichiers .envrc. Un exemple concret d’arborescence de fichiers:

.
└── workspaces
    ├── .envrc
    ├── projet-java-11
    │   └── .envrc
    └── projet-java-17
        └── .envrc

Le fichier ~/workspaces/.envrc contient les variables communes Ă  mes deux projets:

export MAVEN_HOME=/opt/apache-maven-3.8.4
PATH_add $MAVEN_HOME/bin

Le fichier ~/workspaces/projet-java-11/.envrc contient une configuration pour Java 11, ainsi que la commande source_up qui permet de charger le fichier ~/workspaces/.envrc:

source_up
export JAVA_HOME=/opt/jdk-11.0.13+8
PATH_add $JAVA_HOME/bin

Le fichier ~/workspaces/projet-java-17/.envrc contient une configuration similaire pour Java 17:

source_up
export JAVA_HOME=/opt/jdk-17.0.1+12
PATH_add $JAVA_HOME/bin

Avec cette utilisation, la variable MAVEN_HOME est disponible dans les sous-répertoires. Cette configuration permet de factoriser mes fichiers .envrc pour tous mes projets.

dotenv

direnv peut aussi scruter les fichiers .env, qui doivent exposer les variables d’environnement sous forme de clĂ©/valeur. Les fichiers .env sont moins souple car aucun script ne peut y ĂȘtre Ă©crit. La commande dotenv peut nĂ©anmoins aider si des .env existent dĂ©jĂ  sur un projet. Voici un exemple de fichier .env:

AWS_ACCESS_KEY_ID=xxxx-xxx-xxx-xxxx
AWS_SECRET_ACCESS_KEY=xxxx-xxx-xxx-xxxx

Et un exemple de fichier .envrc qui charge le fichier .env:

dotenv

Dans le cas oĂč beaucoup de projets utilisent des .env, on peut aussi configurer direnv pour charger ces fichiers automatiquement, en plus des .envrc (qui restent chargĂ©s en prioritĂ©) Cette configuration se fait dans ~/.config/direnv/direnv.toml:

[global]
load_dotenv = true

whitelist

Par défaut, pour chaque fichier .envrc, nouveau ou modifié, direnv affichera ce message:

direnv: error .envrc is blocked. Run `direnv allow` to approve its content

Il faut donc utiliser la commande direnv allow pour autoriser ces fichiers. Cela peut ĂȘtre assez rĂ©barbatif quand cela arrive sur de nombreux projets. direnv fournit une configuration permettant de whitelister les fichiers et de les autoriser automatiquement. J’y ai ajoutĂ© mon rĂ©pertoire de travail. Cette configuration se fait dans ~/.config/direnv/direnv.toml:

[whitelist]
prefix = ["/home/jwittouck/workspaces"]

Fonction customisées

Pour amĂ©liorer l’exemple plus haut concernant la gestion de la variable JAVA_HOME, il est aussi possible d’enrichir les fonctions disponibles de direnv. Pour cela, il faut les ajouter au fichier ~/.config/direnv/direnvrc. Pour mon usage, j’ai crĂ©Ă© cette fonction :

function use_java(){
    echo "Using Java version $1"
    export JAVA_HOME=$(find /opt -maxdepth 1 -type d -name "jdk-$1*")
    echo "Loading JAVA_HOME $JAVA_HOME"
    PATH_add $JAVA_HOME/bin
}

Tous mes JDK sont installĂ©s dans /opt. Cette fonction permet de trouver le JDK qui correspond au premier paramĂštre de la fonction et de positionner les variables JAVA_HOME et PATH. Elle s’utilise dans un .envrc de cette maniĂšre:

use_java 17
# ou
# use_java 11

et voici ce que loggue direnv Ă  l’entrĂ©e du rĂ©pertoire :

~/workspaces > cd demo-direnv
direnv: loading ~/workspaces/demo-direnv/.envrc
direnv: using java 17
Using Java version 17
Loading JAVA_HOME /opt/jdk-17.0.1+12
direnv: export +JAVA_HOME +MAVEN_HOME ~PATH

Conclusion

direnv est un outil trĂšs pratique pour manipuler les variables d’environnement. Il s’adresse avant tout Ă  ceux qui passent du temps dans un shell, devs ou sysadmins. Cet article a prĂ©sentĂ© principalement les usages que j’en ai pour mes projets Java, mais direnv propose aussi des fonctions pour les projets node, ruby, python et d’autres. C’est vite devenu un indispensable dans mes shells.

Liens