从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
。
然后你可以使用ifdef
, ifndef
, $(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 foo
或command -v foo
,如下所示:
SHELL := /bin/bash all: check check: @type foo
foo
是你的程序/命令。 如果你想让它保持沉默,redirect到> /dev/null
。