Kiểm tra xem chương trình có tồn tại từ Makefile không


115

Làm cách nào để kiểm tra xem một chương trình có thể gọi được từ Makefile không?

(Nghĩa là, chương trình phải tồn tại trong đường dẫn hoặc có thể gọi được.)

Ví dụ, nó có thể được sử dụng để kiểm tra trình biên dịch nào được cài đặt.

Ví dụ: một cái gì đó giống như câu hỏi này , nhưng không giả sử trình bao bên dưới tương thích với POSIX.


1
Bạn có thể gọi một shell tương thích với POSIX không?
reinierpost

Có lẽ là không, tôi đoán tôi có thể yêu cầu một người ở đó, nhưng sẽ dễ dàng hơn nhiều nếu tôi không phải làm vậy.
GS Falken

Trong khi đó, tôi giải quyết nó bằng cách thêm một chương trình cho dự án, được xây dựng đầu tiên, và có mục đích duy nhất là để kiểm tra cho rằng chương trình khác ... :-)
GS Falken

2
Cách giải quyết truyền thống là có một automaketập lệnh kiểm tra các điều kiện tiên quyết khác nhau và viết ra một tập lệnh phù hợp Makefile.
tripleee

Câu trả lời:


83

Đôi khi bạn cần một Makefile để có thể chạy trên các hệ điều hành mục tiêu khác nhau và bạn muốn quá trình xây dựng bị lỗi sớm nếu không có tệp thực thi được yêu cầu PATHthay vì chạy trong một thời gian dài trước khi thất bại.

Giải pháp tuyệt vời do kỹ sưchuan cung cấp đòi hỏi phải tạo ra một mục tiêu . Tuy nhiên, nếu bạn có nhiều tệp thực thi để kiểm tra và Makefile của bạn có nhiều mục tiêu độc lập, mỗi mục tiêu yêu cầu kiểm tra, thì mỗi mục tiêu yêu cầu mục tiêu kiểm tra như một phụ thuộc. Điều đó làm tăng thêm nhiều thời gian nhập cũng như xử lý khi bạn thực hiện nhiều mục tiêu cùng một lúc.

Giải pháp do 0xf cung cấp có thể kiểm tra tệp thực thi mà không cần tạo mục tiêu. Điều đó giúp tiết kiệm rất nhiều thời gian nhập và thực thi khi có nhiều mục tiêu có thể được xây dựng riêng biệt hoặc cùng nhau.

Cải tiến của tôi đối với giải pháp thứ hai là sử dụng whichtệp thực thi ( wheretrong Windows), thay vì dựa vào việc có một --versiontùy chọn trong mỗi tệp thực thi, trực tiếp trong ifeqchỉ thị GNU Make , thay vì xác định một biến mới và sử dụng GNU Make errorchức năng để dừng quá trình xây dựng nếu không có tệp thực thi bắt buộc ${PATH}. Ví dụ: để kiểm tra lzoptệp thực thi:

 ifeq (, $(shell which lzop))
 $(error "No lzop in $(PATH), consider doing apt-get install lzop")
 endif

Nếu bạn có một số tệp thực thi cần kiểm tra, thì bạn có thể muốn sử dụng một foreachhàm với whichtệp thực thi:

EXECUTABLES = ls dd dudu lxop
K := $(foreach exec,$(EXECUTABLES),\
        $(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH")))

Lưu ý việc sử dụng :=toán tử gán được yêu cầu để buộc đánh giá biểu thức RHS ngay lập tức. Nếu Makefile của bạn thay đổi PATH, thì thay vì dòng cuối cùng ở trên, bạn sẽ cần:

        $(if $(shell PATH=$(PATH) which $(exec)),some string,$(error "No $(exec) in PATH")))

Điều này sẽ cung cấp cho bạn đầu ra tương tự như:

ads$ make
Makefile:5: *** "No dudu in PATH.  Stop.

2
Nếu bạn tạo EXECUTABLES tất cả các biến, (tức là LS CC LD) và sử dụng $ ($ (execute)), bạn có thể chuyển chúng để tạo liền mạch từ môi trường hoặc các tệp trang điểm khác. Hữu ích khi biên dịch chéo.
rickfoosusa

1
Tôi làm cuộn cảm tại "" khi chạy "ifeq (, $ (vỏ mà lzop))" = /
mentatkgs

2
Trên Windows, sử dụng where my_exe 2>NULthay vì which.
Aaron Campbell

2
Cẩn thận với TABS thụt lề với ifeq, đó là một cú pháp quy tắc! nó phải KHÔNG CÓ TAB. Đã có một thời gian khó khăn với điều đó. stackoverflow.com/a/21226973/2118777
Pipo

2
Nếu bạn đang sử dụng Bash, không cần thực hiện cuộc gọi bên ngoài tới which. Sử dụng cài sẵn command -vthay thế. Thêm thông tin .
kurczynski

48

Tôi đã trộn các giải pháp từ @kenorb và @ 0xF và nhận được điều này:

DOT := $(shell command -v dot 2> /dev/null)

all:
ifndef DOT
    $(error "dot is not available please install graphviz")
endif
    dot -Tpdf -o pres.pdf pres.dot 

Nó hoạt động rất đẹp vì "command -v" không in ra bất cứ thứ gì nếu tệp thực thi không có sẵn, vì vậy biến DOT không bao giờ được xác định và bạn chỉ có thể kiểm tra nó bất cứ khi nào bạn muốn trong mã của mình. Trong ví dụ này, tôi đang mắc lỗi, nhưng bạn có thể làm điều gì đó hữu ích hơn nếu bạn muốn.

Nếu biến có sẵn, "command -v" thực hiện thao tác in đường dẫn lệnh không tốn kém, xác định biến DOT.


5
Ít nhất trong một số hệ thống (tức là Ubuntu 14.04), $(shell command -v dot)không thành công với make: command: Command not found. Để sửa lỗi này, đầu ra stderr chuyển hướng đến / dev / null: $(shell command -v dot 2> /dev/null). Giải thích
J0HN

3
commandlà một nội trang bash, để có thêm tính di động, hãy cân nhắc sử dụng which?
Julien Palard

10
@JulienPalard Tôi tin rằng bạn đang nhầm lẫn: các commandtiện ích được yêu cầu bởi posix (bao gồm các -vtùy chọn) Mặt khác whichđã không được chuẩn hóa bahaviour (mà tôi biết) và đôi khi hoàn toàn không sử dụng được
Gyom

3
command -vyêu cầu môi trường shell giống như POSIX. Điều này sẽ không hoạt động trên Windows nếu không có cygwin hoặc giả lập tương tự.
dolmen

10
Lý do tại sao commandthường không hoạt động không phải vì sự vắng mặt của nó , mà vì sự tối ưu hóa đặc biệt mà GNU Make thực hiện: nếu một lệnh "đủ đơn giản", nó sẽ bỏ qua trình bao; Điều này không may có nghĩa là các tích hợp sẵn đôi khi không hoạt động trừ khi bạn hiểu lệnh một chút.
Rufflewind

33

đây là những gì bạn đã làm?

check: PYTHON-exists
PYTHON-exists: ; @which python > /dev/null
mytarget: check
.PHONY: check PYTHON-exists

tín dụng cho đồng nghiệp của tôi.


Không, tôi đã biên dịch một chương trình trong một mục tiêu và chạy nó trong một mục tiêu khác.
GS Falken

21

Sử dụng shellhàm để gọi chương trình của bạn theo cách nó in một thứ gì đó ra đầu ra chuẩn. Ví dụ, vượt qua --version.

GNU Make bỏ qua trạng thái thoát của lệnh được chuyển đến shell. Để tránh thông báo "không tìm thấy lệnh" tiềm ẩn, hãy chuyển hướng lỗi tiêu chuẩn đến /dev/null.

Sau đó, bạn có thể kiểm tra kết quả bằng cách sử dụng ifdef, ifndef, $(if), vv

YOUR_PROGRAM_VERSION := $(shell your_program --version 2>/dev/null)

all:
ifdef YOUR_PROGRAM_VERSION
    @echo "Found version $(YOUR_PROGRAM_VERSION)"
else
    @echo Not found
endif

Như một phần thưởng, đầu ra (chẳng hạn như phiên bản chương trình) có thể hữu ích trong các phần khác của Makefile của bạn.


điều này sẽ cho lỗi *** missing separator. Stop.Nếu tôi thêm tab ở tất cả các dòng sau khi all:tôi nhận được lỗimake: ifdef: Command not found
Matthias 009

cũng: ifdefsẽ đánh giá thậm chí còn đúng nếu your_programkhông tồn tại gnu.org/software/make/manual/make.html#Conditional-Syntax
Matthias 009

1
Đừng thêm tab vào ifdef, else, endifdây chuyền. Chỉ cần đảm bảo các @echodòng bắt đầu bằng các tab.
0xF

Tôi đã thử nghiệm giải pháp của mình trên Windows và Linux. Tài liệu bạn đã liên kết cho biết sẽ ifdefkiểm tra giá trị biến không trống. Nếu biến của bạn không trống, nó sẽ đánh giá điều gì?
0xF

1
@ Matthias009 khi tôi kiểm tra GNU Make v4.2.1, việc sử dụng ifdefhoặc ifndef(như trong câu trả lời được chấp nhận) chỉ hoạt động nếu biến đang được đánh giá ngay lập tức được đặt ( :=) thay vì đặt ( =) lười biếng . Tuy nhiên, nhược điểm của việc sử dụng các biến đặt ngay là chúng được đánh giá tại thời điểm khai báo, trong khi các biến được đặt lười biếng được đánh giá khi chúng được gọi. Điều này có nghĩa là bạn sẽ thực hiện các lệnh cho các biến :=ngay cả khi Make chỉ chạy các quy tắc không bao giờ sử dụng các biến đó! Bạn có thể tránh điều này bằng cách sử dụng một =vớiifneq ($(MY_PROG),)
Dennis

9

Đã làm rõ một số giải pháp hiện có ở đây ...

REQUIRED_BINS := composer npm node php npm-shrinkwrap
$(foreach bin,$(REQUIRED_BINS),\
    $(if $(shell command -v $(bin) 2> /dev/null),$(info Found `$(bin)`),$(error Please install `$(bin)`)))

Các $(info ...)bạn có thể loại trừ nếu bạn muốn điều này để được yên tĩnh hơn.

Điều này sẽ thất bại nhanh chóng . Không cần mục tiêu.


8

Giải pháp của tôi liên quan đến một tập lệnh trợ giúp nhỏ 1 đặt tệp cờ nếu tất cả các lệnh bắt buộc tồn tại. Điều này có lợi thế là việc kiểm tra các lệnh được yêu cầu chỉ được thực hiện một lần và không phải trên mọi lệnh makegọi.

check_cmds.sh

#!/bin/bash

NEEDED_COMMANDS="jlex byaccj ant javac"

for cmd in ${NEEDED_COMMANDS} ; do
    if ! command -v ${cmd} &> /dev/null ; then
        echo Please install ${cmd}!
        exit 1
    fi
done

touch .cmd_ok

Makefile

.cmd_ok:
    ./check_cmds.sh

build: .cmd_ok target1 target2

1 Thông tin thêm về command -vkỹ thuật có thể được tìm thấy tại đây .


2
Tôi vừa thử nghiệm nó và nó hoạt động và vì bạn không cung cấp gợi ý về những gì không hoạt động cho bạn (tập lệnh bash hoặc mã Makefile) hoặc bất kỳ thông báo lỗi nào nên tôi không thể giúp bạn tìm ra sự cố.
Dòng chảy

Tôi nhận được "kết thúc tệp không mong muốn" trong ifcâu lệnh đầu tiên .
Adam Grant

6

Đối với tôi, tất cả các câu trả lời trên đều dựa trên linux và không hoạt động với windows. Tôi mới làm nên cách tiếp cận của tôi có thể không lý tưởng. Nhưng ví dụ hoàn chỉnh phù hợp với tôi trên cả linux và windows là:

# detect what shell is used
ifeq ($(findstring cmd.exe,$(SHELL)),cmd.exe)
$(info "shell Windows cmd.exe")
DEVNUL := NUL
WHICH := where
else
$(info "shell Bash")
DEVNUL := /dev/null
WHICH := which
endif

# detect platform independently if gcc is installed
ifeq ($(shell ${WHICH} gcc 2>${DEVNUL}),)
$(error "gcc is not in your system PATH")
else
$(info "gcc found")
endif

tùy chọn khi tôi cần phát hiện thêm các công cụ mà tôi có thể sử dụng:

EXECUTABLES = ls dd 
K := $(foreach myTestCommand,$(EXECUTABLES),\
        $(if $(shell ${WHICH} $(myTestCommand) 2>${DEVNUL} ),\
            $(myTestCommand) found,\
            $(error "No $(myTestCommand) in PATH)))
$(info ${K})        

Tôi chưa bao giờ nghĩ ai đó sẽ sử dụng GNU Make với "shell" cmd.exe ... đáng sợ.
Johan Boulé

4

Bạn có thể sử dụng các lệnh xây dựng cơ bản như type foohoặc command -v foo, như sau:

SHELL := /bin/bash
all: check

check:
        @type foo

fooChương trình / lệnh của bạn ở đâu . Chuyển hướng đến > /dev/nullnếu bạn muốn nó im lặng.


Có, nhưng vấn đề là tôi muốn nó hoạt động khi tôi chỉ có GNU tạo nhưng không có cài đặt bash.
GS Falken

3

Giả sử bạn có các mục tiêu và trình xây dựng khác nhau, mỗi mục tiêu yêu cầu một bộ công cụ khác. Đặt danh sách các công cụ đó và coi chúng là mục tiêu để buộc kiểm tra tính khả dụng của chúng

Ví dụ:

make_tools := gcc md5sum gzip

$(make_tools):  
    @which $@ > /dev/null

file.txt.gz: file.txt gzip
    gzip -c file.txt > file.txt.gz 

3

Cá nhân tôi đang xác định một requiremục tiêu chạy trước tất cả những mục tiêu khác. Mục tiêu này chỉ đơn giản là chạy các lệnh phiên bản của tất cả các yêu cầu tại một thời điểm và in các thông báo lỗi thích hợp nếu lệnh đó không hợp lệ.

all: require validate test etc

require:
    @echo "Checking the programs required for the build are installed..."
    @shellcheck --version >/dev/null 2>&1 || (echo "ERROR: shellcheck is required."; exit 1)
    @derplerp --version >/dev/null 2>&1 || (echo "ERROR: derplerp is required."; exit 1) 

# And the rest of your makefile below.

Đầu ra của tập lệnh dưới đây là

Checking the programs required for the build are installed...
ERROR: derplerp is required.
makefile:X: recipe for target 'prerequisites' failed
make: *** [prerequisites] Error 1

1

Được giải quyết bằng cách biên dịch một chương trình nhỏ đặc biệt trong một đích makefile khác, với mục đích duy nhất là kiểm tra bất kỳ thứ gì trong thời gian chạy mà tôi đang tìm kiếm.

Sau đó, tôi đã gọi chương trình này trong một đích makefile khác.

Nó là một cái gì đó như thế này nếu tôi nhớ chính xác:

real: checker real.c
    cc -o real real.c `./checker`

checker: checker.c
    cc -o checker checker.c

Cảm ơn bạn. Nhưng điều này sẽ hủy bỏ thực hiện, phải không? Có thể ngăn chặn việc phá thai không? Tôi đang cố gắng để bỏ qua một bước xây dựng nếu công cụ cần thiết không có sẵn ..
Marc-Christian Schulze

@ coding.mof đã chỉnh sửa - nó giống như vậy hơn. Chương trình đã kiểm tra một cái gì đó trong thời gian chạy và cung cấp thông tin tương ứng cho phần còn lại của biên dịch.
GS Falken

1

Các giải pháp kiểm tra STDERRđầu ra của --versionkhông hoạt động đối với các chương trình in phiên bản của chúng STDOUTthay vì STDERR. Thay vì kiểm tra đầu ra của chúng đến STDERRhoặc STDOUT, hãy kiểm tra mã trả về của chương trình. Nếu chương trình không tồn tại, mã thoát của nó sẽ luôn khác 0.

#!/usr/bin/make -f
# /programming/7123241/makefile-as-an-executable-script-with-shebang
ECHOCMD:=/bin/echo -e
SHELL := /bin/bash

RESULT := $(shell python --version >/dev/null 2>&1 || (echo "Your command failed with $$?"))

ifeq (,${RESULT})
    EXISTS := true
else
    EXISTS := false
endif

all:
    echo EXISTS: ${EXISTS}
    echo RESULT: ${RESULT}
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.