Una breve introducción a los Makefiles en el desarrollo de software de código abierto con GNU Make


GNU Make es una utilidad de desarrollo que determina las partes de un código base en particular que se van a recompilar y puede emitir comandos para realizar esas operaciones en el código base. Esta utilidad make en particular se puede utilizar con cualquier lenguaje de programación siempre que su compilación se pueda realizar desde el shell mediante la emisión de comandos.

Para usar GNU Make, necesitamos tener un conjunto de reglas que defina la relación entre diferentes archivos en nuestro programa y comandos para actualizar cada archivo. Estos se escriben en un archivo especial llamado " makefile ". El comando " make " utiliza la base de datos " makefile " y las últimas horas de modificación de los archivos para decidir qué archivos se deben volver a compilar.

Contenido de un Makefile

Generalmente, los " makefiles " contienen 5 tipos de cosas, a saber: reglas implícitas, reglas explícitas, definiciones de variables, directivas y comentarios.

  1. Una regla explícita especifica cómo crear/rehacer uno o más archivos (llamados objetivos, se explicarán más adelante) y cuándo hacer lo mismo.
  2. Una regla implícita especifica cómo crear/rehacer uno o más archivos según sus nombres. Describe cómo se relaciona un nombre de archivo de destino con un archivo con un nombre similar al de destino.
  3. Una definición de variable es una línea que especifica un valor de cadena para una variable que se sustituirá más tarde.
  4. Una directiva es una instrucción para hacer algo especial mientras lee el archivo.
  5. Se utiliza un símbolo "#" para representar el inicio de un comentario dentro de makefiles . Una línea que comienza con "#" simplemente se ignora.

La información que le dice a make cómo recompilar un sistema proviene de la lectura de una base de datos llamada makefile . Un makefile simple constará de reglas de la siguiente sintaxis:

target ... : prerequisites ... 
	recipe 
... 
...

Un objetivo se define como el archivo de salida generado por el programa. También pueden ser objetivos falsos , que se explicarán a continuación. Los ejemplos de archivos de destino incluyen ejecutables, archivos de objeto o destinos falsos como limpiar , instalar , desinstalar , etc.

Un requisito previo es un archivo que se utiliza como entrada para crear los archivos de destino.

Una receta es la acción que make realiza para crear el archivo de destino según los requisitos previos. Es necesario poner un carácter de tabulación antes de cada receta dentro de los makefiles a menos que especifiquemos la variable ".RECIPEPREFIX" para definir algún otro carácter como prefijo de la receta.

final: main.o end.o inter.o start.o
	gcc -o final main.o end.o inter.o start.o
main.o: main.c global.h
	gcc -c main.c
end.o: end.c local.h global.h
	gcc -c end.c
inter.o: inter.c global.h
	gcc -c inter.c
start.o: start.c global.h
	gcc -c start.c
clean:
	rm -f main.o end.o inter.o start.o

En el ejemplo anterior usamos 4 archivos fuente C y dos archivos de encabezado para crear el ejecutable final . Aquí, cada archivo ".o" es tanto un objetivo como un requisito previo dentro del makefile . Ahora eche un vistazo al último nombre de destino clean . Es solo una acción en lugar de un archivo de destino.

Dado que normalmente no lo necesitamos durante la compilación, no está escrito como un requisito previo en ninguna otra regla. Los objetivos que no se refieren a archivos, sino que son solo acciones, se denominan objetivos falsos. No tendrán ningún requisito previo como otros archivos de destino.

De forma predeterminada, make comienza con el primer objetivo en el " makefile " y se llama como " objetivo predeterminado ". Teniendo en cuenta nuestro ejemplo, tenemos final como nuestro primer objetivo. Dado que sus requisitos previos incluyen otros archivos de objeto, estos deben actualizarse antes de crear final . Cada uno de estos requisitos previos se procesa de acuerdo con sus propias reglas.

La recompilación se produce si se realizan modificaciones en los archivos de origen o en los archivos de encabezado o si el archivo objeto no existe en absoluto. Después de recompilar los archivos objeto necesarios, make decide si volver a vincular final o no. Esto se debe hacer si el archivo final no existe, o si alguno de los archivos objeto es más nuevo que él.

Por lo tanto, si cambiamos el archivo inter.c , al ejecutar make se recompilará el archivo fuente para actualizar el archivo objeto inter.o y luego enlace final .

En nuestro ejemplo, tuvimos que enumerar todos los archivos de objeto dos veces en la regla para final como se muestra a continuación.

final: main.o end.o inter.o start.o
	gcc -o final main.o end.o inter.o start.o

Para evitar este tipo de duplicaciones, podemos introducir variables para almacenar la lista de archivos objeto que se están utilizando dentro del makefile . Al usar la variable OBJ podemos reescribir el makefile de muestra en uno similar que se muestra a continuación.

OBJ = main.o end.o inter.o start.o
final: $(OBJ)
	gcc -o final $(OBJ)
main.o: main.c global.h
	gcc -c main.c
end.o: end.c local.h global.h
	gcc -c end.c
inter.o: inter.c global.h
	gcc -c inter.c
start.o: start.c global.h
	gcc -c start.c
clean:
	rm -f $(OBJ)

Como vimos en el ejemplo makefile , podemos definir reglas para limpiar el directorio fuente eliminando los archivos objeto no deseados después de la compilación. Supongamos que tenemos un archivo de destino llamado clean . ¿Cómo hacer diferenciar las dos situaciones anteriores? Aquí viene el concepto de objetivos falsos.

Un objetivo falso es aquel que no es realmente el nombre de un archivo, sino que es solo el nombre de una receta que se ejecutará siempre que se realice una solicitud explícita desde el makefile . Una razón principal para utilizar un objetivo falso es evitar un conflicto con un archivo del mismo nombre. Otra razón es mejorar el rendimiento.

Para explicar esto, revelaré un giro inesperado. La receta para clean no se ejecutará de forma predeterminada al ejecutar make . En su lugar, es necesario invocar el mismo emitiendo el comando make clean .

.PHONY: clean
clean:
	rm -f $(OBJ)

Ahora intente crear makefiles para su propia base de código. No dudes en comentar aquí tus dudas.