foobar
可能会创建输出文件,即使它失败了,所以在这种情况下我需要删除它。
我可以这样做:
foo: bar baz
foobar $^ -o $@ || (rm -f $@ && exit 1)
但是这不会传播foobar
返回的相同退出代码(然后由make
输出)。有没有办法在Makefile中而不是在shell中捕获错误?
答案 0 :(得分:9)
.DELETE_ON_ERROR:
在这里做你想做的事吗?
通常当配方行失败时,如果它根本改变了目标文件,则该文件已损坏且无法使用 - 或者至少未完全更新。然而文件的时间戳表示它现在是最新的,所以下次make运行时,它不会尝试更新该文件。情况与外壳被信号杀死时的情况相同;看到中断。因此,通常正确的做法是在开始更改文件后如果配方失败则删除目标文件。如果.DELETE_ON_ERROR作为目标出现,make将执行此操作。这几乎总是你想要做的事情,但这不是历史实践;因此,为了兼容性,您必须明确请求它。
如果没有,或者你只需要那个目标,那么你想要的shell行是:
foobar $^ -o $@ || (ret=$$?; rm -f $@ && exit $$ret)
答案 1 :(得分:8)
如果DELETE_ON_ERROR
没有被剪切,而您正在寻找的东西有点像Java / JUnit中的tearDown
,@After
或finally
,这是您可以做的:
.ONESHELL:
使所有shell命令在单个shell中执行。trap
安装EXIT
,以完成清理工作。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 SHELLOPTS
为pipefail
shell设置errexit
和bash
标志。
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
/ @AfterAll
或tearDown()
/ @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。