Un générateur de site statique - retour accueil
Anonyme, en lecture seule : git clone git://bebou.netlib.re/catium
Authentifié·e, en écriture : git clone ssh://git@bebou.netlib.re:1459/srv/git/catium
Log | Files | Refs | README | zip | tar.gz |
CONSTRUIRE (26428B)
1 # Comment vous aussi vous auriez pu construire catium
2
3 Imaginez vouloir construire une site et être doté.e d’un outil qui traduit le
4 format dans lequel vous préférez écrire en html. Peut-être que vous aimez
5 écrire en markdown et utilisez pandoc, cmark[^1] ou même le [script initial de
6 John Gruber](https://daringfireball.net/projects/markdown/). Peut-être que vous
7 aimez écrire en asciidoc et utilisez asciidoctor. Peut-être que vous avez
8 votre propre format avec votre propre traducteur ! Comment, avec l’aide des
9 outils Unix, créer votre propre générateur de sites statiques ?
10
11 ## Générer une page
12
13 ### Les gabarits html et les métadonnées
14
15 Vous avez un fichier index.md comme ceci :
16
17 # Super document cool
18
19 Blablabla
20
21 * super liste
22 * trop cool
23
24 et pouvez le convertir en faisant cmark index.md :
25
26 <h1>Super document cool</h1>
27 <p>Blablabla</p>
28 <ul>
29 <li>super liste</li>
30 <li>trop cool</li>
31 </ul>
32
33 Premier problème, si vous avez pour ambition d’avoir un site décemment
34 construit vous remarquerez qu’il manque de l’html. On pourrait vouloir ajouter
35 la balise `<html>` et `<meta>` pour l’encodage utf-8 par exemple. Ici
36 habituellement deux solutions s’offrent à vous. Soit vous comptez sur votre
37 traducteur pour le faire. C’est le cas par exemple de lowdown avec son option
38 `-s` (pour standalone) mais tous ne le font pas :
39
40 <<-. lowdown -s
41 # titre
42
43 [lien](katzele.netlib.re)
44 .
45
46 qui donne
47
48 <!DOCTYPE html>
49 <html>
50 <head>
51 <meta charset="utf-8" />
52 <meta name="viewport" content="width=device-width,initial-scale=1" />
53 <title>Untitled article</title>
54 </head>
55 <body>
56
57 <h1 id="titre">titre</h1>
58
59 <p><a href="katzele.netlib.re">lien</a></p>
60 </body>
61 </html>
62
63 soit vous utilisez un générateur de site existant. En réalité une troisième
64 s’offre à vous ! Résoudre ce problème à l’aide d’un outil que vous avez déjà
65 sous la main, le shell.
66
67 Il existe en shell posix une syntaxe nommée here-document ou heredoc[^2]. Cette
68 syntaxe permet de créer des sortes de gabarits (ou templates ou layouts) de la
69 sorte :
70
71 title="machin"
72 <<delim cat
73 blabla
74 le titre : $title
75 blabla
76 delim
77
78 donne
79
80 blabla
81 le titre : machin
82 blabla
83
84 On notera que les variables shell appelées à l’intérieur sont étendues. Et bien
85 c’est également le cas des captures de commandes type `$(pwd)` ! On peut donc
86 imaginer l’utiliser pour insérer notre sortie de traducteur au sein d’un
87 gabarit html :
88
89 title="Un article super"
90 <<delim cat
91 <!DOCTYPE html>
92 <html>
93 <head>
94 <meta charset="utf-8" />
95 <meta name="viewport" content="width=device-width,initial-scale=1" />
96 <title>$title</title>
97 </head>
98 <body>
99 $(cmark index.md)
100 </body>
101 </html>
102 delim
103
104 qui donne
105
106 <!DOCTYPE html>
107 <html>
108 <head>
109 <meta charset="utf-8" />
110 <meta name="viewport" content="width=device-width,initial-scale=1" />
111 <title>Un article super</title>
112 </head>
113 <body>
114 <h1>Super document cool</h1>
115 <p>Blablabla</p>
116 <ul>
117 <li>super liste</li>
118 <li>trop cool</li>
119 </ul>
120 </body>
121 </html>
122
123 Hop pas besoin d’outils extérieur.
124 Si l’on fait de ce code un script on pourrait l’exécuter pour générer l’html.
125 Evidemment ce serait limité puisque l’on aurait toujours la sortie du fichier
126 index.md. On peut donc décider de passer en argument le nom du fichier markdown
127 et le titre tant qu’à faire. On aurait donc un script du type :
128
129 title="$1"
130 <<delim cat
131 [...]
132 $(cmark "$2")
133 [...]
134 delim
135
136 Qu’on appelerait en faisant `./script "Un article super" ./index.md`. Si nos
137 besoins ne vont pas plus loin nous avons déjà quelque chose d’à peu près
138 fonctionnel. Pour rendre tout cela simple d’usage et mieux encapsuler
139 l’information nous allons pousser plus loin.
140
141 Vous remarquerez ici que l’information du titre réside dans votre tête, à
142 l’écriture de la commande et non pas, comme il est préférable, dans le document
143 lui-même. L’idéal serait qu’index.md puisse contenir toutes ses informations,
144 le markdown du corps de l’article mais également les méta-données afférentes.
145 C’est comme cela que fonctionnent la plupart des générateurs de site avec des
146 syntaxes propres. On voudrait pouvoir, par exemple, écrire :
147
148 title: "Un super article"
149 ----
150
151 # Super document cool
152 [...]
153
154 et que la variable title de notre script prenne pour valeur “Un super article”.
155 Pour ce faire on pourrait modifier notre script pour parser index.md et en
156 récupérer l’info que l’on souhaite. Par exemle
157
158 title=$(grep '^title:' | cut -d':' -f2)
159
160 donnerait la bonne valeur à la variable et
161
162 $(sed -n '/----/,$ p` "$1" | grep -v '^----' | cmark)
163
164 parserait uniquement la partie markdown en ométtant les méta-données. C’est
165 pourtant sur une autre solution que nous allons partir, une solution qui nous
166 permettera plus de flexibilité par la suite et surtout la possibilité de
167 dynamiquement créer certaines parties du contenu markdown.
168
169 ### le format “à la catium” et la prolifération des scripts shell
170
171 Cette autre solution consiste à faire du document index.md lui même un script. Et oui, dans catium, si c’est pas une makefile, c’est un script shell. Comme le dit le meme :
172
173 shsh
174 shsh shsh
175 shsh
176 shshsh shsh shsh shshshshsh shshsh shshsh shsh shshsh shsh shsh
177 shsh shsh shsh shshsh shsh shsh shsh shsh shshsh shshsh shsh
178 shsh shsh shsh shsh shsh shsh shsh shsh shsh shsh
179 shsh shsh shsh shsh shsh shsh shsh shsh shsh shsh
180 shshsh sh shshsh shshsh shsh sh shsh shsh shshsh shsh shsh shsh
181 shshshsh shshsh shshsh shshshsh shshsh shsh shshsh shsh shsh shshsh
182
183 Wait it's all shell scripts ?
184 /
185 🚶🔫🚶 - Always has been
186
187 A ce stade on peut renommer index.md en index.sh, ça fera plus sens. L’idée est
188 dorénavant que notre script devienne une sorte d’interpréteur d’index.sh. On
189 l’appelera dorénavant “page”. Il devra préparer le nécessaire pour que la
190 syntaxe de la page index.sh instancie les bonnes variables et génère le
191 nécessaire pour l’affichage final de l’html.
192
193 Par exemple, on pourrait grâce à un alias, cacher derrière la syntaxe `title: "Un super article"` une fonction qui créé la variable title. Pour cela on peut écrire :
194
195 alias title:="title"
196 title() title="$*"
197
198 Ainsi, dans index.sh :
199
200 title: "Un super article"
201 # devient
202 title "Un super article"
203 # devient
204 title="Un super article"
205
206 index.sh a créé sa propre variable. Ce système est généralisable à n’importe
207 quelle métadonnée. Si vous testez cette articulation tel quel vous rencontrerez
208 un problème. En effet, l’alias déclaré dans l’interpréteur page n’est pas
209 disponible dans le script index.sh si on l’exécute en écrivant `./$1` et en
210 faisant `./page index.sh` par exemple. C’est parce que en le faisant de cette
211 manière là, index.sh est exécuté dans un sub-shell, sur un processus différent
212 ne partageant aucune variable, alias ou fonction en commun avec son parent.
213 Pour contourner ce problème on peut utiliser le built-in shell .[^3] qui prend en
214 argument le chemin d’un fichier. Cette fonction va dérouler le code du fichier
215 dans le script courant et l’exécuter comme s’il y avait été écrit. Le code dans
216 index.sh aura donc bien accès à ce qui a été déclaré dans l’interpréteur page.
217 Ainsi `./page index.sh` fera :
218
219 alias title:="title"
220 title() title="$*"
221
222 . "$1"
223
224 <<delim cat
225 <!DOCTYPE html>
226 <html>
227 <head>
228 <meta charset="utf-8" />
229 <meta name="viewport" content="width=device-width,initial-scale=1" />
230 <title>$title</title>
231 [...]
232 delim
233
234 deviendra
235
236 alias title:="title"
237 title() title="$*"
238
239 title: "Un super article"
240
241 [...]
242
243 <<delim cat
244 <!DOCTYPE html>
245 <html>
246 <head>
247 <meta charset="utf-8" />
248 <meta name="viewport" content="width=device-width,initial-scale=1" />
249 <title>$title</title>
250 [...]
251 delim
252
253 et `title: "Un super article"` se déroulera comme décrit précedemment et ainsi
254 de suite.
255
256 Super ! On sait comment gérer les métadonnées. En réalité on vient d’inventer
257 une sorte de [DSL](https://en.wikipedia.org/wiki/DSL) qui ressemble
258 volontairement beaucoup à du [YAML](https://en.wikipedia.org/wiki/YAML) très
259 simple. Cette syntaxe est devenu très commune dans les générateurs de sites
260 statiques.
261
262 Maintenant qu’on a trouvé notre alternative à `title=$(grep '^title:' | cut
263 -d':' -f2)` il nous faut trouver celle à `$(sed -n '/----/,$ p “$1” | grep -v
264 ‘—-’ | cmark)`. Si on ne le fait pas l’exécution d’index.sh aura vite fait de
265 nous faire remarquer que le premier mot de notre page n’est pas une commande.
266
267 Pour cela utilisons la même logique que pour les métadonnées. Imaginons une
268 syntaxe qui nous convienne et créons, en amont dans l’interpréteur le
269 nécessaire pour que cette syntaxe soit comprise et génère le bon contenu. Pour
270 pouvoir dire “attention, ce qui suit c’est du markdown” on peut réutiliser les
271 heredoc. Quelque chose de la sorte devrait générer l’html :
272
273 title: "Un article super"
274
275 <<delim cmark
276 # Ceci est du markdown
277
278 blabla
279 delim
280
281 Sauf qu’il nous faut capturer la sortie de cmark pour pouvoir ensuite
282 l’afficher au bon endroit dans le heredoc template qui suit dans l’interpréteur
283 page. On pourrait le mettre dans une variable en l’entourant d’une capture de
284 commande :
285
286 corps=$(<<delim cmark
287 # Ceci est du markdown
288
289 blabla
290 delim
291 )
292
293 et dans le template faire
294
295 <main>
296 $(echo "$corps")
297 </main>
298
299 mais vous conviendrez que ça n’est pas très élégant (et peut-être sensible au
300 quoting hell ?). On va donc opter pour mettre le résultat dans un fichier
301 temporaire.
302
303 <<delim cmark > A
304 # Ceci est du markdown
305
306 blabla
307 delim
308 [..]
309 <main>
310 $(cat A)
311 </main>
312
313 ce qui permet d’adopter une syntaxe plus sympathique à l’aide d’un nouvel alias :
314
315 section="<<endsection cmark > A"
316 [...]
317 section
318 # Ceci est du markdown
319
320 blabla
321 endsection
322
323 En ajoutant un petit `rm` A à la fin du script page on peut générer notre page
324 html en faisant `./page index.sh` sans souci. index.sh pourra se lire
325 convenablement bien et contiendra les infos à son propos en son sein. La
326 totalité du fichier jusqu’ici est :
327
328 title: "Un super titre"
329
330 section
331 # Ceci est du markdown
332
333 blabla
334 endsection
335
336 Avec ces éléments nous avons, à mon avis, tous les éléments d’un générateur de
337 site très simples. Afin d’organiser un peu les choses on pourrait déporter le
338 heredoc du layout dans un fichier à part, déclarant une fonction `layout`,
339 exécuté avec le builtin . et dont la fonction serait appelé à la fin. Cela
340 permet de séparer le layout du reste, le “fond” et la “forme” d’une certaine
341 manière.
342
343 On a donc un fichier de layout, nommé html et dans le dossier layouts :
344
345 layout {
346 <<@@ cat
347 <!DOCTYPE html>
348 <html>
349 <head>
350 <meta charset="utf-8" />
351 <meta name="viewport" content="width=device-width,initial-scale=1" />
352 <title>$title</title>
353 </head>
354 <body>
355 $(cat A)
356 </body>
357 </html>
358 @@
359 }
360
361 Le fichier index.sh comme décrit juste au dessus et le script page :
362
363 alias title:="title"
364 title() title="$*"
365
366 . "$1"
367
368 . layout/html
369 layout
370 rm A
371
372 Si les sources sont dans contents et les fichiers cibles dans public il
373 suffirait ensuite de créer un script qui appellerait `./page X.sh > X.html` sur
374 toutes les pages sh nécessaires, avec `X` le nom du fichier pour, construire le
375 site.
376
377 Il nous reste quelques améliorations à apporter à notre générateur.
378
379 ### L’encapsulation et les sections
380
381 Premièrement, il manque une information dans le fichier index.sh, celle de son
382 interpréteur. De fait, le fichier est un script ne s’exécutant correctement que
383 s’il est passé en argument d’un autre[^4]. Il serait bon de renseigner cette
384 information dans le fichier. Pour ce faire nous allons lui ajouter le shebang :
385
386 #! page
387
388 Ainsi il est possible de l’exécuter en faisant directement `./index.sh` ce qui en
389 réalité, du fait du fonctionnement du shebang, fera `./page ./index.sh` ce qui
390 revient à ce que nous faisions à la main jusque là. Cette façon de faire est,
391 selon moi, plus élégante, mais permet également de savoir que cette page a été
392 écrite pour être interprétée par un script nommé page ce qui, à défaut de
393 l’avoir, est toujours une bonne information à prendre. Ce système peut être
394 utilisé pour créer des typologies de pages (article, notes etc) qui seraient
395 générées de manières différentes.
396
397 En réalité dans catium c’est le shebang
398
399 #! /usr/bin/env ./page
400
401 qui est utilisé pour des raisons obscures de compatibilité avec les *BSD et les
402 mac[^5].
403
404 Deuxièmement, comment faire si l’on souhaite, sur une même page, écrire deux
405 blocs de markdown qui doivent aller à deux endroits différents dans le layout.
406 Ce besoin est très courant dans les pages avec une structure plus complexe, du
407 type à avoir des div un peu partout, de façon à avoir une présentation moins
408 linéaire à l’aide de css. Pour répondre à ce besoin que l’on a estimé être
409 suffisamment important pour être intégré au générateur dans sa version la plus
410 simple il faut ajouter un peu de complexité dans l’interpréteur page.
411
412 On souhaiterait pouvoir écrire dans index.sh :
413
414 section: truc
415 ## Ce markdown va aller dans la section truc du layout
416 endsection
417
418 section: bidule
419 ## Ce markdown va aller dans la section bidule du layout
420 endsection
421
422 et par exemple dans le layout :
423
424 layout {
425 <<@@ cat
426 [...]
427 <div id="joli-id-pour-du-css">
428 $(show truc)
429 </div>
430 [...]
431 <div id="autre-id-pour-du-css">
432 $(show bidule)
433 </div>
434 @@
435 }
436
437 On sent intuitivement qu'il faut passer une sorte d’argument à notre alias
438 section[^6]. Il faudrait pouvoir, sur la base de cet argument, stocker les données
439 de chacune des sections pour pouvoir les appeler séparément dans le layout.
440 Pour pouvoir traiter cette donnée comme un argument l’alias ne suffira plus, il
441 faudra passer par une fonction qu’on pourrait par exemple nommer `save` :
442
443 alias section:="<<endsection save"
444 save() cat >> "$1"
445
446 Ainsi dans index.sh :
447
448 section: truc
449 blabla
450 endsection
451
452 # Devient
453
454 <<endsection save truc
455 blabla
456 endsection
457
458 # Puis
459
460 <<endsection cat >> truc
461 blabla
462 endsection
463
464 On peut ainsi ouvrir et fermer autant de sections que l’on veut, y compris
465 plusieurs fois la même grâce à l’opérateur de redirection `>>` qui concatène
466 plutôt que ne remplace.
467 Il faut dorénavant que la fonction que l’on appelle dans le layout pour
468 “injecter” les données d’une section à un endroit particulier fasse la
469 traduction du format vers de l’html. On écrit donc une fonction `show` comme ceci
470 :
471
472 show() cmark "$1"
473
474 Cette fonction va donner tout le contenu enregistré dans la section qu’on lui
475 passe en argument (main, footer, comme vous voulez) à `cmark` et en ressortira de
476 l’HTML.
477
478 Afin d’éviter que les fichiers ne se marchent sur les pieds si l’on a plusieurs
479 articles, de polluer l’espace de travail et de potentiellement engorger la
480 mémoire de votre ordinateur on peut faire en sorte que tout cela se produise
481 dans un dossier temporaire, sur des fichiers temporaires tous détruits une fois
482 la génération finie. Pour cela on créé au début de page un dossier temporaire
483 de travail avec `mktemp`, on dit au shell de le supprimer si le processus reçoit
484 le signal `EXIT` (une fois qu’il termine quoi) :
485
486 tmpdir=$(mktemp -d)
487 trap "rm -rf $tmpdir" EXIT
488
489 Il ne nous manque plus qu’à adapter `save` et `show` pour prendre en compte ce
490 nouveau dossier :
491
492 save() cat >> "$tmpdir/$1"
493 show() cmark "$tmpdir/$1"
494
495 Et voilà, à une exception près[^7] vous avez recréé absolument tout Catium dans
496 sa version non étendue. Bravo !
497
498 ## Automatiser la génération : le makefile
499
500 Il y aurait pleins de manières de partir de cet existant et d’automatiser la
501 création d’un site de plusieurs fichiers .sh. Un simple script shell pourrait
502 faire l’affaire. Cependant, pour des raisons pédagogique et, potentiellement de
503 performances, nous avons opté pour make.
504
505 Make est un programme spécifiquement créé pour gérer la construction de
506 logiciels. Initialement fait pour compiler des programmes en C à une époque où
507 les ressources étaient assez rares, make permet à l’utilisateurice de déclarer
508 les règles de dépendances entre les différents éléments de son logiciel. make
509 créera avec ces règles un graph de dépendances lui permettant, lorsqu’il est
510 appelé, de ne recréer que le strict nécessaire ainsi économisant du temps de
511 calcul. Sur le principe la compilation de logiciels et la construction d’un
512 site statique ne sont pas différents. On peut donc faire usage de make pour
513 orchestrer la génération de notre site.
514
515 Tout le nécessaire pour que make puisse fonctionner doit être inscrit dans un
516 fichier nommé makefile à la racine du projet.
517
518 Catium utilise Gnu Make et quelques syntaxes lui sont très spécifiques. A
519 l’avenir un effort sera peut-être consenti pour génériciser la syntaxe.
520
521 ### Le but
522
523 Le but est que make fasse automatiquement les appels aux articles et place
524 leurs sorties dans les bons fichiers de destinations. Par exemple,
525
526 ./contents/index.sh > public/index.html
527
528 Et ainsi pour tous les fichiers .sh qui existent dans l’arborescence à
529 l’intérieur de contents.
530
531 Dans un second temps on souhaite qu’il copie bêtement tous les autres fichiers
532 présents dans l’arborescence comme les images :
533
534 cp contents/super_image.jpg public/super_image.jpg
535
536 Dans notre cas tout simple les fichiers .css et le favicon seront gérés de la
537 même manière que tous les images n’étant pas des .sh.
538
539 ### Lister nos fichiers sources
540
541 La première étape est de lister toutes les sources de notre site. On souhaite faire des choses différentes selon que le fichier soit un .sh ou pas alors on créé deux variables. Dans sources on met la liste des fichiers sh, dans annexes les autres :
542
543 sources != find contents -type f -name '*.sh'
544 annexes != find contents -type f -not -name '*.sh'
545
546 La syntaxe `!=` dit à make que ce qui suit est une commande à faire exécuter
547 par le shell et non pas des fonctions make. On utilise donc ici le find que
548 vous pourriez utiliser dans votre terminal en temps normal.
549
550 ### Créer les cibles
551
552 Une fois ces deux listes obtenues, on cherche à créer deux nouvelles listes
553 contenant les chemins des fichiers correspondants à faire servir par le serveur
554 web. On les appellera fichiers cibles. Par exemple, si l’on a le chemin
555 “contents/blog/article.sh” dans la variable sources, il faudra créer le chemin
556 “public/blog/article.html”. Autrement dit on remplace contents par public et
557 l’extension .sh par l’extension .html. Pour les autres fichiers seul le premier
558 dossier du chemin est à changer.
559
560 Pour le faire on fait usage des “substitutions references” de GMake dont la
561 syntaxe est :
562
563 variablecréée = ${variableinitiale:abc%uvw=efg%xyz}
564
565 Ici, pour toutes les lignes dans la variableinitiale, make cherchera les
566 caractères `abc` et `uvw` et attribuera tout ce qu’il y a entre à `%`. Il
567 remplacera ensuite tous les `abc` par `efg` et les `uvw` par `xyz` en
568 conservant la partie `%`. Ainsi, si l’on reprend notre exemple précédent on
569 identifie que la partie commune (`%`) est `blog/article` et qu’il faut changer
570 ce qu’il y a avant et après. On peut donc écrire :
571
572 pageshtml = ${sources:contents/%.sh=public/%.html}
573 annexescibles = ${annexfiles:contents/%=public/%}
574
575 Vous voyez que pour les annexes pas besoin de modifier l’extension donc pas
576 besoin de la partie après le `%`. On a donc dorénavant dans `pageshtml` et
577 `annexescibles` la liste des chemins des cibles.
578
579 ### Écrire les règles
580
581 C’est maintenant que la partie intéressante de make commence, les règles ! La
582 syntaxe générale d’une règle est la suivante :
583
584 cible : liste dépendances ; commandes
585
586 ou
587
588 cible : liste dépendances
589 commandes
590
591 Pour chacun des chemins de cibles construits précédemment make va chercher une
592 règle permettant de construire le fichier en question. Pour cela il cherche une
593 règle ayant pour cible le même chemin. Une fois qu’il a trouvé une règle qui
594 correspond, il vérifie si la cible existe.
595
596 * Si elle n’existe pas il cherche à construire toutes ses dépendances puis
597 exécute la commande.
598 * Si la cible existe mais que certaines de ses dépendances n’existent pas il
599 va les construire d’abord et passer au point suivant.
600 * Si la cible existe déjà et que les dépendances existent il vérifie si ses
601 dépendances ont été modifiées après la dernière modification de la cible.
602 Si oui alors make suppose que la cible n’est pas à jour (ses sources sont
603 plus récentes qu’elle même) et make relance la commande.
604
605 A noter que tout cela se fait de façon récursive. Quand make tente de créer une
606 dépendance il suit les mêmes règles. Si la dépendance n’existe pas il faut donc
607 qu’il existe une règle pour la créer.
608
609 Pour créer une page html la règle pourrait être la suivante :
610
611 public/blog/article.html : contents/blog/article.sh layouts/html
612 @mkdir -p public/blog
613 contents/blog/article.sh > public/blog/article.html
614
615 Vous noterez que la cible dépend de layouts/html de façon à ce qu’elle soit
616 reconstruite si le layout change. Ajouter un `@` devant une commande permet
617 de ne pas l'afficher dans la sortie lorsqu'on lancera make.
618
619 Vous pourriez penser “mais on ne va pas écrire les règles pour toutes les pages
620 non ?” et vous auriez raison. Il est possible d’écrire des règles génériques
621 pouvant matcher plusieurs chemins de plusieurs cibles. Par exemple, la règle
622 suivante conviendra pour créer tous les fichiers html :
623
624 public/%.html : contents/%.sh layouts/html
625 @mkdir -p $(shell dirname $@)
626 $< > $@
627
628 De la même manière qu’auparavant on généricise les chemins à l’aide de `%`. On
629 récupère le chemin du dossier du fichier cible avec dirname et on fait usage
630 des variables automatiques `$<` et `$@` faisant références respectivement à la
631 première dépendance et la cible. Pour éviter que la commande de création du
632 dossier de destination s’affiche dans la console lorsque l’on construira le
633 site on peut la préfixer d’un `@`.
634
635 Pour les autres fichiers, de façon similaire :
636
637 public/% : contents/%
638 @mkdir -p $(shell dirname $@)
639 cp $< $@
640
641 Si vous écrivez un makefile de la sorte et tenter de l’exécuter vous vourrez
642 qu’il ne créera pas tout le site. Make créé les cibles qu’on lui donne en
643 argument. `make public/index.html` déclenchera la règle correspondant à cette
644 cible si elle existe. Cela dit sans argument make créé uniquement la cible de
645 la première règle qu’il rencontre dans le makefile, dans l’ordre du fichier.
646 Comment, en sachant cela, forcer la création de toutes les cibles dans les
647 variables pageshtml et annexescibles ?
648
649 En utilisant les propriétés des règles décrites précédemment.
650
651 * Si make n’a pas d’argument, la première règle du fichier est déclenchée.
652 * Si la cible d’une règle n’existe pas, il va essayer de la créer. On peut
653 donc en déduire que si la commande de la règle ne créer pas la cible, la
654 règle sera toujours déclenchée.
655 * Make cherche d’abord à créer toutes les dépendances d’une cible avant de
656 créer la cible.
657
658 On en conclu que si l’on écrit une règle qui ;
659
660 * Précède toutes les autres
661 * A une commande qui ne créé pas de fichier ayant pour chemin la cible
662 (aucune commande fera très bien l’affaire)
663 * A pour dépendances toutes les cibles que l’on souhaite construire.
664
665 Alors lorsque l’on lancera make, il cherchera à construire toutes ces cibles à
666 chaque fois.
667
668 Cette règle est très courante dans les makefiles et est généralement nommée par
669 convention “all”, mais elle pourrait s’appeler “toto”. Pour lister les
670 dépendances on peut utiliser les variables déjà existantes :
671
672 all: ${pageshtml} ${annexescibles}
673
674 En utilisant les mêmes propriétés nous allons écrire une règle qu’il faudra
675 appeler volontairement en passant sa cible en argument à make, se déclenchera
676 toujours, et exécutera une commande. Cette règle permettra de nettoyer le
677 dossier de destination “public” :
678
679 clean:; rm -r public/*
680
681 Si elle n’est pas première dans le fichier et que la cible clean n’est une
682 dépendance d’aucune autre règle, elle ne se déclenchera qu’en faisant `make
683 clean`. Puisque la commande ne créé pas de fichier ayant pour chemin la cible
684 alors elle se déclenchera toujours. Cela dit, puisqu’elle n’a pas non plus de
685 dépendances, s’il se trouve qu’un jour un fichier “clean” existe à la racine du
686 projet, il sera considéré comme toujours à jour. Aucune dépendance ne pourra
687 être potentiellement plus récente que lui et la règle ne se déclenchera plus.
688 C’est pour palier ce genre de scénarios qu’il existe une directive `.PHONY` qui
689 permet de lister ce genre de règles[^8]. Ainsi make saura qu’il faut, pour ces
690 règles, ignorer la préexistence de la cible.
691
692 Et voilà, vous avez réécrit le makefile de catium tel qu’il existe aujourd’hui !
693
694 [^1]: que l’on utilisera pour les exemples dans ce document
695 [^2]: documentée ici https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_07_04
696 [^3]: oui le nom de la fonction est un point final `.`.
697 [^4]: quand bien même la syntaxe a été écrite de façon à ce qu’un·e humain·e puisse le comprendre
698 [^5]: Voir: https://www.in-ulm.de/~mascheck/various/shebang/#interpreter-script ou le commit 4bd895
699 [^6]: auquel on a ajouté un : pour ressemble aux autres déclarations
700 [^7]: la ligne [ "$1" ] || set - que je ne m’explique pas
701 [^8]: même s’il est très improbable qu’un fichier all ou clean se faufile dans votre projet, on sait jamais.
702
703