I recently found myself hacking on a Makefile (the GNU kind, not the BSD kind) that made heavy use of pattern rules and grouped rules. These are concepts I haven’t spent a lot of time with so I wrote myself a small test Makefile to explore them in a little more detail.

#Pattern Rules

Pattern rules drive most of the usage of make as a tool for building software. make includes a bunch of pattern rules for many of the kinds of source files you’re likely to encounter. For example, it has implicit rules for building .o files out of .c files.

Here’s a simple pattern rule that generates text files:

%.txt:
	@echo "=> Making $*.txt"
	python -c 'print("$*!".title())' > $@

The % is tells make that this is a patten rule. It’s a placeholder for a string of non-whitespace characters, which make calls a stem. You can reference the stem in the body of the rule with the automatic variable $*.

This particular rule creates a text file with a particular stem by echoing a string to a file with this little Python snippet.

Here’s a slightly more complex pattern rule:

%.x : %.a
	@echo "=> Making X file from $< with pattern rule"
	echo "[X]" > $*.x
	cat "$<" >> $*.x

The major difference with this rule is that it has a prerequisite (the part after the colon) that also has a %. So, this rule defines how to build a .x file from a .a file with the same stem.

Later on, if I write a rule like this:

zip.a : florp.txt
	@echo "=> Making $@ with $<"
	cat $< > $@

make will understand that it first needs to generate florp.txt with the pattern rule for %.txt. Then it can execute this rule to build zip.a.

#Grouped Rules

Grouped rules are another feature of make that lets you specify more than one output for a given rule. If you’re writing C family languages, this is useful for generating a header and source file pair, and making sure that they get updated together.

zip.x zip.y &: zip.a
	@echo "=> Making XY files from $< with explicit rule"
	echo "[X]" > zip.x
	cat "$<" >> zip.x
	echo "[Y]" > zip.y
	cat "$<" >> zip.y

Generally when you write a rule with more than one output, make understands that each of the ouput files is built separately with the same rule. However, a rule with a &: separator indicates that the outputs are built from a single invocation of the rule. make will rebuild the rule if any of the outputs is out-of-date.

#Altogether Now

You can combine pattern rules and group rules into a single rule too. This rule creates a pattern for building a pair of .x and .y files from a .a file with a particular stem.

%.x %.y : %.a
	@echo "=> Making XY files from $< with pattern rule"
	echo "[X]" > $*.x
	cat "$<" >> $*.x
	echo "[Y]" > $*.y
	cat "$<" >> $*.y

These rules are always treated as a group. It doesn’t matter if you use : or &:.

#Rule Precedence

The way make decides which rule to use to produce a file are a little subtle, especially when you combine pattern rules and explicit rules. In general an explicit rule should take precedence over a pattern. In my Makefile, the rule that explicitly builds zip.x and zip.y will win over the pattern for %.x and %.y. When multiple pattern rules match a target, the rules are more complex.


Download the full Makefile.