Переменная Makefile в качестве предпосылки

В 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

?

Спасибо.

Ответ 1

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

.PHONY: deploy check-env

deploy: check-env
	...

other-thing-that-needs-env: check-env
	...

check-env:
ifndef ENV
	$(error ENV is undefined)
endif

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

Ответ 2

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

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

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

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

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

Ответ 3

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

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

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

Причины:

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

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

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 не установлен. В противном случае см. Hudon answer, который является более сложным.

Ответ 4

Как я вижу, для самой команды нужна переменная 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

Ответ 5

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

Ответ 6

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

make -j target

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

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

## Are any of the user 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
    ...

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

Ответ 7

Вы можете использовать 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

Ответ 8

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

Как правило, 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})