5.4 KiB
Make
Qué hacía Go por mí
go build hace tres cosas sin que las pidas:
- Lee los
importy deduce qué paquetes dependen de cuáles. - Recompila solo lo que cambió, usando la caché de
$GOCACHE, que va por hash del contenido. - Llama al compilador y al enlazador con los flags correctos para cada
plataforma (
GOOS/GOARCH).
En C no pasa nada de eso. gcc compila lo que le pases y nada más. make es
la herramienta clásica para escribir a mano ese grafo de dependencias.
El modelo: un grafo de ficheros con fechas
Un Makefile es una lista de reglas:
objetivo: prerrequisitos
receta # OJO: la línea empieza por TABULADOR, no por espacios
- Objetivo: el fichero que se quiere producir, por ejemplo
build/escpos.o. - Prerrequisitos: los ficheros de los que depende.
- Receta: los comandos de shell que producen el objetivo.
Make rehace un objetivo si no existe o si algún prerrequisito tiene una fecha
de modificación más reciente que él. No usa hashes como Go: solo compara
fechas (mtime). Por eso un touch basta para forzar una recompilación.
Make no entiende la receta. Para Make, la receta es texto que pasa a la
shell sin leerlo. Que ponga -Iinclude no le dice nada: -I es un flag de gcc,
no de Make. Make solo mira la línea objetivo: prerrequisitos. Si un fichero
no aparece ahí, sus cambios no provocan ninguna recompilación.
El objetivo debe llamarse como el fichero que produce la receta. Si la
regla se llama link/all pero genera build/hello, Make busca un fichero
link/all. Como nunca existe, ejecuta la receta siempre.
Se declaran dependencias, no pasos. No hace falta que una receta llame a
make para construir otras cosas (make[1]: Entering directory... indica que
se está lanzando otro Make). Basta con escribir de qué depende cada objetivo, y
Make deduce el orden:
all: build/hello # sin receta: solo "depende de"
build/hello: build/hello.o build/escpos.o # enlazar
build/hello.o: examples/hello.c include/escpos.h
build/escpos.o: src/escpos.c include/escpos.h
En Go es la diferencia entre llamar a go build paquete por paquete desde
un script y dejar que go build resuelva los imports.
Make recorre el grafo desde el objetivo que le pides (o el primero del fichero) hacia sus prerrequisitos, en profundidad.
Piezas que vas a necesitar
| Pieza | Qué es |
|---|---|
CC = gcc |
Variable. Se usa como $(CC). |
CFLAGS, CPPFLAGS, LDFLAGS |
Nombres convencionales: flags de compilación, del preprocesador (-Iinclude) y del enlazado. |
$@ |
Nombre del objetivo de la regla. |
$< |
Primer prerrequisito. |
$^ |
Todos los prerrequisitos. |
build/%.o: src/%.c |
Regla patrón: sirve para cualquier .c → .o. |
.PHONY: all clean |
Objetivos que no son ficheros. Sin esto, si existiera un fichero llamado clean, make clean no haría nada. |
$(wildcard src/*.c) |
Lista de ficheros que existen. |
$(patsubst src/%.c,build/%.o,$(SRCS)) |
Transforma una lista de nombres. |
.PHONY: objetivos que no son ficheros
clean y all son nombres de acciones, no ficheros. Pero Make no lo sabe: si
alguien crea un fichero llamado clean, la regla (sin prerrequisitos) queda
"al día" y make clean responde 'clean' is up to date sin borrar nada.
Comprobado con touch clean.
.PHONY: all clean
Con esto Make no busca ningún fichero con esos nombres y ejecuta la receta siempre.
En proyectos Go Make se usa como lanzador de tareas (migrate, test,
lint, run...). Ninguna produce un fichero, y hasta build se delega en
go build, que tiene su propia caché y conoce las dependencias mejor que
Make. Por eso allí todo va en .PHONY y la comparación de fechas no se usa
nunca. En C es al revés: Make es el sistema de compilación, y solo las
acciones (all, clean) son phony.
Variables desde la línea de comandos
make CC=clang sobrescribe la variable CC del Makefile solo para esa
ejecución. Pero Make no se da cuenta de que han cambiado el compilador o los
flags, porque solo compara fechas de ficheros. Si los .o están al día, no
hace nada, y si recompila alguno, mezcla .o de gcc con .o de clang. Cuando
cambies compilador o flags: make clean y construir de cero.
En Go los flags y la versión del compilador forman parte del hash de la
caché, así que go build sí recompila lo necesario.
El problema de las cabeceras
Si escpos.c incluye escpos.h y cambias solo el .h, make no lo sabe:
la regla build/%.o: src/%.c no menciona el .h. Resultado: un .o compilado
con la versión vieja de la cabecera. Es un error silencioso que en Go no
existe.
La solución es que gcc genere las dependencias por ti:
-MMDhace que, al compilarx.c, gcc escriba tambiénx.d, un fragmento de Makefile con las cabeceras que incluyó.-MPañade reglas vacías para cada cabecera, para que borrar un.hno rompa el build.-include $(DEPS)carga esos.den el Makefile. El-evita el error si todavía no existen.
Windows y Linux en el mismo Makefile
En MSYS2, la variable de entorno OS vale Windows_NT. En Linux no está
definida.
ifeq ($(OS),Windows_NT)
EXE = .exe
else
EXE =
endif
Más adelante (Fase 4) aquí irán también las librerías que cambian según la
plataforma, como -lws2_32 para Winsock en Windows.