En los títulos y los textos vais a encontrar unas cuantas citaciones cinematográficas (y si, soy un cinéfilo). Si no os interesan podéis fingir no verlas, ya que no son fundamentales para la comprensión de los post...

Este blog es la versión en Español de mi blog en Italiano L'arte della programmazione in C. Espero que mis traducciones sean comprensibles...

sábado, 18 de febrero de 2017

Toma el makefile y corre
cómo escribir un makefile universal

Este es un post rápido. Y no es exactamente un post sobre el C. El consejo es tomar la información, huir y mantenerla celosamente para el futuro, ya que podría ser muy útil un dia. Y que no os capturen, de lo contrario pueden acabar como Virgil Starkwell.

cara de "pero he solo rubato un makefile..."
Así, supongamos que tenemos que hacer un proyecto (que llamaremos, por ejemplo, pluto) y, por diversas razones, no queremos (somos de la vieja escuela) o no podemos (no hay uno adapto) utilizar un IDE. Organizamos nuestros file de una manera clásica, en tres directory: pluto, lib y include. Obviamente escribiremos el código en C y colocaremos los file de una manera lógica (obviamente el file con el main() irá en la directory pluto). Los file son muchos, y cada vez que volvemos a compilar no quieremos reescribir el comando manualmente, y queremos volver a compilar sólo lo que lo necesita (sólo los fuentes modificados) satisfaciendo de forma automática las dependencias de los header (re-compilar sólo aquellos fuentes que dependen de un header modificado)... ¡Entonces nos necesita un makefile! Ok, vosotros ya sabéis lo que es un makefile, pero... ¿ya sabéis escribir uno muy simple y, al mismo tiempo, muy funcional y, sobre todo, genérico y universal? Si la respuesta es NO esto es el vuestro post (y si la respuesta es SI, entonces ¡Hasta el próximo post!).

Acabamos con las chácharas: si estáis leyendo esta línea habéis respondido NO a la pregunta anterior, ¡entonces vamos con el ejemplo!
# variables
CC = gcc
SRCS = $(wildcard *.c)
SRCS_LIB = $(wildcard ../lib/*.c)
OBJS = $(SRCS:.c=.o)
OBJS_LIB = $(SRCS_LIB:.c=.o)
DEPS = $(SRCS:.c=.d)
DEPS_LIB = $(SRCS_LIB:.c=.d)

# creación del target file ejecutable
pluto: $(OBJS) $(OBJS_LIB)
    $(CC) $^ -o $@ -lcurl

# creación de los object files
%.o: %.c
    $(CC) -MMD -MP -I../include -c $< -o $@ -g -Wall -std=c11 -D SIMULATION

# directivas phony
.PHONY: clean

# limpieza proyecto ($(RM) es de default "rm -f")
clean:
    $(RM) $(OBJS) $(OBJS_LIB) $(DEPS) $(DEPS_LIB)

# creación dependencias
    -include $(DEPS) $(DEPS_LIB)
Como se ve el makefile enseñado es verdaderamente simple. Pero también es muy completo: hace todo lo necesario, incluyendo la generación de los files de dependencia de los header, y podemos utilizarlo para cualquier proyecto, sin importar el número de file (las directory lib e include pueden estar vacías o contener cientos de file). Podemos agregar y eliminar fuentes y header y re-compilar sin cambiar una sola línea del makefile, porque el se adapta automáticamente a lo que encuentra en las tres directory del proyecto: ¿Qué queremos más?

Algunos pequeños detalles sobre los bloques (comentados) que componen el makefile:

# variables
Aquí se ponen las variables que se utilizan en el resto del makefile. En particular, la variable CC indica que compilador utilizar: en nuestro caso es gcc, pero podría ser, por ejemplo, g++ (para C++). Obviamente, en este caso las fuentes serían .cpp o .cc, por lo que hay que acordarse de modificar las otras variables que hacen referencia a los .c.

# creacion del target file ejecutable
Aquí se pone el comando para linkar los file objeto creados y producir el file ejecutable final. Si utilizamos laguna librería externa la referencia se añade aquí (en el ejemplo se linka la libcurl usando -lcurl).

# creacion de los object files
Aquí se pone el comando para compilar cada fuente y crear el file objeto correspondiente, activando todas las opciones del compilador que necesitamos. Si utilizamos alguna #ifdef especial (como las que hemos visto alli) la activación hay que ponerla aquí (en el ejemplo se activa una define SIMULATION utilizada en las fuentes).

# directivas phony
Aquí se ponen las directivas phony (es un poco largo de explicar: mirar en el link, que está muy claro). 

# limpieza proyecto ($(RM) es de default "rm -f")
Aquí se pone el comando de borrado de los objetos para forzar, eventualmente, una siguiente completa re-compilación.

# creacion dependencias
Aquí se pone el comando para generar los file de dependencia que nos permiten volver a compilar sólo lo que necesita cuando se modifica un header file. 

El makefile enseñado es un ejemplo real, listo para su uso. Obviamente las directivas -lcurl y -D SIMULATION se han añadido como ejemplo para mostrar cómo extender las funcionalidades del makefile: si no las necesitamos las podemos eliminar sin problemas (y añadiremos las que necesitamos utilizando la misma sintaxis).

¿Qué pensáis? El objetivo no era explicar lo que es un makefile y cómo se escribe (uh, hay una enorme documentación en la red sobre el tema). Tampoco era para explicar los secretos de la sintaxis (que permite también soluciones complejas). El objetivo era proporcionar un makefile básico y completo al mismo tiempo, un makefile universal para (casi) cualquier proyecto. Yo diría que el objetivo se ha logrado... luego, si tenemos que hacer proyectos complejos y portátiles, con auto-instaladores, etc. tal vez nos vamos a encontrar más cómodos usando un IDE de buena calidad o usando herramientas manuales como Autotools  o CMake... pero os aseguro que el método rápido y de la vieja escuela que he descrito es utilizable siempre y sin limitaciones. Que alivio...

¡Hasta el próximo post!