Batalkan makefile jika variabel tidak disetel


151

Bagaimana saya bisa membatalkan eksekusi make / makefile berdasarkan variabel makefile yang tidak disetel / dihargai?

Saya datang dengan ini, tetapi hanya berfungsi jika penelepon tidak secara eksplisit menjalankan target (yaitu makehanya berjalan ).

ifeq ($(MY_FLAG),)
abort:   ## This MUST be the first target :( ugly
    @echo Variable MY_FLAG not set && false
endif

all:
    @echo MY_FLAG=$(MY_FLAG)

Saya pikir sesuatu seperti ini akan menjadi ide yang bagus, tetapi tidak menemukan apa pun di manual make:

ifndef MY_FLAG
.ABORT
endif

Jawaban:


271

TL; DR : Gunakan errorfungsinya :

ifndef MY_FLAG
$(error MY_FLAG is not set)
endif

Perhatikan bahwa garis tidak boleh diindentasi. Lebih tepatnya, tidak ada tab yang harus mendahului garis-garis ini.


Solusi umum

Jika Anda akan menguji banyak variabel, ada baiknya mendefinisikan fungsi bantu untuk itu:

# Check that given variables are set and all have non-empty values,
# die with an error otherwise.
#
# Params:
#   1. Variable name(s) to test.
#   2. (optional) Error message to print.
check_defined = \
    $(strip $(foreach 1,$1, \
        $(call __check_defined,$1,$(strip $(value 2)))))
__check_defined = \
    $(if $(value $1),, \
      $(error Undefined $1$(if $2, ($2))))

Dan inilah cara menggunakannya:

$(call check_defined, MY_FLAG)

$(call check_defined, OUT_DIR, build directory)
$(call check_defined, BIN_DIR, where to put binary artifacts)
$(call check_defined, \
            LIB_INCLUDE_DIR \
            LIB_SOURCE_DIR, \
        library path)


Ini akan menghasilkan kesalahan seperti ini:

Makefile:17: *** Undefined OUT_DIR (build directory).  Stop.

Catatan:

Pemeriksaan sebenarnya dilakukan di sini:

$(if $(value $1),,$(error ...))

Ini mencerminkan perilaku ifndefkondisional, sehingga variabel yang didefinisikan sebagai nilai kosong juga dianggap "tidak terdefinisi". Tapi ini hanya berlaku untuk variabel sederhana dan variabel rekursif kosong secara eksplisit:

# ifndef and check_defined consider these UNDEFINED:
explicitly_empty =
simple_empty := $(explicitly_empty)

# ifndef and check_defined consider it OK (defined):
recursive_empty = $(explicitly_empty)

Seperti yang disarankan oleh @VictorSergienko dalam komentar, perilaku yang sedikit berbeda mungkin diinginkan:

$(if $(value $1)menguji apakah nilainya tidak kosong. Terkadang OK jika variabel didefinisikan dengan nilai kosong . Saya akan menggunakan$(if $(filter undefined,$(origin $1)) ...

Dan:

Selain itu, jika itu adalah direktori dan harus ada saat pemeriksaan dijalankan, saya akan menggunakan $(if $(wildcard $1)). Tetapi akan menjadi fungsi lain.

Pemeriksaan spesifik target

Dimungkinkan juga untuk memperluas solusi sehingga seseorang dapat memerlukan variabel hanya jika target tertentu dipanggil.

$(call check_defined, ...) dari dalam resep

Cukup pindahkan cek ke dalam resep:

foo :
    @:$(call check_defined, BAR, baz value)

Tanda terkemuka @mematikan perintah yang bergema dan :merupakan perintah aktual, sebuah shell no-op stub .

Menampilkan nama target

Itu check_defined fungsi dapat ditingkatkan untuk juga output nama sasaran (disediakan melalui $@variabel):

check_defined = \
    $(strip $(foreach 1,$1, \
        $(call __check_defined,$1,$(strip $(value 2)))))
__check_defined = \
    $(if $(value $1),, \
        $(error Undefined $1$(if $2, ($2))$(if $(value @), \
                required by target `$@')))

Jadi, sekarang cek yang gagal menghasilkan output yang diformat dengan baik:

Makefile:7: *** Undefined BAR (baz value) required by target `foo'.  Stop.

check-defined-MY_FLAG target khusus

Secara pribadi saya akan menggunakan solusi sederhana dan mudah di atas. Namun, misalnya, jawaban ini menyarankan menggunakan target khusus untuk melakukan pemeriksaan yang sebenarnya. Orang bisa mencoba menggeneralisasi itu dan mendefinisikan target sebagai aturan pola implisit:

# Check that a variable specified through the stem is defined and has
# a non-empty value, die with an error otherwise.
#
#   %: The name of the variable to test.
#   
check-defined-% : __check_defined_FORCE
    @:$(call check_defined, $*, target-specific)

# Since pattern rules can't be listed as prerequisites of .PHONY,
# we use the old-school and hackish FORCE workaround.
# You could go without this, but otherwise a check can be missed
# in case a file named like `check-defined-...` exists in the root 
# directory, e.g. left by an accidental `make -t` invocation.
.PHONY : __check_defined_FORCE
__check_defined_FORCE :

Pemakaian:

foo :|check-defined-BAR

Perhatikan bahwa check-defined-BARterdaftar sebagai pesanan saja|... prasyarat ( ).

Pro:

  • (bisa dibilang) sintaks yang lebih bersih

Cons:

  • Seseorang tidak dapat menentukan pesan kesalahan khusus
  • Menjalankan make -t(lihat Alih-alih Melaksanakan Resep ) akan mencemari direktori root Anda dengan banyak check-defined-...file. Ini adalah kelemahan menyedihkan dari kenyataan bahwa aturan pola tidak dapat dinyatakan.PHONY .

Saya percaya, keterbatasan ini dapat diatasi dengan menggunakan beberapa evalsulap ekspansi sihir dan sekunder , meskipun saya tidak yakin itu layak.


Apa tepatnya? Tidak pernah menggunakan Mac, meskipun saya rasa ia memiliki implementasi lain dari Make install secara default (mis. Make BSD bukan GNU Make). Saya sarankan Anda untuk memeriksa make --versionsebagai langkah pertama.
Eldar Abusalimov

1
Ini sepertinya tidak bekerja di make 3.81. Itu selalu kesalahan, bahkan jika variabel didefinisikan (dan dapat digaungkan).
OrangeDog

Ah, Anda harus menyusunnya dengan tepat seperti pada duplikat tertaut.
OrangeDog

1
@ bibstha Saya menambahkan opsi yang ada dalam pikiran, silakan baca jawaban yang diperbarui.
Eldar Abusalimov

2
Saya akan menambahkan klarifikasi-untuk-noobies (seperti saya) bahwa ifndef tidak perlu indentasi :) Saya menemukan tip itu di tempat lain dan tiba-tiba semua kesalahan saya masuk akal.
helios

40

Gunakan fungsi shell test:

foo:
    test $(something)

Pemakaian:

$ make foo
test 
Makefile:2: recipe for target 'foo' failed
make: *** [foo] Error 1
$ make foo something=x
test x

3
Inilah yang saya gunakan ketika menghadapi masalah yang sama - terima kasih, Messa! Saya membuat dua modifikasi kecil: 1) Saya membuat checkforsomethingtarget yang hanya memiliki testdi dalamnya dan membuat footergantung pada itu, dan 2) saya mengubah cek @if test -z "$(something)"; then echo "helpful error here"; exit 1; fisebagai gantinya. Itu memberi saya kemampuan untuk menambahkan kesalahan yang bermanfaat, dan memungkinkan saya untuk membuat nama target baru sedikit lebih mengindikasikan apa yang salah.
Brian Gerard

Dengan ini saya mendapatkanMakefile:5: *** missing separator. Stop.
silgon

7
Untuk kekompakan saya terbiasa test -n "$(something) || (echo "message" ; exit 1)menghindari yang eksplisit if.
user295691

@ silgon Anda mungkin indentasi menggunakan spasi daripada tab.
eweb

9

Anda dapat menggunakan IF untuk menguji:

check:
        @[ "${var}" ] || ( echo ">> var is not set"; exit 1 )

Hasil:

$ make check
>> var is not set
Makefile:2: recipe for target 'check' failed
make: *** [check] Error 1

[adalah alias untuk perintah test, jadi ini adalah jawaban yang sama dengan @Messa di atas. Ini lebih kompak, dan termasuk pembuatan pesan kesalahan.
user295691

6

Gunakan penanganan kesalahan shell untuk variabel tidak disetel (perhatikan ganda $):

$ cat Makefile
foo:
        echo "something is set to $${something:?}"

$ make foo
echo "something is set to ${something:?}"
/bin/sh: something: parameter null or not set
make: *** [foo] Error 127


$ make foo something=x
echo "something is set to ${something:?}"
something is set to x

Jika Anda memerlukan pesan kesalahan khusus, tambahkan setelah ?:

$ cat Makefile
hello:
        echo "hello $${name:?please tell me who you are via \$$name}"

$ make hello
echo "hello ${name:?please tell me who you are via \$name}"
/bin/sh: name: please tell me who you are via $name
make: *** [hello] Error 127

$ make hello name=jesus
echo "hello ${name:?please tell me who you are via \$name}"
hello jesus

2

Untuk kesederhanaan dan singkatnya:

$ cat Makefile
check-%:
        @: $(if $(value $*),,$(error $* is undefined))

bar:| check-foo
        echo "foo is $$foo"

Dengan output:

$ make bar
Makefile:2: *** foo is undefined. Stop.
$ make bar foo="something"
echo "foo is $$foo"
foo is something
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.