如何检查makefile中目标的存在

时间:2014-05-24 21:47:51

标签: makefile gnu-make

我想在shell中运行某些操作,具体取决于当前目录中的默认makefile是否包含某个目标。

#!/bin/sh
make -q some_target
if test $? -le 1 ; then
    true # do something
else
    false # do something else     
fi

这是有效的,因为如果目标不存在,GNU make返回错误代码2,否则返回0或1。问题是这种方式没有记录。这是男人的一部分:

-q, --question
        ``Question mode''.  Do not run any commands,  or  print  anything;
        just  return  an exit status that is zero if the specified targets
        are already up to date, nonzero otherwise.

仅区分零/非零。这样做的正确方法是什么?

5 个答案:

答案 0 :(得分:3)

您应该阅读the GNU make manual而不是手册页:手册页只是摘要而不是完整定义。手册说:

The exit status of make is always one of three values:

0    The exit status is zero if make is successful

2    The exit status is two if make encounters any errors. It will print messages
     describing the particular errors.

1    The exit status is one if you use the ‘-q’ flag and make determines that
     some target is not already up to date.

由于尝试创建一个不存在的目标是一个错误,因此在这种情况下,您将始终获得退出代码2。

答案 1 :(得分:3)

迟到的答案,但也许它会帮助将来遇到同样问题的人。

我使用下面的方法,它需要您修改Makefile(您需要添加单个%-rule-exists模式规则)。如果您不想这样做,可以直接从脚本运行make -n your-target-to-check &> /dev/null

我使用它来获得autobuild和autoupload等命令,这些命令包含在代码段中的上下文中。

%-rule-exists: 
    @$(MAKE) -n $* &> /dev/null


auto%: %-rule-exists
    $(info Press CTRL-C to stop $*ing)
    @while inotifywait -qq -r -e move -e create -e modify -e delete --exclude "\.#.*" src; do make $* ||: ; date ; done

示例输出:

$ make build-rule-exists; echo Exit code: $?
Exit code: 0
$ make nonsense-rule-exists; echo Exit code: $?
Exit code: 2

请注意,它实际上并不构建目标,以查明规则是否存在,由-n make标志提供。即使构建失败,我也需要它才能工作。

答案 2 :(得分:2)

另一种可能性是在数据库中grep,这是因为读取makefile:

some_target:        
  @ if $(MAKE) -C subdir -npq --no-print-directory .DEFAULT 2>/dev/null | grep -q "^$@:" ;  \
    then                                                                                    \
              echo "do something" ;                                                         \
    fi

其中

-n --just-print, --dry-run
     Print the commands that would be executed, but do not execute them.

-p, --print-data-base
     Print the data base (rules and variable values) that results from reading 
     the makefiles

-q, --question
     Question mode. Do not run any commands, or print anything; just return and 
     exit status that is zero if the specified targets are already up to date, 
     nonzero otherwise.

答案 3 :(得分:0)

我需要一种可以用于多个目标/目标并且不触发Makefile进行重新制作的解决方案。

这是我最终使用的内容:

#!/usr/bin/env bash

set -euo pipefail

dir="$1"
targets="$2"

# If there is no Makefile then return.
[[ ! -f "${dir}/Makefile" ]] && exit 1

# First check if this build will remake itself.
# If it does then add Makefile as a target to stop it.
if eval ${MAKE} --dry-run "--directory=${dir}" Makefile &> /dev/null; then
  db_target=Makefile
else
  db_target=
fi

# Search the Make internal database for the targets.
eval ${MAKE} --dry-run --print-data-base "--directory=${dir}" "${db_target}" | \
  awk -v targets="${targets}" '
    BEGIN {
      # Count the number of targets and put them in an array keyed by their own value.
      numRemaining = split(targets, temp)
      for (i in temp) remaining[temp[i]]
    }
    # If we found all the targets then consume all the input so the pipe does not fail.
    numRemaining == 0 { next }
    # Skip everything from the first line up to the start of the database comment inclusive.
    NR == 1, /\# Make data base/ { next }
    # Skip those that are not real targets.
    /\# Not a target:/, /^[^#]/ { next }
    {
      # Check if this line starts with any of the targets.
      for (target in remaining) {
        if ($0 ~ "^"target":") {
          delete remaining[target]
          numRemaining--
          next
        }
      }
    }
    END {
      # If we get to the end then make sure that we found all the targets.
      exit (numRemaining ? 1 : 0)
    }'

警告是:

  • 必须将Make文件命名为Makefile(或根本不存在)。
  • 在Make文件中有一个export MAKE语句(如果愿意,可以将make硬编码到脚本中)。
  • 您不介意邪恶的eval(真是!)。

我在Make文件中使用它来聚合子项目上的目标,这些子项目可能不像这样:

top_targets := all setup build clean mostlyclean
sub_dirs := foo bar

.PHONY: $(top_targets)
$(top_targets): $(sub_dirs)

.PHONY: $(sub_dirs)
$(sub_dirs):
    @if ./bin/make-target-exists.sh '$@' '$(MAKECMDGOALS)'; then $(MAKE) --directory='$@' $(MAKECMDGOALS); fi

答案 4 :(得分:0)

简单的解决方案:

output=$(make -n some_target 2>&1 | head -1)
if [[ "$output" != *"No rule to make target"* ]]; then
    echo "target is present"
fi

这个想法是在不运行make的情况下检查其输出。不确定,但这是100%正确的方法。

可以包装在函数中:

function check_make_target {
    output=$(make -n "$1" 2>&1 | head -1)
    [[ "$output" != *"No rule to make target"* ]]
}
if check_make_target some_target; then
    echo "target is present"
fi