如何在Makefile中出错后清理?

Mat*_*att 12 makefile gnu-make

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

我可以做这个:

foo: bar baz
        foobar $^ -o $@ || (rm -f $@ && exit 1)
Run Code Online (Sandbox Code Playgroud)

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

Chr*_*jer 12

如果DELETE_ON_ERROR不削减它,你要找的是一个有点像tearDown,@Afterfinally用Java/JUnit的,这是你可以做什么:

  1. 用于.ONESHELL:在单个shell中执行所有shell命令.
  2. 安装trapfor EXIT以完成清理.
  3. 通过设置确保shell在出现任何错误时退出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…
    …
Run Code Online (Sandbox Code Playgroud)

这个怎么运作

  • export SHELL出口告诉GNU使使用bash作为外壳,其中有较重的足迹比默认sh,但方式更多的功能.
  • export SHELLOPTSpipefailerrexit为标志bash的外壳.
    • pipefail确保管道的退出状态不是最后一个命令,而是具有非零退出状态的最后一个命令.所以,false | true将返回1而不是0.
    • errexit确保命令序列的退出状态不是最后一个命令,而是具有非零退出状态的最后一个命令,并且不会执行后续命令.因此,false ; true将返回1,而不是0true会不会被执行.
  • .ONESHELL:告诉GNU使运行在一个单一的外壳中的所有命令.这意味着,你的食谱现在真的是一个shellcript.(需要GNU make 3.82或更高版本.)
  • function tearDown { docker stop test-image }定义了一个名为shell函数tearDown.在此示例中,它将停止docker容器.
  • trap tearDown EXIT最关键的部分在这个例子中的一切.它告诉shell调用的shell tearDown在退出时运行该函数,即无论命令是成功还是失败.

限制

这类似于finallyJava.无法跨多个目标/测试重复使用.它绝对不像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:
    …
Run Code Online (Sandbox Code Playgroud)

现在,这将运行所有测试,即使第一个测试失败,在所有测试完成后进行清理,并在任何测试失败时报告错误.

免责声明:这需要.ONESHELLGNU make 的功能,它是在GNU make 3.82中引入的.当前版本的GNU make在编辑时是GNU make 4.2.1,而Mac OS X仍然附带GNU make 3.81.


Eta*_*ner 9

.DELETE_ON_ERROR:在这做什么吗?

食谱中的错误:

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

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

foobar $^ -o $@ || (ret=$$?; rm -f $@ && exit $$ret)
Run Code Online (Sandbox Code Playgroud)