Прервать make-файл, если переменная не установлена


151

Как я могу прервать выполнение make / makefile, основываясь на том, что переменная make-файла не была установлена ​​/ оценена?

Я придумал это, но работает, только если вызывающая сторона явно не запускает цель (т.е. работает 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)

Я думаю, что-то вроде этого было бы хорошей идеей, но я ничего не нашел в руководстве make:

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)

Ведущий @знак отключает отображение команд и :является фактической командой, заглушка no-op оболочки .

Отображение имени цели

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качестве обязательного условия указан только order ( |...).

Плюсы:

  • (возможно) более чистый синтаксис

Минусы:

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


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

1
Кажется, это не работает в make 3.81. Это всегда ошибки, даже если переменная определена (и может отображаться).
OrangeDog

Ах, вам нужно структурировать его точно так же, как в связанном дубликате.
OrangeDog

1
@bibstha Я добавил варианты, которые приходят на ум, пожалуйста, прочитайте обновленный ответ.
Эльдар Абусалимов

2
Я бы добавил разъяснение для новичков (как и я), что ifndef должен быть без отступа :) Я нашел этот совет где-то еще, и внезапно все мои ошибки обрели смысл.
Helios

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.
user295691

@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 выше. Это более компактно и включает генерацию сообщений об ошибках.
user295691

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.