I find using GNU Make for data completion really helpful for data pipelines.
This process is well-documented in this guide from DataMade.
However, my Makefiles can get somewhat large and my data paths pretty long. I was frustrated that bash completion wasn't working the way I wanted it to. I wanted to type make
and then start typing the target and then be able to tab-complete the rest of the target.
Mac OS doesn't come with a robust set of bash completion rules. For example, while git comes when you install the XCode tools, git's bash completion, that doesn't come with git's bash completion which is super-useful for things like tab-completing branch names.
Luckily, you can install better bash completion with Homebrew.
This is as simple as:
brew install bash-completion
However, this still wasn't working well with autocompleting make targets.
I took a look at the script that does the completion for make, which is installed at /usr/local/etc/bash_completion.d/make
.
I noticed that the list of targets seemed to be generated by searching through the output of.
This is the relevant part of the script:
COMPREPLY=( $( compgen -W "$( make -qp $makef $makef_dir 2>/dev/null | \
awk -F':' '/^[a-zA-Z0-9][^$#\/\t=]*:([^=]|$)/ \
{split($1,A,/ /);for(i in A)print A[i]}' )" \
-- "$cur" ) )
It uses awk
to parse through the output of make -qp
. The -q
runs make
in "question" mode, which the manpage says causes make to:
not run any commands, or print anything; just return an exit status that is zero if the specified targets are already up to date, nonzero otherwise.
The -p
prints makes "database", which is a list of targets and variables.
I noticed that the regex provided to awk, /^[a-zA-Z0-9][^$#\/\t=]*:([^=]|$)/
includes a '/' in the non-matching characters causing targets with paths in them to be excluded.
That is, make a<tab>
would complete to make all
but make data/out/da<tab>
would not complete make data/out/data.csv
.
To fix this, I copied /usr/local/etc/bash_completion.d/make
to ~/.bash_completion
, which I saw from looking in /usr/local/etc/bash_completion
is sourced by that script. I then modified my version to remove /
from the excluded list of characters for targets.
If I used make to build more software, this might not be a great idea because it might take a long time for make to run and print the targets each time I hit tab. However, I think it's worth it for my case where I'm using make to build data files from other data.
While tab-completion is super-helpful to me, I like this idea of defining a list
target in your Makefile to list other targets. See this StackOverflow answer for a recipe on how to do that. You'll notice that the use of make -qp
and awk
together is very similar to the bash completion script.