Makefile variable como requisito previo

134

En un Makefile, una deployreceta necesita ENVque se configure una variable de entorno para ejecutarse correctamente, mientras que a otros no les importa, por ejemplo:

ENV = 

.PHONY: deploy hello

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

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

¿Cómo puedo asegurarme de que esta variable esté configurada?

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

?

Gracias.

abernier
fuente
¿Qué quiere decir con "asegúrese de que esta variable esté configurada"? ¿Quieres decir verificar o asegurar? Si no se configuró antes, ¿debería makeconfigurarlo, dar una advertencia o generar un error fatal?
Beta
1
El usuario mismo debe especificar esta variable, ya que es el único que conoce su entorno (dev, prod ...), por ejemplo, llamando, make ENV=devpero si se olvida ENV=dev, la deployreceta fallará ...
abernier

Respuestas:

171

Esto causará un error fatal si ENV no está definido y algo lo necesita (en GNUMake, de todos modos).

.PHONY: despliegue check-env

desplegar: check-env
	...

otra cosa que necesita env: check-env
	...

check-env:
ifndef ENV
	$ (error ENV no está definido)
terminara si

(Tenga en cuenta que si ifndef y endif no están sangrados, controlan lo que hace "ve" , surten efecto antes de que se ejecute el Makefile. "$ (Error" está sangrado con una pestaña para que solo se ejecute en el contexto de la regla).

Beta
fuente
12
Obtengo ENV is undefinedcuando ejecuto una tarea que no tiene check-env como requisito previo.
lluvia
@rane: Eso es interesante. ¿Puedes dar un ejemplo completo mínimo?
Beta
2
@rane es la diferencia en espacios frente a un carácter de tabulación?
esmit
8
@esmit: sí; Debería haber respondido sobre esto. En mi solución, la línea comienza con una TAB, por lo que es un comando en la check-envregla; Make no lo expandirá a menos que / hasta que ejecute la regla. Si no comienza con una TAB (como en el ejemplo de @ rane), Make lo interpreta como si no estuviera en una regla y lo evalúa antes de ejecutar cualquier regla, independientemente del objetivo.
Beta
1
`` En mi solución, la línea comienza con una TAB, por lo que es un comando en la regla check-env; `` ¿De qué línea estás hablando? En mi caso, la condición if se evalúa cada vez, incluso cuando la línea después de ifndef comienza con TAB
Dhawal
103

Puede crear un objetivo de protección implícito, que verifique que la variable en la raíz esté definida, así:

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

Luego agrega un guard-ENVVARobjetivo en cualquier lugar donde desee afirmar que una variable está definida, como esta:

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

Si llama make change-hostname, sin agregar HOSTNAME=somehostnamela llamada, obtendrá un error y la compilación fallará.

Clayton Stanley
fuente
55
Esa es una solución inteligente, me gusta :)
Elliot Chance
Sé que esta es una respuesta antigua, pero tal vez alguien todavía la esté viendo; de lo contrario, podría volver a publicar esto como una nueva pregunta ... Estoy tratando de implementar este objetivo "protector" implícito para verificar las variables de entorno establecidas y funciona en principio, sin embargo, los comandos en la regla "guard-%" se imprimen realmente en el shell. Esto me gustaría suprimir. ¿Cómo es esto posible?
genomicsio
2
OKAY. encontrado la solución a mí mismo ... @ al comienzo de las líneas de comandos de regla es mi amigo ...
genomicsio
44
One-liner:: if [ -z '${${*}}' ]; then echo 'Environment variable $* not set' && exit 1; fiD
c24w
44
Esta debería ser la respuesta seleccionada. Es una implementación más limpia.
sb32134
46

Variante en línea

En mis archivos MAKE, normalmente uso una expresión como:

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

Las razones:

  • es una simple línea
  • es compacto
  • se encuentra cerca de los comandos que usan la variable

No olvide el comentario que es importante para la depuración:

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

... te obliga a buscar el Makefile mientras ...

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

... explica directamente lo que está mal

Variante global (para completar, pero no solicitada)

Además de tu Makefile, también puedes escribir:

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

Advertencias:

  • no use la pestaña en ese bloque
  • usar con cuidado: incluso el cleanobjetivo fallará si no se establece ENV. De lo contrario, vea la respuesta de Hudon, que es más compleja
Daniel Alder
fuente
Guau. Estaba teniendo problemas con esto hasta que vi "no use la pestaña en ese bloque". ¡Gracias!
Alex K
Es una buena alternativa, pero no me gusta que el "mensaje de error" aparezca aunque sea exitoso (toda la línea está impresa)
Jeff
@Jeff Eso es lo básico del makefile. Simplemente prefija la línea con a @. -> gnu.org/software/make/manual/make.html#Echoing
Daniel Alder
Lo intenté, pero luego el mensaje de error no aparecerá en caso de falla. Hmm lo intentaré de nuevo. Votó su respuesta con seguridad.
Jeff
1
Me gusta el enfoque de prueba. Solía algo como esto:@test -n "$(name)" || (echo 'A name must be defined for the backup. Ex: make backup name=xyz' && exit 1)
swampfox357
6

Un posible problema con las respuestas dadas hasta ahora es que el orden de dependencia en make no está definido. Por ejemplo, ejecutando:

make -j target

cuando targettiene algunas dependencias no garantiza que se ejecutarán en un orden determinado.

La solución para esto (para garantizar que se verifique el ENV antes de elegir las recetas) es verificar el ENV durante la primera pasada, fuera de cualquier receta:

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

Puede leer sobre las diferentes funciones / variables utilizadas aquí y $()es solo una forma de declarar explícitamente que estamos comparando con "nada".

Hudon
fuente
6

He encontrado que la mejor respuesta no se puede usar como un requisito, a excepción de otros objetivos PHONY. Si se usa como una dependencia para un objetivo que es un archivo real, el uso check-envforzará la reconstrucción del objetivo del archivo.

Otras respuestas son globales (por ejemplo, la variable se requiere para todos los objetivos en el Makefile) o usan el shell, por ejemplo, si faltaba ENV, make terminaría independientemente del objetivo.

Una solución que encontré para ambos problemas es

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

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

.PHONY: build
build:
    echo "building"

La salida se ve como

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

value tiene algunas advertencias de miedo, pero para este uso simple, creo que es la mejor opción.

23jodys
fuente
5

Como veo, el comando en sí necesita la variable ENV para que pueda verificarlo en el comando en sí:

.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
fuente
El problema con esto es que deployno es necesariamente la única receta que necesita esta variable. Con esta solución, tengo que probar el estado de ENVcada uno ... mientras me gustaría tratarlo como un prerrequisito único (más o menos).
abernier
4

Sé que esto es viejo, pero pensé en compartir con mis propias experiencias para futuros visitantes, ya que es un poco más ordenado en mi humilde opinión.

Normalmente, makese usará shcomo su shell predeterminado ( establecido a través de la SHELLvariable especial ). En shy sus derivados, es trivial salir con un mensaje de error al recuperar una variable de entorno si no se establece o es nulo al hacer:${VAR?Variable VAR was not set or null} .

Extendiendo esto, podemos escribir un objetivo de fabricación reutilizable que se puede utilizar para fallar otros objetivos si no se estableció una variable de entorno:

.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!"

Cosas de nota:

  • Se $$requiere el signo de dólar escapado ( ) para diferir la expansión al shell en lugar de dentro demake
  • El uso de testes solo para evitar que el shell intente ejecutar el contenido de VAR(no sirve para ningún otro propósito significativo)
  • .check-env-varsse puede extender trivialmente para buscar más variables de entorno, cada una de las cuales agrega solo una línea (por ejemplo @test $${NEWENV?Please set environment variable NEWENV})
Lewis Belcher
fuente
Si ENVcontiene espacios, esto parece fallar (al menos para mí)
eddiegroves
2

Puede usar en ifdeflugar de un objetivo 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
fuente
Hola, gracias por tu respuesta, pero no puedo aceptarla debido al código duplicado que genera tu sugerencia ... lo más importante deployno es la única receta que tiene que verificar la ENVvariable de estado.
abernier
entonces solo refactorizar. Use las declaraciones .PHONY: deployy deploy:antes del bloque ifdef y elimine la duplicación. (por cierto, he editado la respuesta para reflejar el método correcto)
Dwight Spencer