Guia Básica Makefile
Table of Contents
- 1. Conceptos
- 2. Macros/Variables
- 2.1. Conceptos
- 2.2. Convenciones de Macros
- 2.3. Macros predefinidas
- 2.4. Macros automáticas
- 2.4.1. Conceptos
- 2.4.2. Macro ($@) - Obtener target
- 2.4.3. Macro ($<) - Obtener primer dependencia
- 2.4.4. Macro ($?) - Obtener las dependencias más recientes/actualizadas
- 2.4.5. Macro ($^) - Obtener TODAS las dependencias
- 2.4.6. Macro @(F) y @(D) - Obtener directorio y archivo de un target
- 2.4.7. Macro ($*) - Obtener el valor de un patrón que coincidió (stem)
- 2.5. Macros con comodines
- 2.6. Macros de expansión simple y expansión recursiva
- 2.7. Sustitución temporal de cadenas en Macros
- 3. Target/Objetivo
- 4. Dependencias
- 5. Comandos/Ordenes/Recipes
- 6. Reglas
- 7. Directivas
- 8. Ejemplos prácticos
- 9. Ejemplos Interesantes
- 10. Referencias
- 11. [TODO] Referencias pendientes
- 12. Libros
1. Conceptos
- Se utiliza
make
para cualquier proyecto con dependencias entre archivos, no solo para compilar programas de C.. - En
C
la relación de dependencia es- Los ejecutables dependen de los file objects
.o
- Los file objects
.o
dependen de su código fuente.c
y de los archivos de cabecera.h
que incluyan los.c
- Los ejecutables dependen de los file objects
Observaciones:
- Los archivos
.o
son objetos - Los
.h
son archivos de cabecera (headers), se incluyen declaraciones de estructuras, variables globales y funciones (prototipos/firmas) - Los
.c
son archivos fuente, los que tienen la implementación del programa (de las funciones)
2. Macros/Variables
2.1. Conceptos
- Las macros ó variables son identificadores que se sustituyen por su valor
(al parsear el archivo
Makefile
) - Se expanden (sustituyen por su valor) cuando se usa con el
$
Ej.$(macro)
- Son sensibles a las mayusculas, y no pueden contener espacios
- Se definen al principio del Makefile, si fuesen redefinidas se usará la definición más nueva
# Definimos 2 macros: PROGRAMA, OBJETOS PROGRAMA = juego.exe # <- macro #1 OBJETOS = juego.o jugador.o item.o # <- macro #2 # Cuando GNU Make lea esta regla, reemplazará las macros por su valor # # juego.exe : juego.o jugador.o item.o # gcc -I./include -o juego.exe juego.o jugador.o item.o $(PROGRAMA): $(OBJETOS) gcc -I./include -o juego.exe $(OBJETOS) # las siguientes reglas tienen lógica repetida y son muy básicas, # no entramos en detalle porque lo importante en este ejemplo son las MACROS juego.o : juego.c jugador.h item.h gcc -I./include -c -o juego.o juego.c jugador.o : jugador.c jugador.h gcc -I./include -c -o jugador.o jugador.c item.o : item.c item.h gcc -I./include -c -o item.o item.c
2.2. Convenciones de Macros
2.2.1. Conceptos
La mayoría de los Makefiles
tienden a seguir estas convenciones al crear macros
Nombre | Expansión | Descripcion |
---|---|---|
OBJS | Representa todos los archivos .o | |
Nos evita repetir cada .o como dependencia en varias reglas | ||
INSTALLDIR | Para indicar la ruta donde estará el ejecutable del programa | |
Se tiende a utilizar en un target INSTALL que creemos |
||
INCLUDE | Para indicar una o varias rutas de archivos a incluir | |
(por lo general archivos de cabecera .h ) |
2.2.2. Ejemplo 1 - Sin usar macros
La ventaja de usar esta macro (podriamos ponerle cualquier otro nombre, es sólo una convención) es que sólo la nombramos usando el
$
y listo :)Pero.. si NO la usamos y.. hay muchas reglas que usen los mismos archivos
.c
habrán reglas redundantes que repitan dependencias, y en caso que llegase a dejar de usa alguno de los archivos o cambiase el nombre de alguno de ellos deberiamos modificar uno por uno… :(
juego.exe: juego.c jugadores.c items.c ./include/jugador.h ./include/item.h
gcc -I./include -o juego.exe juego.c jugador.c item.c
2.2.3. Ejemplo 2 - Usando macros
CC = gcc CFLAGS = -I./include PROGRAMA = juego.exe FUENTES = juego.c jugador.c item.c $(PROGRAMA): $(FUENTES) $(INCLUDE)/jugador.h $(INCLUDE)/item.h $(CC) $(CFLAGS) -o $(PROGRAMA) $(FUENTES)
2.2.4. Ejemplo 3 - Usando macros automáticas
CC = gcc CFLAGS = -I./include PROGRAMA = juego.exe FUENTES = juego.c jugador.c item.c # Macros automáticas # - $@ para obtener el nombre del objetivo/target # - $^ para obtener todas las dependencias # # Nota: sacamos los .h de las dependencias, para mostrar el $^ # pero si modificaran los .h, el objetivo no se reconstruiría, # sólo ocurriría si se modificaran los fuentes (.c) # (en otros ejemplos resolvemos esta problemática) $(PROGRAMA): $(FUENTES) $(CC) $(CFLAGS) -o $@ $^
2.3. Macros predefinidas
2.3.1. Conceptos
- Tienen un valor por defecto, por tanto las definimos en el Makefile le estamos sobreescribiendo el contenido
- El compilador gcc se comunica con GNU linker cuando enlaza los objetos (.o) para generar el ejecutable (para más detalles
man ld
) - La opción
-L
del flagLDFLAGS
se usa de la forma-LrutaDeBibliotecas
es decir la ruta va seguido sin espacios (Ej.LDFLAGS=-L/usr/jelou/shared
) - La opción
-l
del flagLDLIBS
se usa de la forma-lnombreBiblioteca
(Ej.LDLIBS=-lm -lpthread -lcommons
) - El flag
LDLIBS
se debe agregar luego de los archivos fuente (.c), caso contrario el GNU Linker podría no funcionar correctamente
Nombre | Expansión | Descripcion |
---|---|---|
MAKE | make | Programa que gestiona los Makefile |
LDFLAGS | - Flags al enlazador/linker ld (cuando el compilador lo invoca) |
|
- Se usa con la opción -L para indicar la ruta donde encontrar bibliotecas que usará la aplicación |
||
LDLIBS | - Se usa con la opción -l para indicar los nombres de las bibliotecas que usará |
|
- Se complementa con LDFLAGS porque estas bibliotecas deberían estar en la ruta que se indicó en ese otro FLAG |
||
CC | gcc | Compilador predefinido de C |
CXX | g++ | Compilador predefinido de C++ |
CFLAGS | Flags del compilador de C | |
CPPFLAGS | Flags para el pre-procesador de C | |
CXXFLAGS | Flags del compilador de C++ |
2.3.2. Ejemplos
2.4. Macros automáticas
2.4.1. Conceptos
- Las macros automáticas son macros internas cuyo valor depende de la regla que estemos ejecutando.
- Estas macros previo a ejecutar un comando evalúan el timestamp del target y de sus dependencias
Para ver algunos de los ejemplos, luego de ejecutar el target
init
que hicimos para simular un programa deberemos actualizar los archivos que se creen. Porque estas macros evalúan las dependencias que sean más recientes que el target.Es decir si tratamos de ejecutarlos luego de usar
init
puede que en algunos solo diga "tal archivo está actualizado" y es porque las dependencias tienen la misma fecha/hora de modificación que el target.Por tanto.. repito, debemos modificar los archivos, para ver funcionar algunos de los ejemplos, si no dirá solo el mensaje que dijimos antes.
Macro | Expansión |
---|---|
$@ | Obtiene el nombre del Target de la regla que se está ejecutando |
$< | Captura la primer dependencia de la regla |
$? | Lista los nombres de las dependencias más recientes que el target, separadas por espacio |
(recordemos lo del timestamp más reciente) | |
$^ | Similar a $? pero lista todas las dependencias, no compara contra el target |
$* | Target con el sufijo eliminado |
2.4.2. Macro ($@) - Obtener target
La macro $@
nos permite obtener el nombre del target
- Ejemplo 1 - Crear y nombrar un ejecutable de C
# 1. "supongamos" que ya tenemos los targets OBJS, CC, ... # 2. El -o es un parámetro de gcc para definir el nombre de ejecutable # en este caso se llamará "unprograma" osea tal cual al target unprograma: $(OBJS) $(CC) $(CFLAGS) -o $@ $(OBJS)
- Ejemplo 2 - Imprimir con el comando echo
# - imprimirá por pantalla el nombre del target! # - el @ antes del echo es para no imprimir el comando echo, # permite que se imprima solo el mensaje que el echo quiere imprimir # - el $@ es la MACRO AUTOMATICA :) obtendrá el nombre del target # es decir "unprograma" unprograma: @echo "el target se llama: " $@
2.4.3. Macro ($<) - Obtener primer dependencia
La macro $<
obtiene el primer archivo de dependencia que permitió que se ejecute la regla
(es decir elige la primer dependencia, la que esté más a la izquierda)
- Ejemplo 1 - Imprimir primer dependencia
# - Siempre va a imprimir el nombe "main.o" porque es la primer dependencia # - La macro $< elige la primer dependencia que hizo que se ejecutara # la regla (la regla está formada por el target+dependencias+comando) unprograma.c: main.o players.o items.o @echo $<
- Ejemplo 2 - Compilar programa C
Recordemos que las dependencias van seguido del target, en este caso se va a compilar un programa que usará sólo el archivo
players.c
# - Se compilará un archivo llamado "programa.o" # - La macro $< obtendrá unicamente la primer dependencia (players.c) # - La segunda dependencia (players.h) nunca será usada por $< # - La macro $@ obtiene el nombre del target (programa.o) programa.o: players.c players.h gcc -c $< -o $@ # codigo de abajo no cuenta para el ejemplo init: touch players.c players.h .PHONY: clean clean: -rm *.{c,h}
2.4.4. Macro ($?) - Obtener las dependencias más recientes/actualizadas
La macro $?
obtiene la dependencia más reciente/actualizada.
(mas reciente respecto comparado con el target de la regla)
Si recordamos que una dependencia representa un archivo, y tenemos varias dependencias
entonces $?
obtendrá el último archivo modificado, el que alteramos recientemente.
Observaciones:
- La macro
$?
se expande cuando hay archivos más recientes que el target - Cada vez que compilemos el programa del ejemplo, el timestamp del target se actualizará
- Ejemplo 1
Si modificamos solo dos achivos luego del
make init
se mostrarán los nombe de esos archivos, (si cambiaramos el$?
por$^
mostraría todas las dependencias/archivos)# - La macro $? obtendrá sólo las dependencias más recientes # - Se intentará compilar un archivo con los últimos .c modificados main.o: main.c players.h players.c gcc $? -c $@ @echo "Se compilaron los archivos" $? # el codigo de abajo no cuenta para el ejemplo init: touch main.o main.c players.c players.h .PHONY: clean clean: -rm *.{c,h}
2.4.5. Macro ($^) - Obtener TODAS las dependencias
La macro $^
obtiene todas las dependencias, no le interesa si fue la más reciente o no
- Ejemplo 1
Si modificamos solo dos archivos luego del
make init
se mostrarán todos los nombre de los archivos, no solo los recientemente modificados. Si queremos sólo los más recientes respecto al target debemos usar la macro$?
# - La macro $^ obtendrá todas las dependencias # - Se intentará compilar un archivo todos los .c y .h main.o: main.c players.h players.c gcc $^ -c $@ @echo "Se compilaron los archivos" $^ # el codigo de abajo no cuenta para el ejemplo init: touch main.o main.c players.c players.h .PHONY: clean clean: -rm *.{c,h}
2.4.6. Macro @(F) y @(D) - Obtener directorio y archivo de un target
- Ejemplo
ho/player/player.o: player/items.c player/account.c @echo "Carpeta:" $(@D) @echo "Archivo:" $(@F) # el codigo de abajo no cuenta para el ejemplo .PHONY: init init: crearDirectorios crearArchivos @echo "Terminamos de ejecutar las dependencias :)" crearArchivos: touch player/items.c player/account.c crearDirectorios: mkdir player .PHONY: clean clean: -rm player/*.{c,o}
2.4.7. Macro ($*) - Obtener el valor de un patrón que coincidió (stem)
- Es el valor del objetivo que concide con el patrón de objetivo
Por ejemplo si el objetivo es
player.c
y el patrón de objetivo es%.c
entonces la cadenaplayer
es la que obtiene la macro$*
2.5. Macros con comodines
2.6. Macros de expansión simple y expansión recursiva
2.6.1. Conceptos
- Las macros NO siempre se expanden en el momento de asignación
- Algunas macros se expanden cuando son usadas en alguna reglas
2.6.2. Expansión simple de una macro con el operador :=
- Si usamos el operador
:=
consideramos que la expansión de la macro será simple - En una expansión simple, la asignación del valor a la macro/variable es inmediata
"Podríamos" asociarlo con el término de expansión ansiosa
# la asignación del valor "-O -Wall" a la macro "CFLAGS" es inmediata # lo mismo para "CC" CFLAGS := -O -Wall CC := gcc # OJO..! esta quizás no sería una buena decisión.. # porque las macros automáticas $@ y $* aún no tienen valor.. # hasta que se usen en alguna regla y le asocie un valor DEPFLAGS := -MT $@ -MMD -MP -MF $*.d programa.exe: main.o $(CC) $(CFLAGS) -o $@ %.o: %.c %.d $(CC) $(DEPFLAGS) $(CFLAGS) -c $*.c -o $@
2.6.3. Expansión única y recursiva con el operador =
- Si usamos el operador
=
consideramos que la expansión de la macro será recursiva - En una expansión recursiva, la asignación del valor se realiza cuando la macro es usada
- La macro se expande una única vez durante tiempo de ejecución
"Podríamos" asociarlo con el término de expansión perezosa (no es inmediata, se dará cuando se evalúe la macro)
UNO = $(ESE) # acá no se expande la macro "ESE" ESE = $(OTRO) # acá tampoco se expande la macro "OTRO" OTRO = Pepito # las macros se expanden en este momento, con la evaluación de la regla culpable: @echo $(UNO) # acá se expanden las macros recursivas
2.7. Sustitución temporal de cadenas en Macros
2.7.1. Conceptos
- Para sustituir una cadena de una macro de forma temporal usamos la sig. sintáxis
$(nombreMacro:textoAsociado=nuevoTexto)
- Otra alternativa a
$(nombreMacro:textoAsociado=nuevoTexto)
sería usar la función patsubst$(patsubst textoAsociado, nuevoTexto, nombreMacro)
- Se utiliza para cambiar la extensión de los archivos que tiene asociado una macro (Ej.
$(OBJ:.o=.c)
para reemplazar .o por .c)
2.7.2. Ejemplo
INCLUDE := ./include OBJ := game.o player.o item.o game.exe: $(OBJ) gcc -o game.exe $(OBJ) # - temporalmente reemplazamos la extensión .o por .c de los archivos # que están asociados a la macro OBJ # - podríamos simplemente agregarlo en una macro SRC = $(OBJ:.o=.c) # pero lo dejamos así para que se sobreentienda su uso game.o: $(OBJ:.o=.c) gcc -I$(INCLUDE) -c -o game.o $(OBJ:.o=.c) player.o: player.c $(INCLUDE)/player.h gcc -I$(INCLUDE) -c -o player.o player.c item.o: item.c $(INCLUDE)/item.h gcc -I$(INCLUDE) -c -o item.o item.c
3. Target/Objetivo
3.1. Conceptos
- Cada target representa el nombre del archivo que se generará, luego de cumplirse la regla
- El target se actualiza si alguna de las dependencias fue actualizada (make compara el timestamp de los headers de los archivos)
- Si make detecta que el target está desactualizado respecto de sus dependencias => se ejecutará la regla
3.2. Target Final
- Es la primera regla del makefile y tiende a ser el ejecutable
- Es el último target en el árbol de dependencias por tanto el último que se ejecuta
- Se lo suele llamar
all
3.3. Falso target
3.3.1. Conceptos
- No se les suele agregar Dependencias
- Cuando queremos que la regla se ejecute siempre (evitamos que compare el timestamp del target y las dependencias)
- Es un target que no existe (porque un target tiende a representar un archivo físico que persiste en disco)
- Make nota que no aparece el archivo en las entradas del directorio donde esté el Makefile, entonces ejecuta los comandos que generan la regla
Un posible problema sería si agregamos un falso target y en el directorio existe un fichero con ese nombre.
Porque al no tener dependencias el
make
interpretará que el archivo está actualizado, y no ejecutará los comandos de la reglaLa solución a esto es utilizar
PHONY
(representa a un target ficticio)
3.3.2. Ejemplos
# este es un ejemplo típico de un "falso target", que borra los archivos .o de fichero c compilado # - porque "clean" no es un archivo en si (no debería) # - la regla tampoco tiene dependencia (lo que se agrega seguido de los dos puntos) clean: rm *.o # Podemos usar .PHONY # para evitar problemas si llegase a existir un archivo "clean" .PHONY: clean clean: rm *.o
3.4. Target Ficticio/Simbólico - Phony
3.4.1. Conceptos
- Soluciona el problema de los falsos target (que exista un archivo con el mismo nombre del target)
- Se antepone
.PHONY
previo a definir una regla, seguido al nombre del falso target (Ej..PHONY: all
) - Los casos comunes son
.PHONY: all clean
Observación:
- A diferencia de un target común, en uno con
.PHONY
el comando de la regla se ejecutará antes que la dependencia - En un target común, cuando se cumple la regla (se ejecutan todas las dependencias) => luego el comando asociado
Cuando a un falso target le agregamos como dependencia otro falso target la regla siempre se cumple. Por tanto podemos usar los falsos targets como subrutinas a ejecutar.
3.4.2. Ejemplos comunes
Target | Descripción |
---|---|
all | Ejecuta todas las tareas de la aplicación (Ej. clean, check, install, …) |
install | Crea una instalación a partir de binarios |
clean | Borra todos los archivos binarios |
check | Ejecutar tests asociados a la aplicación |
info | Generar documentación a partir del código fuente de la app |
ctags | Imprimir las firmas de las funciones implementadas |
3.4.3. Ejemplo 1 - Limpiar archivos compilados
# Si usamos .PHONY # evitamos problemas si llegase a existir un archivo "clean" # (osea que un fichero con el mismo nombre que el "falso target") .PHONY: clean clean: rm *.o
3.4.4. Ejemplo 2 - Compilar programas
.PHONY: all all: programa1, programa2 programa1: main.o archivo1.o archivo2.o gcc -o programa1 main.o archivo1.o archivo2.o programa2: main.o archivo1.o archivo3.o gcc -o programa2 main.o archivo1.o archivo3.o
3.4.5. Ejemplo 3 - Subrutinas
Este ejemplo lo podemos ejecutar con makefile cleanall
donde se ejecutarán las subrutinas de borrar los archivos de tipo object
y luego los de texto
.PHONY: cleanall cleanobj cleantxt cleanall: cleanobj cleantxt rm miprograma cleanobj: rm *.o cleantxt: rm *.txt
3.5. Target de Seguimiento
3.5.1. Conceptos
- Para marcar el último momento en el que se produjo un evento
- Útiles cuando en una regla usamos comandos que sólo generan que no generan efecto, como sería el devolver un output (Ej. cat, echo, printf, …)
- Consiste en crear un archivo vacío (el target) del que sólo se usa su timestamp de creación
- Para obtener los archivos modificados más recientes (los compara contra el target) y usar sus nombres con algún comando que no genere efecto
- El
$?
se expande por las dependencias (osea archivos) cuyo timestamp sea más reciente que target
3.5.2. Ejemplo
Suponiendo que ya habiamos creado los archivos main.c, jugadores.c, items.c
Primero ejecutamos
make imprime_actualizados
y se construirá el ejecutable imprimeactualizadosSi modificamos alguno de los .c y volvemos a ejecutar el comando anterior, se ejecutará el
cat nombreArchivo
de los modificados recientemente y se volverá a crear el ejecutable imprimeactualizados contouch imprime_actualizados
(este ejecutable no tiene información útil, sólo la fecha del último archivo actualizado en su header)
Observación:
En este ejemplo el $?
se expande por los archivos más recientes que imprime_actualizados
# Si no crearamos el archivo con "touch", # el target estaría siempre desactualizado, # porque "cat" y "echo" no generan efecto, sólo devuelven un output no crean un archivo, # por lo cual $? compararía contra un target outdate (desactualizado) # - con "cat" imprimimos contenido de un archivo # - el $? se expande con los archivos de timestamp mas reciente que imprime_actualizados # - imprimimos el contenido de los archivos actualizados/modificados # es decir su timestamp es más reciente que el del archivo imprime_actualizados imprime_actualizados: main.c jugadores.c items.c cat $? touch imprime_actualizados # Otro ejemplo.. # Podemos ver los nombres de los archivos .c actualizados recientemente, # creamos el cfiles_actualizados cfiles_actualizados: *.[c] @echo $? touch cfiles_actualizados
4. Dependencias
4.1. Conceptos
- Cada dependencia representa un archivo (si no existe, make busca una regla donde aparezca como target y cree esa dependencia)
- Las dependencias son archivos que son requisitos para cumplir ó construir un target/objetivo (Ej.: juegito: jugador.c items.c jugador.h items.h, los archivos .c y .h serían dependencias para el target juegito)
- GNU Make compara el timestamp de creación/modificación entre las dependencias y el objetivo/target
- Si el timestamp de alguna de dependencia es más moderno que el objetivo/target entonces
- Esa dependencia fue actualizada/modificada, y se volverá a construir el objetivo
- Se ejecuta la orden/es asociado a la regla para construir el objetivo
4.2. Ejemplo
# Este ejemplo no tiene macros automáticas ($?, $<, ...) # tampoco predefinidas (CC, CFLAGS, ...), ni funciones (wildcad, patsubst, ...) # es lo más simple posible para entender las dependencias calculadora.exe: calculadora.o operaciones.o lib.o gcc calculadora.o operaciones.o lib.o -o calculadora.exe calculadora.o: calculadora.c ./include/operaciones.h ./include/lib.h gcc -I./include -c calculadora.c -o calculadora.o operaciones.o: operaciones.c ./include/operaciones.h gcc -I./include -c operaciones.c -o operaciones.o lib.o: lib.c ./include/lib.h gcc -I./include -c lib.c -o lib.o
5. Comandos/Ordenes/Recipes
5.1. Conceptos
- Los comandos se ejecutan cuando se cumple la regla
- Deben estar identados con tabs (si nos olvidamos, habrán errores)
5.2. Definir multiples makefiles
- Por lo general se suele tener sólo un archivo
Makefile
y se ejecuta conmake
- Si el nombre no es
Makefile
podemos usarmake -f nombreDelMakefile
- Si tenemos varios makefiles podemos seguir el estándar de agregarles la extensión
.mk
# suponiendo que tenemos un segundo makefile make -f build.mk # la otra alternativa es make --file=build.mk
5.3. Ignorar errores de ejecución
- Usamos el prefijo
-
previo a declarar el comando en una regla (Ej.-rm *.c
en vez derm *.c
) - Si usamos
-comando
y alguna iteración de éste falla, seguirá ejecutando el resto de instancias
# este era el caso típico que solíamos hacer # si por algún motivo falla el rm, no continúa borrando # (Ej. no encuentra algún archivo .o) .PHONY: clean clean: rm *.o # SOLUCION HERE..! # Si falla el rm por mismos motivos que el de arriba # no importa, seguirá ejecutandose borrando los que falten .PHONY: clean clean: -rm *.o # acá estamos agregando el guión.. :)
5.4. No mostrar la linea de la orden
- Usamos el prefijo
@
previo a declarar el comando en una regla (Ej.@echo hola mundo
en vez deecho hola mundo
) - Se suele utilizar al imprimir mensajes cuando usamos el comando
echo
para evitar que make imprima el comando+mensaje
# - esto sólo imprimira en la shell un "hola mundo" # - si no anteponemos el arroba, lo hará dos veces # y la primera será con el comando echo seguido del string programa: @echo hola mundo
5.5. Simular ejecución del Makefile
- Si ejecutamos
make -n
ómake --just-print
=> simulará la ejecución, imprimiendo los mensajes del Makefile
# ejecutamos el makefile make -n # don't forget el parámetro -n # esto imprimiría la terminal, simulando que se ejecuta el makefile gcc -c jugadores.c gcc -c main.c gcc -o programa main.o jugadores.o
6. Reglas
6.1. Conceptos
- Si una regla tiene una dependencia no existe => GNU Make buscará una regla que la cree (osea que esté como objetivo/target)
- Si una regla tiene archivos en las Dependencias que además son Target/Objetivo de otras reglas, entonces
- Primero se ejecuta la regla donde aparece como
target
- Luego la regla donde aparece como
dependencia
- Primero se ejecuta la regla donde aparece como
# La forma general de cada regla es de la forma target: dependencias comando1 comando2
6.2. Reglas Explícitas
6.2.1. Conceptos
- En las reglas explícitas le indicamos a GNU Make como construir el objetivo/target agregandole una ó varias ordenes que ejecutará
- La orden que agregamos para que se construya el objetivo/target se ejecutará luego que las dependencias se ejecuten ó bien se construyan
6.2.2. Ejemplo Básico - Poco eficiente, sin macros automáticas ni reglas implícitas
# En este ejemplo, no estamos usando macro automáticas ni tampoco reglas implícitas, # lo que implíca que tengamos un Makefile poco configurable, y no óptimo. # # Porque si agregaramos nuevos .c ó .h ó incluso si cambiaramos los nombre de estos, # también deberíamos modificar este Makefile... Si lo llevamos a un proyecto más grande, # con muchos archivos sería dificil de mantener, y poco extensible.. Existiría un gran # nivel de acoplamiento entre el proyecto y este Makefile PROGRAMA = calculadora # nombre del ejecutable CC = gcc # compilador de c # esta macro lo usaremos con el flag -I de gcc # con -I podemos indicar la ruta de los .h que usan los .c # si no indicamos la ruta al compilador gcc, éste fallará y no sabrá donde encontrar los .h INCLUDE = ./include # se podría hacer más genérico si usaramos wildcards + la función patsubst OBJ = calculadora.o operaciones.o lib.o # - primero se ejecutan las dependencias, osea se espera que se construyan los archivos .o # - cuando las dependencias terminen, se ejecuta la orden que se agregó explícitamente # - la orden es opcional, GNU Make la agrega implícitamente (ya sabe que la debería agregar) $(PROGRAMA): $(OBJ) $(CC) -o $(PROGRAMA) $(OBJ) # siempre que alguna de las dependencias calculadora.c ó operaciones.h sea modificada, # entonces se volverá a construir el objetivo (en este caso es el archivo calculadora.o), # - podríamos optimizar la regla y quedara más genérica, si usaramos las macros automáticas como $@ y $^ calculadora.o : calculadora.c $(INCLUDE)/operaciones.h $(CC) -I$(INCLUDE) -c -o calculadora.o calculadora.c # - se repite la misma situación que con la regla que tiene como objetivo/target a calculadora.o # pero las dependencias cambian, y también los parámetros a gcc # - podríamos optimizar la regla y quedara más genérica, si usaramos las macros automáticas como $@ y $^ operaciones.o : operaciones.c $(INCLUDE)/operaciones.h $(INCLUDE)/lib.h $(CC) -I$(INCLUDE) -c -o operaciones.o operaciones.c # - se repite la misma situación que con la regla que tiene como objetivo/target a calculadora.o # pero las dependencias cambian, y también los parámetros a gcc # - podríamos optimizar la regla y quedara más genérica, si usaramos las macros automáticas como $@ y $^ lib.o : lib.c $(INCLUDE)/lib.h $(CC) -I$(INCLUDE) -c -o lib.o lib.c # -------------------------------------------------- .PHONY: clean clean: @-rm *.o @-rm $(PROGRAMA) .PHONY: run run: ; @./$(PROGRAMA)
6.2.3. Ejemplo - Más Optimo
PROGRAMA = calculadora.exe CC = gcc CFLAGS = -I$(INCLUDE) # para que el compilador gcc sepa donde encontrar los .h INCLUDE = ./include # SRC = calculadora.c operaciones.c lib.c SRC = $(wildcard *.c) # obtiene todos los archivo fuente .c OBJ = $(SRC:.c=.o) # reemplazamos la extensión de los archivos .c a .o de la macro SRC # ----------------------------------------------------------------- # (1) REGLA EXPLÍCITA # # - Esta es una Regla Explícita, porque le indicamos a GNU Make # como debe construir el objetivo "calculadora.exe" # con la orden "$(CC) $(CFLAGS) -o $@ $^" # - El objetivo es construir el binario "calculadora.exe" # - Las dependencias son los archivos .o $(PROGRAMA): $(OBJ) $(CC) -o $@ $^ # ----------------------------------------------------------------- # (1) REGLA IMPLÍCITA # # - La siguiente es una Regla Implícita, y hace lo mismo que la regla explícita anterior, # funciona porque GNU Make entiende que implícitamente debe agregar la orden # que usabamos antes "$(CC) -o $@ $^" # - Usar ésta ó la otra forma, pero no ambas, porque no tendría sentido $(PROGRAMA): $(OBJ) # $(CC) -o $@ $^ # <- lo comentamos porque como dijimos más arriba, no sería necesario # ----------------------------------------------------------------- # (3) REGLAS IMPLÍCITAS # # - Estas otras también son reglas implícitas, # porque no tienen una orden que le diga a GNU Make como debe construir los objetivos # - En este caso los objetivos a construir son los archivos .o, # y las dependencias son los archivos de cabecera .h que necesitan # - Podríamos no agregar estas reglas, e igual compilaría el programa calculadora.exe # pero son necesarias si queremos que los objetivos sean reconstruidos # en caso que alguna de sus dependencias (los .h) son actualizadas # # Ej. # Si no tuvieramos estas reglas, e hicieramos cambios en operaciones.h ó lib.h, # entonces GNU Make al leer la regla "$(PROGRAMA): $(OBJ)" (ó bien si quisieramos dejar su forma explícita) # dirá que el objetivo "calculadora.exe" ya está actualizado, y no volverá a construirlo # porque las dependencias .o de esa regla no actualizarían, # y el timestamp del objetivo y esas dependencias estarían el mismo... # # en cambio si agregamos estas reglas, e hicieramos cambios en operaciones.h ó lib.h, # entonces GNU Make volvería a construir los archivos .o (que en estas reglas son objetivos/target) # y en la regla "$(PROGRAMA): $(OBJ)" el objetivo $(PROGRAMA) estaría desactualizado # (por comparar el timestamp de modificación del objetivo y sus dependencias), # por tanto volvería a construirlo también calculadora.o: operaciones.h operaciones.o: operaciones.h lib.h lib.o: lib.h
6.3. Reglas Implícitas
6.3.1. Conceptos
- En las reglas implícitas es el Makefile quien debe interpretar como construir el objetivo/target
- Utiliza el concepto de implicit pattern rules (patrón de reglas implícitas)
6.3.2. Ejemplo
PROGRAMA = calculadora CC = gcc # no interesa si la macro "INCLUDE" se define antes ó después de CFLAGS, # porque el valor de ésta se asigna a la macro CFLAGS cuando se ejecute la orden de regla en donde se use CFLAGS # GNU Make lee bottom-up CFLAGS = -I$(INCLUDE) INCLUDE = ./include #SRC = calculadora.c operaciones.c lib.c SRC = $(wildcard *.c) # buscará en el directorio todas las entradas/archivos que tengan como extensión .c OBJ = $(SRC:.c=.o) # reemplazamos los .c en .o, otra alternativa sería usar la función patsubst de make # (1) Regla Explícita # Indicamos a GNU Make como debe construir el objetivo "calculadora" # 1. Agregamos las dependencias (los archivos que necesita para generar el objetivo) # 2. Definimos la orden (la sentencia donde compilamos con gcc) # # Nota: Esta regla explícita, podría ser implícita porque podriamos borrar la orden "$(CC) -o $@ $^" # y de igual manera GNU Make sabría como interpretar la regla, y que debe agregar por defecto la orden anterior $(PROGRAMA): $(OBJ) $(CC) -o $@ $^ # (3) Reglas implícitas # - Son implícitas, porque no le indicamos a GNU Make como construir el objetivo (en este caso los archivos .o) # es decir no tienen una orden/comando.. Estan formadas por target+dependencias # - En estas reglas los objetos (.o) son objetivos/target, le agregamos dependencias (en este caso archivos cabecera .h de los que dependen) # - Las definimos para que si algún archivo de cabecera (.h) se actualiza, # entonces GNU Make entienda que debe reconstruir los objetivos (en este caso archivos .o) que dependen de esos archivos .h # # Ej. si se actualiza operaciones.h, entonces make volverá a construir los objetivos operaciones.o y calculadora.o pero no lib.o calculadora.o : $(INCLUDE)/operaciones.h operaciones.o : $(INCLUDE)/operaciones.h $(INCLUDE)/lib.h lib.o : $(INCLUDE)/lib.h # -------------------------------------------------- # - Agregamos el @ para que no se imprima el comando en la terminal # - Usamos el - para que en caso de fallar la instrucción "rm", se siga ejecutando # (si no lo usamos, fallará porque no exista algún .o ó el binario y no borrará el resto de los archivos) .PHONY: clean clean: @-rm *.o @-rm $(PROGRAMA) # - Usamos el ; porque para cumplir este objetivo no se requiere dependencias .PHONY: run run: ; @./$(PROGRAMA)
6.4. Reglas de Patrón - Pattern Rules
6.4.1. Conceptos
- Una regla de patrón difiere de una regla común, en que el objetivo/target a construir contiene el caracter
%
(sólo uno) - El símbolo
%
puede representar uno ó varios caracteres cualquiera, para formar el nombre de un archivo con una extensión específica (Ej. %.c, %.h, etc) - Una regla implícita de patrón se puede aplicar a cualquier objetivo/target que coincida/matchee con su patrón (y las dependencias que matcheen deben existir)
El
%
se usa de la forma%.extensionObjetivo : %.extensionDependencias
donde los archivos que son dependencia se usarán para construir el objetivo/target, pudiendo haber varias dependencias con%
Por ejemplo
%.o : %.c comun.h
en esa regla de patrón
- el objetivo/target a construir son archivos con extensión .o (objetos)
- las dependencias (archivos .c que deben existir) que coincidan con el patrón del objetivo (fichero .o) se usarán para construir el objetivo
- podríamos agregar como rutina
gcc -c $< -o $@
para construir los objetos a partir de los .c y el archivo comun.h- se construirá el objetivo siempre que se actualicen las dependencias (gnu make compara el timestamp del target/objetivo y sus dependencias)
OBJ = main.o player.o item.o # (3) reglas implícitas game.exe: $(OBJ) # al escribir estas dos reglas implícitas, estamos reescribiendo la regla de patrón implícita # para cuando GNU Make tenga que construir los objetivos player.o e item.o # sepa que debe agregar como dependencia esos archivos .h # (otra opción sería agregar a la regla de patrón la dependencia %.h, y esto no sería necesario # aunque también debería existir el archivo main.h) player.o: ./include/player.h item.o: ./include/item.h # - estas otras 3 reglas implícitas no serían necesarias, # ya que lo hace la regla de patrón implícita, para cualquier objetivo .o # # main.o: main.c # player.o: player.c # item.o: item.c # REGLA DE PATRÓN # - En realidad es una regla de patrón implícita, # porque GNU Make ya sabe que debe hacer esto.. para construir objetivos .o %.o : %.c $(CC) -I./include -c $< -o $@
6.4.2. Ejemplos
- Ejemplo 1 - Regla Explícita de Patrón Vs Regla Implícita De Patrón
MD_FILES = $(wildcard *.md) HTML_FILES = $(MD_FILES:.md=.html) # REGLA EXPLICITA DE PATRON que podría ser REGLA IMPLICITA DE PATRON # # 1. Es una regla de patrón por tener el símbolo % que actúa como el asterisco en una regex # # 2. Es una regla explícita porque explícitamente le estamos indicando la orden/comando "$(CC) -o $@ $<" # para que GNU Make entienda como construir un objetivo .o a partir de archivos fuente .c # (en el siguiente parrafo aclaramos porque podría no ser necesario agregar dicha orden) # # 3. Podría ser una regla implícita porque GNU Make ya sabe como construir archivos .o a mediante ficheros .c # e implícitamente aplica la orden que pusimos "$(CC) -o $@ $<" # por tanto podríamos remover ese comando quedando sólo "%.o:%.c" # # 4. El motivo para que esta orden sea una Regla Explícita, sería si quisieramos redefinir # la manera en que GNU Make debe construir el objetivo # (Ej. con alguna configuración adicional que no contemplan las macros predefinidas CC, CFLAGS, ...) %.o : %.c $(CC) -o $@ $< # ---------------------------------------------------------- # REGLA EXPLICITA DE PATRON <- Arrojará error, porque el patrón del objetivo no coincide con la regla # # En este ejemplo la regla DEBE ser explícita, porque GNU Make no podría agregar implícitamente # la orden "pandoc --to html5 -o $@ $<" para crear un .html a partir de un .md # # - El patrón del objetivo/target a construir es .md=.html # - La regla de patrón %.html:%.org NO coincide/matchea con el patrón del objetivo .md=html $(HTML_FILES):%.html:%.org pandoc --to html5 -o $@ $<
- Ejemplo 2 - Convertir archivos org-mode a html
ORG_FILES = $(wildcard *.org) # obtenemos los archivos .org de la ruta en donde esté el makefile HTML_FILES = $(ORG_FILES:.org=.html) # reemplazamos la extensión, sólo reemplaza una parte del string # otra manera sería usando la función patsubst # HTML_FILES = $(patsubst %.org, %.html, $(ORG_FILES)) .PHONY: all all: $(HTML_FILES) $(HTML_FILES): %.html: %.org # la regla de patrón debe coincidir con el valor del target cuando éste se va expandiendo @echo "Convirtiendo con pandoc el $< a $@" # simulamos que hacemos la conversión del archivo .org a .html
- Ejemplo 3 - Compilar código fuente C en objetos
# el % reemplaza cualquier caracter %.o: %.c $(CC) -c $< -o $@
6.5. Reglas de Patrón Estáticas
6.5.1. Conceptos
- Una Regla de Patrón Estática es una regla de patrón a la que le agregamos un target donde se debe aplicar la regla
# La sintáxis regla de patrón estática es: # # objetivos: patron_objetivo: patron_dependencias # ordenes OBJ = calculadora.o operaciones.o $(OBJ): %.o: %.c $(INCLUDE)/%.h gcc -I$(INCLUDE) -c $< -o $@ @echo "target=$@ , primer dependencia=$<"
6.5.2. Ejemplo
INCLUDE = ./include OBJ = calculadora.o operaciones.o calculadora: $(OBJ) # esta orden la agrega de forma implícita GNU Make, por tanto es opcional agregarla gcc -o $@ $^ @echo "target=$@ , dependencias=$^" # reescribimos la regla de patrón estática pero para el objetivo "calculadora.o", # le agregamos una nueva dependencia lib.h para que se reconstruya calculadora.o # en caso de que se actualice el archivo de cabecera lib.h calculadora.o: ./include/lib.h # no sería necesario escribir esta regla, ya la genera nueva regla de patrón estática #operaciones.o: operaciones.c ./include/operaciones.h # REGLA DE PATRON ESTATICA # # - Esta regla de patron estática sólo aplica para las dependencias calculadora.c, calculadora.h, # operaciones.c y operaciones.h. Todas esas archivos deben existir, caso contrario GNU Make dirá # que no pudo encontrar alguna de ellas. $(OBJ): %.o: %.c $(INCLUDE)/%.h # esta orden la agrega de forma implícita GNU Make, por tanto es opcional agregarla gcc -I$(INCLUDE) -c $< -o $@ @echo "target=$@ , primer dependencia=$<" # GNU Make interpretará la anterior regla de patrón estática de la sig. manera: # # calculadora.o: calculadora.c ./include/calculadora.h ./include/lib.h <-- aparece lib.h xq reescribimos la regla para calculadora.o # gcc -I./include -c calculadora.c -o calculadora.o # @echo "target=calculadora.o , primer dependencia=calculadora.c" # # operaciones.o: operaciones.c ./include/operaciones.h # gcc -I./include -c operaciones.c -o operaciones.o # @echo "target=operaciones.o , primer dependencia=operaciones.c" #--------------------------------------------------------------------------------------------- # Si a la anterior regla de patrón estática le sacaramos el target $(OBJ) quedando solo %.o : %.c %.h # entonces quedaría una regla de patrón simple, y aplicaría para cualquier .c que coincida con el patrón del objetivo .o # # Ej. # Supongamos que también estaba el archivo fuente funciones.c y sus respectivo archivo de cabecera funciones.h # entonces esta regla de patrón sin el $(OBJ) haria que GNU Make lo interprete como hacía con calculadora.o y operaciones.o # # funciones.o: funciones.c ./include/funciones.h # gcc -o funciones.o funciones.c # @echo "target=funciones.o , primer dependencia=funciones.c" #
6.6. Referencias
6.6.1. Referencias Oficiales
6.6.2. Referencias Extraoficiales
7. Directivas
7.1. Directiva vpath
7.1.1. Conceptos
- Con
vpath
le indicamos a make donde buscar/encontrar un archivo de una lista de dependencias de una regla - Su uso es
vpath patrón ruta
(Un patrón podría ser*.c
para los source files, ó*.h
para los headers) - Nos evita indicar las rutas en las dependencias
(Ej.
player.o: player.c player.h
en vez deplayer.o: src/player.c include/player.h
- Un ejemplo común para un programa de C, es indicarle la ruta para encontrar los source files o headers
The VPATH variable consists of a list of directories to search when make needs a file make will search each directory for any file it needs. If a file of the same name exists in multiple places in the VPATH list, make grabs the first one.
7.1.2. Ejemplo
7.2. [WAITING] Directiva include
8. Ejemplos prácticos
8.1. Proyecto Simple en C
MAKE += -f commons-v3.mk # le cambiamos el nombre de nuestro makefile RM = rm -rfv DIR_BIN = bin DIR_SRC = src DIR_OBJ = obj SRC = $(wildcard $(DIR_SRC)/*.c) OBJ = $(SRC:$(DIR_SRC)/%.c=$(DIR_OBJ)/%.o) BIN = app.out CC=gcc CFLAGS=-g #-Wall #-Wextra INCLUDE=./include CPPFLAGS=-I$(INCLUDE) # le avisamos al pre-procesador de C donde encontrar los archivos de cabecera .h LDFLAGS=-L/usr/include # gcc le indica al GNU linker la ruta donde buscar bibliotecas LDLIBS=-lpthread -lcommons # gcc le indica al GNU linker que bibliotecas usar (agregar luego de los source files) # -------------------------------------------------------------------- .PHONY: all all: $(DIR_BIN)/$(BIN) $(DIR_BIN)/$(BIN): $(OBJ) @echo "Enlazamos los objetos ("$(notdir $^)") para crear el ejecutable" $(notdir $@) en $(dir $@) @$(CC) $(LDFLAGS) $(CFLAGS) $^ -o $@ $(LDLIBS) # - tiene la DESVENTAJA que sólo estamos viendo 1 archivo de cabecera por .h # Ej. player.c tendrá player.h, pero y si también tiene item.h y otro.h # entonces player.o debería de crearse nuevamente... $(OBJ): $(DIR_OBJ)/%.o: $(DIR_SRC)/%.c $(INCLUDE)/%.h @echo "Compilamos el archivo fuente ("$(notdir $<)") en objeto en" $(dir $<) @$(CC) $(CPPFLAGS) $(CFLAGS) -c $(DIR_SRC)/$*.c -o $(DIR_OBJ)/$*.o # -------------------------------------------------------------------- .PHONY: clean clean: @echo "Removiendo archivos ejecutables y objetos existentes" @-$(RM) $(DIR_BIN)/*.out @-$(RM) $(DIR_OBJ)/*.o .PHONY: run run: ; @-$(DIR_BIN)/$(BIN)
8.2. Proyecto Intermedio en C - Alternativa #1 (file dependency + watch)
MAKE += -f commons-v3.mk # le cambiamos el nombre de nuestro makefile RM = rm -rfv DIR_DEP := .dep DIR_BIN := bin DIR_SRC := src DIR_OBJ := .obj DIRS := $(DIR_DEP) $(DIR_BIN) $(DIR_SRC) $(DIR_OBJ) SRC := $(wildcard $(DIR_SRC)/*.c) DEP := $(SRC:$(DIR_SRC)/%.c=$(DIR_DEP)/%.d) OBJ := $(SRC:$(DIR_SRC)/%.c=$(DIR_OBJ)/%.o) BIN := app.out CC := gcc CFLAGS := -g #-Wall #-Wextra INCLUDE :=./include CPPFLAGS :=-I$(INCLUDE) # le avisamos al pre-procesador de C donde encontrar los archivos de cabecera .h LDFLAGS := -L/usr/include # gcc le indica al GNU linker la ruta donde buscar bibliotecas LDLIBS := -lpthread -lcommons # gcc le indica al GNU linker que bibliotecas usar (agregar luego de los source files) # Ojo..! Estamos usando el operador "=" en vez de ":=" # porque queremos que expansión de la macro sea recursiva (la asignación se dará más adelante, cuando se use en una regla) DEPFLAGS = -MT $@ -MMD -MP -MF $(DIR_DEP)/$*.tmp.d .PHONY: all all: $(DIRS) $(DIR_BIN)/$(BIN) $(DIRS): ; @mkdir -p $@ $(DIR_BIN)/$(BIN): $(OBJ) @echo "Enlazamos los objetos ("$(notdir $^)") para crear el ejecutable ($(notdir $@)) en $(dir $@)" @$(CC) $(LDFLAGS) $(CFLAGS) $^ -o $@ $(LDLIBS) # esta otra solución hace lo mismo que la de abajo toda comentada al parecer, pero esta anda ok $(OBJ): $(DIR_OBJ)/%.o: $(DIR_SRC)/%.c $(DIR_DEP)/%.d | $(DIR_DEP) @echo "Se modificó el archivo ($?)" @echo -e "Compilamos el archivo fuente ($(notdir $<)) en objeto en $(dir $@)\n" @$(CC) $(DEPFLAGS) $(CPPFLAGS) $(CFLAGS) -c $(DIR_SRC)/$*.c -o $(DIR_OBJ)/$*.o @mv -f $(DIR_DEP)/$*.tmp.d $(DIR_DEP)/$*.d && touch $@ # se ejecuta si no hubo error de compilación # se sugiere que la directiva "include" esté luego de la definición de la macro que se usa como target, # ó mejor aún al final del Makefile $(DEP): include $(wildcard $(DEP)) # -------------------------------------------------------------------- .PHONY: clean clean: @echo "Removiendo ejecutable, objetos y dependencias" @-$(RM) $(DIR_BIN)/*.out @-$(RM) $(DIR_OBJ)/*.o @-$(RM) $(DIR_DEP)/*{.d,.tmp.d} .PHONY: run run: ; @-$(DIR_BIN)/$(BIN) .PHONY: watch watch: @echo "Observando cambios en la aplicación..." @while true; do $(MAKE) -q || $(MAKE) --no-print-directory; sleep 1; done
8.3. Proyecto Intermedio en C - Alternativa #2 (file dependency + watch)
Este ejemplo a diferencia del anterior, es que usamos algunos comandos
sed
,fmt
, … para resolver el problema de las dependencias (.d)
MAKE += -f commons-v3.mk RM = rm -rfv DIR_DEP := .dep DIR_BIN := bin DIR_SRC := src DIR_OBJ := .obj DIRS := $(DIR_DEP) $(DIR_BIN) $(DIR_SRC) $(DIR_OBJ) SRC := $(wildcard $(DIR_SRC)/*.c) DEP := $(SRC:$(DIR_SRC)/%.c=$(DIR_DEP)/%.d) OBJ := $(SRC:$(DIR_SRC)/%.c=$(DIR_OBJ)/%.o) BIN := app.out CC := gcc CFLAGS := -g #-Wall #-Wextra INCLUDE :=./include CPPFLAGS :=-I$(INCLUDE) # le avisamos al pre-procesador de C donde encontrar los archivos de cabecera .h LDFLAGS := -L/usr/include # gcc le indica al GNU linker la ruta donde buscar bibliotecas LDLIBS := -lpthread -lcommons # gcc le indica al GNU linker que bibliotecas usar (agregar luego de los source files) # Ojo..! Estamos usando el operador "=" en vez de ":=" # porque queremos que expansión de la macro sea recursiva (la asignación se dará más adelante, cuando se use en una regla) .PHONY: all all: $(DIRS) $(DIR_BIN)/$(BIN) $(DIRS): ; @mkdir -p $@ $(DIR_BIN)/$(BIN): $(OBJ) @echo "Enlazamos los objetos ("$(notdir $^)") para crear el ejecutable ($(notdir $@)) en $(dir $@)" @$(CC) $(LDFLAGS) $(CFLAGS) $^ -o $@ $(LDLIBS) # Descripción de algunos de los comandos.. # 1. sed: agregar la ruta de los objects # 2. sed: remueve el target, y cualquier \ slash invertido # 3. fmt: lista las palabras con saltos de linea, convierte los espacios en saltos de linea, porque le damos un ancho de 1 # 4. sed: remueve los espacios al principio de linea, y agrega : al final de cada linea $(OBJ): $(DIR_OBJ)/%.o: $(DIR_SRC)/%.c @echo "Compilamos el archivo fuente ("$(notdir $<)") en objeto en" $(dir $<) @$(CC) $(CPPFLAGS) $(CFLAGS) -c $(DIR_SRC)/$*.c -o $(DIR_OBJ)/$*.o @$(CC) -MM $(CPPFLAGS) $(CFLAGS) $(DIR_SRC)/$*.c > $(DIR_DEP)/$*.d # obtenemos los archivos cabecera y los agregamos al dependency file (.d) @rsync -z $(DIR_DEP)/$*.d $(DIR_DEP)/$*.d.tmp # copia temporal para trabajar @sed -e 's|.*:|$(DIR_OBJ)/$*.o:|' < $(DIR_DEP)/$*.d.tmp > $(DIR_DEP)/$*.d @sed -e 's/.*://' -e 's/\\$$//' < $(DIR_DEP)/$*.d.tmp | fmt -1 | \ sed -e 's/^ *//' -e 's/$$/:/' >> $(DIR_DEP)/$*.d @rm -f $(DIR_DEP)/$*.d.tmp # borramos la copia temporal # se sugiere que la directiva "include" esté luego de la definición de la macro que se usa como target, # ó mejor aún al final del Makefile $(DEP): include $(wildcard $(DEP)) # - tiene la DESVENTAJA que sólo estamos viendo 1 archivo de cabecera por .h # Ej. player.c tendrá player.h, pero y si también tiene item.h y otro.h # entonces player.o debería de crearse nuevamente... # # $(OBJ): $(DIR_OBJ)/%.o: $(DIR_SRC)/%.c $(INCLUDE)/%.h # @echo "Compilamos el archivo fuente ("$(notdir $<)") en objeto en" $(dir $<) # @$(CC) $(CPPFLAGS) $(CFLAGS) -c $(DIR_SRC)/$*.c -o $(DIR_OBJ)/$*.o # -------------------------------------------------------------------- .PHONY: clean clean: @echo "Removiendo ejecutable, objetos y dependencias" @-$(RM) $(DIR_BIN)/*.out @-$(RM) $(DIR_OBJ)/*.o @-$(RM) $(DIR_DEP)/*{.d,.tmp.d} .PHONY: run run: ; @-$(DIR_BIN)/$(BIN) .PHONY: watch watch: @echo "Observando cambios en la aplicación..." @while true; do $(MAKE) -q || $(MAKE) --no-print-directory; sleep 1; done
9. Ejemplos Interesantes
9.1. Expresividad - Lineas muy extensas
Si tenemos lineas muy extensas podemos usar el carácter \
al igual que en bash
main.o: main.c players.h monsters.h items.h \
configs.h events.h messages.h
9.2. Generar varios ejecutables
- Podemos usar
all
(se considea un falso target) cuando tenemos reglas para varios ejecutables, - Si no agregamos este target, sólo se ejecutará la regla 1 (porque es el target final, y la regla 2 no es una dependencia de ella)
Observación:
El make
siempre considera la primera regla como el 3.2 (el ejecutable),
y si la "regla 2" NO es una dependencia de esa primera regla, entonces no lo ejecutará.
# hacemos que se ejecuten ambos por separado # porque no dependen entre ellos all: programa1 programa2 # regla 1 programa1: main.o players.o items.o gcc -o programa1 main.o players.o items.o # regla 2 programa2: server.o configs.o gcc -o programa2 server.o configs.o
9.3. Comandos en Shell diferentes
Cada comando se ejecuta en una shell
diferente
programa: players/items.c @cd players # se ejecuta en otra shell gcc items.c -o $@ # se ejecuta en otra shell comando: @cd test; echo `pwd` # se ejecutan en la misma shell, imprimirá la ruta de la carpeta /test @echo `pwd` # éste se ejecuta en otra shell, imprimirá la ruta actual del makefile
9.4. Prioridad entre target y dependencia
- GNU Make lee las dependencias de izquierda a derecha, y ejecuta las reglas en forma de bottom-up
En este ejemplo, los targets son main.o, players.o, monsters.o
El
main.o
aparece en dos reglas
- En la primera regla aparece como dependencia
- En la segunda regla aparece como target (esta se ejecutará primero)
juegito: main.o players.o monsters.o gcc -o juegito main.o players.o monsters.o main.o: main.c players.h players.c monsters.o gcc -c main.c players.o: players.c players.h gcc -c players.c monsters.o: monsters.c monsters.h gcc -c monsters.c
9.5. Tips para Comandos de Linux
- Usamos
mkdir -p nombreDirectorio
con el parámetro-p
para evitar error si el directorio existe - Ejecutamos
rm -rf ruta/*{.d,.tmp.d}
con las llaves para extensiones específicas en la misma ruta
10. Referencias
10.1. Referencias Oficiales
10.2. Referencias Extraoficiales
- http://profesores.elo.utfsm.cl/~agv/elo320/makefile/makefile.html
- https://www.zator.com/Cpp/E1_4_0a.htm
- https://makefiletutorial.com/
- Practical Makefiles, by Example (nuclear.mutantstargoat.com)
- Most frequently used gcc cmd line options (thegeekstuff.com)
- Advanced auto dependency generation (make.mad-scientist.net)
- Autodependencies with GNU Make (scottmcpeak.com)
- Variables and Macros - Managing Projects with GNU Make (oreilly.com)
- Generation of dependency rules (fatalerrors.org)
- The Make (UNIX) Utility (mathcs.emory.edu)
- Construcción de archivos makefile (it.uc3m.es)
- Automation and Make (swcarpentry.github.io)
- Understanding and using makefile Flags (earthly.dev)
- A sample makefile and how it works (courses.cs.duke.edu)
- Cosas que posiblemente no sepas sobre gnu make #1 (jjmerelo.medmium.com)
- Cosas que posiblemente no sepas sobre gnu make #2 (jjmerelo.medmium.com)
- Comando AR, para crear bibliotecas estáticas (islabit.com)
10.3. Referencias Issues
- Makefile vpath not working for header files (py4u.net)
- What is the difference between % and * (stackoverflow.com)
- A makefile with multiple executables (stackoverflow.com)
- How to add headers to a Makefile (stackoverflow.com)
- Makefile header dependencies (stackoverflow.com)
- Wildcard to obtain list of all directories (stackoverflow.com)
- Sources from subdirectories in makefile (stackoverflow.com)
- Alternative to watch make (stackoverflow.com)
- Makefile silence the maken line specifically (stackoverflow.com)
10.4. Referencias Youtube
11. [TODO] Referencias pendientes
- https://www.cs.colby.edu/maxwell/courses/tutorials/maketutor/
- https://randu.org/tutorials/c/make.php
- https://docs.oracle.com/cd/E19504-01/802-5880/6i9k05dhj/index.html
- https://docs.microsoft.com/en-us/cpp/build/reference/description-blocks?view=msvc-170
- https://gist.github.com/kristopherjohnson/7466917
- https://tsh.io/blog/taskfile-and-gnu-make-for-automation/