Zmienna Makefile jako warunek wstępny

134

W pliku Makefile deployreguła wymaga ustawienia zmiennej środowiskowej, ENVaby poprawnie się wykonywała, podczas gdy innych to nie obchodzi, np .:

ENV = 

.PHONY: deploy hello

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

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

Jak mogę się upewnić, że ta zmienna jest ustawiona, np .: czy istnieje sposób na zadeklarowanie tej zmiennej makefile jako warunku wstępnego reguły wdrażania, na przykład:

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

?

Dziękuję Ci.

abernier
źródło
Co masz na myśli mówiąc „upewnij się, że ta zmienna jest ustawiona”? Masz na myśli weryfikację czy zapewnienie? Jeśli nie został ustawiony wcześniej, czy należy makego ustawić, ostrzegać lub generować błąd krytyczny?
Beta
1
Tę zmienną musi podać sam użytkownik - jako jedyny zna swoje otoczenie (dev, prod ...) - np. Dzwoniąc, make ENV=devale jeśli zapomni ENV=dev, deployprzepis się nie powiedzie ...
abernier

Odpowiedzi:

171

Spowoduje to fatalny błąd, jeśli ENVjest niezdefiniowany i coś tego potrzebuje (w każdym razie w GNUMake).

.PHONY: wdrażanie check-env

wdrożyć: check-env
	...

inna-rzecz-potrzeb-env: check-env
	...

check-env:
ifndef ENV
	$ (błąd ENV jest niezdefiniowany)
endif

(Zauważ, że ifndef i endif nie są wcięte - kontrolują to, co sprawia, że ​​"widzi" , działając przed uruchomieniem pliku Makefile. "$ (Błąd" jest wcięty za pomocą tabulatora, więc działa tylko w kontekście reguły).

Beta
źródło
12
Otrzymuję ENV is undefinedpodczas uruchamiania zadania, które nie ma warunku wstępnego check-env.
raine
@rane: To interesujące. Czy możesz podać minimalny kompletny przykład?
Beta
2
@rane jest różnica między spacjami a znakiem tabulacji?
wysłać
8
@esmit: Tak; Powinienem był na to odpowiedzieć. W moim rozwiązaniu linia zaczyna się od TAB, więc jest to polecenie w check-envregule; Make nie rozwinie tego, chyba że / do czasu wykonania reguły. Jeśli nie zaczyna się od TAB (jak w przykładzie @ rane), Make interpretuje go jako nie będącego w regule i ocenia go przed uruchomieniem jakiejkolwiek reguły, niezależnie od celu.
Beta
1
`` W moim rozwiązaniu wiersz zaczyna się od TAB, więc jest to polecenie w regule check-env; `` O której linii mówisz? W moim przypadku warunek if jest oceniany za każdym razem, nawet gdy wiersz po ifndef zaczyna się od TAB
Dhawal.
103

Możesz utworzyć niejawny cel ochronny, który sprawdza, czy zmienna w rdzeniu jest zdefiniowana, na przykład:

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

Następnie dodajesz guard-ENVVARcel w dowolnym miejscu, w którym chcesz potwierdzić, że zmienna jest zdefiniowana, na przykład:

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

Jeśli zadzwonisz make change-hostnamebez dodawania HOSTNAME=somehostnamewywołania, pojawi się błąd, a kompilacja się nie powiedzie.

Clayton Stanley
źródło
5
To sprytne rozwiązanie, podoba mi się :)
Elliot Chance
Wiem, że jest to stara odpowiedź, ale być może ktoś nadal ją obserwuje, w przeciwnym razie mógłbym zamieścić to ponownie jako nowe pytanie ... Próbuję zaimplementować ten niejawny "strażnik" celu, aby sprawdzić ustawione zmienne środowiskowe i działa w zasadzie jednak polecenia w regule „guard-%” są w rzeczywistości wypisywane w powłoce. Chciałbym to stłumić. Jak to jest możliwe?
genomicsio
2
DOBRZE. sam znalazłem rozwiązanie ... @ na początku wiersza poleceń reguły jest mój przyjaciel ...
genomicsio
4
One- if [ -z '${${*}}' ]; then echo 'Environment variable $* not set' && exit 1; fi
linier
4
powinna to być wybrana odpowiedź. to czystsza implementacja.
sb32134
46

Wariant Inline

W moich plikach makefile zwykle używam wyrażenia takiego jak:

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

Powody:

  • jest to prosta jedna linijka
  • jest kompaktowy
  • znajduje się blisko poleceń używających tej zmiennej

Nie zapomnij komentarza, który jest ważny przy debugowaniu:

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

... zmusza cię do wyszukania Makefile podczas ...

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

... wyjaśnia bezpośrednio, co się stało

Wariant globalny (dla kompletności, ale bez pytania)

Oprócz pliku Makefile możesz również napisać:

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

Ostrzeżenia:

  • nie używaj tabulatora w tym bloku
  • używaj ostrożnie: nawet cleancel nie powiedzie się, jeśli ENV nie jest ustawiony. W przeciwnym razie zobacz odpowiedź Hudona, która jest bardziej złożona
Daniel Alder
źródło
Łał. Miałem z tym problem, dopóki nie zobaczyłem „nie używaj tabulatora w tym bloku”. Dziękuję Ci!
Alex K
To dobra alternatywa, ale nie podoba mi się, że „komunikat o błędzie” pojawia się, nawet jeśli się powiedzie (cała linia jest drukowana)
Jeff
@Jeff To podstawy makefile. Po prostu poprzedz wiersz znakiem @. -> gnu.org/software/make/manual/make.html#Echoing
Daniel Alder
Próbowałem, ale wtedy komunikat o błędzie nie pojawi się w przypadku niepowodzenia. Hmm, spróbuję jeszcze raz. Na pewno zagłosowałem za twoją odpowiedzią.
Jeff
1
Podoba mi się podejście testowe. Użyłem czegoś takiego:@test -n "$(name)" || (echo 'A name must be defined for the backup. Ex: make backup name=xyz' && exit 1)
swampfox357
6

Jednym z możliwych problemów z dotychczas udzielonymi odpowiedziami jest to, że kolejność zależności w make nie jest zdefiniowana. Na przykład bieganie:

make -j target

when targetma kilka zależności, nie gwarantuje, że będą one działać w dowolnej kolejności.

Rozwiązaniem tego problemu (aby zagwarantować, że ENV zostanie sprawdzone przed wybraniem receptury) jest sprawdzenie ENV podczas pierwszego przejścia marki, poza jakąkolwiek recepturą:

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

Możesz przeczytać o różnych funkcjach / zmiennych używanych tutaj i $()jest to tylko sposób na wyraźne stwierdzenie, że porównujemy z „niczym”.

Hudon
źródło
6

Uważam, że z najlepszą odpowiedzią nie można użyć jako wymagania, z wyjątkiem innych celów PHONY. Jeśli jest używany jako zależność dla celu, który jest rzeczywistym plikiem, użycie check-envwymusi odbudowanie tego celu pliku.

Inne odpowiedzi są globalne (np. Zmienna jest wymagana dla wszystkich celów w Makefile) lub używają powłoki, np. Gdyby brakowało ENV, make zakończy działanie niezależnie od celu.

Rozwiązaniem obu problemów, które znalazłem, jest

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

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

.PHONY: build
build:
    echo "building"

Wynik wygląda jak

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

value ma kilka przerażających zastrzeżeń, ale w przypadku tego prostego zastosowania uważam, że jest to najlepszy wybór.

23jodys
źródło
5

Jak widzę, samo polecenie potrzebuje zmiennej ENV, więc możesz to sprawdzić w samym poleceniu:

.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
źródło
Problem polega na tym, że deployniekoniecznie jest to jedyny przepis, który wymaga tej zmiennej. Przy takim rozwiązaniu muszę przetestować stan ENVkażdego z nich ... podczas gdy chciałbym traktować to jako jeden (rodzaj) warunek wstępny.
abernier
4

Wiem, że to jest stare, ale pomyślałem, że wtrącę się z własnymi doświadczeniami dla przyszłych gości, ponieważ jest trochę schludniejszy IMHO.

Zwykle makeużyje shpowłoki domyślnej ( ustawianej przez specjalną SHELLzmienną ). W shi jego pochodne, to trywialny wyjść z komunikatem o błędzie podczas pobierania zmiennej środowiskowej, jeśli nie jest ustawiona lub null, wykonując: ${VAR?Variable VAR was not set or null}.

Rozszerzając to, możemy napisać make target wielokrotnego użytku, który może być użyty do zawodzenia innych celów, jeśli zmienna środowiskowa nie została ustawiona:

.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!"

Uwagi:

  • Znak dolara uciekającego ( $$) jest wymagany do odroczenia rozwinięcia do powłoki zamiast do wewnątrzmake
  • Użycie testjest tylko po to, aby zapobiec próbom wykonania przez powłokę zawartości VAR(nie służy to żadnemu innemu znaczącemu celowi)
  • .check-env-varsmożna w trywialny sposób rozszerzyć, aby sprawdzić więcej zmiennych środowiskowych, z których każda dodaje tylko jedną linię (np. @test $${NEWENV?Please set environment variable NEWENV})
Lewis Belcher
źródło
Jeśli ENVzawiera spacje, wydaje się, że zawodzi (przynajmniej dla mnie)
eddiegroves
2

Możesz użyć ifdefzamiast innego celu.

.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
Daniel Gallagher
źródło
Hej, dzięki za odpowiedź, ale nie mogę jej zaakceptować z powodu zduplikowanego kodu, który generuje twoja sugestia ... tym bardziej deploynie jest to jedyny przepis, który musi sprawdzać ENVzmienną stanu.
Abernier
następnie po prostu refaktoryzuj. Użyj instrukcji .PHONY: deployi deploy:przed blokiem ifdef i usuń duplikację. (przy okazji poprawiłem odpowiedź, aby odzwierciedlić poprawną metodę)
Dwight Spencer