updates
This commit is contained in:
+136
@@ -0,0 +1,136 @@
|
||||
# Make
|
||||
|
||||
## Qué hacía Go por mí
|
||||
|
||||
`go build` hace tres cosas sin que las pidas:
|
||||
|
||||
1. Lee los `import` y deduce qué paquetes dependen de cuáles.
|
||||
2. Recompila solo lo que cambió, usando la caché de `$GOCACHE`, que va por
|
||||
hash del contenido.
|
||||
3. 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**:
|
||||
|
||||
```make
|
||||
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:
|
||||
|
||||
```make
|
||||
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`.
|
||||
|
||||
```make
|
||||
.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:
|
||||
|
||||
- `-MMD` hace que, al compilar `x.c`, gcc escriba también `x.d`, un fragmento
|
||||
de Makefile con las cabeceras que incluyó.
|
||||
- `-MP` añade reglas vacías para cada cabecera, para que borrar un `.h` no
|
||||
rompa el build.
|
||||
- `-include $(DEPS)` carga esos `.d` en 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.
|
||||
|
||||
```make
|
||||
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.
|
||||
Reference in New Issue
Block a user