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, 23 de junio de 2018

Toma el makefile y corre
cómo escribir un makefile universal - pt.2

Louise: Virgil, vamos a tener un niño.
Virgil: No exageres...
Louise: ¡No! No. Vamos a ser padres: he ido a ver el medico. Es mi regalo de Navidad.
Virgil: ¡Hubiese preferido una corbata!
Para Virgil Starkwell era suficiente una corbata... para nosotros es suficiente un buen makefile universal ¿Os acordáis de ese antiguo post en el que he propuesto uno? ¿No os acordáis? Volver inmediatamente a leerlo y luego regresar aquí.
....vamos a tener un makefile...
Aquí estamos (y pequeña premisa: algunos puntos de este post están parcialmente copiados de mi antiguo post... puedo copiar a mi mismo, ¿no?). Si habéis releído el post ya sabéis que será una post rápido, y no exactamente sobre el C: he pensado que era hora de escribir y ofrecer una versión extendida y mejorada del antiguo makefile universal: en comparación con el original esto usa más las variables , por lo que el código es más limpio y legible (¡el estilo, en primer lugar!) y agrega una característica muy interesante: la creación de una shared library (una .so, para los amigos). Veamos esquemáticamente cuáles son los pasos a seguir (en un Linux de la familia Debian) para crear y usar una shared-lib:
1. crear una directory para compartir la shared-lib, por ejemplo: 
       /usr/share/pluto/lib
2. modificar (como veremos luego) el makefile para generar la librería 
   (que llamaremos "libmyutils.so") y copiarla en "/usr/share/pluto/lib".
3. añadir en "/etc/ld.so.conf.d" un nuevo file "libmyutils.conf" que 
   contiene las siguientes dos lineas (la primera es solo un comentario): 
       # libmyutils.so default configuration
       /usr/share/pluto/lib
4. hacer disponible la nueva shared-lib ejecutando: 
       sudo ldconfig
Seguimos: supongamos de usar el mismo proyecto de la otra vez (se llamaba pluto). Nuestros archivos están organizados de manera canónica, esta vez en cuatro directory (la otra vez había tres): pluto, lib, libmyutils e include. La nueva directory es libmyutils, y contiene las fuentes de la shared-lib que queremos crear. En la directory pluto encontramos el main() y el makefile, en la directory lib encontramos las otras fuentes de la aplicación pluto y, finalmente, en la directory include encontramos los header-files comunes a main(), lib y libmyutils. Veamos el nuevo makefile:
# variables
CC = gcc
CPPFLAGS = -I../include -g -Wall -Wshadow -pedantic -std=c11
CPPFLAGS_UTI = -fpic -I../include -g -Wall -Wshadow -pedantic -std=c11
LDFLAGS = -lmyutils -std=c11
LDFLAGS_UTI = -shared -fpic -lcurl -std=c11

# fuentes, objetos y dependencias
SRCS = $(wildcard *.c)
SRCS_LIB = $(wildcard ../lib/*.c)
SRCS_LIB_UTI = $(wildcard ../libmyutils/*.c)
OBJS = $(SRCS:.c=.o)
OBJS_LIB = $(SRCS_LIB:.c=.o)
OBJS_LIB_UTI = $(SRCS_LIB_UTI:.c=.ou)
DEPS = $(SRCS:.c=.d)
DEPS_LIB = $(SRCS_LIB:.c=.d)
DEPS_LIB_UTI = $(SRCS_LIB_UTI:.c=.d)

# tudos los target
all: libmyutils pluto

# creación del target file ejecutable
pluto: $(OBJS) $(OBJS_LIB)
 $(CC) $^ -o $@ $(LDFLAGS) -L/usr/share/pluto/lib

# creación de la shared-lib libmyutils.so
libmyutils: $(OBJS_LIB_UTI)
 $(CC) $^ -o libmyutils.so $(LDFLAGS_UTI)
 mv libmyutils.so /usr/share/pluto/lib

# creación de los object file (para la aplicación y la shared-lib)
%.o: %.c
 $(CC) -MMD -MP $(CPPFLAGS) -c $< -o $@

%.ou: %.c
 $(CC) -MMD -MP $(CPPFLAGS_UTI) -c $< -o $@

# directivas phony
.PHONY: clean

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

# creación dependencias
-include $(DEPS) $(DEPS_LIB) $(DEPS_LIB_UTI)
Como se puede ver, el nuevo makefile presentado es un pariente cercano del anterior y sigue siendo realmente simple y universal: 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 directory del proyecto: ¿Qué queremos más?
 
Añado algunos pequeños detalles sobre los bloques (comentados) que componen el  makefile universale:

# variables
Aqui están las variables que se utilizan en el resto del makefile. Cabe destacar que en CPPFLAGS y LDFLAGS están contenidas todas las opciones de compilación y link necesarias en las etapas de creación de la aplicación, mientras que en CPPFLAGS_UTI y LDFLAGS_UTI están contenidas las relativas a la shared-lib). No daré más detalles sobre el significado de las opciones individuales: tal vez podrían ser el tema de un próximo post... sin embargo, las opciones que he utilizado son definitivamente "universales". Si se utiliza alguna librería externa se puede añadir aquí: en el ejemplo (recordar: ¡es sólo un ejemplo!) he escrito que pluto utiliza libmyutils (con el comando -lmyutils en LDFLAGS) y he escrito que libmyutils utiliza la librería pen-source libcurl (con el comando -lcurl en LDFLAGS_UTI). Tener en cuenta que para compilar y linkar la shared-lib se utilizan dos directivas fundamentales:-fpic (en compilación y link) y -shared (solo en link). Y repito: -fpic se usa tanto en compilación como en link: este es un detalle de que muchas de las guías que están en la red omiten y pueden ser una posible causa de anomalías extrañas de una shared-lib.

# fuentes, objetos y dependencias
Aquí están las directivas que usan internamente el programa make para decidir cómo y dónde buscar fuentes, objetos y dependencias.

# todos los target
Aquí están los objetivos de creación: en nuestro caso, la libreria libmyutils y la aplicación pluto: el comando make "en solo" ejecuta ambos objetivos (la palabra clave es "all"), pero se puede omitir esto ejecutando, por ejemplo, "make libmyutils" que crea solo la shared-lib.

# creación del target file ejecutable
Aquí pone el comando para linkar los file objetos creados y producir el file ejecutable final. Tener en cuenta que con la directiva -L/usr/share/pluto/lib indicamos al linker donde se encuentra la libreria libmyutils.so. También tener en cuenta que esta directiva solo sirve en el nivel del linker, mientras que, en el nivel de ejecución de las aplicaciones que usan la nuestra shared-lib, necesitamos los pasos de la lista descrita al comienzo del post (en particular, los pasos 3 y 4).

# creación de la shared-lib libmyutils.so
Aquí están las instrucciones para la creación de la shared-lib libmyutils.so y par moverla (con el comando "mv") en la directory de destino.

# creación de los object file (para la aplicacion y la shared-lib)
Aquí se pone el comando para compilar cada fuente y crear el file objeto correspondiente, activando (a través de las variables definidas al inicio) todas las opciones del compilador que necesitamos.

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

Creo que por hoy es suficiente... probad en hacer un pequeño proyecto de prueba (por ejemplo, usar funciones medio vacías que solo escriben "Hola, soy la función xyz") y probar el nuevo makefile universale: ¡vais a descubrir que es realmente fácil de usar!

¡Hasta el próximo post!