Makefile abbrechen, wenn die Variable nicht gesetzt ist


150

Wie kann ich eine make / makefile-Ausführung abbrechen, wenn die Variable eines makefiles nicht festgelegt / bewertet wird?

Ich habe mir das ausgedacht, aber es funktioniert nur, wenn der Anrufer ein Ziel nicht explizit ausführt (dh makenur ausführt ).

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)

Ich denke, so etwas wäre eine gute Idee, habe aber nichts im Handbuch von make gefunden:

ifndef MY_FLAG
.ABORT
endif

Antworten:


270

TL; DR : Verwenden Sie die errorFunktion :

ifndef MY_FLAG
$(error MY_FLAG is not set)
endif

Beachten Sie, dass die Zeilen nicht eingerückt werden dürfen. Genauer gesagt dürfen vor diesen Zeilen keine Tabulatoren stehen.


Generische Lösung

Wenn Sie viele Variablen testen möchten, lohnt es sich, eine Hilfsfunktion dafür zu definieren:

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

Und so geht's:

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


Dies würde einen Fehler wie folgt ausgeben:

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

Anmerkungen:

Die eigentliche Prüfung erfolgt hier:

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

Dies spiegelt das Verhalten der ifndefBedingung wider , sodass eine für einen leeren Wert definierte Variable ebenfalls als "undefiniert" betrachtet wird. Dies gilt jedoch nur für einfache Variablen und explizit leere rekursive Variablen:

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

Wie von @VictorSergienko in den Kommentaren vorgeschlagen, kann ein etwas anderes Verhalten erwünscht sein:

$(if $(value $1)testet, ob der Wert nicht leer ist. Es ist manchmal in Ordnung, wenn die Variable mit einem leeren Wert definiert ist . Ich würde verwenden$(if $(filter undefined,$(origin $1)) ...

Und:

Außerdem , wenn es ein Verzeichnis ist und es muss vorhanden sein, wenn die Prüfung ausgeführt wird, würde ich verwenden $(if $(wildcard $1)). Wäre aber eine andere Funktion.

Zielspezifische Prüfung

Es ist auch möglich, die Lösung so zu erweitern, dass nur dann eine Variable erforderlich ist, wenn ein bestimmtes Ziel aufgerufen wird.

$(call check_defined, ...) aus dem Rezept heraus

Verschieben Sie einfach den Scheck in das Rezept:

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

Das führende @Zeichen schaltet das Echo des Befehls aus und :ist der eigentliche Befehl, ein Shell- No-Op-Stub .

Zielnamen anzeigen

Die check_definedFunktion kann verbessert werden, um auch den Zielnamen auszugeben (bereitgestellt durch die $@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 `$@')))

Damit führt eine fehlgeschlagene Prüfung zu einer gut formatierten Ausgabe:

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

check-defined-MY_FLAG spezielles Ziel

Persönlich würde ich die oben beschriebene einfache und unkomplizierte Lösung verwenden. In dieser Antwort wird jedoch beispielsweise vorgeschlagen, ein spezielles Ziel zu verwenden, um die eigentliche Prüfung durchzuführen. Man könnte versuchen, das zu verallgemeinern und das Ziel als implizite Musterregel zu definieren:

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

Verwendung:

foo :|check-defined-BAR

Beachten Sie, dass dies check-defined-BARals Voraussetzung nur für die Bestellung ( |...) aufgeführt ist.

Vorteile:

  • (wohl) eine sauberere Syntax

Nachteile:

Ich glaube, diese Einschränkungen können mit einigen evalmagischen und sekundären Erweiterungs- Hacks überwunden werden , obwohl ich nicht sicher bin, ob es sich lohnt.


Was genau? Nie Mac verwendet, obwohl ich denke, dass standardmäßig eine andere Implementierung von Make installiert ist (z. B. BSD Make anstelle von GNU Make). Ich würde vorschlagen, dass Sie make --versionals ersten Schritt überprüfen .
Eldar Abusalimov

1
Dies scheint in make 3.81 nicht zu funktionieren. Es gibt immer Fehler, auch wenn die Variable definiert ist (und wiedergegeben werden kann).
OrangeDog

Ah, Sie müssen es genau wie im verknüpften Duplikat strukturieren.
OrangeDog

1
@bibstha Ich habe die Optionen hinzugefügt, die mir in den Sinn kommen. Bitte lesen Sie die aktualisierte Antwort.
Eldar Abusalimov

2
Ich würde eine Klarstellung für Noobies hinzufügen (wie ich), dass ifndef nicht eingerückt werden muss :) Ich fand diesen Tipp woanders und plötzlich machten alle meine Fehler Sinn.
Helios

40

Verwenden Sie die Shell-Funktion test:

foo:
    test $(something)

Verwendung:

$ make foo
test 
Makefile:2: recipe for target 'foo' failed
make: *** [foo] Error 1
$ make foo something=x
test x

3
Dies ist, was ich verwendet habe, als ich mit dem gleichen Problem konfrontiert war - danke, Messa! Ich habe zwei geringfügige Änderungen vorgenommen: 1) Ich habe ein checkforsomethingZiel erstellt, das nur das enthält testund davon fooabhängig gemacht hat, und 2) ich habe @if test -z "$(something)"; then echo "helpful error here"; exit 1; fistattdessen die Prüfung in geändert . Das gab mir die Möglichkeit, einen nützlichen Fehler hinzuzufügen, und erlaubte mir, den Namen des neuen Ziels ein wenig deutlicher zu machen, was ebenfalls schief gelaufen ist.
Brian Gerard

Damit bekomme ichMakefile:5: *** missing separator. Stop.
Silgon

7
Aus Gründen der Kompaktheit habe ich test -n "$(something) || (echo "message" ; exit 1)das Explizite vermieden if.
user295691

@silgon Sie rücken wahrscheinlich Leerzeichen anstelle eines Tabulators ein.
Eweb

9

Sie können eine IF verwenden, um Folgendes zu testen:

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

Ergebnis:

$ make check
>> var is not set
Makefile:2: recipe for target 'check' failed
make: *** [check] Error 1

[ist ein Alias ​​für den Befehl test, daher ist dies die gleiche Antwort wie oben bei @Messa. Dies ist jedoch kompakter und umfasst die Generierung von Fehlermeldungen.
user295691

6

Verwenden Sie die Shell-Fehlerbehandlung für nicht gesetzte Variablen (beachten Sie das 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

Wenn Sie eine benutzerdefinierte Fehlermeldung benötigen, fügen Sie diese nach ?:

$ 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

2

Der Einfachheit und Kürze halber:

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

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

Mit Ausgängen:

$ make bar
Makefile:2: *** foo is undefined. Stop.
$ make bar foo="something"
echo "foo is $$foo"
foo is something
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.