Makefile variabile come prerequisito


134

In un Makefile, una deployricetta necessita di una variabile d'ambiente ENVda impostare per eseguire correttamente se stessa, mentre ad altri non importa, ad es .:

ENV = 

.PHONY: deploy hello

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

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

Come posso assicurarmi che questa variabile sia impostata, ad esempio: esiste un modo per dichiarare questa variabile makefile come prerequisito della ricetta deploy, come:

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

?

Grazie.


Cosa intendi con "assicurati che questa variabile sia impostata"? Intendi verificare o garantire? Se non è stato impostato prima, dovrebbe makeimpostarlo o dare un avviso o generare un errore irreversibile?
Beta

1
Questa variabile deve essere specificata dall'utente stesso - poiché è l'unico che conosce il suo ambiente (dev, prod ...) - ad esempio chiamando make ENV=devma se si dimentica di farlo ENV=dev, la deployricetta fallirà ...
abernier

Risposte:


171

Questo causerà un errore fatale se ENVnon definito e qualcosa ne ha bisogno (in GNUMake, comunque).

.PHONY: distribuire check-env

deploy: check-env
	...

altra cosa che ha bisogno di env: check-env
	...

check-ENV:
ifndef ENV
	$ (errore ENV non definito)
finisci se

(Nota che ifndef e endif non sono rientrati: controllano ciò che rende "vede" , che ha effetto prima che il Makefile venga eseguito. "$ (Errore" è rientrato con una scheda in modo che venga eseguito solo nel contesto della regola.)


12
Ricevo ENV is undefinedquando eseguo un'attività che non ha check-env come prerequisito.
raine,

@rane: è interessante. Puoi dare un esempio minimo completo?
Beta

2
@rane è la differenza di spazi rispetto a un carattere di tabulazione?
ammesso il

8
@esmit: Sì; Avrei dovuto rispondere al riguardo. Nella mia soluzione, la linea inizia con una TAB, quindi è un comando nella check-envregola; Make non lo espande a meno che / fino a quando non si esegue la regola. Se non inizia con un TAB (come nell'esempio di @ rane), Make lo interpreta come non presente in una regola e lo valuta prima di eseguire qualsiasi regola, indipendentemente dalla destinazione.
Beta

1
`` Nella mia soluzione, la linea inizia con un TAB, quindi è un comando nella regola check-env; `` Di quale linea stai parlando? Nel mio caso, la condizione if viene valutata ogni volta anche quando la riga dopo ifndef inizia con TAB
Dhawal

103

È possibile creare un target guard implicito, che controlla che la variabile nello stelo sia definita, in questo modo:

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

Quindi aggiungi una guard-ENVVARdestinazione ovunque desideri affermare che una variabile è definita, in questo modo:

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

Se chiami make change-hostname, senza aggiungere HOSTNAME=somehostnamela chiamata, otterrai un errore e la compilazione fallirà.


5
È una soluzione intelligente, mi piace :)
Elliot Chance,

So che si tratta di una risposta antica, ma forse qualcuno la sta ancora osservando, altrimenti potrei ripubblicarla come nuova domanda ... Sto cercando di implementare questa "protezione" target implicita per verificare la presenza di variabili d'ambiente impostate e funziona in linea di principio, tuttavia, i comandi nella regola "guard-%" sono effettivamente stampati sulla shell. Questo vorrei sopprimerlo. Com'è possibile?
genomicsio

2
OK. ho trovato la soluzione da solo ... @ all'inizio delle righe di comando della regola è mio amico ...
genomicsio

4
One-liner:: if [ -z '${${*}}' ]; then echo 'Environment variable $* not set' && exit 1; fiD
c24w

4
questa dovrebbe essere la risposta selezionata. è un'implementazione più pulita.
sb32134

46

Variante in linea

Nei miei makefile, normalmente uso un'espressione come:

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

Le ragioni:

  • è un semplice one-liner
  • è compatto
  • si trova vicino ai comandi che usano la variabile

Non dimenticare il commento che è importante per il debug:

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

... ti costringe a cercare il Makefile mentre ...

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

... spiega direttamente cosa c'è che non va

Variante globale (per completezza, ma non richiesta)

Oltre al tuo Makefile, puoi anche scrivere:

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

Avvertenze:

  • non utilizzare la scheda in quel blocco
  • usare con cura: anche il cleantarget fallirà se ENV non è impostato. Altrimenti vedi la risposta di Hudon che è più complessa

Wow. Ho avuto problemi con questo fino a quando ho visto "non usare la scheda in quel blocco". Grazie!
Alex K,

È una buona alternativa, ma non mi piace che il "messaggio di errore" appaia anche se ha esito positivo (viene stampata l'intera riga)
Jeff

@Jeff Ecco le basi del makefile. Basta aggiungere il prefisso alla riga a @. -> gnu.org/software/make/manual/make.html#Echoing
Daniel Alder

L'ho provato, ma il messaggio di errore non verrà visualizzato in caso di errore. Hmm ci proverò di nuovo. Ho votato di sicuro la tua risposta.
Jeff

1
Mi piace l'approccio di prova. Ho usato qualcosa del genere:@test -n "$(name)" || (echo 'A name must be defined for the backup. Ex: make backup name=xyz' && exit 1)
swampfox357,

6

Un possibile problema con le risposte fornite finora è che l'ordine di dipendenza in make non è definito. Ad esempio, eseguendo:

make -j target

quando targetha alcune dipendenze non garantisce che verranno eseguite in un determinato ordine.

La soluzione per questo (per garantire che ENV verrà controllata prima della scelta delle ricette) è quella di verificare ENV durante il primo passaggio del make, al di fuori di qualsiasi ricetta:

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

Puoi leggere le diverse funzioni / variabili utilizzate qui ed $()è solo un modo per affermare esplicitamente che stiamo confrontando "niente".


6

Ho trovato con la migliore risposta non può essere utilizzato come requisito, ad eccezione di altri obiettivi PHONY. Se utilizzato come dipendenza per una destinazione che è un file effettivo, l'utilizzo check-envforzerà la ricostruzione di quella destinazione del file.

Altre risposte sono globali (ad es. La variabile è richiesta per tutti i target nel Makefile) o usano la shell, ad esempio se mancava ENV make finirebbe indipendentemente dal target.

Una soluzione che ho trovato per entrambi i problemi è

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

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

.PHONY: build
build:
    echo "building"

L'output è simile

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

value ha alcuni avvertimenti spaventosi, ma per questo semplice uso credo che sia la scelta migliore.


5

Come vedo il comando stesso ha bisogno della variabile ENV in modo da poterlo controllare nel comando stesso:

.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

Il problema è che deploynon è necessariamente l'unica ricetta che necessita di questa variabile. Con questa soluzione, devo testare lo stato di ENVognuno di uno ... mentre mi piacerebbe affrontarlo come un unico (tipo di) prerequisito.
abernier il

4

So che è vecchio, ma ho pensato di entrare in contatto con le mie esperienze per i futuri visitatori, dal momento che è un po 'più ordinato IMHO.

Tipicamente, makeuserà shcome shell predefinita ( impostata tramite la SHELLvariabile speciale ). In shed i suoi derivati, è banale per chiudere con un messaggio di errore durante il recupero una variabile d'ambiente, se non è impostato o nulla da fare: ${VAR?Variable VAR was not set or null}.

Estendendo questo, possiamo scrivere un target riutilizzabile che può essere usato per fallire altri target se non è stata impostata una variabile d'ambiente:

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

Cose da notare:

  • Il simbolo di dollaro con escape ( $$) è necessario per rinviare l'espansione alla shell anziché all'internomake
  • L'uso di testè solo per impedire alla shell di provare a eseguire il contenuto di VAR(non ha altri scopi significativi)
  • .check-env-varspuò essere banalmente esteso per verificare la presenza di più variabili d'ambiente, ognuna delle quali aggiunge solo una riga (ad es. @test $${NEWENV?Please set environment variable NEWENV})

Se ENVcontiene spazi questo sembra fallire (almeno per me)
eddiegroves

2

È possibile utilizzare ifdefinvece di un target diverso.

.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

Ehi, grazie per la tua risposta ma non puoi accettarla a causa del codice duplicato generato dal tuo suggerimento ... tanto più deploynon è l'unica ricetta che deve controllare la ENVvariabile di stato.
abernier il

quindi solo refactor. Utilizzare le istruzioni .PHONY: deploye deploy:prima del blocco ifdef e rimuovere la duplicazione. (tra l'altro ho modificato la risposta per riflettere il metodo corretto)
Dwight Spencer il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.