如何在GNU make中捕获错误和中断?

26
我想知道是否有一种方法在GNU make中实现trap,类似于内置于BASH中的功能?
如果用户按下CTRL-C,或者make本身失败(非零退出),我想调用特定的目标或宏。

+1 对于这个有趣的问题,虽然你正在做的听起来像是一个坏主意。 - finnw
6个回答

28

目前,GNU make并不具备本地支持。

然而,有一个可靠的解决方法:

.PHONY: internal-target external-target

external-target:
  bash -c "trap 'trap - SIGINT SIGTERM ERR; <DO CLEANUP HERE>; exit 1' SIGINT SIGTERM ERR; $(MAKE) internal-target"

internal-target:
  echo "doing stuff here"

这会捕获中断、终止和任何非零退出代码。

请注意$(MAKE),因此cmdline覆盖和make选项将传递给子make。

在trap上:

  • 清除陷阱处理程序(使用-)
  • 进行清理
  • 以非零退出状态退出,因此构建自动化工具会报告构建失败。

对于目录,DELETE_ON_ERROR不起作用,因此这对于在mktemp -d后清理非常重要。

使用有效的CMD替换< DO CLEANUP HERE >


除了您echo中的嵌套引用,当然是非法的,这似乎不起作用,至少对于我想要完成的任务来说是这样。一个人应该如何使用它?假设我的makefile只有一个目标,其中包含一个sleep和一个touch,就像@andrewdotn的示例一样(忽略.DELETE_ON_ERROR),您将如何修改它以使用此解决方法? - Davide
请查看编辑。echo "doing stuff here" 将是 sleep/touch。DO CLEANUP HERE 将类似于,echo 你按下了 ctrl-c。 - Kevin

20

@kevinf的回答的简化版本,对于基本情况似乎已经足够好了:

run:
    bash -c "trap 'docker-compose down' EXIT; docker-compose up --build"

(这个例子是有原因的:docker-compose up确实会说

当命令退出时,所有容器都将停止。

但是它不会像docker run --rm一样rm停止的容器,因此您仍然可以使用docker ps -a查看它们。)


当我尝试这样做时,我仍然看到了recipe for target 'run' failed ... make: *** [run] Interrupt的错误,并且该make目标没有运行docker-compose down命令。(我有与此答案中描述的确切用例相同的情况。) - ely
这对于我来说确实适用于确保下行在上行之后运行的确切用例。 - Austin Mackillop

4

不行。GNU make的信号处理已经有很多问题了。在它的信号处理程序中,它调用像printf这样的函数,这些函数不能安全地从信号处理程序中调用。我曾经看到过这导致的问题,例如.DELETE_ON_ERROR规则如果将stderr重定向到stdout,则不一定总是运行。

例如,在CentOS 7.4上:

  1. Create the following Makefile:

    .DELETE_ON_ERROR:
    
    foo:
            touch $@
            sleep 10
    
  2. Open it in vim and run :make,

  3. While it is sleeping, hit Ctrl-C

Vim/make prints

Press ENTER or type command to continue
touch foo
sleep 10
^C
shell returned 130

Interrupt: Press ENTER or type command to continue

Make被发送了一个中断信号,但是foo仍然存在。


您有一些.DELETE_ON_ERROR无法正常工作的示例吗?这是make实现中的错误吗? - Hakan Baba
@HakanBaba 详细信息已添加。 - andrewdotn

2

Make 不支持,但是可以使用 BASH 技巧实现类似的功能。

default: complete

complete: do_mount
        echo "Do something here..."

do_mount:
        mkdir -p "$(MOUNTPOINT)"
        ( while ps -p $$PPID >/dev/null ; do \
                sleep 1 ; \
        done ; \
        unmount "$(MOUNTPOINT)" \
        ) &
        mount "$(MOUNTSOURCE)" "$(MOUNTPOINT)" -o bind

"unmount" 会在 "make" 完成后运行。如果您尝试清理在构建过程中可能发生但通常在 "make" 退出时不会被清理的操作,则这通常是一个令人满意的解决方案。

1

不,据我所知没有这样的功能。


1

make 命令会返回一个状态码。就我目前所知,它返回 0 表示成功,返回 2 表示失败(请查看文档)。因此,您是否可以将 make 命令放入一个 Shell 脚本中以解决问题?


1
我可以做到,但如果可能的话,我会尽量避免这样做,因为它是自包含的,而且我正在努力保持它的这种状态。 - khosrow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接