1. Qué hace make
Un programa en C de tamaño mediano son varios archivos .c, sus encabezados y las órdenes de gcc para convertirlos en un ejecutable. La compilación separada (tema 31) ya permite no repetir trabajo: si solo se tocó numeros.c, basta regenerar numeros.o y enlazar de nuevo. Pero hay que acordarse de qué archivo cambió y de qué depende cada cosa. make automatiza esa memoria con una idea simple: cada archivo generado (objetivo) se declara junto con los archivos de los que se obtiene (sus requisitos) y la orden que lo produce (la receta). Al ejecutarse, compara las fechas de modificación: si algún requisito es más nuevo que el objetivo, o el objetivo no existe, ejecuta la receta. Si no, lo deja como está.
Todo el tema usa un proyecto pequeño, el mismo de los dos temas anteriores: un encabezado inc/util.h, dos módulos (numeros.c y texto.c) y un main.c que los usa. Los compiladores y salidas son los de siempre: gcc -std=c17 -Wall -Wextra, probado con GNU Make 4.4.1 en Linux.
/* inc/util.h */
#ifndef UTIL_H
#define UTIL_H
#include <stddef.h>
int maximo(const int *v, size_t n);
double promedio(const int *v, size_t n);
void invertir(char *s);
#endif
/* numeros.c */
#include "util.h"
int maximo(const int *v, size_t n)
{
int m = v[0];
for (size_t i = 1; i < n; i++)
if (v[i] > m)
m = v[i];
return m;
}
double promedio(const int *v, size_t n)
{
long s = 0;
for (size_t i = 0; i < n; i++)
s += v[i];
return (double)s / (double)n;
}
/* texto.c */
#include <string.h>
#include "util.h"
void invertir(char *s)
{
size_t n = strlen(s);
for (size_t i = 0; i < n / 2; i++) {
char t = s[i];
s[i] = s[n - 1 - i];
s[n - 1 - i] = t;
}
}
/* main.c */
#include <stdio.h>
#include "util.h"
int main(void)
{
int v[] = {4, 9, 2, 7};
char s[] = "enlace";
printf("maximo=%d promedio=%.2f\n", maximo(v, 4), promedio(v, 4));
invertir(s);
printf("%s\n", s);
return 0;
}
2. La regla: objetivo, requisitos y receta
Un Makefile es una lista de reglas. Cada regla tiene esta forma:
objetivo: requisito1 requisito2 ...
<TAB>orden que construye el objetivo
<TAB>otra orden, si hace falta
La primera línea dice qué se construye y de qué depende. Las siguientes, la receta, dicen cómo, y cada una debe empezar con un carácter de tabulación (no espacios). Es la regla de sintaxis que más tropieza al principio; en los ejemplos de este tema, el sangrado de las recetas es un tabulador real. Para el proyecto, una primera versión con todo explícito:
prog: main.o numeros.o texto.o
gcc -o prog main.o numeros.o texto.o
main.o: main.c inc/util.h
gcc -std=c17 -Wall -Wextra -Iinc -c main.c
numeros.o: numeros.c inc/util.h
gcc -std=c17 -Wall -Wextra -Iinc -c numeros.c
texto.o: texto.c inc/util.h
gcc -std=c17 -Wall -Wextra -Iinc -c texto.c
Se ejecuta con la orden make, que busca un archivo llamado Makefile (o makefile) en el directorio actual. Sin argumentos construye el primer objetivo del archivo, el «objetivo por defecto» (aquí, prog):
$ make
gcc -std=c17 -Wall -Wextra -Iinc -c main.c
gcc -std=c17 -Wall -Wextra -Iinc -c numeros.c
gcc -std=c17 -Wall -Wextra -Iinc -c texto.c
gcc -o prog main.o numeros.o texto.o
$ make
make: 'prog' is up to date.
La primera vez construye todo, en el orden que dictan las dependencias: para hacer prog hacen falta los tres .o, y cada uno se hace a partir de su .c. make imprime cada receta antes de ejecutarla. La segunda vez no hay nada que hacer, y lo dice (is up to date: está al día). Veamos qué pasa cuando cambia algo:
$ touch numeros.c; make
gcc -std=c17 -Wall -Wextra -Iinc -c numeros.c
gcc -o prog main.o numeros.o texto.o
$ touch inc/util.h; make
gcc -std=c17 -Wall -Wextra -Iinc -c main.c
gcc -std=c17 -Wall -Wextra -Iinc -c numeros.c
gcc -std=c17 -Wall -Wextra -Iinc -c texto.c
gcc -o prog main.o numeros.o texto.o
$ make main.o
make: 'main.o' is up to date.
(touch actualiza la fecha de un archivo sin cambiarle el contenido, una forma rápida de simular una edición.) Al tocar numeros.c solo se rehace numeros.o y, como cambió, también el ejecutable. Al tocar el encabezado, que figura como requisito de los tres objetos, se rehace todo. Y se puede pedir cualquier objetivo por nombre: make main.o construye solo eso (y lo que necesite).
3. Cómo decide qué reconstruir
Internamente, las reglas forman un grafo de dependencias. Para construir un objetivo, make primero construye (recursivamente) cada uno de sus requisitos, y después compara fechas:
La opción --debug=b (basic) hace que make explique cada decisión. Con numeros.c modificado:
$ touch numeros.c; make --debug=b
Reading makefiles...
Updating makefiles....
Updating goal targets....
Prerequisite 'numeros.c' is newer than target 'numeros.o'.
Must remake target 'numeros.o'.
gcc -Iinc -std=c17 -Wall -Wextra -c -o numeros.o numeros.c
Otra consecuencia es el objetivo por defecto: si no se indica ninguno, se construye el primero del archivo. Por eso en un Makefile el objetivo principal (por convención, all o el nombre del programa) va arriba. Hay además dos mensajes de error que conviene reconocer:
$ make nada
make: *** No rule to make target 'nada'. Stop.
$ mkdir sin; cd sin; make
make: *** No targets specified and no makefile found. Stop.
El primero dice que no hay regla ni archivo con ese nombre. El segundo, que ni siquiera hay un Makefile en el directorio. Si falta un requisito que tampoco se puede construir, el mensaje nombra qué objetivo lo pedía: No rule to make target 'faltante.o', needed by 'prog'.
4. Los encabezados también son requisitos
En la versión 1 cada objeto lista inc/util.h entre sus requisitos, y no es un detalle: make solo sabe lo que el Makefile le dice. Los #include de los fuentes no los lee. Para ver qué pasa si se omite, un proyecto mínimo donde a.c usa una constante de inc/util.h, y la regla de a.o declara solamente a.c:
CFLAGS = -Iinc
a: a.o
$(CC) -o $@ $^
a.o: a.c
$(CC) $(CFLAGS) -c $<
$ make
cc -Iinc -c a.c
cc -o a a.o
$ ./a
2
$ sed -i 's/2/3/' inc/util.h
$ make
make: 'a' is up to date.
$ ./a
2
El encabezado cambió, pero make afirma que todo está al día y el programa sigue imprimiendo el valor viejo. Es una de las fuentes de error más insidiosas del desarrollo en C: un binario que no refleja el código. Hay dos remedios. El simple, el de la versión 1, es listar a mano los encabezados de cada objeto. El correcto para proyectos de verdad es que el compilador mismo genere esa lista (gcc -MMD), lo que se hace en el tema 36. Mientras tanto, ante la duda, make clean y volver a construir.
5. La receta: cada línea es un shell
Las líneas de una receta las ejecuta /bin/sh, y detalles de cómo lo hace explican varios comportamientos curiosos:
- Cada línea se ejecuta en un shell distinto. Un
cden una línea no afecta a la siguiente. - Un
@al comienzo evita quemakeimprima la orden antes de ejecutarla (útil paraecho). - Un
-al comienzo ignora el fallo de esa orden. - Si una orden falla (código de salida distinto de cero),
makese detiene y no ejecuta el resto, como indica el avisoError 1; el propiomaketermina con código 2.
/* sh.mk */
x:
@echo con arroba
echo sin arroba
cd /tmp; pwd
pwd
cd /tmp && pwd
$ make -f err.mk a; echo "rc=$?"
false
make: *** [err.mk:2: a] Error 1
rc=2
$ make -f err.mk b; echo "rc=$?"
false
make: [err.mk:5: b] Error 1 (ignored)
echo si llega
si llega
rc=0
$ make -f sh.mk x
con arroba
echo sin arroba
sin arroba
cd /tmp; pwd
/tmp
pwd
/home/ana/c/proy
cd /tmp && pwd
/tmp
En a, el false falla y la segunda línea nunca corre. En b, el guion hace que make siga. En sh.mk, la línea @echo con arroba se imprime sin repetir la orden; cd /tmp; pwd funciona porque el cd y el pwd están en la misma línea, y el pwd solo de la línea siguiente muestra que el directorio no cambió. Para encadenar, se escribe todo en una línea con && o ;, que puede partirse con \ al final.
Otra consecuencia: el símbolo $ lo interpreta make primero. Para que una variable llegue al shell hay que duplicarlo: $$f.
all:
@for f in uno dos; do echo "archivo $$f"; done
$ make -f dd.mk
archivo uno
archivo dos
6. Variables y variables automáticas
La versión 1 repite gcc y las opciones en cada regla. Las variables permiten escribirlas una vez. Se definen con NOMBRE = valor y se usan con $(NOMBRE). Por convención, y porque las reglas implícitas de make las usan (sección 7), se emplean estos nombres:
| Variable | Contenido |
|---|---|
CC | El compilador de C. |
CPPFLAGS | Opciones del preprocesador: -Iinc, -DDEBUG. |
CFLAGS | Opciones del compilador: -std=c17 -Wall -Wextra -O2 -g. |
LDFLAGS | Opciones del enlazador: -L…, -Wl,-rpath,…. |
LDLIBS | Las bibliotecas a enlazar: -lm, -lbasico. Van al final (tema 32). |
Y las variables automáticas, que make rellena en cada regla según el objetivo y los requisitos, y que evitan repetir los nombres:
| Variable | Significado |
|---|---|
$@ | El nombre del objetivo. |
$< | El primer requisito. |
$^ | Todos los requisitos, sin repetir. |
$? | Los requisitos más nuevos que el objetivo. |
Con ellas, la versión 2 del Makefile se acorta y se vuelve más fácil de mantener. Usa una regla de patrón (sección 7) para no escribir tres veces la receta de los objetos:
CC = gcc
CFLAGS = -std=c17 -Wall -Wextra -Iinc
OBJ = main.o numeros.o texto.o
prog: $(OBJ)
$(CC) -o $@ $(OBJ)
%.o: %.c inc/util.h
$(CC) $(CFLAGS) -c $<
clean:
rm -f prog $(OBJ)
$ make clean; make
rm -f prog main.o numeros.o texto.o
gcc -std=c17 -Wall -Wextra -Iinc -c main.c
gcc -std=c17 -Wall -Wextra -Iinc -c numeros.c
gcc -std=c17 -Wall -Wextra -Iinc -c texto.c
gcc -o prog main.o numeros.o texto.o
Para ver las variables automáticas en acción, un objetivo que solo las muestra. Con main.o y numeros.o recién creados, y luego con numeros.o más nuevo que el objetivo todo:
todo: main.o numeros.o
@echo "@=$@"
@echo "<=$<"
@echo "^=$^"
@echo "?=$?"
$ make -f va.mk
@=todo
<=main.o
^=main.o numeros.o
?=main.o numeros.o
$ touch todo; sleep 1; touch numeros.o; make -f va.mk
@=todo
<=main.o
^=main.o numeros.o
?=numeros.o
La diferencia entre $^ (todos) y $? (solo los que provocaron la reconstrucción) se ve en la segunda ejecución.
Las cuatro formas de asignar
Hay varios operadores de asignación y se comportan distinto:
| Operador | Significado |
|---|---|
= | Asignación diferida: el valor se expande cada vez que se usa, con lo que valgan entonces las demás variables. |
:= | Asignación inmediata: se expande en el momento de definirla. |
?= | Asigna solo si la variable no estaba definida (valor por defecto). |
+= | Agrega al valor existente. |
A = $(B)
B = uno
C := $(B)
B = dos
D ?= defecto
E = base
E += mas
all:
@echo "A=$(A) C=$(C) D=$(D) E=$(E)"
$ make -f var.mk
A=dos C=uno D=defecto E=base mas
$ D=entorno make -f var.mk
A=dos C=uno D=entorno E=base mas
$ make -f var.mk D=linea
A=dos C=uno D=linea E=base mas
A vale dos: con = se expande al usarla, y para entonces B ya cambió. C, definida con :=, conservó uno. D toma el valor por defecto solo si no viene de afuera, y la última ejecución muestra otra regla importante: una variable dada en la línea de órdenes (make D=linea) gana a la del Makefile. Es la manera de cambiar una opción sin editar el archivo:
$ make clean >/dev/null; make CFLAGS="-std=c17 -Iinc -O2 -g"
gcc -std=c17 -Iinc -O2 -g -c main.c
gcc -std=c17 -Iinc -O2 -g -c numeros.c
gcc -std=c17 -Iinc -O2 -g -c texto.c
gcc -o prog main.o numeros.o texto.o
Las variables de entorno también se convierten en variables de make, pero con menos prioridad que el Makefile: si el archivo define V, el entorno no la cambia, salvo que se use make -e. Se ve con un archivo que define V = del makefile:
$ V=entorno make -f env.mk
V=del makefile
$ V=entorno make -e -f env.mk
V=entorno
En resumen, el orden de prioridad (de mayor a menor) es: la línea de órdenes, el Makefile, el entorno y los valores por omisión de make. Por eso la convención de escribir CFLAGS ?= -O2 (valor por defecto que el usuario puede reemplazar desde afuera) es tan frecuente en proyectos de código abierto.
7. Reglas de patrón y reglas implícitas
La regla %.o: %.c inc/util.h de la versión 2 es una regla de patrón: el % representa cualquier «raíz» de nombre, igual en el objetivo y en los requisitos. Dice: para construir cualquier X.o hace falta X.c y el encabezado, y se hace con esta receta. En la receta, $< es el .c correspondiente (el primer requisito). Con tres módulos el ahorro es modesto, pero con cien es la diferencia entre un Makefile manejable y uno que nadie quiere tocar.
Lo curioso es que make ya trae reglas implícitas para los casos comunes, sin que se escriba nada. En un directorio sin Makefile, con solo un hola.c:
$ make hola
cc hola.c -o hola
$ make hola
make: 'hola' is up to date.
$ make -B hola CFLAGS="-Wall -O2" LDLIBS=-lm
cc -Wall -O2 hola.c -lm -o hola
$ make -n -B hola CPPFLAGS=-DDEBUG
cc -DDEBUG hola.c -o hola
$ make hola.o
cc -c -o hola.o hola.c
Sin saber nada del proyecto, make descubrió que para el objetivo hola existe hola.c y aplicó su receta incorporada. Las variables de la sección 6 son justamente las que esa receta consulta: CFLAGS y CPPFLAGS al compilar, LDFLAGS y LDLIBS al enlazar. (Los espacios de más en la salida son las variables vacías que se expanden.) El compilador por defecto es cc, el nombre genérico que en Linux suele ser gcc. Se pueden consultar las reglas y variables incorporadas con make -p (print database):
$ make -p -f /dev/null 2>/dev/null | grep -A2 '^%.o: %.c'
%.o: %.c
# recipe to execute (built-in):
$(COMPILE.c) $(OUTPUT_OPTION) $<
$ make -p -f /dev/null 2>/dev/null | grep -E '^(CC|COMPILE.c|OUTPUT_OPTION) '
COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
OUTPUT_OPTION = -o $@
CC = cc
Con esto se entiende el método habitual en proyectos de C: definir las variables correctas y dejar que las reglas implícitas hagan el trabajo, o escribir una regla de patrón propia cuando se necesite controlar la receta, como en la versión 2, donde los encabezados deben figurar como requisito, algo que la regla incorporada no sabe.
8. Listas de archivos: wildcard y patsubst
Mantener la lista OBJ = main.o numeros.o texto.o a mano se vuelve incómodo al agregar archivos. make tiene funciones para calcularla, que se invocan como $(función argumentos):
| Expresión | Resultado |
|---|---|
$(wildcard *.c) | Los archivos que coinciden con el patrón: main.c numeros.c texto.c. |
$(patsubst %.c,%.o,$(SRC)) | Cambia la terminación de cada elemento de la lista. |
$(SRC:.c=.o) | Forma abreviada de lo anterior (referencia con sustitución). |
$(info texto) | Imprime el texto al leer el Makefile, útil para depurar. |
SRC = $(wildcard *.c)
OBJ = $(patsubst %.c,%.o,$(SRC))
$(info SRC = $(SRC))
$(info OBJ = $(OBJ))
$(info DIR = $(notdir $(CURDIR)))
all: ;
$ make -f info.mk
SRC = main.c numeros.c texto.c
OBJ = main.o numeros.o texto.o
DIR = proy
make: 'all' is up to date.
(all: ; es una regla sin requisitos y con receta vacía, solo para que haya un objetivo.) Con estas funciones, la versión 3 del Makefile no necesita cambios al sumar un módulo: basta crear el .c nuevo. Además adopta la convención de separar CPPFLAGS (preprocesador) de CFLAGS (compilador), y usa $^ para enlazar con todos los objetos:
CC = gcc
CPPFLAGS = -Iinc
CFLAGS = -std=c17 -Wall -Wextra
SRC = $(wildcard *.c)
OBJ = $(SRC:.c=.o)
prog: $(OBJ)
$(CC) -o $@ $^
%.o: %.c inc/util.h
$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
clean:
rm -f prog $(OBJ)
$ make clean; make
rm -f prog main.o numeros.o texto.o
gcc -Iinc -std=c17 -Wall -Wextra -c -o main.o main.c
gcc -Iinc -std=c17 -Wall -Wextra -c -o numeros.o numeros.c
gcc -Iinc -std=c17 -Wall -Wextra -c -o texto.o texto.c
gcc -o prog main.o numeros.o texto.o
$ ./prog
maximo=9 promedio=5.50
ecalne
Hay que tener cuidado con una consecuencia de wildcard: toma todos los .c del directorio, incluidos los de prueba o los que no formen parte del programa. Para proyectos con subdirectorios y varios ejecutables, el tema 36 usa listas explícitas o patrones por carpeta.
9. Objetivos que no son archivos: clean
Un objetivo como clean no produce un archivo llamado «clean»: es un nombre para una acción. Funciona porque, mientras no exista un archivo con ese nombre, make lo considera siempre desactualizado y ejecuta la receta. Pero si un día aparece un archivo (o directorio) llamado clean, la regla deja de ejecutarse:
$ cat p.mk
clean:
rm -f a a.o
$ touch clean; make -f p.mk clean
make: 'clean' is up to date.
$ cat p2.mk
.PHONY: clean
clean:
rm -f a a.o
$ make -f p2.mk clean
rm -f a a.o
La declaración .PHONY: clean (de phony, falso) le dice a make que ese objetivo es solo una acción, que no corresponde a un archivo, y que debe ejecutarse siempre. Es el remedio estándar para clean, all, install y test; el tema 36 lo desarrolla junto con la estructura completa de un Makefile real. La convención de nombres entre los programadores de C es: all construye todo (y va primero), clean borra lo generado, install copia a su destino y test corre las pruebas.
10. Opciones útiles y cómo depurar
La mayoría de los problemas de un Makefile se resuelven viendo qué haría make sin hacerlo, o por qué decidió lo que decidió:
| Opción | Qué hace |
|---|---|
-n | Muestra las órdenes que ejecutaría, sin ejecutarlas (dry run). |
-B | Reconstruye todo, ignorando las fechas. |
-s | Silencioso: no imprime las órdenes. |
-q | No ejecuta nada; el código de salida dice si hay algo desactualizado (0 = al día, 1 = hay trabajo). |
-f archivo | Usa otro Makefile. |
-j N | Ejecuta hasta N recetas en paralelo. |
--debug=b | Explica por qué se rehace cada objetivo. |
-p | Imprime todas las reglas y variables que conoce (incorporadas y propias). |
$ make -q; echo "rc=$?"
rc=0
$ touch a.c; make -q; echo "rc=$?"
rc=1
$ make -s; ./a
3
El caso de -j merece un ejemplo, porque es la mejor razón práctica para tener las dependencias bien escritas: make sabe qué objetivos son independientes y los puede construir a la vez. Tres objetivos que tardan un segundo cada uno:
all: a b c
a:
@sleep 1; echo a
b:
@sleep 1; echo b
c:
@sleep 1; echo c
$ time make -f j.mk
a
b
c
real 0m3.010s
$ time make -j3 -f j.mk
a
b
c
real 0m1.005s
En serie son tres segundos; con -j3, uno. En una compilación real, make -j$(nproc) usa todos los núcleos, y es la razón por la que un Makefile con dependencias incompletas puede funcionar «de casualidad» en serie y fallar en paralelo: si un requisito no está declarado, make no sabe que debe esperarlo.
11. Errores frecuentes
| Error | Qué pasa | Corrección |
|---|---|---|
| Sangrar la receta con espacios | Makefile:2: *** missing separator. Stop. | Un carácter tabulador al comienzo de cada línea de la receta. Configurar el editor para no convertirlo. |
| No listar los encabezados como requisitos | make dice up to date y el binario usa valores viejos. | Listarlos, o generar las dependencias con gcc -MMD (tema 36). |
Un cd en una línea de la receta y el comando en la siguiente | Cada línea es un shell nuevo: el cd no persiste. | cd dir && orden en la misma línea. |
Usar $VAR del shell en una receta | make lo interpreta como $V seguido de AR, y queda vacío. | Duplicar el signo: $$VAR. |
Objetivo clean sin .PHONY | Si existe un archivo clean, la regla no corre. | .PHONY: clean. |
| Un Makefile donde el objetivo principal no es el primero | make sin argumentos ejecuta otra cosa (clean, por ejemplo). | Poner all (o el programa) arriba. |
| Que la receta no cree el objetivo que declara | Se ejecuta en cada make, porque el archivo nunca aparece. | La receta debe producir exactamente $@. |
Poner las bibliotecas en CFLAGS o antes de los objetos | undefined reference (tema 32). | Las bibliotecas en LDLIBS, al final de la línea de enlace. |
| Esperar que el entorno cambie una variable ya definida en el Makefile | Se usa el valor del Makefile. | ?= en el Makefile, o pasar la variable en la línea de órdenes. |
Confundir = con := | Una variable se expande con un valor posterior, o una referencia circular provoca Recursive variable references itself. | := cuando se quiere el valor en ese momento. |
| Declarar un requisito que no existe ni se puede construir | No rule to make target 'x', needed by 'y'. | Revisar el nombre y la ruta; make -n o --debug=b muestran el camino. |
Usar -j con dependencias incompletas | Fallos intermitentes: un objetivo usa algo que otro todavía no generó. | Declarar todos los requisitos; probar con make -j8 desde cero. |
Mezclar wildcard *.c con archivos que no forman el programa | Se compilan y se enlazan por error, con multiple definition de main, por ejemplo. | Lista explícita de fuentes, o separar las pruebas en otro directorio. |
12. Resumen
makelee unMakefilecon reglasobjetivo: requisitosy una receta sangrada con tabulador. Rehace un objetivo si no existe o si algún requisito tiene una fecha más reciente; si no, dice is up to date.- Sin argumentos construye el primer objetivo;
make objetivoconstruye uno concreto. Las reglas forman un grafo y se construyen primero los requisitos. makeno lee los#include: los encabezados hay que declararlos como requisitos (a mano o, en el tema 36, con dependencias automáticas), o un cambio en ellos no provoca recompilación.- Cada línea de la receta se ejecuta en un shell distinto;
@la oculta,-ignora su error; si una orden falla,makese detiene. El$del shell se escribe$$. - Variables:
=(diferida),:=(inmediata),?=(por defecto),+=(agregar). Prioridad: línea de órdenes, Makefile, entorno. Los nombres convencionales sonCC,CPPFLAGS,CFLAGS,LDFLAGSyLDLIBS. - Variables automáticas:
$@(objetivo),$<(primer requisito),$^(todos) y$?(los más nuevos). - Una regla de patrón (
%.o: %.c) cubre todos los casos;maketrae reglas implícitas para C, que usan esas mismas variables.$(wildcard …)y$(SRC:.c=.o)arman las listas de archivos. .PHONY: cleanmarca los objetivos que son acciones y no archivos. Para depurar:-n,--debug=b,-p,$(info …);-Bfuerza todo,-qconsulta,-j Nejecuta en paralelo.
13. Preguntas de repaso
Intenta responder cada una antes de abrir la respuesta.
1. ¿Qué tres partes tiene una regla de Makefile y qué hace cada una?
El objetivo es lo que se construye; los requisitos son los archivos de los que depende; la receta son las órdenes (cada una iniciada por un tabulador) que producen el objetivo. La receta se ejecuta si el objetivo no existe o algún requisito es más nuevo.
2. ¿Qué construye make si se ejecuta sin argumentos?
El primer objetivo del Makefile (el objetivo por defecto). Por eso el objetivo principal, all o el programa, se pone al principio.
3. ¿Cómo decide make si debe rehacer un objetivo?
Compara fechas de modificación: si el objetivo no existe, o alguno de sus requisitos tiene una fecha posterior, ejecuta la receta. Antes construye recursivamente los requisitos que a su vez sean objetivos. Si nada cambió, avisa is up to date.
4. El Makefile muestra missing separator. ¿Qué suele ser?
Una línea de la receta sangrada con espacios en lugar de un tabulador (o una línea que make no reconoce como regla ni como asignación). Se corrige poniendo un tabulador real al comienzo de cada línea de receta.
5. Se cambió un valor en util.h y make dice up to date. ¿Por qué, y cómo se arregla?
Porque make no analiza los #include, y el Makefile no declara el encabezado como requisito de los objetos que lo usan. Se arregla agregándolo a los requisitos o, mejor, generando las dependencias con el compilador (gcc -MMD, tema 36).
6. ¿Por qué un cd en una línea de la receta no afecta a la línea siguiente?
Porque make ejecuta cada línea en un shell distinto. Para que el directorio cuente, el cd y la orden deben ir en la misma línea (cd dir && orden).
7. ¿Qué hacen un @ y un - al comienzo de una línea de receta?
@ evita que make muestre la orden antes de ejecutarla; - hace que se ignore un fallo de esa orden y continúe con las siguientes.
8. ¿Cómo se usa una variable de shell, como $f de un bucle, dentro de una receta?
Duplicando el signo: $$f. make procesa primero los $; con dos, entrega uno solo al shell.
9. ¿Qué valen $@, $< y $^ en prog: main.o numeros.o texto.o?
$@ es prog, $< es main.o (el primer requisito) y $^ es main.o numeros.o texto.o (todos).
10. ¿Qué diferencia hay entre =, :=, ?= y +=?
= expande el valor cada vez que se usa; := lo expande en el momento de la asignación; ?= asigna solo si la variable no estaba definida; += agrega al valor existente.
11. ¿Qué gana: la variable definida en el Makefile, en el entorno o en la línea de órdenes?
La línea de órdenes (make CFLAGS=-O2) gana a todo; después, el Makefile; después, el entorno (salvo que se use make -e); y por último los valores incorporados de make.
12. ¿Qué es una regla de patrón y qué hace %.o: %.c?
Es una regla genérica donde % representa cualquier raíz de nombre. %.o: %.c dice que cualquier X.o se obtiene de X.c, con una misma receta; dentro, $< es el .c y $@ el .o.
13. Hay un hola.c y ningún Makefile. ¿Qué hace make hola y qué variables influyen?
Usa su regla implícita y ejecuta algo como cc hola.c -o hola. Consulta CC, CFLAGS, CPPFLAGS, LDFLAGS y LDLIBS, que pueden definirse en la línea de órdenes.
14. ¿Para qué sirve .PHONY: clean?
Para declarar que clean es una acción y no un archivo. Sin la declaración, si existe un archivo llamado clean, make lo considera actualizado y no ejecuta la receta.
15. ¿Cómo se ve qué haría make sin ejecutarlo, y cómo se ve por qué rehace un objetivo?
make -n imprime las órdenes sin ejecutarlas; make --debug=b explica, por ejemplo, Prerequisite 'numeros.c' is newer than target 'numeros.o'. Con -p se ven todas las reglas y variables.
16. ¿Por qué make -j puede revelar errores en un Makefile que funciona en serie?
Porque en paralelo ejecuta a la vez todo lo que no dependa entre sí según lo declarado. Si falta declarar un requisito, en serie el orden podía salir bien por casualidad, y en paralelo un objetivo corre antes de que exista lo que necesita.
Para practicar
- Escribe a mano el Makefile de la versión 1 para tu propio proyecto con tres módulos. Prueba
make,makeotra vez, ytouchsobre cada archivo para ver qué se rehace. - Quita un encabezado de los requisitos, cambia una constante en él y comprueba que el binario queda viejo. Después restáuralo y compáralo con
make --debug=b. - Pasa el Makefile a la versión 3 con variables, regla de patrón y
wildcard. Agrega un cuarto módulo y verifica que no hay que tocar el Makefile. - Usa
make CFLAGS="-O2 -g"ymake CC=clang(si lo tienes) sin editar el archivo. Comprueba conmake -nlas líneas resultantes. - Agrega los objetivos
all,cleanyrun(que ejecuta el programa), todos con.PHONY. Crea un archivo llamadoruny verifica que sigue funcionando. - Con el
sleepdel ejemplo de-j, mide el tiempo con-j1,-j2y-j3. ¿Qué pasa con-j8si hay solo tres objetivos? - Agrega al Makefile la construcción de la biblioteca estática del tema 32 (
ar rcs) y el enlace del programa con ella enLDLIBS. ¿Dónde tienen que ir-Ly-l? - Ejecuta
make -p -f /dev/null | lessy busca las reglas incorporadas para%.o: %.c,%: %.cy para archivos de lex/yacc. ¿Qué otros lenguajes conocemakesin que se le diga?