从 Makefile 检查程序是否存在

发布于 2024-10-31 06:40:52 字数 239 浏览 5 评论 0原文

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

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

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

例如,类似这个问题,但不假设底层 shell 与 POSIX 兼容。

How can I check if a program is callable from a Makefile?

(That is, the program should exist in the path or otherwise be callable.)

It could be used to check for which compiler is installed, for instance.

E.g. something like this question, but without assuming the underlying shell is POSIX compatible.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(15

小鸟爱天空丶 2024-11-07 06:40:53

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

例如:

make_tools := gcc md5sum gzip

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

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

Assume you have different targets and builders, each requires another set of tools.
Set a list of such tools and consider them as target to force checking their availability

For example:

make_tools := gcc md5sum gzip

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

file.txt.gz: file.txt gzip
    gzip -c file.txt > file.txt.gz 
第七度阳光i 2024-11-07 06:40:53

检查 --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}

The solutions checking for STDERR output of --version does not work for programs which print their version to STDOUT instead of STDERR. Instead of checking their output to STDERR or STDOUT, check for the program return code. If the program does not exist, its exit code will always be non zero.

#!/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}
白云悠悠 2024-11-07 06:40:53

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

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

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

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

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

Solved by compiling a special little program in another makefile target, whose sole purpose is to check for whatever runtime stuff I was looking for.

Then, I called this program in yet another makefile target.

It was something like this if I recall correctly:

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

checker: checker.c
    cc -o checker checker.c
聚集的泪 2024-11-07 06:40:53

有点晚了,但这是我对这个问题的看法,

CMD_NOT_FOUND = $(error $(1) is required for this rule)
CHECK_CMD = $(if $(shell command -v $(1)),,$(call CMD_NOT_FOUND,$(1)))

您可以在全局范围内应用它:

$(call CHECK_CMD,gcc)

或在特定规则内:

do_stuff:
    $(call CHECK_CMD,md5sum)

或在 for 循环内

REQUIREMENTS := gcc md5sum gzip
 
$(foreach req,$(REQUIREMENTS),$(call CHECK_CMD,$(req)))

A bit late but here is my take on that question

CMD_NOT_FOUND = $(error $(1) is required for this rule)
CHECK_CMD = $(if $(shell command -v $(1)),,$(call CMD_NOT_FOUND,$(1)))

you can either apply it globally:

$(call CHECK_CMD,gcc)

or within a specific rule:

do_stuff:
    $(call CHECK_CMD,md5sum)

or within a for loop

REQUIREMENTS := gcc md5sum gzip
 
$(foreach req,$(REQUIREMENTS),$(call CHECK_CMD,$(req)))
黎歌 2024-11-07 06:40:53

甚至后来,在寻找答案时来到这里

...但后来我尝试了其他东西(在Linux上)...

--8<----8<----8<--

zig := $(word 1,$(foreach var,$(subst :, ,$(PATH)),$(wildcard $(var)/zig)))

ifdef zig
$(info $(zig) defined)
endif

even later, got here while searching an answer

... but then I tried something else (on Linux) ...

--8<----8<----8<--

zig := $(word 1,$(foreach var,$(subst :, ,$(PATH)),$(wildcard $(var)/zig)))

ifdef zig
$(info $(zig) defined)
endif
岁月打碎记忆 2024-11-07 06:40:53

受到本主题以及 StackOverflow 和类似网站中其他答案的启发,我使用以下内容:

Makefile

ℹ️ (这只是 Makefile的顶部code>'si will create self...)

#   SPDX-License-Identifier: CC-BY-NC-SA-4.0
#
SHELL = /usr/bin/bash
.SHELLFLAGS = -ec
.ONESHELL:
.SILENT:

UTILS           :=
UTILS           += setfacl
UTILS           += chown
UTILS           += chmod
# UTILS         += nonexisting
include CheckUtils.mk
  1. 第 3 行定义要使用的 shell,您可以取消注释以使用默认的 sh
  2. 第 #9-12 行定义了要检查的实用程序。
  3. 第 13 行包含以下文件。

CheckUtils.mk

ℹ️ 关于突出显示的错误报告:https ://github.com/highlightjs/highlight.js/issues/3878

ℹ️ 此文件的最新版本可在我在 GitLab 的一个存储库中找到。

I use the following, inspired by other answers in this topic and StackOverflow and similar sites:

Makefile

ℹ️ (This is just the top of Makefile's i will create self...)

#   SPDX-License-Identifier: CC-BY-NC-SA-4.0
#
SHELL = /usr/bin/bash
.SHELLFLAGS = -ec
.ONESHELL:
.SILENT:

UTILS           :=
UTILS           += setfacl
UTILS           += chown
UTILS           += chmod
# UTILS         += nonexisting
include CheckUtils.mk
  1. Line #3 defines the shell to use, you can uncomment this to use the default sh.
  2. Lines #9-12 define the utils you want to be checked.
  3. Line #13 includes the below file.

CheckUtils.mk

ℹ️ Bug report wrt highlighting: https://github.com/highlightjs/highlight.js/issues/3878

ℹ️ The latest version of this file is available in one-of-my repo's at GitLab. ????????

#   SPDX-License-Identifier: CC-BY-NC-SA-4.0
#   Version: 2023-11-14
#
# This make-snippet will both check for the utils you define AND
#   automatically create an uppercase variable with the util name
#   for you to use in the rest of your Makefile(s).
#
# Usage:
#   The utils to be used should be put inside the UTILS var,
#   separated by space. Example:
#   UTILS   :=
#   UTILS   += setfacl
#   UTILS   += chown
#   UTILS   += chmod
#   # UTILS += nonexisting
#   include CheckUtils.mk
#
# Explanation of workings:
#   Lines #50-51, #86, #88:
#       For use in recursive make's.
#       Makes sure that the code inside this make-snippet is only applied ONCE.
#   Line #52:
#       Feedback header
#   Lines #54-60:
#       Creates a macro for uppercase conversion.
#       It auto-selects from two versions, which you can extend as needed.
#       The first, bash variant, uses ${VAR^^}.
#       The last, non-bash variant eg sh, uses sed.
#   Lines #64, #74:
#       Is to prevent checking and defining the SHELL itself,
#           but allow it to be displayed in the feedback. ????
#       (The shell is included as first util to check in line #63)
#   Lines #66-71:
#       Is to let make auto-create the respective variable(s) in uppercase
#           with the found executable as value.
#       The export in line #67 makes the created variable(s) available in
#           recursive make's without re-processing.
#   Line #72 in combo with #65/#73:
#       Aborts the make with an error if the requested util can not be found.
#   Lines #75-83:
#       Provide feedback about which util was found and where.
#       The output is nicely right-aligned when the length of the util-name
#           is <=20 (Line #78)
#   Lines #84-85:
#       Feedback footer
#
# Cross-posted-at:
#   https://stackoverflow.com/a/77223358/22510042

# For recorsive invocations.
ifndef CheckUtils
$(info ### START: Utils check)

# Define macro w.r.t. shell in use.
ifeq (bash,$(findstring bash,$(SHELL)))
toUpper = $(shell string="$(strip $1)"; printf "%s" ${string^^})
else
# Insipred by: https://stackoverflow.com/a/37660916/22510042
toUpper = $(shell echo "$(strip $1)" | sed 's/.*/\U&/')
endif

# Inspired by: https://stackoverflow.com/a/37197276/22510042
$(foreach util,shell $(UTILS),\
    $(if $(filter-out shell,$(util)),\
        $(if $(shell command -v $(util) 2>/dev/null),\
            $(eval $(strip \
                export \
                $(call toUpper,$(util) )\
                :=\
                $(shell command -v $(util) 2>/dev/null)\
            ))\
            ,$(error No '$(util)' in PATH)\
        )\
    )\
    $(info \
        $(shell \
            printf "%*s = '%s'" \
                20 \
                "$(call toUpper,$(util))" \
                "$($(call toUpper,$(util)))"\
        )\
    )\
)
$(info ### END: Utils check)
$(info ) # Empty line
export CheckUtils:=1    # Mark already applied for recorsive invocations.
# $(error END: Utils check, forced for debuging this snippet)
endif

Output

  • make without any changes will be similar to:
    ### START: Utils check
    SHELL   = /usr/bin/bash
    SETFACL = /usr/bin/setfacl
    CHOWN   = /usr/bin/chown
    CHMOD   = /usr/bin/chmod
    ### END: Utils check
    
    
  • With the nonexisting util uncommented. will be similar to:
    ### START: Utils check
    SHELL   = /usr/bin/bash
    SETFACL = /usr/bin/setfacl
    CHOWN   = /usr/bin/chown
    CHMOD   = /usr/bin/chmod
    Makefile:21: *** No 'nonexisting' in PATH.  Stop.
    

Hope this will be useful to others, it will at least serve as a backup for myself ????

那小子欠揍 2024-11-07 06:40:52

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

engineerchuan提供的优秀解决方案需要制定一个目标。但是,如果您有许多可执行文件要测试,并且您的 Makefile 有许多独立的目标,每个目标都需要测试,那么每个目标都需要测试目标作为依赖项。当您一次创建多个目标时,这会导致大量额外的打字和处理时间。

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

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

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

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

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.

Sometimes you need a Makefile to be able to run on different target OS's and you want the build to fail early if a required executable is not in PATH rather than to run for a possibly long time before failing.

The excellent solution provided by engineerchuan requires making a target. However, if you have many executables to test and your Makefile has many independent targets, each of which requires the tests, then each target requires the test target as a dependency. That makes for a lot of extra typing as well as processing time when you make more than one target at a time.

The solution provided by 0xf can test for an executable without making a target. That saves a lot of typing and execution time when there are multiple targets that can be built either separately or together.

My improvement to the latter solution is to use the which executable (where in Windows), rather than to rely on there being a --version option in each executable, directly in the GNU Make ifeq directive, rather than to define a new variable, and to use the GNU Make error function to stop the build if a required executable is not in ${PATH}. For example, to test for the lzop executable:

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

If you have several executables to check, then you might want to use a foreach function with the which executable:

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

Note the use of the := assignment operator that is required in order to force immediate evaluation of the RHS expression. If your Makefile changes the PATH, then instead of the last line above you will need:

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

This should give you output similar to:

ads$ make
Makefile:5: *** "No dudu in PATH.  Stop.
蘸点软妹酱 2024-11-07 06:40:52

我混合了 @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 

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

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

I mixed the solutions from @kenorb and @0xF and got this:

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 

It works beautifully because "command -v" doesn't print anything if the executable is not available, so the variable DOT never gets defined and you can just check it whenever you want in your code. In this example I'm throwing an error, but you could do something more useful if you wanted.

If the variable is available, "command -v" performs the inexpensive operation of printing the command path, defining the DOT variable.

情话墙 2024-11-07 06:40:52

这是你做的吗?

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

归功于我的同事。

is this what you did?

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

credit to my coworker.

再见回来 2024-11-07 06:40:52

使用 shell 函数以将某些内容打印到标准输出的方式调用您的程序。例如,传递 --version

GNU Make 忽略传递给 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 的其他部分有用。

Use the shell function to call your program in a way that it prints something to standard output. For example, pass --version.

GNU Make ignores the exit status of the command passed to shell. To avoid the potential "command not found" message, redirect standard error to /dev/null.

Then you may check the result using ifdef, ifndef, $(if) etc.

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

As a bonus, the output (such as program version) might be useful in other parts of your Makefile.

谁人与我共长歌 2024-11-07 06:40:52

在这里清理了一些现有的解决方案...

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

这将快速失败。无需目标。

Cleaned up some of the existing solutions here...

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

The $(info ...) you can exclude if you want this to be quieter.

This will fail fast. No target required.

苦笑流年记忆 2024-11-07 06:40:52

我个人定义了一个 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

I am personally defining a require target which runs before all the others. This target simply runs the version commands of all requirements one at a time and prints appropriate error messages if the command is invalid.

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.

The output of the below script is

Checking the programs required for the build are installed...
ERROR: derplerp is required.
makefile:X: recipe for target 'require' failed
make: *** [require] Error 1
笨死的猪 2024-11-07 06:40:52

我的解决方案涉及一个小帮助程序脚本1,如果所有必需的命令都存在,它会放置一个标志文件。这样做的好处是,对所需命令的检查仅执行一次,而不是在每次 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

Makefile

.cmd_ok:
    ./check_cmds.sh

build: .cmd_ok target1 target2

1 有关 command -v 技术的更多信息,请参见 此处

My solution involves a little helper script1 that places a flag file if all required commands exist. This comes with the advantage that the check for the required commands is only done once and not on every make invocation.

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 More about the command -v technique can be found here.

千紇 2024-11-07 06:40:52

对我来说,以上所有答案都基于 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})        

For me all above answers are based on linux and are not working with windows. I'm new to make so my approach may not be ideal. But complete example that works for me on both linux and windows is this:

# 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

optionally when I need to detect more tools I can use:

EXECUTABLES = ls dd 
K := $(foreach myTestCommand,$(EXECUTABLES),\
        $(if $(shell ${WHICH} $(myTestCommand) 2>${DEVNUL} ),\
            $(myTestCommand) found,\
            $(error "No $(myTestCommand) in PATH)))
$(info ${K})        
余罪 2024-11-07 06:40:52

您可以使用 bash 内置命令,例如 type foocommand -v foo,如下所示:

SHELL := /bin/bash
all: check

check:
        @type foo

其中 foo 是您的程序/命令。重定向到 > /dev/null 如果你想让它保持安静。

You can use bash built commands such as type foo or command -v foo, as below:

SHELL := /bin/bash
all: check

check:
        @type foo

Where foo is your program/command. Redirect to > /dev/null if you want it silent.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文