检查 Makefile 中是否存在程序

如何检查程序是否可以从 Makefile 调用?

(也就是说,程序应该存在于路径中,否则可以调用。)

例如,它可以用来检查安装了哪个编译器。

例如,类似于 这个问题的代码,但是没有假设底层 shell 是 POSIX 兼容的。

83987 次浏览

你就是这么做的吗?

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

归功于我的同事。

通过在另一个 makefile 目标中编译一个特殊的小程序解决了这个问题,该目标的唯一目的是检查我正在寻找的任何运行时内容。

然后,我在另一个 makefile 目标中调用了这个程序。

如果我没记错的话是这样的:

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


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

我的解决方案涉及一个小型 helper script1,如果所有必需的命令都存在,它会放置一个标志文件。这样做的好处是,对所需命令的检查只执行一次,而不会在每次 make调用时执行。

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

马克菲尔

.cmd_ok:
./check_cmds.sh


build: .cmd_ok target1 target2

更多关于 command -v技术的信息可以在 给你中找到。

使用 shell函数调用程序的方式是将输出打印到标准输出。例如,传递 --version

GNUMake 忽略传递给 shell的命令的退出状态。要避免潜在的“命令未找到”消息,请将标准错误重定向到 /dev/null

然后可以使用 ifdefifndef$(if)等检查结果。

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

另外,输出(比如程序版本)可能对 Makefile 的其他部分有用。

有时候,您需要一个 Makefile 才能够在不同的目标操作系统上运行,如果所需的可执行文件不在 PATH中,您希望构建提前失败,而不是在失败之前运行可能很长时间。

工程师川提供的优秀解决方案需要制作 目标。然而,如果你有很多可执行程序要测试,你的 Makefile 有很多独立的目标,每个目标都需要测试,那么每个目标都需要测试目标作为一个依赖项。当您一次创建多个目标时,这将导致大量额外的输入和处理时间。

谢谢提供的解决方案可以在不制定目标的情况下测试可执行文件。当有多个目标可以单独或一起构建时,这可以节省大量的输入和执行时间。

我对后一种解决方案的改进是使用 which可执行文件(Windows 中的 where) ,而不是依赖于在每个可执行文件中都有一个 --version选项,直接在 GNU Make ifeq指令中,而不是定义一个新的变量,如果所需的可执行文件不在 ${PATH}中,则使用 GNU Make error函数停止构建。例如,要测试 lzop可执行文件:

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

如果有几个可执行文件需要检查,那么可能需要对 which可执行文件使用 foreach函数:

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

注意,为了强制立即计算 RHS 表达式,需要使用 :=赋值运算符。如果你的 Makefile 改变了 PATH,那么你需要:

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

这样你的输出应该类似于:

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

您可以使用 bash 构建的命令,如 type foocommand -v foo,如下所示:

SHELL := /bin/bash
all: check


check:
@type foo

其中 foo是您的程序/命令。如果您希望它保持静音,请重定向到 > /dev/null

我把@kenorb 和@0xF 的溶液混合,得到了这个:

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

它工作得很好,因为如果可执行文件不可用,“ command-v”不会打印任何内容,所以变量 DOT 永远不会被定义,只要您想在代码中检查它,就可以随时检查它。在这个例子中,我抛出了一个错误,但是如果需要的话,您可以做一些更有用的事情。

如果变量可用,“ command-v”执行打印命令路径的廉价操作,定义 DOT 变量。

清理了一些现有的解决方案。

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

如果您希望它更安静,可以排除 $(info ...)

这将 很快就会失败。没有目标需要。

对我来说,以上所有的答案都是基于 linux 的,而不是使用 Windows。我是新来的,所以我的方法可能不太理想。但是在 linux 和 windows 上对我都适用的完整例子是:

# 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

当我需要检测更多我可以使用的工具时,可以选择:

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

假设您有不同的目标和构建器,每个目标和构建器都需要另一组工具。 设置此类工具的列表,并将其视为强制检查其可用性的目标

例如:

make_tools := gcc md5sum gzip


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


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

解决方案检查 --versionSTDERR输出不工作的程序,打印他们的版本到 STDOUT而不是 STDERR。不要检查它们输出到 STDERRSTDOUT,而是检查程序返回代码。如果程序不存在,它的退出代码将始终是非零的。

#!/usr/bin/make -f
# https://stackoverflow.com/questions/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}

我个人定义了一个 require目标,它在所有其他目标之前运行。此目标只是一次运行所有需求的版本命令,并在命令无效时打印适当的错误消息。

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.

下面脚本的输出是

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