Definir hacer variable en tiempo de ejecución de regla

209

En mi GNUmakefile, me gustaría tener una regla que use un directorio temporal. Por ejemplo:

out.tar: TMP := $(shell mktemp -d)
        echo hi $(TMP)/hi.txt
        tar -C $(TMP) cf $@ .
        rm -rf $(TMP)

Tal como está escrito, la regla anterior crea el directorio temporal en el momento en que se analiza la regla . Esto significa que, incluso si no lo hago.tar todo el tiempo, se crean muchos directorios temporales. Me gustaría evitar que mi / tmp esté lleno de directorios temporales no utilizados.

¿Hay alguna manera de hacer que la variable solo se defina cuando se activa la regla, en lugar de cuando se define?

Mi pensamiento principal es volcar el mktemp y el alquitrán en un script de shell, pero eso parece algo desagradable.

Emil Sit
fuente

Respuestas:

322

En su ejemplo, la TMPvariable se establece (y se crea el directorio temporal) cada vez queout.tar se evalúan las reglas para . Para crear el directorio solo cuando out.tarrealmente se dispara, debe mover la creación del directorio hacia abajo en los pasos:

out.tar : 
    $(eval TMP := $(shell mktemp -d))
    @echo hi $(TMP)/hi.txt
    tar -C $(TMP) cf $@ .
    rm -rf $(TMP)

La función eval evalúa una cadena como si se hubiera escrito en el archivo MAKE manualmente. En este caso, establece la TMPvariable en el resultado de la shellllamada a la función.

editar (en respuesta a los comentarios):

Para crear una variable única, puede hacer lo siguiente:

out.tar : 
    $(eval $@_TMP := $(shell mktemp -d))
    @echo hi $($@_TMP)/hi.txt
    tar -C $($@_TMP) cf $@ .
    rm -rf $($@_TMP)

Esto antepondría el nombre del objetivo (out.tar, en este caso) a la variable, produciendo una variable con el nombre out.tar_TMP. Con suerte, eso es suficiente para evitar conflictos.

e.James
fuente
2
Refresque algunas aclaraciones ... esto no abarcará TMP a este objetivo, ¿verdad? Entonces, si hay otras reglas que tienen su propio uso de $ (TMP) (posiblemente en paralelo con -j), ¿puede haber conflictos? Además, ¿es incluso necesario el @echo? Parece que podrías dejarlo afuera.
Emil Sit
3
Parece hacer el truco (aunque un poco opaco para el gurú promedio que no es Make :-) ¡Gracias!
Emil Sit
29
¡Ten cuidado con esta solución! $(eval $@_TMP := $(shell mktemp -d))ocurrirá cuando el Makefile se evalúe por primera vez, no en el orden de los procedimientos de la regla. En otras palabras, $(eval ...)ocurre antes de lo que piensas. Si bien eso podría estar bien para este ejemplo, este método causará problemas para algunas operaciones secuenciales.
JamesThomasMoon1979
1
@ JamesThomasMoon1979, ¿sabe cómo superar esas limitaciones, por ejemplo, evaluar algunas variables que deberían ser el resultado de los pasos ejecutados anteriormente en la regla?
Vadim Kotov
2
Respondiendo a mi propia pregunta: la solución para mí era crear archivos durante la ejecución de la primera regla e intentar evaluarlos en el primer paso de la segunda regla.
Vadim Kotov
63

Una forma relativamente fácil de hacer esto es escribir toda la secuencia como un script de shell.

out.tar:
   set -e ;\
   TMP=$$(mktemp -d) ;\
   echo hi $$TMP/hi.txt ;\
   tar -C $$TMP cf $@ . ;\
   rm -rf $$TMP ;\

He consolidado algunos consejos relacionados aquí: https://stackoverflow.com/a/29085684/86967

sin bar
fuente
3
Esta es sin duda la respuesta más simple y por lo tanto mejor (evitando @e evaly haciendo el mismo trabajo). Tenga en cuenta que en su salida puede ver $TMP(por ejemplo tar -C $TMP ...), aunque el valor se pasa correctamente al comando.
Karl Richter
Así es como se hace normalmente; Espero que la gente vea esta respuesta porque en la práctica nadie pasa por todo el esfuerzo del aceptado.
pmos
¿Qué pasa si está utilizando comandos específicos de shell? En ese caso, los comandos de shell deben estar en el mismo lenguaje de shell que el que hace make, ¿verdad? Había visto algunos archivos improvisados ​​con shebang.
ptitpion
@ptitpion: es posible que también desee SHELL := /bin/bashen su archivo MAKE para habilitar funciones específicas de BASH.
nobar
31

Otra posibilidad es utilizar líneas separadas para configurar Crear variables cuando se activa una regla.

Por ejemplo, aquí hay un archivo MAKE con dos reglas. Si se activa una regla, crea un directorio temporal y establece TMP con el nombre del directorio temporal.

PHONY = ruleA ruleB display

all: ruleA

ruleA: TMP = $(shell mktemp -d testruleA_XXXX)
ruleA: display

ruleB: TMP = $(shell mktemp -d testruleB_XXXX)
ruleB: display

display:
    echo ${TMP}

Ejecutar el código produce el resultado esperado:

$ ls
Makefile
$ make ruleB
echo testruleB_Y4Ow
testruleB_Y4Ow
$ ls
Makefile  testruleB_Y4Ow
usuario3124434
fuente
Solo como recordatorio, GNU make tiene una sintaxis para los requisitos previos de solo pedido que pueden ser necesarios para complementar este enfoque.
anol
11
¡Ten cuidado con esta solución! ruleA: TMP = $(shell mktemp -d testruleA_XXXX)y ruleB: TMP = $(shell mktemp -d testruleB_XXXX)sucederá cuando el Makefile se evalúe por primera vez. En otras palabras, ruleA: TMP = $(shell ...ocurre antes de lo que piensas. Si bien esto podría funcionar para este caso particular, este método causará problemas para algunas operaciones secuenciales.
JamesThomasMoon1979
1

No me gustan las respuestas de "No", pero ... no.

makeLas variables son globales y se supone que deben evaluarse durante la etapa de "análisis" del archivo MAKE, no durante la etapa de ejecución.

En este caso, siempre que la variable local a un solo objetivo, siga la respuesta de @ nobar y conviértala en una variable de shell.

Las variables específicas de destino también se consideran dañinas por otras implementaciones de marca: kati , Mozilla pymake . Debido a ellos, un objetivo se puede construir de manera diferente dependiendo de si se construye de manera independiente o como una dependencia de un objetivo principal con una variable específica del objetivo. Y no sabrá de qué manera fue , porque no sabe lo que ya está construido.

Victor Sergienko
fuente