【问题标题】:How do I get the exit status from the first command in a pipe within make?如何从 make 中管道中的第一个命令获取退出状态?
【发布时间】:2014-03-12 17:03:03
【问题描述】:

我正在尝试将编译器的输出通过管道传输到 windows 中的 tee 命令,但我遇到了一个问题,如果编译器在 make 中失败,它将在我想要的时候继续编译下一个文件停止。有没有办法让第一个命令的退出状态成为第二个命令的退出状态?

$(ODIR)/%.o: %.c $(DEPS)
    $(CC) -c -o $@ $< $(CFLAGS) 2>&1 | tee build_log.txt

【问题讨论】:

    标签: windows pipe gnu-make exit-code tee


    【解决方案1】:

    首先,我会将任何日志记录留给 make 的调用者。其次,这种管道制作不整齐。第三,不喜欢在 inside make 中丢失 stderr 流。

    也就是说,这是一个 shell 问题。如果您使用的是 bash,请参阅手册中的 pipefail。不幸的是,我认为打开它非常棘手。 (是的,我知道你说的是 windows,但我假设你没有使用可恶的 cmd。)

    SHELL := /bin/bash
    
    passes:
        (exit 1) |& cat
    
    fails:
        bash -c 'set -o pipefail; (exit 1) |& cat'
    

    【讨论】:

    • 如果将 SHELL 设置为 /bin/bash,则无需在命令行中使用 bash -c ...。一个或另一个会做。至少在 UNIX 系统中……希望在 Windows 中也是如此。尽管在 Windows 中(除非您使用的是 cygwin)该路径不起作用。
    • @MadScientist 有没有办法在 Makefile 中使用 SHELL 变量,以便 bash 启用 pipefail 选项?
    • 不容易:bash 不支持将其作为命令行选项。但我上面的意思是你可以只写set -o pipefail; (exit 1) |&amp; cat 你不需要添加bash -c 部分。我知道对所有配方执行此操作而不显式将其添加到每个配方(目录或通过变量)的唯一方法是创建一个包含该命令的文件,例如initfile,然后将export BASH_ENV = initfile 添加到您的makefile .
    • /bin/bash 在 Windows 上?
    【解决方案2】:

    苦苦挣扎后,我得出了这个解决方案……

    .ONESHELL:
    $(ODIR)/%.o: %.c $(DEPS)
        $(CC) -c -o $@ $< $(CFLAGS) 2> temp_err_file
        set EXIT_STATUS=%ERRORLEVEL%
        type temp_err_file >> build_log.txt
        type temp_err_file 1>&2
        del /q temp_err_file
        exit /b %EXIT_STATUS%
    

    .ONESHELL 允许 make 在单个 shell 命令中运行整个配方,而不是在单独的 cmd 中运行每一行并分别收集每个的返回状态。整体退出状态取决于主编译命令,所以最终需要以编译状态退出。

    我知道它不是一个涉及temp_err_file 的干净解决方案,如果编译命令后出现问题,make 将无法捕获它,但我认为这是我能找到的最好的在不丢失 stderr 流的情况下使用 windows 的解决方案和日志记录。

    【讨论】:

      【解决方案3】:

      一种独立于 shell 且在某些情况下可行的方法如下:

      假设你有一个食谱:

      target:
              try_making_target |& tee target.log
      

      我所做的是将其转换为:

      target:
              (try_making_target || rm -f $@) |& tee target.log
              test -e $@
      

      管道命令失败,“回退”(|| 之后的命令)将删除目标文件,最终测试将失败。请注意,此示例假定操作系统是 Linux('rm' 表示删除)并且您的 shell 支持 ||运算符。

      这假设您在 try_making_target 失败时对部分结果不感兴趣。如果您想将部分结果保留在“目标”中,您可以使用其他“标记”文件来指定 try_making_target 的成功或失败。这样的事情可能会奏效:

      target:
              touch $@.succeeded                                          # Assume success
              (try_making_target || rm -f $@.succeeded) |& tee target.log # Delete to mark failure
              test -e $@.succeeded                                        # Fail if marker
              rm $@.succeeded                                             # Remove unneeded marker
      

      最后一段代码也适用于虚假目标,尽管您确实应该尽量减少使用这些代码(我尝试仅将它们用作真实文件的助记符,这些文件可能具有较长的名称)。

      【讨论】:

        猜你喜欢
        • 2014-09-04
        • 1970-01-01
        • 1970-01-01
        • 2014-02-13
        • 1970-01-01
        • 1970-01-01
        • 2012-06-25
        • 2017-11-11
        相关资源
        最近更新 更多