Journal bake : scripter en bash à la « makefile »

Posté par  (site web personnel) . Licence CC By‑SA.
Étiquettes :
1
14
nov.
2025

Sommaire

Avant propos

make

Make est un bon outil et je me suis inspiré de la lecture de ce manuel pour concevoir l'exemple.

Pourquoi bash ?

Depuis que je code en entreprise et à la maison, sous linux/BSD autant que windows j'ai toujours eu accès à bash et aux GNU coreutils.

Quand vous installer git for windows vous installez plus que bash, vous installez, les Core Utils (dont mount et chroot), vous avez un tty (celui de mingw qui est un peu buggué), une arborescence de système linux, ssh, sftp, perl, make (oui), tk/tcl … ET VIM \o/

Tout ce qu'il faut pour bootstrapper un environnement à la linux surtout si vous ajoutez dans la soupe un gestionnaire de paquet que ce soit scoop ou (je n'ai pas essayé) pkgsrc

Bash est devenu ipso facto grâce à l'installation de git sur windows la stack de dév. pour un devops la plus portable. Le truc qui rappelle l'informatique des années Commodore 64, où partout où l'on arrive on trouve une invite pour coder standardisée.

Bake le design

Quand j'ai regardé le tuto de make j'ai vu le pattern qui me plaisait: une série de description :

  • état actuel, qui produit un artefact
  • une liste de dépendance pour arriver à l'état
  • un corps de « code » qui permet de faire la transition à l'état suivant.

par contre, chose qui me déplaît les artefacts de dépendances ne peuvent pas être genre des ports tcp ouverts (ex: si port 53 pas ouvert alors démarrer DNS).

J'ai décidé de faire une projection de make en bash avec une pile d'appel que l'on manipule explicitement.

Voici le code dont le cœur qui nous intéresse est à la ligne 53

#!/usr/bin/env bash
set -e
declare -a action;
RD='\e[31m'
GB='\e[33m'
BL='\e[34m'
RZ='\e[0m'

DEBUG=${DEBUG:-1}
ONE_VAR=${ONE_VAR:-template.dot}

echo -e "${GB}REPRODUCIBLE BUILD WITH"
echo -e      "-----------------------$RZ"
echo
for var in DEBUG ONE_VAR;
do
     printf "%s='%s' " "$var" "$( eval eval echo \\$\$var)"
done
echo -n $0 $@
echo
echo

d() {
    [ -z "$DEBUG" ] || echo -e "DEBUG:$(date +"%H:%M:%S"):$BL $* $RZ"
}

[ -d out ] || mkdir out


push() {
    local stack
    declare -a stack
    stack=( "$@" )
    for ((i=${#@}; i--; i)); do
        action=( "${stack[$i]}" "${action[@]}" );
    done
}


pop() {
    declare -n v=$1
    v=${action[0]}
    action=("${action[@]:1}")
}

set +x
dispatch_action() {
    while [[ ${#action} -gt 0 ]]; do
        pop act

        d "DISPATCHING $act"
        case $act  in
            all)
                [ -f out/this ] || push "before" ; dispatch_action
                touch out/that
                d all bells and whistles
                push "clean" ; dispatch_action
            ;;
            before)
                [ -f out/init ] || push init; dispatch_action
                d after init before all and clean
                touch out/this
            ;;
            init)
                d init : begin all
                touch out/init
            ;;
            clean)
                d clean everything
                set +e
                d out/*
                rm out/*
                set -e
            ;;
            *)
            echo unrecognized "$act";
                d "${action[@]}"
            break
            ;;
        esac
    done
}
if [ ! -z $1 ]; then
    action=( "$@" );
else
    push "all"
fi
dispatch_action
d fin

Le cœur de faire son makefile à la bash consiste à peupler le switch case des états qu'on veut, en prélude de faire ses tests de prérequis et pousser/appeler la boucle de dispatch si nécessaire, puis exécuter son code.

L’exemple ci dessus donne les sorties suivantes :

$ ./make.bash all
REPRODUCIBLE BUILD WITH
-----------------------

DEBUG='1' ONE_VAR='template.dot' ./make.bash all

DEBUG:10:08:50: DISPATCHING all 
DEBUG:10:08:50: DISPATCHING before 
DEBUG:10:08:50: DISPATCHING init 
DEBUG:10:08:50: init : begin all 
DEBUG:10:08:50: after init before all and clean 
DEBUG:10:08:50: all bells and whistles 
DEBUG:10:08:50: DISPATCHING clean 
DEBUG:10:08:50: clean everything 
DEBUG:10:08:50: out/init out/that out/this 
DEBUG:10:08:50: fin 

Ce qui est ce qu'on attend d'un make all

Maintenant faisons un make before puis un make all

$ ./make.bash all
REPRODUCIBLE BUILD WITH
-----------------------

DEBUG='1' ONE_VAR='template.dot' ./make.bash all

DEBUG:10:10:20: DISPATCHING all 
DEBUG:10:10:20: all bells and whistles 
DEBUG:10:10:20: DISPATCHING clean 
DEBUG:10:10:20: clean everything 
DEBUG:10:10:20: out/init out/that out/this 
DEBUG:10:10:20: fin 

on a bien la propriété des makefiles de ne pas refaire une étape si la dépendance est ouverte

Limites et avantages de l'approche

les wildcards

Si un truc vous manque de make et que c'est les jokers (wildcard) alors cet outil n'est pas fait pour vous ; je ne vois absolument pas comment coder facilement cette fonctionnalité.

pousser des actions futures

En make on ne peut que déclarer les dépendances ici, on peut (voir all) pousser des actions futures. Je suis sûr que c'est une mauvaise idée, mais c'est faisable.

les makes récursifs

Je ne me suis pas penché sur le problème, mais je crois bien que cette fonctionnalité ne m'est pas accessible :)

Conclusion

En résumé, bash n'est pas qu'un shell, c'est aussi un environnement de programmation.

La Gnu/Basherie ultime que déteste la secte concurrent de la mienne qu'est le POSIX-shismes ce sont les tableaux.

Et pourtant avoir des tableaux (dont associatifs) permet de faire des chouettes trucs en bash (voir les primitives push et pop).

La programmation ce n'est pas que sortir un résultat avec un langage bien branlé, c'est aussi s'en sortir avec un langage bringuebalant.

Envoyer un commentaire

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.