Hủy bỏ tệp thực hiện nếu biến không được đặt


151

Làm cách nào tôi có thể hủy bỏ việc thực hiện make / makefile dựa trên biến của makefile không được đặt / giá trị?

Tôi đã đưa ra điều này, nhưng chỉ hoạt động nếu người gọi không chạy rõ ràng một mục tiêu (tức là makechỉ chạy ).

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)

Tôi nghĩ rằng một cái gì đó như thế này sẽ là một ý tưởng tốt, nhưng không tìm thấy bất cứ điều gì trong hướng dẫn sử dụng:

ifndef MY_FLAG
.ABORT
endif

Câu trả lời:


271

TL; DR : Sử dụng errorchức năng :

ifndef MY_FLAG
$(error MY_FLAG is not set)
endif

Lưu ý rằng các dòng không được thụt lề. Chính xác hơn, không có tab nào phải đi trước các dòng này.


Giải pháp chung

Trong trường hợp bạn sẽ kiểm tra nhiều biến, đáng để xác định hàm phụ trợ cho điều đó:

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

Và đây là cách sử dụng nó:

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


Điều này sẽ tạo ra một lỗi như thế này:

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

Ghi chú:

Kiểm tra thực sự được thực hiện ở đây:

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

Điều này phản ánh hành vi của ifndefđiều kiện, do đó một biến được xác định thành giá trị trống cũng được coi là "không xác định". Nhưng điều này chỉ đúng với các biến đơn giản và các biến đệ quy trống rỗng rõ ràng:

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

Theo đề xuất của @VictorSergienko trong các bình luận, một hành vi hơi khác có thể được mong muốn:

$(if $(value $1)kiểm tra nếu giá trị không trống. Đôi khi nó ổn nếu biến được xác định với giá trị trống . Tôi sẽ sử dụng$(if $(filter undefined,$(origin $1)) ...

Và:

Hơn nữa, nếu đó là một thư mục và nó phải tồn tại khi kiểm tra được chạy, tôi sẽ sử dụng $(if $(wildcard $1)). Nhưng sẽ là một chức năng khác.

Kiểm tra cụ thể mục tiêu

Cũng có thể mở rộng giải pháp để người ta có thể yêu cầu một biến chỉ khi một mục tiêu nhất định được gọi.

$(call check_defined, ...) từ bên trong công thức

Chỉ cần di chuyển kiểm tra vào công thức:

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

@Dấu hiệu hàng đầu tắt lệnh lặp lại và :là lệnh thực tế, còn sơ khai không có vỏ .

Hiển thị tên mục tiêu

Các check_definedchức năng có thể được cải thiện để còn ra tên mục tiêu (được cung cấp thông qua các $@biến):

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

Vì vậy, bây giờ một kiểm tra thất bại tạo ra một đầu ra được định dạng độc đáo:

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

check-defined-MY_FLAG mục tiêu đặc biệt

Cá nhân tôi sẽ sử dụng giải pháp đơn giản và dễ hiểu ở trên. Tuy nhiên, ví dụ, câu trả lời này gợi ý sử dụng một mục tiêu đặc biệt để thực hiện kiểm tra thực tế. Người ta có thể cố gắng khái quát hóa điều đó và xác định mục tiêu là một quy tắc mẫu ẩn:

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

Sử dụng:

foo :|check-defined-BAR

Lưu ý rằng điều check-defined-BARđược liệt kê là điều kiện tiên quyết chỉ đặt hàng ( |...).

Ưu điểm:

  • (có thể nói) một cú pháp rõ ràng hơn

Nhược điểm:

  • Người ta không thể chỉ định một thông báo lỗi tùy chỉnh
  • Chạy make -t(xem Thay vì thực thi Bí quyết ) sẽ làm ô nhiễm thư mục gốc của bạn với rất nhiều check-defined-...tệp. Đây là một nhược điểm đáng buồn của thực tế là các quy tắc mẫu không thể được khai báo.PHONY .

Tôi tin rằng, những hạn chế này có thể được khắc phục bằng cách sử dụng một số hack mở rộngeval ma thuật và thứ cấp , mặc dù tôi không chắc nó có giá trị hay không.


Những gì chính xác? Không bao giờ sử dụng Mac, mặc dù tôi đoán nó có cài đặt Make khác theo mặc định (ví dụ BSD Make thay vì GNU Make). Tôi đề nghị bạn kiểm tra make --versionnhư bước đầu tiên.
Eldar Abusalimov

1
Điều này dường như không hoạt động trong 3,81. Nó luôn luôn có lỗi, ngay cả khi biến được xác định (và có thể được lặp lại).
OrangeDog

Ah, bạn cần cấu trúc nó chính xác như trong bản sao được liên kết.
OrangeDog

1
@bibstha Tôi đã thêm các tùy chọn xuất hiện trong đầu, vui lòng đọc câu trả lời được cập nhật.
Eldar Abusalimov

2
Tôi sẽ thêm một lời giải thích rõ ràng (như tôi) rằng ifndef không cần phải thụt lề :) Tôi thấy mẹo đó ở một nơi khác và đột nhiên tất cả các lỗi của tôi đều có ý nghĩa.
helios

40

Sử dụng hàm shell test:

foo:
    test $(something)

Sử dụng:

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

3
Đây là những gì tôi đã sử dụng khi gặp vấn đề tương tự - cảm ơn, Messa! Tôi đã thực hiện hai sửa đổi nhỏ: 1) Tôi đã tạo một checkforsomethingmục tiêu chỉ có testtrong đó và foophụ thuộc vào điều đó, và 2) tôi đã thay đổi kiểm tra thành @if test -z "$(something)"; then echo "helpful error here"; exit 1; fithay thế. Điều đó đã cho tôi khả năng thêm một lỗi hữu ích và cho phép tôi đặt tên của mục tiêu mới thêm một chút chỉ dẫn về những gì đã sai.
Brian Gerard

Với cái này tôi nhận đượcMakefile:5: *** missing separator. Stop.
silgon

7
Để gọn nhẹ tôi đã sử dụng test -n "$(something) || (echo "message" ; exit 1)để tránh rõ ràng if.
dùng295691

@silgon bạn có thể thụt lề bằng cách sử dụng khoảng trắng thay vì tab.
eweb

9

Bạn có thể sử dụng IF để kiểm tra:

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

Kết quả:

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

[là một bí danh cho lệnh test, vì vậy đây là câu trả lời giống như @Messa ở trên. Điều này là nhỏ gọn hơn, mặc dù, và bao gồm việc tạo thông báo lỗi.
dùng295691

6

Sử dụng xử lý lỗi shell cho các biến unset (lưu ý gấp đôi $):

$ 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

Nếu bạn cần một thông báo lỗi tùy chỉnh, hãy thêm nó sau ?:

$ 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

Để đơn giản và ngắn gọn:

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

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

Với đầu ra:

$ make bar
Makefile:2: *** foo is undefined. Stop.
$ make bar foo="something"
echo "foo is $$foo"
foo is something
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.