【问题标题】:Check if a program exists from a Makefile从 Makefile 检查程序是否存在
【发布时间】:2011-08-02 21:09:04
【问题描述】:

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

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

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

例如类似于 this question,但不假设底层 shell 兼容 POSIX。

【问题讨论】:

  • 你能调用 POSIX兼容的shell吗?
  • 可能不会,我想我可以要求一个人在那里,但如果我不必这样做会容易得多。
  • 与此同时,我通过向项目中添加一个程序来解决它,该程序首先构建,其唯一目的是检查其他程序...:-)
  • 传统的解决方法是使用automake 脚本检查各种先决条件并写出合适的Makefile

标签: makefile gnu-make


【解决方案1】:

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

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

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

我对后一种解决方案的改进是使用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

如果您要检查多个可执行文件,那么您可能希望将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.

【讨论】:

  • 如果您将 EXECUTABLES 所有变量(即 LS CC LD)生成并使用 $($(exec)),则可以将它们传递给环境或其他 makefile 无缝生成。交叉编译时很有用。
  • 运行 "ifeq (, $(shell which lzop))" 时,我的 make 阻塞在 "," =/
  • 在 Windows 上,使用 where my_exe 2>NUL 而不是 which
  • 当心带有 ifeq 的 TABS 缩进,这是一种规则语法!它必须没有标签。很难受。 stackoverflow.com/a/21226973/2118777
  • 如果您使用的是Bash,则无需外部调用which。请改用内置的command -vMore info.
【解决方案2】:

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

【讨论】:

  • 至少在某些系统(即 Ubuntu 14.04)中,$(shell command -v dot) 失败并显示make: command: Command not found。要解决此问题,请将 stderr 输出重定向到 /dev/null:$(shell command -v dot 2> /dev/null)Explanation
  • command 是一个 bash 内置,为了更好的可移植性,考虑使用which ?
  • @JulienPalard 我相信你弄错了:posix 需要command 实用程序(包括-v 选项)另一方面,which 没有标准化的行为(我知道),有时是彻头彻尾的unusable
  • command -v 需要类似 POSIX 的 shell 环境。这在没有 cygwin 或类似仿真的 Windows 上不起作用。
  • command 经常不起作用的原因是不是因为它的缺席,而是因为 GNU Make 的 peculiar optimization:如果一个命令是“足够简单”它将绕过外壳;不幸的是,这意味着内置插件有时无法工作,除非您稍微修改一下命令。
【解决方案3】:

这是你做的吗?

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

感谢我的同事。

【讨论】:

  • 不,我在一个目标中编译了一个程序并在另一个目标中运行它。
【解决方案4】:

使用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 的其他部分有用。

【讨论】:

  • 这会给出错误*** missing separator. Stop. 如果我在all: 之后的所有行中添加制表符,我会收到错误make: ifdef: Command not found
  • 不要在ifdefelseendif 行中添加标签。只需确保@echo 行以制表符开头。
  • 我在 Windows 和 Linux 上测试了我的解决方案。您链接的文档指出ifdef 检查非空变量值。如果你的变量不为空,它的计算结果是什么?
  • @Matthias009 当我测试 GNU Make v4.2.1 时,使用 ifdefifndef (如已接受的答案)仅在立即设置正在评估的变量(:=)时才有效懒惰设置 (=)。但是,使用立即设置变量的一个缺点是它们在声明时进行评估,而延迟设置变量在调用时进行评估。这意味着您将使用 := 为变量执行命令,即使 Make 只运行从不使用这些变量的规则!您可以通过使用 =ifneq ($(MY_PROG),) 来避免这种情况
【解决方案5】:

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

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 ...) 如果您想让它更安静,可以排除。

这将快速失败。不需要目标。

【讨论】:

    【解决方案6】:

    我的解决方案涉及一个小帮助脚本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
    

    生成文件

    .cmd_ok:
        ./check_cmds.sh
    
    build: .cmd_ok target1 target2
    

    1 有关command -v 技术的更多信息,请访问here

    【讨论】:

    • 我刚刚对其进行了测试,它可以工作,因为你没有提供关于什么不适合你的提示(bash 脚本或 Makefile 代码)或任何错误消息,我无法帮助你找到问题.
    • 我在第一个 if 语句中得到“意外的文件结尾”。
    【解决方案7】:

    我个人定义了一个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 'prerequisites' failed
    make: *** [prerequisites] Error 1
    

    【讨论】:

      【解决方案8】:

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

      【讨论】:

      • 我从没想过有人会使用带有可怕的 cmd.exe ...“shell”的 GNU Make。
      【解决方案9】:

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

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

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

      【讨论】:

      • 是的,但是当我只安装了 GNU make 但没有安装 bash 时,我希望它能够工作。
      【解决方案10】:

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

      例如:

      make_tools := gcc md5sum gzip
      
      $(make_tools):  
          @which $@ > /dev/null
      
      file.txt.gz: file.txt gzip
          gzip -c file.txt > file.txt.gz 
      

      【讨论】:

        【解决方案11】:

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

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

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

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

        【讨论】:

        • 谢谢。但这会中止,对吧?是否可以防止 make 中止?如果所需的工具不可用,我会尝试跳过构建步骤..
        • @coding.mof 已编辑 - 更像是这样。程序在运行时检查了一些东西,并根据编译的其余部分提供了信息。
        【解决方案12】:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-06-18
          • 2012-01-10
          • 2010-09-19
          • 2011-02-06
          • 2019-08-10
          • 1970-01-01
          相关资源
          最近更新 更多