Makefile-Variable als Voraussetzung

134

In einem Makefile muss für ein deployRezept eine Umgebungsvariable ENVfestgelegt werden, damit sie sich selbst ordnungsgemäß ausführt, während es anderen egal ist, z.

ENV = 

.PHONY: deploy hello

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

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

Wie kann ich sicherstellen, dass diese Variable festgelegt ist, z. B.: Gibt es eine Möglichkeit, diese Makefile-Variable als Voraussetzung für das Bereitstellungsrezept zu deklarieren, z.

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

?

Danke dir.

abernier
quelle
Was meinst du mit "Stellen Sie sicher, dass diese Variable gesetzt ist"? Meinen Sie überprüfen oder sicherstellen? Wenn es vorher nicht festgelegt wurde, sollte makees festgelegt oder eine Warnung ausgegeben oder ein schwerwiegender Fehler generiert werden?
Beta
1
Diese Variable muss vom Benutzer selbst angegeben werden - da er der einzige ist, der seine Umgebung kennt (dev, prod ...) - zum Beispiel durch Aufrufen, make ENV=devaber wenn er es vergisst ENV=dev, deploy
schlägt das

Antworten:

171

Dies führt zu einem schwerwiegenden Fehler, wenn er nicht ENVdefiniert ist und von etwas benötigt wird (jedenfalls in GNUMake).

.PHONY: Check-env bereitstellen

Bereitstellen: check-env
	...

andere-Sache-die-braucht-env: check-env
	...

check-env:
ifndef ENV
	$ (Fehler ENV ist undefiniert)
endif

(Beachten Sie, dass ifndef und endif nicht eingerückt sind - sie steuern, was make "sieht" und werden wirksam, bevor das Makefile ausgeführt wird. "$ (Fehler" wird mit einer Registerkarte eingerückt, sodass es nur im Kontext der Regel ausgeführt wird.)

Beta
quelle
12
Ich erhalte ENV is undefinedbeim Ausführen einer Aufgabe, für die Check-Env nicht erforderlich ist.
Raine
@ Rane: Das ist interessant. Können Sie ein minimales vollständiges Beispiel geben?
Beta
2
@rane ist der Unterschied zwischen Leerzeichen und Tabulatorzeichen?
Esmit
8
@esmit: Ja; Ich hätte darauf antworten sollen. In meiner Lösung beginnt die Zeile mit einer TAB, daher ist dies ein Befehl in der check-envRegel. Make wird es erst erweitern, wenn / bis die Regel ausgeführt wird. Wenn es nicht mit einem TAB beginnt (wie im Beispiel von @ rane), interpretiert Make es als nicht in einer Regel enthalten und wertet es aus, bevor eine Regel ausgeführt wird, unabhängig vom Ziel.
Beta
1
`` `In meiner Lösung beginnt die Zeile mit einem TAB, also ist es ein Befehl in der check-env-Regel;` `` Über welche Zeile sprechen Sie? In meinem Fall wird die if-Bedingung jedes Mal ausgewertet, selbst wenn die Zeile nach ifndef mit TAB
Dhawal am
103

Sie können ein implizites Schutzziel erstellen, das überprüft, ob die Variable im Stamm wie folgt definiert ist:

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

Anschließend fügen Sie ein guard-ENVVARZiel an einer beliebigen Stelle hinzu, an der Sie behaupten möchten, dass eine Variable wie folgt definiert ist:

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

Wenn Sie aufrufen make change-hostname, ohne HOSTNAME=somehostnameden Aufruf hinzuzufügen , wird eine Fehlermeldung angezeigt, und der Build schlägt fehl.

Clayton Stanley
quelle
5
Das ist eine clevere Lösung, ich mag es :)
Elliot Chance
Ich weiß, dass dies eine alte Antwort ist, aber vielleicht sieht sie sich noch jemand an, sonst könnte ich dies als neue Frage erneut posten ... Ich versuche, diesen impliziten Ziel- "Schutz" zu implementieren, um nach festgelegten Umgebungsvariablen zu suchen, und es funktioniert Im Prinzip werden die Befehle in der Regel "guard-%" jedoch tatsächlich auf die Shell gedruckt. Dies möchte ich unterdrücken. Wie ist das möglich?
Genomicsio
2
OK. Ich habe die Lösung selbst gefunden ... @ am Anfang der Regel Befehlszeilen ist mein Freund ...
Genomicsio
4
Einzeiler: if [ -z '${${*}}' ]; then echo 'Environment variable $* not set' && exit 1; fi: D
c24w
4
Dies sollte die ausgewählte Antwort sein. Es ist eine sauberere Implementierung.
sb32134
46

Inline-Variante

In meinen Makefiles verwende ich normalerweise einen Ausdruck wie:

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

Die Gründe:

  • Es ist ein einfacher Einzeiler
  • es ist kompakt
  • Es befindet sich in der Nähe der Befehle, die die Variable verwenden

Vergessen Sie nicht den Kommentar, der für das Debuggen wichtig ist:

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

... zwingt dich, das Makefile nachzuschlagen, während ...

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

... erklärt direkt, was los ist

Globale Variante (der Vollständigkeit halber, aber nicht gefragt)

Über Ihr Makefile können Sie auch schreiben:

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

Warnungen:

  • Verwenden Sie in diesem Block keine Registerkarte
  • Vorsichtig cleananwenden : Auch das Ziel schlägt fehl, wenn ENV nicht eingestellt ist. Ansonsten siehe Hudons Antwort, die komplexer ist
Daniel Alder
quelle
Beeindruckend. Ich hatte Probleme damit, bis ich sah, dass in diesem Block kein Tab verwendet wird. Danke dir!
Alex K
Es ist eine gute Alternative, aber ich mag es nicht, dass die "Fehlermeldung" erscheint, auch wenn sie erfolgreich ist (die ganze Zeile wird gedruckt)
Jeff
@ Jeff Das sind Makefile-Grundlagen. Stellen Sie der Zeile einfach ein vor @. -> gnu.org/software/make/manual/make.html#Echoing
Daniel Alder
Ich habe das versucht, aber dann wird die Fehlermeldung im Fehlerfall nicht angezeigt. Hmm, ich werde es noch einmal versuchen. Ich habe Ihre Antwort mit Sicherheit positiv bewertet.
Jeff
1
Ich mag den Testansatz. Ich habe so etwas benutzt:@test -n "$(name)" || (echo 'A name must be defined for the backup. Ex: make backup name=xyz' && exit 1)
swampfox357
6

Ein mögliches Problem mit den gegebenen Antworten ist bisher, dass die Abhängigkeitsreihenfolge in make nicht definiert ist. Zum Beispiel:

make -j target

Wenn targeteinige Abhängigkeiten vorliegen, kann nicht garantiert werden, dass diese in einer bestimmten Reihenfolge ausgeführt werden.

Die Lösung hierfür (um sicherzustellen, dass ENV überprüft wird, bevor Rezepte ausgewählt werden) besteht darin, ENV während des ersten Durchgangs von make außerhalb eines Rezepts zu überprüfen:

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

Sie können sich über die verschiedenen Funktionen / Variablen informieren, die hier verwendet werden, und können $()nur explizit angeben, dass wir mit "nichts" vergleichen.

Hudon
quelle
6

Ich habe festgestellt, dass die beste Antwort nicht als Voraussetzung verwendet werden kann, außer für andere PHONY-Ziele. Wenn es als Abhängigkeit für ein Ziel verwendet wird, das eine tatsächliche Datei ist, erzwingt check-envdie Verwendung , dass dieses Dateiziel neu erstellt wird.

Andere Antworten sind global (z. B. ist die Variable für alle Ziele im Makefile erforderlich ) oder verwenden die Shell, z. B. wenn ENV fehlte, würde make unabhängig vom Ziel beendet.

Eine Lösung, die ich für beide Probleme gefunden habe, ist

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

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

.PHONY: build
build:
    echo "building"

Die Ausgabe sieht aus wie

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

value hat einige beängstigende Vorbehalte, aber für diese einfache Verwendung glaube ich, dass es die beste Wahl ist.

23jodys
quelle
5

Wie ich sehe, benötigt der Befehl selbst die Variable ENV, damit Sie sie im Befehl selbst überprüfen können:

.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
quelle
Das Problem dabei ist, dass dies deploynicht unbedingt das einzige Rezept ist, das diese Variable benötigt. Mit dieser Lösung muss ich den Status ENVfür jeden einzelnen testen ... während ich ihn als eine einzige (Art) Voraussetzung behandeln möchte.
Abernier
4

Ich weiß, dass dies alt ist, aber ich dachte, ich würde meine eigenen Erfahrungen für zukünftige Besucher einbringen, da es meiner Meinung nach etwas ordentlicher ist.

Typischerweise makewird verwenden shals Standardschale ( eingestellt über die spezielle SHELLVariable ). In shund seinen Derivaten, ist es trivial , um mit einer Fehlermeldung beendet , wenn eine Umgebungsvariable abzurufen , wenn es nicht gesetzt ist oder null , indem Sie: ${VAR?Variable VAR was not set or null}.

Wenn Sie dies erweitern, können Sie ein wiederverwendbares make-Ziel schreiben, mit dem andere Ziele fehlgeschlagen werden können, wenn keine Umgebungsvariable festgelegt wurde:

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

Bemerkenswerte Dinge:

  • Das maskierte Dollarzeichen ( $$) ist erforderlich, um die Erweiterung auf die Shell anstatt auf die Shell zu verschiebenmake
  • Die Verwendung von testdient nur dazu, zu verhindern, dass die Shell versucht, den Inhalt von auszuführen VAR(es dient keinem anderen wichtigen Zweck).
  • .check-env-varskann trivially zu überprüfen , um weitere Umgebungsvariablen erweitert werden, fügt die jeweils nur eine Zeile (z @test $${NEWENV?Please set environment variable NEWENV})
Lewis Belcher
quelle
Wenn ENVLeerzeichen enthält Dies scheint (zumindest für mich) nicht
eddiegroves
2

Sie können ifdefanstelle eines anderen Ziels verwenden.

.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
quelle
Hey, danke für deine Antwort, kann sie aber aufgrund des doppelten Codes, den dein Vorschlag generiert, nicht akzeptieren. Umso mehr deployist es nicht das einzige Rezept, das die ENVStatusvariable überprüfen muss .
Abernier
dann einfach umgestalten. Verwenden Sie die Anweisungen .PHONY: deployund deploy:vor dem ifdef-Block und entfernen Sie die Duplizierung. (Übrigens habe ich die Antwort bearbeitet, um die richtige Methode wiederzugeben)
Dwight Spencer