Me gustan los Makefiles
(switowski.com)- 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 comandosmakemake buildmake install
- Incluso al volver a abrir un proyecto personal antiguo, se puede configurar para que con solo ejecutar
make devse 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 buildcompila las imágenes de Docker necesarias pasando argumentos adicionales según el proyecto
- Ejemplo:
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 desarrollobuild: compilar el proyecto si hace faltadeploy: desplegar o publicar el proyectowatch: 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 ejecutanpm run dev
- Debajo de
- En proyectos personales más complejos, las siguientes tareas se agrupan en un Makefile
dev: ejecutar el servidor de desarrollo conbundle exec jekyll servebuild: compilar los assets connpm run gulp buildwatch: vigilar una carpeta específica connpm run gulp watchdeploy: después de compilar Jekyll para producción, ejecutarmake encrypt,netlify deploy --prodencrypt: cifrar los archivos HTML de la carpeta_siteconnpx 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- Lectores de Hacker News recomiendan agregar phony targets en esos casos
- 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
Si un
makefileno define dependencias, reemplazarlo porjustfileofrece una mejor experiencia de uso.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
.PHONYpuede 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 abajoMake toma archivos de entrada y dependencias para generar exactamente un archivo de salida. Las reglas que no generan una salida, como
install,alloclean, y todos los objetivos del artículo, deben depender de algún objetivo mágico especial como.PHONY, pero.PHONYno 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/IllumosNo 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
buildodeven la raíz del proyecto, ese Makefile deja de funcionarHoy 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
.PHONYque tanto preocupan a la genteEs 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
Todo el mundo tiene que empezar en algún lado y, una vez que algo empieza a funcionar, es fácil agregar mejoras graduales
$@. El Makefile completo puede crear una cantidad arbitraria de salidas, ya que una regla puede depender de otraDe 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ácilphonies :=phonies += somethingsomething:./do-somethingphonies += something-elsesomething-else: something./do-something-else# touches $@ and thus does not need to be phonycreate-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.
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, luegomakeymake 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 únicos sistemas de build generales que abarcan varios lenguajes son
makeo sistemas que apuntan amake, como CMake. La razón por la que esto es malo es quemakees malo.makees 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 ynmakede Windows; y es bastante feo.Aun así, un
makebien usado es bastante bueno. Es una gran suerte que existamakecomo mínimo común denominador.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.
¿En qué medida crees que aplica? ¿No será algo como 20%, 40% o 70%?
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 recentejecuta 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.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.
Saber que, sin importar el stack, el lenguaje o el repositorio,
make testva a funcionar es un gran alivio.El Makefile de mi proyecto anterior tenía cuatro comandos principales:
build,test,frontendydeploy. 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é hacerhttps://just.systems/
.PHONY: $(MAKECMDGOALS)./build.sh deves menos claro quemake 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 phonySu Makefile podría mejorarse técnicamente, pero lo más importante es que la experiencia de “empezar” al entrar a un proyecto sea consistente
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
.PHONYAntes 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
:, de forma que se distinguieran solo por el nombreClaro que ahora ya es demasiado tarde
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
makey va a funcionarPor ejemplo, si alguien que usa C quiere compilar un proyecto Rust, no necesita buscar cómo se usa cargo; solo escribe
makeogmake. Intento cumplir POSIX sin usar funciones específicas de GNU, aunque también es cierto que casi el 100% de los make reales son gmakeLa propuesta de usar el atemporal
makecomo una especie de lanzador de sistema de build de nivel superior probablemente seguirá funcionando dentro de 250 añoscargo buildMake 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
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
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
uploadsque sube al servidor solo los PDFs modificados, y como ese target es una dependencia del targetdeploy, que hace git push, no tengo que preocuparme por esoAdemá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/
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.
makeno puede resolver dependencias?dependency:...target: dependency...make configure. Eso configura lo necesario paramake [build].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.