El receterio de cocina¶
Siendo que make
se usa para literalmente hornear nuestros programas, este
apunte está construido de forma que nos ayude a construir Makefiles
razonables
para automatizar la compilación de programas más grandes que un “Hola Mundo
”
;-).
Mucha de la funcionalidad avanzada está puesta como una receta, de forma que podamos leer de que se trata cuando tengamos que ver que pasa con algún makefile ya hecho.
En particular porque el manual es colosal Manual GNU/Make
Traducido y adaptado del Tutorial de Makefile
escrito por Chase Lambert en
chaselambda
El ciclo de un programa en C¶
Lo que conocemos como la compilación de un programa C, es una de las cuatro etapas que pasa un programa para ser ejecutable. Actualmente, el compilador se encarga de todas las etapas que le sea posible. En especial cuando se trate
Primeros pasos¶
¿Por qué existen los Makefiles
?¶
Los Makefiles
existen por la misma razón que los scripts de Bash, la
automatización. Solo que en este caso, la herramienta está dedicada a la
construcción y ensamblado de cualquier cosa que requiera de múltiples pasos y
configuración. Se usa generalmente para programas compilados y enlazados, pero
se puede utilizar para cualquier cosa que podamos llamar desde la consola, como
por ejemplo, la creación del archivo PDF de este apunte.
Hemos hablado antes de que los programas son como recetas de cocina, pero los
Makefiles
son literalmente las recetas para hornear nuestro programa. Contiene
las instrucciones para transformar el código en un programa. Esta herramienta
viene con un agregado para acelerar la cocción, el trabajo únicamente será hecho
en aquellos archivos que tengan cambios exclusivamente.
Esta herramienta tiene muchos años ya (1979) y fue creada para que la construcción de programas complejos no demore mucho tiempo. En los ejemplos y trabajos de la cátedra, el proceso es instantáneo, pero pensando en proyectos grandes, como el kernel de Linux, el proceso de compilación demorar tanto que se lo utiliza como medida dela capacidad de cómputo[1] de una computadora.
¿Hay alternativas a make
?¶
No solo existen alternativas directas, SCons, CMake, Bazel y Ninja, por nombrar los más populares, algunos entornos de desarrollo como Microsoft Visual Studio tienen sus propias herramientas de compilación. Por otro lado, para el lenguaje de programación Java están las herramientas Ant, Maven y Gradle. Y otros lenguajes como Go y Rust tienen sus propias herramientas de compilación.
Los lenguajes interpretados como Python, Ruby y Javascript no requieren un
análogo a Makefiles
, cuando sus archivos de programa cambian, no es necesario
compilar nada, simplemente se ejecuta directamente la versión más reciente del
archivo (podemos decir que la tarea de make
ya está integrada en el lenguaje).
Las versiones y tipos de make
¶
Hay una variedad de implementaciones de make
, en especial si tenemos en cuenta
que tiene 43 años de vida. Este apunte funcionará en cualquier versión que estés
usando, ya que apuntaremos al mínimo común denominador de funcionalidad. Sin
embargo, está escrita específicamente para
GNU make
, que es la estándar en Linux,
MacOS y las herramientas que utilizaremos en Windows. Todos los ejemplos
funcionan para las versiones 3 y 4 de make
, que son casi equivalentes salvo
algunas diferencias esotéricas que no vamos a ver.
Instalación¶
No es necesario instalar específicamente make
, este ya viene como parte del
kit de herramientas del compilador, por lo que refiéranse a la guía de
instalación especifica antes de seguir con este apunte.
Pero pueden verificar que está todo en orden ejecutando.
$> make
Debieran ver...
`make`: *** No targets specified and no makefile found. Stop.
Que indica que la herramienta está lista para ser configurada.
Ejecución de los ejemplos¶
Para cada ejemplo, pon el contenido en un archivo llamado Makefile, y en ese
directorio ejecuta el comando make
. Comencemos con el “Hola Mundo” de los
Makefiles
:
hola:
echo "hola mundo"
Aquí está la salida de la ejecución del ejemplo anterior:
$> make
echo "hola mundo"
hola mundo
Este es un ejemplo simple, pero como ves, es simplemente una forma de llamar a otros programas, en donde veremos como salida, el comando ejecutado y luego la salida del mismo.
Sintaxis del Makefile¶
Un Makefile consiste en un conjunto de reglas. Una regla generalmente tiene el siguiente aspecto:
objetivo: prerequisitos
comando
comando
comando
Los objetivos es el nombre del archivo generado por los comandos en la regla. Aunque, lo más común es que cada regla contenga un único archivo como objetivo, es posible que sea una lista, la cual estará separada por espacios.
Los comandos son la serie de programas que se utilizan para construir el objetivo. Deben comenzar con un carácter de tabulación
\t
, no con espacios.Los prerrequisitos son también nombres de archivos, separados por espacios. Estos son los archivos que deben existir antes que el objetivo, estos son los archivos de los cuales depende el objetivo, por lo que también se los denominan dependencias.
Nos podemos imaginar que el objetivo es cualquier paso intermedio y final en una receta de cocina, los comandos como las operaciones individuales en la receta y los prerrequisitos como todo aquello que sea posible hacer por separado, del que dependa alguno de los pasos. Por ejemplo, cocinar unas galletitas para hornear.
bandeja_galletitas_crudas: bandeja_en_mantecada masa_preparada
hacer_galletitas bandeja masa --en=bandeja_galletitas_crudas
bandeja_galletitas: bandeja_galletitas_crudas
hornear 20min 250C bandeja_galletitas_crudas
De este ejemplo podemos extraer un detalle interesante, ¿en qué se diferencia una receta de este tipo de una en la que todos los pasos son una secuencia completa?
Esta herramienta simplifica la forma de dividir las tareas, dedicándose a la coordinación en la construcción de las piezas y su ensamblado final.
Pero al igual que la compilación de un programa, si es de un solo archivo, o un
solo paso; mucho sentido no tiene armar un Makefile si podemos llamar
directamente la herramienta que hace el trabajo, como venimos utilizando gcc
hasta ahora, concretamente.
Un ejemplo simple¶
El siguiente Makefile tiene tres reglas separadas. Cuando ejecute make blah
en la terminal, construirá un programa llamado blah
en una serie de pasos:
A
make
se le dablah
como objetivo, por lo que primero busca este objetivoblah
requiereblah.o
, así quemake
busca el objetivoblah.o
blah.o
requiereblah.c
, así quemake
busca el objetivoblah.c
blah.c
no tiene dependencias, así que se ejecuta el comandoecho
Entonces se ejecuta el comando
gcc -c
, porque todas las dependencias deblah.o
están listasSe ejecuta el comando
gcc
de arriba, porque todas las dependencias deblah
están listasEso es todo:
blah
es un programa C compilado
blah: blah.o
cc blah.o -o blah # Tercero
blah.o: blah.c
cc -c blah.c -o blah.o # Segundo
blah.c:
echo "int main() { return 0; }" > blah.c # Primero
Objetivo ‘implícito’¶
El siguiente makefile
tiene un único objetivo, llamado some_file
. El
objetivo por defecto es el primer objetivo, por lo que en este caso some_file
se ejecutará.
some_file:
echo "Esta línea siempre imprime"
Como make
no “ve” el archivo some_file
, el objetivo no es alcanzado y make
seguirá ejecutando sus comandos.
¿Qué pasa cuando lo ejecutamos dos veces?¶
En el siguiente ejemplo, al ejecutar make some_file
por segunda vez, veremos
algo como make: 'some_file' is up to date.
. Esto pasa porque some_file
es
creado dentro del objetivo, y como se llega al objetivo, no es necesario volver
a modificarlo.
Esta es una de las características más importantes de la herramienta, si la “dependencia” no cambió, entonces, no es necesario volver a completar el paso.
Dependencias¶
Aquí, el objetivo some_file
“depende” de other_file
. Cuando ejecutamos
make
, el objetivo por defecto (some_file
, ya que es el primero) será
llamado. Primero mirará su lista de dependencias, y si alguna de ellas es más
antigua, primero ejecutará los objetivos de esas dependencias, y luego se
ejecutará a sí mismo. La segunda vez que se ejecute, ningún objetivo se
ejecutará porque ambos objetivos existen.
some_file: other_file
echo "Esto va después, porque depende de other_file"
touch some_file
other_file:
echo "Esto va primero"
touch other_file
La noción de dependencia es importante, en el siguiente Makefile, ambos
objetivos se ejecutarán, ya que la dependencia de some_file
con respecto de
other_file
nunca es satisfecha, ya que el archivo no es creado.
some_file: other_file
touch some_file
other_file:
echo "Tipo nada"
Empezando de cero con clean
¶
El objetivo clean
es uno muy común y se utiliza para limpiar las salidas de
los objetivos, esto es usado para “empezar de cero”. Este objetivo no lleva un
nombre especial o reservado, y lo podríamos llamar “limpiar”, pero es mejor
apegarnos al nombre en inglés para mantener consistencia.
some_file:
touch some_file
clean:
rm -f some_file`
Variables¶
Las variables solo pueden contener texto. Lo más común es utilizar :=
, pero
=
también funciona.
Hay más sobre este tema por acá [variables parte 2](#7.Variables parte 2|outline).
Acá tenés un ejemplo con variables:
files := file1 file2
some_file: $(files)
echo "Mirá a esta variable!: " $(files)
touch some_file
file1:
touch file1
file2:
touch file2
clean:
rm -f file1 file2 some_file
Podes referenciar las variables con ${}
o $()
x := coso
all:
echo $(x)
echo ${x}
Demás está decir que es muy recomendable usar nombres razonables para las variables
Objetivos¶
El objetivo all
¶
¿Estás construyendo múltiples objetivos sin dependencias entre sí y deseas ejecutarlos todos?
Es muy recomendable agregar al principio a este objetivo. Ya que de esta forma, será el llamado si no indicamos uno específico y de esta forma podemos compilar todo sin tener que indicarlo puntualmente cada vez.
all: one two three
one:
touch one
two:
touch two
three:
touch three
clean:
rm -f one two three
Objetivos múltiples¶
Cuando hay varios objetivos para una regla, los comandos se ejecutarán para cada
objetivo, para saber cuál se está procesando, podemos utilizar la variable
automática $@
que contiene el nombre del objetivo.
all: f1.o f2.o
f1.o f2.o:
echo "ahora con" $@
# Equivalente a:
# f1.o:
# echo f1.o
# f2.o:
# echo f2.o
Es particularmente útil cuando tenemos que hacer lo mismo en varios archivos diferentes, pero no es la única forma.
Variables automáticas y comodines¶
Comodín *
(wildcard)¶
Tanto *
como %
se llaman comodines en make
, pero significan cosas
totalmente diferentes. *
busca en su sistema de archivos nombres de archivos
que coincidan. Es muy recomendable que siempre esté envuelto en la función
wildcard
, porque de lo contrario podés caer en una trampa común que se
describe un poco más abajo.
# Imprime información de cada archivo .c
print: $(wildcard *.c)
ls -la $?
El comodín *
puede ser utilizado en el objetivo, prerrequisitos o en la
función wildcard
.
Pero, no es posible usarlo directamente en la creación de variables, y cuando no
hay coincidencias, es dejado como está, a no ser que sea utilizado dentro de la
función wildcard
.
thing_wrong := *.o # No lo hagas. '*' no se expandirá
thing_right := $(wildcard *.o)
all: one two three four
# Falla, porque $(thing_wrong) es la cadena "*.o"
one: $(thing_wrong)
# Se queda como *.o si no hay archivos que coincidan con este patrón :(
two: *.o
# Funciona como cabría esperar. En este caso, no hace nada.
three: $(thing_right)
# Igual que la regla three
four: $(wildcard *.o)
Trampa con el comodín
El comodín %
¶
El otro comodín, es realmente útil, pero es algo confuso debido a la variedad de situaciones en las que se puede utilizar.
Cuando se utiliza en modo “coincidente”, coincide con uno o más caracteres de una cadena. Esta coincidencia se denomina tronco[2].
Cuando se usa en modo “reemplazo”, toma la raíz que se ha comparado y la reemplaza en una cadena.
%
se utiliza sobre todo en las definiciones de reglas y en algunas funciones específicas.
Consulte estas secciones para ver ejemplos de su uso:
Variables automáticas¶
Hay más variabless automáticas, pero estas son las más comunes:
hey: one two
# Da como resultado "hey", ya que este es el primer objetivo
echo $@
# Muestra a todos los prerrequisitos más nuevos que el objetivo
echo $?
# Muestra a todos los prerrequisitos
echo $^
touch hey
one:
touch one
two:
touch two
clean:
rm -f hey one two
Fancy Rules¶
Reglas implícitas¶
A make
le encanta la compilación de C. Y cada vez que expresa su amor, las
cosas se vuelven confusas, y quizás lo más enrevesado de la herramienta son las
reglas mágicas/automáticas.
make
llama a estas reglas “implícitas”. Personalmente no estoy de acuerdo con
esta decisión de diseño, y no recomiendo usarlas, pero se usan seguido y por lo
tanto, es importante conocerlas. Aquí hay una lista de reglas implícitas:
Compilación de un programa C:
n.o
que es construido a partir den.c
con un comando que sigue la forma:$(CC) -c $(CPPFLAGS) $(CFLAGS)
Compilación de un programa C++:
n.o
es construido a partir den.cc
on.cpp
con un comando que sigue la forma:$(CXX) -c $(CPPFLAGS) $(CXXFLAGS)
Enlace de un archivo objeto único: el archivo
n
es construido automáticamente a partir den.o
ejecutando el comando$(CC) $(LDFLAGS) n.o $(LDLIBS)
Las variables importantes que utilizan las reglas implícitas son:
CC
: Compilador de C, por defectocc
CXX
: Compilador para C++, por defectog++
CFLAGS
: Opciones adicionales para el compilador de CCXXFLAGS
: Opciones adicionales para el compilador de C++CPPFLAGS
: Opciones adicionales para el preprocesador de CLDFLAGS
: Opciones adicionales para el enlazador.
Veamos cómo podemos ahora construir un programa en C sin tener que decirle
explícitamente a make
cómo hacer la compilación:
CC = gcc # Utilizamos gcc como compilador
CFLAGS = -g # Activar la información de depuración
# #1: blah se construye a través de la regla implícita del enlazador de C
# #2: blah.o se construye a través de la regla implícita de compilación de C,
# porque blah.c existe
blah: blah.o
blah.c:
echo "int main() { return 0; }" > blah.c
clean:
rm -f blah*
Patrones de reglas estáticas¶
Las reglas de patrones estáticos son otra forma de escribir menos en un Makefile, pero yo diría que son más útiles y un poco menos “mágicas”. Esta es su sintaxis:
objetivos...: patron-objetivo: patrones-prereq ...
comandos
La esencia es que el objetivo
dado coincide con el patrón-objetivo
(a través
de un comodín %
). Lo que se ha encontrado se denomina “tronco”, este se
sustituye por patrones-prereq
, para generar los prerrequisitos del objetivo.
Un caso de uso típico es compilar archivos .c
en archivos .o
. Esta es la
forma manual:
objects = foo.o bar.o all.o
all: $(objects)
# Estos archivos se compilan mediante reglas implícitas
foo.o: foo.c
bar.o: bar.c
all.o: all.c
all.c:
echo "int main() { return 0; }" > all.c
%.c:
touch $@
clean:
rm -f *.c *.o all
Esta es la forma más eficiente, utilizando una regla de patrón estático:
objects = foo.o bar.o all.o
all: $(objects)
# Estos archivos son compilados por las reglas implicitas
# Sintaxis - targets ...: target-patterns: prereq-patterns ...
# En el caso del primer objetivo, foo.o, el patrón-objetivo coincide
# con foo.o y establece que la "raíz" sea "foo".
# A continuación, sustituye el "%" de patrones-prereq por esa raíz
$(objects): %.o: %.c
all.c:
echo "int main() { return 0; }" > all.c
%.c:
touch $@
clean:
rm -f *.c *.o all
Reglas y filtro de patrones estáticos¶
Si bien las funciones son introducidas más adelante, adelantaré lo que se puede
hacer con ellas. La función filter
se puede utilizar en las reglas de patrones
estáticos para hacer coincidir los archivos correctos. En este ejemplo, hice las
extensiones .raw
y .result
.
obj_files = foo.result bar.o lose.o
src_files = foo.raw bar.c lose.c
.PHONY: all
all: $(obj_files)
$(filter %.o,$(obj_files)): %.o: %.c
echo "target: $@ prereq: $<"
$(filter %.result,$(obj_files)): %.result: %.raw
echo "target: $@ prereq: $<"
%.c %.raw:
touch $@
clean:
rm -f $(src_files)
Reglas de los patrones¶
Las reglas de los patrones se utilizan a menudo, pero son bastante confusas. Puedes verlas de dos maneras:
Una forma de definir sus propias reglas implícitas
Una forma más simple de reglas de patrones estáticos
Empecemos con un ejemplo:
# Definir una regla de patrón que compile cada archivo .c en un archivo .o
%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
Las reglas de patrones contienen un ‘%
’ en el objetivo. Este ‘%
’ coincide
con cualquier cadena no vacía, y los demás caracteres coinciden por sí mismos.
El ‘%
’ en un prerrequisito de una regla de patrón representa el mismo tronco
que fue igualado por el ‘%
’ en el objetivo.
Aquí hay otro ejemplo:
# Definir una regla de patrón que no tenga ningún
# patrón en los prerrequisitos.
# Esto sólo crea archivos .c vacíos cuando es necesario.
%.c:
touch $@
Reglas de dos puntos¶
Las Reglas de dos puntos se utilizan raramente, pero permiten definir múltiples reglas para el mismo objetivo. Si fueran dos puntos simples, se imprimiría una advertencia y solo se ejecutaría el segundo conjunto de comandos.
all: blah
blah::
echo "Hola!"
blah::
echo "Hola denuevo!"
Comandos y ejecución¶
Eco de comandos/Silencio¶
Añade una @
antes de un comando para evitar que se imprima. También puedes
ejecutar make
con -s
para añadir una @
antes de cada línea
all:
@echo "Esto no va a aparecer en la salida"
echo "Pero eso si!"
Ejecución de comandos¶
Cada comando se ejecuta en un nuevo shell (o al menos el efecto es como tal)
all:
cd ..
# El cd anterior no afecta a esta línea,
porque cada comando se ejecuta efectivamente en un nuevo shell
echo `pwd`
# Este comando cd afecta al siguiente porque están en la misma línea
cd ..;echo `pwd`
# Igual que antes, pero con la barra para dividir la linea en dos
cd ..; \
echo `pwd`
Shell por defecto¶
El shell por defecto es /bin/sh
. Es posible cambiar esto modificando la
variable SHELL
:
SHELL=/bin/bash
cool:
echo "Hola desde bash"
Gestión de errores con -k
, -i
, y -
¶
Añadí -k
cuando ejecutes make
para que siga funcionando incluso ante
errores. Útil si querés ver todos los errores de make
a la vez.
(--keep-going
)
Agregá un -
antes de un comando para suprimir el error
Y con -i
al llamar a make
para que esto pase con cada comando.
(--ignore-errors
)
one:
# Este error se imprimirá pero se ignorará, make continuará ejecutándose
-false
touch one
Uso recursivo de make
¶
Para llamar recursivamente a un makefile
, usá el especial $(make)
en lugar
de make
, ya que pasará las opciones de la llamada original de make
por usted
y no se verá afectado por ellas.
new_contents = "hello:\n\ttouch a inside_file"
all:
mkdir -p subdir
printf $(new_contents) | sed -e 's/^ //' > subdir/makefile
cd subdir && $(MAKE)
clean:
rm -rf subdir
Usar export para un make
recursivo¶
La directiva export
toma una variable y la hace accesible a los comandos
sub-make
. En este ejemplo, cooly se exporta de tal manera que el makefile
en
el subdirectorio puede usarlo.
Nota: export tiene la misma sintaxis que en la terminal de la consola (sh
,
bash
), pero no están relacionados (aunque son similares en su función)
new_contents = "hello:\n\\techo \$$(cooly)"
all:
mkdir -p subdir
echo $(new_contents) | sed -e 's/^ //' > subdir/makefile
@echo "---MAKEFILE CONTENTS---"
@cd subdir && cat makefile
@echo "---END MAKEFILE CONTENTS---"
cd subdir && $(MAKE)
# Tenga en cuenta que las variables y las exportaciones.
# Se establecen/afectan globalmente.
cooly = "¡El subdirectorio puede verme!"
export cooly
# Esto anularía la línea anterior: unexport cooly
clean:
rm -rf subdir
Es necesario exportar las variables para que se ejecuten también en el shell.
one=esto sólo funcionará localmente
export two=podemos ejecutar subcomandos con esto
all:
@echo $(one)
@echo $$one
@echo $(two)
@echo $$two
Mientras que .EXPORT_ALL_VARIABLES
lo hace por tí, para todas las variables.
.EXPORT_ALL_VARIABLES:
new_contents = "hello:\n\techo \$$(cooly)"
cooly = "¡El subdirectorio puede verme!"
# Esto anularía la línea anterior: unexport cooly
all:
mkdir -p subdir
echo $(new_contents) | sed -e 's/^ //' > subdir/makefile
@echo "---MAKEFILE CONTENTS---"
@cd subdir && cat makefile
@echo "---END MAKEFILE CONTENTS---"
cd subdir && $(MAKE)
clean:
rm -rf subdir
Argumentos a make
¶
De la muy bonita lista en el manual
lista de opciones,
peguenlé una mirada a --dry-run
, --touch
y --old-file
. Además de ver en
detalle que hace -i
y -k
.
El otro tipo de argumentos son los objetivos a ejecutar, y podemos ejecutar varios y en orden, solo tenemos que indicarlos uno a continuación de otro.
Por ejemplo:
$> make clean run test
Que limpia los archivos generados previamente (clean
), compila y ejecuta el
programa (run
) y finalmente corre los test
’s.
Variables parte 2¶
Sabores y modificaciones¶
Hay dos sabores de variables
Recursivo (usá
=
) - sólo construye el valor de la variable cuando se utiliza el comando, no cuando se define.Expansión simple (usa
:=
) - como en programación normal, las variables toman su valor cuando son definidas, sin expansión
# Variable recursiva. Esto imprimirá "tardio" a continuación
uno = uno ${later_variable}
# Variable simplemente expandida. Esto no imprimirá "tardio" abajo
dos := dos ${later_variable}
later_variable = tardio
all:
echo $(uno)
echo $(dos)
La expansión simple (utilizando :=
) permite añadir a una variable. Las
definiciones recursivas darán un error de bucle infinito.
uno = hola
uno ?= no será asignado
dos ?= será asignado
all:
echo $(uno)
echo $(dos)
Los espacios al final de una línea no se eliminan, pero sí los del principio.
Para crear una variable con un solo espacio, utilice $(nullstring)
con_espacios = alo # con_espacios tiene 3 espacios luego de "alo"
after = $(con_espacios)there
nullstring =
space = $(nullstring) # Hace una variable con un solo espacio.
all:
echo "$(after)"
echo inicio"$(space)"fin
Una variable indefinida es en realidad una cadena vacía, por lo que emplear una variable desconocida no producirá un error.
all:
# Las variables no definidas son sólo cadenas vacías!
echo $(nowhere)
Y podes usar +=
para concatenar.
foo := start
foo += more
all:
echo $(foo)
La substitución de cadenas es una forma muy útil para modificar el contenido de
las variables. Para más información, consulten las páginas del manual
Text Functions
y
Filename Functions
de GNU/make
.
Argumentos de linea de comandos y anulaciones¶
Puedes anular las variables que provienen de la línea de comandos utilizando
override
. Aquí ejecutamos make
con make option_one=hi
# Supera los argumentos de la línea de comandos
override option_one = did_override
# No anula los argumentos de la línea de comandos
option_two = not_override
all:
echo $(option_one)
echo $(option_two)
Puesto de otra manera, no importa lo que venga de afuera, el valor siempre va a ser el que este definido internamente.
Lista de comandos y definición¶
“define
” es en realidad sólo una lista de comandos. No tiene nada que ver con
ser una función. Tené en cuenta aquí que es un poco diferente a tener un punto y
coma entre los comandos, porque cada uno se ejecuta en un shell separado, como
se espera.
one = export blah="Estaba definida!"; echo $$blah
define two
export blah=set
echo $$blah
endef
# One y two son diferentes.
all:
@echo "Esto muestra 'Estaba definida!'"
@$(one)
@echo "Esto no muestra 'Estaba definida!', ya que cada comando se ejecuta en un shell separado."
@$(two)
Variables específicas de los objetivos¶
Las variables pueden ser asignadas para objetivos específicos:
all: uno = copado
all:
echo uno esta definida: $(uno)
other:
echo uno esta definida: $(uno)
Variables específicas a patrones¶
Así como podemos definir variables para objetivos específicos, podemos también asignarlas a patrones.
%.c: one = buenardo
blah.c:
echo uno esta definida: $(one)
other:
echo uno esta definida: $(one)
La parte condicional de los Makefiles
¶
Un if
/else
clásico¶
foo = ok
all:
ifeq ($(foo), ok)
echo "foo vale ok"
else
echo "nope"
endif
[]{#anchor-13}Verificar si una variable esta vacía¶
nullstring =
foo = $(nullstring) # fin de linea; noten que hay un espacio acá
all:
ifeq ($(strip $(foo)),)
echo "foo está vacío después de ser limpiada"
endif
ifeq ($(nullstring),)
echo "nullstring ni siquiera tiene espacios"
endif
Verificar si una variable está definida¶
ifdef
no expande las referencias a variables, solo se encarga de verificar que
existen.
bar =
foo = $(bar)
all:
ifdef foo
echo "foo esta definida"
endif
ifndef bar
echo "pero bar no lo esta"
endif
$(makeflags)
¶
Este ejemplo muestra como verificar opciones (flags) con findstring
y
MAKEFLAGS
. Estos son útiles para indicar alguna variación en lo que se desea
construir.
Para el siguiente ejemplo, utilizá -i
para ver el texto en el echo
.
bar =
foo = $(bar)
all:
# Buscando por la opción "-i". MAKEFLAGS es solo una lista de caracteres
# Y en estes caso, busca por "i"
ifneq (,$(findstring i, $(MAKEFLAGS)))
echo "la opcion i fue agregada a MAKEFLAGS"
endif
Funciones¶
Primeras funciones¶
Las funciones son principalmente para procesamiento de texto, después de todo, la idea de esta herramienta es llamar a otras. Estas se llaman con la siguiente sintaxis:
$(fn, argumentos) o ${fn, argumentos}.
Podes hacer make
propio utilizando
call
para llamar funciones ‘de libreria’. Y make
tiene una cantidad interesante de
funciones integradas.
Substituciones textuales (subst
)¶
bar := ${subst no, absolutamentoe, "Yo no soy Paturuzu"}
all:
@echo $(bar)
Si querés reemplazar espacio o comas, usá variables:
comma := ,
empty:=
space := $(empty) $(empty)
foo := a b c
bar := $(subst $(space),$(comma),$(foo))
all:
@echo $(bar)
No incluyas espacios en los argumentos antes del primero, esto será visto como parte de la cadena.
comma := ,
empty:=
space := $(empty) $(empty)
foo := a b c
bar := $(subst $(space), $(comma) , $(foo))
all:
# La salida es is ", a , b , c". Mirá los espacios que se introdujeron
@echo $(bar)
Substituciones usando patrones (patsubst
)¶
$(patsubst patron,reemplazo,texto)
does the following:
Según el manual:
"Busca palabras separadas por espacios en blanco en el texto que coinciden con el patrón y las reemplaza con el ‘reemplazo’ indicado. Aquí, el patrón puede contener un “
%
”, que actúa como comodón y coincide con cualquier número de caracteres dentro de una palabra. Si el reemplazo también contiene un “%
”, este se reemplaza por el texto que coincidió con el “%
” en el patrón. Solo el primero “%
” en el patrón y el reemplazo se trata de esta manera; cualquier “%
” posterior no se modifica.
(GNU docs)
La referencia de substitución $(texto:patron=reemplazo)
es un atajo para esto.
Existe otra abreviación que solo reemplaza sufijos, $(texto:sufijo=reemplazo)
.
No es necesario el comodín “%
”.
A julio del 2022, el primer puesto lo tiene una computadora doble AMD EPYC 7773X de 64 núcleos, para hacer un total de ¡128 núcleos! Completando la tarea en 138 segundos. Una computadora más mundana, suele llevar varias horas.
NdT: stem
- Feldman, S. I. (1979). Make — a program for maintaining computer programs. Software: Practice and Experience, 9(4), 255–265. 10.1002/spe.4380090402