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.

Introducción

Universidad Nacional de Río Negro

title: Automatización de Compilación con Makefiles short_title: ‘Makefiles’ subtitle: ‘Estructura de Makefiles, dependencias y compilación incremental en C’

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

Solution to Exercise 1

make examina las marcas de tiempo de modificación (timestamps) de los archivos en el disco físico.

Si la fecha de última modificación del archivo de entrada (funciones.c o alguna de sus cabeceras declaradas como dependencias, ej: funciones.h) es más reciente que la fecha de última modificación del archivo objeto de salida (funciones.o), make deduce que el código fuente cambió y ejecuta la receta de compilación para reconstruir el objeto. Si el objeto es más nuevo que sus dependencias, make omite su compilación.

Solution to Exercise 2

La directiva .PHONY le indica a make que el objetivo indicado no corresponde a un archivo real que deba ser generado.

Si se omitiera .PHONY: clean y en el mismo directorio existiera un archivo llamado clean, al ejecutar make clean, la herramienta buscaría dependencias para el objetivo clean. Al no tener dependencias y ver que el archivo clean ya existe físicamente en el disco, make reportaría que el objetivo está actualizado (make: 'clean' is up to date) y se negaría a ejecutar el bloque de comandos para limpiar el proyecto.

Solution to Exercise 3
usuario.o: usuario.c usuario.h
	$(CC) $(CFLAGS) -c usuario.c -o usuario.o

Nota: La línea de comandos de la receta debe estar precedida obligatoriamente por un carácter de tabulación (Tab) en el archivo físico.

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.