如何定义 makefile 条件并在多个构建规则中重用它?

How define a makefile condition and reuse it in several build rules?

这可能是也可能不是 Makefile Variable Assignment Executes Early

的重复

是否可以定义一个条件并在以后重用它?

MAIN_FILE_PATH := main

define copy_resulting_pdf=
if [[ -f "${MAIN_FILE_PATH}" ]] \
then \
    cp "${MAIN_FILE_PATH}" "${MAIN_FILE_PATH}.pdf"; \
else \
    $(error Error: The PDF ${MAIN_FILE_PATH} was not generated!); \
fi
endef

all:
    echo doing other things.
    $(copy_resulting_pdf)


other:
    echo doing more other things.
    $(copy_resulting_pdf)

当运行这个时候,make甚至不会开始构建all规则。它只是抛出:

Makefile:14: *** Error: The PDF main was not generated!.  Stop.

我用这个做到了:

SHELL := /bin/bash
MAIN_FILE_PATH := main

define copy_resulting_pdf=
echo Printing results...
eval "if [[ -f "${MAIN_FILE_PATH}" ]]; then \
    printf 'Coping PDF...\n'; \
    cp "${MAIN_FILE_PATH}" "${MAIN_FILE_PATH}.pdf"; \
else \
    printf \"Error: The PDF "${MAIN_FILE_PATH}" was not generated!\n\"; \
    exit 1; \
fi"
endef

all:
    echo doing other things.
    $(copy_resulting_pdf)

运行它:

echo doing other things.
doing other things.
echo Printing results...
Printing results...
eval "if [[ -f "main" ]]; then printf 'Coping PDF...\n'; cp "main" "main.pdf"; else printf \"Error: The PDF "main" was not generated!\n\"; exit 1; fi"
Error: The PDF main was not generated!
make: *** [setup/makefile.mk:17: all] Error 1

参考文献:

  1. How can I use Bash syntax in Makefile targets?
  2. How to use ifeq inside of a define in GNU Make?

您的代码的问题是 $(error ...) 也是一个 文本替换 。所以 $(copy_resulting_pdf) 的任何扩展也会导致所有嵌入变量和函数的递归扩展,包括 $(error ...)。通常,$(error ...) 由 make 条件(例如 ifeq)或其他替换(例如 $(if ...))保护。但在你的情况下,它是 "guarded" by shell 有条件的,这意味着没有什么可做的。

但下面的工作与预期的一样:

MAIN_FILE_PATH := main

define copy_resulting_pdf
    echo Printing results...
    if [[ -f "${MAIN_FILE_PATH}" ]]; then \
        printf 'Copying PDF...\n'; \
        cp "${MAIN_FILE_PATH}" "${MAIN_FILE_PATH}.pdf"; \
    else \
        printf "Error: The PDF \"${MAIN_FILE_PATH}\" was not generated!\n"; \
        exit 1; \
    fi
endef

.PHONY: all other
all:
    @echo doing other things.
    @$(copy_resulting_pdf)

other:
    @echo doing more other things.
    @$(copy_resulting_pdf)

注意:这里不需要 eval 和一些花哨的转义符。

只需添加一个注释:恕我直言,在配方中编写 bash 控制结构有点代码味。如果你用 make 习语替换它们,事情总是看起来更好。

这里的另一种味道是当你对 make 撒谎时:你的 makefile 没有说 all 的配方创建 main.pdf 例如。

MAIN_FILE_PATH := main

.PHONY: other_things
other_things:
    blah-dy-blah…

${MAIN_FILE_PATH}.pdf: | other_things
${MAIN_FILE_PATH}.pdf: ${MAIN_FILE_PATH}
    cp $< $@

.PHONY: all
all: ${MAIN_FILE_PATH}.pdf
    echo $@ Success

那么当你要求 make 制作 all 时会发生什么?

  • all:
    • 先决条件:make 需要构建 main.pdf
      • 先决条件:make 构建 other_things
        • blah-dy-blah…运行
      • 食谱:main被复制到main.pdf
    • 食谱:all Success 打印

main 的存在由 make 而不是 bash 检查。 这样做的一个好处是,如果您有创建 main 的编程方式,则可以轻松添加另一个配方。 另一个是如果 main.pdf 已经是最新的,则不会发生复制。

既然你不再对 make撒谎, 这个 makefile 是并行安全的, 允许您使用 -jn 标志。 你好性能。