Abandonner le makefile si la variable n'est pas définie

151

Comment pourrais-je abandonner une exécution make / makefile basée sur la variable d'un makefile qui n'est pas définie / valorisée?

Je suis venu avec cela, mais ne fonctionne que si l'appelant n'exécute pas explicitement une cible (c'est-à-dire makene fonctionne que).

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)

Je pense que quelque chose comme ça serait une bonne idée, mais je n'ai rien trouvé dans le manuel de make:

ifndef MY_FLAG
.ABORT
endif
Caruccio
la source
3
duplication possible de la variable Makefile comme prérequis
Keith Smiley

Réponses:

271

TL; DR : utilisez la errorfonction :

ifndef MY_FLAG
$(error MY_FLAG is not set)
endif

Notez que les lignes ne doivent pas être indentées. Plus précisément, aucun onglet ne doit précéder ces lignes.


Solution générique

Dans le cas où vous allez tester de nombreuses variables, il vaut la peine de définir une fonction auxiliaire pour cela:

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

Et voici comment l'utiliser:

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


Cela produirait une erreur comme celle-ci:

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

Remarques:

Le vrai contrôle se fait ici:

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

Cela reflète le comportement du ifndefconditionnel, de sorte qu'une variable définie à une valeur vide est également considérée comme "non définie". Mais cela n'est vrai que pour les variables simples et les variables récursives explicitement vides:

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

Comme suggéré par @VictorSergienko dans les commentaires, un comportement légèrement différent peut être souhaité:

$(if $(value $1)teste si la valeur n'est pas vide. C'est parfois OK si la variable est définie avec une valeur vide . J'utiliserais$(if $(filter undefined,$(origin $1)) ...

Et:

De plus, si c'est un répertoire et qu'il doit exister lorsque la vérification est exécutée, j'utiliserais $(if $(wildcard $1)). Mais ce serait une autre fonction.

Vérification spécifique à la cible

Il est également possible d'étendre la solution afin que l'on puisse exiger une variable uniquement si une certaine cible est invoquée.

$(call check_defined, ...) de l'intérieur de la recette

Déplacez simplement le chèque dans la recette:

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

Le @signe principal désactive l'écho de commande et :est la commande réelle, un stub no-op du shell .

Affichage du nom de la cible

La check_definedfonction peut être améliorée pour afficher également le nom de la cible (fourni via 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 `$@')))

Donc, maintenant, une vérification échouée produit une sortie bien formatée:

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

check-defined-MY_FLAG cible spéciale

Personnellement, j'utiliserais la solution simple et directe ci-dessus. Cependant, par exemple, cette réponse suggère d'utiliser une cible spéciale pour effectuer le contrôle réel. On pourrait essayer de généraliser cela et de définir la cible comme une règle de modèle implicite:

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

Usage:

foo :|check-defined-BAR

Notez que le check-defined-BARest répertorié comme condition préalable à la commande uniquement ( |...).

Avantages:

  • (sans doute) une syntaxe plus propre

Les inconvénients:

Je crois que ces limitations peuvent être surmontées en utilisant des hacks d' expansioneval magiques et secondaires , même si je ne suis pas sûr que cela en vaille la peine.

Eldar Abusalimov
la source
Quoi exactement? Jamais utilisé Mac, même si je suppose qu'il a une autre implémentation de Make installée par défaut (par exemple BSD Make au lieu de GNU Make). Je vous suggère de vérifier make --versiondans un premier temps.
Eldar Abusalimov
1
Cela ne semble pas fonctionner dans la version 3.81. C'est toujours des erreurs, même si la variable est définie (et peut être renvoyée).
OrangeDog le
Ah, vous devez le structurer exactement comme dans le duplicata lié.
OrangeDog
1
@bibstha J'ai ajouté les options qui vous viennent à l'esprit, veuillez lire la réponse mise à jour.
Eldar Abusalimov
2
J'ajouterais une clarification-pour-noobies (comme moi) que ifndef ne doit pas être indenté :) J'ai trouvé cette astuce ailleurs et soudainement toutes mes erreurs ont eu un sens.
helios
40

Utilisez la fonction shell test:

foo:
    test $(something)

Usage:

$ make foo
test 
Makefile:2: recipe for target 'foo' failed
make: *** [foo] Error 1
$ make foo something=x
test x
Messa
la source
3
C'est ce que j'ai utilisé face au même problème - merci Messa! J'ai fait deux légères modifications: 1) j'ai créé une checkforsomethingcible qui ne contenait que le testet qui en foodépendait, et 2) j'ai changé le chèque à la @if test -z "$(something)"; then echo "helpful error here"; exit 1; fiplace. Cela m'a donné la possibilité d'ajouter une erreur utile et m'a permis de rendre le nom de la nouvelle cible un peu plus indicatif de ce qui n'allait pas.
Brian Gerard
Avec ceci je reçoisMakefile:5: *** missing separator. Stop.
silgon
7
Pour la compacité, test -n "$(something) || (echo "message" ; exit 1)j'évitais l'explicite if.
user295691
@silgon vous indentez probablement en utilisant des espaces plutôt qu'une tabulation.
eweb
9

Vous pouvez utiliser un IF pour tester:

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

Résultat:

$ make check
>> var is not set
Makefile:2: recipe for target 'check' failed
make: *** [check] Error 1
raittes
la source
[est un alias pour la commande test, c'est donc la même réponse que @Messa ci-dessus. Ceci est cependant plus compact et inclut la génération de message d'erreur.
user295691
6

Utilisez la gestion des erreurs du shell pour les variables non définies (notez le double $):

$ 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 vous avez besoin d'un message d'erreur personnalisé, ajoutez-le après le ?:

$ 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
la source
2

Pour plus de simplicité et de brièveté:

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

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

Avec sorties:

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