Makefiles: automatiza tareas repetitivas sin complicar tu proyecto
En muchos proyectos pequeños, los primeros comandos caben perfectamente en la memoria: instalar dependencias, ejecutar pruebas, iniciar el servidor o limpiar archivos generados.
Con el tiempo aparecen variaciones: alguien usa un gestor de paquetes distinto, las pruebas requieren preparar una base de datos o el comando de desarrollo necesita tres variables de entorno. Lo que antes era una línea termina repartido entre mensajes de chat, documentación incompleta y el historial de la terminal.
Un Makefile no sustituye a las herramientas propias de un lenguaje ni pretende ser un sistema de despliegue completo. Su valor está en otro lugar: ofrece una interfaz breve y uniforme para tareas repetitivas.
En lugar de recordar cómo se ejecutan las pruebas, una persona puede ejecutar:
make testQué es un Makefile
make nació como una herramienta para compilar software a partir de dependencias. Sigue siendo excelente para eso, pero también se usa como un pequeño catálogo de tareas de proyecto.
Un archivo llamado Makefile define objetivos —targets— y las recetas necesarias para alcanzarlos.
La forma mínima es esta:
objetivo:
comandoHay un detalle importante: la línea del comando comienza con un tabulador real, no con espacios. Es uno de los errores más comunes al crear un Makefile a mano.
Si el editor reemplaza los tabuladores por espacios, make normalmente mostrará un error parecido a:
missing separatorAl guardar ese contenido en un archivo llamado Makefile, se puede ejecutar:
make objetivoObjetivos, dependencias y recetas
Un objetivo puede depender de otros objetivos. En un proyecto que debe instalar dependencias antes de probar, podríamos escribir:
install:
npm ci
test: install
npm testAquí test depende de install. Antes de ejecutar npm test, make intentará ejecutar el objetivo install.
Para tareas de automatización general conviene distinguir entre archivos reales y acciones. Si existe un archivo o directorio llamado test, make puede pensar que el objetivo ya está actualizado y omitir la receta.
La solución es declarar como .PHONY los objetivos que siempre representan acciones:
.PHONY: install test
install:
npm ci
test: install
npm testEn la práctica, la mayoría de los objetivos de un Makefile de aplicación —run, test, lint y clean— deberían ser .PHONY.
Un ejemplo útil para un proyecto web
El siguiente ejemplo no presupone una aplicación concreta. Agrupa comandos habituales de un proyecto JavaScript que ya define scripts en su archivo package.json:
.PHONY: help install run test lint check clean
help:
@printf "Comandos disponibles:\n"
@printf " make install Instala dependencias bloqueadas\n"
@printf " make run Inicia el entorno de desarrollo\n"
@printf " make test Ejecuta las pruebas\n"
@printf " make lint Ejecuta el linter\n"
@printf " make check Ejecuta pruebas y linter\n"
@printf " make clean Elimina artefactos locales seguros\n"
install:
npm ci
run:
npm run dev
test:
npm test
lint:
npm run lint
check: test lint
clean:
rm -rf coverage .cachemake help funciona como la puerta de entrada del proyecto. El prefijo @ evita que make imprima el comando printf antes de su resultado, lo que hace la salida más clara.
El objetivo check no tiene una receta propia: simplemente agrupa test y lint.
El objetivo clean merece cuidado. Borrar coverage o una caché local suele ser seguro; borrar node_modules, datos de desarrollo o archivos de configuración puede no serlo.
Una buena regla es que un comando de limpieza sea idempotente: debe poder ejecutarse dos veces sin producir un error ni borrar datos que una persona no esperaba perder.
Variables para evitar repetición
Las variables hacen que un Makefile sea más fácil de adaptar. Por ejemplo, si un proyecto permite elegir el puerto de desarrollo:
PORT ?= 3000
.PHONY: run
run:
PORT=$(PORT) npm run devEl operador ?= define un valor solo si no existe uno previo. Así se mantiene un valor práctico por defecto y se deja una salida para casos puntuales:
make run PORT=4173No todas las configuraciones deberían vivir dentro del Makefile. Las credenciales, los tokens y otros valores privados deben continuar en el mecanismo de secretos del proyecto, como un gestor de secretos o un archivo local que no se incluya en Git.
El Makefile puede consumir una variable de entorno ya disponible, pero no debe contener secretos.
También es útil definir los nombres de las herramientas que se repiten:
PACKAGE_MANAGER ?= npm
install:
$(PACKAGE_MANAGER) ciEste patrón tiene sentido cuando el equipo realmente necesita la variación. Añadir variables «por si acaso» puede volver un archivo pequeño más difícil de leer que los comandos que buscaba simplificar.
Diseña comandos que una persona quiera ejecutar
Un buen Makefile describe tareas, no trucos de shell. Los nombres deben expresar intención:
make test, nomake all-the-tests-now.make format, nomake prettier-and-eslint-fix.make db-reset, solo si deja claro que afecta datos de desarrollo.make deploy, únicamente si incorpora las comprobaciones y protecciones apropiadas.
La consistencia importa más que la cantidad de objetivos. Es preferible tener cinco comandos previsibles que veinte objetivos con nombres ambiguos.
El objetivo predeterminado es el primero del archivo. Por eso es habitual colocar help al principio: ejecutar solo make muestra qué puede hacer el proyecto.
Si el proyecto necesita un flujo de verificación estándar, también puede ser razonable que el primer objetivo sea check. Depende de qué acción sea más segura y frecuente.
Errores comunes
Usar espacios donde make espera un tabulador
Las recetas necesitan tabuladores. Configura el editor para que el Makefile conserve los tabuladores y revisa ese carácter si aparece el error missing separator.
Mezclar dependencias de archivos con tareas de aplicación
make compara las fechas de los archivos para decidir si debe reconstruirlos. Eso es perfecto al compilar, pero puede sorprender en un objetivo como test.
Declara .PHONY cuando el nombre representa una acción.
Crear dependencias implícitas
Si make test requiere que exista una base de datos, que las migraciones estén aplicadas o que un servicio esté activo, el objetivo debe prepararlo explícitamente o documentarlo con claridad.
Un comando que funciona únicamente en el equipo de quien lo escribió no es una buena interfaz.
Convertir Make en una capa innecesaria
Si el proyecto ya usa npm run, poetry, just, task o los scripts de un framework y esos comandos son claros, quizá no haga falta sumar make.
Úsalo cuando reduzca la fricción entre herramientas o cree un vocabulario común para el equipo.
Cuándo usarlo y cuándo no
Un Makefile encaja especialmente bien cuando un repositorio mezcla varios lenguajes, contenedores, pruebas y herramientas de calidad.
Por ejemplo, make check puede ejecutar comandos de Python, Node, Docker o una utilidad del sistema sin obligar a cada persona a recordar el orden.
No es la única opción. Un proyecto puramente Node puede estar perfectamente servido por los scripts de package.json; uno en Python puede preferir tareas de tox, nox o poe; y un flujo complejo de infraestructura puede necesitar una herramienta específica.
El criterio es simple: elige la interfaz que haga que las tareas correctas sean más fáciles de descubrir y ejecutar.
Checklist para incorporarlo
Antes de añadir un Makefile a un repositorio existente, revisa esta lista:
- Identifica los tres a cinco comandos que el equipo repite con mayor frecuencia.
- Conserva los comandos nativos debajo del objetivo;
makedebe ser una interfaz, no una abstracción opaca. - Declara como
.PHONYlas tareas que no producen archivos. - Incluye
make helpo documenta el objetivo predeterminado. - Evita secretos y operaciones destructivas silenciosas.
- Ejecuta cada objetivo desde un clon limpio antes de pedirle a otra persona que lo use.
Un Makefile pequeño no hace magia. Lo que sí hace es convertir conocimiento disperso en comandos cortos, documentados y repetibles.
Esa mejora se nota desde el primer make test que alguien puede ejecutar sin tener que preguntar cómo funciona el proyecto.
Comments ()