1 puntos por GN⁺ 2024-09-22 | 2 comentarios | Compartir por WhatsApp
  • Aunque siguen apareciendo nuevas herramientas de build, Makefile sigue siendo útil como una herramienta base ligera para agrupar tareas repetitivas de un proyecto
  • Gracias a comandos convencionales como make, make build, make install, make dev, incluso en un proyecto desconocido se puede probar rápido el build, la instalación y la ejecución del servidor de desarrollo
  • Aunque cambie el stack, como Jekyll, Hugo, 11ty, frameworks web de Python, Docker o gulp, Makefile funciona como una interfaz de ejecución común
  • En proyectos personales, muchas veces bastan unos pocos targets simples como dev, build, deploy, en lugar de condicionales o flags
  • GNU Make está muy difundido y, aunque no esté disponible, se pueden ejecutar directamente los comandos de shell dentro del Makefile, así que la carga es baja incluso en entornos restringidos

Un solo punto de entrada para comandos distintos en cada proyecto

  • Makefile se usa para envolver proyectos con distintas formas de compilar, ejecutar o desplegar dentro de un conjunto de comandos familiar
  • Si un proyecto desconocido tiene Makefile, normalmente se puede empezar probando estos comandos
    • make
    • make build
    • make install
  • Incluso al volver a abrir un proyecto personal antiguo, se puede configurar para que con solo ejecutar make dev se hagan de una vez los pasos de build necesarios y el arranque del servidor de desarrollo
  • Aunque el stack tecnológico cambie, como Jekyll, Hugo, 11ty o varios frameworks web de Python, se puede mantener el mismo punto de entrada
  • Incluso cuando se usa Docker o gulp, Makefile no reemplaza esas herramientas, sino que actúa como una capa de orquestación
    • Ejemplo: make build compila las imágenes de Docker necesarias pasando argumentos adicionales según el proyecto

Uso simple en proyectos personales

  • El uso de Makefile se centra más en agrupar comandos de shell que en funciones complejas
    • No se usan funciones como condicionales o flags
    • Técnicamente, la unidad de trabajo en make es el target, pero en la práctica se trata como una tarea
    • También podría hacerse con funciones en scripts de bash, pero se considera que Makefile es más fácil y rápido
  • En proyectos personales, las tareas más usadas suelen ser 2 o 3
    • dev: iniciar el servidor de desarrollo
    • build: compilar el proyecto si hace falta
    • deploy: desplegar o publicar el proyecto
    • watch: tarea adicional para volver a ejecutar automáticamente el build cuando cambian los archivos fuente
  • El Makefile actual del blog solo tiene un target: dev
    • Debajo de dev: se ejecuta npm run dev
  • En proyectos personales más complejos, las siguientes tareas se agrupan en un Makefile
    • dev: ejecutar el servidor de desarrollo con bundle exec jekyll serve
    • build: compilar los assets con npm run gulp build
    • watch: vigilar una carpeta específica con npm run gulp watch
    • deploy: después de compilar Jekyll para producción, ejecutar make encrypt, netlify deploy --prod
    • encrypt: cifrar los archivos HTML de la carpeta _site con npx staticrypt

Límites y ventajas que conviene conocer

  • En los ejemplos se omitieron los phony targets, pero si existen archivos con nombres como dev, build, watch, deploy, encrypt, el Makefile podría no funcionar como se espera
  • GNU Make es el software que ejecuta los makefiles; es muy probable que ya venga instalado en Linux y es tan común en MacBook que ni siquiera se recuerda haberlo instalado por separado
  • Make no requiere muchas dependencias adicionales, así que puede ser útil en entornos restringidos donde por seguridad sea difícil o imposible instalar paquetes
  • Aunque no haya Make, los comandos dentro del Makefile pueden copiarse y ejecutarse directamente en el shell
    • En cambio, si gulp no está en el servidor, es difícil pegar código JavaScript tal cual en la terminal para ejecutarlo, y ahí está la diferencia
  • Aunque se descubran nuevas herramientas de build mejores y más rápidas, Make puede seguir usándose como una capa de comandos común para administrar distintas herramientas y configuraciones

2 comentarios

 
kayws426 2024-09-22

Si un makefile no define dependencias, reemplazarlo por justfile ofrece una mejor experiencia de uso.

 
GN⁺ 2024-09-22
Opiniones de Hacker News
  • No hace falta desanimarse por quienes dicen en este hilo que “estás usando mal make”. Una de las razones por las que Make es una buena herramienta es que se puede empezar de una forma más simple de lo que parece
    No usar .PHONY puede causar problemas, pero en un proyecto pequeño quizá sea una trampa que recién encuentres dentro de un año, y aun entonces probablemente se resuelva rascándote la cabeza durante una hora. En el 99% de los casos puedes empezar de inmediato sin preocuparte por “la forma correcta”, y Make solo exige la complejidad suficiente para no venirse abajo

    • Una de las peores cosas de Make es que parece simple a primera vista
      Make toma archivos de entrada y dependencias para generar exactamente un archivo de salida. Las reglas que no generan una salida, como install, all o clean, y todos los objetivos del artículo, deben depender de algún objetivo mágico especial como .PHONY, pero .PHONY no estaba en POSIX 2017 (IEEE Std 1003.1-2017, https://pubs.opengroup.org/onlinepubs/9699919799/utilities/m...) y recién entró en el estándar actual (IEEE Std 1003.1-2024, https://pubs.opengroup.org/onlinepubs/9799919799/utilities/m...). Si quieres generar varios archivos, como archivos objeto, módulos o encabezados precompilados, tienes que armar tú mismo trucos frágiles. No todos los Make son GNU Make; todavía existen variantes nix como BSD y Solaris/Illumos
      No significa que Make no sirva. Sirve para proyectos lo bastante complejos, pero que todavía no necesitan un sistema de build “mejor”. El problema es que esos proyectos se vuelven más complejos a medida que crece el código y, al final, terminan con scripts/programas que generan el Makefile o partes de él, creando un metasistema de build improvisado
      El problema más grande no es usarlo, sino proponerlo como solución para “todo el mundo”. Y en cuanto aparece un directorio o archivo llamado build o dev en la raíz del proyecto, ese Makefile deja de funcionar
    • Aquí hay un ejemplo de Makefile un poco más complejo que uso al iniciar un proyecto nuevo de TypeScript: https://github.com/borisovg/node-ts-template/blob/main/Makef...
      Hoy en día suelo cambiarlo a pnpm, pero aun así no me parece tan complejo. Hay que saber algo de la sintaxis, sí, pero es menos difícil que hacer que otras herramientas funcionen correctamente desde el inicio
    • He usado make bien durante más de 20 años en muchos proyectos y varios lenguajes, y nunca tuve problemas con las tareas .PHONY que tanto preocupan a la gente
      Es simple, fácil de leer, modificable, componible y ya está instalado en todas partes. Hace lo que dice en la superficie y casi nada más
      De hecho, incluso cuando en otros proyectos tengo que lidiar con sistemas de build de moda o pesadillescos, los envuelvo con make
    • Totalmente de acuerdo. Es mejor usar la herramienta adecuada para el trabajo, y mucho mejor que elegir una herramienta incorrecta para evitar las críticas perfeccionistas, aunque no seas experto
      Todo el mundo tiene que empezar en algún lado y, una vez que algo empieza a funcionar, es fácil agregar mejoras graduales
    • Todas las recetas de un makefile deberían crear exactamente un producto de salida, $@. El Makefile completo puede crear una cantidad arbitraria de salidas, ya que una regla puede depender de otra
      De aquí sale una regla práctica clara para los objetivos phony. Si una receta no toca $@, entonces $@ debería marcarse como phony. Mantener una lista de objetivos phony lo hace mucho más fácil
      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 es una tecnología pésima. Solo que es un poco menos mala que la mayoría de los otros sistemas de build que hemos creado, así que se vuelve útil en un sentido masoquista.
    Los sistemas de build suelen ser tan básicos que, si compilas algo más que un juguete, se vuelven un caos; o tan complejos que requieren demasiados conocimientos previos, burocracia, sincronización y boilerplate; o no tienen biblioteca estándar, así que los mismos patrones de build quedan dispersos en 10 mil implementaciones incompatibles; o son tan restrictivos que, cuando crecen los requisitos, hay que migrar; o tienen demasiada magia; o su sintaxis es críptica e inconsistente.

    • En mi opinión, Makefile es una tecnología excelente, y mucha gente simplemente no la aprendió bien o no la usa de la forma prevista.
      Más de la mitad del software que uso emplea solo un Makefile común y, a veces, un script configure. Sin autotools, ejecutas opcionalmente ./configure, luego make y make install, y simplemente funciona. Eso no es en absoluto una configuración de juguete; está compuesto por pequeños programas que hacen bien una sola cosa.
      Make y su forma de funcionar son fáciles de entender, al menos para mí. Creo que las malas experiencias de la gente muchas veces se deben más a malas configuraciones de build en proyectos complejos, o a falta del conocimiento necesario, que a Make en sí.
      Que no tenga biblioteca estándar es una intención de independencia de herramientas, y por eso puede aplicarse de forma universal a distintas herramientas, lenguajes y usos. Lo veo como una funcionalidad, no como un defecto. La capa simple es el seguimiento de dependencias, y los targets y dependencias también son claros, así que cuesta verlo como algo mágico. La sintaxis, sin duda, podría mejorar.
    • Los peores sistemas de build son los creados alrededor de un lenguaje de programación específico. Si hay N>>1 lenguajes, también terminas con N>>1 sistemas de build, y la carga cognitiva se vuelve demasiado grande como para escalar.
      Los únicos sistemas de build generales que abarcan varios lenguajes son make o sistemas que apuntan a make, como CMake. La razón por la que esto es malo es que make es malo. make es realmente difícil de usar bien; tiene problemas como make recursivo vs. no recursivo; muchas variantes incompatibles como Unix/POSIX make, BSD make, GNU make y nmake de Windows; y es bastante feo.
      Aun así, un make bien usado es bastante bueno. Es una gran suerte que exista make como mínimo común denominador.
    • De acuerdo. Muchos de los reemplazos tampoco son herramientas generales para “ejecutar cosas”, como Make, sino que están enfocadas en lenguajes específicos.
      Es bastante absurdo que Make ni siquiera maneje subdirectorios de forma limpia. Me pregunto si hay algo mejor que Make. Existe Ninja, pero no fue diseñado para que lo escriban directamente las personas.
    • ¿Podemos decir que ese caos, “de forma muy hipotética”, no se debe en absoluto a compiladores o lenguajes sin módulos?
      ¿En qué medida crees que aplica? ¿No será algo como 20%, 40% o 70%?
    • Si se dice “más de una”, hay mucho margen. Mi estimación es esta: Makefile no es demasiado básico. Puede volverse demasiado complejo, pero depende de cómo lo construyas. Biblioteca estándar sí tiene, en forma de funciones integradas. Nunca lo sentí demasiado restrictivo, así que diría que no. Como los targets y dependencias son claros, tampoco tiene mucha magia. Pero la sintaxis sin duda podría mejorar; creo que incluso un simple archivo JSON sería mejor.
  • Me gusta mucho Make. Durante los últimos 20 años he escrito al menos uno para cada tarea o proyecto en el que metí mano.
    No tiene nada inteligente: solo es una colección de fragmentos con algunas variables. Cosas como make run, make test, make lint.
    make recent ejecuta el script modificado más recientemente después de pasar el lint. También se podría hacer con Bash u otro shell, pero entonces te absorbe el mundo del desarrollo, solo aumenta la complejidad y no aporta valor adicional. Make no es más que un lenguaje específico de dominio para decir: “este archivo se crea a partir de estos archivos, y en ese momento se ejecutan uno o dos comandos”. Por eso es tremendamente potente.

    • Me gusta la frase “este archivo se crea a partir de estos archivos, y en ese momento se ejecutan uno o dos comandos”.
      Hace décadas hice un framework de pruebas en Java, y permitía especificar pruebas y clases dependientes con una sintaxis parecida a make. Había un conjunto de clases de prueba que definía un “paquete base de pruebas”, y otras capas de clases de prueba que dependían de él solo se ejecutaban si la etapa anterior tenía éxito.
      No sé por qué hoy complican tanto todo. Siempre he aconsejado apegarse a las herramientas estándar de Unix y a su forma de hacer las cosas, es decir, a métodos probados por el tiempo, salvo que sea una situación en la que realmente no se pueda hacer así. El tiempo es finito, así que quiero usarlo en lo esencial para resolver problemas —diseño de sistemas/programas, modelado, estructura, patrones, etc.— y lo demás es secundario.
    • Todos los Makefile que me he encontrado en la práctica hacían mucho más que solo “eso”.
    • A mí me pasa algo parecido. Antes no lo usaba mucho, pero en mi trabajo actual casi todos los repositorios tienen un Makefile.
      Saber que, sin importar el stack, el lenguaje o el repositorio, make test va a funcionar es un gran alivio.
    • El problema de Make no es Make, sino que las cosas que Make invoca ya no se comportan según ese modelo.
      El Makefile de mi proyecto anterior tenía cuatro comandos principales: build, test, frontend y deploy. build y test invocaban maven, frontend invocaba npm, y deploy invocaba docker y aws.
      Todas esas herramientas tienen su propio seguimiento de estado, caché y procesamiento incremental, pero no informan qué hicieron. Por eso no puedes escribir reglas a nivel molecular como “desplegar solo si el build se actualizó”. Porque maven/cargo/dotnet/npm/go no exponen esa información.
  • El autor ni siquiera usa seguimiento de dependencias basado en mtime. Los targets también deberían ser .PHONY, pero no están marcados así. Podría haberse reemplazado por un script de shell que lea $1, haga el match y decida qué hacer

    • O también podría reemplazarse por un simple ejecutor de comandos como just
      https://just.systems/
    • Hay una historia de terror de una sola línea sacada de un proyecto real en el que estoy trabajando: .PHONY: $(MAKECMDGOALS)
      ./build.sh dev es menos claro que make dev, es cierto. Otro motivo para usar Make aunque no haya ningún paso no phony es que más adelante puedes agregar esos pasos si los necesitas. Claro que coincido en que el autor debería marcar {dev,build,deploy} como phony
    • Técnicamente es correcto, pero lo principal que saqué del artículo es que se obtiene consistencia en la forma de uso, no solo en los proyectos propios sino también en proyectos de terceros
      Su Makefile podría mejorarse técnicamente, pero lo más importante es que la experiencia de “empezar” al entrar a un proyecto sea consistente
    • Todos fuimos principiantes alguna vez. Para aprender una herramienta, ayuda usarla de verdad, aunque sea con un uso imperfecto de principiante. Luego se puede ir mejorando gradualmente mientras se aprende
    • La belleza de make y del shell está en que siguen el principio Unix: son simples y hacen bien una sola cosa
      La gente quiere que esto haga muchas otras cosas, como ser un lenguaje de scripting o un rastreador de dependencias, y termina trayendo herramientas infladas. Que algo sea nuevo no significa que sea mejor. Autoconf y automake no son make
  • Técnicamente, todos estos targets de make buscan un archivo con el mismo nombre que el target. Cada target debería definirse realmente como .PHONY
    Antes yo también usaba siempre Makefiles así, pero en los últimos años me pasé a just y justfile. Este comportamiento es el predeterminado y en general es más fácil de usar. Cosas como los parámetros también son más simples
    https://github.com/casey/just

    • Estos sistemas parecidos a make son bastante buenos, pero tienen un problema. Make ya está en todos los Linux y Mac, y en Windows también se consigue con bastante facilidad. Es una verdadera lástima que no venga incluido en Git Bash. Creo que usar el mínimo común denominador en sí mismo es una gran ventaja de Make
    • El gran error de Make fue mezclar targets phony y targets de archivo en el mismo espacio de nombres. Por ejemplo, los targets phony deberían haber empezado con :, de forma que se distinguieran solo por el nombre
      Claro que ahora ya es demasiado tarde
    • just está realmente bien, pero es un poco molesto porque no suele estar instalado
      Creo que ya va siendo hora de que aparezca algo como una nueva distribución de coreutils que agrupe herramientas nuevas y útiles como just y ripgrep, y que se instale en cualquier lugar donde quieras usarlas
  • Es interesante que make genere debates tan intensos. Se parece a aquellas viejas discusiones casi de guerra religiosa entre vi y emacs
    Estoy totalmente de acuerdo con el post original, y en particular me parece inteligente envolver incluso herramientas de build más sofisticadas con un Makefile de nivel superior. La ventaja es la estandarización. No hace falta recordar nada: si es un proyecto mío, sé que basta con escribir make y va a funcionar
    Por ejemplo, si alguien que usa C quiere compilar un proyecto Rust, no necesita buscar cómo se usa cargo; solo escribe make o gmake. Intento cumplir POSIX sin usar funciones específicas de GNU, aunque también es cierto que casi el 100% de los make reales son gmake
    La propuesta de usar el atemporal make como una especie de lanzador de sistema de build de nivel superior probablemente seguirá funcionando dentro de 250 años

    • Es gracioso que un título tan simple haya provocado una discusión incendiaria. El artículo en sí es un caso de uso absurdamente simple de make, y trata sobre usar gulp en 2024, pero evidentemente parece que nadie lo leyó
    • cargo es un mal ejemplo. Porque universalmente se usa cargo build
      Make en sí es excelente, pero en la mayoría de los casos en que trabajé con proyectos C era una combinación de cmake/autotools e instalación global de paquetes, y esas cosas había que buscarlas seguido
    • Puede que no sea así si antes bazel/blaze se impone. Si lo dudas, mira Chrome vs Firefox, o Kubernetes vs docker-compose
  • Estoy de acuerdo con el sentimiento de fondo, pero estoy reescribiendo muchas cosas como Justfile
    https://github.com/casey/just
    Permite evitar muchas de las rarezas propias de Makefile

    • Es verdad que GPT le dio una segunda vida a Makefile al ayudar a escribir Makefiles, y retrasó su muerte
    • Yo igual. Que el mismo archivo funcione también en Windows es un plus
  • Sí, Make es excelente. Lo uso para muchísimas cosas. Es una buena forma de automatizar tareas
    Por ejemplo, en mi sitio web personal, un Makefile llama scripts de bash para volver a generar las páginas actualizadas, hace git push al servidor, y luego un git hook del servidor llama a Make para desplegar. Pero no quiero meter en el repositorio Git blobs binarios que cambian con frecuencia, como PDFs de material de clases. No hay problema. En el Makefile hay un target uploads que sube al servidor solo los PDFs modificados, y como ese target es una dependencia del target deploy, que hace git push, no tengo que preocuparme por eso
    Además, otro Makefile inserta automáticamente en el árbol fuente del sitio web los PDFs actualizados del material de clases. Con ese Makefile puedo generar desde fuentes LaTeX los PDFs que uso, o desde las mismas fuentes versiones alternativas para estudiantes, por ejemplo una versión sin las respuestas de los ejercicios prácticos, publicarlas automáticamente en la versión local del sitio web y luego subirlas al sitio actualizado cuando quiera
    Es una especie de cadena interminable de Makefiles. Me gustan los Makefiles

  • En este caso, lo que el autor realmente necesita no es make, sino just
    https://just.systems/man/en/

    • just no es más que otra dependencia. make está en todas partes
  • Como ejecutor de tareas, make no está mal, pero hoy existen mejores alternativas como just, tal como mencionan otros comentarios.
    Como sistema de compilación, make está bien hasta que te topas con sus desventajas. make/Makefile no está estandarizado, por eso existe automake. Así que no se usa un Makefile directamente, sino una plantilla para generar el makefile real. Si eres dueño de toda la cadena de herramientas, no importa, pero la mayoría no lo es, así que se hace esto para garantizar un Makefile portable.
    make no hace ningún tipo de resolución de dependencias y asume que lo necesario está justo ahí. Por eso aparecieron los scripts configure; y como los scripts configure tampoco son un estándar, terminas generando el script configure con autoconf/autoreconf antes de ejecutar los targets de make.
    make y herramientas cercanas como automake/autoconf/autoreconf usan mtime para decidir si una entrada quedó obsoleta. Aunque ejecutes autoconf/autoreconf/automake/configure, puedes quedar en una situación en la que la entrada siga permanentemente obsoleta y no puedas compilar nada. Como referencia, muchos sistemas de build pueden usar mtime sin problema si tienen un seguimiento adecuado de dependencias.
    En conjunto, el defecto fundamental de diseño de make es que está alineado con la filosofía Unix, es decir, “haz una sola cosa y hazla bien”. Esa única cosa es “si la entrada está obsoleta, recompila el target”, pero es una herramienta muy limitada, así que los sistemas de build modernos tienen que apilar mucho trabajo encima para hacerla útil más allá de un ejecutor básico de tareas.

    • ¿Que make no puede resolver dependencias?
      dependency:
      ...
      target: dependency
      ...
    • Puede que sea una pregunta ignorante, pero ¿la resolución de dependencias no es precisamente el núcleo de make? ¿A qué se refieren exactamente aquí?
    • La vieja convención de ese lado es make configure. Eso configura lo necesario para make [build].
    • Lo único que realmente echo de menos en make es la capacidad de interpretar mtime con algún criterio distinto de mtime.
      Por eso uno termina usando touchfiles; aunque sea desprolijo, todavía funciona mejor que muchas otras cosas. Lo digo pensando especialmente en el caché de docker build.