Змінна 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

?

Дякую.


Що ви маєте на увазі, "переконайтеся, що ця змінна встановлена"? Ви маєте на увазі підтвердження чи забезпечення? Якщо вона не була встановлена ​​раніше, слід makeвстановити її, або попередити, або створити фатальну помилку?
Бета

1
Цю змінну повинен вказати сам користувач - оскільки він єдиний, хто знає своє оточення (dev, prod ...) - наприклад, зателефонувавши, make ENV=devале якщо він забуде ENV=dev, deployрецепт не вдасться ...
abernier

Відповіді:


171

Це призведе до фатальної помилки, якщо ENVвона не визначена і щось потрібно в ній (у GNUMake, у будь-якому випадку).

.PHONY: розгорнути чек-оточення

розгорнути: check-env
	...

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

реєстрація:
ifndef ENV
	$ (помилка ENV не визначена)
закінчення

(Зверніть увагу, що ifndef та endif не мають відступів - вони керують тим, що робить " бачу", набуваючи чинності перед запуском Makefile. "$ (Помилка" відрізається вкладкою, щоб вона працювала лише в контексті правила.)


12
Я отримую ENV is undefinedпід час виконання завдання, яке не має обов'язковою умовою check-env.
дощ

@rane: Це цікаво. Чи можете ви навести мінімальний повний приклад?
Бета

2
@rane - різниця між пробілами та символами вкладки?
емітувати

8
@esmit: Так; Я повинен був відповісти на це. У моєму рішенні рядок починається з TAB, тому це команда в check-envправилі; Зробити це не розширюватиме його, доки не буде виконано правило. Якщо він не починається з 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
Це розумне рішення, мені це подобається :)
Шанс Елліот

Я знаю, що це давня відповідь, але, можливо, хтось і досі спостерігає за нею, інакше я можу повторно опублікувати це як нове запитання ... Я намагаюся реалізувати цю неявну цільову "охорону", щоб перевірити наявність змінних середовища в принципі, однак команди в правилі "guard-%" насправді друкуються до оболонки. Це я хотів би придушити. Як це можливо?
genomicsio

2
ГАРАЗД. я знайшов рішення сам ... @ на початку командного рядка правила - мій друг ...
genomicsio

4
Один лайнер: if [ -z '${${*}}' ]; then echo 'Environment variable $* not set' && exit 1; fi: D
c24w

4
це має бути обрана відповідь. її більш чиста реалізація.
sb32134

46

Вбудований варіант

У моїх файлах я зазвичай використовую такий вираз:

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 під час першого проходження марки, поза будь-яким рецептом:

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

Ви можете прочитати про різні функції / змінні, які використовуються тут, і $()це лише спосіб явно заявити, що ми порівнюємо проти "нічого".


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


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

Проблема в тому, що deployце не обов'язково єдиний рецепт, який потребує цієї змінної. За допомогою цього рішення я повинен перевірити стан ENVкожного з них ... тоді як я хотів би мати справу з цим як єдину (своєрідну) умову.
аберньє

4

Я знаю, що це по-старому, але я подумав, що я надіюсь своїм власним досвідом для майбутніх відвідувачів, оскільки це трохи акуратніше ІМХО.

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

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

.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пробіли містять пробіли, то, здається, не вдалося (принаймні для мене)
eddiegroves

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

Привіт, thx для вашої відповіді, але не можу прийняти її через дублюючий код, який створюється вашою пропозицією ... тим більше, deployце не єдиний рецепт, який повинен перевірити ENVзмінну стану.
аберньє

то просто рефактор. Використовуйте .PHONY: deployі deploy:оператори перед блоком ifdef та видаліть дублювання. (btw Я відредагував відповідь, щоб відобразити правильний метод)
Дуайт Спенсер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.