Variabel makefile sebagai prasyarat

134

Dalam Makefile, deployresep membutuhkan variabel lingkungan ENVuntuk diatur agar dapat dieksekusi dengan benar, sedangkan yang lain tidak peduli, misalnya:

ENV = 

.PHONY: deploy hello

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

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

Bagaimana saya bisa memastikan variabel ini diatur, misalnya: apakah ada cara untuk mendeklarasikan variabel makefile ini sebagai prasyarat dari resep deploy, seperti:

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

?

Terima kasih.

lebih baik
sumber
Apa maksud Anda, "pastikan variabel ini disetel"? Apakah maksud Anda memverifikasi atau memastikan? Jika tidak diset sebelumnya, haruskah makemengaturnya, atau memberikan peringatan, atau menghasilkan kesalahan fatal?
Beta
1
Variabel ini harus ditentukan oleh pengguna sendiri - karena ia adalah satu-satunya yang tahu lingkungannya (dev, prod ...) - misalnya dengan menelepon make ENV=devtetapi jika ia lupa ENV=dev, deployresepnya akan gagal ...
abernier

Jawaban:

171

Ini akan menyebabkan kesalahan fatal jika ENVtidak terdefinisi dan sesuatu membutuhkannya (dalam GNUM, bagaimanapun juga).

.PHONY: deploy check-env

deploy: check-env
	...

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

check-env:
ifndef ENV
	$ (error ENV tidak terdefinisi)
berakhir jika

(Perhatikan bahwa jikandef dan endif tidak diindentasi - mereka mengontrol apa yang membuat "melihat" , mulai berlaku sebelum Makefile dijalankan. "$ (Kesalahan" diindentasi dengan tab sehingga hanya berjalan dalam konteks aturan.)

Beta
sumber
12
Saya mendapatkan ENV is undefinedketika menjalankan tugas yang tidak memiliki check-env sebagai prasyarat.
raine
@rane: Itu menarik. Bisakah Anda memberikan contoh lengkap minimal?
Beta
2
@rane adalah perbedaan spasi vs karakter tab?
esmit
8
@ KTT: Ya; Saya seharusnya menjawab tentang ini. Dalam solusi saya, baris dimulai dengan TAB, jadi itu perintah dalam check-envaturan; Make tidak akan memperluasnya kecuali / sampai menjalankan aturan. Jika itu tidak dimulai dengan TAB (seperti dalam contoh @ rane), Jadikan itu sebagai tidak dalam aturan, dan evaluasi sebelum menjalankan aturan apa pun, apa pun targetnya.
Beta
1
`` `Dalam solusi saya, baris dimulai dengan TAB, jadi ini adalah perintah dalam aturan check-env;` `` Baris mana yang sedang Anda bicarakan? Dalam kasus saya, kondisi if dievaluasi setiap kali bahkan ketika baris setelah ifndef dimulai dengan TAB
Dhawal
103

Anda dapat membuat target pelindung tersirat, yang memeriksa bahwa variabel di batang didefinisikan, seperti ini:

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

Anda kemudian menambahkan guard-ENVVARtarget di mana saja Anda ingin menegaskan bahwa suatu variabel didefinisikan, seperti ini:

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

Jika Anda menelepon make change-hostname, tanpa menambahkan HOSTNAME=somehostnamepanggilan, maka Anda akan mendapatkan kesalahan, dan build akan gagal.

Clayton Stanley
sumber
5
Itu solusi cerdas, saya menyukainya :)
Elliot Chance
Saya tahu bahwa ini adalah jawaban kuno, tetapi mungkin seseorang masih menontonnya kalau tidak saya mungkin memposting ulang ini sebagai pertanyaan baru ... Saya mencoba menerapkan "penjaga" target implisit ini untuk memeriksa variabel lingkungan yang ditetapkan dan berfungsi pada prinsipnya, namun perintah dalam aturan "guard-%" sebenarnya dicetak ke shell. Ini saya ingin menekan. Bagaimana ini mungkin?
genomicsio
2
BAIK. menemukan solusinya sendiri ... @ pada awal baris perintah aturan adalah teman saya ...
genomicsio
4
One-liner:: if [ -z '${${*}}' ]; then echo 'Environment variable $* not set' && exit 1; fiD
c24w
4
ini harus menjadi jawaban yang dipilih. ini merupakan implementasi yang lebih bersih.
sb32134
46

Varian sebaris

Di makefile saya, saya biasanya menggunakan ekspresi seperti:

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

Alasan:

  • ini sederhana
  • itu kompak
  • itu terletak dekat dengan perintah yang menggunakan variabel

Jangan lupa komentar yang penting untuk debugging:

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

... memaksa Anda untuk mencari Makefile sementara ...

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

... menjelaskan secara langsung apa yang salah

Varian global (untuk kelengkapan, tetapi tidak ditanyakan)

Di atas Makefile Anda, Anda juga bisa menulis:

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

Peringatan:

  • jangan gunakan tab di blok itu
  • gunakan dengan hati-hati: bahkan cleantarget akan gagal jika ENV tidak diatur. Kalau tidak, lihat jawaban Hudon yang lebih kompleks
Daniel Alder
sumber
Wow. Saya mengalami masalah dengan ini sampai saya melihat "jangan gunakan tab di blok itu." Terima kasih!
Alex K
Ini adalah alternatif yang baik, tetapi saya tidak suka bahwa "pesan kesalahan" muncul bahkan jika berhasil (seluruh baris dicetak)
Jeff
@ Jeff Itu dasar-dasar makefile. Awali saja baris dengan a @. -> gnu.org/software/make/manual/make.html#Echoing
Daniel Alder
Saya mencobanya, tetapi kemudian pesan kesalahan tidak akan muncul jika terjadi kegagalan. Hmm saya akan coba lagi. Jawaban Anda tervervikasi pasti.
Jeff
1
Saya suka pendekatan tes. Saya menggunakan sesuatu seperti ini:@test -n "$(name)" || (echo 'A name must be defined for the backup. Ex: make backup name=xyz' && exit 1)
swampfox357
6

Satu kemungkinan masalah dengan jawaban yang diberikan sejauh ini adalah bahwa urutan ketergantungan pada make tidak didefinisikan. Misalnya, menjalankan:

make -j target

ketika targetmemiliki beberapa dependensi tidak menjamin bahwa ini akan berjalan dalam urutan tertentu.

Solusi untuk ini (untuk memastikan bahwa ENV akan diperiksa sebelum resep dipilih) adalah dengan memeriksa ENV selama pass pertama make, di luar resep apa pun:

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

Anda dapat membaca tentang berbagai fungsi / variabel yang digunakan di sini dan $()hanya cara untuk secara eksplisit menyatakan bahwa kami membandingkan dengan "tidak ada".

Hudon
sumber
6

Saya menemukan bahwa jawaban terbaik tidak dapat digunakan sebagai persyaratan, kecuali untuk target PHONY lainnya. Jika digunakan sebagai ketergantungan untuk target yang merupakan file aktual, gunakancheck-env akan memaksa target file untuk dibangun kembali.

Jawaban lain bersifat global (mis. Variabel diperlukan untuk semua target di Makefile) atau menggunakan shell, mis. Jika ENV tidak ada, make akan berakhir tanpa mempedulikan target.

Solusi yang saya temukan untuk kedua masalah tersebut adalah

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

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

.PHONY: build
build:
    echo "building"

Outputnya seperti

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

value memiliki beberapa peringatan menakutkan, tetapi untuk penggunaan sederhana ini saya percaya itu adalah pilihan terbaik.

23jodys
sumber
5

Seperti yang saya lihat, perintah itu sendiri membutuhkan variabel ENV sehingga Anda dapat memeriksanya dalam perintah itu sendiri:

.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
sumber
Masalahnya adalah deployini bukan satu-satunya resep yang membutuhkan variabel ini. Dengan solusi ini, saya harus menguji keadaan ENVuntuk masing-masing ... sementara saya ingin menghadapinya sebagai prasyarat tunggal (semacam).
abernier
4

Saya tahu ini sudah tua, tapi saya pikir saya akan berpadu dengan pengalaman saya sendiri untuk pengunjung masa depan, karena itu IMHO sedikit lebih rapi.

Biasanya, makeakan digunakan shsebagai shell default ( ditetapkan melalui SHELLvariabel khusus ). Dish dan turunannya, sepele untuk keluar dengan pesan kesalahan saat mengambil variabel lingkungan jika tidak disetel atau dibatalkan dengan melakukan:${VAR?Variable VAR was not set or null} .

Memperluas ini, kita dapat menulis target make yang dapat digunakan kembali yang dapat digunakan untuk gagal target lain jika variabel lingkungan tidak ditetapkan:

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

Hal-hal yang perlu diperhatikan:

  • Tanda dolar yang lolos ($$ ) diperlukan untuk menunda ekspansi ke shell alih-alih di dalammake
  • Penggunaan test ini hanya untuk mencegah shell dari mencoba mengeksekusi konten VAR(tidak ada tujuan signifikan lainnya)
  • .check-env-varsdapat dengan mudah diperluas untuk memeriksa lebih banyak variabel lingkungan, yang masing-masing hanya menambahkan satu baris (misalnya @test $${NEWENV?Please set environment variable NEWENV})
Lewis Belcher
sumber
Jika ENVberisi spasi, ini tampaknya gagal (setidaknya untuk saya)
eddiegroves
2

Anda dapat menggunakan ifdefbukannya target yang berbeda.

.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
sumber
Hai, terima kasih atas jawaban Anda tetapi tidak dapat menerimanya karena kode duplikat yang dihasilkan saran Anda ... terlebih lagi deploybukan satu-satunya resep yang harus memeriksa ENVvariabel keadaan.
abernier
maka hanya refactor. Gunakan pernyataan .PHONY: deploydan deploy:sebelum blok ifdef dan hapus duplikasi. (btw saya sudah mengedit jawaban untuk mencerminkan metode yang benar)
Dwight Spencer