ตัวแปร Makefile เป็นข้อกำหนดเบื้องต้น


141

ใน Makefile deployสูตรอาหารต้องการตัวแปรสภาพแวดล้อมENVเพื่อตั้งค่าให้ดำเนินการเองอย่างถูกต้องในขณะที่คนอื่นไม่สนใจเช่น:

ENV = 

.PHONY: deploy hello

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

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

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

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

คุณหมายถึงอะไร "ตรวจสอบให้แน่ใจว่าได้ตั้งค่าตัวแปรนี้แล้ว" คุณหมายถึงยืนยันหรือแน่ใจ? หากไม่ได้ตั้งค่าไว้ก่อนควรmakeตั้งค่าหรือให้คำเตือนหรือสร้างข้อผิดพลาดร้ายแรง?
เบต้า

1
ตัวแปรนี้จะต้องมีการระบุโดยผู้ใช้เอง - ในขณะที่เขาเป็นเพียงคนเดียวที่รู้ว่าสภาพแวดล้อมของเขา (dev แยง ... ) - ตัวอย่างเช่นโดยการเรียกmake ENV=devแต่ถ้าเขาลืมENV=dev, deployสูตรจะล้มเหลว ...
abernier

คำตอบ:


179

สิ่งนี้จะทำให้เกิดข้อผิดพลาดร้ายแรงหากENVไม่ได้กำหนดไว้และมีบางอย่างที่ต้องการ (ใน GNUMake อย่างไรก็ตาม)

.PHONY: ปรับใช้ check-env

ปรับใช้: check-env
	...

อื่น ๆ สิ่งที่ต้องการ env: check-env
	...

ตรวจสอบ env:
ifndef ENV
	$ (ข้อผิดพลาด ENV ไม่ได้กำหนด)
endif

(โปรดทราบว่า ifndef และ endif จะไม่เยื้อง - พวกเขาควบคุมสิ่งที่ทำให้ "เห็น"มีผลก่อนที่จะเรียกใช้ Makefile "$ (error" จะเยื้องกับแท็บเพื่อให้ทำงานในบริบทของกฎเท่านั้น)


13
ฉันได้รับENV is undefinedเมื่อเรียกใช้งานที่ไม่มี check-env เป็นข้อกำหนดเบื้องต้น
ฝนตก

@rane: มันน่าสนใจ คุณสามารถยกตัวอย่างที่สมบูรณ์เล็กน้อยได้หรือไม่?
Beta

2
@rane ความแตกต่างของช่องว่างกับอักขระแท็บคืออะไร?
งด

8
@esmit: ใช่; ฉันควรจะตอบเกี่ยวกับเรื่องนี้ ในวิธีการแก้ปัญหาของฉันบรรทัดเริ่มต้นด้วย TAB ดังนั้นจึงเป็นคำสั่งในcheck-envกฎ Make จะไม่ขยายจนกว่า / จนกว่าจะดำเนินการตามกฎ หากไม่ได้ขึ้นต้นด้วย TAB (ตามตัวอย่างของ @ rane) ให้ตีความว่าไม่อยู่ในกฎและประเมินค่าก่อนที่จะเรียกใช้กฎใด ๆ โดยไม่คำนึงถึงเป้าหมาย
เบต้า

1
`` ในวิธีแก้ปัญหาของฉันบรรทัดเริ่มต้นด้วย TAB ดังนั้นจึงเป็นคำสั่งในกฎ check-env `` คุณกำลังพูดถึงบรรทัดไหน? ในกรณีของฉันเงื่อนไข if ถูกประเมินทุกครั้งแม้ว่าบรรทัดหลัง ifndef จะเริ่มต้นด้วย TAB
Dhawal

107

คุณสามารถสร้างเป้าหมายการป้องกันโดยนัยซึ่งจะตรวจสอบว่าตัวแปรใน Stem ถูกกำหนดไว้ดังนี้:

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

จากนั้นคุณเพิ่มguard-ENVVARเป้าหมายที่ใดก็ได้ที่คุณต้องการยืนยันว่ามีการกำหนดตัวแปรดังนี้:

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

หากคุณโทรmake change-hostnameโดยไม่เพิ่มHOSTNAME=somehostnameในการโทรคุณจะได้รับข้อผิดพลาดและการสร้างจะล้มเหลว


6
นั่นเป็นวิธีแก้ปัญหาที่ชาญฉลาดฉันชอบ :)
Elliot Chance

ฉันรู้ว่านี่เป็นการตอบกลับแบบโบราณ แต่บางทีอาจมีใครบางคนยังเฝ้าดูอยู่ไม่เช่นนั้นฉันอาจโพสต์คำถามนี้ซ้ำอีกครั้ง ... ฉันกำลังพยายามใช้ "ยาม" เป้าหมายโดยนัยนี้เพื่อตรวจสอบตัวแปรสภาพแวดล้อมที่ตั้งไว้และใช้งานได้ ตามหลักการแล้วอย่างไรก็ตามคำสั่งในกฎ "guard-%" จะถูกพิมพ์ลงในเชลล์ นี้ฉันขอระงับ เป็นไปได้อย่างไร?
genomicsio

2
ตกลง. พบวิธีแก้ปัญหาด้วยตัวเอง ... @ ที่จุดเริ่มต้นของบรรทัดคำสั่งกฎคือเพื่อนของฉัน ...
genomicsio

4
ซับเดียว: if [ -z '${${*}}' ]; then echo 'Environment variable $* not set' && exit 1; fiD
c24w

4
นี่ควรเป็นคำตอบที่เลือก มันเป็นการใช้งานที่สะอาดกว่า
sb32134

46

ตัวแปรแบบอินไลน์

ใน makefiles ของฉันฉันมักจะใช้นิพจน์เช่น:

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

เหตุผล:

  • มันเป็นซับเดียวง่ายๆ
  • มันกะทัดรัด
  • มันอยู่ใกล้กับคำสั่งที่ใช้ตัวแปร

อย่าลืมความคิดเห็นที่สำคัญสำหรับการแก้ไขข้อบกพร่อง:

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

... บังคับให้คุณค้นหา Makefile ในขณะที่ ...

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

... อธิบายโดยตรงว่ามีอะไรผิดปกติ

ตัวแปรสากล (เพื่อความสมบูรณ์ แต่ไม่ได้ถาม)

ที่ด้านบนของ Makefile คุณสามารถเขียน:

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

คำเตือน:

  • อย่าใช้แท็บในบล็อกนั้น
  • ใช้ด้วยความระมัดระวัง: แม้cleanเป้าหมายจะล้มเหลวหากไม่ได้ตั้งค่า ENV มิฉะนั้นให้ดูคำตอบของ Hudon ซึ่งซับซ้อนกว่า

ว้าว. ฉันประสบปัญหานี้จนกระทั่งเห็น "อย่าใช้แท็บในบล็อกนั้น" ขอบคุณ!
Alex K

เป็นทางเลือกที่ดี แต่ฉันไม่ชอบที่ "ข้อความแสดงข้อผิดพลาด" ปรากฏขึ้นแม้ว่าจะสำเร็จ (พิมพ์ทั้งบรรทัด)
Jeff

@ เจฟฟ์นั่นคือพื้นฐานของ makefile เพียงนำหน้าบรรทัดด้วย@. -> gnu.org/software/make/manual/make.html#Echoing
Daniel Alder

ฉันลองแล้ว แต่ข้อความแสดงข้อผิดพลาดจะไม่ปรากฏในกรณีที่ล้มเหลว ฉันจะลองอีกครั้ง เพิ่มคะแนนให้คำตอบของคุณอย่างแน่นอน
Jeff

1
ฉันชอบแนวทางการทดสอบ ฉันใช้อะไรแบบนี้:@test -n "$(name)" || (echo 'A name must be defined for the backup. Ex: make backup name=xyz' && exit 1)
swapfox357

6

เนื่องจากฉันเห็นว่าคำสั่งนั้นต้องการตัวแปร ENV ดังนั้นคุณสามารถตรวจสอบได้ในคำสั่งเอง:

.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

ปัญหาdeployคือไม่จำเป็นต้องเป็นสูตรอาหารเดียวที่ต้องการตัวแปรนี้ ด้วยวิธีแก้ปัญหานี้ฉันต้องทดสอบสถานะของENVแต่ละรายการ ... ในขณะที่ฉันต้องการจัดการกับมันเป็นสิ่งที่จำเป็นต้องมี
abernier

6

ปัญหาที่เป็นไปได้อย่างหนึ่งกับคำตอบที่ให้มาคือไม่มีการกำหนดลำดับการอ้างอิงในการสร้าง ตัวอย่างเช่นการเรียกใช้:

make -j target

เมื่อtargetมีการอ้างอิงน้อยไม่รับประกันว่าสิ่งเหล่านี้จะทำงานตามลำดับที่กำหนด

วิธีแก้ปัญหานี้ (เพื่อรับประกันว่า ENV จะได้รับการตรวจสอบก่อนที่จะเลือกสูตรอาหาร) คือการตรวจสอบ ENV ในระหว่างการส่งครั้งแรกนอกสูตรใด ๆ :

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

คุณสามารถอ่านเกี่ยวกับฟังก์ชัน / ตัวแปรต่างๆที่ใช้ที่นี่และ$()เป็นเพียงวิธีระบุอย่างชัดเจนว่าเรากำลังเปรียบเทียบกับ "ไม่มีอะไร"


6

ฉันพบว่าคำตอบที่ดีที่สุดไม่สามารถใช้เป็นข้อกำหนดได้ยกเว้นเป้าหมาย PHONY อื่น ๆ หากใช้เป็นการอ้างอิงสำหรับเป้าหมายที่เป็นไฟล์จริงการใช้check-envจะบังคับให้สร้างไฟล์นั้นใหม่

คำตอบอื่น ๆ เป็นแบบโกลบอล (เช่นตัวแปรจำเป็นสำหรับเป้าหมายทั้งหมดใน Makefile) หรือใช้เชลล์เช่นหากขาด ENV การสร้างจะยุติโดยไม่คำนึงถึงเป้าหมาย

วิธีแก้ปัญหาที่ฉันพบสำหรับปัญหาทั้งสองคือ

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

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

.PHONY: build
build:
    echo "building"

ผลลัพธ์มีลักษณะดังนี้

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

value มีข้อแม้ที่น่ากลัว แต่สำหรับการใช้งานง่าย ๆ นี้ฉันเชื่อว่าเป็นทางเลือกที่ดีที่สุด


4

ฉันรู้ว่ามันเก่า แต่ฉันคิดว่าฉันจะพูดคุยกับประสบการณ์ของตัวเองสำหรับผู้มาเยือนในอนาคตเพราะมันเป็น IMHO ที่ดูดีกว่าเล็กน้อย

โดยทั่วไปmakeจะใช้shเป็นเชลล์เริ่มต้น ( ตั้งค่าผ่านSHELLตัวแปรพิเศษ ) ในshและอนุพันธ์ของมันก็น่ารำคาญที่จะออกด้วยข้อผิดพลาดเมื่อเรียกตัวแปรสภาพแวดล้อมหากยังไม่ได้ตั้งค่าหรือ null${VAR?Variable VAR was not set or null}โดยการทำ:

เมื่อขยายสิ่งนี้เราสามารถเขียนเป้าหมายที่ใช้ซ้ำได้ซึ่งสามารถใช้เพื่อล้มเหลวเป้าหมายอื่น ๆ หากไม่ได้ตั้งค่าตัวแปรสภาพแวดล้อม:

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

สิ่งที่ควรทราบ:

  • เครื่องหมายดอลลาร์หลบหนี ($$จำเป็นต้อง ) เพื่อเลื่อนการขยายไปยังเชลล์แทนที่จะเป็นภายในmake
  • การใช้testเป็นเพียงเพื่อป้องกันไม่ให้เชลล์พยายามดำเนินการกับเนื้อหาของVAR(มันไม่มีจุดประสงค์สำคัญอื่นใด)
  • .check-env-varsสามารถขยายได้เล็กน้อยเพื่อตรวจสอบตัวแปรสภาพแวดล้อมเพิ่มเติมซึ่งแต่ละรายการจะเพิ่มเพียงบรรทัดเดียว (เช่น@test $${NEWENV?Please set environment variable NEWENV})

หากENVมีช่องว่างสิ่งนี้ดูเหมือนจะล้มเหลว (สำหรับฉันอย่างน้อย)
eddiegroves

2

คุณสามารถใช้ifdefแทนเป้าหมายอื่นได้

.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

สวัสดีขอบคุณสำหรับคำตอบของคุณ แต่ไม่สามารถยอมรับได้เนื่องจากรหัสซ้ำที่คำแนะนำของคุณสร้างขึ้น ... ยิ่งไปกว่าdeployนั้นไม่ใช่สูตรเดียวที่ต้องตรวจสอบENVตัวแปรสถานะ
abernier

จากนั้นเพียงแค่ refactor ใช้คำสั่ง.PHONY: deployand deploy:ก่อนบล็อก ifdef และลบการทำซ้ำ (btw ฉันแก้ไขคำตอบเพื่อสะท้อนวิธีการที่ถูกต้อง)
Dwight Spencer
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.