Скасувати makefile, якщо змінна не встановлена


151

Як я можу скасувати виконання make / makefile на основі змінної makefile, яка не встановлюється / оцінюється?

Я придумав це, але працює лише в тому випадку, якщо абонент явно не виконує ціль (тобто працює makeлише).

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)

Я думаю, що щось подібне було б гарною ідеєю, але в посібнику марки нічого не знайдено:

ifndef MY_FLAG
.ABORT
endif

Відповіді:


271

TL; DR : Використовуйте errorфункцію :

ifndef MY_FLAG
$(error MY_FLAG is not set)
endif

Зверніть увагу, що рядки не повинні бути відступними. Точніше, жодні вкладки не повинні передувати цим рядкам.


Загальне рішення

Якщо ви збираєтеся перевірити багато змінних, для цього варто визначити допоміжну функцію:

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

А ось як ним користуватися:

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


Це призведе до такої помилки:

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

Примітки:

Справжня перевірка робиться тут:

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

Це відображає поведінку ifndefумовного, так що змінна, визначена порожнім значенням, також вважається "невизначеною". Але це справедливо лише для простих змінних і явно порожніх рекурсивних змінних:

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

Як запропонував @VictorSergienko у коментарях, може бути бажаною дещо інша поведінка:

$(if $(value $1)тестує, якщо значення не порожнє. Іноді добре, якщо змінна визначена з порожнім значенням . Я б користувався$(if $(filter undefined,$(origin $1)) ...

І:

Більше того, якщо це каталог і він повинен існувати, коли перевірка виконується, я б використовував $(if $(wildcard $1)). Але була б інша функція.

Цільова перевірка

Можна також розширити рішення так, що можна вимагати змінну, лише якщо викликається певна ціль.

$(call check_defined, ...) зсередини рецепта

Просто перенесіть чек у рецепт:

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

Ведучий @знак вимикає команду, що лунає, і :є фактичною командою, заглушкою без оболонки оболонки .

Показано назву цілі

check_definedФункція може бути поліпшена також вихід цільового імені ( при умови , через $@змінну):

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

Отже, тепер невдала перевірка дає добре відформатований вихід:

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

check-defined-MY_FLAG спеціальна ціль

Особисто я б скористався простим і зрозумілим рішенням вище. Однак, наприклад, ця відповідь пропонує використовувати спеціальну ціль для здійснення фактичної перевірки. Можна спробувати узагальнити це та визначити ціль як неявне правило шаблону:

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

Використання:

foo :|check-defined-BAR

Зверніть увагу , що check-defined-BARзазначений в якості замовлення тільки ( |...) передумови.

Плюси:

  • (мабуть) більш чистий синтаксис

Мінуси:

Я вважаю, що ці обмеження можна подолати, використовуючи деякі evalмагічні та вторинні хаки розширення , хоча я не впевнений, що цього варто.


Що саме? Ніколи не використовував Mac, хоча, мабуть, у нього інша реалізація Make встановлена ​​за замовчуванням (наприклад, BSD Make замість GNU Make). Я б запропонував вам перевірити make --versionяк перший крок.
Ельдар Абусалімов

1
Схоже, це не працює в марці 3.81. Це завжди помилки, навіть якщо змінна визначена (і може бути повтореною).
OrangeDog

Ах, вам потрібно структурувати його точно, як у пов'язаному дублікаті.
OrangeDog

1
@bibstha Я додав варіанти, які пам’ятають, прочитайте оновлену відповідь.
Ельдар Абусалімов

2
Я додам уточнення для noobies (як я), що ifndef не потрібно відступати :) Я знайшов цю пораду десь в іншому місці і раптом усі мої помилки мали сенс.
геліос

40

Використовуйте функцію оболонки test:

foo:
    test $(something)

Використання:

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

3
Це те, що я використовував, коли стикався з тим же питанням - дякую, Месса! Я вніс дві незначні модифікації: 1) Я створив checkforsomethingціль, яка мала лише testїї, і зробила fooзалежність від цього, і 2) я змінив чек на це @if test -z "$(something)"; then echo "helpful error here"; exit 1; fi. Це дало мені можливість додати корисну помилку і дозволило мені зробити ім'я нової цілі трохи більш показовим, що також пішло не так.
Брайан Джерард

З цим я отримуюMakefile:5: *** missing separator. Stop.
silgon

7
Для компактності я test -n "$(something) || (echo "message" ; exit 1)уникав явного if.
користувач295691

@silgon ви, ймовірно, відступаєте, використовуючи пробіли, а не вкладку.
eweb

9

Ви можете використовувати IF для тестування:

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

Результат:

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

[Псевдонім для команди test, тому це та сама відповідь, що і @Messa вище. Це, однак, більш компактно і включає генерацію повідомлення про помилку.
користувач295691

6

Використовуйте обробку помилок оболонки для невстановлених змінних (зверніть увагу на подвійний $):

$ 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

Якщо вам потрібно користувацьке повідомлення про помилку, додайте його після ?:

$ 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

Для простоти та стислості:

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

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

З виходами:

$ make bar
Makefile:2: *** foo is undefined. Stop.
$ make bar foo="something"
echo "foo is $$foo"
foo is something
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.