Biến Makefile là điều kiện tiên quyết


134

Trong Makefile, một deploycông thức cần một biến môi trường ENVđược thiết lập để tự thực thi chính xác, trong khi các công thức khác không quan tâm, ví dụ:

ENV = 

.PHONY: deploy hello

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

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

Làm cách nào để đảm bảo biến này được đặt, ví dụ: có cách nào để khai báo biến makefile này như một điều kiện tiên quyết của công thức triển khai, như:

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

?

Cảm ơn bạn.


Ý bạn là gì, "đảm bảo biến này được đặt"? Bạn có nghĩa là xác minh hoặc đảm bảo? Nếu nó không được thiết lập trước đó, nên makeđặt nó, hoặc đưa ra cảnh báo, hoặc tạo ra một lỗi nghiêm trọng?
Beta

1
Biến này phải được chỉ định bởi chính người dùng - vì anh ta là người duy nhất biết môi trường của mình (dev, prod ...) - ví dụ bằng cách gọi make ENV=devnhưng nếu anh ta quên ENV=dev, deploycông thức sẽ thất bại ...
abernier

Câu trả lời:


171

Điều này sẽ gây ra một lỗi nghiêm trọng nếu ENVkhông được xác định và một cái gì đó cần nó (trong GNUMake, dù sao).

.PHONY: triển khai kiểm tra-env

triển khai: check-env
	...

other-thing-that-cần-env: check-env
	...

kiểm tra-env:
ENV
	$ (lỗi ENV không xác định)
endif

(Lưu ý rằng ifndef và endif không được thụt lề - chúng kiểm soát những gì tạo ra "nhìn thấy" , có hiệu lực trước khi Makefile được chạy. "$ (Lỗi" được thụt vào bằng một tab để nó chỉ chạy trong ngữ cảnh của quy tắc.)


12
Tôi nhận được ENV is undefinedkhi chạy một tác vụ không có check-env là điều kiện tiên quyết.
cơn mưa

@rane: Thật thú vị. Bạn có thể đưa ra một ví dụ hoàn chỉnh tối thiểu?
Beta

2
@rane là sự khác biệt trong không gian so với ký tự tab?
nhận

8
@ thừa nhận: Có; Tôi nên đã trả lời về điều này. Trong giải pháp của tôi, dòng bắt đầu bằng TAB, vì vậy đó là một lệnh trong check-envquy tắc; Làm cho sẽ không mở rộng nó trừ khi / cho đến khi thực hiện quy tắc. Nếu nó không bắt đầu bằng TAB (như trong ví dụ của @ rane), hãy diễn giải nó không theo quy tắc và đánh giá nó trước khi chạy bất kỳ quy tắc nào, bất kể mục tiêu.
Beta

1
`` `Trong giải pháp của tôi, dòng bắt đầu bằng TAB, vì vậy đó là một lệnh trong quy tắc check-env;` `` Bạn đang nói về dòng nào? Trong trường hợp của tôi, điều kiện if được đánh giá mọi lúc ngay cả khi dòng sau ifndef bắt đầu bằng TAB
Dhawal

103

Bạn có thể tạo một mục tiêu bảo vệ ngầm, kiểm tra xem biến trong thân được xác định, như sau:

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

Sau đó, bạn thêm một guard-ENVVARmục tiêu bất cứ nơi nào bạn muốn để xác nhận rằng một biến được xác định, như thế này:

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

Nếu bạn gọi make change-hostname, không thêm HOSTNAME=somehostnamecuộc gọi, bạn sẽ gặp lỗi và quá trình xây dựng sẽ thất bại.


5
Đó là một giải pháp thông minh, tôi thích nó :)
Elliot Chance

Tôi biết rằng đây là một câu trả lời cổ xưa, nhưng có lẽ ai đó vẫn đang xem nó nếu không tôi có thể đăng lại đây là một câu hỏi mới ... Tôi đang cố gắng thực hiện mục tiêu "bảo vệ" ẩn này để kiểm tra các biến môi trường và nó hoạt động về nguyên tắc, tuy nhiên, các lệnh trong quy tắc "Guard-%" thực sự được in ra trình bao. Điều này tôi muốn đàn áp. Sao có thể như thế được?
genomicsio

2
ĐỒNG Ý. tự mình tìm ra giải pháp ... @ ở đầu dòng lệnh quy tắc là bạn của tôi ...
genomicsio

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

4
đây nên là câu trả lời được chọn nó thực hiện sạch hơn.
sb32134

46

Biến thể nội tuyến

Trong tệp tạo tệp của tôi, tôi thường sử dụng một biểu thức như:

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

Những lý do:

  • nó là một lớp lót đơn giản
  • nó nhỏ gọn
  • Nó nằm gần các lệnh sử dụng biến

Đừng quên bình luận quan trọng để gỡ lỗi:

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

... buộc bạn phải tra cứu Makefile trong khi ...

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

... giải thích trực tiếp những gì sai

Biến thể toàn cầu (cho đầy đủ, nhưng không được hỏi)

Trên Makefile của bạn, bạn cũng có thể viết:

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

Cảnh báo:

  • không sử dụng tab trong khối đó
  • sử dụng cẩn thận: thậm chí cleanmục tiêu sẽ thất bại nếu ENV không được đặt. Nếu không, hãy xem câu trả lời của Hudon phức tạp hơn

Ồ Tôi đã gặp rắc rối với điều này cho đến khi tôi thấy "không sử dụng tab trong khối đó." Cảm ơn bạn!
Alex K

Đó là một lựa chọn tốt, nhưng tôi không thích rằng "thông báo lỗi" xuất hiện ngay cả khi thành công (toàn bộ dòng được in)
Jeff

@Jeff Đó là những điều cơ bản. Chỉ cần tiền tố dòng với a @. -> gnu.org/software/make/manual/make.html#Echoing
Daniel Alder

Tôi đã thử điều đó, nhưng sau đó thông báo lỗi sẽ không xuất hiện trong trường hợp thất bại. Hmm, tôi sẽ thử lại lần nữa. Nâng cao câu trả lời của bạn cho chắc chắn.
Jeff

1
Tôi thích cách tiếp cận thử nghiệm. Tôi đã sử dụng một cái gì đó như thế này:@test -n "$(name)" || (echo 'A name must be defined for the backup. Ex: make backup name=xyz' && exit 1)
swampfox357

6

Một vấn đề có thể xảy ra với các câu trả lời cho đến nay là thứ tự phụ thuộc trong thực hiện không được xác định. Ví dụ: đang chạy:

make -j target

khi targetcó một vài phụ thuộc không đảm bảo rằng chúng sẽ chạy theo bất kỳ thứ tự nào.

Giải pháp cho việc này (để đảm bảo rằng ENV sẽ được kiểm tra trước khi chọn công thức nấu ăn) là kiểm tra ENV trong lần thực hiện đầu tiên, bên ngoài bất kỳ công thức nào:

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

Bạn có thể đọc về các hàm / biến khác nhau được sử dụng ở đây$()chỉ là một cách để tuyên bố rõ ràng rằng chúng ta đang so sánh với "không có gì".


6

Tôi đã tìm thấy với câu trả lời tốt nhất không thể được sử dụng như một yêu cầu, ngoại trừ các mục tiêu PHONY khác. Nếu được sử dụng làm phụ thuộc cho mục tiêu là một tệp thực tế, sử dụngcheck-env sẽ buộc mục tiêu tệp đó được xây dựng lại.

Các câu trả lời khác là toàn cục (ví dụ: biến được yêu cầu cho tất cả các mục tiêu trong Makefile) hoặc sử dụng shell, ví dụ: nếu ENV bị thiếu, sẽ chấm dứt bất kể mục tiêu.

Một giải pháp tôi tìm thấy cho cả hai vấn đề là

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

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

.PHONY: build
build:
    echo "building"

Đầu ra trông như

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

value có một số cảnh báo đáng sợ, nhưng với cách sử dụng đơn giản này, tôi tin rằng đó là sự lựa chọn tốt nhất.


5

Như tôi thấy lệnh chính nó cần biến ENV để bạn có thể kiểm tra nó trong chính lệnh đó:

.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

Vấn đề với điều này deploykhông nhất thiết là công thức duy nhất cần biến này. Với giải pháp này, tôi phải kiểm tra trạng thái của ENVtừng người ... trong khi tôi muốn giải quyết nó như một điều kiện tiên quyết (sắp xếp) duy nhất.
abernier

4

Tôi biết điều này đã cũ, nhưng tôi nghĩ tôi sẽ hòa nhập với những trải nghiệm của riêng mình cho những vị khách tương lai, vì nó là IMHO gọn gàng hơn một chút.

Thông thường, makesẽ sử dụng shlàm vỏ mặc định của nó ( được đặt thông qua SHELLbiến đặc biệt ). Trong shvà các dẫn xuất của nó, việc thoát ra với một thông báo lỗi là không đáng kể khi truy xuất một biến môi trường nếu nó không được đặt hoặc null bằng cách thực hiện:${VAR?Variable VAR was not set or null} .

Mở rộng điều này, chúng ta có thể viết một mục tiêu tạo có thể tái sử dụng có thể được sử dụng để thất bại các mục tiêu khác nếu một biến môi trường không được đặt:

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

Những điều cần lưu ý:

  • Ký hiệu đô la thoát ( $$) được yêu cầu để trì hoãn việc mở rộng sang vỏ thay vì bên trongmake
  • Việc sử dụng testchỉ là để ngăn vỏ cố gắng thực thi nội dung của VAR(nó không phục vụ mục đích quan trọng nào khác)
  • .check-env-varscó thể được mở rộng một cách tầm thường để kiểm tra thêm các biến môi trường, mỗi biến chỉ thêm một dòng (ví dụ @test $${NEWENV?Please set environment variable NEWENV})

Nếu ENVchứa khoảng trắng, điều này dường như không thành công (ít nhất là đối với tôi)
eddiegroves

2

Bạn có thể sử dụng ifdefthay vì một mục tiêu khác.

.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

Xin chào, câu trả lời của bạn nhưng không thể chấp nhận vì mã trùng lặp mà đề xuất của bạn tạo ra ... tất cả nhiều hơn deploykhông phải là công thức duy nhất phải kiểm tra ENVbiến trạng thái.
abernier

sau đó chỉ cần tái cấu trúc. Sử dụng các câu lệnh .PHONY: deploydeploy:trước khối ifdef và loại bỏ trùng lặp. (btw Tôi đã chỉnh sửa câu trả lời để phản ánh đúng phương pháp)
Dwight Spencer
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.