# 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.