Переменная Makefile как необходимое условие

134

В Makefile deployрецепту нужна переменная окружения, которая ENVдолжна быть настроена для правильного выполнения, в то время как другим все равно, например:

ENV = 

.PHONY: deploy hello

deploy:
    rsync . $(ENV).example.com:/var/www/myapp/

hello:
    echo "I don't care about ENV, just saying hello!"

Как я могу убедиться, что эта переменная установлена, например: есть ли способ объявить эту переменную makefile как обязательное условие рецепта развертывания, например:

deploy: make-sure-ENV-variable-is-set

?

Спасибо.

abernier
источник
Что вы имеете в виду, «убедитесь, что эта переменная установлена»? Вы имеете в виду проверить или убедиться? Если он не был установлен ранее, должен ли makeон быть установлен, или выдавать предупреждение, или генерировать фатальную ошибку?
бета,
1
Эта переменная должна быть указана самим пользователем - поскольку он единственный, кто знает свое окружение (dev, prod ...) - например, путем вызова, make ENV=devно если он забудет ENV=dev, deployрецепт не удастся ...
abernier

Ответы:

171

Это приведет к фатальной ошибке, если ENVона не определена и что-то нужно (в любом случае в GNUMake).

.PHONY: развернуть check-env

развернуть: check-env
	...

другое-что-нужно-env: check-env
	...

чек-ENV:
ifndef ENV
	$ (ошибка ENV не определена)
ENDIF

(Обратите внимание, что ifndef и endif не имеют отступов - они управляют тем, что make «видит» , вступая в силу перед запуском Makefile. «$ (Error» имеет отступ с вкладкой, поэтому он запускается только в контексте правила.)

Бета
источник
12
Я получаю, ENV is undefinedкогда запускаю задачу, у которой нет check-env в качестве предварительного условия.
дождь
@rane: это интересно. Можете ли вы привести минимальный полный пример?
бета,
2
@rane разница в пробелах по сравнению с символом табуляции?
13
8
@esmit: да; Я должен был ответить об этом. В моем решении строка начинается с табуляции, так что это команда в check-envправиле; Make не будет расширять его до тех пор, пока не будет выполнено правило. Если он не начинается с TAB (как в примере @ rane), Make интерпретирует его как не входящий в правило, и оценивает его перед выполнением любого правила, независимо от цели.
бета
1
`` `В моем решении строка начинается с TAB, так что это команда в правиле check-env;` `` О какой строке вы говорите? В моем случае условие if вычисляется каждый раз, даже когда строка после ifndef начинается с TAB
Dhawal
103

Вы можете создать неявную защитную цель, которая проверяет, что переменная в стебле определена, например так:

guard-%:
    @ if [ "${${*}}" = "" ]; then \
        echo "Environment variable $* not set"; \
        exit 1; \
    fi

Затем вы добавляете guard-ENVVARцель везде, где хотите утверждать, что переменная определена, например так:

change-hostname: guard-HOSTNAME
        ./changeHostname.sh ${HOSTNAME}

Если вы позвоните make change-hostnameбез добавления HOSTNAME=somehostnameзвонка, вы получите сообщение об ошибке, и сборка не удастся.

Клейтон Стэнли
источник
5
Это умное решение, мне это нравится :)
Elliot Chance
Я знаю, что это древний ответ, но, возможно, кто-то все еще смотрит его, в противном случае я мог бы повторно опубликовать это как новый вопрос ... Я пытаюсь реализовать эту неявную целевую «охрану», чтобы проверить наличие установленных переменных среды, и это работает в принципе, однако, команды в правиле «guard-%» фактически печатаются в оболочку. Это я хотел бы подавить. Как это возможно?
genomicsio
2
ХОРОШО. сам нашел решение ... @ в начале правила командной строки мой друг ...
genomicsio
4
One-liner:: if [ -z '${${*}}' ]; then echo 'Environment variable $* not set' && exit 1; fiD
c24w
4
это должен быть выбранный ответ. это более чистая реализация.
sb32134
46

Встроенный вариант

В моих make-файлах я обычно использую выражение вроде:

deploy:
    test -n "$(ENV)"  # $$ENV
    rsync . $(ENV).example.com:/var/www/myapp/

Причины:

  • это простой однострочник
  • это компактно
  • он расположен близко к командам, которые используют переменную

Не забудьте комментарий, который важен для отладки:

test -n ""
Makefile:3: recipe for target 'deploy' failed
make: *** [deploy] Error 1

... заставляет вас искать Makefile, пока ...

test -n ""  # $ENV
Makefile:3: recipe for target 'deploy' failed
make: *** [deploy] Error 1

... объясняет прямо, что не так

Глобальный вариант (для полноты, но не задан)

В верхней части вашего Makefile вы также можете написать:

ifeq ($(ENV),)
  $(error ENV is not set)
endif

Предупреждения:

  • не используйте вкладку в этом блоке
  • используйте с осторожностью: даже cleanцель потерпит неудачу, если ENV не установлен. В противном случае см. Ответ Гудона, который является более сложным
Даниэль Алдер
источник
Вот это да. У меня были проблемы с этим, пока я не увидел «не использовать вкладку в этом блоке». Спасибо!
Алекс К
Это хорошая альтернатива, но мне не нравится, что «сообщение об ошибке» появляется даже в случае успеха (печатается вся строка)
Джефф
@Jeff Это основы makefile. Просто префикс строки с @. -> gnu.org/software/make/manual/make.html#Echoing
Даниэль Алдер,
Я пробовал это, но тогда сообщение об ошибке не появится в случае сбоя. Хм, попробую еще раз. Подтвердил ваш ответ наверняка.
Джефф
1
Мне нравится тестовый подход. Я использовал что-то вроде этого:@test -n "$(name)" || (echo 'A name must be defined for the backup. Ex: make backup name=xyz' && exit 1)
swampfox357
6

Пока что возможной проблемой с данными ответами является то, что порядок зависимости в make не определен. Например, работает:

make -j target

когда target есть несколько зависимостей, это не гарантирует, что они будут работать в любом заданном порядке.

Решением для этого (чтобы гарантировать, что ENV будет проверен до выбора рецептов) является проверка ENV во время первого прохода make вне любого рецепта:

## Are any of the user's goals dependent on ENV?
ifneq ($(filter deploy other-thing-that-needs-ENV,$(MAKECMDGOALS)),$())
ifndef ENV 
$(error ENV not defined)
endif
endif

.PHONY: deploy

deploy: foo bar
    ...

other-thing-that-needs-ENV: bar baz bono
    ...

Вы можете прочитать о различных функциях / переменных, используемых здесь, и $()это просто способ явно заявить, что мы сравниваем с «ничем».

Hudon
источник
6

Я обнаружил, что лучший ответ не может быть использован в качестве требования, за исключением других целей PHONY. Если используется как зависимость для цели, которая является реальным файлом, использование check-envзаставит цель файла быть перестроенной.

Другие ответы являются глобальными (например, переменная требуется для всех целей в Makefile) или используют оболочку, например, если ENV отсутствовал, make завершится независимо от цели.

Решение, которое я нашел для обеих проблем:

ndef = $(if $(value $(1)),,$(error $(1) not set))

.PHONY: deploy
deploy:
    $(call ndef,ENV)
    echo "deploying $(ENV)"

.PHONY: build
build:
    echo "building"

Выход выглядит как

$ make build
echo "building"
building
$ make deploy
Makefile:5: *** ENV not set.  Stop.
$ make deploy ENV="env"
echo "deploying env"
deploying env
$

value есть некоторые страшные предостережения, но для этого простого использования я считаю, что это лучший выбор.

23jodys
источник
5

Как я вижу, самой команде нужна переменная ENV, чтобы вы могли проверить это в самой команде:

.PHONY: deploy check-env

deploy: check-env
    rsync . $(ENV).example.com:/var/www/myapp/

check-env:
    if test "$(ENV)" = "" ; then \
        echo "ENV not set"; \
        exit 1; \
    fi
ssmir
источник
Проблема в том, что deployэто не единственный рецепт, которому нужна эта переменная. С этим решением я должен проверить состояние ENVкаждого из них ... в то время как я хотел бы иметь дело с ним как с одной (своего рода) предпосылкой.
abernier
4

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

Как правило, make будет использоваться в shкачестве оболочки по умолчанию ( устанавливается через специальную SHELLпеременную ). В shи его производных, это тривиально выйти с сообщением об ошибке при извлечении переменного окружения , если он не установлен или нулевой , выполнив: ${VAR?Variable VAR was not set or null}.

Расширяя это, мы можем написать повторно используемую цель make, которая может использоваться для сбоя других целей, если переменная окружения не была установлена:

.check-env-vars:
    @test $${ENV?Please set environment variable ENV}


deploy: .check-env-vars
    rsync . $(ENV).example.com:/var/www/myapp/


hello:
    echo "I don't care about ENV, just saying hello!"

Вещи примечания:

  • Экранированный знак доллара ( $$) необходим для отсрочки расширения в оболочку, а не вmake
  • Использование testпросто предотвращает попытки оболочки выполнить содержимое VAR(это не служит никакой другой важной цели)
  • .check-env-varsможет быть тривиально расширен для проверки большего количества переменных среды, каждая из которых добавляет только одну строку (например @test $${NEWENV?Please set environment variable NEWENV})
Льюис Белчер
источник
Если ENVесть пробелы, это, кажется, не
удается
2

Вы можете использовать ifdefвместо другой цели.

.PHONY: deploy
deploy:
    ifdef ENV
        rsync . $(ENV).example.com:/var/www/myapp/
    else
        @echo 1>&2 "ENV must be set"
        false                            # Cause deploy to fail
    endif
Даниэль Галлахер
источник
Привет, спасибо за ваш ответ, но не могу принять его из-за дублированного кода, который генерирует ваше предложение ... тем более, что deployэто не единственный рецепт проверки ENVпеременной состояния.
abernier
тогда просто рефакторинг. Используйте операторы .PHONY: deployand deploy:перед блоком ifdef и удалите дублирование. (Кстати, я отредактировал ответ, чтобы отразить правильный метод)
Дуайт Спенсер