Wednesday 4 April 2018 — This is over six years old. Be careful.
In a pull request today, I was struck again by the difficulty of providing a “help” target for Makefiles. The make command doesn’t natively have a way to see what targets are available, because the set is dynamic and large, so we are left to cobble things together ourselves.
We’d been cargo-culting this target across Makefiles for a while:
help: ## display this help message
@echo "Please use \`make <target>' where <target> is one of"
@perl -nle'print $& if m{^[a-zA-Z_-]+:.*?## .*$$}' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m %-25s\033[0m %s\n", $$1, $$2}'
Here’s the meaty line, split across lines for readability, as all the rest of the code in this post will be:
perl -nle'print $& if m{^[a-zA-Z_-]+:.*?## .*$$}' $(MAKEFILE_LIST) |\
sort |\
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m %-25s\033[0m %s\n", $$1, $$2}'
It finds labelled lines with double-hash comments, and prints them, sorted, in a nice two-column layout, with the target names in cyan.
We’re a Python shop, so that Perl command really seemed out of place. What would it look like in Python? Longer, that’s what:
python -c 'import fileinput,re; \
ms=filter(None, (re.search("([a-zA-Z_-]+):.*?## (.*)$$",l) for l in fileinput.input())); \
print("\n".join(sorted("\033[36m {:25}\033[0m {}".format(*m.groups()) for m in ms)))' $(MAKEFILE_LIST)
But looking at that original line more, what is the Perl even doing? It’s just selecting lines. That’s what grep is for:
grep -E '^[a-zA-Z_-]+:.*?##' $(MAKEFILE_LIST) | \
sort | \
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m %-25s\033[0m %s\n", $$1, $$2}'
That’s shorter than the original, but we can do even better by using awk more effectively:
grep '^[a-zA-Z]' $(MAKEFILE_LIST) | \
sort | \
awk -F ':.*?## ' 'NF==2 {printf "\033[36m %-25s\033[0m %s\n", $$1, $$2}'
The terminal coloring is cute, but unnecessary and can actually be counterproductive depending on your terminal’s natural colors, so:
grep '^[a-zA-Z]' $(MAKEFILE_LIST) | \
sort | \
awk -F ':.*?## ' 'NF==2 {printf " %-26s%s\n", $$1, $$2}'
Looking around for other people’s techniques, marmelab had a very similar line, while Rodrigo Machado and O. Libre went down the all-awk path with fancier behavior.
In many ways, this doesn’t matter at all. But it’s a fun rabbit-hole...
Comments
?
(also, isn't the '?' redundant for .'*?'?)
(also also, what if your pretty target depends on something...)
grep | awk is something that tends to evolve organically on command lines and you can almost always make awk do all the work. I think awk should be able to do anything sed can, but sed expresses what it does much more elegantly.
Separately, the original formula has bare $& which is interpolated by make as the empty string. It still works because perl's print, in the absence of an argument, prints $_ which is the current line of input.
Can't help myself from golfing a bit :-)
perl -ne's/^([-\w]+):.*?##\s*(.*)/printf" %-26s %s\n",$$1,$$2/e' $(MAKEFILE_LIST) | sort
"make " TAB TAB
https://stackoverflow.com/questions/4188324/bash-completion-of-makefile-target
https://suva.sh/posts/well-documented-makefiles/
Add a comment: