<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Tooling | Julien Maxant</title><link>https://www.julien-maxant.com/tags/tooling/</link><description>Articles, projets et veille sur le thème « Tooling ».</description><generator>Hugo -- gohugo.io</generator><language>fr-FR</language><copyright>2026 - Julien Maxant</copyright><lastBuildDate>Fri, 16 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://www.julien-maxant.com/tags/tooling/index.xml" rel="self" type="application/rss+xml"/><item><title>Facilitez-vous la vie avec Taskfile</title><link>https://www.julien-maxant.com/blog/taskfile-facilitez-vous-la-vie/</link><guid>https://www.julien-maxant.com/blog/taskfile-facilitez-vous-la-vie/</guid><pubDate>Fri, 16 Oct 2026 00:00:00 +0000</pubDate><description>En termes de tooling ou de task runners, Makefile a longtemps été la référence. Et pour cause : standard historique, disponible nativement sur les environnements Linux. Et puis j'ai découvert Taskfile ...</description><content:encoded><![CDATA[<p>Sur tous mes projets, je récupère plus ou moins la même Makefile. Elle déclare des tâches type <code>setup</code>, <code>update</code>, <code>build</code>,
<code>cache:clear</code>, et en général j&rsquo;adapte ces commandes à la stack du projet ; le build exécutera <code>gulp</code>, <code>npm run build</code> ou
<code>yarn</code> selon le projet. C&rsquo;est pratique, ça réduit le temps d&rsquo;onboarding parce que, d&rsquo;un projet à l&rsquo;autre, je n&rsquo;ai pas à
me poser la question de savoir quelle commande je dois exécuter pour réaliser une action donnée.</p>
<p>Mais Makefile, ce n&rsquo;est pas sans inconvénients : la compatibilité cross-platform est inégale (bien que, dès lors
qu&rsquo;un projet tourne sous Docker, beaucoup de ces problèmes sont réglés), c&rsquo;est un peu rigide si on veut avoir des tâches
polyvalentes (je pense à la récupération de dump d&rsquo;environnements distants différents avec <code>drush sql:sync</code>).
Accessoirement, j&rsquo;utilise la même Makefile depuis bientôt 8 ans, et il y a cette tâche que je ne comprends toujours pas :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-makefile" data-lang="makefile"><span class="line"><span class="cl"><span class="nf">help</span><span class="o">:</span>
</span></span><span class="line"><span class="cl"> @grep -E <span class="s1">&#39;(^[a-zA-Z_-]+:.*?##.*$$)|(^##)&#39;</span> <span class="k">$(</span>MAKEFILE_LIST<span class="k">)</span> <span class="p">|</span> awk <span class="s1">&#39;BEGIN {FS = &#34;:.*?## &#34;}; {printf &#34;\033[32m%-30s\033[0m %s\n&#34;, $$1, $$2}&#39;</span> <span class="p">|</span> sed -e <span class="s1">&#39;s/\[32m##/[33m/&#39;</span>
</span></span></code></pre></div><p>Cette commande sert à afficher la liste des tâches disponibles avec <code>make help</code>.</p>
<p>Mais : insérez ici un panneau &ldquo;Here be dragons&rdquo;, car ça fonctionne, mais je n&rsquo;ai aucune idée de comment. C&rsquo;est un peu
ésotérique, et vous trouverez cette commande sur toutes les Makefile du monde (ou presque).</p>
<h2 id="et-cest-alors-que-je-découvris-taskfile">Et c&rsquo;est alors que je découvris Taskfile</h2>
<p>Le côté un peu abscons et daté de Makefile a été, étonnamment, un des principaux points de friction quant à son adoption
à chaque fois que je portais le sujet (ça et les habitudes, mais c&rsquo;est attendu dans la conduite du changement).</p>
<p>Taskfile, en revanche, a cet avantage de miser sur la clarté : c&rsquo;est du <code>yaml</code>, on déclare ses tâches sous la clé <code>tasks</code>,
ses variables sous la clé <code>vars</code> et la documentation est à la fois bien fournie et très explicite.</p>
<p>À titre de comparaison, la tâche <code>help</code> ci-dessus est déclarée ainsi :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">tasks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">default</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">desc</span><span class="p">:</span><span class="w"> </span><span class="l">List available tasks.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cmd</span><span class="p">:</span><span class="w"> </span><span class="l">task --list</span><span class="w"> </span><span class="c"># Affichera la liste des tâches ayant une desc.</span></span></span></code></pre></div><p>C&rsquo;est tout de même plus convivial.</p>
<p>Et les avantages ne s&rsquo;arrêtent pas là ! Taskfile, c&rsquo;est :</p>
<ul>
<li>nativement cross-platform</li>
<li>facile à étendre ou surcharger</li>
<li>riche de fonctionnalités avancées (<code>watch: true</code> ou <code>-w/--watch</code>, prise en charge des <code>.env</code>)</li>
</ul>
<p>Si je devais, pour être honnête, lui trouver un inconvénient, c&rsquo;est que le binaire nécessite une installation, là où
<code>make</code> est globalement disponible sur tous les environnements Linux.</p>
<p>Pour le développement de ce portfolio, par exemple, j&rsquo;ai <a href="https://github.com/JMaxant/portfolio/blob/main/Taskfile.yml">une Taskfile</a>
qui me permet de gérer toutes les étapes, hors versionnage, du projet (lancement du serveur Hugo, build des assets, <code>qa</code>).</p>
<h2 id="un-petit-aperçu-des-possibilités">Un petit aperçu des possibilités</h2>
<h3 id="facilité-dextension-et-gestion-des-namespaces">Facilité d&rsquo;extension et gestion des namespaces</h3>
<p>Les Taskfiles viennent avec un <a href="https://taskfile.dev/docs/guide/running-tasks#supported-file-names">système de priorisation</a> :
un <code>Taskfile.dist.yaml</code> sera supplanté par un <code>Taskfile.yaml</code>.</p>
<p>Concrètement, ça veut dire que si l&rsquo;on versionne le <code>.dist.yaml</code> et que l&rsquo;on ajoute <code>Taskfile.yaml</code> au <code>.gitignore</code> :</p>
<ul>
<li><code>task</code> utilisera <code>Taskfile.dist.yaml</code> tant qu&rsquo;aucun <code>Taskfile.yaml</code> n&rsquo;existe</li>
<li>je pourrai copier le <code>dist.yaml</code> en <code>.yaml</code> et le modifier à volonté sans impacter les autres membres de l&rsquo;équipe</li>
</ul>
<p>Mais en plus de ça, les Taskfiles gèrent un système de namespace qui ressemble à ceci :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">includes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">docker</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">taskfile</span><span class="p">:</span><span class="w"> </span><span class="l">Taskfile.docker.yml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">optional</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">front</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">taskfile</span><span class="p">:</span><span class="w"> </span><span class="l">./path/vers/app.js/Taskfile.front.yml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">dir</span><span class="p">:</span><span class="w"> </span><span class="l">./path/vers/app.js</span></span></span></code></pre></div><p>Dans cet exemple <code>docker</code> et <code>front</code> sont donc des namespaces :</p>
<ul>
<li><code>docker</code> inclut une Taskfile <em>si elle existe</em></li>
<li><code>front</code> inclut la Taskfile spécifiée, et exécute ses tâches dans le répertoire <code>dir</code></li>
</ul>
<p>Ces inclusions ajoutées, les tasks qui y sont déclarées seront exécutables avec <code>task {namespace}:{task}</code> (par exemple :
<code>task docker:up</code>).</p>
<h3 id="gestion-des-variables">Gestion des variables</h3>
<p>Les variables se déclarent à plusieurs niveaux :</p>
<ul>
<li>globales sous la clé <code>vars</code> en tête du fichier</li>
<li>locales au niveau d&rsquo;une tâche</li>
<li>les arguments passés après <code>--</code> (<code>task ma_tache -- mon_argument</code>), lus avec <code>.CLI_ARGS</code></li>
<li>ou directement à l&rsquo;appel de la tâche avec <code>task ma_tache VAR=ma_valeur</code></li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">vars</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">USER</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;Julien&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">tasks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">greet</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cmd</span><span class="p">:</span><span class="w"> </span><span class="l">echo &#34;Hello {{ .USER }} !&#34;</span><span class="w"> </span><span class="c"># task greet affichera &#34;Hello Julien !&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">hello-world</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">vars</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">USER</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;world&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cmd</span><span class="p">:</span><span class="w"> </span><span class="l">echo &#34;Hello {{ .USER }} !&#34;</span><span class="w"> </span><span class="c"># task hello-world affichera &#34;Hello world !&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># Il s&#39;agit là d&#39;un exemple naïf : tout input doit être validé.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">custom-greet</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cmd</span><span class="p">:</span><span class="w"> </span><span class="l">echo &#34;Hello {{ .CLI_ARGS }} !&#34;</span><span class="w"> </span><span class="c"># task custom-greet -- &#34;test&#34; affichera &#34;Hello test !&#34;</span></span></span></code></pre></div><p>Notons au passage que la syntaxe <code>{{ }}</code> est celle des templates Go, enrichie de <a href="https://sprig.taskfile.dev/">slim-sprig</a> :
elle supporte donc conditions, boucles et filtres.</p>
<p>Il y a une spécificité au sujet de la priorité des variables : une variable déclarée localement l&rsquo;emporte sur une
variable globale ou surchargée :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">task greet <span class="nv">USER</span><span class="o">=</span><span class="s2">&#34;Bob&#34;</span> <span class="c1"># =&gt; Hello Bob !</span>
</span></span><span class="line"><span class="cl">task hello-world <span class="nv">USER</span><span class="o">=</span><span class="s2">&#34;Bob&#34;</span> <span class="c1"># =&gt; Hello world !</span></span></span></code></pre></div><p>Quant aux <code>{{.CLI_ARGS}}</code>, s&rsquo;ils ne sont pas exploités dans la commande, ils seront ignorés. Il est en revanche
nécessaire de les valider en situation réelle, sinon la valeur passée est injectée telle quelle et on risque de passer
un mauvais moment.</p>
<p>Et &ldquo;Quid des .env&rdquo; me direz-vous ?
Eh bien :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">dotenv</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s1">&#39;.env&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;.env.docker&#39;</span><span class="p">]</span></span></span></code></pre></div><p>Et vos variables d&rsquo;environnement sont chargées.</p>
<h3 id="dépendances-et-préconditions">Dépendances et préconditions</h3>
<p>Deux propriétés différentes : d&rsquo;abord <code>deps</code> :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">tasks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">hello</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cmd</span><span class="p">:</span><span class="w"> </span><span class="l">echo &#34;Hello&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">world</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cmd</span><span class="p">:</span><span class="w"> </span><span class="l">echo &#34;World&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">greet</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">deps</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">hello, world]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cmd</span><span class="p">:</span><span class="w"> </span><span class="l">echo &#34;The world was hello-ed !&#34;</span></span></span></code></pre></div><p>Les deux tâches déclarées dans <code>deps</code> seront exécutées <em>avant</em> la <code>cmd</code> de la tâche appelée. Deux subtilités :</p>
<ol>
<li>Ces tâches sont exécutées en parallèle, donc la sortie pourrait être <code>Hello World</code> comme <code>World Hello</code></li>
<li>Si une des <code>deps</code> échoue, alors <code>task greet</code> échouera</li>
</ol>
<p>Ensuite <code>preconditions</code> déclare des conditions à remplir pour que la tâche s&rsquo;exécute. Si l&rsquo;une échoue, Taskfile affiche le
<code>msg</code> et s&rsquo;arrête avec une erreur, sans lancer la commande :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">tasks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">check-env</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">preconditions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">sh</span><span class="p">:</span><span class="w"> </span><span class="l">test -f .env</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">msg</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Missing .env !&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cmd</span><span class="p">:</span><span class="w"> </span><span class="l">echo &#34;.env found.&#34;</span></span></span></code></pre></div><p>Sans <code>.env</code>, <code>task check-env</code> affiche « Missing .env ! » et <code>cmd</code> ne sera pas exécutée.</p>
<h2 id="pour-finir">Pour finir</h2>
<p>Vous avez une Makefile et elle répond à vos besoins ? Eh bien : parfait.</p>
<p>En revanche, si vous commencez un nouveau projet, ou que vos besoins évoluent, alors la migration vers une Taskfile a
son intérêt : l&rsquo;évolutivité, la lisibilité et la facilité de prise en main. Je n&rsquo;ai pas couvert, dans cet article, la
moitié des features et des cas d&rsquo;usage (allez voir <a href="https://www.taskfile.dev">la doc officielle</a> si vous êtes curieux).
Au-delà de son apport évident en tant que task runner, Taskfile est aussi un excellent outil de facilitation et de
documentation par son côté explicite. Et fini, le petit margoulin qui vous
dira que la Makefile ne fonctionne pas parce qu&rsquo;il a décidé de rester sous Windows.</p>
<p>La migration de l&rsquo;un vers l&rsquo;autre est suffisamment rapide pour que ça vaille le coup de tester. Dans le pire des cas,
c&rsquo;est une petite heure à faire du YAML (ce qui est, de toute façon, ce que l&rsquo;on fait le plus souvent).</p>
<p>Une mise en garde, tout de même : le but d&rsquo;une Taskfile n&rsquo;est pas de remplacer un script dédié, mais de se faciliter la
vie. Il peut être tentant d&rsquo;y mettre des <code>preconditions</code> complexes ou des variables dynamiques avancées avec le
templating intégré, mais c&rsquo;est contre-productif.</p>
<h2 id="approfondir-en-savoir-plus">Approfondir, en savoir plus</h2>
<ul>
<li><a href="https://taskfile.dev/docs/">La documentation</a></li>
<li><a href="https://blog.stephane-robert.info/docs/developper/autres-outils/build/task/">Blog de Stéphane Robert</a></li>
</ul>
]]></content:encoded></item></channel></rss>