Makefile variable comme prérequis

134

Dans un Makefile, une deployrecette a besoin d'une variable d'environnement ENVà définir pour s'exécuter correctement, alors que d'autres s'en moquent, par exemple:

ENV = 

.PHONY: deploy hello

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

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

Comment puis-je m'assurer que cette variable est définie, par exemple: existe-t-il un moyen de déclarer cette variable makefile comme prérequis de la recette de déploiement, comme:

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

?

Je vous remercie.

abernier
la source
Que voulez-vous dire, "assurez-vous que cette variable est définie"? Voulez-vous dire vérifier ou assurer? S'il n'a pas été défini auparavant, doit- makeil le définir, ou donner un avertissement ou générer une erreur fatale?
Bêta du
1
Cette variable doit être précisée par l'utilisateur lui-même - car il est le seul à connaître son environnement (dev, prod ...) - par exemple en appelant make ENV=devmais s'il l'oublie ENV=dev, la deployrecette échouera ...
abernier

Réponses:

171

Cela causera une erreur fatale si elle ENVn'est pas définie et que quelque chose en a besoin (dans GNUMake, de toute façon).

.PHONY: déployer check-env

déployer: check-env
	...

autre-chose-qui-a-besoin-env: check-env
	...

check-env:
ifndef ENV
	$ (l'erreur ENV n'est pas définie)
fin si

(Notez que ifndef et endif ne sont pas indentés - ils contrôlent ce que make "voit" , prenant effet avant l'exécution du Makefile. "$ (Error" est indenté avec une tabulation de sorte qu'il ne s'exécute que dans le contexte de la règle.)

Bêta
la source
12
J'obtiens ENV is undefinedlors de l'exécution d'une tâche qui n'a pas check-env comme prérequis.
raine
@rane: C'est intéressant. Pouvez-vous donner un exemple complet minimal?
Bêta
2
@rane est la différence entre les espaces et un caractère de tabulation?
esmettre
8
@esmit: Oui; J'aurais dû répondre à ce sujet. Dans ma solution, la ligne commence par une tabulation, donc c'est une commande dans la check-envrègle; Make ne le développera pas tant que / jusqu'à l'exécution de la règle. S'il ne commence pas par un TAB (comme dans l'exemple de @ rane), Make l'interprète comme n'étant pas dans une règle, et l'évalue avant d'exécuter une règle, quelle que soit la cible.
Bêta du
1
`` Dans ma solution, la ligne commence par une TAB, donc c'est une commande dans la règle check-env; `` De quelle ligne parlez-vous? Dans mon cas, la condition if est évaluée à chaque fois, même lorsque la ligne après ifndef commence par TAB
Dhawal
103

Vous pouvez créer une cible de garde implicite, qui vérifie que la variable dans le radical est définie, comme ceci:

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

Vous ajoutez ensuite une guard-ENVVARcible partout où vous souhaitez affirmer qu'une variable est définie, comme ceci:

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

Si vous appelez make change-hostname, sans ajouter HOSTNAME=somehostnamel'appel, vous obtiendrez une erreur et la construction échouera.

Clayton Stanley
la source
5
C'est une solution intelligente, j'aime ça :)
Elliot Chance
Je sais que c'est une réponse ancienne, mais peut-être que quelqu'un la regarde encore, sinon je pourrais la re-publier comme une nouvelle question ... J'essaie d'implémenter cette cible implicite "garde" pour vérifier les variables d'environnement définies et cela fonctionne en principe, cependant, les commandes de la règle "guard-%" sont en fait imprimées sur le shell. Je voudrais supprimer cela. Comment est-ce possible?
genomicsio
2
D'ACCORD. j'ai trouvé la solution moi-même ... @ au début des lignes de commande de la règle est mon ami ...
genomicsio
4
One-liner:: if [ -z '${${*}}' ]; then echo 'Environment variable $* not set' && exit 1; fiD
c24w
4
cela devrait être la réponse choisie. c'est une mise en œuvre plus propre.
sb32134
46

Variante en ligne

Dans mes makefiles, j'utilise normalement une expression comme:

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

Les raisons:

  • c'est un simple one-liner
  • c'est compact
  • il est situé à proximité des commandes qui utilisent la variable

N'oubliez pas le commentaire qui est important pour le débogage:

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

... vous oblige à rechercher le Makefile pendant que ...

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

... explique directement ce qui ne va pas

Variante globale (par souci d'exhaustivité, mais non demandée)

En plus de votre Makefile, vous pouvez également écrire:

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

Avertissements:

  • n'utilisez pas l'onglet dans ce bloc
  • à utiliser avec précaution: même la cleancible échouera si ENV n'est pas défini. Sinon, voyez la réponse de Hudon qui est plus complexe
Daniel Alder
la source
Sensationnel. J'avais des problèmes avec cela jusqu'à ce que je vois «ne pas utiliser l'onglet dans ce bloc». Je vous remercie!
Alex K
C'est une bonne alternative, mais je n'aime pas que le "message d'erreur" apparaisse même en cas de succès (toute la ligne est imprimée)
Jeff
@Jeff Ce sont les bases de makefile. Préfixez simplement la ligne avec un @. -> gnu.org/software/make/manual/make.html#Echoing
Daniel Alder
J'ai essayé cela, mais le message d'erreur n'apparaîtra pas en cas d'échec. Hmm je vais essayer à nouveau. A voté pour votre réponse à coup sûr.
Jeff
1
J'aime l'approche du test. J'ai utilisé quelque chose comme ceci:@test -n "$(name)" || (echo 'A name must be defined for the backup. Ex: make backup name=xyz' && exit 1)
swampfox357
6

Un problème possible avec les réponses données jusqu'à présent est que l'ordre des dépendances dans make n'est pas défini. Par exemple, exécuter:

make -j target

quand targeta quelques dépendances ne garantit pas que celles-ci s'exécuteront dans un ordre donné.

La solution pour cela (pour garantir que ENV sera vérifié avant que les recettes ne soient choisies) est de vérifier ENV lors du premier passage de make, en dehors de toute recette:

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

Vous pouvez lire sur les différentes fonctions / variables utilisées ici et $()c'est juste un moyen de déclarer explicitement que nous comparons avec "rien".

Hudon
la source
6

J'ai trouvé que la meilleure réponse ne peut pas être utilisée comme une exigence, sauf pour les autres cibles PHONY. S'il est utilisé comme dépendance pour une cible qui est un fichier réel, l'utilisation check-envforcera cette cible de fichier à être reconstruite.

D'autres réponses sont globales (par exemple, la variable est requise pour toutes les cibles dans le Makefile) ou utilisent le shell, par exemple si ENV manquait, make se terminerait indépendamment de la cible.

Une solution que j'ai trouvée aux deux problèmes est

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

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

.PHONY: build
build:
    echo "building"

La sortie ressemble à

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

value a quelques mises en garde effrayantes, mais pour cette utilisation simple, je pense que c'est le meilleur choix.

23jodys
la source
5

Comme je le vois, la commande elle-même a besoin de la variable ENV afin que vous puissiez la vérifier dans la commande elle-même:

.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
la source
Le problème avec ceci est que ce deployn'est pas nécessairement la seule recette qui a besoin de cette variable. Avec cette solution, je dois tester l'état de ENVpour chacun d'entre eux ... alors que j'aurais aimé le traiter comme une seule (sorte de) condition préalable.
abernier
4

Je sais que c'est vieux, mais j'ai pensé que je ferais écho à mes propres expériences pour les futurs visiteurs, car c'est un peu plus soigné à mon humble avis.

En règle générale, makeutilisera shcomme shell par défaut ( défini via la SHELLvariable spéciale ). Dans shet ses dérivés, il est trivial de quitter avec un message d'erreur lors de la récupération d'une variable d'environnement si elle n'est pas définie ou nulle en faisant:${VAR?Variable VAR was not set or null} .

En prolongeant cela, nous pouvons écrire une cible de création réutilisable qui peut être utilisée pour faire échouer d'autres cibles si une variable d'environnement n'a pas été définie:

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

Choses à noter:

  • Le signe dollar échappé ($$ ) est nécessaire pour reporter l'expansion vers le shell au lieu de l'intérieurmake
  • L'utilisation de testest juste pour empêcher le shell d'essayer d'exécuter le contenu deVAR (cela ne sert à rien d'autre)
  • .check-env-varspeut être étendu de manière triviale pour rechercher plus de variables d'environnement, chacune n'ajoutant qu'une seule ligne (par exemple @test $${NEWENV?Please set environment variable NEWENV})
Lewis Belcher
la source
Si ENVcontient des espaces, cela semble échouer (pour moi au moins)
eddiegroves
2

Vous pouvez utiliser à la ifdefplace d'une cible différente.

.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
la source
Hé, merci pour votre réponse mais je ne peux pas l'accepter à cause du code dupliqué que votre suggestion génère ... d'autant plus deployn'est pas la seule recette à vérifier la ENVvariable d'état.
abernier
puis juste refactor. Utilisez les instructions .PHONY: deployet deploy:avant le bloc ifdef et supprimez la duplication. (btw j'ai édité la réponse pour refléter la bonne méthode)
Dwight Spencer