Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Automatización de Compilación con Makefiles

Estructura de Makefiles, dependencias y compilación incremental en C

Universidad Nacional de Río Negro

Prerrequisitos: compilación de más de un archivo .c, cabeceras .h y terminal Unix/WSL2.

Objetivos: 1. Leer una regla de Make y sus dependencias. 2. Crear un objetivo que compile un proyecto sin recompilar lo intacto.

Comprobación de salida: explicá qué archivos se reconstruyen tras modificar una cabecera y verificá la predicción con make.

Automatización de Compilación con Makefiles

Introducción

A medida que los proyectos escritos en C crecen y se modularizan en múltiples archivos de cabecera (.h) y de implementación (.c), compilar el código manualmente invocando directamente al compilador en la terminal se vuelve ineficiente y propenso a errores.

Para automatizar este flujo, en el ecosistema Unix se emplea la herramienta make, la cual lee las reglas de construcción de un archivo de configuración denominado Makefile.

Desarrollo

1. ¿Qué es un Makefile y la Compilación Incremental?

La utilidad make funciona modelando el proyecto como un Grafo Dirigido Acíclico (DAG). En esta estructura formal, los nodos representan archivos físicos del sistema de archivos y las aristas dirigidas representan las relaciones de dependencia lógica entre ellos.

Compilación Incremental y Marcas de Tiempo

La principal ventaja de make es la compilación incremental. La herramienta make decide si un archivo objetivo debe reconstruirse comparando la marca de tiempo de modificación física del objetivo con la de sus dependencias en el disco:

Si T(Objetivo)<T(Dependencia)  ⟹  Se ejecuta la regla de compilacioˊn\text{Si } T(\text{Objetivo}) < T(\text{Dependencia}) \implies \text{Se ejecuta la regla de compilación}

Si cambiaste un archivo de cabecera punto.h, todos los archivos .o que dependen de él serán detectados como más viejos que la cabecera modificada y se recompilarán. Los módulos que no dependen de punto.h no serán procesados, permitiendo una compilación incremental eficiente.

Representación como Grafo Dirigido Acíclico (DAG) de dependencias de
compilación. Si main.c cambia, se invalida main.o y por ende se requiere
volver a generar el ejecutable programa, pero se omite la recompilación de
comun.c.

Figure 1:Representación como Grafo Dirigido Acíclico (DAG) de dependencias de compilación. Si main.c cambia, se invalida main.o y por ende se requiere volver a generar el ejecutable programa, pero se omite la recompilación de comun.c.

2. Anatomía de una Regla en un Makefile

Un archivo Makefile está compuesto por un conjunto de reglas que tienen la siguiente estructura sintáctica:

objetivo: dependencias
	receta (comandos de compilación)

3. Evolución Gradual de un Makefile (Las 3 Fases)

Para simplificar la introducción a la sintaxis de make, analizaremos cómo evoluciona un Makefile de forma incremental. Supongamos un proyecto con:

Fase 1: Makefiles Explícitos

En esta fase inicial, declaramos de forma rígida cada archivo y comando de compilación sin abstracciones. Cada regla llama directamente a gcc:

1
2
3
4
5
6
7
8
9
10
# El primer objetivo es el que se ejecuta por defecto
programa: main.o calculadora.o
	gcc -Wall -Wextra -Werror -std=c99 -o programa main.o calculadora.o

# Reglas rígidas para construir cada archivo objeto
main.o: main.c calculadora.h
	gcc -Wall -Wextra -Werror -std=c99 -c main.c -o main.o

calculadora.o: calculadora.c calculadora.h
	gcc -Wall -Wextra -Werror -std=c99 -c calculadora.c -o calculadora.o

Makefile de Fase 1 (Explícito)

Fase 2: Introducción de Variables (CC y CFLAGS)

Para evitar repetir las banderas del compilador y permitir cambiar de compilador rápidamente (por ejemplo, a clang), introducimos variables (o macros de Makefile):

1
2
3
4
5
6
7
8
9
10
11
CC = gcc
CFLAGS = -Wall -Wextra -Werror -std=c99

programa: main.o calculadora.o
	$(CC) $(CFLAGS) -o programa main.o calculadora.o

main.o: main.c calculadora.h
	$(CC) $(CFLAGS) -c main.c -o main.o

calculadora.o: calculadora.c calculadora.h
	$(CC) $(CFLAGS) -c calculadora.c -o calculadora.o

Makefile de Fase 2 (Parametrizado con Variables)

Fase 3: Reglas de Patrón y Variables Automáticas (Producción)

En proyectos reales con decenas de archivos, escribir una regla por cada .c es inviable. Para solucionar esto, la Fase 3 utiliza reglas de patrón (`%o:

1
2
3
4
5
6
7
8
9
10
11
CC = gcc
CFLAGS = -Wall -Wextra -Werror -std=c99
OBJS = main.o calculadora.o

programa: $(OBJS)
	$(CC) $(CFLAGS) -o $@ $^

# Regla de patrón: compila cualquier archivo .o a partir de su .c
# $@ toma el valor del objeto (ej. calculadora.o) y $< el del fuente (calculadora.c)
%.o: %.c calculadora.h
	$(CC) $(CFLAGS) -c $< -o $@

Makefile de Fase 3 (Robustez y Patrones)

Recetario de Plantilla Única

La cátedra provee en las prácticas una plantilla genérica funcional de Makefile para que el alumno pueda copiarla y pegarla directamente en sus proyectos sin necesidad de profundizar inmediatamente en la sintaxis interna de Make. Esta plantilla escanea de forma automática todos los archivos .c de la carpeta para generar el ejecutable:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Variables de compilación
CC = gcc
CFLAGS = -Wall -Wextra -Werror -std=c99

# Nombre del ejecutable binario
TARGET = programa

# Escaneo automático de archivos fuente y generación de objetos
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)

# Regla principal por defecto
all: $(TARGET)

# Vinculación del ejecutable
$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) -o $@ $^

# Compilación de objetos
%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

# Objetivo de limpieza
.PHONY: clean
clean:
	rm -f $(OBJS) $(TARGET)

4. Objetivos Ficticios (.PHONY)

Los objetivos declarados bajo la directiva .PHONY son objetivos especiales que no representan archivos reales en el disco. Le indican a make que debe ejecutar la receta asociada de forma incondicional siempre que se invoque la regla, sin importar si existe un archivo físico con el mismo nombre en la carpeta.

.PHONY: clean
clean:
	rm -f *.o programa

Ejercicios de Autoevaluación

Glosario

Grafo de dependencias
Estructura dirigida que representa las relaciones entre los archivos fuente y los archivos generados.
Compilación incremental
Técnica de optimización que solo recompila los archivos modificados desde la última construcción.
Regla (.PHONY)
Objetivo especial en un Makefile que no representa un archivo real y se ejecuta de forma incondicional.

Síntesis y Resumen

La herramienta make automatiza la compilación incremental leyendo un Makefile. Este archivo define un grafo de dependencias entre objetivos (archivos a generar), prerrequisitos (archivos de entrada) y recetas (comandos de compilación). Al comparar las marcas de tiempo físicas, make evita la recompilación innecesaria de módulos sin cambios. Se utilizan objetivos ficticios (.PHONY) para definir tareas que no producen archivos reales en disco.

Referencias y Lecturas Complementarias

References
  1. Mecklenburg, R. (2004). Managing Projects with GNU Make (3rd ed.). O’Reilly.