从Makefile检查程序是否存在

我怎样才能检查一个程序是否可以从Makefile中调用?

(也就是说,程序应该存在于path中或者可以被调用。)

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

例如像这个问题 ,但没有假设底层shell是POSIX兼容的。

我混合@kenorb和@ 0xF的解决scheme,得到这个:

 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”不会打印任何东西,所以variablesDOT永远不会被定义,只要在代码中检查就可以了。 在这个例子中,我抛出一个错误,但是如果你愿意,你可以做一些更有用的事情。

如果variables可用,则“command -v”执行打印命令path的廉价操作,定义DOTvariables。

这是你做的?

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

信贷给我的同事。

有时候你需要一个Makefile才能够在不同的目标操作系统上运行,并且如果所需的可执行文件不在PATH而且希望在发生故障之前运行很长时间,那么你希望构build早期失败。

工程师提供的优秀解决scheme需要制定目标 。 但是,如果您有许多可执行文件需要testing,并且您的makefile有许多独立的目标,而每个目标都需要testing,那么每个目标都需要testing目标作为依赖项。 这使得一次只能创build一个以上的目标时需要大量额外的input以及处理时间。

由0xf提供的解决scheme可以testing一个可执行文件而无需制定目标。 这样可以节省大量的打字和执行时间,当有多个目标可以单独或一起构build时。

我对后一种解决scheme的改进是使用which可执行文件(在Windows中),而不是依靠每个可执行文件中都有一个--version选项,直接在GNU Make ifeq指令中,而不是定义一个新的variables,并且如果所需的可执行文件不在${PATH} ,则使用GNU Make error函数来停止构build。 例如,要testinglzop可执行文件:

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

请注意,为了强制立即评估RHSexpression式,需要使用:=赋值运算符。 如果你的Makefile改变了PATH ,那么你将不需要上面的最后一行:

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

这应该给你类似的输出:

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

使用shell函数以某种方式调用程序,以便将其输出到标准输出。 例如,传递 – --version

GNU Make忽略了传递给shell的命令的退出状态。 为了避免潜在的“command not found”消息,将标准错误redirect到/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的其他部分有用。

我的解决scheme涉及一个小助手脚本1 ,如果所有必需的命令存在,放置一个标志文件。 这带来的好处是检查所需的命令只做一次,而不是每一次调用。

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关于command -v技术的更多信息可以在这里find。

清理了一些现有的解决scheme

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

通过在另一个makefile目标中编译一个特殊的小程序来解决,其唯一目的是检查我所寻找的任何运行时间的东西。

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

如果我记得正确的话就是这样的:

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

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

 SHELL := /bin/bash all: check check: @type foo 

foo是你的程序/命令。 如果你想让它保持沉默,redirect到> /dev/null