在makefile中处理pipe道及其退出状态的最佳方法

如果某个命令在make失败,如gcc ,则退出…

 gcc gcc: fatal error: no input files compilation terminated. make: *** [main.o] Error 4 

但是,如果我有一个pipe道,pipe道中最后一个命令的退出状态将被采用。 作为一个例子, gcc | cat gcc | cat ,不会因为cat成功而失败。

我知道整个pipe道的退出代码存储在PIPESTATUS数组中,我可以用${PIPESTATUS[0]}得到错误代码4。 我应该如何构build我的生成文件来处理pipe道命令并正常退出失败?


正如在评论中,另一个例子是gcc | grep something gcc | grep something 。 在这里,我认为最希望的行为仍然是gcc ,只有gcc导致失败,如果没有find任何东西,不会grep

您应该能够告诉make使用bash而不是sh并让bash set -o pipefail set,以便在管道中首次出现故障时退出。

GNU Make 3.81(大概是早些时候,虽然我不知道肯定),你应该可以用SHELL = /bin/bash -o pipefail来做到这SHELL = /bin/bash -o pipefail

GNU Make 3.82(以及更新的版本)中,你应该可以用SHELL = /bin/bash.SHELLFLAGS = -o pipefail -c来做到这一点(尽管我不知道是否需要在结尾添加-c ,或者如果make会为你添加,即使你指定.SHELLFLAGS

bash手册页:

管道的返回状态是最后一个命令的退出状态,除非启用了pipefail选项。 如果启用pipefail,则管道的返回状态是以非零状态退出的最后一个(最右边)命令的值,如果所有命令都成功退出,则为零。 如果保留字! 在流水线之前,流水线的退出状态是如上所述的退出状态的逻辑否定。 在返回值之前,shell等待管道中的所有命令终止。

我会去pipefail 。 但是,如果你真的不想要( 或者如果你只想在第一个过程中失败 – 而不是在管道其余部分失败的情况下):

 SHELL=bash all: gcc | cat ; exit "$${PIPESTATUS[0]}" 

与@jozxyqk自我回答相比,唯一的优势是你不会丢失退出状态码。

只要在你的makefile的开头添加这个命令即可:

 SHELL=/bin/bash -o pipefail 

现在,您可以例如从对象(第1条规则)生成errors.err文件,而不用担心它会被可执行文件(第2条规则)覆盖。

 %.o : %.c gcc $(CFLAGS) $(CPPFLAGS) $^ -o $@ 2>&1 | tee errors.err %.x : %.o $(OBJECTS) gcc $(LDLIBS) $^ -o $@ 2>&1 | tee errors.err 

没有它,从规则1得到没有错误,并运行规则2,覆盖它。 errors.err只有一行errors.err说明没有运行gcc目标文件

 gcc: error: program.o: No such file or directory 

一个合理和便携的方法是重构您的构建作业使用文件,而不是管道。 例如:

 foo: gcc >$@.log grep success $@.log cat $@.log rm $@.log 

打印后删除日志文件显然是没有必要的; 这只是一个通用的模板。 牛肉是重新定向来取代管道。 你甚至可以重构它到多个食谱:

 foo: foo.tmp foo.log grep success $@.log mv $< $@ %.tmp %.log: gcc -o $*.tmp >$*.log 

恰当地清理临时文物并对其进行管理是这种方法的明显缺点。