ยกเลิก makefile หากไม่ได้ตั้งค่าตัวแปร


151

ฉันจะยกเลิกการดำเนินการ make / makefile ตามตัวแปรของ makefile ที่ไม่ได้ตั้งค่า / มีค่าได้อย่างไร

ฉันมากับสิ่งนี้ แต่ใช้ได้เฉพาะเมื่อผู้โทรไม่ได้เรียกใช้เป้าหมายอย่างชัดเจน (เช่นทำงานmakeเท่านั้น)

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)

ฉันคิดว่าสิ่งนี้จะเป็นความคิดที่ดี แต่ไม่พบอะไรในคู่มือของยี่ห้อ:

ifndef MY_FLAG
.ABORT
endif

คำตอบ:


271

TL; DR : ใช้errorฟังก์ชั่น :

ifndef MY_FLAG
$(error MY_FLAG is not set)
endif

โปรดทราบว่าบรรทัดจะต้องไม่เยื้อง แม่นยำยิ่งขึ้นไม่มีแท็บใดที่ต้องนำหน้าบรรทัดเหล่านี้


วิธีแก้ปัญหาทั่วไป

ในกรณีที่คุณจะทดสอบตัวแปรหลายตัวมันก็คุ้มค่าที่จะกำหนดฟังก์ชั่นเสริมสำหรับสิ่งนั้น:

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

และนี่คือวิธีการใช้งาน:

$(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)


สิ่งนี้จะทำให้เกิดข้อผิดพลาดเช่นนี้:

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

หมายเหตุ:

การตรวจสอบจริงเสร็จแล้วที่นี่:

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

สิ่งนี้สะท้อนถึงพฤติกรรมของifndefเงื่อนไขดังนั้นตัวแปรที่กำหนดให้กับค่าว่างจะถูกพิจารณาว่าเป็น "undefined" แต่นี่เป็นจริงสำหรับตัวแปรอย่างง่ายและตัวแปรแบบเรียกซ้ำที่ว่างเปล่าเท่านั้น:

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

ตามที่แนะนำโดย @VictorSergienko ในความคิดเห็นอาจมีพฤติกรรมที่แตกต่างออกไปเล็กน้อย:

$(if $(value $1)ทดสอบว่าค่าไม่ว่างเปล่า บางครั้งก็ตกลงถ้าตัวแปรถูกกำหนดด้วยค่าว่าง ฉันจะใช้$(if $(filter undefined,$(origin $1)) ...

และ:

นอกจากนี้ถ้าหากมันเป็นไดเรกทอรีและมันต้องมีอยู่$(if $(wildcard $1))เมื่อการันฉันต้องการใช้ แต่จะเป็นฟังก์ชั่นอื่น

การตรวจสอบเฉพาะเป้าหมาย

นอกจากนี้ยังเป็นไปได้ที่จะขยายโซลูชันเพื่อให้สามารถต้องการตัวแปรเฉพาะเมื่อเรียกใช้เป้าหมายที่แน่นอน

$(call check_defined, ...) จากภายในสูตร

เพียงแค่ย้ายเช็คไปที่สูตร:

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

ชั้นนำ@ผลัดกันลงชื่อสะท้อนคำสั่งและ:เป็นคำสั่งที่เกิดขึ้นจริงเปลือกไม่มี-op ต้นขั้ว

แสดงชื่อเป้าหมาย

check_definedฟังก์ชั่นได้ดีขึ้นนอกจากนี้ยังส่งออกชื่อเป้าหมาย (ให้ผ่าน$@ตัวแปร):

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 `$@')))

ดังนั้นตอนนี้การตรวจสอบที่ล้มเหลวจะสร้างผลลัพธ์ที่ได้รับการจัดรูปแบบอย่างดี:

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

check-defined-MY_FLAG เป้าหมายพิเศษ

โดยส่วนตัวแล้วฉันจะใช้วิธีที่ง่ายและตรงไปตรงมาข้างต้น อย่างไรก็ตามตัวอย่างเช่นคำตอบนี้แนะนำให้ใช้เป้าหมายพิเศษเพื่อทำการตรวจสอบจริง ใคร ๆ ก็สามารถพยายามที่จะพูดคุยเรื่องนั้นและกำหนดเป้าหมายเป็นกฎรูปแบบโดยนัย:

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

การใช้งาน:

foo :|check-defined-BAR

โปรดสังเกตว่าสิ่งที่check-defined-BARแสดงรายการเป็นสิ่งที่จำเป็นต้องมีเพื่อคำสั่งซื้ออย่างเดียว ( |...)

ข้อดี:

  • (เนื้อหา) ไวยากรณ์ที่สะอาดมากขึ้น

จุดด้อย:

  • ไม่มีใครสามารถระบุข้อความแสดงข้อผิดพลาดที่กำหนดเองได้
  • การเรียกใช้make -t(ดูแทนการใช้สูตรอาหาร ) จะทำให้ไดเรกทอรีรากของคุณสกปรกด้วยcheck-defined-...ไฟล์จำนวนมาก นี่คืออุปสรรคที่น่าเศร้าของความจริงที่ว่ากฎรูปแบบที่ไม่สามารถประกาศ.PHONY

ฉันเชื่อว่าข้อ จำกัด เหล่านี้สามารถเอาชนะได้โดยใช้evalเวทมนตร์และแฮ็กการขยายตัวรองแม้ว่าฉันไม่แน่ใจว่ามันคุ้มค่า


อะไรกันแน่ ไม่เคยใช้ Mac แต่ฉันคิดว่ามันมีการติดตั้ง Make อื่นตามค่าเริ่มต้น (เช่น Make BSD แทนที่จะเป็น GNU Make) ฉันขอแนะนำให้คุณตรวจสอบmake --versionเป็นขั้นตอนแรก
Eldar Abusalimov

1
ดูเหมือนจะไม่สามารถใช้งานได้ใน 3.81 มันเป็นข้อผิดพลาดเสมอแม้ว่าจะมีการกำหนดตัวแปร (และสามารถสะท้อนได้)
OrangeDog

อาคุณต้องจัดโครงสร้างให้ถูกต้องเหมือนในสำเนาที่เชื่อมโยง
OrangeDog

1
@bibstha ฉันได้เพิ่มตัวเลือกที่คำนึงถึงโปรดอ่านคำตอบที่ปรับปรุงแล้ว
Eldar Abusalimov

2
ฉันต้องการเพิ่มการชี้แจงสำหรับ noobies (เช่นฉัน) ที่ ifndef ต้องไม่เยื้อง :) ฉันพบว่าเคล็ดลับที่อื่นและทันใดนั้นข้อผิดพลาดทั้งหมดของฉันทำให้รู้สึก
helios

40

ใช้ฟังก์ชั่นเปลือกtest:

foo:
    test $(something)

การใช้งาน:

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

3
นี่คือสิ่งที่ฉันใช้เมื่อต้องเผชิญกับปัญหาเดียวกัน - ขอบคุณ Messa! ฉันทำการแก้ไขเล็กน้อยสองครั้ง: 1) ฉันสร้างcheckforsomethingเป้าหมายที่มีเฉพาะtestในนั้นและfooขึ้นอยู่กับว่าและ 2) ฉันเปลี่ยนเช็คเป็น@if test -z "$(something)"; then echo "helpful error here"; exit 1; fiแทน นั่นทำให้ฉันมีความสามารถในการเพิ่มข้อผิดพลาดที่มีประโยชน์และทำให้ฉันสามารถตั้งชื่อของเป้าหมายใหม่ได้อีกเล็กน้อยบ่งบอกถึงสิ่งที่ผิดพลาดเช่นกัน
Brian Gerard

ด้วยสิ่งนี้ฉันได้รับMakefile:5: *** missing separator. Stop.
silgon

7
สำหรับความเป็นปึกแผ่นฉันใช้เพื่อหลีกเลี่ยงการอย่างชัดเจนtest -n "$(something) || (echo "message" ; exit 1) if
user295691

@silgon คุณอาจเยื้องโดยใช้ช่องว่างแทนแท็บ
eweb

9

คุณสามารถใช้ IF เพื่อทดสอบ:

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

ผลลัพธ์:

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

[เป็นนามแฝงสำหรับคำสั่งtestดังนั้นนี่คือคำตอบเดียวกับ @Messa ด้านบน สิ่งนี้มีขนาดกะทัดรัดกว่าและรวมถึงการสร้างข้อความแสดงข้อผิดพลาด
user295691

6

ใช้การจัดการข้อผิดพลาดของเชลล์สำหรับตัวแปรที่ไม่ได้ตั้งค่า (จดบันทึกคู่$):

$ 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

หากคุณต้องการข้อความแสดงข้อผิดพลาดที่กำหนดเองให้เพิ่มไว้หลัง?:

$ 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

เพื่อความง่ายและกระชับ:

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

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

ด้วยผลลัพธ์:

$ make bar
Makefile:2: *** foo is undefined. Stop.
$ make bar foo="something"
echo "foo is $$foo"
foo is something
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.