Je pense pouvoir maintenant remplacer Direnv par Mise đŸ€ž

Journal du jeudi 19 décembre 2024 à 17:09

Le 6 novembre 2024, j'ai publié la note "Le support des variables d'environments de Mise est limité, je continue à utiliser direnv".

L'issue indiquée dans cette note a été cloturée il y a 3 jours : Use mise tools in env template · Issue #1982.

Voici le contenu de changement de la documentation : https://github.com/jdx/mise/pull/3598/files#diff-e8cfa8083d0343d5a04e010a9348083f7b64035c407faa971074c6f0e8d0d940

Ce qui signifie que je vais pouvoir maintenant utiliser terraform installé via Mise dans le fichier .envrc :

[env]
_.source = { value = "./.envrc.sh", tools = true }

[tools]
terraform="1.9.8"

AprÚs avoir installé la derniÚre version de Mise, j'ai testé cette configuration dans repository suivant : install-and-configure-mise-skeleton.

Le fichier .envrc suivant a fonctionné :

export HELLO_WORLD=foo3
export NOW=$(date)
export TERRAFORM_VERSION=$(terraform --version | head -n1)

Exemple :

$ mise trust
$ echo $TERRAFORM_VERSION
Terraform v1.9.8

Je n'ai pas trouvé de commande Mise pour recharger les variables d'environnement du dossier courant, sans quitter le dossier. Avec direnv, pour effectuer un rechargement je lançais : direnv allow.

À la place, j'ai trouvĂ© la mĂ©thode suivante :

$ source .envrc

Je pense pouvoir maintenant remplacer Direnv par Mise đŸ€ž.


Journaux liées à cette note :

Enlever des couches : mon chemin de Make vers de simples scripts Bash #Doctrine, #dev-kit, #software-engineering, #mise

J'estime le temps de lecture de cette note Ă  10 minutes

Je profite d'une discussion entre deux amis au sujet de just et make pour partager mon point de vue et mes pratiques sur ce sujet.

Je tiens tout de suite Ă  prĂ©ciser que c'est un sujet qui me tient Ă  cƓur, parce qu'il m'irrite fortement : j'ai luttĂ© pendant des annĂ©es avec la mauvaise Developer eXperience de l'outil make dans mes projets, et je continue Ă  voir tant de dĂ©veloppeurs s'entĂȘter Ă  utiliser un outil dont la raison d'ĂȘtre est la rĂ©solution de dĂ©pendances basĂ©e sur les timestamps de fichiers — or, il me semble que cette fonctionnalitĂ© n'est probablement jamais utilisĂ©e, sauf dans les projets C ou C++.

Tout d'abord, je souhaite commencer par lister quelques éléments de complexité des makefile.

Quelques exemples de complexité accidentelle apportée par Make

  • Chaque ligne est un sous-shell indĂ©pendant et ça c'est super pĂ©nible, exemple :
# Le "cd" n'a aucun effet sur la ligne suivante
broken-cd:
	cd /tmp
	ls    # ← liste le rĂ©pertoire original, pas /tmp

Une solution de contournement est d'utiliser des backslashes pour continuer la ligne, mais cela complique la lisibilité :

build:
    cd /tmp && \
    ls && \
    echo "done"
  • Par dĂ©faut, Make utilise sh et non pas bash ou zsh et ne supporte pas la construction [[ ]], les tableaux, etc qui cassent silencieusement. Exemple de code qui ne fonctionne pas :
check-env:
	@if [[ -z "$(ENV)" ]]; then \
		echo "ENV is not set"; \
		exit 1; \
	fi
	@echo "ENV = $(ENV)"
  • L'indentation du contenu des rules doit ĂȘtre une tabulation (pas des espaces)
  • Les $ doivent ĂȘtre doublĂ©s pour le shell, sinon make l'interprĂšte, exemple :
greet:
	@MSG="Hello $(APP_NAME)" && \    # $(APP_NAME) → rĂ©solu par make ✓
	echo $$MSG                        # $$MSG → variable bash du sous-shell ✓
	@echo $(MSG)                      # $(MSG) → make cherche "MSG" → vide ! ✗

# PiĂšge 3 : le $ doit ĂȘtre doublĂ© pour bash, sinon make l'interprĂšte
list:
	@for i in 1 2 3; do echo $$i; done   # ✓ correct
	@for i in 1 2 3; do echo $i; done    # ✗ make interprùte $i → vide
  • Le prĂ©fixe "-" permet d'ignorer les erreurs d'une commande est une convention propre Ă  makefile, sans Ă©quivalent dans Bash
clean:
	-rm -rf build/    # sans "-", make s'arrĂȘte si build/ n'existe pas
	-docker rmi $(APP_NAME)
  • Par dĂ©faut, make affiche chaque commande avant de l'exĂ©cuter. Pour le supprimer, il faut prĂ©fixer chaque ligne avec @ :
build:
    echo "Building..."      # affiche : echo "Building..." puis : Building...
    @echo "Building..."     # affiche seulement : Building...

Du coup, dans la pratique, on se retrouve à préfixer toutes les lignes avec @ :

deploy:
    @echo "Deploying..."
    @docker build -t myapp .
    @kubectl apply -f k8s/
  • NĂ©cessitĂ© d'ajouter des .PHONY

Pourquoi tant de difficulté pour lancer de simples commandes ?

À chaque fois que je rencontrais des problĂšmes avec make, je culpabilisais. Je me disais que c'Ă©tait de ma faute, que tout le monde utilisait make et qu'il devait y avoir une bonne raison. Je voyais bien que mon expĂ©rience de dĂ©veloppeur (DX) Ă©tait mauvaise, que je n'avais pas besoin de rĂ©solution de dĂ©pendance
 mais je me disais que je devais utiliser make, et que mon erreur Ă©tait de ne pas avoir pris le temps de lire sa documentation.
Alors je replongeais rĂ©guliĂšrement dans les 16 chapitres de la documentation de make et je me demandais pourquoi je devais apprendre la syntaxe de make en plus de celle de bash. Et au final, je finissais mĂȘme par dĂ©tester Bash en plus de make.

Pourquoi tout cela était-il aussi compliqué, alors que je voulais seulement lancer de simples commandes et intégrer quelques conditions dans mes scripts ?

La recherche d'alternative

En 2018, la douleur des makefiles revenait souvent dans nos discussions en interne, au sein de mon équipe, et on cherchait réguliÚrement des alternatives. Parmi les pistes étudiées :

  • Task, en Golang, apparu en 2017 — je l'ai testĂ© et ai fortement envisagĂ© de l'adopter
  • Pydoit, en Python, dĂ©marrĂ© en 2008
  • Rake, en Ruby, lancĂ© en 2003 — alors que je ne maĂźtrise pas le Ruby et que, par goĂ»t personnel, j'Ă©vite au maximum d'intĂ©grer ce type de projet dans mes stacks
  • CMake, qu'un collĂšgue avait explorĂ©

Fin 2018, la prise de conscience

Fin 2018, je ne me souviens plus pour quelle raison, en parcourant le code source de Terraform, je suis tombé sur le dossier scripts/) de Terraform.

├── ...
├── Makefile
├── ...
├── scripts
│   ├── build.sh
│   ├── changelog-links.sh
│   ├── changelog.sh
│   ├── copyright.sh
│   ├── debug-terraform
│   ├── exhaustive.sh
│   ├── gofmtcheck.sh
│   ├── gogetcookie.sh
│   ├── goimportscheck.sh
│   ├── staticcheck.sh
│   ├── syncdeps.sh
│   └── version-bump.sh
└── ...

Et un fichier makefile minimaliste qui lance simplement des fichiers Bash :

$ cat Makefile
protobuf:
        go run ./tools/protobuf-compile .

fmtcheck:
        "$(CURDIR)/scripts/gofmtcheck.sh"

importscheck:
        "$(CURDIR)/scripts/goimportscheck.sh"

staticcheck:
        "$(CURDIR)/scripts/staticcheck.sh"

exhaustive:
        "$(CURDIR)/scripts/exhaustive.sh"

[...snip...]

Et, ce jour-là, je me suis senti trÚs stupide d'avoir passé tant de temps à trouver une solution qui était en réalité trÚs simple, à portée de main !

Je pense aussi que le fait que cette méthode ait été utilisée par Mitchell Hashimoto en personne, dans Terraform, m'a probablement donné une sorte d'autorisation d'utiliser cette approche.

J'ai compris que je pouvais simplement me passer de make.

2019 Ă  2026 : utilisation de simples scripts Bash

Suite Ă  ma prise de conscience de fin 2018, j'ai appliquĂ© un principe que je nomme "enlever des couches" : plutĂŽt que d'ajouter une technologie pour rĂ©soudre un problĂšme, rĂ©flĂ©chir Ă  ce que peut enlever pour rĂ©duire la complexitĂ© — et, par la mĂȘme, peut-ĂȘtre supprimer le problĂšme lui-mĂȘme. C'est une vigilance consciente contre le biais cognitif du cargo cult : la tendance Ă  reproduire des pratiques par habitude ou imitation, sans vraiment les comprendre ni les justifier.

En appliquant ce principe, il m'a semblĂ© que je pouvais simplement enlever make — sans avoir Ă  le remplacer par un outil tel que Task qui aurait Ă©tĂ© une couche supplĂ©mentaire dont je n'avais probablement pas besoin.

J'ai mĂȘme pris conscience qu'en plaçant tous mes scripts dans un dossier ./scripts/, je bĂ©nĂ©ficiais nativement de l'autocomplĂ©tion de mes commandes par le filesystem — tout comme ce que proposait aussi make.

Par exemple :

  • make up devenait ./scripts/up.sh
  • make build devenait ./scripts/build.sh
  • make clean devenait ./scripts/clean.sh
  • etc.

Et surtout, je pouvais désormais pleinement me concentrer sur ma maßtrise de Bash pour améliorer l'expérience de développeur (DX) de mes kits de développement.

L'astuce du cd automatique

Pour exécuter ces commandes sans se préoccuper du dossier courant, j'ai ajouté la ligne suivante au début de chaque script :

cd "$(dirname "$0")/../"

Cela permet de lancer ./scripts/up.sh depuis la racine du projet comme depuis un sous-répertoire (cd subproject && ../scripts/up.sh), et le script s'exécutera toujours depuis le dossier parent de scripts.

Voici le boilerplate code qu'utilise la quasi-totalité de mes scripts :

#!/usr/bin/env bash
set -e

cd "$(dirname "$0")/../"

...

Mais "make" est un standard ?

L'argument revient souvent : « make est un standard, tout le monde le connaßt, un nouveau contributeur saura immédiatement quoi faire. ».

Seulement voilĂ  : la partie « standard » de make, celle que tout le monde utilise rĂ©ellement, c'est make <target> — et c'est exactement ce que fait ./scripts/<target>.sh, sans syntaxe supplĂ©mentaire, sans piĂšges de tabulations, sans rĂ©solution de dĂ©pendances par timestamps dont on ne veut probablement pas.

Il me semble que cet argument touche au cargo cult : on place un Makefile Ă  la racine du projet par habitude, sans vraiment tirer parti des capacitĂ©s qui justifient l'existence mĂȘme de make.

De plus, si l'on parle de standard, bash est probablement au moins aussi universel que make. Et écrire un script bash est sans doute plus accessible pour un développeur que d'apprendre les subtilités du makefile ($ doublés, sous-shells, .PHONY, @, -, etc.).

Il me semble donc que l'argument du "standard" est lĂ©gitime — mais mon choix de ne plus utiliser make n'est pas un obstacle pour autant : si ./scripts/up.sh est clairement documentĂ© dans le README, je pense que n'importe quel dĂ©veloppeur comprendra sans difficultĂ© son usage et sa fonction. Pas besoin de connaĂźtre make pour exĂ©cuter un script bash dont le nom est explicite.

Retour d'expérience : 4 ans, de 2 à 10 développeurs

J'ai utilisĂ© cette mĂ©thode avec succĂšs pendant 4 ans, en passant de 2 Ă  10 dĂ©veloppeurs, sans que j'aie constatĂ© de friction. À ma connaissance, personne n'a eu de difficultĂ© avec ce systĂšme d'exĂ©cution des scripts et, il me semble, personne ne m'a suggĂ©rĂ© de les remplacer par autre chose.

Et Just, alors ?

J'ai découvert just en 2022, puis je l'ai vu gagner en popularité à partir de 2023 (199 commentaires sur HackerNews) :

J'ai failli me laisser tenter. Mais je n'avais aucune douleur avec mes scripts, j'Ă©tais pleinement satisfait — et conformĂ©ment au principe d'enlever des couches, ajouter une couche supplĂ©mentaire n'avait aucun intĂ©rĂȘt.

D'autre part, just est riche en fonctionnalités et sa documentation est déjà importante : il me semble que c'est beaucoup à apprendre pour un outil dont je n'ai pas besoin.

Et puis j'ai craqué pour Mise Tasks

Je suis un grand utilisateur de Mise et derniĂšrement ce projet a ajoutĂ© la fonctionnalitĂ© Tasks. Et au grand dĂ©sespoir de mon ami Alexandre — qui me fait rĂ©guliĂšrement remarquer cette contradiction —, j'ai craquĂ©, j'ai commencĂ© Ă  utiliser cette fonctionnalitĂ© en janvier 2026. Je n'ai pas d'argument solide Ă  avancer ; sans doute un mĂ©lange de curiositĂ© et d'affection pour Mise.

Contrairement Ă  just, la fonctionnalitĂ© task de Mise reste minimaliste et est compatible avec mon paradigme : le if dans l'exemple ci-dessous est du Bash standard — pas besoin de $$, de \, de sous-shells par ligne. J'Ă©cris du Bash et rien d'autre.

D'autre part, Mise est dĂ©jĂ  au cƓur de mes development kit, je l'utilise Ă  depuis 2023 Ă  la place de Asdf pour installer du tooling de dĂ©veloppement. Depuis 1 an, j'ai remplacĂ© direnv par Mise. Par consĂ©quent, ce n'est pas une dĂ©pendance en plus Ă  ajouter Ă  mes projets.

Mise task supporte trois syntaxes pour définir des tasks.

Dans le fichier .mise.toml, en ligne simple :

[tasks.build]
run = "pnpm run build"

Ou en bloc multiligne :

[tasks.clean]
run = """
if [ "$1" = "--with-lint" ]; then
  mise run lint
fi
pnpm run test
"""

Ou alors, via des scripts dans le dossier mise-tasks/, par exemple mise-tasks/build :

#!/usr/bin/env bash
#MISE description="Build the web application"
pnpm run build

Voici un extrait de Mise tasks mises en Ɠuvre dans un vrai projet :

$ mise task
Name                           Description
build-cli                      Build the sklein-devbox CLI application
build-image                    Build the sklein-devbox container image
[...snip...]
up                             Start the devbox container

Le code source est consultable ici : https://github.com/stephane-klein/sklein-devbox/blob/main/.mise.toml

C'est important pour moi de prĂ©ciser que j'ai bien conscience que Mise Tasks est une couche de plus — et que ça contredit ma doctrine « enlever des couches ».
Dans un projet d'Ă©quipe, je partirais par dĂ©faut sur des scripts Bash simples, sans Mise task. Je n'intĂ©grerais Mise task que s'il y a un consensus fort de l'Ă©quipe — et je ne l'imposerais pas.

Remerciements

Je remercie mes deux amis de m'avoir motivĂ© Ă  Ă©crire cette note — c'est un sujet que je souhaitais traiter depuis 2019 (j'avais mĂȘme créé une issue Ă  ce sujet dans mon ancien backlog).

Journal du mardi 25 février 2025 à 14:20 #JaiDécouvert, #OnMaPartagé, #mise, #asdf, #dev-kit

Alexandre vient de partager ce thread : « Asdf Version Manager Has Been Re-Written in Golang »

Je découvre que Asdf n'est pas mort ! La version 0.16.0 publié le 30 janvier 2025 a été réécrite en Golang !

La raison principale semble ĂȘtre une volontĂ© d'amĂ©lioration de la vitesse de Asdf :

With improvements ranging from 2x-7x faster than asdf version 0.15.0!

source

Depuis cette date, Mise a publié un benchmark qui compare la vitesse d'exécution de Asdf et Mise : https://mise.jdx.dev/dev-tools/comparison-to-asdf.html#asdf-in-go-0-16.

Comme mon ami Alexandre, certains utilisateurs sont inquiets de voir Mise faire trop de choses :

I tried mise a while back, and the main reason I went away from it is like you said, it does too much. It tries to be asdf, direnv and a task runner. I just want a tool manager, and is the reason why I switched to aquaproj/aqua.

source

J'ai migré de Asdf vers Mise en novembre 2023 et pour le moment, je n'ai pas envie, ni de raison pratique particuliÚre pour retourner à Asdf.
De plus, je suis plutĂŽt satisfait d'avoir remplacĂ© direnv par Mise, voir Je pense pouvoir maintenant remplacer Direnv par Mise đŸ€ž.

Le support des variables d'environments de Mise est limité, je continue à utiliser direnv #direnv, #dev-kit

J'ai décidé d'utiliser la fonctionnalité "mise env._source" pendant quelques semaines pour me faire une opinion.

-- (from)

J'ai déjà rencontré un problÚme : il n'est pas possible de lancer des outils installés via Mise dans un script lancé par _.source.

Voici un exemple, j'ai ce fichier .envrc.sh :

export SERVER1_IP=$(terraform output --json | jq -r '.server1_public_ip | .value')

Terraform est installé avec Mise :

[env]
_.source = "./.envrc.sh"

[tools]
terraform="1.9.8"

Quand je lance mise set, j'ai cette erreur :

$ mise set
.envrc.sh: line 6: terraform: command not found

J'ai trouvé une issue pour traiter cette limitation : "Use mise tools in env template".

En attendant que cette issue soit implémentée, j'ai décidé de continuer d'utiliser direnv.


2024-12-19 : suite Ă  la cloture de l'issue indiquĂ©e dans cette note, j'ai publiĂ© la note Je pense maintenant pouvoir remplacer Direnv par Mise đŸ€ž.