如何在Makefile中出错后清理?

时间:2015-02-19 02:38:53

标签: makefile gnu-make

foobar可能会创建输出文件,即使它失败了,所以在这种情况下我需要删除它。

我可以这样做:

foo: bar baz
        foobar $^ -o $@ || (rm -f $@ && exit 1)

但是这不会传播foobar返回的相同退出代码(然后由make输出)。有没有办法在Makefile中而不是在shell中捕获错误?

2 个答案:

答案 0 :(得分:9)

.DELETE_ON_ERROR:在这里做你想做的事吗?

来自Errors in Recipes

  

通常当配方行失败时,如果它根本改变了目标文件,则该文件已损坏且无法使用 - 或者至少未完全更新。然而文件的时间戳表示它现在是最新的,所以下次make运行时,它不会尝试更新该文件。情况与外壳被信号杀死时的情况相同;看到中断。因此,通常正确的做法是在开始更改文件后如果配方失败则删除目标文件。如果.DELETE_ON_ERROR作为目标出现,make将执行此操作。这几乎总是你想要做的事情,但这不是历史实践;因此,为了兼容性,您必须明确请求它。

如果没有,或者你只需​​要那个目标,那么你想要的shell行是:

foobar $^ -o $@ || (ret=$$?; rm -f $@ && exit $$ret)

答案 1 :(得分:8)

如果DELETE_ON_ERROR没有被剪切,而您正在寻找的东西有点像Java / JUnit中的tearDown@Afterfinally,这是您可以做的:

  1. 使用.ONESHELL:使所有shell命令在单个shell中执行。
  2. trap安装EXIT,以完成清理工作。
  3. 通过设置errexit确保在出现任何错误的情况下退出外壳程序。进行操作时,我们也可以立即设置pipefail

例如,假设您要启动Docker容器,进行测试,无论如何都停止docker contaner,但要获得测试结果。这样做是这样的:

export SHELL:=/bin/bash
export SHELLOPTS:=$(if $(SHELLOPTS),$(SHELLOPTS):)pipefail:errexit

.ONESHELL:

.PHONY: test
test:
    function tearDown {
        docker stop test-image
    }
    trap tearDown EXIT
    docker run --name test-image …
    testStep1…
    testStep2…
    testStep3…
    …

工作原理

  • export SHELL导出告诉GNU make使用bash作为shell,它的覆盖区比默认的sh重,但功能更多。
  • export SHELLOPTSpipefail shell设置errexitbash标志。
    • pipefail确保管道的退出状态不是最后一个命令,而是最后一个具有非零退出状态的命令。因此,false | true将返回1而不是0
    • errexit确保命令序列的退出状态不是最后一个命令,而是退出状态不为零的最后一个命令,并且不会执行后续命令。因此,false ; true将返回1而不是0,并且true将不会执行。
  • .ONESHELL:告诉GNU make在单个shell中运行所有命令。这意味着,您的食谱现在确实是一个shellscript。 (需要GNU make 3.82或更高版本。)
  • function tearDown { docker stop test-image }定义了一个名为tearDown的shell函数。在此示例中,它将停止docker容器。
  • trap tearDown EXIT 是此示例中所有内容的最关键的部分。它告诉配方调用的外壳程序在退出时运行tearDown函数,也就是说,无论命令是成功还是失败。

限制

这类似于Java中的finally。无法跨多个目标/测试重复使用。绝对不同于JUnit中的@AfterClass / @AfterAlltearDown() / @After / @AfterEach

变得更好

但是您可以这样做,以防万一。假设您要在同一个Docker容器上运行多个测试,然后不管它都将其拆解。这类似于JUnit中的@AfterClass / @AfterAll。然后看起来像这样:

export SHELL:=/bin/bash
export SHELLOPTS:=$(if $(SHELLOPTS),$(SHELLOPTS):)pipefail:errexit

.ONESHELL:

.PHONY: start
start:
    docker run --name test-image …

.PHONY: stop
stop:
    docker stop test-image

.PHONY: test
test: start
    function tearDown {
        $(MAKE) stop
    }
    trap tearDown EXIT
    $(MAKE) -k testImpl

.PHONY: testImpl
testImpl: testCase1 testCase2 testCase3

.PHONY: testCase1
testCase1:
    …

.PHONY: testCase2
testCase2:
    …

.PHONY: testCase3
testCase3:
    …

这将运行所有测试,即使第一个测试失败,也要在所有测试完成后进行清理,并在任何测试失败的情况下报告错误。

免责声明:这需要GNU make的.ONESHELL功能,该功能是GNU make 3.82中引入的。截至本次编辑时,GNU make的当前版本为GNU make 4.2.1,而Mac OS X仍随附GNU make 3.81。