Colored error messages in make
I use Make for automating different tasks, and what I’ve covered in my notes is normally good enough.
In the last period, I had a makefile that grew considerably. There were a lot of targets, and running a make all would fill the terminal.
Normally, there are no errors, and the output can be ignored. But if something goes wrong, it is very hard to understand what actually happened.
In those situations, I normally execute make all -j1 so that targets are executed one by one sequentially; at that point, finding the culprit is obvious. The downside is that in that case, the build process takes a lot of time.
Note that make by default does not build the targets in parallel, but I have defined my MAKEFLAGS as
-j$(($(grep -c processor /proc/cpuinfo)+1)) on my machine.
So I decided to clean up the output; in particular, I wanted a red error message with the failing command and its output, so that it is easy to find when something does not work as expected.
In retrospect, I should have done it from the beginning.
So I had to define a Make function that:
-
store the output of a command and its error code
-
if the command failed (error code is different than zero), then print the command in red, otherwise do not print anything
This is my first revision:
RED := \033[31m
RESET := \033[0m
define run_silent
@\
output=$$( ( $(1) ) 2>&1 ); \
code=$$?; \
if [ $$code -ne 0 ]; then :; printf "$(RED)%s\n%s\n$(RESET)" "$(1)" "$$output"; fi; \
exit $$code;
endef After testing it a little bit, I’ve updated it to the following; as some commands do have an empty output in case of errors:
RED := \033[31m
RESET := \033[0m
define run_silent
@\
output=$$( ( $(1) ) 2>&1 ); \
code=$$?; \
if [ -n "$$output" ]; then :; output=$$( printf "%s\nX" "$$output" ); output=$${output%X}; fi; \
if [ $$code -ne 0 ]; then :; printf "$(RED)%s\n%s$(RESET)" "$(1)" "$$output"; fi; \
exit $$code;
endef And this is my current revision; I wanted to highlight warnings too:
RED := \033[31m
YELLOW := \033[33m
define run_silent
@\
output=$$( ( $(1) ) 2>&1 ); \
code=$$?; \
if [ -n "$$output" ]; then :; output=$$( printf "%s\nX" "$$output" ); output=$${output%X}; fi; \
if [ $$code -ne 0 ]; then :; \
printf "$(RED)%s\n%s$(RESET)" "$(1)" "$$output"; \
elif printf '%s' "$$output" | grep -i -q "warning"; then :; \
printf "$(YELLOW)%s\n%s$(RESET)" "$(1)" "$$output"; \
fi; \
exit $$code;
endef Highlighting the warning might seem like a minor improvement; but it made a huge difference. In a project, I had over twenty legitimate warnings that led to a slightly wrong output, which I did not notice. A couple of warnings are false positives; I’ll need to find a way to suppress them to keep a clean output.
How do I use run_silent?
Previously I wrote
.DELETE_ON_ERROR:
main.txt: source.txt
command $^ > $@.tmp
cat $@.tmp | pipe-command > $@ Now I have
.DELETE_ON_ERROR:
main.txt: source.txt
@echo "create $@ from $^"
$(call run_silent,command $^ > $@.tmp )
$(call run_silent,cat $@.tmp | pipe-command > $@ ) The code in the makefile is unfortunately much more verbose; instead of writing the command, I have to prefix it with $(call run_silent,, plus an @echo, unless I want no output at all.
On the other hand, when building the target main.txt, the output is just create main.txt from source.txt, instead of all single commands (and some targets consisted of four or five commands). If there is an error, I have the command and output, as before, but colored in red, thus making it trivial to find out what the root cause is, even when executing make all with jobs running in parallel.
I wish that Make provided such functionality out of the box, but as far as I could see, it does not.
If you have questions, comments, or found typos, the notes are not clear, or there are some errors; then just contact me.