1 points par GN⁺ 2024-09-22 | 2 commentaires | Partager sur WhatsApp
  • Même si de nouveaux outils de build continuent d’apparaître, les Makefiles restent utiles comme outil de base léger pour regrouper les tâches répétitives d’un projet
  • Grâce à des commandes conventionnelles comme make, make build, make install, make dev, on peut rapidement essayer de builder, installer ou lancer le serveur de développement d’un projet inconnu
  • Que la stack repose sur Jekyll, Hugo, 11ty, un framework web Python, Docker ou gulp, le Makefile joue le rôle d’interface d’exécution commune
  • Sur des projets personnels, quelques cibles simples comme dev, build, deploy suffisent souvent, plutôt que des conditions ou des flags
  • GNU Make est très répandu et, même s’il n’est pas disponible, on peut exécuter directement les commandes shell du Makefile, ce qui en fait une solution peu contraignante même dans des environnements limités

Regrouper des commandes différentes selon les projets derrière un point d’entrée unique

  • Un Makefile sert à envelopper des projets dont les méthodes de build, d’exécution et de déploiement diffèrent derrière un jeu de commandes familier
  • Lorsqu’un projet inconnu contient un Makefile, on peut en général commencer par essayer les commandes suivantes
    • make
    • make build
    • make install
  • Quand on rouvre un ancien projet personnel, on peut aussi le configurer pour que le simple lancement de make dev exécute d’un coup les étapes de build nécessaires et le serveur de développement
  • Même si la stack technique varie — Jekyll, Hugo, 11ty, ou plusieurs frameworks web Python — on peut conserver le même point d’entrée
  • Avec Docker ou gulp également, le Makefile ne remplace pas ces outils mais fonctionne comme une couche d’orchestration
    • Exemple : make build construit les images Docker nécessaires en passant des arguments supplémentaires propres au projet

Une utilisation simple dans les projets personnels

  • L’usage du Makefile se concentre davantage sur des ensembles de commandes shell que sur des fonctionnalités complexes
    • Les fonctionnalités comme les conditions ou les flags ne sont pas utilisées
    • Techniquement, l’unité de travail de make est la cible (target), mais en pratique elle est traitée comme une tâche
    • On pourrait aussi le faire avec des fonctions dans un script bash, mais le Makefile est jugé plus simple et plus rapide
  • Dans un projet personnel, les tâches les plus fréquentes sont généralement au nombre de 2 ou 3
    • dev : démarrer le serveur de développement
    • build : builder le projet si nécessaire
    • deploy : déployer ou publier le projet
    • watch : tâche supplémentaire servant à relancer automatiquement le build quand les fichiers source changent
  • Le Makefile du blog actuel ne contient qu’une seule cible, dev
    • Sous dev:, il exécute npm run dev
  • Dans des projets personnels plus complexes, les tâches suivantes sont regroupées dans le Makefile
    • dev : lancer le serveur de développement avec bundle exec jekyll serve
    • build : builder les assets avec npm run gulp build
    • watch : surveiller un dossier spécifique avec npm run gulp watch
    • deploy : après avoir buildé Jekyll en production, exécuter make encrypt, puis netlify deploy --prod
    • encrypt : chiffrer les fichiers HTML du dossier _site avec npx staticrypt

Limites et avantages à connaître

  • Dans ces exemples, les phony targets ont été omises, mais si des fichiers portent des noms comme dev, build, watch, deploy, encrypt, le Makefile peut ne pas se comporter comme prévu
  • GNU Make est le logiciel qui exécute les makefiles ; il est probablement déjà installé sous Linux et il est si courant sur MacBook qu’on ne se souvient même plus d’avoir dû l’installer séparément
  • Make ne demande pas beaucoup de dépendances supplémentaires, ce qui peut le rendre utile dans des environnements limités où l’installation de paquets est difficile ou impossible pour des raisons de sécurité
  • Même sans Make, on peut copier et exécuter directement dans le shell les commandes présentes dans le Makefile
    • À l’inverse, si gulp n’est pas installé sur le serveur, il est difficile de copier-coller du code JavaScript tel quel dans le terminal pour l’exécuter
  • Même si l’on découvre un nouvel outil de build meilleur et plus rapide, Make peut continuer à servir de couche de commandes commune pour gérer des outils et configurations hétérogènes

2 commentaires

 
kayws426 2024-09-22

Si votre makefile ne définit pas de dépendances, le remplacer par justfile offre une meilleure ergonomie.

 
GN⁺ 2024-09-22
Avis de Hacker News
  • Il n’y a pas lieu de se décourager en lisant, dans ce fil, qu’on « utilise mal make ». L’une des raisons pour lesquelles Make est un bon outil, c’est qu’on peut commencer plus simplement qu’il n’y paraît
    Ne pas utiliser .PHONY peut poser problème, mais sur un petit projet, c’est peut-être un piège dans lequel vous ne tomberez que dans un an, et même alors, il y a de bonnes chances qu’une heure à vous gratter la tête suffise à le régler. Dans 99 % des cas, vous pouvez vous lancer sans vous soucier de la « bonne manière » de faire, et Make ne demande que le niveau de complexité nécessaire pour ne pas s’écrouler

    • L’un des pires aspects de Make, c’est qu’il a l’air simple
      Make prend des fichiers d’entrée et des dépendances pour produire exactement un fichier de sortie. Les règles qui ne produisent pas de sortie, comme install, all ou clean, ainsi que toutes les cibles de l’article, doivent dépendre d’une cible magique spéciale comme .PHONY, mais .PHONY n’existait pas dans POSIX 2017 (IEEE Std 1003.1-2017, https://pubs.opengroup.org/onlinepubs/9699919799/utilities/m...) et n’a été intégré que dans la norme actuelle (IEEE Std 1003.1-2024, https://pubs.opengroup.org/onlinepubs/9799919799/utilities/m...). Si vous voulez créer plusieurs fichiers, comme des fichiers objet, des modules ou des en-têtes précompilés, vous devez bricoler vous-même des contournements fragiles. Tous les Make ne sont pas GNU Make, et des systèmes de type Unix comme BSD ou Solaris/Illumos existent encore
      Cela ne veut pas dire que Make est inutile. Il l’est pour des projets suffisamment complexes, mais pas encore au point de nécessiter un « meilleur » système de build. Le problème, c’est que ces projets gagnent en code et deviennent plus complexes, jusqu’à finir avec des scripts/programmes qui génèrent le Makefile, ou une partie de celui-ci, créant ainsi un méta-système de build improvisé
      Le plus gros problème n’est pas de l’utiliser, mais de le proposer comme solution pour « tout le monde ». Et dès qu’un répertoire ou un fichier comme build ou dev apparaît à la racine du projet, ce Makefile cesse de fonctionner
    • Un exemple de Makefile un peu plus complexe que j’utilise au démarrage d’un nouveau projet TypeScript se trouve ici : https://github.com/borisovg/node-ts-template/blob/main/Makef...
      Aujourd’hui je passe plutôt à pnpm, mais je ne trouve toujours pas cela si complexe. Il faut certes connaître un peu la syntaxe, mais c’est moins difficile que de faire fonctionner correctement d’autres outils dès le départ
    • J’utilise make avec succès depuis plus de 20 ans sur de nombreux projets et dans plusieurs langages, et je n’ai jamais eu de problème avec ces tâches .PHONY dont les gens se préoccupent tant
      C’est simple, lisible, modifiable, composable, et déjà installé partout. Il fait ce qui est écrit en surface, et guère plus
      Au passage, quand je dois gérer des systèmes de build à la mode ou cauchemardesques dans d’autres projets, je les enveloppe aussi avec make
    • Entièrement d’accord. Mieux vaut utiliser l’outil adapté à la tâche, et c’est bien préférable à choisir le mauvais outil pour éviter les critiques perfectionnistes, même si l’on n’est pas expert
      Tout le monde doit commencer quelque part, et une fois que ça tourne, les améliorations progressives s’ajoutent facilement
    • Chaque recette de makefile devrait produire exactement un artefact de sortie, $@. Le Makefile dans son ensemble peut produire un nombre arbitraire de sorties, puisque les règles peuvent dépendre d’autres règles
      Il en découle une règle empirique propre pour les cibles phony. Si une recette ne touche pas uniquement $@, alors $@ doit être marqué comme phony. Gérer les cibles phony sous forme de liste rend les choses beaucoup plus simples
      phonies :=
      phonies += something
      something:
      ./do-something
      phonies += something-else
      something-else: something
      ./do-something-else
      # touches $@ and thus does not need to be phony
      create-file:
      ./generate-some-output > $@
      .PHONY: $(phonies)
  • Makefile est une technologie médiocre. Simplement, elle est un peu moins mauvaise que la plupart des autres systèmes de build que nous avons créés, ce qui la rend utile dans un sens masochiste.
    Les systèmes de build sont généralement soit trop rudimentaires et deviennent chaotiques dès qu’on construit autre chose qu’un jouet, soit trop complexes avec trop de prérequis, de bureaucratie, de synchronisation et de boilerplate, soit dépourvus de bibliothèque standard, si bien que les mêmes patterns de build se dispersent en 10 000 implémentations incompatibles, soit trop contraignants et obligent à migrer quand les besoins augmentent, soit remplis de magie, soit dotés d’une syntaxe obscure et incohérente.

    • À mon avis, Makefile est une excellente technologie ; c’est juste que beaucoup de gens ne l’ont pas vraiment apprise ou ne l’utilisent pas comme prévu.
      Plus de la moitié des logiciels que j’utilise reposent seulement sur un Makefile ordinaire et parfois un script configure. Sans autotools, on lance éventuellement ./configure, puis make, make install, et ça marche. Ce n’est absolument pas une configuration jouet : c’est composé de petits programmes qui font bien une seule chose.
      Make et son mode de fonctionnement sont faciles à comprendre, du moins pour moi. Je pense que les mauvaises expériences des gens viennent souvent davantage de mauvaises configurations de build dans des projets complexes, ou d’un manque de connaissances nécessaires, que de Make lui-même.
      L’absence de bibliothèque standard vise justement l’indépendance vis-à-vis des outils, ce qui permet de l’appliquer largement à divers outils, langages et usages. Je vois ça comme une fonctionnalité, pas comme un défaut. La couche simple, c’est le suivi des dépendances ; les cibles et dépendances sont également explicites, donc difficile d’y voir quelque chose de magique. La syntaxe, elle, pourrait clairement être meilleure.
    • Les pires systèmes de build sont ceux conçus autour d’un langage de programmation précis. Si le nombre de langages est N>>1, le nombre de systèmes de build devient aussi N>>1, et la charge cognitive devient énorme et ne passe pas à l’échelle.
      Les seuls systèmes de build généralistes couvrant plusieurs langages sont make ou ceux qui ciblent make, comme CMake. La raison pour laquelle ce n’est pas terrible, c’est que make n’est pas terrible. make est vraiment difficile à bien utiliser, il y a des problèmes comme le make récursif et non récursif, beaucoup de variantes incompatibles comme Unix/POSIX make, BSD make, GNU make, Windows nmake, et c’est assez laid.
      Malgré tout, un make bien utilisé est plutôt bon. C’est une vraie chance d’avoir make comme plus petit dénominateur commun.
    • D’accord. Beaucoup d’alternatives se concentrent elles aussi sur un langage précis, au lieu d’être comme Make un outil généraliste pour « exécuter quelque chose ».
      C’est assez sidérant que Make ne gère même pas proprement les sous-répertoires. Je me demande s’il existe mieux que Make. Il y a bien Ninja, mais il n’a pas été conçu pour être écrit directement par des humains.
    • Peut-on vraiment dire que ce chaos n’est « très hypothétiquement » absolument pas dû à des compilateurs ou à des langages sans modules ?
      À quel point, selon toi ? 20 %, 40 %, peut-être 70 %, non ?
    • Avec « plus d’un », il y a beaucoup de marge. Mon estimation est la suivante : Makefile n’est pas trop rudimentaire. Il peut devenir trop complexe, mais cela dépend de la façon dont on le conçoit. Pour la bibliothèque standard, il y a quand même des fonctions intégrées. Je ne l’ai jamais trouvé trop contraignant, donc je dirais non. Comme les cibles et les dépendances sont explicites, il n’y a pas non plus beaucoup de magie. En revanche, la syntaxe pourrait clairement être meilleure ; même un simple fichier JSON ordinaire serait sans doute préférable.
  • J’aime vraiment Make. Ces 20 dernières années, j’en ai écrit au moins un pour chaque tâche ou projet auquel j’ai touché.
    Rien d’intelligent : juste un assemblage de bouts avec quelques variables. Des choses comme make run, make test, make lint.
    make recent lance le lint puis exécute le script modifié le plus récemment. On pourrait le faire en Bash ou avec un autre shell, mais on se ferait alors aspirer dans le monde des développeurs, avec plus de complexité et aucune valeur ajoutée. Make n’est qu’un langage dédié à un domaine qui dit : « ce fichier se fabrique à partir de ces fichiers, et à ce moment-là on exécute une ou deux commandes ». C’est ce qui le rend extrêmement puissant.

    • J’aime bien la formulation « ce fichier se fabrique à partir de ces fichiers, et à ce moment-là on exécute une ou deux commandes ».
      Il y a des décennies, j’ai créé un framework de test en Java, où l’on pouvait spécifier les tests et les classes dépendantes avec une syntaxe proche de make. Il y avait un ensemble de classes de test définissant un « lot de tests de référence », puis d’autres couches de classes de test qui en dépendaient et ne s’exécutaient que si l’étape précédente réussissait.
      Je ne comprends pas pourquoi aujourd’hui on rend tout si compliqué. J’ai toujours conseillé de s’en tenir aux outils Unix standards et à leur approche, c’est-à-dire à des méthodes éprouvées par le temps, sauf dans les situations où c’est absolument impossible. Le temps est limité, donc je veux le consacrer à ce qui est au cœur de la résolution de problèmes — conception de systèmes/programmes, modélisation, structure, patterns, etc. — le reste est secondaire.
    • Tous les Makefile que j’ai rencontrés en pratique faisaient beaucoup plus de choses que simplement « ça ».
    • Pareil pour moi. Je l’utilisais peu par le passé, mais dans mon poste actuel, presque tous les dépôts ont un Makefile.
      Savoir que make test fonctionne, quels que soient la stack, le langage ou le dépôt, est un vrai soulagement.
    • Le problème de Make n’est pas Make, mais le fait que ce que Make appelle ne fonctionne plus selon ce modèle.
      Le Makefile de mon dernier projet avait quatre commandes principales : build, test, frontend, deploy. build et test appelaient maven, frontend appelait npm, et deploy appelait docker et aws.
      Tous ces outils ont leur propre suivi d’état, leur cache et leur traitement incrémental, mais ils ne signalent pas ce qu’ils ont fait. On ne peut donc pas écrire de règles atomiques du type « déployer uniquement si le build a été mis à jour », parce que maven/cargo/dotnet/npm/go n’exposent pas cette information.
  • L’auteur n’utilise même pas le suivi des dépendances basé sur mtime. Les cibles devraient aussi être .PHONY, mais elles ne sont pas marquées comme telles. Cela aurait pu être remplacé par un script shell qui lit $1, le fait correspondre et décide quoi faire

    • Ou bien par un simple lanceur de commandes comme just
      https://just.systems/
    • Il y a une histoire d’horreur en une ligne tirée d’un vrai projet en cours : .PHONY: $(MAKECMDGOALS)
      ./build.sh dev est certes moins explicite que make dev. Même s’il n’y a aucune étape non phony, une autre raison d’utiliser Make est qu’on peut en ajouter plus tard si nécessaire. Bien sûr, je suis d’accord pour dire que l’auteur devrait marquer {dev,build,deploy} comme phony
    • Techniquement, c’est juste, mais ce que je retiens surtout de l’article, c’est qu’on obtient une cohérence d’utilisation, non seulement dans ses propres projets, mais aussi dans les projets tiers
      Son Makefile pourrait être amélioré techniquement, mais le plus important est que l’expérience de “prise en main” soit cohérente quand on arrive dans un projet
    • Nous avons tous été débutants un jour. Pour apprendre un outil, il est utile de l’utiliser réellement, même de manière imparfaite et débutante. On peut l’améliorer progressivement en apprenant
    • La beauté de make et du shell, c’est qu’ils suivent le principe Unix : être simples et bien faire une seule chose
      Les gens veulent qu’ils fassent beaucoup d’autres choses — langage de script, suivi des dépendances, etc. — et finissent par introduire des outils boursouflés. Nouveau ne veut pas forcément dire meilleur. Autoconf et automake ne sont pas make
  • Techniquement, toutes ces cibles make cherchent un fichier portant le même nom que la cible. Chaque cible devrait en réalité être définie comme .PHONY
    Avant, j’utilisais tout le temps ce genre de Makefile, mais ces dernières années je suis passé à just et aux justfile. Ce comportement y est celui par défaut et, globalement, c’est plus facile à utiliser. Les paramètres et autres choses du genre sont aussi plus simples
    https://github.com/casey/just

    • Ces systèmes proches de make sont plutôt bons, mais ils ont un problème : Make est déjà présent sur tous les Linux et Mac, et il est assez facile à obtenir sous Windows. Le fait qu’il ne soit pas inclus dans Git Bash est vraiment dommage. Pour moi, utiliser le plus petit dénominateur commun est en soi l’un des grands avantages de Make
    • La grande erreur de Make a été de mélanger les cibles phony et les cibles fichier dans le même espace de noms. Par exemple, les cibles phony auraient dû commencer par :, pour qu’on puisse les distinguer rien qu’à leur nom
      Bien sûr, il est désormais trop tard
    • just est vraiment pas mal, mais il n’est généralement pas installé, ce qui est un peu pénible
      Il serait peut-être temps d’avoir une sorte de nouvelle distribution de coreutils regroupant des outils nouveaux et utiles comme just, ripgrep, et qui s’installe partout où l’on veut les utiliser
  • C’est amusant de voir à quel point make déclenche des débats aussi virulents. Cela ressemble aux anciennes querelles presque religieuses entre vi et emacs
    Je suis entièrement d’accord avec l’article original, et je trouve particulièrement malin d’envelopper même des outils de build plus sophistiqués dans un Makefile de premier niveau. L’avantage, c’est la standardisation. Pas besoin de se souvenir de quoi que ce soit : si c’est un projet que j’ai écrit, je sais qu’il suffit de taper make pour que ça marche
    Par exemple, si quelqu’un qui fait du C veut compiler un projet Rust, il n’a pas besoin de chercher comment utiliser cargo : il lui suffit de taper make ou gmake. J’essaie de respecter POSIX sans utiliser de fonctionnalités propres à GNU, même s’il est vrai que quasiment 100 % des make réellement utilisés sont des gmake
    Proposer d’utiliser l’intemporel make comme une sorte de pilote de système de build de premier niveau fonctionnera probablement encore dans 250 ans

    • C’est amusant qu’un titre aussi simple ait déclenché un débat enflammé. L’article lui-même présente un cas d’usage follement simple de make, et parle d’utiliser gulp en 2024, mais manifestement personne ne l’a lu
    • cargo est un mauvais exemple. On utilise universellement cargo build
      Make lui-même est excellent, mais dans la plupart des projets C sur lesquels j’ai travaillé, c’était une combinaison de cmake/autotools et d’installation globale de paquets, et il fallait souvent aller vérifier
    • Cela pourrait ne pas arriver si bazel/blaze prend le dessus avant. Si vous en doutez, regardez Chrome contre Firefox, ou Kubernetes contre docker-compose
  • Je suis d’accord avec l’état d’esprit ici, mais je suis en train de réécrire beaucoup de choses en Justfile
    https://github.com/casey/just
    Cela permet d’éviter beaucoup des bizarreries propres aux Makefile

    • GPT a réellement donné une seconde vie aux Makefile en aidant à les écrire, et a retardé leur mort
    • Pareil pour moi. Le fait que le même fichier fonctionne aussi sous Windows est un bonus
  • Oui, Make est excellent. Je l’utilise pour énormément de choses. C’est une bonne façon d’automatiser des tâches
    Par exemple, pour mon site personnel, un Makefile appelle des scripts bash pour régénérer les pages web mises à jour, fait un git push vers le serveur, puis un hook git sur le serveur appelle Make pour déployer. En revanche, je ne veux pas mettre dans le dépôt Git de gros blobs binaires qui changent souvent, comme les PDF de supports de cours. Pas de souci : le Makefile a une cible uploads qui envoie uniquement les PDF modifiés vers le serveur, et cette cible est une dépendance de la cible deploy qui fait le git push, donc je n’ai pas à m’en préoccuper
    De même, les PDF mis à jour pour les supports de cours sont automatiquement placés dans l’arborescence source du site par un autre Makefile. Avec ce Makefile, je peux générer depuis les sources LaTeX le PDF que j’utilise, ou une version alternative pour les étudiants à partir des mêmes sources, par exemple sans les corrigés des exercices, puis la publier automatiquement dans la version locale du site et la mettre en ligne avec le site mis à jour quand je le souhaite
    C’est une sorte de chaîne infinie de Makefile. J’aime les Makefile

  • Dans ce cas, ce dont l’auteur a vraiment besoin, ce n’est pas make, mais just
    https://just.systems/man/en/

    • just n’est qu’une dépendance de plus. make est partout
  • En tant qu’exécuteur de tâches, make n’est pas mauvais, mais aujourd’hui, comme le disent d’autres commentaires, il existe de meilleures alternatives comme just
    En tant que système de build, make convient jusqu’à ce qu’on se heurte à ses limites. make/Makefile n’est pas standardisé, d’où l’existence d’automake. On n’écrit donc pas un Makefile directement : on utilise un template qui génère le makefile réel. Si vous possédez toute la chaîne d’outils, ce n’est pas un problème, mais comme ce n’est généralement pas le cas, on procède ainsi pour garantir un Makefile portable
    make ne fait aucune forme de résolution de dépendances et suppose que ce dont il a besoin se trouve déjà là. C’est pour cela que les scripts configure sont apparus ; et comme les scripts configure ne sont pas non plus un standard, on génère le script configure avec autoconf/autoreconf avant d’exécuter les cibles make
    make et les outils voisins comme automake/autoconf/autoreconf utilisent le mtime pour déterminer si les entrées sont obsolètes. Même après avoir exécuté autoconf/autoreconf/automake/configure, on peut se retrouver dans une situation où les entrées restent définitivement considérées comme obsolètes et où rien ne peut être construit. À noter que beaucoup de systèmes de build peuvent très bien utiliser le mtime, à condition que le suivi des dépendances soit correct
    Dans l’ensemble, le défaut de conception fondamental de make est qu’il suit la philosophie Unix, à savoir « faire une seule chose et la faire bien ». Cette chose, c’est « reconstruire la cible si les entrées sont obsolètes », mais c’est un outil très limité ; les systèmes de build modernes doivent donc empiler beaucoup de choses par-dessus pour le rendre utile au-delà d’un simple exécuteur de tâches

    • make ne sait pas résoudre les dépendances ?
      dependency:
      ...
      target: dependency
      ...
    • Question peut-être naïve, mais la résolution des dépendances n’est-elle pas justement le cœur de make ? De quoi parle-t-on exactement ici ?
    • L’ancienne convention, dans ce monde-là, c’est make configure. C’est ce qui configure ce dont make [build] a besoin
    • Ce qui me manque vraiment dans make, c’est seulement la capacité d’interpréter le mtime selon d’autres critères que le mtime
      C’est pour ça qu’on finit par utiliser des touchfiles ; c’est un peu sale, mais ça fonctionne toujours mieux que beaucoup d’autres choses. Je pense notamment au caching de docker build