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

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.


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

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

[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

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.