Interromper makefile se a variável não estiver definida

151

Como posso interromper uma execução de make / makefile com base na variável de makefile que não está sendo definida / avaliada?

Eu vim com isso, mas funciona apenas se o chamador não executar explicitamente um destino (ou seja, executar makeapenas).

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)

Acho que algo assim seria uma boa ideia, mas não encontrei nada no manual do make:

ifndef MY_FLAG
.ABORT
endif
Caruccio
fonte
3
possível duplicata de Makefile variável como pré-requisito
Keith Smiley

Respostas:

271

TL; DR : use a errorfunção :

ifndef MY_FLAG
$(error MY_FLAG is not set)
endif

Observe que as linhas não devem ser recuadas. Mais precisamente, nenhuma guia deve preceder essas linhas.


Solução genérica

Caso você teste várias variáveis, vale a pena definir uma função auxiliar para isso:

# 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))))

E aqui está como usá-lo:

$(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)


Isso geraria um erro como este:

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

Notas:

A verificação real é feita aqui:

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

Isso reflete o comportamento da ifndefcondicional, de modo que uma variável definida com um valor vazio também é considerada "indefinida". Mas isso só é verdade para variáveis ​​simples e variáveis ​​recursivas explicitamente vazias:

# 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)

Conforme sugerido por @VictorSergienko nos comentários, um comportamento ligeiramente diferente pode ser desejado:

$(if $(value $1)testa se o valor não está vazio. Às vezes, tudo bem se a variável for definida com um valor vazio . Eu usaria$(if $(filter undefined,$(origin $1)) ...

E:

Além disso, se é um diretório e deve existir quando a verificação é executada, eu usaria $(if $(wildcard $1)). Mas seria outra função.

Verificação específica do alvo

Também é possível estender a solução para que se possa exigir uma variável apenas se um determinado destino for invocado.

$(call check_defined, ...) de dentro da receita

Basta mover o cheque para a receita:

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

O @sinal principal desativa o eco do comando e :é o comando real, um esboço de no-op do shell .

Mostrando o nome do destino

A check_definedfunção pode ser aprimorada para também gerar o nome do destino (fornecido através da $@variável):

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 `$@')))

Portanto, agora uma verificação com falha produz uma saída bem formatada:

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

check-defined-MY_FLAG alvo especial

Pessoalmente, eu usaria a solução simples e direta acima. No entanto, por exemplo, esta resposta sugere o uso de um destino especial para executar a verificação real. Pode-se tentar generalizar isso e definir o destino como uma regra padrão implícita:

# 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 ele check-defined-BARestá listado como pré-requisito somente para pedido ( |...).

Prós:

  • (sem dúvida) uma sintaxe mais limpa

Contras:

Eu acredito que essas limitações podem ser superadas usando um pouco de evalmagia e truques de expansão secundários , embora não tenha certeza de que vale a pena.

Eldar Abusalimov
fonte
O que exatamente? Nunca usei o Mac, embora eu ache que tenha outra implementação do Make instalada por padrão (por exemplo, o BSD Make em vez do GNU Make). Eu sugiro que você verifique make --versioncomo o primeiro passo.
Eldar Abusalimov 6/09/15
1
Isso não parece estar funcionando no make 3.81. Sempre erros, mesmo que a variável esteja definida (e possa ser repetida).
OrangeDog
Ah, você precisa estruturá-lo exatamente como na duplicata vinculada.
OrangeDog
1
@bibstha Adicionei as opções que vêm em mente, leia a resposta atualizada.
Eldar Abusalimov
2
Eu acrescentaria um esclarecimento para noobies (como eu) de que o ifndef não precisa ser recuado :) Encontrei essa dica em outro lugar e, de repente, todos os meus erros fizeram sentido.
Helios
40

Use a função 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
Messa
fonte
3
Foi isso que eu usei ao enfrentar o mesmo problema - obrigado, Messa! Fiz duas pequenas modificações: 1) criei um checkforsomethingdestino que tinha apenas o testconteúdo e foodependente disso; e 2) mudei a verificação para @if test -z "$(something)"; then echo "helpful error here"; exit 1; fi. Isso me deu a capacidade de adicionar um erro útil e me permitiu tornar o nome do novo destino um pouco mais indicativo do que também deu errado.
22817 Brian Gerard
Com isso eu receboMakefile:5: *** missing separator. Stop.
silgon
7
Para compacidade, eu costumava test -n "$(something) || (echo "message" ; exit 1)evitar o explícito if.
user295691
@ silg você provavelmente está recuando usando espaços em vez de uma guia.
EWEB
9

Você pode usar um IF para testar:

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
raittes
fonte
[é um apelido para o comando test, portanto, esta é a mesma resposta que o @Messa acima. Porém, isso é mais compacto e inclui a geração de mensagens de erro.
user295691
6

Use o tratamento de erros do shell para variáveis ​​não definidas (observe o dobro $):

$ 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

Se você precisar de uma mensagem de erro personalizada, adicione-a após o ?:

$ 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
kesselborn
fonte
2

Por simplicidade e brevidade:

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

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

Com saídas:

$ make bar
Makefile:2: *** foo is undefined. Stop.
$ make bar foo="something"
echo "foo is $$foo"
foo is something
bsimpson53
fonte