Makefile 在生成文件中出现错误后,如何清理?

Makefile 在生成文件中出现错误后,如何清理?,makefile,gnu-make,Makefile,Gnu Make,foobar即使失败也可能创建输出文件,因此在这种情况下我需要删除它 我可以这样做: foo: bar baz foobar $^ -o $@ || (rm -f $@ && exit 1) 但这不会传播由foobar返回的相同退出代码(然后由make输出)。有没有办法在Makefile而不是shell中捕获错误?是否执行。删除错误:在此处执行所需操作 发件人: 通常,当配方行失败时,如果它根本更改了目标文件,则该文件已损坏且无法使用,或者至少没有完全更新。但是

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

我可以这样做:

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

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

是否执行
。删除错误:
在此处执行所需操作

发件人:

通常,当配方行失败时,如果它根本更改了目标文件,则该文件已损坏且无法使用,或者至少没有完全更新。但是文件的时间戳显示它现在是最新的,所以下次运行时,它不会尝试更新该文件。情况与炮弹被信号击毙时一样;参见中断。因此,通常正确的做法是,如果配方在开始更改文件后失败,则删除目标文件。make将在以下情况下执行此操作。删除\u ON\u错误显示为目标。这几乎总是你想要做的,但它不是历史的实践;因此,为了兼容性,您必须显式地请求它

如果不是,或者如果您只需要一个目标,那么您想要的shell行是:

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

如果Java/JUnit中的
DELETE\ON\u ERROR
无法将其删除,并且您看到的有点像
tearDown
@After
、或
finally
,您可以这样做:

  • 使用
    .ONESHELL:
    在单个shell中执行所有shell命令
  • 退出安装
    陷阱
    ,以完成清理
  • 通过设置
    errexit
    ,确保shell在出现任何错误时退出。在进行此操作时,我们还可以立即设置
    pipefail
  • 例如,假设您想要启动docker容器,执行测试,停止docker容器,不管发生什么,但是要获得测试结果。这是如何做到的:

    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
      export告诉gnumake使用
      bash
      作为SHELL,它比默认的
      sh
      占用的空间更大,但功能更多
    • export SHELLOPTS
      bash
      shell设置
      pipefail
      errexit
      标志。
      • pipefail
        确保管道的退出状态不是最后一个命令,而是最后一个非零退出状态的命令。因此,
        false | true
        将返回
        1
        ,而不是
        0
      • errexit
        确保命令序列的退出状态不是最后一个命令,而是最后一个非零退出状态的命令,并且后续命令不会执行。所以,
        false;true
        将返回
        1
        而不是
        0
        ,并且
        true
        将不会执行
    • .ONESHELL:
      告诉gnumake在一个shell中运行所有命令。这意味着,你的食谱现在真的是一个脚本。(需要GNU make 3.82或更高版本。)
    • 函数tearDown{docker stop test image}
      定义了一个名为
      tearDown
      的shell函数。在本例中,它将停止docker容器
    • 陷阱拆卸出口是本例中最关键的部分。它告诉为配方调用的shell在退出时运行
      tearDown
      函数,也就是说,无论命令是成功还是失败
    局限性 这类似于Java中的
    finally
    。不可能跨多个目标/测试重用。它绝对不像JUnit中的AfterClass
    /
    @AfterAll
    或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。

    我想在这种情况下,将其用于所有目标不会有什么坏处。但是,作为将来的参考,是否有一种方法可以在不污染shell命令的情况下执行此操作?很高兴看到
    make
    而不是长时间的shell hacks重复了简单而清晰的代码。除非有办法抑制shell命令部分的回音,我对此表示怀疑。这个选项要么全有,要么全无。我不知道有什么方法可以做到这一点,仅仅是为了特定的规则。这种贝壳游戏是我最好的。也就是说,即使面对“丑陋”的shell命令,您也可以使用automake样式来控制make输出的外观。@matt您可以将shell代码移动到脚本中,使其更漂亮。您还可以在它前面加上类似于
    @echo foobar$^-o$@;的前缀
    但这可能会让查看make输出的人感到困惑,他们想知道文件是如何被删除的。@RossRidge第二个建议是我的沉默规则建议,仅供记录。我的automake样式沉默规则功能现在有一个github项目:。警告一句:。ONESHELL可能会使你的食谱表现不同。请参阅。@schieferstapel的最后一段的确,这就是为什么关于
    SHELLOPTS
    的那一行非常重要的原因。
    .ONESHELL
    不是默认值是有原因的,原因是
    errexit
    不是shell的默认值,并且没有标准的设置方法,这取决于shell。感谢您的详细说明。但是,即使使用
    set-e
    ,食谱也可能表现出