Reputation: 17294
If a command fails in make
, such as gcc
, it exits...
gcc
gcc: fatal error: no input files
compilation terminated.
make: *** [main.o] Error 4
However, if I have a pipe the exit status of the last command in the pipe is taken. As an example, gcc | cat
, will not fail because cat
succeeds.
I'm aware the exit codes for the whole pipe are stored in the PIPESTATUS
array and I could get the error code 4 with ${PIPESTATUS[0]}
. How should I structure my makefile to handle a piped command and exit on failure as normal?
As in the comments, another example is gcc | grep something
. Here, I assume the most desired behavior is still for gcc
and only gcc
to cause failure and not grep
if it doesn't find anything.
Upvotes: 17
Views: 7268
Reputation: 52030
I would go for pipefail
. But if you really don't want (or if you want to only fail on the first process -- not in case of failure from the rest of the pipe):
SHELL=bash
all:
gcc | cat ; exit "$${PIPESTATUS[0]}"
Upvotes: 11
Reputation: 11785
Just add in the begin of your makefile
the command:
SHELL=/bin/bash -o pipefail
Now you can, for example, generate the errors.err
file from objects (1st rule) without being worried it would be overwritten by the executable (2nd rule).
%.o : %.c
gcc $(CFLAGS) $(CPPFLAGS) $^ -o $@ 2>&1 | tee errors.err
%.x : %.o $(OBJECTS)
gcc $(LDLIBS) $^ -o $@ 2>&1 | tee errors.err
Without it, make
get no errors from rule 1, and run rule 2, overwriting it. You will end up with only a single line in errors.err
stating that there are no object file to run gcc
gcc: error: program.o: No such file or directory
Upvotes: 1
Reputation: 189597
A reasonable and portable approach is to refactor your build jobs to use files instead of pipes. For example:
foo:
gcc >[email protected]
grep success [email protected]
cat [email protected]
rm [email protected]
Removing the log file after printing it is obviously not necessary; this is just a general template. The beef is the redirection to replace the pipeline. You could even refactor it to multiple recipes:
foo: foo.tmp foo.log
grep success [email protected]
mv $< $@
%.tmp %.log:
gcc -o $*.tmp >$*.log
Properly cleaning up the temporary artefacts and generally managing them is an obvious drawback of this approach.
Upvotes: 1
Reputation: 80969
You should be able to tell make to use bash
instead of sh
and get bash
to have set -o pipefail
set so it exits with the first failure in the pipeline.
In GNU Make
3.81 (and presumably earlier though I don't know for sure) you should be able to do this with SHELL = /bin/bash -o pipefail
.
In GNU Make
3.82 (and newer) you should be able to do this with SHELL = /bin/bash
and .SHELLFLAGS = -o pipefail -c
(though I don't know if adding -c
to the end like that is necessary or if make will add that for you even when you specify .SHELLFLAGS
.
From the bash
man page:
The return status of a pipeline is the exit status of the last command, unless the pipefail option is enabled. If pipefail is enabled, the pipeline's return status is the value of the last (rightmost) command to exit with a non-zero status, or zero if all commands exit successfully. If the reserved word ! precedes a pipeline, the exit status of that pipeline is the logical negation of the exit status as described above. The shell waits for all commands in the pipeline to terminate before returning a value.
Upvotes: 18