Variável Makefile como pré-requisito

134

Em um Makefile, uma deployreceita precisa que uma variável de ambiente ENVseja definida para se executar adequadamente, enquanto outras não se importam, por exemplo:

ENV = 

.PHONY: deploy hello

deploy:
    rsync . $(ENV).example.com:/var/www/myapp/

hello:
    echo "I don't care about ENV, just saying hello!"

Como posso garantir que essa variável seja definida, por exemplo: existe uma maneira de declarar essa variável de makefile como um pré-requisito da receita de implantação, como:

deploy: make-sure-ENV-variable-is-set

?

Obrigado.

abernier
fonte
O que você quer dizer com "verifique se essa variável está definida"? Você quer dizer verificar ou garantir? Se não foi definido antes, deve makeconfigurá-lo ou dar um aviso ou gerar um erro fatal?
Beta
1
Essa variável deve ser especificada pelo próprio usuário - como ele é o único que conhece seu ambiente (dev, prod ...) - por exemplo, ligando, make ENV=devmas se ele esquecer ENV=dev, a deployreceita falhará ...
abernier

Respostas:

171

Isso causará um erro fatal se não ENVfor definido e algo precisar (no GNUMake, de qualquer maneira).

.PHONY: implantar check-env

deploy: check-env
	...

outra coisa que precisa env: check-env
	...

check-env:
ifndef ENV
	$ (o erro ENV é indefinido)
fim se

(Observe que ifndef e endif não são recuados - eles controlam o que "vê" , entrando em vigor antes da execução do Makefile. "$ (Error" é recuado com uma guia para que seja executado apenas no contexto da regra.)

Beta
fonte
12
Estou recebendo ENV is undefinedao executar uma tarefa que não tem o check-env como pré-requisito.
Raine
@rane: Isso é interessante. Você pode dar um exemplo completo mínimo?
Beta
2
@rane é a diferença de espaços versus um caractere de tabulação?
esmit
8
@mit: Sim; Eu deveria ter respondido sobre isso. Na minha solução, a linha começa com um TAB, por isso é um comando na check-envregra; O Make não o expandirá, a menos que / até executar a regra. Se não começar com um TAB (como no exemplo de @ rane), o Make interpreta como não estando em uma regra e o avalia antes de executar qualquer regra, independentemente do destino.
Beta
1
`` Na minha solução, a linha começa com um TAB, por isso é um comando na regra check-env; `` `De qual linha você está falando? No meu caso, a condição if é avaliada todas as vezes, mesmo quando a linha após o ifndef começa com TAB
Dhawal 16/01/16
103

Você pode criar um destino de guarda implícito, que verifica se a variável no tronco está definida, assim:

guard-%:
    @ if [ "${${*}}" = "" ]; then \
        echo "Environment variable $* not set"; \
        exit 1; \
    fi

Em seguida, você adiciona um guard-ENVVARdestino em qualquer lugar que desejar afirmar que uma variável está definida, assim:

change-hostname: guard-HOSTNAME
        ./changeHostname.sh ${HOSTNAME}

Se você ligar make change-hostname, sem adicionar HOSTNAME=somehostnamea chamada, você receberá um erro e a compilação falhará.

Clayton Stanley
fonte
5
Essa é uma solução inteligente, eu gosto :)
Elliot Possibilidade
Eu sei que essa é uma resposta antiga, mas talvez alguém ainda esteja assistindo, caso contrário, eu posso postar novamente como uma nova pergunta ... Estou tentando implementar esse destino implícito "guarda" para verificar se há variáveis ​​de ambiente definidas e ele funciona em princípio, no entanto, os comandos na regra "% guarda" são realmente impressos no shell. Eu gostaria de suprimir isso. Como isso é possível?
21414
2
ESTÁ BEM. encontrou a solução me ... @ no início das linhas de comando regra é meu amigo ...
genomicsio
4
Uma linha:: if [ -z '${${*}}' ]; then echo 'Environment variable $* not set' && exit 1; fiD
c24w
4
essa deve ser a resposta selecionada. é uma implementação mais limpa.
precisa saber é o seguinte
46

Variante em linha

Nos meus makefiles, eu normalmente uso uma expressão como:

deploy:
    test -n "$(ENV)"  # $$ENV
    rsync . $(ENV).example.com:/var/www/myapp/

As razões:

  • é uma linha simples
  • é compacto
  • está localizado próximo aos comandos que usam a variável

Não se esqueça do comentário que é importante para a depuração:

test -n ""
Makefile:3: recipe for target 'deploy' failed
make: *** [deploy] Error 1

... força você a procurar o Makefile enquanto ...

test -n ""  # $ENV
Makefile:3: recipe for target 'deploy' failed
make: *** [deploy] Error 1

... explica diretamente o que há de errado

Variante global (por completo, mas não solicitada)

Além do Makefile, você também pode escrever:

ifeq ($(ENV),)
  $(error ENV is not set)
endif

Advertências:

  • não use tab nesse bloco
  • use com cuidado: até o cleandestino falhará se ENV não estiver definido. Caso contrário, veja a resposta de Hudon, que é mais complexa
Daniel Alder
fonte
Uau. Eu estava tendo problemas com isso até ver "não use a guia nesse bloco". Obrigado!
Alex K
É uma boa alternativa, mas eu não gosto que a "mensagem de erro" aparece mesmo se bem-sucedida (toda a linha é impresso)
Jeff
@ Jeff Isso é básico no makefile. Apenas prefixe a linha com a @. -> gnu.org/software/make/manual/make.html#Echoing
Daniel Alder
Eu tentei isso, mas a mensagem de erro não aparecerá em caso de falha. Hmm, vou tentar novamente. Promovido sua resposta com certeza.
Jeff
1
Eu gosto da abordagem de teste. Eu usei algo parecido com isto:@test -n "$(name)" || (echo 'A name must be defined for the backup. Ex: make backup name=xyz' && exit 1)
swampfox357
6

Um possível problema com as respostas dadas até agora é que a ordem de dependência no make não está definida. Por exemplo, executando:

make -j target

Quando targetpossui algumas dependências, não garante que elas sejam executadas em qualquer ordem.

A solução para isso (para garantir que o ENV será verificado antes da escolha das receitas) é verificar o ENV durante o primeiro passe do make, fora de qualquer receita:

## Are any of the user's goals dependent on ENV?
ifneq ($(filter deploy other-thing-that-needs-ENV,$(MAKECMDGOALS)),$())
ifndef ENV 
$(error ENV not defined)
endif
endif

.PHONY: deploy

deploy: foo bar
    ...

other-thing-that-needs-ENV: bar baz bono
    ...

Você pode ler sobre as diferentes funções / variáveis ​​usadas aqui e $()é apenas uma maneira de declarar explicitamente que estamos comparando "nada".

Hudon
fonte
6

Descobri que a melhor resposta não pode ser usada como requisito, exceto para outros destinos PHONY. Se usado como uma dependência para um destino que é um arquivo real, o uso check-envforçará a reconstrução desse destino.

Outras respostas são globais (por exemplo, a variável é necessária para todos os destinos no Makefile) ou usam o shell, por exemplo, se ENV estava ausente, o make terminaria independentemente do destino.

Uma solução que encontrei para ambos os problemas é

ndef = $(if $(value $(1)),,$(error $(1) not set))

.PHONY: deploy
deploy:
    $(call ndef,ENV)
    echo "deploying $(ENV)"

.PHONY: build
build:
    echo "building"

A saída parece

$ make build
echo "building"
building
$ make deploy
Makefile:5: *** ENV not set.  Stop.
$ make deploy ENV="env"
echo "deploying env"
deploying env
$

value tem algumas advertências assustadoras, mas para esse uso simples, acredito que seja a melhor escolha.

23jodys
fonte
5

Como eu vejo, o próprio comando precisa da variável ENV para que você possa verificá-lo no próprio comando:

.PHONY: deploy check-env

deploy: check-env
    rsync . $(ENV).example.com:/var/www/myapp/

check-env:
    if test "$(ENV)" = "" ; then \
        echo "ENV not set"; \
        exit 1; \
    fi
ssmir
fonte
O problema disso é que deploynão é necessariamente a única receita que precisa dessa variável. Com esta solução, tenho que testar o estado de ENVcada um deles ... enquanto gostaria de lidar com isso como um (pré-requisito) único.
abernier
4

Eu sei que isso é antigo, mas achei que iria concorrer com minhas próprias experiências para futuros visitantes, já que é um IMHO um pouco melhor.

Normalmente, makeusará shcomo seu shell padrão ( definido através da SHELLvariável especial ). Em she seus derivados, é trivial para sair com uma mensagem de erro ao recuperar uma variável de ambiente se não for definida ou nulo fazendo: ${VAR?Variable VAR was not set or null}.

Estendendo isso, podemos escrever um destino de reutilização que pode ser usado para falhar em outros destinos se uma variável de ambiente não foi configurada:

.check-env-vars:
    @test $${ENV?Please set environment variable ENV}


deploy: .check-env-vars
    rsync . $(ENV).example.com:/var/www/myapp/


hello:
    echo "I don't care about ENV, just saying hello!"

Coisas de nota:

  • O cifrão com escape ( $$) é necessário para adiar a expansão para o shell em vez de dentromake
  • O uso de testé apenas para impedir que o shell tente executar o conteúdo de VAR(ele não serve para nenhum outro propósito significativo)
  • .check-env-varspode ser estendido trivialmente para verificar se há mais variáveis ​​de ambiente, cada uma das quais adiciona apenas uma linha (por exemplo @test $${NEWENV?Please set environment variable NEWENV})
Lewis Belcher
fonte
Se ENVcontiver espaços, isso parece falhar (pelo menos para mim)
eddiegroves 16/04
2

Você pode usar em ifdefvez de um destino diferente.

.PHONY: deploy
deploy:
    ifdef ENV
        rsync . $(ENV).example.com:/var/www/myapp/
    else
        @echo 1>&2 "ENV must be set"
        false                            # Cause deploy to fail
    endif
Daniel Gallagher
fonte
Ei, obrigado pela sua resposta, mas não posso aceitá-la por causa do código duplicado que sua sugestão gera ... mais deployainda não é a única receita que precisa verificar a ENVvariável de estado.
abernier
basta refatorar. Use as instruções .PHONY: deploye deploy:antes do bloco ifdef e remova a duplicação. (btw eu editar tenho a resposta para refletir o método correto)
Dwight Spencer