Przerwij makefile, jeśli zmienna nie jest ustawiona

151

Jak mogę przerwać wykonanie make / makefile na podstawie zmiennej makefile, która nie została ustawiona / wyceniona?

Wymyśliłem to, ale działa tylko wtedy, gdy wywołujący nie uruchamia jawnie celu (tj. Działa maketylko).

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)

Myślę, że coś takiego byłoby dobrym pomysłem, ale nie znalazłem nic w instrukcji make:

ifndef MY_FLAG
.ABORT
endif
Caruccio
źródło
3
możliwy duplikat zmiennej Makefile jako warunek wstępny
Keith Smiley

Odpowiedzi:

271

TL; DR : użyj errorfunkcji :

ifndef MY_FLAG
$(error MY_FLAG is not set)
endif

Zwróć uwagę, że linie nie mogą być wcięte. Dokładniej, żadne tabulatory nie mogą poprzedzać tych wierszy.


Rozwiązanie ogólne

W przypadku, gdy zamierzasz przetestować wiele zmiennych, warto zdefiniować w tym celu funkcję pomocniczą:

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

A oto jak z niego korzystać:

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


Spowoduje to wyświetlenie następującego błędu:

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

Uwagi:

Prawdziwe sprawdzenie odbywa się tutaj:

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

Odzwierciedla to zachowanie ifndefwarunku, tak że zmienna zdefiniowana jako pusta wartość jest również uważana za „nieokreśloną”. Ale dotyczy to tylko prostych zmiennych i jawnie pustych zmiennych rekurencyjnych:

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

Jak sugeruje @VictorSergienko w komentarzach, pożądane może być nieco inne zachowanie:

$(if $(value $1)sprawdza, czy wartość nie jest pusta. Czasami jest OK, jeśli zmienna jest zdefiniowana z pustą wartością . Użyłbym$(if $(filter undefined,$(origin $1)) ...

I:

Co więcej, jeśli jest to katalog i musi istnieć podczas sprawdzania, użyłbym $(if $(wildcard $1)). Ale byłaby inna funkcja.

Kontrola specyficzna dla celu

Możliwe jest również rozszerzenie rozwiązania tak, aby wymagać zmiennej tylko w przypadku wywołania określonego celu.

$(call check_defined, ...) od wewnątrz przepisu

Po prostu przenieś czek do przepisu:

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

@Znak wiodący wyłącza echo polecenia i :jest faktycznym poleceniem, skrótem powłoki bez operacji .

Wyświetlam nazwę celu

check_definedFunkcja może poprawić również wysyłać nazwy docelowego (dostarczone przez $@zmienną)

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

Tak więc, teraz nieudane sprawdzenie daje ładnie sformatowany wynik:

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

check-defined-MY_FLAG specjalny cel

Osobiście skorzystałbym z prostego i prostego rozwiązania powyżej. Jednak na przykład ta odpowiedź sugeruje użycie specjalnego celu do przeprowadzenia faktycznej kontroli. Można by spróbować uogólnić to i zdefiniować cel jako niejawną regułę wzorcową:

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

Stosowanie:

foo :|check-defined-BAR

Zwróć uwagę, że check-defined-BARjest wymieniony jako warunek wstępny dotyczący tylko zamówienia ( |...).

Plusy:

  • (prawdopodobnie) bardziej przejrzysta składnia

Cons:

Uważam, że te ograniczenia można pokonać za pomocą evalmagicznych i dodatkowych hacków rozszerzeń , chociaż nie jestem pewien, czy warto.

Eldar Abusalimov
źródło
Co dokładnie? Nigdy nie używałem Maca, chociaż domyślam się, że ma inną implementację Make (np. BSD Make zamiast GNU Make). Proponuję sprawdzić make --versionjako pierwszy krok.
Eldar Abusalimov
1
Wydaje się, że to nie działa w make 3.81. Zawsze zawiera błędy, nawet jeśli zmienna jest zdefiniowana (i może być powtórzona).
OrangeDog,
Ach, musisz to uporządkować dokładnie tak, jak w połączonym duplikacie.
OrangeDog
1
@bibstha Dodałem opcje, które przychodzą mi do głowy, przeczytaj zaktualizowaną odpowiedź.
Eldar Abusalimov
2
Dodałbym wyjaśnienie-dla-noobies (jak ja), że ifndef nie musi być wcięty :) Znalazłem tę wskazówkę gdzie indziej i nagle wszystkie moje błędy miały sens.
helios
40

Użyj funkcji powłoki test:

foo:
    test $(something)

Stosowanie:

$ make foo
test 
Makefile:2: recipe for target 'foo' failed
make: *** [foo] Error 1
$ make foo something=x
test x
Messa
źródło
3
Właśnie tego użyłem, gdy miałem ten sam problem - dzięki, Messa! Dokonałem dwóch drobnych modyfikacji: 1) stworzyłem checkforsomethingcel, który miał tylko testw sobie i foouzależniłem się od tego, oraz 2) @if test -z "$(something)"; then echo "helpful error here"; exit 1; fizamiast tego zmieniłem czek na . To dało mi możliwość dodania użytecznego błędu i pozwoliło mi nieco bardziej wskazać nazwę nowego celu, co również poszło nie tak.
Brian Gerard
Dzięki temu dostajęMakefile:5: *** missing separator. Stop.
silgon
7
Dla zwięzłości użyłem test -n "$(something) || (echo "message" ; exit 1)aby uniknąć jawne if.
user295691
@silgon, prawdopodobnie wcinasz używając spacji zamiast tabulatora.
eweb
9

Możesz użyć IF, aby przetestować:

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

Wynik:

$ make check
>> var is not set
Makefile:2: recipe for target 'check' failed
make: *** [check] Error 1
Raittes
źródło
[jest aliasem dla polecenia test, więc jest to ta sama odpowiedź, co @Messa powyżej. Jest to jednak bardziej kompaktowe i obejmuje generowanie komunikatów o błędach.
user295691
6

Użyj obsługi błędów powłoki dla nieustawionych zmiennych (zwróć uwagę na podwójne $):

$ 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

Jeśli potrzebujesz niestandardowego komunikatu o błędzie, dodaj go po ?:

$ 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
źródło
2

Dla prostoty i zwięzłości:

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

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

Z wyjściami:

$ make bar
Makefile:2: *** foo is undefined. Stop.
$ make bar foo="something"
echo "foo is $$foo"
foo is something
bsimpson53
źródło