Un générateur de site statique - retour accueil
git clone git://bebou.netlib.re/catium
Log | Files | Refs | README |
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