blog: Make Pattern Rules
This commit is contained in:
parent
f6955df4f9
commit
cc98666b2d
2 changed files with 194 additions and 0 deletions
67
content/blog/2024/make-pattern-rules/Makefile
Normal file
67
content/blog/2024/make-pattern-rules/Makefile
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
# A Makefile that explores pattern and grouped rules. This Makefile expects the
|
||||||
|
# GNU version of Make.
|
||||||
|
|
||||||
|
# Relevant docs:
|
||||||
|
# https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html
|
||||||
|
# https://www.gnu.org/software/make/manual/html_node/Pattern-Match.html
|
||||||
|
# https://www.gnu.org/software/make/manual/html_node/Pattern-Intro.html
|
||||||
|
# https://www.gnu.org/software/make/manual/html_node/Multiple-Targets.html
|
||||||
|
|
||||||
|
|
||||||
|
NAMES=zip zap
|
||||||
|
|
||||||
|
.PHONY: all clean
|
||||||
|
|
||||||
|
all: $(NAMES:%=%.x) $(NAMES:%=%.y)
|
||||||
|
|
||||||
|
|
||||||
|
# Here's a simple pattern rule that generates a .txt file from a name. In this
|
||||||
|
# rule % represents the name (the 'stem' in Makefile lingo) in the targets and
|
||||||
|
# prerequisites, and $* represents the stem in the recipe.
|
||||||
|
|
||||||
|
%.txt:
|
||||||
|
@echo "=> Making $*.txt"
|
||||||
|
python -c 'print("$*!".title())' > "$@"
|
||||||
|
|
||||||
|
|
||||||
|
# Here are a couple explicit rules that generate a single file given some input
|
||||||
|
# files. Note the input files (prerequisites) will be generated with the pattern
|
||||||
|
# rule above.
|
||||||
|
|
||||||
|
zip.a : florp.txt
|
||||||
|
@echo "=> Making $@ with $<"
|
||||||
|
cat "$<" > "$@"
|
||||||
|
|
||||||
|
zap.a : bloop.txt
|
||||||
|
@echo "=> Making $@ with $<"
|
||||||
|
cat "$<" > "$@"
|
||||||
|
|
||||||
|
|
||||||
|
# This is a Grouped rule, marked with &:. It indicates that the two target files
|
||||||
|
# are produced together in one invocation of the rule, rather than two separate
|
||||||
|
# files produced by the same rule.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
# Pattern rules with multiple targets are always Grouped, so you don't need the
|
||||||
|
# &: separator.
|
||||||
|
|
||||||
|
%.x %.y : %.a
|
||||||
|
@echo "=> Making XY files from $< with pattern rule"
|
||||||
|
echo "[X]" > $*.x
|
||||||
|
cat "$<" >> $*.x
|
||||||
|
echo "[Y]" > $*.y
|
||||||
|
cat "$<" >> $*.y
|
||||||
|
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f bloop.txt florp.txt
|
||||||
|
rm -f $(NAMES:%=%.a)
|
||||||
|
rm -f $(NAMES:%=%.x)
|
||||||
|
rm -f $(NAMES:%=%.y)
|
127
content/blog/2024/make-pattern-rules/index.md
Normal file
127
content/blog/2024/make-pattern-rules/index.md
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
---
|
||||||
|
title: "Makefile Pattern Rules"
|
||||||
|
date: 2024-12-04T14:26:57-08:00
|
||||||
|
publishDate: 2024-12-05
|
||||||
|
resources:
|
||||||
|
- name: makefile
|
||||||
|
src: Makefile
|
||||||
|
tags:
|
||||||
|
- TIL
|
||||||
|
- Make
|
||||||
|
- Software
|
||||||
|
---
|
||||||
|
|
||||||
|
I recently found myself hacking on a Makefile (the GNU kind, not the [BSD
|
||||||
|
kind][bsdmake]) 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][patterns] 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:
|
||||||
|
|
||||||
|
```make
|
||||||
|
%.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][auto]
|
||||||
|
`$*`.
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```make
|
||||||
|
%.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:
|
||||||
|
|
||||||
|
```make
|
||||||
|
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][multiples] 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.
|
||||||
|
|
||||||
|
```make
|
||||||
|
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.
|
||||||
|
|
||||||
|
```make
|
||||||
|
%.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][matching].
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Download the full [Makefile]({{< page/resource-ref makefile >}}).
|
||||||
|
|
||||||
|
[bsdmake]: {{< ref "blog/2024/bsd-make" >}}
|
||||||
|
[auto]: https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html
|
||||||
|
[patterns]: https://www.gnu.org/software/make/manual/html_node/Pattern-Intro.html
|
||||||
|
[multiples]: https://www.gnu.org/software/make/manual/html_node/Multiple-Targets.html
|
||||||
|
[matching]: https://www.gnu.org/software/make/manual/html_node/Pattern-Match.html
|
Loading…
Add table
Add a link
Reference in a new issue