Annulla makefile se la variabile non è impostata


151

Come potrei interrompere un'esecuzione di make / makefile in base al fatto che la variabile di un makefile non viene impostata / valutata?

Ho pensato a questo, ma funziona solo se il chiamante non esegue esplicitamente un target (cioè viene eseguito makesolo).

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)

Penso che qualcosa del genere sarebbe una buona idea, ma non ho trovato nulla nel manuale di make:

ifndef MY_FLAG
.ABORT
endif

Risposte:


271

TL; DR : utilizzare la errorfunzione :

ifndef MY_FLAG
$(error MY_FLAG is not set)
endif

Si noti che le righe non devono essere rientrate. Più precisamente, nessuna scheda deve precedere queste righe.


Soluzione generica

Nel caso in cui testerai molte variabili, vale la pena definire una funzione ausiliaria per questo:

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

Ed ecco come usarlo:

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


Ciò produrrebbe un errore come questo:

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

Appunti:

Il vero controllo è fatto qui:

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

Ciò riflette il comportamento del ifndefcondizionale, in modo che anche una variabile definita su un valore vuoto sia considerata "non definita". Questo è vero solo per variabili semplici e variabili ricorsive esplicitamente vuote:

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

Come suggerito da @VictorSergienko nei commenti, può essere desiderato un comportamento leggermente diverso:

$(if $(value $1)verifica se il valore non è vuoto. A volte è OK se la variabile è definita con un valore vuoto . Vorrei usare$(if $(filter undefined,$(origin $1)) ...

E:

Inoltre, se si tratta di una directory e deve esistere quando viene eseguito il controllo, utilizzerei $(if $(wildcard $1)). Ma sarebbe un'altra funzione.

Controllo specifico per target

È anche possibile estendere la soluzione in modo da poter richiedere una variabile solo se viene invocato un determinato target.

$(call check_defined, ...) dall'interno della ricetta

Basta spostare il segno di spunta nella ricetta:

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

Il @segno principale disattiva l'eco del comando ed :è il comando effettivo, uno stub no-op della shell .

Mostra il nome target

La check_definedfunzione può essere migliorata per generare anche il nome di destinazione (fornito attraverso la $@variabile):

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 `$@')))

Quindi, ora un controllo non riuscito produce un output ben formattato:

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

check-defined-MY_FLAG bersaglio speciale

Personalmente userei la soluzione semplice e diretta sopra. Tuttavia, ad esempio, questa risposta suggerisce di utilizzare un obiettivo speciale per eseguire il controllo effettivo. Si potrebbe provare a generalizzare questo e definire il target come una regola del modello implicito:

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

Uso:

foo :|check-defined-BAR

Si noti che il check-defined-BARè elencato come prerequisito per il solo ordine ( |...).

Professionisti:

  • (discutibilmente) una sintassi più pulita

Contro:

Credo che queste limitazioni possano essere superate usando alcuni hack evalmagici e di espansione secondaria , anche se non sono sicuro che ne valga la pena.


Che cosa esattamente? Non ho mai usato Mac, anche se immagino che abbia un'altra implementazione di Make installata per impostazione predefinita (ad esempio BSD Make anziché GNU Make). Ti suggerirei di controllare make --versioncome primo passo.
Eldar Abusalimov,

1
Questo non sembra funzionare in Make 3.81. Errori sempre, anche se la variabile è definita (e può essere ripetuta).
OrangeDog

Ah, è necessario strutturarlo esattamente come nel duplicato collegato.
OrangeDog,

1
@bibstha Ho aggiunto le opzioni che vengono in mente, per favore leggi la risposta aggiornata.
Eldar Abusalimov,

2
Aggiungerei un chiarimento per i noobies (come me) sul fatto che ifndef non debba essere indentato :) Ho trovato quel suggerimento da qualche altra parte e improvvisamente tutti i miei errori avevano senso.
helios,

40

Usa la funzione shell test:

foo:
    test $(something)

Uso:

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

3
Questo è quello che ho usato di fronte allo stesso problema: grazie Messa! Ho apportato due lievi modifiche: 1) Ho creato un checkforsomethingobiettivo che conteneva solo l' testinterno e ne foodipendevo, e 2) invece ho cambiato il controllo @if test -z "$(something)"; then echo "helpful error here"; exit 1; fi. Ciò mi ha dato la possibilità di aggiungere un errore utile e mi ha permesso di rendere il nome del nuovo target un po 'più indicativo di ciò che è andato storto.
Brian Gerard,

Con questo ottengoMakefile:5: *** missing separator. Stop.
silgon il

7
Per compattezza ho usato test -n "$(something) || (echo "message" ; exit 1)per evitare l'esplicito if.
user295691

@silgon probabilmente stai rientrando usando gli spazi piuttosto che una scheda.
eweb

9

È possibile utilizzare un IF per testare:

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

Risultato:

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

[è un alias per il comando test, quindi questa è la stessa risposta di @Messa sopra. Questo è più compatto, tuttavia, e include la generazione di messaggi di errore.
user295691

6

Utilizzare la gestione degli errori della shell per le variabili non impostate (notare il doppio $):

$ 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

Se hai bisogno di un messaggio di errore personalizzato, aggiungilo dopo ?:

$ 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

Per semplicità e brevità:

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

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

Con uscite:

$ make bar
Makefile:2: *** foo is undefined. Stop.
$ make bar foo="something"
echo "foo is $$foo"
foo is something
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.