Abortar el archivo MAKE si la variable no está configurada


151

¿Cómo podría abortar una ejecución de make / makefile basada en una variable de makefile que no se establece / valora?

Se me ocurrió esto, pero funciona solo si la persona que llama no ejecuta explícitamente un objetivo (es decir, makesolo se ejecuta ).

ifeq ($(MY_FLAG),)
abort:   ## This MUST be the first target :( ugly
    @echo Variable MY_FLAG not set && false
endif

all:
    @echo MY_FLAG=$(MY_FLAG)

Creo que algo como esto sería una buena idea, pero no encontré nada en el manual de make:

ifndef MY_FLAG
.ABORT
endif

Respuestas:


271

TL; DR : Use la errorfunción :

ifndef MY_FLAG
$(error MY_FLAG is not set)
endif

Tenga en cuenta que las líneas no deben sangrarse. Más precisamente, ninguna pestaña debe preceder a estas líneas.


Solución genérica

En caso de que vaya a probar muchas variables, vale la pena definir una función auxiliar para eso:

# Check that given variables are set and all have non-empty values,
# die with an error otherwise.
#
# Params:
#   1. Variable name(s) to test.
#   2. (optional) Error message to print.
check_defined = \
    $(strip $(foreach 1,$1, \
        $(call __check_defined,$1,$(strip $(value 2)))))
__check_defined = \
    $(if $(value $1),, \
      $(error Undefined $1$(if $2, ($2))))

Y aquí está cómo usarlo:

$(call check_defined, MY_FLAG)

$(call check_defined, OUT_DIR, build directory)
$(call check_defined, BIN_DIR, where to put binary artifacts)
$(call check_defined, \
            LIB_INCLUDE_DIR \
            LIB_SOURCE_DIR, \
        library path)


Esto generaría un error como este:

Makefile:17: *** Undefined OUT_DIR (build directory).  Stop.

Notas:

La verificación real se realiza aquí:

$(if $(value $1),,$(error ...))

Esto refleja el comportamiento del ifndefcondicional, de modo que una variable definida a un valor vacío también se considera "indefinida". Pero esto solo es cierto para variables simples y variables recursivas explícitamente vacías:

# ifndef and check_defined consider these UNDEFINED:
explicitly_empty =
simple_empty := $(explicitly_empty)

# ifndef and check_defined consider it OK (defined):
recursive_empty = $(explicitly_empty)

Como lo sugiere @VictorSergienko en los comentarios, puede desearse un comportamiento ligeramente diferente:

$(if $(value $1)prueba si el valor no está vacío. A veces está bien si la variable se define con un valor vacío . Yo usaría$(if $(filter undefined,$(origin $1)) ...

Y:

Además, si es un directorio y debe existir cuando se ejecuta la verificación, lo usaría $(if $(wildcard $1)). Pero sería otra función.

Verificación específica del objetivo

También es posible extender la solución para que se pueda requerir una variable solo si se invoca un determinado objetivo.

$(call check_defined, ...) desde dentro de la receta

Simplemente mueva el cheque a la receta:

foo :
    @:$(call check_defined, BAR, baz value)

El @signo inicial desactiva el comando que hace eco y :es el comando real, un trozo de shell no operativo .

Mostrando el nombre del objetivo

La check_definedfunción se puede mejorar para generar también el nombre de destino (proporcionado a través de la $@variable):

check_defined = \
    $(strip $(foreach 1,$1, \
        $(call __check_defined,$1,$(strip $(value 2)))))
__check_defined = \
    $(if $(value $1),, \
        $(error Undefined $1$(if $2, ($2))$(if $(value @), \
                required by target `$@')))

De modo que, ahora una comprobación fallida produce una salida con un formato agradable:

Makefile:7: *** Undefined BAR (baz value) required by target `foo'.  Stop.

check-defined-MY_FLAG objetivo especial

Personalmente, usaría la solución simple y directa anterior. Sin embargo, por ejemplo, esta respuesta sugiere usar un objetivo especial para realizar la verificación real. Se podría tratar de generalizar eso y definir el objetivo como una regla de patrón implícito:

# Check that a variable specified through the stem is defined and has
# a non-empty value, die with an error otherwise.
#
#   %: The name of the variable to test.
#   
check-defined-% : __check_defined_FORCE
    @:$(call check_defined, $*, target-specific)

# Since pattern rules can't be listed as prerequisites of .PHONY,
# we use the old-school and hackish FORCE workaround.
# You could go without this, but otherwise a check can be missed
# in case a file named like `check-defined-...` exists in the root 
# directory, e.g. left by an accidental `make -t` invocation.
.PHONY : __check_defined_FORCE
__check_defined_FORCE :

Uso:

foo :|check-defined-BAR

Observe que check-defined-BARse enumera como el requisito previo de solo pedido ( |...).

Pros:

  • (posiblemente) una sintaxis más limpia

Contras:

  • No se puede especificar un mensaje de error personalizado
  • La ejecución make -t(consulte En lugar de ejecutar recetas ) contaminará su directorio raíz con muchos check-defined-...archivos. Este es un triste inconveniente del hecho de que las reglas de patrón no se pueden declarar.PHONY .

Creo que estas limitaciones pueden superarse usando algunos hacks de expansióneval mágicos y secundarios , aunque no estoy seguro de que valga la pena.


¿Qué exactamente? Nunca usé Mac, aunque supongo que tiene otra implementación de Make instalada por defecto (por ejemplo, BSD Make en lugar de GNU Make). Te sugiero que verifiques make --versioncomo el primer paso.
Eldar Abusalimov

1
Esto no parece estar funcionando en make 3.81. Siempre tiene errores, incluso si la variable está definida (y puede repetirse).
OrangeDog

Ah, necesita estructurarlo exactamente como en el duplicado vinculado.
OrangeDog

1
@bibstha Agregué las opciones que vienen en mente, por favor lea la respuesta actualizada.
Eldar Abusalimov

2
Agregaría una aclaración para novatos (como yo) que ifndef no debe sangrarse :) Encontré ese consejo en otro lugar y de repente todos mis errores tenían sentido.
helios

40

Use la función de shell test:

foo:
    test $(something)

Uso:

$ make foo
test 
Makefile:2: recipe for target 'foo' failed
make: *** [foo] Error 1
$ make foo something=x
test x

3
Esto es lo que usé cuando me enfrenté al mismo problema: ¡gracias, Messa! Hice dos modificaciones leves: 1) Creé un checkforsomethingobjetivo que solo tenía el contenido testy foodependí de eso, y 2) cambié el cheque a @if test -z "$(something)"; then echo "helpful error here"; exit 1; ficambio. Eso me dio la posibilidad de agregar un error útil y me permitió hacer que el nombre del nuevo objetivo sea un poco más indicativo de lo que salió mal también.
Brian Gerard

Con esto consigoMakefile:5: *** missing separator. Stop.
silgon

77
Por compacidad, solía test -n "$(something) || (echo "message" ; exit 1)evitar lo explícito if.
user295691

@silgon probablemente esté usando sangría en lugar de una pestaña.
eweb

9

Puede usar un IF para probar:

check:
        @[ "${var}" ] || ( echo ">> var is not set"; exit 1 )

Resultado:

$ make check
>> var is not set
Makefile:2: recipe for target 'check' failed
make: *** [check] Error 1

[es un alias para el comando test, por lo que esta es la misma respuesta que @Messa anterior. Sin embargo, esto es más compacto e incluye la generación de mensajes de error.
user295691

6

Utilice el manejo de errores de shell para variables no establecidas (tenga en cuenta el doble $):

$ cat Makefile
foo:
        echo "something is set to $${something:?}"

$ make foo
echo "something is set to ${something:?}"
/bin/sh: something: parameter null or not set
make: *** [foo] Error 127


$ make foo something=x
echo "something is set to ${something:?}"
something is set to x

Si necesita un mensaje de error personalizado, agréguelo después de ?:

$ cat Makefile
hello:
        echo "hello $${name:?please tell me who you are via \$$name}"

$ make hello
echo "hello ${name:?please tell me who you are via \$name}"
/bin/sh: name: please tell me who you are via $name
make: *** [hello] Error 127

$ make hello name=jesus
echo "hello ${name:?please tell me who you are via \$name}"
hello jesus

2

Por simplicidad y brevedad:

$ cat Makefile
check-%:
        @: $(if $(value $*),,$(error $* is undefined))

bar:| check-foo
        echo "foo is $$foo"

Con salidas:

$ make bar
Makefile:2: *** foo is undefined. Stop.
$ make bar foo="something"
echo "foo is $$foo"
foo is something
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.